surface: the fitted extension path is deleted; the exact extension is the only one

Deleted: Surface/Plane/OffsetSurface/BSplineSurface create_extended_surface(_dir), with
create_extended_surface_dispatched, extend_side and MAX_EXTEND_HALVINGS/DOUBLINGS
(CreateExtendedSurface on every kind), srf_adv::p1::extend_by_dist (N_SrfExtendByDist) and
srf_adv::p2's ST_surxsdr (extend_by_dist_at_depth/_in_place, SrfContinuity,
MAX_EXTEND_DEPTH). Their only callers were each other: every caller extends through
surface::extend since the switch. remove_knot_conditional and extend_by_param_dist stay.

difftest: their ops are removed with their FFI ops, C++ shim cases, fixtures and fuzz
corpus entries (nlib.srf_adv_p1/p2, surface.surface_p2, surface.plane_p1,
nurbs.b_spline_surface_n_lib_p1, surface.surf_of_extrusion_p2, surface.offset_surface; each
unit passes in full; removed shim op numbers are left unused).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NvoxKMbMFSQU8tzRtoJhtT
This commit is contained in:
jyviko
2026-11-04 21:06:14 -08:00
committed by jyviko
parent 5797046b67
commit a5de86aaf8
45 changed files with 133 additions and 13192 deletions
+23 -116
View File
@@ -1,6 +1,6 @@
//! Runner for unit `nlib.srf_adv_p1`: NLib surface extension by a distance, offset
//! second-derivative bounds, unbounded evaluation, skinning along a spine and
//! cross-boundary derivative strips from a vector field.
//! Runner for unit `nlib.srf_adv_p1`: NLib offset second-derivative bounds, unbounded
//! evaluation, skinning along a spine and cross-boundary derivative strips from a
//! vector field.
//!
//! C++: `solidcore_ffi::units::nlib_srf_adv_p1` (upstream
//! `source/SMLib/src/NL_SrfAdv.cpp`). Rust: `solidcore::nurbs::srf_adv::p1`.
@@ -10,7 +10,7 @@
//! number, `1000` for upstream's bare error flag; `-1` on the Rust side for any other
//! error, which never matches), then on success the results: the surface
//! (`dims = [n, m, p, q, r, s]`, `net` x y z w row-major, `u`, `v`) for
//! `extend_by_dist`, `skin_spine` (followed by every profile after the call,
//! `skin_spine` (followed by every profile after the call,
//! `c<i>dims = [n, p, m]`, `c<i>net`, `c<i>u`, since upstream modifies them in place)
//! and `vector_field`; `max = [Muu, Muv, Mvv]` for `max_deriv`. `unbounded` runs a
//! sequence of evaluations sharing the extension (`esur` / `gflg` upstream): per
@@ -27,11 +27,10 @@ use serde::{Deserialize, Serialize};
use serde_json::Value;
use solidcore::math::Vec3;
use solidcore::nurbs::crv_adv::p1::{CurveEnd, BARE_FLAG_REASON};
use solidcore::nurbs::crv_adv::p1::BARE_FLAG_REASON;
use solidcore::nurbs::crv_basic::{Boundary, Curve};
use solidcore::nurbs::math::{CPoint, NOW, NOZ};
use solidcore::nurbs::srf_adv::p1 as adv;
use solidcore::nurbs::srf_adv::p2::SrfContinuity;
use solidcore::nurbs::srf_adv::p3::ExtendContinuity;
use solidcore::nurbs::srf_basic::{Direction, Surface};
use solidcore::{Result as SolidResult, SolidError};
@@ -93,15 +92,6 @@ enum CrvIn {
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "op", rename_all = "snake_case")]
enum Inputs {
ExtendByDist {
sur: SrfIn,
dist: f64,
dir: i32,
end: i32,
cont: i32,
#[serde(default)]
inplace: bool,
},
MaxDeriv {
sur: SrfIn,
d: f64,
@@ -323,24 +313,6 @@ fn to_output(i: &Inputs, flat: &[f64]) -> Result<Output, HarnessError> {
fn cpp_input(i: &Inputs) -> Result<(Op, Vec<f64>), HarnessError> {
let mut o = Vec::new();
let op = match i {
Inputs::ExtendByDist {
sur,
dist,
dir,
end,
cont,
inplace,
} => {
encode(&resolve(sur)?, &mut o)?;
o.extend([
*dist,
f64::from(*dir),
f64::from(*end),
f64::from(*cont),
flag(*inplace),
]);
Op::ExtendByDist
}
Inputs::MaxDeriv { sur, d } => {
encode(&resolve(sur)?, &mut o)?;
o.push(*d);
@@ -400,21 +372,7 @@ const BARE_FLAG: f64 = 1000.0;
fn cpp(i: &Inputs) -> Result<Output, HarnessError> {
let (op, input) = cpp_input(i)?;
match ffi::eval(op, &input) {
Ok(mut flat) => {
// Past its recursion limit `ST_surxsdr` returns a bare error flag and
// `NL_ERROR` still holds whatever an earlier, recovered error left there
// (plan/findings.md, nlib.srf_adv_p2). When the port reports the recursion
// limit, an upstream failure is compared as the bare flag.
if matches!(i, Inputs::ExtendByDist { .. })
&& flat.first().is_some_and(|&st| st != 0.0)
&& rust_flat(i)?.first() == Some(&BARE_FLAG)
{
if let Some(st) = flat.first_mut() {
*st = BARE_FLAG;
}
}
to_output(i, &flat)
}
Ok(flat) => to_output(i, &flat),
Err(FfiError::InvalidArgument(m)) => Err(harness(format!("ffi rejected: {m}"))),
Err(e) => Err(harness(format!("shim failure: {e}"))),
}
@@ -429,11 +387,6 @@ fn status<T>(r: &SolidResult<T>) -> f64 {
Ok(_) => 0.0,
Err(SolidError::Nlib { code, .. }) => f64::from(code.code()),
Err(SolidError::InvalidInput { reason, .. } | SolidError::PortInvariant { reason, .. }) if *reason == BARE_FLAG_REASON => BARE_FLAG,
Err(SolidError::NotConverged { operation, .. })
if operation.ends_with("srf_adv::p2::extend_by_dist_at_depth") =>
{
BARE_FLAG
}
Err(_) => -1.0,
}
}
@@ -441,23 +394,6 @@ fn status<T>(r: &SolidResult<T>) -> f64 {
fn rust_flat(i: &Inputs) -> Result<Vec<f64>, HarnessError> {
let mut o = Vec::new();
match i {
Inputs::ExtendByDist {
sur,
dist,
dir,
end,
cont,
..
} => {
let s = build(&resolve(sur)?)?;
let end = CurveEnd::from_flag(*end).ok_or_else(|| harness("bad end flag"))?;
let cont = SrfContinuity::from_flag(*cont).ok_or_else(|| harness("bad cont flag"))?;
let r = adv::extend_by_dist(&s, *dist, direction(*dir)?, end, cont);
o.push(status(&r));
if let Ok(q) = r {
dump(&mut o, &q);
}
}
Inputs::MaxDeriv { sur, d } => {
let s = build(&resolve(sur)?)?;
let r = adv::offset_get_max_deriv(&s, *d);
@@ -828,32 +764,6 @@ fn cont_flag(rng: &mut Rng) -> i32 {
*rng.choose(&[2, 3, 4, 6]).unwrap_or(&2)
}
fn gen_extend(rng: &mut Rng) -> Inputs {
let s = gen_srf(rng);
let dist = match rng.below(10) {
0 => 1e-6,
1 => rng.uniform(3.0, 20.0),
_ => rng.uniform(0.01, 3.0),
};
let dir = 1 + rng.below(2) as i32;
let mut cont = cont_flag(rng);
// A degree-1 extension direction makes upstream's reflection-plane fallback read
// past its scratch row (plan/findings.md, nlib.srf_adv_p2); the port reports
// `InvalidInput` there. Keep degree-1 extensions to C-max.
let ext_deg = if dir == 1 { s.p } else { s.q };
if ext_deg == 1 && cont != 4 {
cont = 4;
}
Inputs::ExtendByDist {
sur: SrfIn::Inline(s),
dist,
dir,
end: 1 + rng.below(2) as i32,
cont,
inplace: rng.chance(0.3),
}
}
fn unbounded_param(rng: &mut Rng, k: &[f64]) -> f64 {
let a = k.first().copied().unwrap_or(0.0);
let b = k.last().copied().unwrap_or(1.0);
@@ -1071,17 +981,16 @@ fn gen_vector_field(rng: &mut Rng) -> Inputs {
}
fn gen_case(rng: &mut Rng) -> Inputs {
match rng.below(10) {
0 | 1 => gen_extend(rng),
2 => Inputs::MaxDeriv {
match rng.below(8) {
0 => Inputs::MaxDeriv {
sur: SrfIn::Inline(gen_srf(rng)),
d: match rng.below(4) {
0 => 0.0,
_ => rng.uniform(-3.0, 3.0),
},
},
3..=5 => gen_unbounded(rng),
6..=8 => gen_skin_spine(rng),
1..=3 => gen_unbounded(rng),
4..=6 => gen_skin_spine(rng),
_ => gen_vector_field(rng),
}
}
@@ -1121,25 +1030,23 @@ mod tests {
/// closed spine skins with end and all derivatives.
#[test]
fn generated_cases_cover_outcomes() {
// [extend ok, extend err, maxd ok, unb ok, unb err, unb wrapped, unb ext,
// spine none ok, spine ends ok, spine all ok, spine err, spine rational ok,
// vfield ok, vfield err]
let mut stats = [0usize; 14];
// [maxd ok, unb ok, unb err, unb wrapped, unb ext, spine none ok, spine ends ok,
// spine all ok, spine err, spine rational ok, vfield ok, vfield err]
let mut stats = [0usize; 12];
for seed in 0..3000u64 {
let mut rng = Rng::new(seed);
let i = gen_case(&mut rng);
let flat = rust_flat(&i).unwrap();
let ok = flat.first() == Some(&0.0);
match &i {
Inputs::ExtendByDist { .. } => stats[usize::from(!ok)] += 1,
Inputs::MaxDeriv { .. } => stats[2] += usize::from(ok),
Inputs::MaxDeriv { .. } => stats[0] += usize::from(ok),
Inputs::Unbounded { .. } => {
let out = to_output(&i, &flat).unwrap();
let all_ok = out.values.contains_key("has_ext");
stats[3 + usize::from(!all_ok)] += 1;
stats[1 + usize::from(!all_ok)] += 1;
match out.values.get("gflg").and_then(|g| g.first()) {
Some(&g) if g > 0.0 => stats[5] += 1,
Some(_) => stats[6] += 1,
Some(&g) if g > 0.0 => stats[3] += 1,
Some(_) => stats[4] += 1,
None => {}
}
}
@@ -1148,19 +1055,19 @@ mod tests {
} => {
if ok {
stats[match derivs {
1 => 7,
2 => 8,
_ => 9,
1 => 5,
2 => 6,
_ => 7,
}] += 1;
let c0 = resolve_crv(&curves[0]).unwrap();
if c0.cpts[0][3] != NOW && *derivs != 1 {
stats[11] += 1;
stats[9] += 1;
}
} else {
stats[10] += 1;
stats[8] += 1;
}
}
Inputs::VectorField { .. } => stats[12 + usize::from(!ok)] += 1,
Inputs::VectorField { .. } => stats[10 + usize::from(!ok)] += 1,
}
}
eprintln!("{stats:?}");
+32 -252
View File
@@ -1,41 +1,30 @@
//! Runner for unit `nlib.srf_adv_p2`: NLib surface extension by a distance
//! (`ST_surxsdr`, the recursive core of `N_SrfExtendByDist`) and the derivatives of
//! the unit surface normal (`N_SrfEvalPtNormalDeriv`).
//! Runner for unit `nlib.srf_adv_p2`: the derivatives of the unit surface normal
//! (`N_SrfEvalPtNormalDeriv`).
//!
//! C++: `solidcore_ffi::units::nlib_srf_adv_p2` (upstream
//! `source/SMLib/src/NL_SrfAdv.cpp`). Rust: `solidcore::nurbs::srf_adv::p2`.
//!
//! Each case runs one *op* (`inputs.op`). Both sides produce the same flat record of
//! doubles, split into keys by [`to_output`]: `status` (`0` ok, else the `NL_*_ERR`
//! number; `1000` for upstream's bare error flag past the recursion limit, the Rust
//! `NotConverged` of the extension; `-1` on the Rust side for any other non-NLib
//! error, which never matches), then on success the results: the extended
//! surface for `extend` (`dims = [n, m, p, q, r, s]`, the control points `net`, x y z
//! w row-major, the knots `u`, `v`), and `nd` (`N_u, N_v, N_uu, N_uv, N_vv`, xyz each)
//! for `normal_deriv`. Upstream's in-place mode (`inplace`, the way every upstream
//! caller uses `N_SrfExtendByDist`) is compared against the same Rust call.
//! number; `-1` on the Rust side for any non-NLib error, which never matches), then
//! on success `nd` (`N_u, N_v, N_uu, N_uv, N_vv`, xyz each) for `normal_deriv`.
//!
//! Past its recursion limit upstream returns a bare error flag with a stale
//! `NL_ERROR` number (left by an earlier recovered error or cleared by a callee; see
//! `plan/findings.md`). When the Rust side reports the recursion limit, a failing C++
//! side is therefore reported as the bare flag (`1000`) instead of that number; a
//! C++ success still fails the case.
//! The surface extension by a distance (`extend`, `ST_surxsdr`) was removed with the
//! port's fitted extension.
//!
//! A surface input is inline (`net` rows of `[x, y, z, w]`, degrees, knots) or an
//! upstream `.sms` file (`{"sms": {"$upstream_file": "Surface.sms"}}`). Inputs must be
//! valid clamped surfaces (the FFI wrapper rejects anything else). Generated surfaces
//! are random nets or smooth grid-like nets with noise; extension distances are drawn
//! relative to the net size (small to several times the size, which exercises the
//! recursion). Every run has at least [`MIN_GENERATED`] generated cases.
//! are random nets or smooth grid-like nets with noise. Every run has at least
//! [`MIN_GENERATED`] generated cases.
use serde::{Deserialize, Serialize};
use serde_json::Value;
use solidcore::nurbs::crv_adv::p1::CurveEnd;
use solidcore::nurbs::knots::Side;
use solidcore::nurbs::math::{CPoint, NOW, NOZ};
use solidcore::nurbs::srf_adv::p2::{self as adv, SrfContinuity};
use solidcore::nurbs::srf_basic::{Direction, Surface};
use solidcore::nurbs::srf_adv::p2 as adv;
use solidcore::nurbs::srf_basic::Surface;
use solidcore::{Result as SolidResult, SolidError};
use solidcore_ffi::units::nlib_srf_adv_p2::{self as ffi, Op};
use solidcore_ffi::FfiError;
@@ -76,17 +65,6 @@ enum SrfIn {
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "op", rename_all = "snake_case")]
enum Inputs {
Extend {
sur: SrfIn,
dist: f64,
dir: i32,
end: i32,
cont: i32,
#[serde(default)]
depth: usize,
#[serde(default)]
inplace: bool,
},
NormalDeriv {
sur: SrfIn,
u: f64,
@@ -99,7 +77,7 @@ enum Inputs {
impl Inputs {
fn sur(&self) -> &SrfIn {
match self {
Inputs::Extend { sur, .. } | Inputs::NormalDeriv { sur, .. } => sur,
Inputs::NormalDeriv { sur, .. } => sur,
}
}
}
@@ -182,33 +160,6 @@ fn take<'a>(flat: &'a [f64], at: &mut usize, k: usize) -> Result<&'a [f64], Harn
Ok(part)
}
fn count(x: f64) -> Result<usize, HarnessError> {
if x.fract() != 0.0 || !(0.0..=1e7).contains(&x) {
return Err(harness(format!("bad count {x} in record")));
}
Ok(x as usize)
}
/// Splits a surface dump starting at `at` into `<pfx>dims`, `<pfx>net`, `<pfx>u`,
/// `<pfx>v`.
fn split_srf(
flat: &[f64],
at: &mut usize,
pfx: &str,
mut out: Output,
) -> Result<Output, HarnessError> {
let dims = take(flat, at, 6)?;
let dim = |k: usize| count(dims.get(k).copied().unwrap_or(f64::NAN));
let (n, m, r, s) = (dim(0)?, dim(1)?, dim(4)?, dim(5)?);
out = out.values(&format!("{pfx}dims"), dims.to_vec());
let net = take(flat, at, 4 * (n + 1) * (m + 1))?;
out = out.values(&format!("{pfx}net"), net.to_vec());
let u = take(flat, at, r + 1)?;
out = out.values(&format!("{pfx}u"), u.to_vec());
let v = take(flat, at, s + 1)?;
Ok(out.values(&format!("{pfx}v"), v.to_vec()))
}
fn to_output(i: &Inputs, flat: &[f64]) -> Result<Output, HarnessError> {
let mut out = Output::new();
let mut at = 0usize;
@@ -217,7 +168,6 @@ fn to_output(i: &Inputs, flat: &[f64]) -> Result<Output, HarnessError> {
out = out.values("status", status);
if ok {
match i {
Inputs::Extend { .. } => out = split_srf(flat, &mut at, "", out)?,
Inputs::NormalDeriv { .. } => out = out.values("nd", take(flat, &mut at, 15)?.to_vec()),
}
}
@@ -236,25 +186,6 @@ fn cpp_input(i: &Inputs) -> Result<(Op, Vec<f64>), HarnessError> {
let mut o = Vec::new();
encode(&resolve(i.sur())?, &mut o)?;
let op = match i {
Inputs::Extend {
dist,
dir,
end,
cont,
depth,
inplace,
..
} => {
o.extend([
*dist,
f64::from(*dir),
f64::from(*end),
f64::from(*cont),
*depth as f64,
if *inplace { 1.0 } else { 0.0 },
]);
Op::ExtendByDist
}
Inputs::NormalDeriv { u, v, ufl, vfl, .. } => {
o.extend([*u, *v, f64::from(*ufl), f64::from(*vfl)]);
Op::NormalDeriv
@@ -263,28 +194,10 @@ fn cpp_input(i: &Inputs) -> Result<(Op, Vec<f64>), HarnessError> {
Ok((op, o))
}
/// Status of the upstream bare error flag (no `NL_ERROR` number of its own).
const BARE_FLAG: f64 = 1000.0;
fn cpp(i: &Inputs) -> Result<Output, HarnessError> {
let (op, input) = cpp_input(i)?;
match ffi::eval(op, &input) {
Ok(mut flat) => {
// Past the recursion limit `ST_surxsdr` returns a bare error flag and
// `NL_ERROR` still holds whatever an earlier, recovered error (or a
// callee's `N_ErrClear`) left there: that number is not a result of the
// routine. When the port reports the recursion limit, an upstream failure
// is compared as the bare flag.
if matches!(i, Inputs::Extend { .. })
&& flat.first().is_some_and(|&st| st != 0.0)
&& rust_flat(i)?.first() == Some(&BARE_FLAG)
{
if let Some(st) = flat.first_mut() {
*st = BARE_FLAG;
}
}
to_output(i, &flat)
}
Ok(flat) => to_output(i, &flat),
Err(FfiError::InvalidArgument(m)) => Err(harness(format!("ffi rejected: {m}"))),
Err(e) => Err(harness(format!("shim failure: {e}"))),
}
@@ -292,34 +205,15 @@ fn cpp(i: &Inputs) -> Result<Output, HarnessError> {
// ---- Rust side --------------------------------------------------------------------
/// `0` for `Ok`, the `NL_*_ERR` number for an NLib error, `1000` for the recursion
/// limit of the extension (upstream's bare error flag), `-1` otherwise.
/// `0` for `Ok`, the `NL_*_ERR` number for an NLib error, `-1` otherwise.
fn status<T>(r: &SolidResult<T>) -> f64 {
match r {
Ok(_) => 0.0,
Err(SolidError::Nlib { code, .. }) => f64::from(code.code()),
Err(SolidError::NotConverged { operation, .. })
if operation.ends_with("srf_adv::p2::extend_by_dist_at_depth") =>
{
BARE_FLAG
}
Err(_) => -1.0,
}
}
fn dump(o: &mut Vec<f64>, s: &Surface) {
let (n, m, r, ss) = s.array_sizes();
let (p, q) = s.degrees();
o.extend([n, m, p, q, r, ss].map(|k| k as f64));
for row in s.rows() {
for c in row {
o.extend_from_slice(&c.to_array());
}
}
o.extend_from_slice(s.u_knots());
o.extend_from_slice(s.v_knots());
}
fn side(f: i32) -> Result<Side, HarnessError> {
match f {
1 => Ok(Side::Left),
@@ -332,24 +226,6 @@ fn rust_flat(i: &Inputs) -> Result<Vec<f64>, HarnessError> {
let s = build(&resolve(i.sur())?)?;
let mut o = Vec::new();
match i {
Inputs::Extend {
dist,
dir,
end,
cont,
depth,
..
} => {
let dir = Direction::from_flag(*dir).ok_or_else(|| harness("bad direction flag"))?;
let end = CurveEnd::from_flag(*end).ok_or_else(|| harness("bad end flag"))?;
let cont =
SrfContinuity::from_flag(*cont).ok_or_else(|| harness("bad continuity flag"))?;
let r = adv::extend_by_dist_at_depth(&s, *dist, dir, end, cont, *depth);
o.push(status(&r));
if let Ok(q) = r {
dump(&mut o, &q);
}
}
Inputs::NormalDeriv { u, v, ufl, vfl, .. } => {
let r = adv::eval_pt_normal_deriv(&s, *u, *v, side(*ufl)?, side(*vfl)?);
o.push(status(&r));
@@ -584,89 +460,22 @@ fn gen_smooth(rng: &mut Rng) -> InlineSrf {
InlineSrf { net, p, q, u, v }
}
/// Size of a net: the largest coordinate range of its Euclidean points.
fn net_size(s: &InlineSrf) -> f64 {
let pts: Vec<[f64; 3]> = s
.net
.iter()
.flatten()
.map(|c| {
let w = if c[3] == NOW { 1.0 } else { c[3] };
let z = if c[2] == NOZ { 0.0 } else { c[2] / w };
[c[0] / w, c[1] / w, z]
})
.collect();
(0..3)
.map(|k| {
let lo = pts
.iter()
.filter_map(|p| p.get(k).copied())
.fold(f64::INFINITY, f64::min);
let hi = pts
.iter()
.filter_map(|p| p.get(k).copied())
.fold(f64::NEG_INFINITY, f64::max);
hi - lo
})
.fold(0.0, f64::max)
}
fn gen_cont(rng: &mut Rng) -> i32 {
match rng.below(4) {
0 => 2,
1 => 3,
2 => 4,
_ => 6,
}
}
fn gen_case(rng: &mut Rng) -> Inputs {
let s = if rng.chance(0.7) {
gen_smooth(rng)
} else {
gen_srf(rng)
};
if rng.chance(0.25) {
let (ufl, vfl) = (1 + rng.below(2) as i32, 1 + rng.below(2) as i32);
let (ou, ov) = (rng.chance(0.1), rng.chance(0.1));
let u = param(rng, &s.u, ou);
let v = param(rng, &s.v, ov);
return Inputs::NormalDeriv {
sur: SrfIn::Inline(s),
u,
v,
ufl,
vfl,
};
}
let size = net_size(&s).max(1e-6);
let dist = size
* match rng.below(20) {
0 => 0.0,
1 => -0.1,
2 | 3 => rng.uniform(1.0, 4.0),
4 => rng.uniform(1e-4, 1e-2),
_ => rng.uniform(0.02, 0.8),
};
let depth = if rng.chance(0.05) {
rng.below(12) as usize
} else {
0
};
// A zero distance makes upstream's G2 iso-curve extension (used for G1 and G2)
// loop forever; keep it to Cmax / G1R.
let mut cont = gen_cont(rng);
if dist == 0.0 && cont < 4 {
cont = 4;
}
Inputs::Extend {
let (ufl, vfl) = (1 + rng.below(2) as i32, 1 + rng.below(2) as i32);
let (ou, ov) = (rng.chance(0.1), rng.chance(0.1));
let u = param(rng, &s.u, ou);
let v = param(rng, &s.v, ov);
Inputs::NormalDeriv {
sur: SrfIn::Inline(s),
dist,
dir: 1 + rng.below(2) as i32,
end: 1 + rng.below(2) as i32,
cont,
depth,
inplace: rng.chance(0.3),
u,
v,
ufl,
vfl,
}
}
@@ -700,46 +509,17 @@ impl UnitDiff for Runner {
mod tests {
use super::*;
/// The generator reaches successful and failing extensions of every continuity
/// and both normal-derivative outcomes.
///
/// Deep-but-bounded extension recursion (`ST_curxcdr` fails only past 50 levels,
/// nested in `ST_surxsdr`'s 10) must fit the thread stack: generated cases reach
/// the recursion limits, and a debug build's frames are much larger than
/// upstream's, so the loop runs on a dedicated thread with a raised stack (same
/// pattern as the reader fuzz `run_guarded`); the release difftest profile needs
/// the default stack.
/// The generator reaches successful normal derivatives.
#[test]
fn generated_cases_cover_outcomes() {
const STACK: usize = 64 * 1024 * 1024;
let h = std::thread::Builder::new()
.stack_size(STACK)
.spawn(|| {
let mut stats = [0usize; 6];
for seed in 0..1500u64 {
let mut rng = Rng::new(seed);
let i = gen_case(&mut rng);
let flat = rust_flat(&i).unwrap();
let ok = flat.first() == Some(&0.0);
match (&i, ok) {
(Inputs::Extend { cont, .. }, true) => {
stats[match cont {
2 => 0,
3 => 1,
4 => 2,
_ => 3,
}] += 1;
}
(Inputs::Extend { .. }, false) => stats[4] += 1,
(Inputs::NormalDeriv { .. }, true) => stats[5] += 1,
(Inputs::NormalDeriv { .. }, false) => {}
}
}
stats
})
.expect("spawn");
let stats = h.join().unwrap();
eprintln!("{stats:?}");
assert!(stats.iter().all(|&k| k >= 10), "{stats:?}");
let mut ok = 0usize;
for seed in 0..1500u64 {
let mut rng = Rng::new(seed);
let i = gen_case(&mut rng);
let flat = rust_flat(&i).unwrap();
ok += usize::from(flat.first() == Some(&0.0));
}
eprintln!("{ok}");
assert!(ok >= 10, "{ok}");
}
}
@@ -19,15 +19,14 @@
//! derivatives, knots.
//!
//! Cases whose Rust side reaches an unported `SmSurface` method are deferred (none
//! now: `CreateExtendedSurface` and `sm_CreateNonRationalCurve`'s `DropCurve`, unit
//! `intersect.surface_drop_lib_p3`, are ported). Every run has at least
//! now: `sm_CreateNonRationalCurve`'s `DropCurve`, unit
//! `intersect.surface_drop_lib_p3`, is ported). Every run has at least
//! [`MIN_GENERATED`] generated cases.
use serde::{Deserialize, Serialize};
use serde_json::Value;
use solidcore::geom::extent2d::Extent2d;
use solidcore::geom::geom_utility::p2::ContinuityType;
use solidcore::geom::vector2d::Point2;
use solidcore::geom::vector3d::Vec3;
use solidcore::nurbs::b_spline_curve::BSplineCurve;
@@ -123,12 +122,6 @@ enum OpIn {
#[serde(default)]
param: u8,
},
Extend {
dir_overload: bool,
dir: u8,
dist: f64,
cont: u8,
},
Join {
other: SrfIn,
dir: u8,
@@ -229,7 +222,6 @@ impl OpIn {
fn ffi(&self) -> Op {
match self {
OpIn::Offset { .. } => Op::Offset,
OpIn::Extend { .. } => Op::Extend,
OpIn::Join { .. } => Op::Join,
OpIn::ExtEval { .. } => Op::ExtEval,
OpIn::Reparam { .. } => Op::Reparam,
@@ -254,7 +246,6 @@ impl OpIn {
matches!(
self,
OpIn::Offset { .. }
| OpIn::Extend { .. }
| OpIn::Join { .. }
| OpIn::Coons { .. }
| OpIn::Ruled { .. }
@@ -388,17 +379,6 @@ fn cpp_input(i: &Inputs) -> Result<Vec<f64>, HarnessError> {
}
match &i.op {
OpIn::Offset { dist, tol, param } => o.extend([*dist, *tol, f64::from(*param)]),
OpIn::Extend {
dir_overload,
dir,
dist,
cont,
} => o.extend([
flag(*dir_overload),
f64::from(*dir),
*dist,
f64::from(*cont),
]),
OpIn::Join { other, dir, tol } => {
push_srf(&mut o, &srf(other)?)?;
o.extend([f64::from(*dir), *tol]);
@@ -764,26 +744,6 @@ fn param_of(v: u8) -> Result<SurfParamType, HarnessError> {
})
}
fn continuity_of(v: u8) -> Result<ContinuityType, HarnessError> {
Ok(match v {
0 => ContinuityType::Undefined,
1 => ContinuityType::Discontinuous,
2 => ContinuityType::C0,
3 => ContinuityType::G1,
4 => ContinuityType::G1R,
5 => ContinuityType::G1G2,
6 => ContinuityType::G1G2G3,
7 => ContinuityType::C1,
8 => ContinuityType::C1G2,
9 => ContinuityType::C1G2G3,
10 => ContinuityType::C1C2,
11 => ContinuityType::C1C2G3,
12 => ContinuityType::C1C2C3,
13 => ContinuityType::CInfinity,
_ => return Err(harness(format!("bad SmContinuityType {v}"))),
})
}
fn offset_param_of(v: u8) -> Result<OffsetParamType, HarnessError> {
Ok(match v {
0 => OffsetParamType::Inherited,
@@ -860,7 +820,7 @@ fn fail(e: &SolidError) -> Fail {
}
/// Runs a member op; returns the flat record.
fn rust_member(k: &solidcore::kernel::Kernel, this: &mut BSplineSurface, op: &OpIn) -> Result<Result<Vec<f64>, Fail>, HarnessError> {
fn rust_member(this: &mut BSplineSurface, op: &OpIn) -> Result<Result<Vec<f64>, Fail>, HarnessError> {
let mut o = vec![0.0];
let new = |o: &mut Vec<f64>, r: solidcore::Result<BSplineSurface>| match r {
Ok(s) => {
@@ -875,20 +835,6 @@ fn rust_member(k: &solidcore::kernel::Kernel, this: &mut BSplineSurface, op: &Op
&mut o,
this.approximate_offset_surface(*dist, *tol, offset_param_of(*param)?),
),
OpIn::Extend {
dir_overload,
dir,
dist,
cont,
} => {
let cont = continuity_of(*cont)?;
let r = if *dir_overload {
this.create_extended_surface_dir(k, param_of(*dir)?, *dist, cont)
} else {
this.create_extended_surface(k, *dist, cont)
};
new(&mut o, r)
}
OpIn::Join { other, dir, tol } => {
let mut other = BSplineSurface::from_nurb(surface_of(&srf(other)?)?, false);
new(&mut o, this.join_surface(&mut other, param_of(*dir)?, *tol))
@@ -1179,11 +1125,11 @@ fn rust_static(op: &OpIn) -> Result<Result<Vec<f64>, Fail>, HarnessError> {
Ok(res.map(|()| o))
}
fn rust(k: &solidcore::kernel::Kernel, i: &Inputs) -> Result<Output, HarnessError> {
fn rust(i: &Inputs) -> Result<Output, HarnessError> {
let res = match &i.this {
Some(t) => {
let mut this = this_of(t)?;
rust_member(k, &mut this, &i.op)?
rust_member(&mut this, &i.op)?
}
None => rust_static(&i.op)?,
};
@@ -1711,12 +1657,6 @@ fn gen_member(rng: &mut Rng) -> Inputs {
tol: *rng.choose(&[1e-2, 3e-2]).unwrap_or(&1e-2),
param: rng.below(4) as u8,
},
1 => OpIn::Extend {
dir_overload: rng.chance(0.7),
dir: rng.below(9) as u8,
dist: rng.uniform(0.05, 1.0),
cont: *rng.choose(&[3, 4, 5, 13, 2]).unwrap_or(&3),
},
2 => {
let along_v = rng.chance(0.5);
let s_nr = InlineSrf {
@@ -1843,7 +1783,6 @@ impl UnitDiff for Runner {
}
fn run_rust(&self, case: &Case) -> Result<Output, HarnessError> {
let k = &solidcore::Kernel::default();
rust(k, &case.parse()?)
rust(&case.parse()?)
}
}
@@ -23,7 +23,6 @@ use solidcore::geom::assert_array::{AssertArray, ObjectKey};
use solidcore::geom::axis2_placement::Axis2Placement;
use solidcore::geom::extent1d::Extent1d;
use solidcore::geom::extent2d::Extent2d;
use solidcore::geom::geom_utility::p2::ContinuityType;
use solidcore::geom::vector2d::Point2;
use solidcore::geom::vector3d::Vec3;
use solidcore::kernel::Kernel;
@@ -206,15 +205,6 @@ enum StepIn {
CreateOffset {
d: f64,
},
Extend {
dist: f64,
cont: u8,
},
ExtendDir {
param: u8,
dist: f64,
cont: u8,
},
Copy,
Eq {
other: OffIn,
@@ -394,15 +384,6 @@ fn ffi_step(s: &StepIn) -> Result<ffi::Step, HarnessError> {
param: *param,
},
StepIn::CreateOffset { d } => F::CreateOffset { d: *d },
StepIn::Extend { dist, cont } => F::Extend {
dist: *dist,
cont: *cont,
},
StepIn::ExtendDir { param, dist, cont } => F::ExtendDir {
param: *param,
dist: *dist,
cont: *cont,
},
StepIn::Copy => F::Copy,
StepIn::Eq { other } => F::Eq {
other: Box::new(ffi_off(other)?),
@@ -467,27 +448,6 @@ fn param_of(v: u8) -> Result<SurfParamType, HarnessError> {
SurfParamType::from_code(u64::from(v)).ok_or_else(|| harness(format!("bad param {v}")))
}
fn continuity_of(v: u8) -> Result<ContinuityType, HarnessError> {
use ContinuityType as C;
Ok(match v {
0 => C::Undefined,
1 => C::Discontinuous,
2 => C::C0,
3 => C::G1,
4 => C::G1R,
5 => C::G1G2,
6 => C::G1G2G3,
7 => C::C1,
8 => C::C1G2,
9 => C::C1G2G3,
10 => C::C1C2,
11 => C::C1C2G3,
12 => C::C1C2C3,
13 => C::CInfinity,
_ => return Err(harness(format!("bad continuity {v}"))),
})
}
/// The `SmStatus` of a Rust error.
fn status(e: &SolidError) -> f64 {
match e {
@@ -834,18 +794,6 @@ fn run_step(
StepIn::CreateOffset { d } => {
push_res(o, s.create_offset_surface(k, *d), |o, r| put_result(k, o, &r))
}
StepIn::Extend { dist, cont } => {
let c = continuity_of(*cont)?;
push_res(o, s.create_extended_surface(k, *dist, c), |o, r| {
put_result(k, o, &r)
})
}
StepIn::ExtendDir { param, dist, cont } => {
let (p, c) = (param_of(*param)?, continuity_of(*cont)?);
push_res(o, s.create_extended_surface_dir(k, p, *dist, c), |o, r| {
put_result(k, o, &r)
})
}
StepIn::Copy => push_res(o, s.copy(k), |o, c| put_result(k, o, &Surface::from(c))),
StepIn::Eq { other } => match rust_off(k, other)? {
Ok(other) => {
@@ -1511,17 +1459,6 @@ fn gen_step(rng: &mut Rng, d: [f64; 4], scale: f64, size: f64, can_via: bool) ->
rng.uniform(-1.0, 1.0) * scale
},
},
// (`ffi::check` refuses `CreateExtendedSurface` in process, UB-107/HANG-6/HANG-22,
// so `gen_case` drops a script with one of these)
86..=87 => StepIn::Extend {
dist: rng.uniform(0.05, 0.4) * size,
cont: *rng.choose(&[3, 4, 5, 13]).unwrap_or(&3),
},
88 => StepIn::ExtendDir {
param: *rng.choose(&[0, 1, 3, 5, 6, 7, 8]).unwrap_or(&0),
dist: rng.uniform(0.05, 0.4) * size,
cont: *rng.choose(&[3, 4, 5, 13]).unwrap_or(&3),
},
89..=91 => StepIn::Copy,
92..=93 => StepIn::Eq {
other: gen_off(rng, scale, 2),
@@ -1591,10 +1528,7 @@ fn gen_case(rng: &mut Rng) -> Option<Inputs> {
bytes: Some(bytes),
});
}
let mut steps = gen_steps(rng, &off, d, scale, size);
if let Some(degree_1) = degree_1_directions(&k, &off) {
avoid_reflection_extensions(&mut steps, degree_1);
}
let steps = gen_steps(rng, &off, d, scale, size);
let fsteps = ffi_steps(&steps).ok()?;
ffi::check(&foff, &fsteps).ok()?;
// Domain/zero-offset guards depend on the actual C++ state after preceding
@@ -1693,74 +1627,6 @@ fn gen_assert_valid_case(rng: &mut Rng) -> Option<Inputs> {
Some(Inputs::Script { off, steps })
}
/// Which directions (`[u, v]`) of the B-spline NLib extends for this offset (its base,
/// looking through nested offsets, curve-bounded and STEP wrappers) have degree 1;
/// `None` when neither does.
fn degree_1_directions(k: &Kernel, off: &OffIn) -> Option<[bool; 2]> {
let Ok(Ok(base)) = rust_srf(k, &off.base) else {
return None;
};
let mut s = &base;
for _ in 0..=solidcore::surface::offset_surface::MAX_SURFACE_NESTING {
s = match s.kind() {
SurfaceKind::OffsetSurface(o) => o.base_surface(),
SurfaceKind::CurveBounded(c) => c.basis(),
SurfaceKind::Step(st) => st.surface()?,
SurfaceKind::BSpline(_)
| SurfaceKind::ProceduralBlend(_)
| SurfaceKind::BallPatch(_)
| SurfaceKind::Plane(_)
| SurfaceKind::Cone(_)
| SurfaceKind::Sphere(_)
| SurfaceKind::Torus(_)
| SurfaceKind::SurfOfRevolution(_)
| SurfaceKind::SurfOfExtrusion(_) => {
let d = [
s.degree(SurfParamType::U) == 1,
s.degree(SurfParamType::V) == 1,
];
return (d[0] || d[1]).then_some(d);
}
SurfaceKind::SrfInVolume(_) => return None,
};
}
None
}
/// UB-32 (`plan/upstream-issues.md`): a reflection extension (`G1`, `G1G2`) in a
/// degree-1 direction can make `ST_surxsdr`'s reflection-plane fallback read past its
/// scratch row; the port reports `InvalidInput` there. As in `nlib.srf_adv_p1`, keep
/// such extensions to C-max, only for the extensions that reach a degree-1 direction
/// (`degree_1` is `[u, v]` of the base; `SwapUV` exchanges them as the script runs).
/// (Applied after generation, so the random stream and the other cases are unchanged;
/// the evaluation-triggered extension is ruled and safe.)
fn avoid_reflection_extensions(steps: &mut [StepIn], degree_1: [bool; 2]) {
use SurfParamType as P;
let [mut du, mut dv] = degree_1;
for step in steps {
let (cont, reaches) = match step {
StepIn::Swap => {
core::mem::swap(&mut du, &mut dv);
continue;
}
StepIn::Extend { cont, .. } => (cont, du || dv),
StepIn::ExtendDir { param, cont, .. } => {
let reaches = match param_of(*param) {
Ok(P::U | P::UMin | P::UMax) => du,
Ok(P::V | P::VMin | P::VMax) => dv,
Ok(P::Neither) => false,
_ => du || dv,
};
(cont, reaches)
}
_ => continue,
};
if reaches && matches!(*cont, 3 | 5) {
*cont = 13;
}
}
}
/// Fills a fixture's placeholders from the base built on the Rust side: a `dom` of
/// `"NAT"` (the base's natural domain), `"SUB"` (its middle half) or `"OUT"` (a quarter
/// larger on every side); a `uv` of `"MID"`, `"VMIN"` or `"VMAX"` (the middle of the
@@ -24,7 +24,6 @@ use solidcore::curve::line::Line;
use solidcore::geom::axis2_placement::Axis2Placement;
use solidcore::geom::extent1d::Extent1d;
use solidcore::geom::extent2d::Extent2d;
use solidcore::geom::geom_utility::p2::ContinuityType;
use solidcore::geom::global_solver::SolverOperation;
use solidcore::geom::solution_array::{ObjectKind, SolutionArray, SolutionObject, SolutionType};
use solidcore::geom::vector2d::{Point2, Vec2};
@@ -224,12 +223,6 @@ enum OpIn {
v_dir: bool,
},
Swap,
Extend {
#[serde(default, skip_serializing_if = "Option::is_none")]
dir: Option<u8>,
dist: f64,
cont: u8,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -423,11 +416,6 @@ fn ffi_op(o: &OpIn) -> ffi::Op {
OpIn::Inherited { v } => ffi::Op::Inherited(*v),
OpIn::Reverse { v_dir } => ffi::Op::Reverse(*v_dir),
OpIn::Swap => ffi::Op::Swap,
OpIn::Extend { dir, dist, cont } => ffi::Op::Extend {
dir: *dir,
dist: *dist,
cont: *cont,
},
}
}
@@ -972,45 +960,10 @@ fn rust_op(k: &Kernel, op: &OpIn, a: &mut Plane, o: &mut Vec<f64>) -> Result<(),
OpIn::Swap => {
st(o, a.swap_uv())?;
}
OpIn::Extend { dir, dist, cont } => {
let cont = continuity_of(*cont)?;
let r = match dir {
Some(d) => {
let d = SurfParamType::from_code(u64::from(*d))
.ok_or_else(|| harness(format!("bad direction {d}")))?;
a.create_extended_surface_dir(d, *dist, cont)
}
None => a.create_extended_surface(*dist, cont),
};
if let Some(e) = st(o, r)? {
put_surface_nurb(o, e.gw_nurb());
}
}
}
Ok(())
}
fn continuity_of(v: u8) -> Result<ContinuityType, HarnessError> {
use ContinuityType as C;
Ok(match v {
0 => C::Undefined,
1 => C::Discontinuous,
2 => C::C0,
3 => C::G1,
4 => C::G1R,
5 => C::G1G2,
6 => C::G1G2G3,
7 => C::C1,
8 => C::C1G2,
9 => C::C1G2G3,
10 => C::C1C2,
11 => C::C1C2G3,
12 => C::C1C2C3,
13 => C::CInfinity,
_ => return Err(harness(format!("bad continuity {v}"))),
})
}
fn op_code(op: &OpIn) -> Result<f64, HarnessError> {
let prog = ffi::encode(
&ffi::Make::Normal {
@@ -1534,14 +1487,6 @@ fn gen_op(rng: &mut Rng, m: &MakeIn, g: &Geo) -> OpIn {
line: [l0, l1],
s: rng.uniform(0.0, 1.0),
}
} else if rng.chance(0.25) {
OpIn::Extend {
dir: rng
.chance(0.6)
.then(|| pick(rng, &[0u8, 1, 2, 3, 4, 5, 6, 7, 8])),
dist: pick(rng, &[0.01, 0.1, 0.5, 2.0]),
cont: pick(rng, &[2u8, 3, 4, 5, 13]),
}
} else if rng.chance(0.5) {
OpIn::Reverse {
v_dir: rng.chance(0.5),
@@ -31,7 +31,6 @@ use serde_json::Value;
use solidcore::geom::axis2_placement::Axis2Placement;
use solidcore::geom::extent1d::Extent1d;
use solidcore::geom::geom_utility::p2::ContinuityType;
use solidcore::geom::vector2d::{Point2, Vec2};
use solidcore::geom::vector3d::Vec3;
use solidcore::kernel::Kernel;
@@ -45,7 +44,7 @@ use solidcore::surface::plane::Plane;
use solidcore::surface::surf_of_extrusion::p2::{ExtrusionIntersection, IntersectTarget};
use solidcore::surface::surf_of_extrusion::SurfOfExtrusion;
use solidcore::surface::surface::p1::crossed_seam_or_pole;
use solidcore::surface::surface::{Surface, SurfaceKind};
use solidcore::surface::surface::Surface;
use solidcore::topology::database_io::{DatabaseReader, FileType};
use solidcore_ffi::units::surface_surf_of_extrusion_p2 as ffi;
@@ -307,12 +306,6 @@ enum QueryIn {
dom: Option<[f64; 4]>,
tol: f64,
},
Extend {
#[serde(default)]
dir: Option<u8>,
dist: f64,
cont: u8,
},
Planar {
tol: f64,
},
@@ -714,7 +707,6 @@ fn ffi_query(q: &QueryIn) -> ffi::Query {
QueryIn::BBox { dom, expand } => ffi::Query::BBox { dom, expand },
QueryIn::DegenPoint { tol, dom } => ffi::Query::DegenPoint { tol, dom },
QueryIn::DegenCurve { ang, dom, tol } => ffi::Query::DegenCurve { ang, dom, tol },
QueryIn::Extend { dir, dist, cont } => ffi::Query::Extend { dir, dist, cont },
QueryIn::Planar { tol } => ffi::Query::Planar { tol },
QueryIn::NumCpts { dir } => ffi::Query::NumCpts { dir },
QueryIn::Bilinear => ffi::Query::Bilinear,
@@ -1226,40 +1218,6 @@ fn rust_query(k: &Kernel, s: &Surface, q: &QueryIn, o: &mut Vec<f64>) -> Result<
QueryIn::DegenCurve { ang, dom, tol } => {
truth(o, s.is_degenerate_curve(k, ang, opt(dom).as_ref(), tol))?;
}
QueryIn::Extend { dir, dist, cont } => {
let cont = continuity_of(cont);
let r = match dir {
None => s.create_extended_surface(k, dist, cont),
Some(d) => match SurfParamType::from_code(u64::from(d)) {
Some(d) => s.create_extended_surface_dir(k, d, dist, cont),
None => Err(solidcore::SolidError::invalid_input(
"difftest",
"parameter type",
)),
},
};
if let Some(e) = st(o, r)? {
o.push(e.type_code() as f64);
let sur = match e.kind() {
SurfaceKind::BSpline(b) => b.gw_nurb(),
_ => None,
};
o.push(flag(sur.is_some()));
if let Some(sur) = sur {
let ix = sur.indices();
o.extend([ix.p, ix.q, ix.n + 1, ix.m + 1].map(|x| x as f64));
for row in sur.rows() {
for p in row {
o.extend([p.x, p.y, p.z, p.w]);
}
}
o.push(sur.u_knots().len() as f64);
o.extend_from_slice(sur.u_knots());
o.push(sur.v_knots().len() as f64);
o.extend_from_slice(sur.v_knots());
}
}
}
QueryIn::Planar { tol } => match s.is_planar(k, tol) {
Ok(p) => {
o.push(flag(p.is_planar));
@@ -1327,27 +1285,6 @@ fn param_code(c: u8) -> solidcore::Result<SurfParamType> {
.ok_or_else(|| solidcore::SolidError::invalid_input("difftest", "parameter type"))
}
/// `SmContinuityType` by code (as the `surface.surface_p2` runner).
fn continuity_of(v: u8) -> ContinuityType {
use ContinuityType as C;
match v {
0 => C::Undefined,
1 => C::Discontinuous,
2 => C::C0,
3 => C::G1,
4 => C::G1R,
5 => C::G1G2,
6 => C::G1G2G3,
7 => C::C1,
8 => C::C1G2,
9 => C::C1G2G3,
10 => C::C1C2,
11 => C::C1C2G3,
12 => C::C1C2C3,
_ => C::CInfinity,
}
}
// ---- generation -----------------------------------------------------------------------
/// A built source with its Rust surface (to size the parameters).
@@ -232,15 +232,6 @@ enum OpIn {
tol: f64,
guess: Option<[f64; 2]>,
},
Extend {
dist: f64,
cont: u8,
},
ExtendDir {
dir: u8,
dist: f64,
cont: u8,
},
AdjustStep {
dom: [f64; 4],
},
@@ -452,8 +443,6 @@ impl OpIn {
OpIn::Trim { .. } => Op::Trim,
OpIn::Split { .. } => Op::Split,
OpIn::StepInv { .. } => Op::StepInv,
OpIn::Extend { .. } => Op::Extend,
OpIn::ExtendDir { .. } => Op::ExtendDir,
OpIn::AdjustStep { .. } => Op::AdjustStep,
OpIn::MaxAnal => Op::MaxAnal,
OpIn::Copy => Op::Copy,
@@ -545,111 +534,6 @@ fn flag(b: bool) -> f64 {
f64::from(u8::from(b))
}
/// Whether `CreateExtendedSurface` would retry a failed `N_SrfExtendByDist`: upstream
/// extends in place (`surP == surQ`), and a failed attempt leaves the surface on freshly
/// allocated, partly written `NL_STACKS` storage that the halved retry then reads
/// (undefined; its outcome depends on earlier calls in the process, see
/// `plan/findings.md`). Such cases are not generated. The side sequence is the one of
/// `create_extended_surface_dispatched` (singular and closed sides skipped); any failure
/// of the emulation counts as a retry.
fn extension_retries_in_place(i: &Inputs) -> bool {
let (dir, dist, cont) = match &i.op {
OpIn::Extend { dist, cont } => (SurfParamType::Both, *dist, *cont),
OpIn::ExtendDir { dir, dist, cont } => match param_of(*dir) {
Ok(d) => (d, *dist, *cont),
Err(_) => return false,
},
_ => return false,
};
let Some(b) = i
.this
.as_ref()
.filter(|t| !t.base)
.and_then(|t| t.bss.as_ref())
else {
return false;
};
let Ok(bs) = bss_of(b) else {
return false;
};
!matches!(extension_needs_no_retry(&bs, dir, dist, cont), Ok(true))
}
/// The first attempts of [`extension_retries_in_place`]: `Ok(true)` when every side
/// extends at the full distance.
fn extension_needs_no_retry(
bs: &BSplineSurface,
dir: SurfParamType,
dist: f64,
cont: u8,
) -> solidcore::Result<bool> {
use solidcore::nurbs::crv_adv::p1::CurveEnd;
use solidcore::nurbs::srf_adv::p1::extend_by_dist;
use solidcore::nurbs::srf_adv::p2::SrfContinuity;
use solidcore::nurbs::srf_basic::Direction;
use SurfParamType as P;
let Some(nurb) = bs.gw_nurb() else {
return Ok(true);
};
let k = Kernel::default();
let s = Surface::from(bs.clone_exact());
let dom = bs.natural_uv_domain();
let gflg = match continuity_of(cont) {
Ok(ContinuityType::G1R) => SrfContinuity::G1R,
Ok(ContinuityType::G1G2) => SrfContinuity::G2,
Ok(ContinuityType::CInfinity) => SrfContinuity::Cmax,
_ => SrfContinuity::G1,
};
let u_dir = matches!(dir, P::U | P::UMin | P::UMax);
let v_dir = matches!(dir, P::V | P::VMin | P::VMax);
let neither = dir == P::Neither;
let mut skip_umin = neither || v_dir || dir == P::UMax;
let mut skip_umax = neither || v_dir || dir == P::UMin;
let mut skip_vmin = neither || u_dir || dir == P::VMax;
let mut skip_vmax = neither || u_dir || dir == P::VMin;
let sing = s.is_singularity(&k, dom.min(), solidcore::math::EFF_ZERO, false)?;
if sing.singular {
if sing.direction == P::U {
skip_vmin = true;
} else {
skip_umin = true;
}
}
let sing = s.is_singularity(&k, dom.max(), solidcore::math::EFF_ZERO, false)?;
if sing.singular {
if sing.direction == P::U {
skip_vmax = true;
} else {
skip_umax = true;
}
}
let mut cur = nurb.clone();
let mut sides = Vec::new();
if !v_dir && !s.is_closed(&k, &dom, P::U, None, false)?.0 {
sides.extend([
(Direction::U, CurveEnd::Start, skip_umin),
(Direction::U, CurveEnd::End, skip_umax),
]);
}
if !u_dir && !s.is_closed(&k, &dom, P::V, None, false)?.0 {
sides.extend([
(Direction::V, CurveEnd::Start, skip_vmin),
(Direction::V, CurveEnd::End, skip_vmax),
]);
}
for (d, e, skipped) in sides {
if skipped {
continue;
}
match extend_by_dist(&cur, dist, d, e, gflg) {
Ok(x) => cur = x,
Err(_) => return Ok(false),
}
}
Ok(true)
}
// ---- C++ side -----------------------------------------------------------------------
fn push_srf(o: &mut Vec<f64>, s: &InlineSrf) -> Result<(), HarnessError> {
@@ -800,10 +684,6 @@ fn cpp_input(i: &Inputs) -> Result<(Op, Vec<f64>), HarnessError> {
o.push(flag(guess.is_some()));
o.extend_from_slice(&guess.unwrap_or([0.0, 0.0]));
}
OpIn::Extend { dist, cont } => o.extend([*dist, f64::from(*cont)]),
OpIn::ExtendDir { dir, dist, cont } => {
o.extend([f64::from(*dir), *dist, f64::from(*cont)]);
}
OpIn::DropFast { dom, p } => {
o.extend_from_slice(dom);
o.extend_from_slice(p);
@@ -1602,29 +1482,6 @@ fn rust_surface_op(k: &Kernel, op: &OpIn, t: &This) -> Result<Run, HarnessError>
o.push(f64::from(loc as i32));
Ok(())
}),
OpIn::Extend { dist, cont } => {
let cont = continuity_of(*cont)?;
record(|o| {
let e = match &s {
Some(x) => x.create_extended_surface(k, *dist, cont)?,
None => sp2::base_create_extended_surface()?,
};
dump_state(o, Some(&e));
Ok(())
})
}
OpIn::ExtendDir { dir, dist, cont } => {
let cont = continuity_of(*cont)?;
let dir = param_of(*dir)?;
record(|o| {
let e = match &s {
Some(x) => x.create_extended_surface_dir(k, dir, *dist, cont)?,
None => sp2::base_create_extended_surface()?,
};
dump_state(o, Some(&e));
Ok(())
})
}
OpIn::AdjustStep { dom } => edit(&mut s, |x| {
x.adjust_step_uv_domain(&Kernel::default(), &dom_of(*dom))
}),
@@ -2555,15 +2412,6 @@ fn gen_surface_case(rng: &mut Rng) -> Inputs {
tol: 1.0e-6,
guess: rng.chance(0.3).then(|| gen_uv(rng, d)),
},
13 => OpIn::Extend {
dist: rng.uniform(0.05, 0.5),
cont: *rng.choose(&[2u8, 3, 4, 5, 13]).unwrap_or(&3),
},
14 => OpIn::ExtendDir {
dir: *rng.choose(&[0u8, 1, 3, 5, 6, 7, 8]).unwrap_or(&0),
dist: rng.uniform(0.05, 0.5),
cont: *rng.choose(&[2u8, 3, 4, 5, 13]).unwrap_or(&3),
},
15 => OpIn::AdjustStep {
dom: gen_sub_dom(rng, d),
},
@@ -3085,9 +2933,6 @@ impl UnitDiff for Runner {
let Ok((op, input)) = cpp_input(&i) else {
continue;
};
if extension_retries_in_place(&i) {
continue;
}
if ffi::validate(op, &input).is_ok() {
return serde_json::to_value(i).ok();
}
@@ -1,7 +1,6 @@
{
"unit": "nlib.srf_adv_p1",
"cases": [
{"name": "fuzz01437_ffi_rejected_bad_index_count_flag", "origin": "fuzz-out/n2000/nlib.srf_adv_p1/fuzz01437.json (panic: rust: attempt to add with overflow at crates/solidcore/src/nurbs/srf_nurb.rs:1107)", "cpp": "harness: ffi rejected: bad index/count/flag 9223372036854776000", "rust": "ok", "inputs": {"cont": 3, "ndr": 0, "op": "unbounded", "sur": {"net": [[[2.44, -0.224, 8.48, -1.7976931348623157e+308], [3.64, -0.722, 7.63, -1.7976931348623157e+308], [5.41, 0.369, -9.12, -1.7976931348623157e+308], [0.00994, 0.135, 3.58, -1.7976931348623157e+308]], [[2.21, 6.08, -921.0, -1.7976931348623157e+308], [-7.0, 4.59, -9.03, -1.7976931348623157e+308], [-5.96, -4.15, 2.4, -1.7976931348623157e+308], [-8.46, -0.974, 1.28, -1.7976931348623157e+308]]], "p": 1, "q": 9223372036854775807, "u": [229.0], "v": [8.55]}, "uv": [[454.0, 8.55]]}},
{"name": "fuzz01998_ffi_rejected_extend_by_distance_g1", "origin": "fuzz-out/n2000/nlib.srf_adv_p1/fuzz01998.json (timeout: cpp: no result after 20 s)", "issue": "HANG-6", "cpp": "harness: ffi rejected: extend by distance: G1/G2 step lost to rounding (distance too short)", "rust": "ok", "inputs": {"cont": 3, "dir": 1, "dist": -0.0, "end": 1, "op": "extend_by_dist", "sur": {"sms": {"$upstream_file": "offset_nurb.sms"}}}}
{"name": "fuzz01437_ffi_rejected_bad_index_count_flag", "origin": "fuzz-out/n2000/nlib.srf_adv_p1/fuzz01437.json (panic: rust: attempt to add with overflow at crates/solidcore/src/nurbs/srf_nurb.rs:1107)", "cpp": "harness: ffi rejected: bad index/count/flag 9223372036854776000", "rust": "ok", "inputs": {"cont": 3, "ndr": 0, "op": "unbounded", "sur": {"net": [[[2.44, -0.224, 8.48, -1.7976931348623157e+308], [3.64, -0.722, 7.63, -1.7976931348623157e+308], [5.41, 0.369, -9.12, -1.7976931348623157e+308], [0.00994, 0.135, 3.58, -1.7976931348623157e+308]], [[2.21, 6.08, -921.0, -1.7976931348623157e+308], [-7.0, 4.59, -9.03, -1.7976931348623157e+308], [-5.96, -4.15, 2.4, -1.7976931348623157e+308], [-8.46, -0.974, 1.28, -1.7976931348623157e+308]]], "p": 1, "q": 9223372036854775807, "u": [229.0], "v": [8.55]}, "uv": [[454.0, 8.55]]}}
]
}
@@ -1,8 +0,0 @@
{
"unit": "nlib.srf_adv_p2",
"cases": [
{"name": "fuzz00017_ffi_rejected_invalid_knot_vector", "origin": "fuzz-out/n2000/nlib.srf_adv_p2/fuzz00017.json (timeout: rust: no result after 20 s)", "cpp": "harness: ffi rejected: invalid knot vector", "rust": "ok", "inputs": {"cont": 2, "dir": 1, "dist": 2.25, "end": 1, "op": "extend", "sur": {"net": [[[0.00541, 0.0163, 0.0, -1.7976931348623157e+308], [-0.0085, 1.32, -0.0776, -1.7976931348623157e+308], [-0.0112, 2.72, -0.31, -1.7976931348623157e+308], [0.0626, 4.07, -0.698, -1.7976931348623157e+308], [0.0554, 5.42, -1.24, -1.7976931348623157e+308]], [[1.32, -0.01, 0.173, -1.7976931348623157e+308], [1.32, 1.36, 0.0273, -1.7976931348623157e+308], [1.34, 2.71, -0.274, -1.7976931348623157e+308], [1.25, 4.03, -0.73, -1.7976931348623157e+308], [1.32, 5.39, -1.34, -1.7976931348623157e+308]], [[2.61, 0.0276, 0.693, -1.7976931348623157e+308], [2.59, 1.32, 0.479, -1.7976931348623157e+308], [2.59, 2.73, 0.109, -1.7976931348623157e+308], [2.65, 4.05, -0.415, -1.7976931348623157e+308], [2.61, 5.41, -1.09, -1.7976931348623157e+308]], [[3.95, -0.0361, 1.56, -1.7976931348623157e+308], [3.91, 1.33, 1.28, -1.7976931348623157e+308], [3.88, 2.67, 0.839, -1.7976931348623157e+308], [3.86, 3.98, 0.246, -1.7976931348623157e+308], [3.95, 5.42, -0.502, -1.7976931348623157e+308]]], "p": 3, "q": 3, "u": [297.0, 297.0, 297.0, 297.0], "v": [0.0, 0.0, 0.0, 2.11, 3.76, 3.76, 3.76]}}},
{"name": "fuzz00349_ffi_rejected_mixed_or_non_positive", "origin": "fuzz-out/n2000/nlib.srf_adv_p2/fuzz00349.json (timeout: cpp: no result after 20 s)", "issue": "HANG-6", "cpp": "harness: ffi rejected: mixed or non-positive weights", "rust": "ok", "inputs": {"cont": 2, "dir": 2, "dist": 13.3, "end": 2, "op": "extend", "sur": {"net": [[[9.53, 0.189, 1.7976931348623157e+308, -1.7976931348623157e+308], [9.46, 4.84, 1.7976931348623157e+308, -1.7976931348623157e+308], [9.64, 9.21, 1.7976931348623157e+308, -0.0]], [[12.7, -0.032, 1.7976931348623157e+308, -1.7976931348623157e+308], [12.8, 4.55, 1.7976931348623157e+308, -1.7976931348623157e+308], [12.8, 9.51, 1.7976931348623157e+308, -1.7976931348623157e+308]], [[15.9, -0.0803, 1.7976931348623157e+308, -1.7976931348623157e+308], [15.9, 4.61, 1.7976931348623157e+308, -1.7976931348623157e+308], [15.8, 9.28, 1.7976931348623157e+308, -1.7976931348623157e+308]], [[19.2, 0.196, 1.7976931348623157e+308, -1.7976931348623157e+308], [19.0, 4.67, 1.7976931348623157e+308, -1.7976931348623157e+308], [19.0, 9.3, 1.7976931348623157e+308, -1.7976931348623157e+308]]], "p": 3, "q": 2, "u": [1.0], "v": [157.0]}}},
{"name": "fuzz01877_ffi_rejected_extend_by_distance_g1", "origin": "fuzz-out/n2000/nlib.srf_adv_p2/fuzz01877.json (timeout: cpp: no result after 20 s)", "cpp": "harness: ffi rejected: extend by distance: G1/G2 step lost to rounding (distance too short)", "rust": "ok", "inputs": {"cont": 3, "dir": 1, "dist": 1e-300, "end": 1, "op": "extend", "sur": {"sms": {"$upstream_file": "Surface.sms"}}}}
]
}
@@ -5,7 +5,6 @@
{"name": "fuzz00530_sweep_tolerance_too_small_for_the", "origin": "fuzz-out/n2000/nurbs.b_spline_surface_n_lib_p1/fuzz00530.json (timeout: cpp: no result after 20 s)", "issue": "HANG-5", "cpp": "harness: invalid argument: sweep: tolerance too small for the trajectory", "rust": "ok", "inputs": {"op": "sweep", "section": {"cpts": [[-2.73, -2.28, -2.87, -1.7976931348623157e+308], [-1.87, -1.05, -2.19, -1.7976931348623157e+308], [-1.0, 0.18, -1.48, -1.7976931348623157e+308], [-0.147, 1.42, -0.788, -1.7976931348623157e+308]], "p": 1, "u": [1.93, 1.93, 3.07, 3.78, 3.95, 3.95]}, "tol": 1e-300, "traj": {"cpts": [[1.1, 0.855, 1.72, -1.7976931348623157e+308], [-0.206, 0.48, 1.49, -1.7976931348623157e+308], [-1.59, 0.234, 1.12, -1.7976931348623157e+308], [-2.84, -0.144, 0.684, -1.7976931348623157e+308]], "p": 3, "u": [0.769, 0.769, 0.769, 0.769, 3.66, 3.66, 3.66, 3.66]}, "trans": false}},
{"name": "fuzz00651_curve_euclidean_point_out_of_range", "origin": "fuzz-out/n2000/nurbs.b_spline_surface_n_lib_p1/fuzz00651.json (crash: rust: killed by signal 6 (realloc(): invalid next size))", "issue": "UB-225", "cpp": "harness: invalid argument: curve: Euclidean point out of range", "rust": "ok", "inputs": {"curve": {"cpts": [[-0.364, 0.0577, 2.43, 1.0], [-0.104, -0.216, 1.24, 0.551], [-0.062, -0.903, 1e+300, 0.907], [-0.0828, -1.07, 1.86, 0.814], [0.137, -0.702, 1.57, 0.69], [0.362, -1.68, 2.3, 1.0]], "p": 2, "u": [1.79, 1.79, 1.79, 2.35, 2.91, 3.48, 4.04, 4.04, 4.04]}, "op": "non_rat", "tol": 0.001}},
{"name": "fuzz00902_runs", "origin": "fuzz-out/n2000/nurbs.b_spline_surface_n_lib_p1/fuzz00902.json (crash: cpp: killed by signal 6 (only after the 50 preceding cases) (double free or corruption (out)))", "issue": "UB-225", "cpp": "ok", "rust": "ok", "inputs": {"op": "ext_eval", "this": {"srf": {"net": [[[-0.124, -0.18, 0.827, -1.7976931348623157e+308], [-0.108, 1.06, -0.736, -1.7976931348623157e+308], [0.00887, 1.99, 0.215, -1.7976931348623157e+308]], [[1.16, 0.154, 0.417, -1.7976931348623157e+308], [1.11, 1.16, -1.0, -1.7976931348623157e+308], [0.89, 1.9, -0.557, -1.7976931348623157e+308]]], "p": 1, "q": 1, "u": [0.0, 0.0, 1.0, 1.0], "v": [0.0, 0.0, 0.5, 1.0, 1.0]}}, "uv": [0.584, 0.294]}},
{"name": "fuzz01032_bad_index_count_9223372036854776000", "origin": "fuzz-out/n2000/nurbs.b_spline_surface_n_lib_p1/fuzz01032.json (panic: rust: capacity overflow)", "cpp": "harness: invalid argument: bad index/count 9223372036854776000", "rust": "harness: make_knots: knot count too large", "inputs": {"n": 3, "op": "make_knots", "p": 9223372036854775807, "u": [0.632]}},
{"name": "fuzz01411_surface_euclidean_point_out_of_range", "origin": "fuzz-out/n2000/nurbs.b_spline_surface_n_lib_p1/fuzz01411.json (timeout: cpp: no result after 20 s)", "issue": "HANG-6", "cpp": "harness: invalid argument: surface: Euclidean point out of range", "rust": "ok", "inputs": {"cont": 5, "dir": 2, "dir_overload": true, "dist": 0.383, "op": "extend", "this": {"srf": {"net": [[[-0.101, -0.00117, 0.0614, -1.7976931348623157e+308], [0.0924, 1.17, 0.243, -1.7976931348623157e+308], [0.112, 1e+300, 0.00758, -1.7976931348623157e+308], [0.0343, 2.89, 0.156, -1.7976931348623157e+308]], [[1.04, 0.00631, 0.301, -1.7976931348623157e+308], [0.962, 0.907, 0.11, -1.7976931348623157e+308], [1.17, 1.9, -0.0589, -1.7976931348623157e+308], [0.854, 3.11, -0.215, -1.7976931348623157e+308]], [[2.0, -0.128, 0.289, -1.7976931348623157e+308], [1.82, 0.959, -0.0642, -1.7976931348623157e+308], [1.84, 2.12, -0.308, -1.7976931348623157e+308], [1.88, 2.84, -0.126, -1.7976931348623157e+308]], [[2.99, 0.1, -0.214, -1.7976931348623157e+308], [3.08, 0.979, 0.244, -1.7976931348623157e+308], [2.83, 1.98, 0.193, -1.7976931348623157e+308], [2.96, 2.87, -0.279, -1.7976931348623157e+308]], [[3.94, 0.0173, 0.245, -1.7976931348623157e+308], [4.13, 1.04, 0.292, -1.7976931348623157e+308], [3.99, 2.11, 0.172, -1.7976931348623157e+308], [3.85, 3.19, -0.219, -1.7976931348623157e+308]]], "p": 3, "q": 2, "u": [0.0, 0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0, 1.0], "v": [0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0]}}}}
{"name": "fuzz01032_bad_index_count_9223372036854776000", "origin": "fuzz-out/n2000/nurbs.b_spline_surface_n_lib_p1/fuzz01032.json (panic: rust: capacity overflow)", "cpp": "harness: invalid argument: bad index/count 9223372036854776000", "rust": "harness: make_knots: knot count too large", "inputs": {"n": 3, "op": "make_knots", "p": 9223372036854775807, "u": [0.632]}}
]
}
@@ -5,7 +5,6 @@
//!
//! | [`Op`] | upstream | input |
//! |-----------------|------------------------------------|-----------------------------------------|
//! | `ExtendByDist` | `N_SrfExtendByDist` | surface, `dist dflg end cont inplace` |
//! | `MaxDeriv` | `N_SrfOffsetGetMaxDeriv` | surface, `d` |
//! | `Unbounded` | `N_SrfEvalPtDerivsUnbounded` | surface, `cflg ndr cnt`, `cnt` `(u, v)` |
//! | `SkinSpine` | `N_CreateSkinSpine` | `k`, curves `0..=k`, spine, params `0..=k`, `cmp align z0x z0y z0z subdiv dflag deg dir` |
@@ -20,9 +19,6 @@
//! and rejects every argument that would make upstream index outside its arrays or
//! loop without end:
//!
//! - `ExtendByDist`: `dflg` `1..=2`, `end` `1..=2`, `cont` one of `2, 3, 4, 6`; with
//! `cont` `2` or `3` a distance of at least a parameter step that
//! is not lost to rounding (see `g2_extension_ok`).
//! - `Unbounded`: `cflg` one of `2, 3, 4, 6`; `ndr` `0..=`[`MAX_NDR`]; `1..=`[`MAX_EVALS`]
//! evaluations; each parameter either within [`MAX_REACH`] domain lengths of the
//! domain (bounding the periodic wrap loops) or beyond `1e12` in magnitude
@@ -35,8 +31,11 @@
//! - `VectorField`: `bndy` `1..=4`. Two valid curves with equal knot vectors have
//! equal degrees and control point counts, so upstream never reads past the
//! vector field's control points.
//!
//! Shim op `0` (`N_SrfExtendByDist`) was removed with the port's fitted extension;
//! its number is not reused.
use crate::validate::{bad, euclid, NOW};
use crate::validate::{bad, NOW};
use crate::{call_with_buffer, FfiError};
/// Largest highest net index (`n`, `m`) of a surface accepted by [`eval`].
@@ -59,8 +58,6 @@ pub const MAX_SUBDIV: usize = 16;
/// The upstream routine run by the shim.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Op {
/// `N_SrfExtendByDist`.
ExtendByDist,
/// `N_SrfOffsetGetMaxDeriv`.
MaxDeriv,
/// `N_SrfEvalPtDerivsUnbounded` (a sequence of calls sharing `esur`).
@@ -74,7 +71,6 @@ pub enum Op {
impl Op {
fn code(self) -> i32 {
match self {
Op::ExtendByDist => 0,
Op::MaxDeriv => 1,
Op::Unbounded => 2,
Op::SkinSpine => 3,
@@ -188,89 +184,6 @@ fn curve<'a>(rd: &mut Reader<'a>) -> Result<(usize, usize, &'a [f64]), FfiError>
Ok((n, p, u))
}
/// Smallest parameter step of the `NL_G2` search, in units of `f64::EPSILON` times the
/// largest knot magnitude (see `g2_extension_ok`).
pub const MIN_G2_STEP_EPS: f64 = 1024.0;
/// The validated surface at the start of `input` (`n m p q`, the net, the u and v
/// knots): a bound on the control polygon length of every iso-curve running in u
/// (`udir`) or v, and the knots of that direction. The bound is the sum, over
/// consecutive net rows (columns), of the largest distance between corresponding
/// Euclidean points; it is infinite when a distance is not finite.
fn iso_polygon_bound(input: &[f64], udir: bool) -> Result<(f64, &[f64]), FfiError> {
let head = |k: usize| {
input
.get(k)
.map(|&x| x as usize)
.ok_or_else(|| bad("input too short"))
};
let (n, m, p, q) = (head(0)?, head(1)?, head(2)?, head(3)?);
let pt = |i: usize, j: usize| -> Result<[f64; 3], FfiError> {
let at = 4 + 4 * (i * (m + 1) + j);
match input.get(at..at + 4) {
Some(&[x, y, z, w]) => Ok(euclid([x, y, z, w])),
_ => Err(bad("input too short")),
}
};
let u0 = 4 + 4 * (n + 1) * (m + 1);
let v0 = u0 + n + p + 2;
let knots = if udir {
input.get(u0..v0)
} else {
input.get(v0..v0 + m + q + 2)
}
.ok_or_else(|| bad("input too short"))?;
let dist = |a: [f64; 3], b: [f64; 3]| {
let [dx, dy, dz] = [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
(dx * dx + dy * dy + dz * dz).sqrt()
};
let (rows, cols) = if udir { (n, m) } else { (m, n) };
let mut total = 0.0;
for a in 0..rows {
let mut longest: f64 = 0.0;
for b in 0..=cols {
let (p0, p1) = if udir {
(pt(a, b)?, pt(a + 1, b)?)
} else {
(pt(b, a)?, pt(b, a + 1)?)
};
let d = dist(p0, p1);
if !d.is_finite() {
return Ok((f64::INFINITY, knots));
}
longest = longest.max(d);
}
total += longest;
}
Ok((total, knots))
}
/// `ST_surxsdr` extends sampled iso-curves with `ST_curxcdr` `NL_G2` for `cont`
/// `NL_G1` / `NL_G2`. Its parameter search steps by `du = range * dist / d1` (`d1` the
/// iso-curve's arc length) and never ends when that step is lost to rounding (zero
/// or tiny `dist`, a huge or overflowing iso-curve length; HANG-6). So `dist` must
/// be non-zero and the step
/// estimated with [`iso_polygon_bound`] in place of `d1` (a lower bound of `du` for
/// non-rational surfaces) at least [`MIN_G2_STEP_EPS`] `f64::EPSILON`s of the largest
/// knot magnitude.
fn g2_extension_ok(input: &[f64], dist: f64, udir: bool, cont: usize) -> Result<(), FfiError> {
if cont != 2 && cont != 3 {
return Ok(());
}
let (bound, knots) = iso_polygon_bound(input, udir)?;
let (Some(&first), Some(&last)) = (knots.first(), knots.last()) else {
return Err(bad("empty knot vector"));
};
let step = (last - first) * (dist / bound);
let floor = MIN_G2_STEP_EPS * f64::EPSILON * first.abs().max(last.abs());
if !(step.abs() >= floor && dist != 0.0) {
return Err(bad(
"extend by distance: G1/G2 step lost to rounding (distance too short)",
));
}
Ok(())
}
fn cont_flag(rd: &mut Reader<'_>) -> Result<usize, FfiError> {
let cont = rd.index(2, 6)?;
if cont == 5 {
@@ -301,15 +214,6 @@ fn check_reach(t: f64, k: &[f64]) -> Result<(), FfiError> {
fn validate(op: Op, input: &[f64]) -> Result<(), FfiError> {
let mut rd = Reader::new(input);
match op {
Op::ExtendByDist => {
surface(&mut rd)?;
let dist = rd.num()?;
let udir = rd.index(1, 2)? == 1;
rd.index(1, 2)?;
let cont = cont_flag(&mut rd)?;
rd.index(0, 1)?;
g2_extension_ok(input, dist, udir, cont)?;
}
Op::MaxDeriv => {
surface(&mut rd)?;
rd.num()?;
@@ -5,7 +5,6 @@
//!
//! | [`Op`] | upstream |
//! |-----------------|-------------------------------------------|
//! | `ExtendByDist` | `ST_surxsdr` (core of `N_SrfExtendByDist`) |
//! | `NormalDeriv` | `N_SrfEvalPtNormalDeriv` |
//!
//! Input encoding (flat doubles) and output records are described in the shim.
@@ -14,14 +13,13 @@
//! `r = n + p + 1`, `s = m + q + 1` and valid clamped knot vectors
//! (`N_KnotVectorIsValid`: end multiplicities `p + 1`, non-decreasing, interior
//! multiplicities at most `p`), weights all `NOW` or all positive; every coordinate
//! and parameter must be finite. The
//! extension takes `dist dflg end cont depth inplace` with `dflg` in `1..=2`, `end`
//! in `1..=2`, `cont` one of `2, 3, 4, 6` (with `2` or `3`, a non-zero distance whose
//! parameter step is not lost to rounding, see `g2_extension_ok`) and
//! `depth` in `0..=11`; the normal
//! derivatives take `u v ufl vfl` with side flags in `1..=2`.
//! and parameter must be finite. The normal derivatives take `u v ufl vfl` with side
//! flags in `1..=2`.
//!
//! Shim op `0` (`ST_surxsdr`, the surface extension by a distance) was removed with
//! the port's fitted extension; its number is not reused.
use crate::validate::{bad, euclid, NOW};
use crate::validate::{bad, NOW};
use crate::{call_with_buffer, FfiError};
/// Largest highest index (`n`, `m`) accepted by [`eval`].
@@ -32,8 +30,6 @@ pub const MAX_DEGREE: usize = 17;
/// The upstream routine run by the shim.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Op {
/// `ST_surxsdr`; then `dist dflg end cont depth inplace`.
ExtendByDist,
/// `N_SrfEvalPtNormalDeriv`; then `u v ufl vfl`.
NormalDeriv,
}
@@ -41,7 +37,6 @@ pub enum Op {
impl Op {
fn code(self) -> i32 {
match self {
Op::ExtendByDist => 0,
Op::NormalDeriv => 1,
}
}
@@ -122,8 +117,7 @@ fn surface(rd: &mut Reader<'_>) -> Result<[usize; 4], FfiError> {
}
let net = rd.slice(4 * (n + 1) * (m + 1))?;
// Weights: all NOW (non-rational) or all positive. Anything else is no valid
// surface; a zero weight makes Euclidean points infinite, and the extension then
// reads past its scratch rows when a reflection plane cannot be normalised.
// surface.
let ws: Vec<f64> = net.chunks(4).filter_map(|c| c.get(3).copied()).collect();
if !(ws.iter().all(|&w| w == NOW) || ws.iter().all(|&w| w > 0.0)) {
return Err(bad("mixed or non-positive weights"));
@@ -136,105 +130,10 @@ fn surface(rd: &mut Reader<'_>) -> Result<[usize; 4], FfiError> {
Ok([n, m, p, q])
}
/// Smallest parameter step of the `NL_G2` search, in units of `f64::EPSILON` times the
/// largest knot magnitude (see `g2_extension_ok`).
pub const MIN_G2_STEP_EPS: f64 = 1024.0;
/// The validated surface at the start of `input` (`n m p q`, the net, the u and v
/// knots): a bound on the control polygon length of every iso-curve running in u
/// (`udir`) or v, and the knots of that direction. The bound is the sum, over
/// consecutive net rows (columns), of the largest distance between corresponding
/// Euclidean points; it is infinite when a distance is not finite.
fn iso_polygon_bound(input: &[f64], udir: bool) -> Result<(f64, &[f64]), FfiError> {
let head = |k: usize| {
input
.get(k)
.map(|&x| x as usize)
.ok_or_else(|| bad("input too short"))
};
let (n, m, p, q) = (head(0)?, head(1)?, head(2)?, head(3)?);
let pt = |i: usize, j: usize| -> Result<[f64; 3], FfiError> {
let at = 4 + 4 * (i * (m + 1) + j);
match input.get(at..at + 4) {
Some(&[x, y, z, w]) => Ok(euclid([x, y, z, w])),
_ => Err(bad("input too short")),
}
};
let u0 = 4 + 4 * (n + 1) * (m + 1);
let v0 = u0 + n + p + 2;
let knots = if udir {
input.get(u0..v0)
} else {
input.get(v0..v0 + m + q + 2)
}
.ok_or_else(|| bad("input too short"))?;
let dist = |a: [f64; 3], b: [f64; 3]| {
let [dx, dy, dz] = [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
(dx * dx + dy * dy + dz * dz).sqrt()
};
let (rows, cols) = if udir { (n, m) } else { (m, n) };
let mut total = 0.0;
for a in 0..rows {
let mut longest: f64 = 0.0;
for b in 0..=cols {
let (p0, p1) = if udir {
(pt(a, b)?, pt(a + 1, b)?)
} else {
(pt(b, a)?, pt(b, a + 1)?)
};
let d = dist(p0, p1);
if !d.is_finite() {
return Ok((f64::INFINITY, knots));
}
longest = longest.max(d);
}
total += longest;
}
Ok((total, knots))
}
/// `ST_surxsdr` extends sampled iso-curves with `ST_curxcdr` `NL_G2` for `cont`
/// `NL_G1` / `NL_G2`. Its parameter search steps by `du = range * dist / d1` (`d1` the
/// iso-curve's arc length) and never ends when that step is lost to rounding (zero
/// or tiny `dist`, a huge or overflowing iso-curve length; HANG-6). So `dist` must
/// be non-zero and the step
/// estimated with [`iso_polygon_bound`] in place of `d1` (a lower bound of `du` for
/// non-rational surfaces) at least [`MIN_G2_STEP_EPS`] `f64::EPSILON`s of the largest
/// knot magnitude.
fn g2_extension_ok(input: &[f64], dist: f64, udir: bool, cont: usize) -> Result<(), FfiError> {
if cont != 2 && cont != 3 {
return Ok(());
}
let (bound, knots) = iso_polygon_bound(input, udir)?;
let (Some(&first), Some(&last)) = (knots.first(), knots.last()) else {
return Err(bad("empty knot vector"));
};
let step = (last - first) * (dist / bound);
let floor = MIN_G2_STEP_EPS * f64::EPSILON * first.abs().max(last.abs());
if !(step.abs() >= floor && dist != 0.0) {
return Err(bad(
"extend by distance: G1/G2 step lost to rounding (distance too short)",
));
}
Ok(())
}
fn validate(op: Op, input: &[f64]) -> Result<(), FfiError> {
let mut rd = Reader::new(input);
surface(&mut rd)?;
match op {
Op::ExtendByDist => {
let dist = rd.num()?;
let udir = rd.index(1, 2)? == 1;
rd.index(1, 2)?;
let cont = rd.index(2, 6)?;
if cont == 5 {
return Err(bad("bad continuity flag"));
}
rd.index(0, 11)?;
rd.index(0, 1)?;
g2_extension_ok(input, dist, udir, cont)?;
}
Op::NormalDeriv => {
rd.num()?;
rd.num()?;
@@ -15,7 +15,6 @@
//! | [`Op`] | upstream | parameters |
//! |---------------|---------------------------------------|----------------------------------------------|
//! | `Offset` | `ApproximateOffsetSurface` | `dist tol param_type` |
//! | `Extend` | `CreateExtendedSurface` | `dir_overload dir dist continuity` |
//! | `Join` | `JoinSurface` | surface `dir tol` |
//! | `ExtEval` | `EvaluatePointOnExtendedSurface` | `u v` |
//! | `Reparam` | `Reparameterize` | `D[4]` |
@@ -88,7 +87,6 @@ const FORM_POLYNOMIAL: usize = 10;
#[allow(missing_docs)]
pub enum Op {
Offset,
Extend,
Join,
ExtEval,
Reparam,
@@ -111,7 +109,8 @@ impl Op {
fn code(self) -> i32 {
match self {
Op::Offset => 0,
Op::Extend => 1,
// 1: `CreateExtendedSurface`, removed with the port's fitted extension; not
// reused.
Op::Join => 2,
Op::ExtEval => 3,
Op::Reparam => 4,
@@ -457,12 +456,6 @@ fn validate_surface_op(
rd.positive("offset tol")?;
rd.index(3)?;
}
Op::Extend => {
rd.flag()?;
rd.index(9)?;
rd.positive("extend dist")?;
rd.index(13)?;
}
Op::Join => {
surface(rd)?;
if rd.index(1)? > 1 {
@@ -34,7 +34,6 @@
//! | `Trim` | `TrimWithDomain` | the (snapped) domain |
//! | `Split` | `SplitAt` | both halves' states |
//! | `CreateOffset` | `CreateOffsetSurface` | the result's state |
//! | `Extend`, `ExtendDir` | `CreateExtendedSurface` | (refused, see below) |
//! | `Copy` | `Copy` | the copy's state |
//! | `Eq` | `operator==` with another offset and itself | `eq self_eq` |
//! | `IsPlanar` | `IsPlanar` (`SmSurface` body, virtual box) | `planar P N dev` |
@@ -51,10 +50,8 @@
//! may lie outside the base domain (the base-class box of the offset asks the offset
//! again for the outside part, without bound once an extension exists, and its surface
//! cache subdivision runs away for small trimmed bases), no planarity test once an
//! extension may exist, and no `CreateExtendedSurface` ([`Step::Extend`],
//! [`Step::ExtendDir`]): its in-place retry reads arrays a failed extension half-wrote
//! (UB-107) and its arc-length searches can stall or crawl (HANG-6, HANG-22), which
//! nothing outside NLib can detect before the C++ runs in this process. The shim checks
//! extension may exist. (Step codes `15` and `16`, `CreateExtendedSurface`, were removed
//! with the port's fitted extension; they are not reused.) The shim checks
//! the state-dependent cases on the actual surfaces as the script runs: the non-zero
//! offset writing past the caller's array for `bOnlyUpperHalf` orders (UB-171, only
//! when the compound distance is not exactly `0.0`), zero-distance delegation to an
@@ -269,22 +266,6 @@ pub enum Step {
/// Signed distance.
d: f64,
},
/// `CreateExtendedSurface(dist, cont)` (refused: module docs).
Extend {
/// Distance.
dist: f64,
/// `SmContinuityType` code.
cont: u8,
},
/// `CreateExtendedSurface(param, dist, cont)` (refused: module docs).
ExtendDir {
/// `SmSurfParamType` code.
param: u8,
/// Distance.
dist: f64,
/// `SmContinuityType` code.
cont: u8,
},
/// `Copy`.
Copy,
/// `operator==` with another offset, then with itself.
@@ -347,8 +328,6 @@ impl Step {
Step::Trim { .. } => 12,
Step::Split { .. } => 13,
Step::CreateOffset { .. } => 14,
Step::Extend { .. } => 15,
Step::ExtendDir { .. } => 16,
Step::Copy => 17,
Step::Eq { .. } => 18,
Step::SetDist { .. } => 19,
@@ -791,11 +770,6 @@ fn push_steps(o: &mut Vec<f64>, off: &Off, info: BaseInfo, steps: &[Step]) -> Re
finite(&[*d], "offset distance")?;
o.push(*d);
}
Step::Extend { .. } | Step::ExtendDir { .. } => {
return Err(bad(
"extend: CreateExtendedSurface is not run in process (UB-107, HANG-6, HANG-22)",
));
}
Step::Eq { other } => {
push_off(o, other, 1)?;
}
@@ -1136,41 +1110,6 @@ mod tests {
assert!(refused(&script(&tiny, &[Step::State, eval(1, 1, false)])));
}
/// UB-107, HANG-6 and HANG-22 are inside `CreateExtendedSurface`, which cannot be
/// guarded in process: the FFI refuses every extension step, and the shim refuses
/// one sent past the FFI without running it.
#[test]
fn extension_steps_are_refused_in_process() {
let off = Off {
base: bss(1.0),
..plane(0.25)
};
let steps = [
Step::Extend { dist: 0.1, cont: 5 },
Step::ExtendDir {
param: 7,
dist: 0.1,
cont: 13,
},
];
for step in steps {
assert!(matches!(
check(&off, std::slice::from_ref(&step)),
Err(FfiError::InvalidArgument(_))
));
assert!(refused(&script(&off, &[Step::State, step])));
}
let mut input = Vec::new();
push_off(&mut input, &off, 0).unwrap();
input.extend([2.0, 0.0, 15.0, 0.1, 5.0]);
assert!(refused(&call(OP_SCRIPT, &input)));
input.truncate(input.len() - 4);
input.extend([16.0, 7.0, 0.1, 13.0]);
assert!(refused(&call(OP_SCRIPT, &input)));
// a refusal leaves nothing behind for the next call
assert!(script(&off, &[Step::State, eval(1, 1, false)]).is_ok());
}
#[test]
fn unknown_revolution_domain_cannot_bypass_polar_guard() {
let off = Off {
@@ -36,7 +36,6 @@
//! | `Copy` | `Copy` | state |
//! | `Grazing` | `sm_IsGrazingIntersection` (file static, copied into the shim) | flag |
//! | `Inherited` | of a copy: `GetNaturalUVDomain`, `GetMaxAnalyticDomain`, `IsBilinear`, `IsParallelToVector(v)`, `GetDegree` and `GetNumberControlPoints` (u, v) | values |
//! | `Extend` | of a copy: `CreateExtendedSurface(dist, cont)` or `CreateExtendedSurface(dir, dist, cont)` | the extension's NURB `has [..]` |
//!
//! [`run`] accepts only finite inputs of magnitude at most [`MAX_COORD`], well-formed
//! clamped B-splines (at most [`MAX_INDEX`] + 1 control points per direction, degree
@@ -292,17 +291,6 @@ pub enum Op {
Reverse(bool),
/// `SwapUV()`.
Swap,
/// `CreateExtendedSurface` of a copy: without a direction the `SM_SP_BOTH`
/// overload, else with `SmSurfParamType` `dir` (at most `SM_SP_VMAX`, 8). The
/// distance must be positive.
Extend {
/// The `SmSurfParamType` code, or `None` for the overload without one.
dir: Option<u8>,
/// Extension distance.
dist: f64,
/// The `SmContinuityType` code (at most `SM_CT_CINFINITY`, 13).
cont: u8,
},
}
fn finite(x: f64) -> Result<f64, FfiError> {
@@ -662,21 +650,6 @@ impl Enc {
self.flag(*v_dir);
}
Op::Swap => self.int(25),
Op::Extend { dir, dist, cont } => {
if dir.is_some_and(|d| d > 8) || *cont > 13 {
return Err(bad("extension direction or continuity"));
}
// upstream halves a failing extension until it falls below dDist / 100,
// which never happens for a zero distance (an endless loop)
if dist.is_nan() || *dist <= 0.0 {
return Err(bad("extension distance must be positive"));
}
self.int(26);
self.flag(dir.is_some());
self.int(usize::from(dir.unwrap_or(0)));
self.num(*dist)?;
self.int(usize::from(*cont));
}
}
Ok(())
}
@@ -336,15 +336,6 @@ pub enum Query {
/// `dOpt3DTol`.
tol: f64,
},
/// `CreateExtendedSurface(ctx, [dir,] dist, cont, e)`.
Extend {
/// `eExtDirection` (`SmSurfParamType` code); `None` for the overload without it.
dir: Option<u8>,
/// `dDist`.
dist: f64,
/// `SmContinuityType` code.
cont: u8,
},
/// `IsPlanar(tol, &point, &normal, &deviation)`.
Planar {
/// `dTol3d`.
@@ -796,9 +787,7 @@ pub fn is_nurb(test: &TestSurf, scale: f64) -> Result<Vec<f64>, FfiError> {
/// returns the shim's record: as [`is_nurb`] up to `0 1 had_nurb`, then the query's
/// outputs. `EvalSimple`, `EvalFirst`: status, then on success the `(hu + 1) (hv + 1)`
/// derivatives `x y z` (U order major). `BBox`: status, then on success the box corners.
/// `DegenPoint`, `DegenCurve`: the flag. `Extend`: status, then on success the new
/// surface's type, whether it has a NURB and that NURB (`p q n+1 m+1 cpts(x y z w) nu U nv
/// V`). `Planar`: the flag, point, normal and deviation. `NumCpts`: the count.
/// `DegenPoint`, `DegenCurve`: the flag. `Planar`: the flag, point, normal and deviation. `NumCpts`: the count.
/// `Bilinear`, `Parallel`, `Seam`: the flag. `Knots`: status, then on success `n knots m
/// mults`. `TessSplits`: status, then on success `n splits`.
///
@@ -864,21 +853,6 @@ pub fn lazy(test: &TestSurf, scale: f64, query: &Query) -> Result<Vec<f64>, FfiE
opt_dom(o, *dom)?;
o.push(*tol);
}
Query::Extend { dir, dist, cont } => {
finite(&[*dist], "extension distance")?;
if *dist <= 0.0 {
return Err(bad("extension distance not positive"));
}
if dir.is_some_and(|d| d > 8) || *cont > 13 {
return Err(bad("extension enumerator out of range"));
}
o.extend([
if dir.is_some() { 6.0 } else { 5.0 },
f64::from(dir.unwrap_or(0)),
*dist,
f64::from(*cont),
]);
}
Query::Planar { tol } => {
finite(&[*tol], "planar tolerance")?;
o.extend([7.0, *tol]);
@@ -29,8 +29,6 @@
//! | `Trim` | `TrimWithDomain` | `D` |
//! | `Split` | `SplitAt` | `t dir` |
//! | `StepInv` | `STEPInversion` | `D V tol has_guess gu gv` |
//! | `Extend` | `CreateExtendedSurface` | `dist cont` |
//! | `ExtendDir` | `CreateExtendedSurface` (dir) | `dir dist cont` |
//! | `AdjustStep` | `AdjustSTEPUVDomain` | `D` |
//! | `MaxAnal` | `GetMaxAnalyticDomain` | - |
//! | `Copy`, `CopyAnal` | `Copy`, `CopyAndAddAnalytics` | - |
@@ -95,8 +93,6 @@ pub enum Op {
Trim,
Split,
StepInv,
Extend,
ExtendDir,
AdjustStep,
MaxAnal,
Copy,
@@ -149,8 +145,8 @@ impl Op {
Trim => 9,
Split => 10,
StepInv => 11,
Extend => 12,
ExtendDir => 13,
// 12, 13: `CreateExtendedSurface` (both forms), removed with the port's fitted
// extension; not reused.
AdjustStep => 14,
MaxAnal => 15,
Copy => 16,
@@ -474,15 +470,6 @@ fn validate_surface_op(op: Op, rd: &mut Reader<'_>, dom: Option<[f64; 4]>) -> Re
rd.flag()?;
rd.finites(2)?;
}
Extend => {
rd.positive("extension distance")?;
rd.index(13)?;
}
ExtendDir => {
rd.index(8)?;
rd.positive("extension distance")?;
rd.index(13)?;
}
DropFast => {
rd.domain()?;
rd.vec3()?;
@@ -17,8 +17,6 @@
//! | [`create_coons_srf`], [`CoonsWarning`] | `sm_CreateCoonsSrf` (static) |
//! | [`BSplineSurface::create_coons_patch`], [`BSplineSurface::create_coons_patch_on_faces`] | `CreateCoonsPatch` |
//! | `face_side_twist` (private) | the fork's `sm_FaceSideTwist` (static) |
//! | [`BSplineSurface::create_extended_surface`] | `CreateExtendedSurface` (distance from each side) |
//! | [`BSplineSurface::create_extended_surface_dir`] | `CreateExtendedSurface` (with `SmSurfParamType`) |
//! | [`BSplineSurface::join_surface`] | `JoinSurface` |
//! | [`BSplineSurface::create_n_sided_patch`] | `CreateNSidedPatch` |
//! | [`BSplineSurface::create_ruled_surface`] | `CreateRuledSurface` |
@@ -100,13 +98,6 @@
//! `NER` for other curve types) and the caller's
//! [`Surface`](crate::surface::surface::Surface) as base surface (with its kind and
//! owner, as upstream's `SmSurface*`).
//! - **`CreateExtendedSurface`** halves the extension distance after each failed
//! extension until it drops below `dDist / 100`, which never happens for a zero or
//! NaN distance (upstream then loops forever); the port stops after
//! [`MAX_EXTEND_HALVINGS`] halvings with [`SolidError::NotConverged`]. After a
//! successful extension it doubles the distance until it reaches `dDist / 2`, which
//! a negative distance never does (it doubles towards `-inf`, upstream HANG-16); the
//! port stops after [`MAX_EXTEND_DOUBLINGS`] doublings with the same error.
//! - **`N_LinearMultiplyCrv`** returns nothing upstream; its port fails only for a
//! degree-0 cross-boundary derivative (no Greville abscissae), which
//! [`create_coons_srf`] passes on.
@@ -119,7 +110,6 @@ use crate::error::{Result, SolidError};
use crate::geom::axis2_placement::Axis2Placement;
use crate::geom::extent1d::Extent1d;
use crate::geom::extent2d::Extent2d;
use crate::geom::geom_utility::p2::ContinuityType;
use crate::geom::tol::are_same;
use crate::geom::vector2d::Point2;
use crate::geom::vector3d::{Point3, Vec3};
@@ -137,7 +127,6 @@ use crate::nurbs::math::{
combine2_pts, power_basis_srf_eval_pts, srf_power_basis_eval_derivs, CPoint,
};
use crate::nurbs::srf_adv::p1::eval_pt_derivs_unbounded;
use crate::nurbs::srf_adv::p2::{extend_by_dist_in_place, SrfContinuity};
use crate::nurbs::srf_adv::p3::{
create_swung_srf, create_trans_sweep_srf, extend_by_param_dist, ExtendContinuity,
};
@@ -150,20 +139,6 @@ use crate::nurbs::srf_common::ruled_srf;
use crate::nurbs::srf_tool::p3::{join, make_compatible as make_surfaces_compatible};
use crate::nurbs::srf_tool::p4::make_compatible_uv;
/// Largest number of distance halvings of one side of
/// [`BSplineSurface::create_extended_surface_dir`] (upstream has no bound and loops
/// forever for a zero or NaN distance; halving a positive distance reaches zero after
/// fewer than 1100 steps).
pub const MAX_EXTEND_HALVINGS: usize = 1100;
/// Largest number of distance doublings of one side of
/// [`BSplineSurface::create_extended_surface_dir`]. A positive distance is doubled back
/// at most once more than it was halved, so only a negative distance reaches this:
/// upstream doubles it away from the loop's exit (`dExtDist < dDist/2.0`) towards
/// `-inf` and loops forever while the extensions succeed (`plan/upstream-issues.md`
/// HANG-16).
pub const MAX_EXTEND_DOUBLINGS: usize = MAX_EXTEND_HALVINGS + 1;
const OP_BUILD_CURVE: &str = "nurbs::b_spline_surface_n_lib::p1::build_nlib_curve";
const OP_NLIB_CURVE: &str = "nurbs::b_spline_surface_n_lib::p1::create_nlib_curve";
const OP_NLIB_SURFACE: &str = "nurbs::b_spline_surface_n_lib::p1::create_nlib_surface";
@@ -172,8 +147,6 @@ const OP_OFFSET: &str =
const OP_NONRAT: &str = "nurbs::b_spline_surface_n_lib::p1::create_non_rational_curve";
const OP_COONS_SRF: &str = "nurbs::b_spline_surface_n_lib::p1::create_coons_srf";
const OP_COONS: &str = "nurbs::b_spline_surface_n_lib::p1::BSplineSurface::create_coons_patch";
const OP_EXTEND: &str =
"nurbs::b_spline_surface_n_lib::p1::BSplineSurface::create_extended_surface_dir";
const OP_JOIN: &str = "nurbs::b_spline_surface_n_lib::p1::BSplineSurface::join_surface";
const OP_NSIDED: &str = "nurbs::b_spline_surface_n_lib::p1::BSplineSurface::create_n_sided_patch";
const OP_RULED: &str = "nurbs::b_spline_surface_n_lib::p1::BSplineSurface::create_ruled_surface";
@@ -222,25 +195,6 @@ fn surface_nurb<'a>(surface: &'a BSplineSurface, operation: &'static str) -> Res
// ---- unported SmSurface methods ------------------------------------------------------
/// `SmSurface::IsSingularity(uv, dir)`: whether `uv` is a singularity and its
/// direction.
fn is_singularity(k: &Kernel, srf: &BSplineSurface, uv: Point2) -> Result<(bool, SurfParamType)> {
// the SmSurface body (surface.surface_p5), on a surface holding a copy
let surface = crate::surface::surface::Surface::from(srf.clone_exact());
let s = surface.is_singularity(k, uv, crate::math::EFF_ZERO, false)?;
Ok((s.singular, s.direction))
}
/// `SmSurface::IsClosed(domain, dir)`.
fn is_closed(
k: &Kernel,
srf: &BSplineSurface,
domain: &Extent2d,
dir: SurfParamType,
) -> Result<bool> {
Ok(crate::surface::surface::p7::base_is_closed(k, srf, domain, dir, None, false)?.0)
}
/// The curves dropped by `SmSurface::DropCurve` (with its two distance outputs, which
/// the caller here ignores).
struct DroppedCurves {
@@ -1313,199 +1267,6 @@ impl BSplineSurface {
Ok(Self::from_gw_nurb(&sur))
}
/// The surface extended by `dist` across each side ([`SurfParamType::Both`]) with
/// continuity `extension_continuity` (`CreateExtendedSurface(crContext, dDist,
/// eExtensionContinuity, rpExtended)`).
///
/// # Errors
///
/// The errors of [`BSplineSurface::create_extended_surface_dir`].
pub fn create_extended_surface(
&self,
k: &Kernel,
dist: f64,
extension_continuity: ContinuityType,
) -> Result<BSplineSurface> {
// pass the call along
self.create_extended_surface_dir(k, SurfParamType::Both, dist, extension_continuity)
}
/// The surface extended by `dist` across the sides named by `ext_direction`
/// (`CreateExtendedSurface` with `SmSurfParamType`): [`SurfParamType::U`] /
/// [`SurfParamType::V`] both sides of one direction, `UMin`, `VMin`, `UMax`,
/// `VMax` one side, `Both` all four, `Neither` none.
///
/// The continuity is `NL_G1` for [`ContinuityType::G1`] (and any type not listed),
/// `NL_G1R` for `G1R`, `NL_G2` for `G1G2` and `NL_CMAX` for `CInfinity`. Sides at a
/// singularity and directions in which the surface is closed are not extended; a
/// surface closed in both directions is copied. A failing extension is retried with
/// half the distance (down to `dist / 100`), then repeated with doubled distances
/// until about `dist` is reached.
///
/// # Errors
///
/// `SM_ERR` without NURB, when no extension succeeds, a repeated extension fails
/// or an extended surface is invalid; [`SolidError::Unported`] from the
/// singularity and closedness tests.
pub fn create_extended_surface_dir(
&self,
k: &Kernel,
ext_direction: SurfParamType,
dist: f64,
extension_continuity: ContinuityType,
) -> Result<BSplineSurface> {
self.create_extended_surface_dispatched(
ext_direction,
dist,
extension_continuity,
&|uv| is_singularity(k, self, uv),
&|domain, dir| is_closed(k, self, domain, dir),
)
}
/// The body of [`BSplineSurface::create_extended_surface_dir`] with the virtual
/// `IsSingularity(uv, dir)` and `IsClosed(domain, dir)` of the calling object
/// passed in (an `SmPlane` overrides both), as upstream's virtual dispatch.
pub(crate) fn create_extended_surface_dispatched(
&self,
ext_direction: SurfParamType,
dist: f64,
extension_continuity: ContinuityType,
is_singularity: &dyn Fn(Point2) -> Result<(bool, SurfParamType)>,
is_closed: &dyn Fn(&Extent2d, SurfParamType) -> Result<bool>,
) -> Result<BSplineSurface> {
const OP: &str = OP_EXTEND;
// copy input surface into local object
let mut esur = surface_nurb(self, OP)?.clone();
let uv_domain = self.natural_uv_domain();
let mut ext_dist = dist;
// pick continuity level
let gflg = match extension_continuity {
ContinuityType::G1R => SrfContinuity::G1R,
ContinuityType::G1G2 => SrfContinuity::G2,
ContinuityType::CInfinity => SrfContinuity::Cmax,
_ => SrfContinuity::G1, // SM_CT_G1 and anything else
};
// sort out where extension is needed
let u_dir = matches!(
ext_direction,
SurfParamType::U | SurfParamType::UMin | SurfParamType::UMax
);
let v_dir = matches!(
ext_direction,
SurfParamType::V | SurfParamType::VMin | SurfParamType::VMax
);
let ext_u_or_v = if u_dir {
SurfParamType::U
} else if v_dir {
SurfParamType::V
} else {
SurfParamType::Both
};
let neither = ext_direction == SurfParamType::Neither;
let mut skip_umin = neither || v_dir || ext_direction == SurfParamType::UMax;
let mut skip_vmin = neither || u_dir || ext_direction == SurfParamType::VMax;
let mut skip_umax = neither || v_dir || ext_direction == SurfParamType::UMin;
let mut skip_vmax = neither || u_dir || ext_direction == SurfParamType::VMin;
// Don't extend through singularities in U or V
let (sing, sing_direction) = is_singularity(uv_domain.min())?;
if sing {
if sing_direction == SurfParamType::U {
skip_vmin = true;
} else {
skip_umin = true;
}
}
let (sing, sing_direction) = is_singularity(uv_domain.max())?;
if sing {
if sing_direction == SurfParamType::U {
skip_vmax = true;
} else {
skip_umax = true;
}
}
// when asked and allowed - extend in U direction
if (ext_u_or_v == SurfParamType::U || ext_u_or_v == SurfParamType::Both)
&& !is_closed(&uv_domain, SurfParamType::U)?
{
// when extending in the UMin direction
if !skip_umin {
extend_side(
&mut esur,
dist,
&mut ext_dist,
Direction::U,
CurveEnd::Start,
gflg,
)?;
}
// reset the extension distance
ext_dist = dist;
// when asked - extend in the UMax direction
if !skip_umax {
extend_side(
&mut esur,
dist,
&mut ext_dist,
Direction::U,
CurveEnd::End,
gflg,
)?;
}
}
// when asked - extend in the V direction
if (ext_u_or_v == SurfParamType::V || ext_u_or_v == SurfParamType::Both)
&& !is_closed(&uv_domain, SurfParamType::V)?
{
// reset extension distance
ext_dist = dist;
// when asked - extend in the V Min direction
if !skip_vmin {
extend_side(
&mut esur,
dist,
&mut ext_dist,
Direction::V,
CurveEnd::Start,
gflg,
)?;
}
// reset the extension distance
ext_dist = dist;
// when asked - extend in the V Max direction
if !skip_vmax {
extend_side(
&mut esur,
dist,
&mut ext_dist,
Direction::V,
CurveEnd::End,
gflg,
)?;
}
}
// build output SmBSplineSurface from esur SURFACE object
if is_closed(&uv_domain, SurfParamType::U)? && is_closed(&uv_domain, SurfParamType::V)? {
// surfaces closed in both directions - don't get extended - copy input
Ok(self.copy())
} else {
Ok(Self::from_gw_nurb(&esur))
}
}
/// Joins the surface with `surface_to_join` along a common boundary
/// (`JoinSurface`): with [`SurfParamType::U`] the v-maximum isocurve of `self`
/// meets the v-minimum isocurve of `surface_to_join` (made compatible in v), with
@@ -2135,69 +1896,6 @@ impl BSplineSurface {
}
}
/// One side of `CreateExtendedSurface`: extends `esur` across `end` of `dir` by
/// `ext_dist`, halving the distance after each failure (`SM_ERR` below
/// `dist / 100`), checks validity, then repeats with doubled distances while the
/// distance is below `dist / 2` (at most [`MAX_EXTEND_DOUBLINGS`] times).
fn extend_side(
esur: &mut Surface,
dist: f64,
ext_dist: &mut f64,
dir: Direction,
end: CurveEnd,
gflg: SrfContinuity,
) -> Result<()> {
const OP: &str = OP_EXTEND;
// extend Surf - shorter dists until extension succeeds (in place: a failed
// extension can leave its extended surface in esur, which the retry extends)
let mut halvings = 0usize;
loop {
match extend_by_dist_in_place(esur, *ext_dist, dir, end, gflg) {
Ok(()) => break,
Err(e @ (SolidError::Unported { .. } | SolidError::Cancelled { .. })) => return Err(e),
Err(_) => {
*ext_dist /= 2.0;
if *ext_dist < dist / 100.0 {
return Err(SolidError::sm_err(OP));
}
// upstream loops forever when dist is 0 or NaN
halvings += 1;
if halvings > MAX_EXTEND_HALVINGS {
return Err(SolidError::NotConverged {
operation: OP,
iterations: u32::try_from(MAX_EXTEND_HALVINGS).unwrap_or(u32::MAX),
});
}
}
}
}
// quit - when extended surface is no good
if esur.is_valid().is_err() {
return Err(SolidError::sm_err(OP));
}
// if needed - multiple extensions until Extension gets to be near requested size
let mut doublings = 0usize;
while *ext_dist < dist / 2.0 {
// upstream loops forever for a negative distance (HANG-16)
doublings += 1;
if doublings > MAX_EXTEND_DOUBLINGS {
return Err(SolidError::NotConverged {
operation: OP,
iterations: u32::try_from(MAX_EXTEND_DOUBLINGS).unwrap_or(u32::MAX),
});
}
*ext_dist *= 2.0;
extend_by_dist_in_place(esur, *ext_dist, dir, end, gflg).map_err(SolidError::nl_ser(OP))?;
if esur.is_valid().is_err() {
return Err(SolidError::sm_err(OP));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
@@ -2358,14 +2056,6 @@ mod tests {
assert_eq!(s.natural_uv_domain().max().x, 2.0);
}
#[test]
fn extended_surface_runs() {
let k = &Kernel::default();
let s = plane_surface();
let r = s.create_extended_surface(k, 0.1, ContinuityType::G1);
assert!(r.is_ok(), "{r:?}");
}
#[test]
fn missing_nurb() {
let s = BSplineSurface::new();
@@ -2378,18 +2068,6 @@ mod tests {
));
}
#[test]
fn negative_extension_distance_stops() {
// upstream doubles a negative distance towards -inf forever (HANG-16)
let k = &Kernel::default();
let r = plane_surface().create_extended_surface(k, -5.0, ContinuityType::G1R);
assert!(matches!(r, Err(SolidError::NotConverged { .. })), "{r:?}");
// a positive distance is unaffected
assert!(plane_surface()
.create_extended_surface(k, 0.5, ContinuityType::G1R)
.is_ok());
}
#[test]
fn make_knots_absurd_sizes_are_errors() {
// fuzz regression: n + p + 2 overflowed
+2 -25
View File
@@ -1,12 +1,10 @@
//! Surface extension by a distance, maximal second derivatives of an offset surface,
//! evaluation outside the parameter domain (by extension or periodic wrap), skinning
//! Maximal second derivatives of an offset surface, evaluation outside the parameter domain (by extension or periodic wrap), skinning
//! along a spine curve, and cross-boundary derivative strips from a vector field.
//!
//! Upstream: `source/SMLib/src/NL_SrfAdv.cpp`, `source/SMLib/inc/NL_SrfAdv.h`.
//!
//! | Rust | Upstream |
//! |-------------------------------------------------------|------------------------------------|
//! | [`extend_by_dist`] | `N_SrfExtendByDist` |
//! | [`offset_get_max_deriv`], [`MaxSecondDerivs`] | `N_SrfOffsetGetMaxDeriv` |
//! | [`eval_pt_derivs_unbounded`], [`UnboundedExtension`] | `N_SrfEvalPtDerivsUnbounded` |
//! | [`create_skin_spine`], [`SkinSpineOptions`], [`SpineAlignment`], [`SkinDerivs`] | `N_CreateSkinSpine` |
@@ -68,7 +66,7 @@ use crate::nurbs::geom::{dist_cpt_cpt, dist_pt_pt, transform_matrix_from_axes, F
use crate::nurbs::index::{at, at2, oob, put};
use crate::nurbs::knots::{KnotVector, Side};
use crate::nurbs::math::{CPoint, MODEL_TOL};
use crate::nurbs::srf_adv::p2::{eval_pt_normal_deriv, extend_by_dist_at_depth, SrfContinuity};
use crate::nurbs::srf_adv::p2::eval_pt_normal_deriv;
use crate::nurbs::srf_adv::p3::{create_skin_srf, extend_by_param_dist, ExtendContinuity};
use crate::nurbs::srf_adv::p4::create_b_vectors;
use crate::nurbs::srf_basic::{Direction, Surface};
@@ -88,27 +86,6 @@ const OP_UNB: &str = "nurbs::srf_adv::p1::eval_pt_derivs_unbounded";
const OP_SPINE: &str = "nurbs::srf_adv::p1::create_skin_spine";
const OP_VFIELD: &str = "nurbs::srf_adv::p1::cross_boundary_derivs_vector_field";
// ---- N_SrfExtendByDist ----------------------------------------------------------------
/// Extends `sur` across the boundary `end` of direction `dir` by approximately the
/// distance `dist` (upstream `N_SrfExtendByDist`), with the continuity `cont` at the
/// old boundary. This is the entry point of the recursive
/// [`extend_by_dist_at_depth`] at depth `0`.
///
/// # Errors
///
/// The errors of [`extend_by_dist_at_depth`].
pub fn extend_by_dist(
sur: &Surface,
dist: f64,
dir: Direction,
end: CurveEnd,
cont: SrfContinuity,
) -> Result<Surface> {
// Pass the call along to the recursive function.
extend_by_dist_at_depth(sur, dist, dir, end, cont, 0)
}
// ---- N_SrfOffsetGetMaxDeriv -----------------------------------------------------------
/// Bounds of the second derivatives of the offset of a surface (upstream `Muu`,
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -1,10 +1,10 @@
//! A surface extended past one side of its parameter box by a distance measured across that
//! side, exactly: [`Surface::extended_by_distance`].
//!
//! Not upstream: the kernel's own extension (`N_SrfExtendByDist`, `srf_adv::p2`) estimates
//! Not upstream: upstream's extension (`CreateExtendedSurface` on `N_SrfExtendByDist`) estimates
//! its distance, fits a rational curvature-continuous extension by least squares and ends by
//! removing knots within a tolerance, which can move the source's own part. This is a separate
//! exact path; that one and its callers are unchanged.
//! removing knots within a tolerance, which can move the source's own part. It is not ported;
//! every surface extension in the kernel is this exact one.
//!
//! **The construction.** The surface's B-spline form (an analytic kind's only once proved on
//! its label, [`AnalyticSupport::check`]) is worked on its homogeneous net `(H, w)`, one row of
+2 -2
View File
@@ -3,8 +3,8 @@
//! callers that extend a surface "on every side" take.
//!
//! Not upstream: upstream's `CreateExtendedSurface` extends every side by an estimated
//! distance, fits its extension by least squares and removes knots within a tolerance; this
//! is the exact path side by side.
//! distance, fits its extension by least squares and removes knots within a tolerance; it is
//! not ported, this exact path replaces it.
//!
//! Each asked side, in [`BoxSide::ALL`]'s order, is extended on the box the sides before it
//! left (the result's natural box), so a later side runs along the earlier extensions. A side
+1 -49
View File
@@ -10,7 +10,6 @@
//! | [`OffsetSurface`], [`OffsetSurface::new`] | `SmOffsetSurface`, `SmOffsetSurface(d, rSurface, bOwnsSurface, lSingularities)`, `~SmOffsetSurface` (`Drop`) |
//! | [`OffsetSurface::copy`] | `Copy` |
//! | [`OffsetSurface::approx_eq`] | `operator==` |
//! | [`OffsetSurface::create_extended_surface`], [`OffsetSurface::create_extended_surface_dir`] | `CreateExtendedSurface` (both) |
//! | [`OffsetSurface::calculate_bounding_box`] | `CalculateBoundingBox` |
//! | `extend_surface` (private) | `sm_ExtendSurface` (static) |
//! | `extend_as_itself` (private) | the fork's `sm_ExtendAsItself` (static; WR-633, WR-650) |
@@ -56,7 +55,7 @@
//! - **Context.** Every surface has a context here, so the clamp-instead-of-extend
//! branch for `GetContext() == NULL` is never taken; `SetContext` has nothing to set.
//! The constructor and every member that constructs an offset ([`OffsetSurface::copy`],
//! [`OffsetSurface::split_at`], [`OffsetSurface::create_extended_surface_dir`],
//! [`OffsetSurface::split_at`],
//! [`OffsetSurface::create_offset_surface`], [`OffsetSurface::read_from_db`]) take the
//! kernel for the base's `GetSingularities`.
//! - **Extension cache.** Upstream's `const` evaluators (`EvaluatePoint`, `Evaluate`,
@@ -118,7 +117,6 @@ use crate::geom::axis2_placement::Axis2Placement;
use crate::geom::extent1d::Extent1d;
use crate::geom::extent2d::{Extent2d, SS_UMAX, SS_UMIN, SS_VMAX, SS_VMIN};
use crate::geom::extent3d::Extent3d;
use crate::geom::geom_utility::p2::ContinuityType;
use crate::geom::object::ObjectRef;
use crate::geom::polar_box::{PolarBox, PolarBoxState};
use crate::geom::pseudo_box::PseudoBox;
@@ -776,52 +774,6 @@ impl OffsetSurface {
// ---- new surfaces ---------------------------------------------------------------------
/// An offset of the base surface extended by `dist` on every side
/// (`CreateExtendedSurface(ctx, dist, continuity, ..)`: the directional overload
/// with `SM_SP_BOTH`).
///
/// # Errors
///
/// Those of [`OffsetSurface::create_extended_surface_dir`].
pub fn create_extended_surface(
&self,
k: &Kernel,
dist: f64,
extension_continuity: ContinuityType,
) -> Result<Surface> {
// pass the call along
self.create_extended_surface_dir(k, SurfParamType::Both, dist, extension_continuity)
}
/// An offset (same distance, owning, default singularities) of the base surface
/// extended by `dist` in `ext_direction` (`CreateExtendedSurface` with a direction).
///
/// # Errors
///
/// Those of the base surface's [`Surface::create_extended_surface_dir`] and of
/// [`OffsetSurface::new`].
pub fn create_extended_surface_dir(
&self,
k: &Kernel,
ext_direction: SurfParamType,
dist: f64,
extension_continuity: ContinuityType,
) -> Result<Surface> {
let surface = self.surface.create_extended_surface_dir(
k,
ext_direction,
dist,
extension_continuity,
)?;
Ok(Surface::from(Self::new(
k,
self.offset_distance.get(),
surface,
true,
SS_UNKNOWN,
)?))
}
/// The fork's `SmOffsetSurface::ContinuesAsItself` (tangency-labs/smlib@db4a655):
/// whether `surface` continues as itself past its domain ([`Self::continue_as_itself`]):
/// a cylinder, cone, sphere or torus.
-60
View File
@@ -38,7 +38,6 @@
//! | [`Plane::is_kind_of`], [`Plane::TYPE`], [`Plane::type_string`], [`Plane::class_string`] | `IsKindOf`, `GetType`, `GetTypeString`, `GetClassString` (`SM_COMMON`) |
//! | [`Plane::base`], [`Plane::base_with_nurb`], [`Plane::natural_uv_domain`] | the `SmBSplineSurface` part, `GetOrCreateGwNurbPointer`, `GetNaturalUVDomain` (inherited) |
//! | [`Plane::max_analytic_domain`], [`Plane::is_parallel_to_vector`], [`Plane::is_parallel_to_vector_opt_in`], [`Plane::is_parallel_to_vector_with_kernel`], [`Plane::reverse`], [`Plane::swap_uv`] | `SmBSplineSurface::GetMaxAnalyticDomain`, `IsParallelToVector`, `Reverse`, `SwapUV` (inherited, re-dispatched) |
//! | [`Plane::create_extended_surface`], [`Plane::create_extended_surface_dir`] | `SmBSplineSurface::CreateExtendedSurface` (both overloads; inherited, re-dispatched to the `IsSingularity` / `IsClosed` overrides) |
//!
//! Deviations:
//! - **`SwapUV` keeps the plane's definition (WR-701, opt-in).** With
@@ -548,46 +547,6 @@ impl Plane {
(false, SurfParamType::Neither)
}
/// `SmBSplineSurface::CreateExtendedSurface(dDist, ..)` (inherited): the plane's
/// NURB extended by `dist` across each side, through the `SmPlane` overrides of
/// `IsSingularity` and `IsClosed` (none, never closed).
///
/// # Errors
///
/// Those of [`Plane::create_extended_surface_dir`].
pub fn create_extended_surface(
&self,
dist: f64,
extension_continuity: ContinuityType,
) -> Result<BSplineSurface> {
// pass the call along
self.create_extended_surface_dir(SurfParamType::Both, dist, extension_continuity)
}
/// `SmBSplineSurface::CreateExtendedSurface(eExtDirection, dDist, ..)` (inherited):
/// [`BSplineSurface::create_extended_surface_dir`] of the plane's NURB, with the
/// virtual `IsSingularity` and `IsClosed` dispatched to the `SmPlane` overrides
/// ([`Plane::is_singularity`], [`Plane::is_closed`]) as upstream.
///
/// # Errors
///
/// `SM_ERR` when no NURB can be made (upstream copies a `NULL` NURB) and the
/// errors of the extension.
pub fn create_extended_surface_dir(
&self,
ext_direction: SurfParamType,
dist: f64,
extension_continuity: ContinuityType,
) -> Result<BSplineSurface> {
self.base_for_query().create_extended_surface_dispatched(
ext_direction,
dist,
extension_continuity,
&|_| Ok(self.is_singularity()),
&|_, _| Ok(self.is_closed().0),
)
}
/// `SmBSplineSurface::GetMaxAnalyticDomain` (inherited): planar, so the natural
/// domain expanded relatively by 100.
#[must_use]
@@ -2765,25 +2724,6 @@ mod tests {
.is_ok());
}
#[test]
fn extension_uses_plane_overrides() {
// the B-spline IsSingularity / IsClosed are unported; the plane's are not
let p = Plane::new(
Point3::ZERO,
Vec3::new(1.0, 0.0, 0.0),
Vec3::new(0.0, 1.0, 0.0),
Vec2::new(1.0, 1.0),
&Extent2d::from_bounds(0.0, 0.0, 1.0, 1.0),
);
let e = p.create_extended_surface(0.5, ContinuityType::G1).unwrap();
let d = e.natural_uv_domain();
assert!(d.max().x - d.min().x > 1.0);
let e = p
.create_extended_surface_dir(SurfParamType::UMax, 0.5, ContinuityType::G1)
.unwrap();
assert!(e.gw_nurb().is_some());
}
#[test]
fn wr41_plane_reparam_default_keeps_origin() {
let mut p = Plane::new(
+7 -255
View File
@@ -21,7 +21,6 @@
//! | [`Surface::owner`] | `GetOwner` |
//! | [`Surface::copy`], [`Surface::copy_and_add_analytics`] | `Copy`, `CopyAndAddAnalytics` |
//! | [`Surface::adjust_step_uv_domain`] | `AdjustSTEPUVDomain` |
//! | [`Surface::create_extended_surface`], [`Surface::create_extended_surface_dir`] | `CreateExtendedSurface` (both) |
//! | [`Surface::drop_point_fast`] | `DropPointFast` |
//! | [`Surface::evaluate`], [`Surface::evaluate_point`] | `Evaluate`, `EvaluatePoint` |
//! | [`Surface::natural_uv_domain_if_built`], [`Surface::step_uv_domain`] | `GetNaturalUVDomain`, `GetSTEPUVDomain` |
@@ -3793,257 +3792,15 @@ impl Surface {
}
}
/// The surface extended by `dist` on every side (`CreateExtendedSurface`; the
/// kernel is the context the new surface is constructed in).
///
/// # Errors
///
/// Those of the kind's override (the base default is `SM_ERR`).
pub fn create_extended_surface(
&self,
k: &Kernel,
dist: f64,
extension_continuity: ContinuityType,
) -> Result<Self> {
let made = self.create_extended_surface_of_kind(k, dist, extension_continuity)?;
Ok(self.carry_exact_round_on_extension(made, extension_continuity))
}
/// An exact round's definition carried onto `extension`, this surface extended by the
/// same NURBS pieces continued (`CInfinity`, #2487): the analytic class continued, still
/// on S. Another continuity (a ruled `G1R` extension) leaves S, so it carries none, and
/// so does a corner's face (#2488), whose net continued leaves its patch's definition.
fn carry_exact_round_on_extension(
&self,
extension: Self,
extension_continuity: ContinuityType,
) -> Self {
let round = matches!(self.definition.as_deref(), Some(KeptDefinition::Round(_)));
if round && extension_continuity == ContinuityType::CInfinity {
self.carry_definition(extension)
} else {
extension
}
}
/// An exact round's definition carried onto `continuation`, this surface's own NURBS
/// net continued Natural (`surface::extend`'s analytic kinds): still on S, as
/// [`Surface::create_extended_surface`]'s `CInfinity` carries it (#2487).
/// net continued Natural (`surface::extend`'s analytic kinds): the analytic class
/// continued, still on S (#2487). A corner's face (#2488), whose net continued leaves its
/// patch's definition, carries none.
pub(crate) fn carry_round_on_continuation(&self, continuation: Self) -> Self {
self.carry_exact_round_on_extension(continuation, ContinuityType::CInfinity)
}
/// [`Surface::create_extended_surface`] of the kind alone.
fn create_extended_surface_of_kind(
&self,
k: &Kernel,
dist: f64,
extension_continuity: ContinuityType,
) -> Result<Self> {
match self.kind_with_nurb(k)? {
SurfaceKind::BSpline(s) => Ok(Self::from(s.create_extended_surface(
k,
dist,
extension_continuity,
)?)),
// (the fork's SmBallPatch inherits SmBSplineSurface's: the net extended, a
// plain B-spline)
SurfaceKind::BallPatch(s) => Ok(Self::from(s.net().create_extended_surface(
k,
dist,
extension_continuity,
)?)),
// SmBSplineSurface's: the net extended, its singularity and closure tests
// (virtual, on the blend) taking the definition's points
SurfaceKind::ProceduralBlend(pb) => {
Ok(Self::from(pb.net().create_extended_surface_dispatched(
SurfParamType::Both,
dist,
extension_continuity,
&|uv| {
self.is_singularity(k, uv, EFF_ZERO, false)
.map(|r| (r.singular, r.direction))
},
&|domain, param| self.is_closed(k, domain, param, None, false).map(|r| r.0),
)?))
}
SurfaceKind::CurveBounded(_) | SurfaceKind::Step(_) | SurfaceKind::SrfInVolume(_) => {
base_create_extended_surface()
}
SurfaceKind::Plane(s) => Ok(Self::from(
s.create_extended_surface(dist, extension_continuity)?,
)),
SurfaceKind::Sphere(s) => Ok(Self::from(
s.base().base().create_extended_surface_dispatched(
SurfParamType::Both,
dist,
extension_continuity,
&|uv| {
self.is_singularity(k, uv, EFF_ZERO, false)
.map(|r| (r.singular, r.direction))
},
&|domain, param| self.is_closed(k, domain, param, None, false).map(|r| r.0),
)?,
)),
SurfaceKind::Torus(s) => Ok(Self::from(
s.base().base().create_extended_surface_dispatched(
SurfParamType::Both,
dist,
extension_continuity,
&|uv| {
self.is_singularity(k, uv, EFF_ZERO, false)
.map(|r| (r.singular, r.direction))
},
&|domain, param| self.is_closed(k, domain, param, None, false).map(|r| r.0),
)?,
)),
SurfaceKind::Cone(s) => Ok(Self::from(
s.rev().base().create_extended_surface_dispatched(
SurfParamType::Both,
dist,
extension_continuity,
&|uv| {
self.is_singularity(k, uv, EFF_ZERO, false)
.map(|r| (r.singular, r.direction))
},
&|domain, param| self.is_closed(k, domain, param, None, false).map(|r| r.0),
)?,
)),
SurfaceKind::OffsetSurface(s) => {
s.create_extended_surface(k, dist, extension_continuity)
}
SurfaceKind::SurfOfRevolution(s) => Ok(Self::from(s.base().create_extended_surface(
k,
dist,
extension_continuity,
)?)),
// (`GetOrCreateGwNurbPointer` makes a recognised extrusion's NURB)
SurfaceKind::SurfOfExtrusion(s) => Ok(Self::from(
s.base_with_nurb(k)?
.create_extended_surface(k, dist, extension_continuity)?,
)),
}
}
/// The surface extended by `dist` in `ext_direction` (`CreateExtendedSurface`
/// with a direction; the kernel is the context the new surface is constructed in).
///
/// # Errors
///
/// Those of the kind's override (the base default is `SM_ERR`).
pub fn create_extended_surface_dir(
&self,
k: &Kernel,
ext_direction: SurfParamType,
dist: f64,
extension_continuity: ContinuityType,
) -> Result<Self> {
crate::profile::profile_scope!(k, "surface::create_extended_surface_dir");
let made =
self.create_extended_surface_dir_of_kind(k, ext_direction, dist, extension_continuity)?;
Ok(self.carry_exact_round_on_extension(made, extension_continuity))
}
/// [`Surface::create_extended_surface_dir`] of the kind alone.
fn create_extended_surface_dir_of_kind(
&self,
k: &Kernel,
ext_direction: SurfParamType,
dist: f64,
extension_continuity: ContinuityType,
) -> Result<Self> {
match self.kind_with_nurb(k)? {
SurfaceKind::BSpline(s) => Ok(Self::from(s.create_extended_surface_dir(
k,
ext_direction,
dist,
extension_continuity,
)?)),
// (the fork's SmBallPatch inherits SmBSplineSurface's: the net extended)
SurfaceKind::BallPatch(s) => Ok(Self::from(s.net().create_extended_surface_dir(
k,
ext_direction,
dist,
extension_continuity,
)?)),
// SmBSplineSurface's: the net extended, its singularity and closure tests
// (virtual, on the blend) taking the definition's points
SurfaceKind::ProceduralBlend(pb) => {
Ok(Self::from(pb.net().create_extended_surface_dispatched(
ext_direction,
dist,
extension_continuity,
&|uv| {
self.is_singularity(k, uv, EFF_ZERO, false)
.map(|r| (r.singular, r.direction))
},
&|domain, param| self.is_closed(k, domain, param, None, false).map(|r| r.0),
)?))
}
SurfaceKind::CurveBounded(_) | SurfaceKind::Step(_) | SurfaceKind::SrfInVolume(_) => {
base_create_extended_surface()
}
SurfaceKind::Plane(s) => Ok(Self::from(s.create_extended_surface_dir(
ext_direction,
dist,
extension_continuity,
)?)),
SurfaceKind::Sphere(s) => Ok(Self::from(
s.base().base().create_extended_surface_dispatched(
ext_direction,
dist,
extension_continuity,
&|uv| {
self.is_singularity(k, uv, EFF_ZERO, false)
.map(|r| (r.singular, r.direction))
},
&|domain, param| self.is_closed(k, domain, param, None, false).map(|r| r.0),
)?,
)),
SurfaceKind::Torus(s) => Ok(Self::from(
s.base().base().create_extended_surface_dispatched(
ext_direction,
dist,
extension_continuity,
&|uv| {
self.is_singularity(k, uv, EFF_ZERO, false)
.map(|r| (r.singular, r.direction))
},
&|domain, param| self.is_closed(k, domain, param, None, false).map(|r| r.0),
)?,
)),
SurfaceKind::Cone(s) => Ok(Self::from(
s.rev().base().create_extended_surface_dispatched(
ext_direction,
dist,
extension_continuity,
&|uv| {
self.is_singularity(k, uv, EFF_ZERO, false)
.map(|r| (r.singular, r.direction))
},
&|domain, param| self.is_closed(k, domain, param, None, false).map(|r| r.0),
)?,
)),
SurfaceKind::OffsetSurface(s) => {
s.create_extended_surface_dir(k, ext_direction, dist, extension_continuity)
}
SurfaceKind::SurfOfRevolution(s) => {
Ok(Self::from(s.base().create_extended_surface_dir(
k,
ext_direction,
dist,
extension_continuity,
)?))
}
// (`GetOrCreateGwNurbPointer` makes a recognised extrusion's NURB)
SurfaceKind::SurfOfExtrusion(s) => Ok(Self::from(
s.base_with_nurb(k)?.create_extended_surface_dir(
k,
ext_direction,
dist,
extension_continuity,
)?,
)),
if matches!(self.definition.as_deref(), Some(KeptDefinition::Round(_))) {
self.carry_definition(continuation)
} else {
continuation
}
}
@@ -5150,11 +4907,6 @@ pub const fn base_adjust_step_uv_domain() -> Result<()> {
Err(base_err())
}
/// `SmSurface::CreateExtendedSurface` (both overloads): `SM_ERR`.
pub const fn base_create_extended_surface() -> Result<Surface> {
Err(base_err())
}
/// `SmSurface::DropPointFast`: `SM_ERR`.
pub const fn base_drop_point_fast<O>() -> Result<SolutionArray<O>> {
Err(base_err())
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -982,203 +982,6 @@
]
}
},
{
"name": "surf1_extend_cinfinity",
"upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:4627 my_test_surface_interface (surface: the surf1 points as control net; ApproximatePoints belongs to another unit) (CreateExtendedSurface 0.5 SM_CT_CINFINITY)",
"inputs": {
"this": {
"srf": {
"net": [
[
[
0,
0,
-0.6,
-1.7976931348623157e+308
],
[
1,
0.1,
0.4,
-1.7976931348623157e+308
],
[
2,
0.2,
0.3,
-1.7976931348623157e+308
],
[
3,
0.3,
0,
-1.7976931348623157e+308
]
],
[
[
0,
1,
0.2,
-1.7976931348623157e+308
],
[
1,
1,
1.5,
-1.7976931348623157e+308
],
[
2,
1,
1.6,
-1.7976931348623157e+308
],
[
3,
1,
0.2,
-1.7976931348623157e+308
]
],
[
[
0,
2,
0.3,
-1.7976931348623157e+308
],
[
1,
2,
1.6,
-1.7976931348623157e+308
],
[
2,
2,
1.5,
-1.7976931348623157e+308
],
[
3,
2,
0.4,
-1.7976931348623157e+308
]
],
[
[
0,
3,
0.4,
-1.7976931348623157e+308
],
[
1,
3,
1.2,
-1.7976931348623157e+308
],
[
2,
3,
1.1,
-1.7976931348623157e+308
],
[
3,
3,
0.7,
-1.7976931348623157e+308
]
],
[
[
0,
4,
0.4,
-1.7976931348623157e+308
],
[
1,
4,
-1.2,
-1.7976931348623157e+308
],
[
2,
4,
-1.1,
-1.7976931348623157e+308
],
[
3,
4,
0.5,
-1.7976931348623157e+308
]
],
[
[
0,
5,
0.4,
-1.7976931348623157e+308
],
[
1,
5,
0.2,
-1.7976931348623157e+308
],
[
2,
5,
0.1,
-1.7976931348623157e+308
],
[
3,
5,
0.5,
-1.7976931348623157e+308
]
]
],
"p": 3,
"q": 3,
"u": [
0,
0,
0,
0,
0.3333333333333333,
0.6666666666666666,
1,
1,
1,
1
],
"v": [
0,
0,
0,
0,
1,
1,
1,
1
]
}
},
"op": "extend",
"dir_overload": false,
"dir": 3,
"dist": 0.5,
"cont": 13
}
},
{
"name": "sweep_translational",
"upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5715 my_test_bsplinesurface_nlib (CreateSweepSurface, translational)",
-827
View File
@@ -1,827 +0,0 @@
{
"cases": [
{
"name": "extend_unit",
"inputs": {
"make": {
"kind": "bounded",
"o": [
0,
0,
0
],
"x": [
1,
0,
0
],
"y": [
0,
1,
0
],
"scale": [
1,
1
],
"domain": [
0,
0,
1,
1
]
},
"ops": [
{
"op": "extend",
"dist": 0.25,
"cont": 3
},
{
"op": "extend",
"dist": 0.25,
"cont": 4
},
{
"op": "extend",
"dist": 0.25,
"cont": 5
},
{
"op": "extend",
"dist": 0.25,
"cont": 13
},
{
"op": "extend",
"dist": 0.25,
"cont": 2
},
{
"op": "extend",
"dir": 0,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 1,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 2,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 3,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 4,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 5,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 6,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 7,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 8,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 3,
"dist": 10.0,
"cont": 4
},
{
"op": "state"
}
]
}
},
{
"name": "extend_scaled",
"inputs": {
"make": {
"kind": "bounded",
"o": [
1,
2,
3
],
"x": [
0.6,
0.8,
0
],
"y": [
-0.8,
0.6,
0
],
"scale": [
2,
3
],
"domain": [
-1,
-2,
3,
4
]
},
"ops": [
{
"op": "extend",
"dist": 0.25,
"cont": 3
},
{
"op": "extend",
"dist": 0.25,
"cont": 4
},
{
"op": "extend",
"dist": 0.25,
"cont": 5
},
{
"op": "extend",
"dist": 0.25,
"cont": 13
},
{
"op": "extend",
"dist": 0.25,
"cont": 2
},
{
"op": "extend",
"dir": 0,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 1,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 2,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 3,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 4,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 5,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 6,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 7,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 8,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 3,
"dist": 10.0,
"cont": 4
},
{
"op": "state"
}
]
}
},
{
"name": "extend_infinite",
"inputs": {
"make": {
"kind": "normal",
"o": [
1,
2,
3
],
"n": [
0,
0,
1
]
},
"ops": [
{
"op": "extend",
"dist": 0.25,
"cont": 3
},
{
"op": "extend",
"dist": 0.25,
"cont": 4
},
{
"op": "extend",
"dist": 0.25,
"cont": 5
},
{
"op": "extend",
"dist": 0.25,
"cont": 13
},
{
"op": "extend",
"dist": 0.25,
"cont": 2
},
{
"op": "extend",
"dir": 0,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 1,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 2,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 3,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 4,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 5,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 6,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 7,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 8,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 3,
"dist": 10.0,
"cont": 4
},
{
"op": "state"
}
]
}
},
{
"name": "extend_canonical",
"inputs": {
"make": {
"kind": "canonical",
"o": [
0,
0,
1
],
"x": [
1,
0,
0
],
"y": [
0,
0,
1
]
},
"ops": [
{
"op": "extend",
"dist": 0.25,
"cont": 3
},
{
"op": "extend",
"dist": 0.25,
"cont": 4
},
{
"op": "extend",
"dist": 0.25,
"cont": 5
},
{
"op": "extend",
"dist": 0.25,
"cont": 13
},
{
"op": "extend",
"dist": 0.25,
"cont": 2
},
{
"op": "extend",
"dir": 0,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 1,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 2,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 3,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 4,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 5,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 6,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 7,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 8,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 3,
"dist": 10.0,
"cont": 4
},
{
"op": "state"
}
]
}
},
{
"name": "extend_degen",
"inputs": {
"make": {
"kind": "bounded",
"o": [
0,
0,
0
],
"x": [
1,
0,
0
],
"y": [
0,
1,
0
],
"scale": [
1,
1
],
"domain": [
1,
0,
1,
1
]
},
"ops": [
{
"op": "extend",
"dist": 0.25,
"cont": 3
},
{
"op": "extend",
"dist": 0.25,
"cont": 4
},
{
"op": "extend",
"dist": 0.25,
"cont": 5
},
{
"op": "extend",
"dist": 0.25,
"cont": 13
},
{
"op": "extend",
"dist": 0.25,
"cont": 2
},
{
"op": "extend",
"dir": 0,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 1,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 2,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 3,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 4,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 5,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 6,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 7,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 8,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 3,
"dist": 10.0,
"cont": 4
},
{
"op": "state"
}
]
}
},
{
"name": "extend_inverted",
"inputs": {
"make": {
"kind": "bounded",
"o": [
0,
0,
0
],
"x": [
1,
0,
0
],
"y": [
0,
1,
0
],
"scale": [
1,
1
],
"domain": [
1,
1,
0,
0
]
},
"ops": [
{
"op": "extend",
"dist": 0.25,
"cont": 3
},
{
"op": "extend",
"dist": 0.25,
"cont": 4
},
{
"op": "extend",
"dist": 0.25,
"cont": 5
},
{
"op": "extend",
"dist": 0.25,
"cont": 13
},
{
"op": "extend",
"dist": 0.25,
"cont": 2
},
{
"op": "extend",
"dir": 0,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 1,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 2,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 3,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 4,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 5,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 6,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 7,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 8,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dir": 3,
"dist": 10.0,
"cont": 4
},
{
"op": "state"
}
]
}
},
{
"name": "extend_after_toggle",
"inputs": {
"make": {
"kind": "bounded",
"o": [
0,
0,
0
],
"x": [
1,
0,
0
],
"y": [
0,
1,
0
],
"scale": [
2,
3
],
"domain": [
0,
0,
1,
2
]
},
"ops": [
{
"op": "make_nurb"
},
{
"op": "toggle"
},
{
"op": "extend",
"dir": 5,
"dist": 0.5,
"cont": 3
},
{
"op": "extend",
"dist": 0.5,
"cont": 5
},
{
"op": "state"
}
]
}
},
{
"name": "extend_after_swap",
"inputs": {
"make": {
"kind": "bounded",
"o": [
0,
0,
0
],
"x": [
1,
0,
0
],
"y": [
0,
1,
0
],
"scale": [
2,
3
],
"domain": [
0,
0,
1,
2
]
},
"ops": [
{
"op": "swap"
},
{
"op": "extend",
"dir": 7,
"dist": 0.5,
"cont": 3
},
{
"op": "reverse"
},
{
"op": "extend",
"dist": 0.5,
"cont": 3
},
{
"op": "state"
}
]
}
}
]
}
File diff suppressed because it is too large Load Diff
-79
View File
@@ -239,24 +239,6 @@
"dir": 0
}
},
{
"name": "Surface_extend",
"upstream_ref": "prog_test smSurfaceTest.cpp:4117 (my_test_surface_closest_point, my_test_drop_point)",
"inputs": {
"this": {
"oob": false,
"form": 11,
"srf": {
"sms": {
"$upstream_file": "Surface.sms"
}
}
},
"op": "extend",
"dist": 0.1,
"cont": 3
}
},
{
"name": "Surface_seval",
"upstream_ref": "prog_test smSurfaceTest.cpp:4117 (my_test_surface_closest_point, my_test_drop_point)",
@@ -569,24 +551,6 @@
"dir": 0
}
},
{
"name": "DropCurveSurface_extend",
"upstream_ref": "pt_TestFiles/DropCurveSurface.sms (prog_test drop-curve surface input)",
"inputs": {
"this": {
"oob": false,
"form": 11,
"srf": {
"sms": {
"$upstream_file": "DropCurveSurface.sms"
}
}
},
"op": "extend",
"dist": 0.1,
"cont": 3
}
},
{
"name": "DropCurveSurface_seval",
"upstream_ref": "pt_TestFiles/DropCurveSurface.sms (prog_test drop-curve surface input)",
@@ -899,24 +863,6 @@
"dir": 0
}
},
{
"name": "offset_nurb_extend",
"upstream_ref": "prog_test smFilletTest.cpp:3769 (offset_nurb.sms)",
"inputs": {
"this": {
"oob": false,
"form": 11,
"srf": {
"sms": {
"$upstream_file": "offset_nurb.sms"
}
}
},
"op": "extend",
"dist": 0.1,
"cont": 3
}
},
{
"name": "offset_nurb_seval",
"upstream_ref": "prog_test smFilletTest.cpp:3769 (offset_nurb.sms)",
@@ -1191,31 +1137,6 @@
"op": "max_anal"
}
},
{
"name": "base_extend",
"upstream_ref": "SmSurface.h inline base-class defaults",
"inputs": {
"this": {
"base": true
},
"op": "extend",
"dist": 0.1,
"cont": 3
}
},
{
"name": "base_extend_dir",
"upstream_ref": "SmSurface.h inline base-class defaults",
"inputs": {
"this": {
"base": true
},
"op": "extend_dir",
"dir": 0,
"dist": 0.1,
"cont": 3
}
},
{
"name": "base_adjust_step",
"upstream_ref": "SmSurface.h inline base-class defaults",
+5 -27
View File
@@ -1,13 +1,14 @@
// SPDX-License-Identifier: Apache-2.0
// difftest shim for unit `nlib.srf_adv_p1`.
// Wraps N_SrfExtendByDist, N_SrfOffsetGetMaxDeriv, N_SrfEvalPtDerivsUnbounded,
// N_CreateSkinSpine and N_CrossBoundaryDerivsVectorField (upstream
// source/SMLib/src/NL_SrfAdv.cpp).
// Wraps N_SrfOffsetGetMaxDeriv, N_SrfEvalPtDerivsUnbounded, N_CreateSkinSpine and
// N_CrossBoundaryDerivsVectorField (upstream source/SMLib/src/NL_SrfAdv.cpp).
//
// One entry point, sc_nlib_srf_adv_p1_eval(op, in, n_in, out, cap, out_len): `op`
// selects the routine, `in` holds its inputs, `out` receives a flat record of
// doubles: the status (0 = ok, else NL_ERROR.eno, cleared before the call; 1000 if
// the flag was set without a number), then, on success only, the results.
// the flag was set without a number), then, on success only, the results. Op 0 (the
// surface extension by a distance, N_SrfExtendByDist) was removed with the port's
// fitted extension; its number is not reused.
//
// A surface in `in` is: n m p q, then (n+1)*(m+1) control points (x y z w,
// row-major), n+p+2 u knots, m+q+2 v knots. A curve is: n p, then n+1 control points
@@ -64,7 +65,6 @@ NL_CURVE* read_crv(const double* in, int64_t* at, NL_STACKS* S)
} // namespace
// op 0 extend by distance: surface, dist dflg end cont inplace -> status, surface.
// op 1 offset max derivatives: surface, d -> status, Muu Muv Mvv.
// op 2 unbounded evaluation: surface, cflg ndr cnt, cnt (u v) pairs -> for each pair
// until the first failure: status, then D[i][j] (xyz, i + j <= ndr, i outer; the
@@ -87,28 +87,6 @@ SC_EXPORT int32_t sc_nlib_srf_adv_p1_eval(int32_t op, const double* in, int64_t
N_ErrClear();
switch (op)
{
case 0:
{
NL_SURFACE* sur = read_srf(in, &at, &S);
if (sur == NULL)
{
rc = SC_ERR_ARG;
break;
}
const double* prm = in + at;
bool inplace = prm[4] != 0.0;
NL_SURFACE* res = inplace ? sur : null_srf(&S);
if (res == NULL)
{
rc = SC_ERR_ARG;
break;
}
err = N_SrfExtendByDist(sur, prm[0], (NL_FLAG)prm[1], (NL_FLAG)prm[2], (NL_FLAG)prm[3], res, &S, &S);
o.push_back(nl_status(err));
if (err == NL_NO)
dump_srf(o, res);
break;
}
case 1:
{
NL_SURFACE* sur = read_srf(in, &at, &S);
+4 -28
View File
@@ -1,19 +1,18 @@
// SPDX-License-Identifier: Apache-2.0
// difftest shim for unit `nlib.srf_adv_p2`.
// Wraps ST_surxsdr (the recursive core of N_SrfExtendByDist) and
// N_SrfEvalPtNormalDeriv (upstream source/SMLib/src/NL_SrfAdv.cpp).
// Wraps N_SrfEvalPtNormalDeriv (upstream source/SMLib/src/NL_SrfAdv.cpp).
//
// One entry point, sc_nlib_srf_adv_p2_eval(op, in, n_in, out, cap, out_len): `op`
// selects the routine, `in` holds its inputs, `out` receives a flat record of
// doubles: the status (0 = ok, else NL_ERROR.eno, cleared before the call; 1000 if
// the flag was set without a number), then, on success only, the results.
// the flag was set without a number), then, on success only, the results. Op 0 (the
// surface extension by a distance, ST_surxsdr) was removed with the port's fitted
// extension; its number is not reused.
//
// A surface in `in` is: n m p q, then (n+1)*(m+1) control points (x y z w,
// row-major), n+p+2 u knots, m+q+2 v knots (storage = indices in use). Everything is
// allocated on one NL_STACKS. The Rust wrapper validates the surface (valid clamped
// knot vectors, degrees 1..=NL_MAXDEG) and the parameters.
//
// A surface is dumped as n m p q r s, the control points, the u and v knots.
#include "sc_shim.h"
#include <nurbs.h>
@@ -23,15 +22,9 @@
#include "sc_nlib.h"
using sc::dump_srf;
using sc::nl_status;
using sc::null_srf;
using sc::read_srf;
// File-local in spirit, but defined with external linkage in NL_SrfAdv.cpp.
NL_FLAG ST_surxsdr(NL_SURFACE* surP, NL_REAL dist, NL_FLAG dflg, NL_FLAG end, NL_FLAG cont, NL_SURFACE* surQ,
NL_STACKS* SP, NL_STACKS* SQ, int depth);
namespace
{
@@ -44,7 +37,6 @@ void push_pt(std::vector<double>& o, const NL_POINT& p)
} // namespace
// op 0 extension: surface, dist dflg end cont depth inplace -> status, surface.
// op 1 normal derivatives: surface, u v ufl vfl -> status, Nu Nv Nuu Nuv Nvv (xyz each).
SC_EXPORT int32_t sc_nlib_srf_adv_p2_eval(int32_t op, const double* in, int64_t n_in, double* out, int64_t cap,
int64_t* out_len)
@@ -64,22 +56,6 @@ SC_EXPORT int32_t sc_nlib_srf_adv_p2_eval(int32_t op, const double* in, int64_t
N_ErrClear();
switch (rc == SC_OK ? op : -1)
{
case 0:
{
bool inplace = prm[5] != 0.0;
NL_SURFACE* res = inplace ? sur : null_srf(&S);
if (res == NULL)
{
rc = SC_ERR_ARG;
break;
}
err = ST_surxsdr(sur, prm[0], (NL_FLAG)prm[1], (NL_FLAG)prm[2], (NL_FLAG)prm[3], res, &S, &S,
(int)prm[4]);
o.push_back(nl_status(err));
if (err == NL_NO)
dump_srf(o, res);
break;
}
case 1:
{
NL_POINT** ND = N_AllocPt2dArray(2, 2, &S);
@@ -2,7 +2,7 @@
// difftest shim for unit `nurbs.b_spline_surface_n_lib_p1`.
// Wraps the SmBSplineSurface members owned by the unit (source/SMLib/src/SmBSplineSurfaceNLib.cpp):
// ApproximateOffsetSurface, CreateCoonsPatch (with the static sm_CreateCoonsSrf),
// CreateExtendedSurface (both overloads), JoinSurface, CreateNSidedPatch, CreateRuledSurface
// JoinSurface, CreateNSidedPatch, CreateRuledSurface
// (with the static sm_BuildNlibCurve), CreateSweepSurface, CreateSwungSurface,
// EvaluatePointOnExtendedSurface, Reparameterize, Reverse, ExpandBoundarySpan, EvaluateSimple
// and EvaluatePoint.
@@ -347,7 +347,7 @@ enum : int32_t
{
// surface ops (`this` from a surface block)
OP_OFFSET = 0,
OP_EXTEND = 1,
// 1: CreateExtendedSurface, removed with the port's fitted extension.
OP_JOIN = 2,
OP_EXT_EVAL = 3,
OP_REPARAM = 4,
@@ -568,18 +568,6 @@ void surface_op(int32_t op, Rd& rd, const SmContext& ctx, SmBSplineSurface* s, s
put_new(o, stt, r);
break;
}
case OP_EXTEND:
{
bool dir_overload = rd.flag();
SmSurfParamType dir = (SmSurfParamType)rd.idx();
double dist = rd.num();
SmContinuityType cont = (SmContinuityType)rd.idx();
SmSurface* r = NULL;
SmStatus stt = dir_overload ? s->CreateExtendedSurface(ctx, dir, dist, cont, r)
: s->CreateExtendedSurface(ctx, dist, cont, r);
put_new(o, stt, r);
break;
}
case OP_JOIN:
{
SmBSplineSurface* other = new (ctx) SmBSplineSurface(read_nurb(rd), FALSE, NULL);
+1 -9
View File
@@ -6,8 +6,6 @@
// STEPInversion, SwapUV, Transform, TrimWithDomain, CreateOffsetSurface, ReadFromDB,
// IsKindOf, AssertValid and the setters, run as a script on one offset so that the extension upstream's
// const evaluators store is seen by the following ops. WriteToDB is a generator helper.
// CreateExtendedSurface steps are refused: UB-107 and the HANG-6/HANG-22 searches inside it
// cannot be guarded in process.
//
// One entry point, sc_surface_offset_surface_eval(op, in, n_in, out, cap, out_len). Layouts
// are documented in crates/solidcore-ffi/src/units/surface_offset_surface.rs, which
@@ -92,8 +90,7 @@ enum : int64_t
S_TRIM = 12,
S_SPLIT = 13,
S_CREATE_OFFSET = 14,
S_EXTEND = 15,
S_EXTEND_DIR = 16,
// 15, 16: CreateExtendedSurface (refused here), removed with the port's fitted extension.
S_COPY = 17,
S_EQ = 18,
S_SET_DIST = 19,
@@ -752,11 +749,6 @@ void run_step(int64_t step, Rd& rd, const SmContext& ctx, SmOffsetSurface* s, st
}
break;
}
case S_EXTEND:
case S_EXTEND_DIR:
// CreateExtendedSurface reaches UB-107 and the HANG-6/HANG-22 searches, which cannot
// be guarded in process: refused (the FFI refuses these steps before calling).
throw ScOffsetUnsafeInput{};
case S_CREATE_OFFSET:
case S_COPY:
{
+1 -26
View File
@@ -83,8 +83,7 @@ enum : int32_t
OP_INHERITED = 23,
OP_REVERSE = 24,
OP_SWAP = 25,
OP_EXTEND = 26,
OP_LAST = 26,
OP_LAST = 25,
};
// Reads the protected STEP flags through pointers to members named in a derived class.
@@ -781,30 +780,6 @@ void run_op(int32_t op, Rd& rd, const SmContext& ctx, SmPlane* a, std::vector<do
break;
}
case OP_SWAP: o.push_back(st(a->SwapUV())); break;
case OP_EXTEND:
{
bool has_dir = rd.flag();
SmSurfParamType dir = (SmSurfParamType)rd.idx(0, SM_SP_VMAX);
double dist = rd.num();
SmContinuityType cont = (SmContinuityType)rd.idx(0, SM_CT_CINFINITY);
// upstream copies a NULL NURB (N_SrfCopy) when none can be made
if (a->GetOrCreateGwNurbPointer() == NULL)
{
o.push_back(st(SM_ERR)); // the port returns SM_ERR (no NURB)
break;
}
SmSurface* e = NULL;
SmStatus stt = has_dir ? a->CreateExtendedSurface(ctx, dir, dist, cont, e)
: a->CreateExtendedSurface(ctx, dist, cont, e);
o.push_back(st(stt));
if (stt == SM_SUCCESS)
{
SmBSplineSurface* bs = e ? SM_CAST_PTR(SmBSplineSurface, e) : NULL;
put_surface_nurb(o, bs ? bs->GetGwNurbPointer() : NULL);
}
delete e;
break;
}
default: throw Bad();
}
}
@@ -79,8 +79,7 @@ enum : int32_t
Q_BBOX = 2,
Q_DEGEN_POINT = 3,
Q_DEGEN_CURVE = 4,
Q_EXTEND = 5,
Q_EXTEND_DIR = 6,
// 5, 6: CreateExtendedSurface (both forms), removed with the port's fitted extension.
Q_PLANAR = 7,
Q_NUM_CPTS = 8,
Q_BILINEAR = 9,
@@ -656,29 +655,6 @@ SmBSplineSurface* read_test_surface(Rd& rd, const SmContext& ctx, const uint8_t*
return t;
}
// A NURB `p q n+1 m+1 cpts(x y z w, v fastest) nu U nv V`.
void put_nurb(std::vector<double>& o, const gw_SURFACE* sur)
{
o.push_back((double)sur->p);
o.push_back((double)sur->q);
o.push_back((double)(sur->net->n + 1));
o.push_back((double)(sur->net->m + 1));
for (gw_INDEX i = 0; i <= sur->net->n; i++)
for (gw_INDEX j = 0; j <= sur->net->m; j++)
{
o.push_back(sur->net->Pw[i][j].x);
o.push_back(sur->net->Pw[i][j].y);
o.push_back(sur->net->Pw[i][j].z);
o.push_back(sur->net->Pw[i][j].w);
}
o.push_back((double)(sur->knu->m + 1));
for (gw_INDEX i = 0; i <= sur->knu->m; i++)
o.push_back(sur->knu->U[i]);
o.push_back((double)(sur->knv->m + 1));
for (gw_INDEX i = 0; i <= sur->knv->m; i++)
o.push_back(sur->knv->U[i]);
}
// Copy of the file-local sm_CrossedSeamOrPole of SmSurface.cpp.
SmBoolean crossed_seam_or_pole(SmSurface* pSurf, const SmPoint2d& crPrevUV, const SmPoint2d& crCurrUV,
const SmExtent2d& crDomain)
@@ -777,29 +753,6 @@ void lazy_query(Rd& rd, const SmContext& ctx, SmSurface* s, std::vector<double>&
o.push_back(flg(s->IsDegenerateCurve(ang, has ? &d : NULL, tol)));
break;
}
case Q_EXTEND:
case Q_EXTEND_DIR:
{
SmSurfParamType dir = (SmSurfParamType)rd.idx();
double dist = rd.num();
SmContinuityType cont = (SmContinuityType)rd.idx();
SmSurface* e = NULL;
SmStatus stt = q == Q_EXTEND ? s->CreateExtendedSurface(ctx, dist, cont, e)
: s->CreateExtendedSurface(ctx, dir, dist, cont, e);
o.push_back(st(stt));
if (stt == SM_SUCCESS)
{
const gw_SURFACE* sur = e && e->IsKindOf(SmBSplineSurface_TYPE)
? ((SmBSplineSurface*)e)->GetGwNurbPointer()
: NULL;
o.push_back(e ? (double)e->GetType() : 0.0);
o.push_back(flg(sur != NULL));
if (sur)
put_nurb(o, sur);
}
delete e;
break;
}
case Q_PLANAR:
{
double tol = rd.num();
+1 -19
View File
@@ -662,8 +662,7 @@ enum : int32_t
OP_TRIM = 9,
OP_SPLIT = 10,
OP_STEP_INV = 11,
OP_EXTEND = 12,
OP_EXTEND_DIR = 13,
// 12, 13: CreateExtendedSurface (both forms), removed with the port's fitted extension.
OP_ADJUST_STEP = 14,
OP_MAX_ANAL = 15,
OP_COPY = 16,
@@ -1099,23 +1098,6 @@ void surface_op(int32_t op, Rd& rd, const SmContext& ctx, SmSurface* s, std::vec
}
break;
}
case OP_EXTEND:
case OP_EXTEND_DIR:
{
SmSurfParamType dir = SM_SP_U;
if (op == OP_EXTEND_DIR)
dir = (SmSurfParamType)rd.idx();
double dist = rd.num();
SmContinuityType cont = (SmContinuityType)rd.idx();
SmSurface* e = NULL;
SmStatus stt = op == OP_EXTEND ? s->CreateExtendedSurface(ctx, dist, cont, e)
: s->CreateExtendedSurface(ctx, dir, dist, cont, e);
o.push_back(st(stt));
if (stt == SM_SUCCESS)
put_state(o, e);
delete e;
break;
}
case OP_ADJUST_STEP:
{
SmExtent2d d = rd.domain();
+1 -1
View File
@@ -9,4 +9,4 @@ tests = ["prog_test:my_test_surface_closest_point", "prog_test:my_test_drop_poin
status = "green"
claimed_by = "swarm-nlib.srf_adv_p1-a1"
claimed_at = "2026-10-12T23:52:37Z"
notes = "5 fns; largest: N_CreateSkinSpine, N_SrfEvalPtDerivsUnbounded, N_CrossBoundaryDerivsVectorField, N_SrfOffsetGetMaxDeriv; unit-SCC #22 (220 units); 317 upstream test entry points reach this unit; green: 2886 difftest cases (386 fixtures + 2500 generated), 0 deferred, bit-identical"
notes = "5 fns; largest: N_CreateSkinSpine, N_SrfEvalPtDerivsUnbounded, N_CrossBoundaryDerivsVectorField, N_SrfOffsetGetMaxDeriv; unit-SCC #22 (220 units); 317 upstream test entry points reach this unit; green: 2886 difftest cases (386 fixtures + 2500 generated), 0 deferred, bit-identical UPDATE (2026-11-04): N_SrfExtendByDist's port deleted with the fitted surface extension; its difftest op extend_by_dist removed (128 fixtures, 1 fuzz-corpus entry, generator arm, shim op 0). difftest 2758 cases (258 fixtures + 2500 generated), all pass bit-identical."
+1 -1
View File
@@ -9,4 +9,4 @@ tests = ["prog_test:my_test_ss_nurb", "prog_test:my_test_surface_point_intersect
status = "green"
claimed_by = "swarm-nlib.srf_adv_p2-a1"
claimed_at = "2026-10-12T23:00:10Z"
notes = "2 fns; largest: ST_surxsdr, N_SrfEvalPtNormalDeriv; unit-SCC #22 (220 units); 182 upstream test entry points reach this unit; difftest 1695 cases (195 fixtures + 1500 generated), 0 deferred, bit-identical"
notes = "2 fns; largest: ST_surxsdr, N_SrfEvalPtNormalDeriv; unit-SCC #22 (220 units); 182 upstream test entry points reach this unit; difftest 1695 cases (195 fixtures + 1500 generated), 0 deferred, bit-identical UPDATE (2026-11-04): ST_surxsdr's port deleted with the fitted surface extension; its difftest op extend removed (175 fixtures, the 3-entry fuzz corpus, shim op 0). difftest covers N_SrfEvalPtNormalDeriv only: 1520 cases (20 fixtures + 1500 generated), all pass bit-identical."
@@ -9,4 +9,4 @@ tests = ["prog_test:my_test_surface_closest_point", "prog_test:my_test_drop_poin
status = "green"
claimed_by = "swarm-nurbs.b_spline_surface_n_lib_p1-a1"
claimed_at = "2026-10-13T00:47:51Z"
notes = "main CI e33985411 (2026-10-23): PASS nurbs.b_spline_surface_n_lib_p1 cases=699 passed=699 failed=0 deferred=0. difftest: 665 cases (65 fixtures + 600 generated), 638 pass bit-identical, 27 deferred (surface.surface_p5: SmSurface::IsSingularity in CreateExtendedSurface; then surface.surface_p7 IsClosed); DropCurve path of sm_CreateNonRationalCurve not diffed (upstream crash, see findings). 23 fns; largest: sm_CreateCoonsSrf, SmBSplineSurface::CreateExtendedSurface, SmBSplineSurface::CreateCoonsPatch, SmBSplineSurface::CreateNSidedPatch; cycle back-edges (call into later units; stub or co-port): surface.surface_p2(4), curve.curve_p2(1), intersect.surface_drop_lib_p3(1), surface.surface_p5(1), surface.surface_p7(1); unit-SCC #22 (220 units); 317 upstream test entry points reach this unit Re-run with surface.surface_p5's back-edges wired (PR #50): cases=665 passed=638 failed=0 deferred=27 max_rel_err=0e0 blocked_by=surface.surface_p3(27). GREEN (PR #52, after merging surface_p4/#49 and surface_p5/#50): GetSingularities wired, 665/665 pass, 0 deferred."
notes = "main CI e33985411 (2026-10-23): PASS nurbs.b_spline_surface_n_lib_p1 cases=699 passed=699 failed=0 deferred=0. difftest: 665 cases (65 fixtures + 600 generated), 638 pass bit-identical, 27 deferred (surface.surface_p5: SmSurface::IsSingularity in CreateExtendedSurface; then surface.surface_p7 IsClosed); DropCurve path of sm_CreateNonRationalCurve not diffed (upstream crash, see findings). 23 fns; largest: sm_CreateCoonsSrf, SmBSplineSurface::CreateExtendedSurface, SmBSplineSurface::CreateCoonsPatch, SmBSplineSurface::CreateNSidedPatch; cycle back-edges (call into later units; stub or co-port): surface.surface_p2(4), curve.curve_p2(1), intersect.surface_drop_lib_p3(1), surface.surface_p5(1), surface.surface_p7(1); unit-SCC #22 (220 units); 317 upstream test entry points reach this unit Re-run with surface.surface_p5's back-edges wired (PR #50): cases=665 passed=638 failed=0 deferred=27 max_rel_err=0e0 blocked_by=surface.surface_p3(27). GREEN (PR #52, after merging surface_p4/#49 and surface_p5/#50): GetSingularities wired, 665/665 pass, 0 deferred. UPDATE (2026-11-04): BSplineSurface::create_extended_surface(_dir) deleted with the fitted surface extension; difftest op extend removed (1 fixture, 1 fuzz-corpus entry, generator arm, shim op 1). difftest 701 cases, all pass bit-identical."
+1 -1
View File
@@ -9,4 +9,4 @@ tests = ["prog_test:my_test_extent3d", "prog_test:my_test_vector2d", "prog_test:
status = "green"
claimed_by = ""
claimed_at = ""
notes = "GREEN (PR #62): 3077 difftest cases (77 fixtures + 3000 generated), 3077 pass bit-identical, 0 failed, 0 deferred, max_rel_err 0. Extended 100,000-seed run: 100,076 cases, 100,076 pass, 0 failed, 0 deferred, no crash or hang. Promotes intersect.surface_drop_lib_p2 and nurbs.b_spline_surface_p1 to green (their last deferrals were this unit). Adds SurfaceKind::OffsetSurface, kernel-aware copy/split/extension/planarity dispatch, state-preserving STEP inversion and database dispatch; Surface::create_offset_surface dispatches to the plane, revolution (with approx_tol_3d) and offset overrides; an owning offset is its base's owner (BASE_OWNER, upstream m_pSurface->SetOwner(this)) and hands down the face its own owner chain reaches, so the base's GetFace-based tolerances follow upstream (a face-owned offset's expanded box returns Unported(topology.face_p2) instead of the default zone tolerance; LocalPointSolve_1 on the base takes GetZoneTol3d(GetOwner()) as its loose tolerance), and a non-owning offset's base keeps its caller's owner (Surface::clone_keeping_owner); SmSurface::IsPeriodic ignores Evaluate statuses as upstream (Surface::evaluate_ignoring_status; curve-bounded and STEP surfaces pass the array on to their basis); step_inversion_extending stores the extension an offset base's inversion makes. Wires the offset base into the point solver and the intersector kind test. Constructor nesting bounded at 64, JSON bit-roundtrips preserved. The C++ reference runs in process: the shim guards UB-171, UB-172, UB-173 (boxes and IsPlanar's natural-domain box at every level of the offset chain) and HANG-21 on the actual surfaces as the script runs, without changing valid upstream numerics, and CreateExtendedSurface steps are refused (UB-107, HANG-6 and HANG-22 cannot be guarded outside NLib; the per-case worker process that compared them was split out of this PR for a maintainer-reviewed one); the static FFI checks are otherwise well-formedness (no coordinate cap, scale floor or order cap below 64; a revolution base keeps surface.surf_of_revolution_p1's 1e-6 minimum sizes); generated cases cover magnitudes 1e-9 to 1e150, zero/negative scales and orders up to 8; a BaseOffset op compares the CreateOffsetSurface dispatch through SmSurface* over every base kind (distances relative to the base size: a degenerate generator offset copies an uninitialised dMaxGap3d upstream). Candidates the shim refuses are discarded at generation, never counted as passes. The generator keeps reflection extensions of degree-1 NURBs to C-max (UB-32, as nlib.srf_adv_p1). WR-160..WR-165 intentionally retained (WR-166 fixed in nlib.srf_nurb / nlib.srf_adv_p1). Dispatch gaps: the surface cache box callback of an offset (no kernel, no forwarded basis guesses; unreached); SetOffsetDistance through the intersector's shared surfaces waits on blend.fillet_executive_p1. 37 fns; largest: SmOffsetSurface::EvaluatePlainOrSimple, SmOffsetSurface::CalculateBoundingBox, sm_ExtendSurface, SmOffsetSurface::EvaluatePoint; unit-SCC #22 (220 units); 319 upstream test entry points reach this unit. Back-edges wired (PR #62): the SmOffsetSurface CO-PORTED in blend.fillet_custom_solver (PR #78) is now solidcore::surface::offset_surface::OffsetSurface plus the base-pointer SurfaceId; its evaluation stubs call the port, storing extensions (OffsetSurface::with_evaluator runs inherited SmSurface bodies on the offset), and rpSurface2->LocalPointSolve runs SmSurface::LocalPointSolve on the offset; topology.face_p2's thick-face SmOffsetSurface::EvaluateSimple (PR #91) is wired. No UNPORTED(surface.offset_surface) sites remain. Re-runs: blend.fillet_custom_solver 448 cases, 401 pass, 0 failed, 47 deferred (blend.fillet_standard_solver_p2); topology.face_p2 2692 cases, 2322 pass, 0 failed, 370 deferred (offset deferrals 112 -> 0); mass.integrator 1528 cases, 1521 pass, 0 failed, 7 deferred.; CO-PORTED in intersect.surface_drop_lib_p2 (PR #112): the SmOffsetSurface(d, *this, FALSE, lSingularities) DropCurve hands to SmDropPt -> solidcore::intersect::surface_drop_lib::p2::DropOffset (distance and singularities); wired in PR #62: its DropVectors / EvaluateDirectionalDerivs build the ported OffsetSurface over a copy of the drop surface and run the SmSurface bodies (intersect.surface_drop_lib_p2: 1531 cases, 1531 pass, 0 failed, 0 deferred: green) TOUCHED (blend.fillet_solver_p4, PR #212): m_dOffsetDistance is a Cell (set_offset_distance takes &self) so a fillet trace's copy of a solver's offset surface follows the distances the solver sets (upstream: one shared object); 3082/3082 pass."
notes = "GREEN (PR #62): 3077 difftest cases (77 fixtures + 3000 generated), 3077 pass bit-identical, 0 failed, 0 deferred, max_rel_err 0. Extended 100,000-seed run: 100,076 cases, 100,076 pass, 0 failed, 0 deferred, no crash or hang. Promotes intersect.surface_drop_lib_p2 and nurbs.b_spline_surface_p1 to green (their last deferrals were this unit). Adds SurfaceKind::OffsetSurface, kernel-aware copy/split/extension/planarity dispatch, state-preserving STEP inversion and database dispatch; Surface::create_offset_surface dispatches to the plane, revolution (with approx_tol_3d) and offset overrides; an owning offset is its base's owner (BASE_OWNER, upstream m_pSurface->SetOwner(this)) and hands down the face its own owner chain reaches, so the base's GetFace-based tolerances follow upstream (a face-owned offset's expanded box returns Unported(topology.face_p2) instead of the default zone tolerance; LocalPointSolve_1 on the base takes GetZoneTol3d(GetOwner()) as its loose tolerance), and a non-owning offset's base keeps its caller's owner (Surface::clone_keeping_owner); SmSurface::IsPeriodic ignores Evaluate statuses as upstream (Surface::evaluate_ignoring_status; curve-bounded and STEP surfaces pass the array on to their basis); step_inversion_extending stores the extension an offset base's inversion makes. Wires the offset base into the point solver and the intersector kind test. Constructor nesting bounded at 64, JSON bit-roundtrips preserved. The C++ reference runs in process: the shim guards UB-171, UB-172, UB-173 (boxes and IsPlanar's natural-domain box at every level of the offset chain) and HANG-21 on the actual surfaces as the script runs, without changing valid upstream numerics, and CreateExtendedSurface steps are refused (UB-107, HANG-6 and HANG-22 cannot be guarded outside NLib; the per-case worker process that compared them was split out of this PR for a maintainer-reviewed one); the static FFI checks are otherwise well-formedness (no coordinate cap, scale floor or order cap below 64; a revolution base keeps surface.surf_of_revolution_p1's 1e-6 minimum sizes); generated cases cover magnitudes 1e-9 to 1e150, zero/negative scales and orders up to 8; a BaseOffset op compares the CreateOffsetSurface dispatch through SmSurface* over every base kind (distances relative to the base size: a degenerate generator offset copies an uninitialised dMaxGap3d upstream). Candidates the shim refuses are discarded at generation, never counted as passes. The generator keeps reflection extensions of degree-1 NURBs to C-max (UB-32, as nlib.srf_adv_p1). WR-160..WR-165 intentionally retained (WR-166 fixed in nlib.srf_nurb / nlib.srf_adv_p1). Dispatch gaps: the surface cache box callback of an offset (no kernel, no forwarded basis guesses; unreached); SetOffsetDistance through the intersector's shared surfaces waits on blend.fillet_executive_p1. 37 fns; largest: SmOffsetSurface::EvaluatePlainOrSimple, SmOffsetSurface::CalculateBoundingBox, sm_ExtendSurface, SmOffsetSurface::EvaluatePoint; unit-SCC #22 (220 units); 319 upstream test entry points reach this unit. Back-edges wired (PR #62): the SmOffsetSurface CO-PORTED in blend.fillet_custom_solver (PR #78) is now solidcore::surface::offset_surface::OffsetSurface plus the base-pointer SurfaceId; its evaluation stubs call the port, storing extensions (OffsetSurface::with_evaluator runs inherited SmSurface bodies on the offset), and rpSurface2->LocalPointSolve runs SmSurface::LocalPointSolve on the offset; topology.face_p2's thick-face SmOffsetSurface::EvaluateSimple (PR #91) is wired. No UNPORTED(surface.offset_surface) sites remain. Re-runs: blend.fillet_custom_solver 448 cases, 401 pass, 0 failed, 47 deferred (blend.fillet_standard_solver_p2); topology.face_p2 2692 cases, 2322 pass, 0 failed, 370 deferred (offset deferrals 112 -> 0); mass.integrator 1528 cases, 1521 pass, 0 failed, 7 deferred.; CO-PORTED in intersect.surface_drop_lib_p2 (PR #112): the SmOffsetSurface(d, *this, FALSE, lSingularities) DropCurve hands to SmDropPt -> solidcore::intersect::surface_drop_lib::p2::DropOffset (distance and singularities); wired in PR #62: its DropVectors / EvaluateDirectionalDerivs build the ported OffsetSurface over a copy of the drop surface and run the SmSurface bodies (intersect.surface_drop_lib_p2: 1531 cases, 1531 pass, 0 failed, 0 deferred: green) TOUCHED (blend.fillet_solver_p4, PR #212): m_dOffsetDistance is a Cell (set_offset_distance takes &self) so a fillet trace's copy of a solver's offset surface follows the distances the solver sets (upstream: one shared object); 3082/3082 pass. UPDATE (2026-11-04): OffsetSurface::create_extended_surface(_dir) deleted with the fitted surface extension; the (already refused) Extend / ExtendDir steps removed from runner, FFI and shim (steps 15/16), with the degree-1 reflection-extension guard. difftest 3182 cases, all pass bit-identical."
+1 -1
View File
@@ -9,4 +9,4 @@ tests = ["prog_test:my_test_extent3d", "prog_test:my_test_vector2d", "prog_test:
status = "green"
claimed_by = ""
claimed_at = ""
notes = "partial: difftest 2705 cases (1205 fixture: 18 upstream-derived, 1179 adversarial, 8 extension; 1500 generated): 2538 passed, 167 deferred (intersect.surface_drop_lib_p3 125, surface.surface_p1 40: IsPlanar of a B-spline reaches SmSurface::GetFace through owner_zone_tolerance once surface.surface_p7 wired IsPlanar (PR #24), surface.surface_p8 2), max_rel_err 0, perturb 2538/2538 detected; private Evaluate1st/2ndDerivatives copies now call surface::surface::p7 (PR #24); CreateExtendedSurface re-dispatched to the plane overrides; surface cache plane paths are UNPORTED(surface.plane_p1) (iso curve needs Curve storage in the cache, CalculateBoundingBox not re-dispatched); 39 fns; largest: SmPlane::CoincidenceCheck, SmPlane::DropCurve, SmPlane::GlobalLineIntersect, SmPlane::DropPointFast; cycle back-edges (call into later units; stub or co-port): surface.surface_p1(2), surface.surface_p7(2), intersect.surface_drop_lib_p3(1), surface.surface_p5(1), surface.surface_p6(1), surface.surface_p8(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit; FOLLOW-UP (difftest --ssi-corpus, PR #84): model the NURB cache of upstream const queries (GetNaturalUVDomain makes and keeps m_pNurb through GetOrCreateGwNurbPointer; the port makes it on a copy), which IntersectWithPlane's parallel branch (SmPlane.cpp:2183) depends on; see plan/findings.md surface.plane_p1 [surface.surface_p6 wired (PR #92): 2705 cases, 2570 pass, 135 deferred (intersect.surface_drop_lib_p3 129, surface.surface_p5 4, surface.surface_p8 2)] Review PR #106 recertification: 2705 cases, 2574 pass, 131 deferred, 0 failed, max_rel_err=0.0; deferred: intersect.surface_drop_lib_p3 129, surface.surface_p8 2. GREEN (2026-10-23, PR #587): difftest PASS surface.plane_p1 cases=2705 passed=2705 failed=0 deferred=0 max_rel_err=0e0 with_values=2705 no_values=0 vacuous=0; the former 131 deferrals (intersect.surface_drop_lib_p3 129, surface.surface_p8 2) no longer occur on main 9fd1c67c5 (wired by #463, intersect.surface_drop_lib_p3); local rerun and CI run 39923812958 agree. NURB cache modelled (PR #1300): const queries make and keep the plane's NURB as GetOrCreateGwNurbPointer (resolves the FOLLOW-UP above; plan/findings/surface.plane_p1--smplane-part-1--addendum-2026-10-27-nurb-cache-modelled.md); the shim asks const queries of the plane itself and ToggleSwapUVBit / operator== are generated anywhere: difftest PASS surface.plane_p1 cases=2705 passed=2705 failed=0 deferred=0 max_rel_err=0e0."
notes = "partial: difftest 2705 cases (1205 fixture: 18 upstream-derived, 1179 adversarial, 8 extension; 1500 generated): 2538 passed, 167 deferred (intersect.surface_drop_lib_p3 125, surface.surface_p1 40: IsPlanar of a B-spline reaches SmSurface::GetFace through owner_zone_tolerance once surface.surface_p7 wired IsPlanar (PR #24), surface.surface_p8 2), max_rel_err 0, perturb 2538/2538 detected; private Evaluate1st/2ndDerivatives copies now call surface::surface::p7 (PR #24); CreateExtendedSurface re-dispatched to the plane overrides; surface cache plane paths are UNPORTED(surface.plane_p1) (iso curve needs Curve storage in the cache, CalculateBoundingBox not re-dispatched); 39 fns; largest: SmPlane::CoincidenceCheck, SmPlane::DropCurve, SmPlane::GlobalLineIntersect, SmPlane::DropPointFast; cycle back-edges (call into later units; stub or co-port): surface.surface_p1(2), surface.surface_p7(2), intersect.surface_drop_lib_p3(1), surface.surface_p5(1), surface.surface_p6(1), surface.surface_p8(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit; FOLLOW-UP (difftest --ssi-corpus, PR #84): model the NURB cache of upstream const queries (GetNaturalUVDomain makes and keeps m_pNurb through GetOrCreateGwNurbPointer; the port makes it on a copy), which IntersectWithPlane's parallel branch (SmPlane.cpp:2183) depends on; see plan/findings.md surface.plane_p1 [surface.surface_p6 wired (PR #92): 2705 cases, 2570 pass, 135 deferred (intersect.surface_drop_lib_p3 129, surface.surface_p5 4, surface.surface_p8 2)] Review PR #106 recertification: 2705 cases, 2574 pass, 131 deferred, 0 failed, max_rel_err=0.0; deferred: intersect.surface_drop_lib_p3 129, surface.surface_p8 2. GREEN (2026-10-23, PR #587): difftest PASS surface.plane_p1 cases=2705 passed=2705 failed=0 deferred=0 max_rel_err=0e0 with_values=2705 no_values=0 vacuous=0; the former 131 deferrals (intersect.surface_drop_lib_p3 129, surface.surface_p8 2) no longer occur on main 9fd1c67c5 (wired by #463, intersect.surface_drop_lib_p3); local rerun and CI run 39923812958 agree. NURB cache modelled (PR #1300): const queries make and keep the plane's NURB as GetOrCreateGwNurbPointer (resolves the FOLLOW-UP above; plan/findings/surface.plane_p1--smplane-part-1--addendum-2026-10-27-nurb-cache-modelled.md); the shim asks const queries of the plane itself and ToggleSwapUVBit / operator== are generated anywhere: difftest PASS surface.plane_p1 cases=2705 passed=2705 failed=0 deferred=0 max_rel_err=0e0. UPDATE (2026-11-04): Plane::create_extended_surface(_dir) deleted with the fitted surface extension; difftest op extend removed (fixture file extend.json, 8 cases; generator arm; shim op 26). difftest 2698 cases, all pass bit-identical."
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long