From e0fcf50875b7ac18f8c2709b5361acfb46827060 Mon Sep 17 00:00:00 2001 From: Andreas <64703083+froglemonade2@users.noreply.github.com> Date: Tue, 13 Oct 2026 23:39:52 +0200 Subject: [PATCH 1/5] claim: surface.torus Co-Authored-By: Claude Opus 5.5 From b0b634e3282bc9c38516565c543eda8192f600a1 Mon Sep 17 00:00:00 2001 From: Andreas <64703083+froglemonade2@users.noreply.github.com> Date: Wed, 14 Oct 2026 07:29:03 +0200 Subject: [PATCH 2/5] surface.torus: port SmTorus with difftest runner Torus construction (CreateCanonical/MakeNurb), operator==, Copy, offset, STEP/NURB conversions, STEP evaluation, iso circles, RebuildSTEPFromNURBParameters, Transform and ReadFromDB (the last two end in the unported SmSurfOfRevolution part). SurfOfRevolution's NURB and polar-converter rebuilds take the iso-curve maker so the torus override is used, as upstream's virtual call. Surface::read_from_db reads the torus record before the unported base record. Co-Authored-By: Claude Opus 5.5 --- crates/difftest/src/units/surface_torus.rs | 854 ++++++++ .../solidcore-ffi/src/units/surface_torus.rs | 354 +++ crates/solidcore/src/surface.rs | 1 + .../src/surface/surf_of_revolution/p1.rs | 55 +- crates/solidcore/src/surface/surface/p2.rs | 12 +- crates/solidcore/src/surface/torus.rs | 1283 +++++++++++ fixtures/surface.torus/cases.json | 1917 +++++++++++++++++ harness/cpp/shim/surface_torus.cpp | 417 ++++ 8 files changed, 4881 insertions(+), 12 deletions(-) create mode 100644 crates/difftest/src/units/surface_torus.rs create mode 100644 crates/solidcore-ffi/src/units/surface_torus.rs create mode 100644 crates/solidcore/src/surface/torus.rs create mode 100644 fixtures/surface.torus/cases.json create mode 100644 harness/cpp/shim/surface_torus.cpp diff --git a/crates/difftest/src/units/surface_torus.rs b/crates/difftest/src/units/surface_torus.rs new file mode 100644 index 0000000000..8d3fde1f0e --- /dev/null +++ b/crates/difftest/src/units/surface_torus.rs @@ -0,0 +1,854 @@ +//! Runner for unit `surface.torus`: `SmTorus` (construction through `CreateCanonical`, +//! `operator==`, `Copy`, `CreateOffsetSurface`, the STEP/NURB conversions, STEP +//! evaluation, iso curves, `RebuildSTEPFromNURBParameters`, `Transform`). +//! +//! C++: `solidcore_ffi::units::surface_torus` (upstream `source/SMLib/src/SmTorus.cpp`). +//! Rust: `solidcore::surface::torus`. +//! +//! A case is `torus` (placement, radii, flags, optional domain and generator circle) +//! plus `op` and its parameters. Both sides produce the same flat record (layout in +//! the FFI module docs), split into `build` (the construction status), `status` (the +//! op status) and `out` (the rest). Statuses are `0` (`SM_SUCCESS`) or the +//! `SmStatus`; `-1` on the Rust side for an error upstream cannot produce. +//! +//! Deferred cases: `Transform` other than the identity or a rejected scale +//! (`SmSurfOfRevolution::Transform`, `surface.surf_of_revolution_p3`). + +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +use solidcore::curve::curve::{Curve, CurveKind}; +use solidcore::curve::ellipse::{Ellipse, EllipseOptions}; +use solidcore::geom::axis2_placement::Axis2Placement; +use solidcore::geom::extent1d::Extent1d; +use solidcore::geom::extent2d::Extent2d; +use solidcore::geom::vector2d::Point2; +use solidcore::geom::vector3d::Vec3; +use solidcore::kernel::Kernel; +use solidcore::nurbs::b_spline_surface::{IsoCurveOptions, SurfParamType}; +use solidcore::nurbs::crv_basic::Curve as NurbCurve; +use solidcore::surface::surface::p2::{ + B_SPLINE_SURFACE_TYPE, SURF_OF_REVOLUTION_TYPE, TORUS_TYPE, TYPE, +}; +use solidcore::surface::torus::{Torus, TorusOptions}; +use solidcore::SolidError; +use solidcore_ffi::units::surface_torus as ffi; + +use crate::case::CaseContext; +use crate::runner::default_cases; +use crate::{Case, HarnessError, Output, Rng, UnitDiff}; + +/// Generated cases per run, whatever `--seeds` says (it can only raise this). +pub const MIN_GENERATED: usize = 500; + +/// `surface.torus` runner. +pub struct Runner; + +fn harness(msg: impl Into) -> HarnessError { + HarnessError::new(msg) +} + +// ---- inputs ----------------------------------------------------------------------- + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct CircleIn { + c: [f64; 3], + x: [f64; 3], + y: [f64; 3], + d: [f64; 2], + r: f64, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct TorusIn { + origin: [f64; 3], + x: [f64; 3], + y: [f64; 3], + major: f64, + minor: f64, + #[serde(default)] + swap: bool, + #[serde(default)] + inside_out: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + dom: Option<[f64; 4]>, + #[serde(default, skip_serializing_if = "Option::is_none")] + circle: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "op", rename_all = "snake_case")] +enum OpIn { + Build {}, + EvalStep { + u: f64, + v: f64, + k: usize, + #[serde(default)] + point: bool, + }, + ToStep { + u: f64, + v: f64, + }, + ToNurbs { + u: f64, + v: f64, + }, + Iso { + param: u8, + iso: f64, + #[serde(default)] + dom: Option<[f64; 4]>, + }, + Offset { + dist: f64, + tol: f64, + #[serde(default)] + rebuild: bool, + }, + Rebuild {}, + Transform { + origin: [f64; 3], + x: [f64; 3], + y: [f64; 3], + #[serde(default)] + scale: Option<[f64; 3]>, + }, + Eq { + other: TorusIn, + }, + Copy {}, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct Inputs { + torus: TorusIn, + #[serde(flatten)] + op: OpIn, +} + +// ---- C++ side ------------------------------------------------------------------------- + +fn ffi_torus(t: &TorusIn) -> ffi::TorusIn { + ffi::TorusIn { + origin: t.origin, + x: t.x, + y: t.y, + major: t.major, + minor: t.minor, + swap: t.swap, + inside_out: t.inside_out, + dom: t.dom, + circle: t.circle.as_ref().map(|c| ffi::CircleIn { + c: c.c, + x: c.x, + y: c.y, + d: c.d, + r: c.r, + }), + } +} + +fn cpp(i: &Inputs) -> Result { + let op = match &i.op { + OpIn::Build {} => ffi::Op::Build, + OpIn::EvalStep { u, v, k, point } => ffi::Op::EvalStep { + u: *u, + v: *v, + k: *k, + point: *point, + }, + OpIn::ToStep { u, v } => ffi::Op::ToStep { u: *u, v: *v }, + OpIn::ToNurbs { u, v } => ffi::Op::ToNurbs { u: *u, v: *v }, + OpIn::Iso { param, iso, dom } => ffi::Op::Iso { + param: *param, + iso: *iso, + dom: *dom, + }, + OpIn::Offset { dist, tol, rebuild } => ffi::Op::Offset { + dist: *dist, + tol: *tol, + rebuild: *rebuild, + }, + OpIn::Rebuild {} => ffi::Op::Rebuild, + OpIn::Transform { + origin, + x, + y, + scale, + } => ffi::Op::Transform { + origin: *origin, + x: *x, + y: *y, + scale: *scale, + }, + OpIn::Eq { other } => ffi::Op::Eq { + other: Box::new(ffi_torus(other)), + }, + OpIn::Copy {} => ffi::Op::Copy, + }; + let flat = ffi::torus_op(&ffi_torus(&i.torus), &op).map_err(|e| harness(e.to_string()))?; + to_output(i, &flat) +} + +/// Splits a record into `build`, `status` and `out`. +fn to_output(i: &Inputs, flat: &[f64]) -> Result { + let (b, rest) = flat + .split_first() + .ok_or_else(|| harness("empty record"))?; + let mut out = Output::new().values("build", vec![*b]); + if *b != 0.0 { + if !rest.is_empty() { + return Err(harness("record continues after a failed build")); + } + return Ok(out); + } + let mut rest = rest; + if !matches!(i.op, OpIn::Build {}) { + let (s, r) = rest + .split_first() + .ok_or_else(|| harness("record has no op status"))?; + out = out.values("status", vec![*s]); + rest = r; + } + Ok(out.values("out", rest.to_vec())) +} + +// ---- Rust side ------------------------------------------------------------------------ + +fn flag(b: bool) -> f64 { + f64::from(u8::from(b)) +} + +fn v3(p: [f64; 3]) -> Vec3 { + let [x, y, z] = p; + Vec3::new(x, y, z) +} + +fn push_v(o: &mut Vec, v: Vec3) { + o.extend([v.x, v.y, v.z]); +} + +/// The `SmStatus` of a Rust error; `-1` for one upstream cannot return. +fn status(e: &SolidError) -> f64 { + match e { + SolidError::Upstream { code, .. } => f64::from(*code), + _ => -1.0, + } +} + +/// `SC_ERR_ARG` (`harness/cpp/shim/sc_shim.h`): the shim rejected the arguments. +const SC_ERR_ARG: f64 = -1002.0; + +/// Builds the torus; `Ok(Err(e))` when the construction fails. +fn rust_torus(k: &Kernel, t: &TorusIn) -> Result, HarnessError> { + let circle = match &t.circle { + None => None, + Some(c) => { + let [d0, d1] = c.d; + let e = Ellipse::new_circle( + k, + v3(c.c), + v3(c.x), + v3(c.y), + Extent1d::from_min_max(d0, d1), + c.r, + &EllipseOptions::default(), + ) + .map_err(|e| harness(format!("circle: {e}")))?; + Some(Curve::from(e)) + } + }; + let frame = Axis2Placement::new(v3(t.origin), v3(t.x), v3(t.y)); + let dom = t.dom.map(|[a, b, c, d]| Extent2d::from_bounds(a, b, c, d)); + Ok(Torus::create_canonical( + k, + &frame, + t.major, + t.minor, + TorusOptions { + swap_uv: t.swap, + inside_out: t.inside_out, + circle_nurb: circle, + }, + dom.as_ref(), + )) +} + +fn nurb_of(c: &Curve) -> Option<&NurbCurve> { + match c.kind() { + CurveKind::BSpline(b) => b.gw_nurb(), + CurveKind::Ellipse(e) => e.base().gw_nurb(), + CurveKind::Line(l) => l.base().gw_nurb(), + CurveKind::Hyperbola(h) => h.base().gw_nurb(), + CurveKind::Parabola(p) => p.base().gw_nurb(), + _ => None, + } +} + +fn step_interval(c: &Curve) -> Extent1d { + match c.kind() { + CurveKind::Ellipse(e) => e.step_interval(), + CurveKind::Line(l) => l.step_interval(), + CurveKind::Hyperbola(h) => h.step_interval(), + CurveKind::Parabola(p) => p.step_interval(), + _ => c.natural_interval(), + } +} + +fn put_curve(o: &mut Vec, c: Option<&Curve>) { + o.push(flag(c.is_some())); + let Some(c) = c else { return }; + o.push(c.type_code() as f64); + let ivl = step_interval(c); + o.extend([ivl.min(), ivl.max()]); + let cur = nurb_of(c); + o.push(flag(cur.is_some())); + if let Some(cur) = cur { + o.push(cur.degree() as f64); + o.push(cur.cpts().len() as f64); + for p in cur.cpts() { + o.extend([p.x, p.y, p.z, p.w]); + } + o.push(cur.knots().len() as f64); + o.extend_from_slice(cur.knots()); + } + match c.kind() { + CurveKind::Ellipse(e) => { + o.push(1.0); + let p = e.position(); + push_v(o, p.origin()); + push_v(o, p.x_axis()); + push_v(o, p.y_axis()); + o.extend([e.x_radius(), e.y_radius(), flag(e.inside_out())]); + } + _ => o.push(0.0), + } +} + +fn put_state(o: &mut Vec, t: &Torus) -> solidcore::Result<()> { + let s = t.base(); + o.extend([ + flag(t.is_kind_of(TORUS_TYPE)?), + flag(t.is_kind_of(SURF_OF_REVOLUTION_TYPE)?), + flag(t.is_kind_of(B_SPLINE_SURFACE_TYPE)?), + flag(t.is_kind_of(TYPE)?), + t.type_code() as f64, + flag(s.swap_uv()), + flag(s.planar_generator()), + flag(t.inside_out()), + t.major_radius(), + t.minor_radius(), + ]); + let pos = s.position(); + push_v(o, pos.origin()); + push_v(o, pos.x_axis()); + push_v(o, pos.y_axis()); + let d = s.step_uv_domain(); + o.extend([d.min().x, d.min().y, d.max().x, d.max().y]); + let mx = t.max_analytic_domain(); + o.extend([mx.min().x, mx.min().y, mx.max().x, mx.max().y]); + let pc = s.polar_converter(); + o.push(flag(s.have_polar_conversion())); + let angles = pc.angles(); + o.push(angles.len() as f64); + if let (Some(a), Some(b)) = (angles.first(), angles.last()) { + o.extend([*a, *b]); + } + let polar_curve = s.polar_curve().map(|e| Curve::from(e.clone())); + put_curve(o, polar_curve.as_ref()); + put_curve(o, s.gen_curve()); + let sur = s.base().gw_nurb(); + o.push(flag(sur.is_some())); + let Some(sur) = sur else { return Ok(()) }; + 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()); + Ok(()) +} + +fn param_of(p: u8) -> SurfParamType { + if p == 0 { + SurfParamType::U + } else { + SurfParamType::V + } +} + +/// What a Rust op run produced: the record continues (`Ok`), or the op failed with an +/// error whose status ends the record (`Err`). Ops that write the state after a +/// failure push it themselves. +type OpRun = solidcore::Result<()>; + +/// Runs the op on the Rust torus, appending its status and outputs. `Err` from the +/// harness only; `Ok(Err(e))` when the op failed before writing its status. +fn rust_op( + k: &Kernel, + t: &mut Torus, + op: &OpIn, + o: &mut Vec, +) -> Result { + let r: OpRun = match op { + OpIn::Build {} => put_state(o, t), + OpIn::EvalStep { u, v, k: kk, point } => { + let uv = Point2::new(*u, *v); + if *point { + t.evaluate_step_point(uv).map(|p| { + o.push(0.0); + push_v(o, p); + }) + } else { + t.evaluate_step(uv, *kk, *kk).map(|d| { + o.push(0.0); + for v in d { + push_v(o, v); + } + }) + } + } + OpIn::ToStep { u, v } => t + .convert_uv_from_nurbs_to_step(k, Point2::new(*u, *v)) + .map(|p| o.extend([0.0, p.x, p.y])), + OpIn::ToNurbs { u, v } => t + .convert_uv_from_step_to_nurbs(Point2::new(*u, *v)) + .map(|p| o.extend([0.0, p.x, p.y])), + OpIn::Iso { param, iso, dom } => { + let dom = dom.map(|[a, b, c, d]| Extent2d::from_bounds(a, b, c, d)); + // the shim's guard: the base extraction must succeed + let probe = t.base().base().create_iso_parametric_curve( + k, + param_of(*param), + *iso, + IsoCurveOptions { + domain: dom, + uv_iso_curve: false, + }, + ); + match probe { + Err(e) if e.is_unported() => Err(e), + Err(_) => { + o.push(SC_ERR_ARG); + Ok(()) + } + Ok(_) => t + .create_iso_parametric_curve(k, param_of(*param), *iso, dom.as_ref()) + .map(|c| { + o.extend([0.0, c.max_gap_3d]); + put_curve(o, Some(&c.curve)); + }), + } + } + OpIn::Offset { dist, tol, rebuild } => { + t.create_offset_surface(k, *dist, *tol).and_then(|off| { + o.push(0.0); + o.push(flag(off.is_some())); + if let Some(mut off) = off { + if *rebuild { + match off.rebuild_step_from_nurb_parameters(k) { + Err(e) if e.is_unported() => return Err(e), + Err(e) => o.push(status(&e)), + Ok(()) => o.push(0.0), + } + } + put_state(o, &off)?; + } + Ok(()) + }) + } + OpIn::Rebuild {} => match t.rebuild_step_from_nurb_parameters(k) { + Err(e) if e.is_unported() => Err(e), + r => { + o.push(r.map_or_else(|e| status(&e), |()| 0.0)); + put_state(o, t) + } + }, + OpIn::Transform { + origin, + x, + y, + scale, + } => { + let a2p = Axis2Placement::new(v3(*origin), v3(*x), v3(*y)); + match t.transform(k, &a2p, scale.map(v3)) { + Err(e) if e.is_unported() => Err(e), + r => { + o.push(r.map_or_else(|e| status(&e), |()| 0.0)); + put_state(o, t) + } + } + } + OpIn::Eq { other } => match rust_torus(k, other)? { + Err(e) if e.is_unported() => Err(e), + Err(e) => { + o.push(status(&e)); + Ok(()) + } + Ok(other) => { + o.extend([0.0, flag(t.approx_eq(&other)), flag(other.approx_eq(t))]); + Ok(()) + } + }, + OpIn::Copy {} => { + let c = t.copy(); + o.push(0.0); + put_state(o, &c).map(|()| o.push(flag(t.approx_eq(&c)))) + } + }; + Ok(r) +} + +fn rust(i: &Inputs) -> Result { + let k = Kernel::default(); + let mut o = Vec::new(); + match rust_torus(&k, &i.torus)? { + Err(e) => { + if let Some(out) = Output::unported(&e) { + return Ok(out); + } + o.push(status(&e)); + } + Ok(mut t) => { + o.push(0.0); + let at = o.len(); + if let Err(e) = rust_op(&k, &mut t, &i.op, &mut o)? { + if let Some(out) = Output::unported(&e) { + return Ok(out); + } + o.truncate(at); + o.push(status(&e)); + } + } + } + to_output(i, &o) +} + +// ---- generation ----------------------------------------------------------------------- + +fn unit_vec(rng: &mut Rng) -> [f64; 3] { + loop { + let [x, y, z] = rng.point3(-1.0, 1.0); + let l = (x * x + y * y + z * z).sqrt(); + if l > 0.2 && l <= 1.0 { + return [x / l, y / l, z / l]; + } + } +} + +fn cross3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] { + let [ax, ay, az] = a; + let [bx, by, bz] = b; + [ay * bz - az * by, az * bx - ax * bz, ax * by - ay * bx] +} + +fn normalize(a: [f64; 3]) -> [f64; 3] { + let [x, y, z] = a; + let l = (x * x + y * y + z * z).sqrt(); + [x / l, y / l, z / l] +} + +fn add(a: [f64; 3], b: [f64; 3], s: f64) -> [f64; 3] { + let [ax, ay, az] = a; + let [bx, by, bz] = b; + [ax + s * bx, ay + s * by, az + s * bz] +} + +/// A unit frame (X, Y); retried until both axes pass the FFI's unit check. +fn frame(rng: &mut Rng) -> ([f64; 3], [f64; 3]) { + match rng.below(3) { + 0 => ([1.0, 0.0, 0.0], [0.0, 1.0, 0.0]), + 1 => ([0.0, 1.0, 0.0], [0.0, 0.0, 1.0]), + _ => loop { + let z = unit_vec(rng); + let t = unit_vec(rng); + let y = normalize(cross3(z, t)); + let x = normalize(cross3(y, z)); + let unit = |v: [f64; 3]| { + let [a, b, c] = v; + ((a * a + b * b + c * c) - 1.0).abs() <= 1e-12 + }; + if unit(x) && unit(y) { + return (x, y); + } + }, + } +} + +fn gen_torus(rng: &mut Rng) -> TorusIn { + let origin = if rng.chance(0.3) { + [0.0, 0.0, 0.0] + } else { + rng.point3(-5.0, 5.0) + }; + let (x, y) = frame(rng); + let major = match rng.below(10) { + 0 => *rng.choose(&[0.0, -1.0, 1e-13]).unwrap_or(&0.0), + _ => rng.uniform(0.5, 6.0), + }; + let minor = match rng.below(12) { + 0 => *rng.choose(&[0.0, -0.5]).unwrap_or(&0.0), + // spindle torus (minor >= major) + 1 => major.abs() * rng.uniform(1.0, 1.5), + _ => major.abs() * rng.uniform(0.05, 0.9), + }; + let angular = |rng: &mut Rng| -> [f64; 2] { + match rng.below(4) { + 0 => [0.0, 360.0], + 1 => { + let a = rng.uniform(-360.0, 0.0); + [a, a + rng.uniform(10.0, 360.0)] + } + _ => { + let a = rng.uniform(0.0, 200.0); + [a, (a + rng.uniform(10.0, 160.0)).min(360.0)] + } + } + }; + let dom = rng.chance(0.5).then(|| { + let [u0, u1] = angular(rng); + let [v0, v1] = angular(rng); + [u0, v0, u1, v1] + }); + // an explicit generator: the minor circle the constructor would make, over the V + // interval (or a little off it) + let circle = (rng.chance(0.15) && minor > 1e-6).then(|| { + let [_, v0, _, v1] = dom.unwrap_or([0.0, 0.0, 360.0, 360.0]); + let d = if rng.chance(0.7) { + [v0, v1] + } else { + [v0, (v1 - rng.uniform(1.0, 5.0)).max(v0 + 1.0)] + }; + CircleIn { + c: add(origin, x, major), + x, + y: cross3(x, y), + d, + r: minor, + } + }); + TorusIn { + origin, + x, + y, + major, + minor, + swap: rng.chance(0.4), + inside_out: rng.chance(0.4), + dom, + circle, + } +} + +fn gen_case(rng: &mut Rng) -> Option { + let k = Kernel::default(); + let torus = gen_torus(rng); + // the Rust torus sizes the parameters (both sides get the same numbers) + let built = rust_torus(&k, &torus).ok()?.ok(); + let nd = built + .as_ref() + .map_or(Extent2d::from_bounds(0.0, 0.0, 1.0, 1.0), |b| { + b.base().base().natural_uv_domain() + }); + let sd = built + .as_ref() + .map_or(Extent2d::from_bounds(0.0, 0.0, 360.0, 360.0), |b| { + b.base().step_uv_domain() + }); + let pick = |rng: &mut Rng, lo: f64, hi: f64| -> f64 { + match rng.below(10) { + 0 => lo, + 1 => hi, + 2 => lo - rng.uniform(0.0, 0.5) * (hi - lo), + _ => lo + rng.unit() * (hi - lo), + } + }; + let op = match rng.below(13) { + 0 => OpIn::Build {}, + 1..=3 => OpIn::EvalStep { + u: pick(rng, sd.min().x, sd.max().x), + v: pick(rng, sd.min().y, sd.max().y), + k: rng.below(4) as usize, + point: rng.chance(0.2), + }, + 4 => OpIn::ToStep { + u: pick(rng, nd.min().x, nd.max().x), + v: pick(rng, nd.min().y, nd.max().y), + }, + 5 => OpIn::ToNurbs { + u: pick(rng, sd.min().x, sd.max().x), + v: pick(rng, sd.min().y, sd.max().y), + }, + 6 | 7 => { + let param = rng.below(2) as u8; + let (lo, hi) = if param == 0 { + (nd.min().x, nd.max().x) + } else { + (nd.min().y, nd.max().y) + }; + let dom = rng.chance(0.35).then(|| { + let a = nd.evaluate(rng.uniform(0.0, 0.4), rng.uniform(0.0, 0.4)); + let b = nd.evaluate(rng.uniform(0.6, 1.0), rng.uniform(0.6, 1.0)); + [a.x, a.y, b.x, b.y] + }); + OpIn::Iso { + param, + iso: pick(rng, lo, hi), + dom, + } + } + 8 => OpIn::Offset { + dist: rng.uniform(-1.5, 1.5) * torus.minor.abs(), + tol: *rng.choose(&[0.0, 1e-6, 1e-3, 0.1])?, + rebuild: rng.chance(0.4), + }, + 9 => OpIn::Rebuild {}, + 10 => { + let identity = rng.chance(0.4); + let (x, y) = if identity { + ([1.0, 0.0, 0.0], [0.0, 1.0, 0.0]) + } else { + frame(rng) + }; + let scale = match rng.below(4) { + 0 => None, + 1 => Some([1.0, 1.0, 1.0]), + 2 => { + let s = rng.uniform(0.5, 2.0); + Some([s, s, s]) + } + _ => Some([ + rng.uniform(0.5, 2.0), + rng.uniform(0.5, 2.0), + *rng.choose(&[1.0, -1.0, 0.0])?, + ]), + }; + OpIn::Transform { + origin: if identity { + [0.0; 3] + } else { + rng.point3(-2.0, 2.0) + }, + x, + y, + scale, + } + } + 11 => { + let mut other = torus.clone(); + match rng.below(5) { + 0 => {} + 1 => other.swap = !other.swap, + 2 => other.minor += 1e-3, + 3 => other.inside_out = !other.inside_out, + _ => other.origin = add(other.origin, [1e-3, 0.0, 0.0], 1.0), + } + OpIn::Eq { other } + } + _ => OpIn::Copy {}, + }; + Some(Inputs { torus, op }) +} + +impl UnitDiff for Runner { + fn id(&self) -> &'static str { + "surface.torus" + } + + fn generate(&self, rng: &mut Rng) -> Option { + for _ in 0..8 { + if let Some(i) = gen_case(rng) { + return serde_json::to_value(i).ok(); + } + } + None + } + + fn cases(&self, ctx: &CaseContext) -> Result, HarnessError> { + let ctx = CaseContext { + seeds: ctx.seeds.max(MIN_GENERATED), + ..ctx.clone() + }; + default_cases(self, &ctx) + } + + fn run_cpp(&self, case: &Case) -> Result { + cpp(&case.parse()?) + } + + fn run_rust(&self, case: &Case) -> Result { + rust(&case.parse()?) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::BTreeMap; + + fn op_name(op: &OpIn) -> &'static str { + match op { + OpIn::Build {} => "build", + OpIn::EvalStep { .. } => "eval_step", + OpIn::ToStep { .. } => "to_step", + OpIn::ToNurbs { .. } => "to_nurbs", + OpIn::Iso { .. } => "iso", + OpIn::Offset { .. } => "offset", + OpIn::Rebuild {} => "rebuild", + OpIn::Transform { .. } => "transform", + OpIn::Eq { .. } => "eq", + OpIn::Copy {} => "copy", + } + } + + /// The generators reach a successful construction and a successful run of every op + /// on the Rust side. + #[test] + fn generated_cases_cover_every_op() { + let mut tally: BTreeMap<(String, String), usize> = BTreeMap::new(); + for seed in 0..500u64 { + let mut rng = Rng::new(0x7075_0000 + seed); + let Some(i) = gen_case(&mut rng) else { + continue; + }; + let out = rust(&i).expect("harness"); + let kind = if let Some(d) = &out.deferred { + format!("deferred:{}", d.symbol) + } else { + let b = out.values.get("build").and_then(|v| v.first()).copied(); + let st = out.values.get("status").and_then(|v| v.first()).copied(); + format!("build:{b:?} status:{st:?}") + }; + *tally.entry((op_name(&i.op).to_string(), kind)).or_default() += 1; + } + for (k, v) in &tally { + eprintln!("{k:?} {v}"); + } + for op in [ + "build", + "eval_step", + "to_step", + "to_nurbs", + "iso", + "offset", + "rebuild", + "transform", + "eq", + "copy", + ] { + let ok = tally.iter().any(|((o, k), _)| { + o == op + && (k == "build:Some(0.0) status:Some(0.0)" + || k == "build:Some(0.0) status:None") + }); + assert!(ok, "{op} never succeeds"); + } + } +} diff --git a/crates/solidcore-ffi/src/units/surface_torus.rs b/crates/solidcore-ffi/src/units/surface_torus.rs new file mode 100644 index 0000000000..89e3623772 --- /dev/null +++ b/crates/solidcore-ffi/src/units/surface_torus.rs @@ -0,0 +1,354 @@ +//! FFI wrappers for unit `surface.torus`. +//! +//! Shim: `harness/cpp/shim/surface_torus.cpp` (`sc_surface_torus_eval`). +//! Upstream: `source/SMLib/src/SmTorus.cpp`. +//! +//! [`torus_op`] builds a torus with `SmTorus::CreateCanonical` from a [`TorusIn`] and +//! runs one [`Op`] on it. The shim's flat record is the construction status (`0` = +//! `SM_SUCCESS`, else the `SmStatus`), then on success the op's record: +//! +//! * `Build`: the state (no status). A state is `is_torus is_rev is_bspline is_surface +//! type swap planar inside_out major minor O[3] X[3] Y[3] D[4] MAXD[4] have_polar +//! angle_count [first last] polar_curve gen_curve has_nurb [p q nu nv cpts(4 nu nv, +//! v fastest) nku KU nkv KV]`; a curve is `has [type step0 step1 has_nurb [p n +//! cpts(4 n) nk knots] is_ellipse [O[3] X[3] Y[3] rx ry inside_out]]`. +//! * the other ops: their status, then on success `EvalStep`: `(k+1)^2` vectors (or the +//! point); `ToStep`, `ToNurbs`: `u v`; `Iso`: `gap curve` (the shim answers +//! `SC_ERR_ARG` without status when `SmBSplineSurface::CreateIsoParametricCurve` +//! fails, since the torus override then reads an uninitialised curve); `Offset`: +//! `has [rebuild_status] state`; `Rebuild`: the state (also after a failure); +//! `Transform`: the state (also after a failure); `Eq`: the other torus's +//! construction status, then on success `this == other`, `other == this`; `Copy`: +//! `state copy == this`. +//! +//! The wrappers accept only what upstream handles without undefined behaviour: finite +//! values, unit and independent placement axes (the base constructor passes the Y axis +//! to `IsOnPlane`, whose output a longer one leaves uninitialised), angular domains +//! inside `[-360, 360]` at most 360 long, generator circles with independent axes and a +//! positive radius, derivative orders up to 3. + +use crate::{call_with_buffer, FfiError}; + +mod sys { + extern "C" { + pub fn sc_surface_torus_eval( + op: i32, + input: *const f64, + n_in: i64, + out: *mut f64, + cap: i64, + out_len: *mut i64, + ) -> i32; + } +} + +/// `SmCircle(c, x, y, [d0, d1], r, 3)`, the optional generator (`pOptCircleNurb`). +#[derive(Debug, Clone, PartialEq)] +pub struct CircleIn { + /// Centre. + pub c: [f64; 3], + /// X axis. + pub x: [f64; 3], + /// Y axis. + pub y: [f64; 3], + /// Angular domain in degrees. + pub d: [f64; 2], + /// Radius. + pub r: f64, +} + +/// `SmTorus::CreateCanonical(SmAxis2Placement(origin, x, y), major, minor, .., swap, +/// inside_out, circle, dom)`. +#[derive(Debug, Clone, PartialEq)] +pub struct TorusIn { + /// Torus centre. + pub origin: [f64; 3], + /// Major circle start direction. + pub x: [f64; 3], + /// Major circle 90 degree direction. + pub y: [f64; 3], + /// Major radius. + pub major: f64, + /// Minor radius. + pub minor: f64, + /// `bSwapUV`. + pub swap: bool, + /// `bInsideOut`. + pub inside_out: bool, + /// Optional STEP domain `umin vmin umax vmax` in degrees. + pub dom: Option<[f64; 4]>, + /// Optional generator. + pub circle: Option, +} + +/// The upstream member run on the torus. +#[derive(Debug, Clone, PartialEq)] +pub enum Op { + /// The construction state only. + Build, + /// `EvaluateSTEP(uv, k, k, ..)`, or `EvaluateSTEPPoint` when `point`. + EvalStep { + /// Major angle in degrees. + u: f64, + /// Minor angle in degrees. + v: f64, + /// Derivative order (both directions), at most 3. + k: usize, + /// Use `EvaluateSTEPPoint`. + point: bool, + }, + /// `ConvertUVFromNURBSToSTEP`. + ToStep { + /// NURB u. + u: f64, + /// NURB v. + v: f64, + }, + /// `ConvertUVFromSTEPToNURBS`. + ToNurbs { + /// STEP u. + u: f64, + /// STEP v. + v: f64, + }, + /// `CreateIsoParametricCurve`. + Iso { + /// `0` = `SM_SP_U`, `1` = `SM_SP_V`. + param: u8, + /// Iso parameter. + iso: f64, + /// Optional trim domain. + dom: Option<[f64; 4]>, + }, + /// `CreateOffsetSurface`, then optionally `RebuildSTEPFromNURBParameters` on the + /// offset torus. + Offset { + /// Signed offset distance. + dist: f64, + /// Approximation tolerance. + tol: f64, + /// Rebuild the offset torus. + rebuild: bool, + }, + /// `RebuildSTEPFromNURBParameters`. + Rebuild, + /// `Transform(SmAxis2Placement(origin, x, y), scale)`. + Transform { + /// Placement origin. + origin: [f64; 3], + /// Placement X axis. + x: [f64; 3], + /// Placement Y axis. + y: [f64; 3], + /// Optional scale. + scale: Option<[f64; 3]>, + }, + /// `operator==` with another torus. + Eq { + /// The other torus. + other: Box, + }, + /// `Copy`. + Copy, +} + +impl Op { + fn code(&self) -> i32 { + match self { + Op::Build => 0, + Op::EvalStep { .. } => 1, + Op::ToStep { .. } => 2, + Op::ToNurbs { .. } => 3, + Op::Iso { .. } => 4, + Op::Offset { .. } => 5, + Op::Rebuild => 6, + Op::Transform { .. } => 7, + Op::Eq { .. } => 8, + Op::Copy => 9, + } + } +} + +fn bad(msg: impl Into) -> FfiError { + FfiError::InvalidArgument(msg.into()) +} + +fn finite(v: &[f64], what: &str) -> Result<(), FfiError> { + if v.iter().all(|x| x.is_finite()) { + Ok(()) + } else { + Err(bad(format!("{what}: non-finite value"))) + } +} + +fn len(v: [f64; 3]) -> f64 { + let [x, y, z] = v; + (x * x + y * y + z * z).sqrt() +} + +fn cross(a: [f64; 3], b: [f64; 3]) -> [f64; 3] { + let [ax, ay, az] = a; + let [bx, by, bz] = b; + [ay * bz - az * by, az * bx - ax * bz, ax * by - ay * bx] +} + +/// Checks two axes are long enough and far from parallel. +fn axes(x: [f64; 3], y: [f64; 3], what: &str) -> Result<(), FfiError> { + finite(&x, what)?; + finite(&y, what)?; + let (lx, ly) = (len(x), len(y)); + if lx < 1e-6 || ly < 1e-6 || len(cross(x, y)) < 1e-3 * lx * ly { + return Err(bad(format!("{what}: degenerate or parallel axes"))); + } + Ok(()) +} + +/// `SM_EFF_ZERO`. +const EFF_ZERO: f64 = 1e-12; + +/// Checks a vector is a unit vector within `SM_EFF_ZERO` on its squared length. +fn unit(v: [f64; 3], what: &str) -> Result<(), FfiError> { + let [x, y, z] = v; + if ((x * x + y * y + z * z) - 1.0).abs() > EFF_ZERO { + return Err(bad(format!("{what}: not a unit vector"))); + } + Ok(()) +} + +fn domain(d: [f64; 4], what: &str) -> Result<(), FfiError> { + finite(&d, what)?; + let [a, b, c, e] = d; + if !(a <= c && b <= e) { + return Err(bad(format!("{what}: domain not ordered"))); + } + Ok(()) +} + +fn angular(d0: f64, d1: f64, what: &str) -> Result<(), FfiError> { + finite(&[d0, d1], what)?; + if !(d0 < d1 && d0 >= -360.0 && d1 <= 360.0 && d1 - d0 <= 360.0) { + return Err(bad(format!("{what}: bad angular domain"))); + } + Ok(()) +} + +fn flag(b: bool) -> f64 { + f64::from(u8::from(b)) +} + +fn push_torus(o: &mut Vec, t: &TorusIn) -> Result<(), FfiError> { + finite(&t.origin, "origin")?; + axes(t.x, t.y, "placement")?; + unit(t.x, "placement X")?; + unit(t.y, "placement Y")?; + finite(&[t.major, t.minor], "radii")?; + o.extend_from_slice(&t.origin); + o.extend_from_slice(&t.x); + o.extend_from_slice(&t.y); + o.extend([t.major, t.minor, flag(t.swap), flag(t.inside_out)]); + o.push(flag(t.dom.is_some())); + let d = t.dom.unwrap_or([0.0, 0.0, 360.0, 360.0]); + domain(d, "torus domain")?; + let [u0, v0, u1, v1] = d; + angular(u0, u1, "torus domain u")?; + angular(v0, v1, "torus domain v")?; + o.extend_from_slice(&d); + o.push(flag(t.circle.is_some())); + match &t.circle { + Some(CircleIn { c, x, y, d, r }) => { + finite(c, "circle")?; + axes(*x, *y, "circle")?; + let [d0, d1] = *d; + angular(d0, d1, "circle")?; + if !(r.is_finite() && *r > 1e-6) { + return Err(bad("circle: radius")); + } + o.extend_from_slice(c); + o.extend_from_slice(x); + o.extend_from_slice(y); + o.extend([d0, d1, *r]); + } + None => o.extend([0.0; 12]), + } + Ok(()) +} + +fn push_op(o: &mut Vec, op: &Op) -> Result<(), FfiError> { + match op { + Op::Build | Op::Rebuild | Op::Copy => {} + Op::EvalStep { u, v, k, point } => { + finite(&[*u, *v], "eval")?; + if *k > 3 { + return Err(bad("eval: order above 3")); + } + o.extend([*u, *v, *k as f64, flag(*point)]); + } + Op::ToStep { u, v } | Op::ToNurbs { u, v } => { + finite(&[*u, *v], "convert")?; + o.extend([*u, *v]); + } + Op::Iso { param, iso, dom } => { + if *param > 1 { + return Err(bad("iso: param")); + } + finite(&[*iso], "iso")?; + o.extend([f64::from(*param), *iso, flag(dom.is_some())]); + let d = dom.unwrap_or([0.0, 0.0, 0.0, 0.0]); + domain(d, "iso domain")?; + o.extend_from_slice(&d); + } + Op::Offset { dist, tol, rebuild } => { + finite(&[*dist, *tol], "offset")?; + o.extend([*dist, *tol, flag(*rebuild)]); + } + Op::Transform { + origin, + x, + y, + scale, + } => { + finite(origin, "transform")?; + axes(*x, *y, "transform")?; + let s = scale.unwrap_or([1.0, 1.0, 1.0]); + finite(&s, "transform scale")?; + o.extend_from_slice(origin); + o.extend_from_slice(x); + o.extend_from_slice(y); + o.push(flag(scale.is_some())); + o.extend_from_slice(&s); + } + Op::Eq { other } => push_torus(o, other)?, + } + Ok(()) +} + +/// Builds the torus `t` and runs `op` on it; returns the shim's record (layout in the +/// module docs). +/// +/// # Errors +/// +/// [`FfiError::InvalidArgument`] for input the shim must not see; [`FfiError::Status`] +/// for a shim failure. +pub fn torus_op(t: &TorusIn, op: &Op) -> Result, FfiError> { + let mut input = Vec::new(); + push_torus(&mut input, t)?; + push_op(&mut input, op)?; + let code = op.code(); + call_with_buffer(4096, |buf, len| { + // SAFETY: input is valid for input.len() reads and holds exactly what the shim + // reads for `code` (built and validated above: finite values, unit independent + // placement axes, non-degenerate circles, ordered angular domains, orders up to + // 3); buf is valid for buf.len() writes and the shim honours the capacity; len + // is valid. + unsafe { + sys::sc_surface_torus_eval( + code, + input.as_ptr(), + input.len() as i64, + buf.as_mut_ptr(), + buf.len() as i64, + &raw mut *len, + ) + } + }) +} diff --git a/crates/solidcore/src/surface.rs b/crates/solidcore/src/surface.rs index 1149a4965f..da364ca53e 100644 --- a/crates/solidcore/src/surface.rs +++ b/crates/solidcore/src/surface.rs @@ -14,6 +14,7 @@ pub mod surf_of_revolution; #[allow(clippy::module_inception)] pub mod surface; pub mod surface_cache; +pub mod torus; pub mod trim_srf_cache; pub mod twist_volume; pub mod volume; diff --git a/crates/solidcore/src/surface/surf_of_revolution/p1.rs b/crates/solidcore/src/surface/surf_of_revolution/p1.rs index a270bd7267..2cd43699be 100644 --- a/crates/solidcore/src/surface/surf_of_revolution/p1.rs +++ b/crates/solidcore/src/surface/surf_of_revolution/p1.rs @@ -38,7 +38,10 @@ //! - **Derived class.** `SmSurfOfRevolution` derives from `SmBSplineSurface`; the base //! object is the [`SurfOfRevolution::base`] field. The surface joins //! [`crate::surface::surface::SurfaceKind`] with the units porting its remaining -//! virtual overrides (`_p2`, `_p3`). +//! virtual overrides (`_p2`, `_p3`). The NURB and polar-converter rebuilds reach +//! `CreateIsoParametricCurve` through a `SweepIsoFn` so that a derived class +//! (`SmTorus`, [`crate::surface::torus`]) supplies its override, as upstream's +//! virtual call does. //! - **Generator.** `m_pGenCurve` is a [`Curve`] of a kind derived from //! `SmBSplineCurve` (B-spline, ellipse, line, hyperbola, parabola); the other kinds //! are rejected (`InvalidInput`) where upstream casts. It is only `None` for the @@ -243,6 +246,12 @@ fn gen_with_out_of_bounds(c: &Curve, enabled: bool) -> Option { } } +/// The sweep iso-curve maker of the NURB and polar-converter rebuilds: upstream's +/// virtual `CreateIsoParametricCurve(.., eSurfParam, dIsoParameter, ..)` without trim +/// domain, of [`SurfOfRevolution`] or of a derived class (`SmTorus`). +pub(crate) type SweepIsoFn<'a> = + &'a dyn Fn(&SurfOfRevolution, &Kernel, SurfParamType, f64) -> Result; + // ---- results --------------------------------------------------------------------------- /// The result of [`SurfOfRevolution::create_iso_parametric_curve`]. @@ -507,6 +516,18 @@ impl SurfOfRevolution { &mut self, k: &Kernel, opt_sweep_curve: Option<&Curve>, + ) -> Result<()> { + self.make_nurb_with_sweep_params_using(k, opt_sweep_curve, &Self::own_sweep_iso) + } + + /// [`Self::make_nurb_with_sweep_params`] with the polar converter rebuilt from the + /// sweep iso curves of `iso` (upstream's virtual `CreateIsoParametricCurve`: a + /// derived class such as `SmTorus` passes its override). + pub(crate) fn make_nurb_with_sweep_params_using( + &mut self, + k: &Kernel, + opt_sweep_curve: Option<&Curve>, + iso: SweepIsoFn<'_>, ) -> Result<()> { // locals let surf_sweep_step_ivl = self.anal_uv_domain.u_interval(); @@ -552,20 +573,38 @@ impl SurfOfRevolution { self.base.nurb = tmp.nurb; // Set up the Polar conversion - create a sweep isoParametric curve - ignore_status(self.rebuild_polar_converter(k))?; + ignore_status(self.rebuild_polar_converter(k, iso))?; Ok(()) } + /// This class's `CreateIsoParametricCurve` without trim domain, as a [`SweepIsoFn`]. + fn own_sweep_iso( + &self, + k: &Kernel, + surf_param: SurfParamType, + iso_parameter: f64, + ) -> Result { + Ok(self + .create_iso_parametric_curve(k, surf_param, iso_parameter, None)? + .curve) + } + /// Rebuilds the polar converter from the sweep iso curve at the NURB domain's /// generator-direction start (or, when that circle is degenerate, at 45% of the - /// domain) (`RebuildPolarConverter`). + /// domain) (`RebuildPolarConverter`), the iso curves made by `iso` (upstream's + /// virtual `CreateIsoParametricCurve`: this class's + /// [`Self::create_iso_parametric_curve`] or a derived class's override). /// /// # Errors /// /// `SM_ERR` when both iso curves are degenerate; those of the iso curves (upstream /// then dereferences a null curve) and of the table set-up; `InvalidInput` when the /// iso curve is not a circle (upstream casts it). - pub(crate) fn rebuild_polar_converter(&mut self, k: &Kernel) -> Result<()> { + pub(crate) fn rebuild_polar_converter( + &mut self, + k: &Kernel, + iso: SweepIsoFn<'_>, + ) -> Result<()> { // Try the beginning of the curve if possible let nurb_domain = self.base.natural_uv_domain(); let nurb_min_uv = nurb_domain.min(); @@ -576,9 +615,7 @@ impl SurfOfRevolution { } else { (SurfParamType::V, nurb_min_uv.y) }; - let mut sweep_iso = self - .create_iso_parametric_curve(k, param, sweep_param, None)? - .curve; + let mut sweep_iso = iso(self, k, param, sweep_param)?; let mut iso_degenerate = sweep_iso.is_degenerate(EFF_ZERO, None)?; if iso_degenerate { @@ -591,9 +628,7 @@ impl SurfOfRevolution { } else { nurb_mid_uv.y }; - sweep_iso = self - .create_iso_parametric_curve(k, param, sweep_param, None)? - .curve; + sweep_iso = iso(self, k, param, sweep_param)?; iso_degenerate = sweep_iso.is_degenerate(EFF_ZERO, None)?; } diff --git a/crates/solidcore/src/surface/surface/p2.rs b/crates/solidcore/src/surface/surface/p2.rs index 1fdc8a4f17..e29c2d9831 100644 --- a/crates/solidcore/src/surface/surface/p2.rs +++ b/crates/solidcore/src/surface/surface/p2.rs @@ -170,6 +170,7 @@ use crate::nurbs::crv_fit::p1::ParamMethod; use crate::nurbs::knots::Side; use crate::surface::curve_bounded_surface::CurveBoundedSurface; use crate::surface::plane::Plane; +use crate::surface::torus::Torus; use crate::topology::database_io::DatabaseReader; // ---- type codes --------------------------------------------------------------------- @@ -912,8 +913,15 @@ impl Surface { "SmSphere::ReadFromDB", "surface.sphere", )), - // UNPORTED(surface.torus): SmTorus::ReadFromDB - TORUS_TYPE => Err(SolidError::unported("SmTorus::ReadFromDB", "surface.torus")), + // (SmTorus::ReadFromDB reads the torus record, then needs the unported + // SmSurfOfRevolution record; the torus is not a SurfaceKind yet) + TORUS_TYPE => Torus::read_from_db(db, version).and_then(|_| { + // UNPORTED(surface.surf_of_revolution_p3): SmSurfOfRevolution::ReadFromDB + Err(SolidError::unported( + "SmSurfOfRevolution::ReadFromDB", + "surface.surf_of_revolution_p3", + )) + }), CURVE_BOUNDED_SURFACE_TYPE => Ok(Self::from(CurveBoundedSurface::read_from_db( k, db, version, )?)), diff --git a/crates/solidcore/src/surface/torus.rs b/crates/solidcore/src/surface/torus.rs new file mode 100644 index 0000000000..c544aac3ed --- /dev/null +++ b/crates/solidcore/src/surface/torus.rs @@ -0,0 +1,1283 @@ +//! Tori (`SmTorus`): a surface of revolution sweeping a minor circle about the axis of +//! its placement, with the STEP evaluation `S(u, v) = O + (R + r cos v)(cos u X + +//! sin u Y) + r sin v Z` (angles in degrees), the conversions between the STEP and +//! NURB parameters, the circular iso curves, the offset torus and the rebuild of the +//! analytic data from the NURB. +//! +//! Upstream: `source/SMLib/src/SmTorus.cpp`, `source/SMLib/inc/SmTorus.h`. +//! +//! | Rust | Upstream | +//! |----------------------------------------------|----------------------------------------------| +//! | [`Torus`], [`Torus::new`], [`TorusOptions`] | `SmTorus`, `SmTorus(crOrigin, crXAxis, crYAxis, crAnalUVDomain, ..)` | +//! | [`Torus::empty`] | `SmTorus()` | +//! | `Clone`, [`Torus::copy`] | `SmTorus(const SmTorus&)`, `Copy` | +//! | `Drop` (implicit) | `~SmTorus` | +//! | [`Torus::approx_eq`] | `operator==` | +//! | [`Torus::create_canonical`] | `CreateCanonical` | +//! | [`Torus::make_nurb`] | `SmSurfOfRevolution::MakeNurb` on a torus (virtual iso curves) | +//! | [`Torus::create_offset_surface`] | `CreateOffsetSurface` | +//! | [`Torus::convert_uv_from_step_to_nurbs`] | `ConvertUVFromSTEPToNURBS` | +//! | [`Torus::convert_uv_from_nurbs_to_step`] | `ConvertUVFromNURBSToSTEP` | +//! | [`Torus::evaluate_step`], [`Torus::evaluate_step_point`] | `EvaluateSTEP`, `EvaluateSTEPPoint` | +//! | [`Torus::create_iso_parametric_curve`] | `CreateIsoParametricCurve` | +//! | [`Torus::rebuild_step_from_nurb_parameters`] | `RebuildSTEPFromNURBParameters` | +//! | [`Torus::transform`] | `Transform` | +//! | [`Torus::read_from_db`] | `ReadFromDB` | +//! | [`Torus::max_analytic_domain`] | `GetMaxAnalyticDomain` | +//! | [`Torus::is_kind_of`], [`Torus::TYPE`], [`Torus::type_code`], [`Torus::type_string`], [`Torus::class_string`] | `IsKindOf`, `GetType`, `GetTypeString`, `GetClassString` (`SM_COMMON`) | +//! | [`Torus::inside_out`], [`Torus::major_radius`], [`Torus::minor_radius`] | `GetInsideOut`, `GetMajorRadius`, `GetMinorRadius` (inline) | +//! +//! Deviations: +//! - **Derived class.** `SmTorus` derives from `SmSurfOfRevolution`; the base object is +//! the [`Torus::base`] field ([`SurfOfRevolution`]). Like the surface of revolution, +//! the torus is not yet a [`crate::surface::surface::SurfaceKind`]. Upstream's +//! virtual `CreateIsoParametricCurve`, called by the base class's NURB and +//! polar-converter rebuilds, reaches the torus override through +//! [`Torus::make_nurb`] and the private `rebuild_polar_converter`. +//! - **Copies.** As upstream's copy constructor, which leaves `m_bInsideOut` at its +//! default, `Clone` (and [`Torus::copy`]) resets [`Torus::inside_out`] to `false`. +//! The constructor also leaves it `false` (its `bInsideOut` only orients the minor +//! circle); only [`Torus::rebuild_step_from_nurb_parameters`] and +//! [`Torus::read_from_db`] set it. +//! - **Arguments.** Out-params become return values; `SmContext` arguments and the +//! arguments upstream marks `NotUsed` (`EvaluateSTEP`'s side flags, +//! `CreateIsoParametricCurve`'s approximation tolerance and UV iso line, +//! `ReadFromDB`'s type) are dropped. `EvaluateSTEP` entries it does not write are +//! `0`. `CreateOffsetSurface` returns `None` where upstream succeeds without a surface +//! (a new minor radius below the approximation tolerance). +//! - **Statuses.** Calls whose status upstream ignores keep upstream's values, except +//! that [`SolidError::Unported`] always propagates and a failure whose output +//! upstream would read uninitialised (a failed iso-curve extraction or evaluation, a +//! missing generator, NURB or iso-circle output) is returned. A generator that is not +//! an ellipse, which upstream casts to `SmCircle`, is `InvalidInput`. +//! `RebuildSTEPFromNURBParameters` returns `FALSE` (`SM_SUCCESS`) when the +//! constant-u iso curves are not circles; that is `Ok(())` with nothing changed. +//! - **Unported callees.** `Transform` finishes in `SmSurfOfRevolution::Transform` and +//! `ReadFromDB` in `SmSurfOfRevolution::ReadFromDB` (both unit +//! `surface.surf_of_revolution_p3`); they return [`SolidError::Unported`] after the +//! torus's own part. + +use core::ops::Div; + +use crate::curve::curve::{Curve, CurveKind}; +use crate::curve::ellipse::{Ellipse, EllipseOptions}; +use crate::error::{ignore_status, ignored, Result, SolidError}; +use crate::geom::axis2_placement::Axis2Placement; +use crate::geom::extent1d::Extent1d; +use crate::geom::extent2d::Extent2d; +use crate::geom::geom_utility::p1::point_project_to_plane; +use crate::geom::tol::are_same; +use crate::geom::vector2d::Point2; +use crate::geom::vector3d::{Point3, Vec3}; +use crate::kernel::Kernel; +use crate::math::{max2, min2, EFF_ZERO, EFF_ZERO_DEG, EFF_ZERO_SQRT, PI, UNDEF_DOUBLE}; +use crate::nurbs::b_spline_surface::{BSplineSurfaceForm, IsoCurveOptions, SurfParamType}; +use crate::nurbs::nurbs_crv::to_euclid; +use crate::surface::surf_of_revolution::{ + test_for_iso_circles, RevolutionIsoCurve, SurfOfRevolution, +}; +use crate::surface::surface::p2::{scale_step_angular_derivatives, TORUS_TYPE}; +use crate::topology::database_io::{DatabaseReader, FileType, InputStream}; + +const OP_NEW: &str = "surface::torus::Torus::new"; +const OP_CANONICAL: &str = "surface::torus::Torus::create_canonical"; +const OP_OFFSET: &str = "surface::torus::Torus::create_offset_surface"; +const OP_CONVERT: &str = "surface::torus::Torus::convert_uv"; +const OP_EVAL_STEP: &str = "surface::torus::Torus::evaluate_step"; +const OP_REBUILD: &str = "surface::torus::Torus::rebuild_step_from_nurb_parameters"; +const OP_TRANSFORM: &str = "surface::torus::Torus::transform"; + +/// `ANALYTIC_TOL_SCALE` (`SmConfig.h`). +const ANALYTIC_TOL_SCALE: f64 = 100.0; + +/// The optional constructor arguments of [`Torus::new`] and +/// [`Torus::create_canonical`]. +#[derive(Debug, Clone, Default)] +pub struct TorusOptions { + /// `bSwapUV`: the NURB's v is the sweep (major circle) direction. + pub swap_uv: bool, + /// `bInsideOut`: the minor circle's NURB runs against its STEP angle. + pub inside_out: bool, + /// `pOptCircleNurb`: the generator (minor circle) to use instead of making one; + /// a curve derived from `SmBSplineCurve`, a circle for the conversions and the + /// offset. + pub circle_nurb: Option, +} + +/// The torus's own data, captured for the iso curves the base class makes. +#[derive(Debug, Clone, Copy)] +struct Shape { + major_radius: f64, + minor_radius: f64, + inside_out: bool, +} + +/// A torus (`SmTorus`): the surface of revolution of a minor circle of radius +/// [`Torus::minor_radius`] centred on the major circle of radius +/// [`Torus::major_radius`] about the Z axis of the placement. STEP parameters are the +/// major angle `u` (from the X axis about Z) and the minor angle `v`, both in degrees. +#[derive(Debug)] +pub struct Torus { + /// The `SmSurfOfRevolution` base object. + pub(crate) base: SurfOfRevolution, + /// `m_dMajorRadius`. + pub(crate) major_radius: f64, + /// `m_dMinorRadius`. + pub(crate) minor_radius: f64, + /// `m_bInsideOut`. + pub(crate) inside_out: bool, +} + +/// The copy constructor: copies the surface of revolution and the radii; the +/// inside-out flag keeps its default (`false`), as upstream. +impl Clone for Torus { + fn clone(&self) -> Self { + Self { + base: self.base.clone(), + major_radius: self.major_radius, + minor_radius: self.minor_radius, + inside_out: false, + } + } +} + +/// `is >> value` of a double: stores the extracted value unless the stream's sentry +/// fails. +fn extract(s: &mut InputStream, slot: &mut f64) { + if let Some(v) = s.extract_double() { + *slot = v; + } +} + +/// The generator as the `SmCircle` upstream casts it to. +fn gen_circle<'a>(gen: Option<&'a Curve>, op: &'static str) -> Result<&'a Ellipse> { + match gen.map(Curve::kind) { + Some(CurveKind::Ellipse(e)) => Ok(e), + Some(_) => Err(SolidError::invalid_input(op, "generator is not a circle")), + None => Err(SolidError::null_pointer(op)), + } +} + +impl Torus { + /// `SmTorus_TYPE` (`SURF_BASE_TYPE + 15`). + pub const TYPE: i64 = TORUS_TYPE; + + // ---- construction --------------------------------------------------------------- + + /// The empty torus `ReadFromDB` fills (`SmTorus()`): the empty surface of + /// revolution and undefined radii. + #[must_use] + pub fn empty() -> Self { + Self { + base: SurfOfRevolution::empty(), + major_radius: UNDEF_DOUBLE, + minor_radius: UNDEF_DOUBLE, + inside_out: false, + } + } + + /// The torus centred at `origin` with major circle start `x_axis` and 90 degree + /// direction `y_axis` over `anal_uv_domain` (major by minor angles in degrees), + /// without NURB (constructor). Without `opts.circle_nurb` the generator is the + /// minor circle at `origin + major_radius x_axis` in the plane of `x_axis` and + /// `x_axis x y_axis` over the domain's V interval, inside out with + /// `opts.inside_out` (`SmCircle::CreateCanonical`); the generator is then not + /// tested for planarity (it is marked planar). The surface's own inside-out flag + /// stays `false`, as upstream. + /// + /// # Errors + /// + /// Those of the circle (a minor radius not above `SM_EFF_ZERO`, where upstream + /// dereferences the null circle) and of [`SurfOfRevolution::new`] for a given + /// generator. + pub fn new( + k: &Kernel, + origin: Point3, + x_axis: Vec3, + y_axis: Vec3, + anal_uv_domain: Extent2d, + major_radius: f64, + minor_radius: f64, + opts: TorusOptions, + ) -> Result { + let base = if let Some(gen) = opts.circle_nurb { + SurfOfRevolution::new(k, gen, origin, x_axis, y_axis, anal_uv_domain, opts.swap_uv)? + } else { + // (the base constructor without generator) + let mut base = SurfOfRevolution::empty(); + base.planar_generator = true; + base.position = Axis2Placement::new(origin, x_axis, y_axis); + base.anal_uv_domain = anal_uv_domain; + base.swap_uv = opts.swap_uv; + base.base + .set_b_spline_surface_form(BSplineSurfaceForm::SurfOfRevolution); + + // Domain limits + let gen_ivl = anal_uv_domain.v_interval(); + + // when not given a GenCurve to use: get minor circle parameters + let circle_center = origin + major_radius * x_axis; + let circle = Ellipse::create_canonical_circle( + k, + &Axis2Placement::new(circle_center, x_axis, x_axis.cross(y_axis)), + minor_radius, + Some(&gen_ivl), + Some(opts.inside_out), + ) + .map_err(|e| { + if e.is_unported() { + e + } else { + SolidError::null_pointer(OP_NEW) + } + })?; + base.gen_curve = Some(Curve::from(circle)); + base + }; + + // (the self-intersection test of a major radius below the minor radius only + // warns upstream) + Ok(Self { + base, + major_radius, + minor_radius, + inside_out: false, + }) + } + + /// The torus of `origin` (centre, sweep start and 90 degree directions) and the two + /// radii over `anal_uv_domain` (default `[0, 360] x [0, 360]`) with its NURB made + /// (`CreateCanonical`). + /// + /// # Errors + /// + /// `SM_ERR` for a radius not above `SM_EFF_ZERO` (or NaN) and when the NURB could + /// not be made; those of [`Self::new`]. + pub fn create_canonical( + k: &Kernel, + origin: &Axis2Placement, + major_radius: f64, + minor_radius: f64, + opts: TorusOptions, + anal_uv_domain: Option<&Extent2d>, + ) -> Result { + // non-positive minor or major -> genuinely degenerate (no valid NURB) + if minor_radius.is_nan() + || minor_radius <= EFF_ZERO + || major_radius.is_nan() + || major_radius <= EFF_ZERO + { + return Err(SolidError::sm_err(OP_CANONICAL)); + } + + // In the future, allow user to specify angles + let domain = anal_uv_domain + .copied() + .unwrap_or(Extent2d::from_bounds(0.0, 0.0, 360.0, 360.0)); + + let mut torus = Self::new( + k, + origin.origin(), + origin.x_axis(), + origin.y_axis(), + domain, + major_radius, + minor_radius, + opts, + )?; + + // Build NURB; fail cleanly rather than leak a null-NURB object. + match torus.make_nurb(k) { + Err(e) if e.is_unported() => return Err(e), + Err(_) => return Err(SolidError::sm_err(OP_CANONICAL)), + Ok(()) => {} + } + if torus.base.base.gw_nurb().is_none() { + return Err(SolidError::sm_err(OP_CANONICAL)); + } + Ok(torus) + } + + /// Makes the NURB (`SmSurfOfRevolution::MakeNurb`), the polar converter rebuilt + /// from the torus's iso circles. + /// + /// # Errors + /// + /// Those of [`SurfOfRevolution::make_nurb`]. + pub fn make_nurb(&mut self, k: &Kernel) -> Result<()> { + let shape = self.shape(); + self.base + .make_nurb_with_sweep_params_using(k, None, &|s, k, p, t| { + Ok(iso_curve(s, shape, k, p, t, None)?.curve) + }) + } + + /// `RebuildPolarConverter` with the torus's iso circles. + fn rebuild_polar_converter(&mut self, k: &Kernel) -> Result<()> { + let shape = self.shape(); + self.base.rebuild_polar_converter(k, &|s, k, p, t| { + Ok(iso_curve(s, shape, k, p, t, None)?.curve) + }) + } + + const fn shape(&self) -> Shape { + Shape { + major_radius: self.major_radius, + minor_radius: self.minor_radius, + inside_out: self.inside_out, + } + } + + /// A copy (`Copy`: the copy constructor, see `Clone`). + #[must_use] + pub fn copy(&self) -> Self { + self.clone() + } + + // ---- access --------------------------------------------------------------------- + + /// The `SmSurfOfRevolution` base object. + #[must_use] + pub const fn base(&self) -> &SurfOfRevolution { + &self.base + } + + /// The major radius (`GetMajorRadius`). + #[must_use] + pub const fn major_radius(&self) -> f64 { + self.major_radius + } + + /// The minor radius (`GetMinorRadius`). + #[must_use] + pub const fn minor_radius(&self) -> f64 { + self.minor_radius + } + + /// Whether the generator's NURB runs against the minor angle (`GetInsideOut`). + #[must_use] + pub const fn inside_out(&self) -> bool { + self.inside_out + } + + /// `[0, 360] x [0, 360]` (`GetMaxAnalyticDomain`). + #[must_use] + pub const fn max_analytic_domain(&self) -> Extent2d { + Extent2d::from_bounds(0.0, 0.0, 360.0, 360.0) + } + + /// `SmTorus_TYPE` (`GetType`). + #[must_use] + pub const fn type_code(&self) -> i64 { + Self::TYPE + } + + /// `"SmTorus"` (`GetTypeString`). + #[must_use] + pub const fn type_string(&self) -> &'static str { + "SmTorus" + } + + /// `"SmTorus"` (`GetClassString`). + #[must_use] + pub const fn class_string(&self) -> &'static str { + "SmTorus" + } + + /// `SmTorus_TYPE` or a type the surface of revolution is (`IsKindOf`). + /// + /// # Errors + /// + /// Those of [`SurfOfRevolution::is_kind_of`]. + pub fn is_kind_of(&self, t: i64) -> Result { + if t == Self::TYPE { + return Ok(true); + } + self.base.is_kind_of(t) + } + + // ---- equality ------------------------------------------------------------------- + + /// `operator==`: equal B-spline surfaces (`SmBSplineSurface::operator==`), radii + /// equal within `SM_EFF_ZERO` and the same inside-out flag. + #[must_use] + pub fn approx_eq(&self, other: &Self) -> bool { + // low work + if core::ptr::eq(self, other) { + return true; + } + + // first check the base + if !self.base.base.approx_eq(&other.base.base) { + return false; + } + + // check equivalence of these objects + crate::geom::tol::is_zero(self.major_radius - other.major_radius) + && crate::geom::tol::is_zero(self.minor_radius - other.minor_radius) + && self.inside_out == other.inside_out + } + + // ---- offset --------------------------------------------------------------------- + + /// The torus offset by `signed_offset_distance` along the NURB normal + /// (`CreateOffsetSurface`): a copy with the minor radius changed by the distance + /// (negated with [`SurfOfRevolution::swap_uv`]), the generator scaled about its + /// centre and every NURB control point scaled from the torus origin and moved back + /// towards the axis to keep the major radius. `None` when the new minor radius is + /// below `approx_tol_3d`. + /// + /// # Errors + /// + /// `SM_ERR_NULL_POINTER` without NURB; `InvalidInput` for a generator that is not + /// a circle (upstream casts it); those of the control points. + pub fn create_offset_surface( + &self, + k: &Kernel, + signed_offset_distance: f64, + approx_tol_3d: f64, + ) -> Result> { + // When UVs are swapped - reverse orientation of offset + let offset_sign = 1.0 * (if self.base.swap_uv { -1.0 } else { 1.0 }); + + // get new radius + let old_min_radius = self.minor_radius; + let new_min_radius = self.minor_radius + offset_sign * signed_offset_distance; + + // skip building degenerate or negative radius minor circles + if new_min_radius < approx_tol_3d { + return Ok(None); + } + + // copy the current torus + let mut off = self.clone(); + + // change the minor radius + off.minor_radius = new_min_radius; + + // scale the NewTorus->GenCurve about circle center + if off.base.gen_curve.is_some() { + let circle_origin = gen_circle(off.base.gen_curve.as_ref(), OP_OFFSET)? + .position + .origin(); + let scale = new_min_radius / old_min_radius; + let move_vec = (1.0 - scale) * circle_origin; + let scale_vec = Vec3::new(scale, scale, scale); + let mut mv = Axis2Placement::IDENTITY; + mv.translate(move_vec); + if let Some(gen) = off.base.gen_curve.as_mut() { + ignore_status(gen.transform(k, &mv, Some(scale_vec)))?; + } + } + + // rebuild the underlying Nurb Surface: just move the Nurb Points + let origin = self.base.position.origin(); + let z_axis = self.base.position.z_axis(); + let mut first_vec_p_size = 0.0; + + // offset the nurb by combining a scale and a translate + let scale = new_min_radius / old_min_radius; + let trans = (1.0 - scale) * self.major_radius; + + let swap_uv = self.base.swap_uv; + let off_nurb = off + .base + .base + .nurb + .as_mut() + .ok_or(SolidError::null_pointer(OP_OFFSET))?; + let ix = off_nurb.indices(); + + // For every m_pNurb ControlPoint - make inner iteration the Sweep direction + let (i_cnt, j_cnt) = if swap_uv { (ix.n, ix.m) } else { (ix.m, ix.n) }; + for ii in 0..=i_cnt { + // move every control point on this sweep curve row + for jj in 0..=j_cnt { + // get Control Point Euclidean position + let (a, b) = if swap_uv { (ii, jj) } else { (jj, ii) }; + let cp = off_nurb.cpt_mut(a, b)?; + let euclid = to_euclid(*cp); + + // get scale and move directions for this ControlPoint + let vec = Vec3::new( + euclid.x - origin.x, + euclid.y - origin.y, + euclid.z - origin.z, + ); + let mut vec_p = vec - vec.dot(z_axis) * z_axis; + + // scale VecP so that the translates are larger in the corners + if jj == 0 { + first_vec_p_size = vec_p.length(); + } + // (upstream's operator/, not the guarded operator/=) + vec_p = Div::div(vec_p, first_vec_p_size); + + // move the ControlPoint + cp.x = (origin.x + vec.x * scale + vec_p.x * trans) * cp.w; + cp.y = (origin.y + vec.y * scale + vec_p.y * trans) * cp.w; + cp.z = (origin.z + vec.z * scale + vec_p.z * trans) * cp.w; + } + } + Ok(Some(off)) + } + + // ---- parameter conversion ------------------------------------------------------- + + /// The NURB parameters of the STEP parameters `step_uv` (`ConvertUVFromSTEPToNURBS`): + /// the major angle through the polar converter, the minor angle through the + /// generator circle's conversion, swapped with [`SurfOfRevolution::swap_uv`]. + /// + /// # Errors + /// + /// `InvalidInput` without converter circle (upstream dereferences it) or for a + /// generator that is not a circle; those of the conversions. + pub fn convert_uv_from_step_to_nurbs(&self, step_uv: Point2) -> Result { + // convert sweep coordinate + let polar_curve = self + .base + .polar_curve + .as_ref() + .ok_or(SolidError::invalid_input( + OP_CONVERT, + "polar converter has no curve", + ))?; + let (mut nurbs_x, _exact) = + self.base + .polar + .convert_to_nurbs_parameter(polar_curve, step_uv.x, 0.0)?; + + // (Torus GenCurves are centred on 0: the PolarParam is the StepParam) + let polar_param = step_uv.y; + + // convert GenCurve PolarParam to NurbsParam + let circle = gen_circle(self.base.gen_curve.as_ref(), OP_CONVERT)?; + let mut nurbs_y = circle.convert_t_from_step_to_nurbs(polar_param)?; + + // m_bSwapUV - Nurb and STEP parameters are swapped + if self.base.swap_uv { + core::mem::swap(&mut nurbs_x, &mut nurbs_y); + } + Ok(Point2::new(nurbs_x, nurbs_y)) + } + + /// The STEP parameters of the NURB parameters `nurbs_uv` (`ConvertUVFromNURBSToSTEP`): + /// the sweep parameter through the polar converter, the generator parameter + /// through the generator circle's conversion. + /// + /// # Errors + /// + /// `InvalidInput` without converter circle or for a generator that is not a + /// circle; those of the conversions. + pub fn convert_uv_from_nurbs_to_step(&self, k: &Kernel, nurbs_uv: Point2) -> Result { + // m_bSwapUV - Nurb and STEP parameters are swapped + let nurb_uv = if self.base.swap_uv { + Point2::new(nurbs_uv.y, nurbs_uv.x) + } else { + nurbs_uv + }; + + // convert sweep coordinate + let polar_curve = self + .base + .polar_curve + .as_ref() + .ok_or(SolidError::invalid_input( + OP_CONVERT, + "polar converter has no curve", + ))?; + let (step_x, _exact) = + self.base + .polar + .convert_to_polar_parameter(k, polar_curve, nurb_uv.x)?; + + // convert genCurve NurbParam to PolarParam + let circle = gen_circle(self.base.gen_curve.as_ref(), OP_CONVERT)?; + let polar_param = circle.convert_t_from_nurbs_to_step(k, nurb_uv.y)?; + Ok(Point2::new(step_x, polar_param)) + } + + // ---- STEP evaluation ------------------------------------------------------------ + + /// The point and triangular derivatives at the STEP parameters `uv` (degrees), + /// clamped to the STEP domain (`EvaluateSTEP`); derivatives are per degree. The + /// result is laid out `[D, Dv, .., Du, Duv, .., Duu, ..]` (`(hu + 1) * (hv + 1)` + /// entries, index `i * (hv + 1) + j`); mixed derivatives above the lower order + /// are `0`. + /// + /// # Errors + /// + /// `SM_ERR` for orders above 3; `InvalidInput` for unequal orders (upstream asserts + /// and then indexes as if they were equal). + pub fn evaluate_step(&self, uv: Point2, hu: usize, hv: usize) -> Result> { + // check input + if hu > 3 || hv > 3 { + return Err(SolidError::sm_err(OP_EVAL_STEP)); + } + if hu != hv { + return Err(SolidError::invalid_input( + OP_EVAL_STEP, + "derivative orders must be equal", + )); + } + + // locals + let mut uv = uv; + let r_o = self.base.position.origin(); + let r_x = self.base.position.x_axis(); + let r_y = self.base.position.y_axis(); + let s_z = self.base.position.z_axis(); + let maj_r = self.major_radius; + let min_r = self.minor_radius; + let min_highest = hu.min(hv); + + // per SMLib practice clamp out of bounds UV points + if !self.base.anal_uv_domain.contains_point(uv, 0.0) { + uv = self.base.anal_uv_domain.clamp_point(uv); + } + + // evaluate expensive trigonometric functions + let cos_u = (uv.x * PI / 180.0).cos(); + let sin_u = (uv.x * PI / 180.0).sin(); + let cos_v = (uv.y * PI / 180.0).cos(); + let sin_v = (uv.y * PI / 180.0).sin(); + + let n = (hu + 1) * (hv + 1); + let mut d = vec![Vec3::ZERO; n]; + let mut set = |i: usize, v: Vec3| { + if let Some(slot) = d.get_mut(i) { + *slot = v; + } + }; + + // evaluate position + set( + 0, + r_o + (maj_r + min_r * cos_v) * (cos_u * r_x + sin_u * r_y) + (min_r * sin_v * s_z), + ); + + // 1st U and V derivatives + if hu >= 1 { + set( + hv + 1, + (maj_r + min_r * cos_v) * (-sin_u * r_x + cos_u * r_y), + ); + } + if hv >= 1 { + set( + 1, + (min_r * -sin_v) * (cos_u * r_x + sin_u * r_y) + (min_r * cos_v * s_z), + ); + } + + // cross derivative dUV + if hu >= 1 && hv >= 1 && min_highest >= 2 { + set(hv + 2, (min_r * -sin_v) * (-sin_u * r_x + cos_u * r_y)); + } + + // cross derivative dUVV + if hu >= 1 && hv >= 2 && min_highest >= 3 { + set(hv + 3, (min_r * -cos_v) * (-sin_u * r_x + cos_u * r_y)); + } + + // 2nd U and V derivatives + if hu >= 2 { + set( + 2 * hv + 2, + (maj_r + min_r * cos_v) * (-cos_u * r_x - sin_u * r_y), + ); + } + if hv >= 2 { + set( + 2, + (min_r * -cos_v) * (cos_u * r_x + sin_u * r_y) - (min_r * sin_v * s_z), + ); + } + + // cross derivative dUUV + if hu >= 2 && hv >= 1 && min_highest >= 3 { + set(2 * hv + 3, (min_r * -sin_v) * (-cos_u * r_x - sin_u * r_y)); + } + + // 3rd U and V derivatives + if hu >= 3 { + set( + 3 * hv + 3, + (maj_r + min_r * cos_v) * (sin_u * r_x - cos_u * r_y), + ); + } + if hv >= 3 { + set( + 3, + (min_r * sin_v) * (cos_u * r_x + sin_u * r_y) - (min_r * cos_v * s_z), + ); + } + + // The formulas above are per radian; STEP parameters are in degrees. + scale_step_angular_derivatives(&mut d, hu, hv, true, true)?; + Ok(d) + } + + /// The point at the STEP parameters `uv` (`EvaluateSTEPPoint`: + /// [`Self::evaluate_step`] without derivatives). + /// + /// # Errors + /// + /// Those of [`Self::evaluate_step`]. + pub fn evaluate_step_point(&self, uv: Point2) -> Result { + self.evaluate_step(uv, 0, 0)? + .first() + .copied() + .ok_or(SolidError::invalid_input(OP_EVAL_STEP, "empty evaluation")) + } + + // ---- iso curves ----------------------------------------------------------------- + + /// The iso-parametric curve at the NURB parameter `iso_parameter` in `surf_param`'s + /// direction (`CreateIsoParametricCurve`): the B-spline extraction of the NURB + /// (trimmed to `domain`) as a circle (`SmCircle`) with that NURB, a minor circle + /// for constant STEP u and a circle about the axis otherwise, over the STEP + /// interval or, with `domain`, the angles of the curve's ends. A degenerate + /// extraction is returned as the B-spline. + /// + /// # Errors + /// + /// Those of the extraction, the end-point evaluations and the circle. + pub fn create_iso_parametric_curve( + &self, + k: &Kernel, + surf_param: SurfParamType, + iso_parameter: f64, + domain: Option<&Extent2d>, + ) -> Result { + iso_curve( + &self.base, + self.shape(), + k, + surf_param, + iso_parameter, + domain, + ) + } + + // ---- analytic data from the NURB ------------------------------------------------ + + /// Rebuilds the generator, radii, inside-out flag, placement, swap flag, STEP + /// domain and polar converter from the NURB (`RebuildSTEPFromNURBParameters`): the + /// NURB must be rational of degree 2 both ways with circular iso curves, those in + /// one direction on parallel planes and those in the other on planes through a + /// common line (the axis). Nothing changes when the constant-u iso curves are not + /// circles. + /// + /// # Errors + /// + /// `SM_ERR` for another degree or a non-rational NURB, iso circles without the + /// torus orientation or a degenerate generator; `InvalidInput` when an iso-circle + /// output upstream would read is missing; those of the iso-circle tests, the + /// evaluations, the generator extraction and the circle. + pub fn rebuild_step_from_nurb_parameters(&mut self, k: &Kernel) -> Result<()> { + let srf = &self.base.base; + + // check m_pNurb - a degree 2 rational surface in at least 1 direction + if srf.degree(SurfParamType::U) != 2 + || srf.degree(SurfParamType::V) != 2 + || !srf.is_rational() + { + return Err(SolidError::sm_err(OP_REBUILD)); + } + + // evaluate min/max domain points + let nurb_uv_domain = srf.natural_uv_domain(); + let nurb_min_point = srf.evaluate_point(nurb_uv_domain.min())?; + let nurb_max_point = srf.evaluate_point(nurb_uv_domain.max())?; + + // scaled zero value + let tolerance_scale = 1000.0; // loose because surf is already labeled analytic + let scaled_zero = tolerance_scale + * ANALYTIC_TOL_SCALE + * EFF_ZERO + * (1.0 + nurb_min_point.max_dimension() + nurb_max_point.max_dimension()); + let ang_tol = EFF_ZERO_SQRT * tolerance_scale; + + // test sequence of V Direction (U Constant) isoCurves for circularity and + // parallelism + let iso_u = test_for_iso_circles(k, srf, SurfParamType::U, scaled_zero, ang_tol)?; + + // quit when not a torus (upstream returns FALSE, i.e. SM_SUCCESS) + if !iso_u.found_circles { + return Ok(()); + } + + // test sequence of U Direction (V Constant) isoCurves + let iso_v = test_for_iso_circles(k, srf, SurfParamType::V, scaled_zero, ang_tol)?; + + // see if we have a U or a V oriented torus + let is_torus_with_swap_uv = iso_u.parallel_planes && iso_v.common_axis; + let is_torus_no_swap_uv = iso_v.parallel_planes && iso_u.common_axis; + + // quit when not a properly oriented torus + if !is_torus_with_swap_uv && !is_torus_no_swap_uv { + return Err(SolidError::sm_err(OP_REBUILD)); + } + + // locals + let min_uv = nurb_uv_domain.evaluate(0.0, 0.0); + let missing = || SolidError::invalid_input(OP_REBUILD, "iso circle output not written"); + + // Get torus position and parameters + let (swap_uv, ref_iso, other_iso) = if is_torus_no_swap_uv { + // frame where all circles are parallel / where all circle planes intersect + // on a common line + (false, &iso_v, &iso_u) + } else { + (true, &iso_u, &iso_v) + }; + let mut ref_frame = ref_iso.reference_frame.ok_or_else(missing)?; + let other_frame = other_iso.reference_frame.ok_or_else(missing)?; + let min_rad = other_iso.radius.ok_or_else(missing)?; + let sweep_ivl = ref_iso.angles.ok_or_else(missing)?; + let common_point = other_iso.common_line_point.ok_or_else(missing)?; + let common_vec = other_iso.common_line_vec.ok_or_else(missing)?; + + // get torus origin = MinorCircleCenter projected onto common line + let torus_origin = + common_point + common_vec.dot(other_frame.origin() - common_point) * common_vec; + + // Install the origin into pRefFrame. // [B328] + let _ = ref_frame.set_canonical(torus_origin, ref_frame.x_axis(), ref_frame.y_axis()); + + // get torus orientation and size + let major_vec = other_frame.origin() - ref_frame.origin(); + let maj_rad = major_vec.length(); + + // Check GenCurve minor circle rotation direction, its y axis is either +/- + // sweepCircle z + let torus_z_axis = ref_frame.z_axis(); + let minor_orientation = other_frame.z_axis().cross(major_vec); + let inside_out = torus_z_axis.dot(minor_orientation) < 0.0; + + // project Nurb Min/Max Point to Torus XY plane to get vec to Center of each + // GenCurve + let min_plane_point = point_project_to_plane(nurb_min_point, torus_origin, torus_z_axis); + let max_plane_point = point_project_to_plane(nurb_max_point, torus_origin, torus_z_axis); + let start_for_min_angle = (min_plane_point - torus_origin).unitize_or_z(); + let start_for_max_angle = (max_plane_point - torus_origin).unitize_or_z(); + let min_origin = torus_origin + maj_rad * start_for_min_angle; + let max_origin = torus_origin + maj_rad * start_for_max_angle; + + // Get Vec from Nurb Min/Max points to each genCurve origin + let vec_min = nurb_min_point - min_origin; + let vec_max = nurb_max_point - max_origin; + + // get GenCurve min and max angles (statuses ignored upstream: 0 on failure) + let mut gen_angle_min = ignored(start_for_min_angle.angle_between(vec_min))?.unwrap_or(0.0); + let mut gen_angle_max = ignored(start_for_max_angle.angle_between(vec_max))?.unwrap_or(0.0); + gen_angle_min = 180.0 * gen_angle_min / PI; + gen_angle_max = 180.0 * gen_angle_max / PI; + + // snap to ends for small tolerances + if gen_angle_min.abs() < EFF_ZERO_DEG { + gen_angle_min = 0.0; + } + if gen_angle_max.abs() < EFF_ZERO_DEG || (gen_angle_max - 360.0).abs() < EFF_ZERO_DEG { + gen_angle_max = 360.0; + } + if gen_angle_max < gen_angle_min + EFF_ZERO_DEG { + gen_angle_max += 360.0; + } + + // define Torus domain + let anal_uv_domain = Extent2d::from_bounds( + sweep_ivl.min(), + gen_angle_min, + sweep_ivl.max(), + gen_angle_max, + ); + + // Get GenCurve Isoparameter Curve + let gen_curve_iso = srf.create_iso_parametric_curve( + k, + if swap_uv { + SurfParamType::V + } else { + SurfParamType::U + }, + if swap_uv { min_uv.y } else { min_uv.x }, + IsoCurveOptions::default(), + )?; + let circle = Ellipse::new_circle( + k, + min_origin, + start_for_min_angle, + torus_z_axis, + anal_uv_domain.v_interval(), + min_rad, + &EllipseOptions { + dim: 3, + nurb: Some(&gen_curve_iso.curve), + inside_out, + }, + )?; + + // no work - GenCurve is degenerate + if circle.is_degenerate(scaled_zero) { + return Err(SolidError::sm_err(OP_REBUILD)); + } + + // set m_pGenCurve + self.base.gen_curve = Some(Curve::from(circle)); + + // set Analytic parameters + self.major_radius = maj_rad; + self.minor_radius = min_rad; + self.inside_out = inside_out; + self.base.planar_generator = true; + self.base.position = ref_frame; + self.base.swap_uv = swap_uv; + self.base.anal_uv_domain = anal_uv_domain; + + // Set PolarConverter from Nurb midPoint rotation isoParamCurve (status ignored) + ignore_status(self.rebuild_polar_converter(k))?; + Ok(()) + } + + // ---- transform ------------------------------------------------------------------ + + /// Scales the radii by the optional uniform `scale`, then transforms the surface of + /// revolution (`Transform`). The identity placement with no or unit scale changes + /// nothing. + /// + /// # Errors + /// + /// `SM_ERR` for a non-uniform scale or one below `SM_EFF_ZERO`; + /// [`SolidError::Unported`] from `SmSurfOfRevolution::Transform` (unit + /// `surface.surf_of_revolution_p3`), after the radii are scaled. + pub fn transform( + &mut self, + k: &Kernel, + rotate_n_move: &Axis2Placement, + scale: Option, + ) -> Result<()> { + let _ = k; + let identity_scale = Vec3::new(1.0, 1.0, 1.0); + + // no work - identity transform + if rotate_n_move.is_identity(EFF_ZERO) && scale.is_none_or(|s| s.approx_eq(identity_scale)) + { + return Ok(()); + } + + // check input and get scaling value - only allow uniform scaling + let mut d_scale = 1.0; + if let Some(s) = scale { + if !are_same(s.x, s.y) || !are_same(s.x, s.z) || s.x < EFF_ZERO { + return Err(SolidError::sm_err(OP_TRANSFORM)); + } + d_scale = s.x; + } + + // update cone size + self.major_radius *= d_scale; + self.minor_radius *= d_scale; + + // finish the call in the parent function (status ignored upstream) + // UNPORTED(surface.surf_of_revolution_p3): SmSurfOfRevolution::Transform + Err(SolidError::unported( + "SmSurfOfRevolution::Transform", + "surface.surf_of_revolution_p3", + )) + } + + // ---- I/O ------------------------------------------------------------------------ + + /// Reads a torus record (`ReadFromDB`): the major radius, the minor radius and the + /// inside-out flag (one per line in ASCII files), then the surface of revolution + /// record. Unreadable ASCII values keep their defaults. + /// + /// # Errors + /// + /// A failed binary read; [`SolidError::Unported`] for the surface of revolution + /// record (`SmSurfOfRevolution::ReadFromDB`, unit `surface.surf_of_revolution_p3`). + pub fn read_from_db(db: &mut DatabaseReader, version: u64) -> Result { + let _ = version; + + // init output object + let mut torus = Self::empty(); + + if db.file_type() == FileType::Ascii { + if let Some(s) = db.in_stream_mut() { + extract(s, &mut torus.major_radius); + } + db.go_to_next_line(); + if let Some(s) = db.in_stream_mut() { + extract(s, &mut torus.minor_radius); + } + db.go_to_next_line(); + if let Some(v) = db.in_stream_mut().and_then(InputStream::extract_int) { + torus.inside_out = v != 0; + } + db.go_to_next_line(); + } else { + torus.major_radius = db.read_double()?; + torus.minor_radius = db.read_double()?; + torus.inside_out = db.read_boolean()? != 0; + } + + // read the parent object + // UNPORTED(surface.surf_of_revolution_p3): SmSurfOfRevolution::ReadFromDB + Err(SolidError::unported( + "SmSurfOfRevolution::ReadFromDB", + "surface.surf_of_revolution_p3", + )) + } +} + +/// `SmTorus::CreateIsoParametricCurve` of the surface of revolution `rev` of a torus +/// of `shape` (also called from the base class's rebuilds, where it is virtual). +fn iso_curve( + rev: &SurfOfRevolution, + shape: Shape, + k: &Kernel, + surf_param: SurfParamType, + iso_parameter: f64, + domain: Option<&Extent2d>, +) -> Result { + // get the BSplineCurve (status ignored upstream, which then reads the curve) + let iso = rev.base.create_iso_parametric_curve( + k, + surf_param, + iso_parameter, + IsoCurveOptions { + domain: domain.copied(), + uv_iso_curve: false, + }, + )?; + + // low work - when curve is degenerate don't bother making a circle + if iso.curve.is_degenerate(EFF_ZERO, None)? { + return Ok(RevolutionIsoCurve { + curve: Curve::from(iso.curve), + max_gap_3d: iso.max_gap_3d, + uv_iso_curve: iso.uv_iso_curve, + }); + } + + // locals + let step_param = if rev.swap_uv { + if surf_param == SurfParamType::U { + SurfParamType::V + } else { + SurfParamType::U + } + } else { + surf_param + }; + let torus_origin = rev.position.origin(); + let torus_x_axis = rev.position.x_axis(); + let torus_y_axis = rev.position.y_axis(); + let torus_z_axis = rev.position.z_axis(); + + // Get IsoCurve Nurb endPoints (statuses ignored upstream) + let nurb_ivl = iso.curve.natural_interval(); + let nurb_min_point = iso.curve.evaluate_point(nurb_ivl.min())?; + let nurb_max_point = iso.curve.evaluate_point(nurb_ivl.max())?; + let nurb_min_vec = nurb_min_point - torus_origin; + let nurb_max_vec = nurb_max_point - torus_origin; + + // get iso circle parameters + let (circle_center, x_axis, y_axis, radius, inside_out, anal_domain) = if step_param + == SurfParamType::U + { + // return minor isoCurve circle + let mut x_axis = torus_z_axis.cross(nurb_min_vec.cross(torus_z_axis)); + let mut length = x_axis.length(); + if length < EFF_ZERO_SQRT { + // (the mid point is evaluated but not used upstream) + let _ = ignored(iso.curve.evaluate_point(nurb_ivl.evaluate(0.5)))?; + x_axis = torus_z_axis.cross(nurb_min_vec).cross(torus_z_axis); + length = x_axis.length(); + } + x_axis = Div::div(x_axis, length); + ( + rev.position.origin() + shape.major_radius * x_axis, + x_axis, + torus_z_axis, + shape.minor_radius, + shape.inside_out, + rev.anal_uv_domain.v_interval(), + ) + } else { + // return sweep isoCurve circle + let circle_center = rev.position.origin() + nurb_min_vec.dot(torus_z_axis) * torus_z_axis; + ( + circle_center, + torus_x_axis, + torus_y_axis, + (nurb_min_point - circle_center).length(), + false, + rev.anal_uv_domain.u_interval(), + ) + }; + + // get isoParamCurve domain + let circle_anal_domain = if domain.is_none() { + // when using the current domain + anal_domain + } else { + // get angles to min/max points inside periodic interval (statuses ignored + // upstream; a failed angle is 0) + let circle_z_axis = x_axis.cross(y_axis); + let min_ang_rad = + ignored(circle_z_axis.ccw_angle_between(x_axis, nurb_min_vec))?.unwrap_or(0.0); + let max_ang_rad = + ignored(circle_z_axis.ccw_angle_between(x_axis, nurb_max_vec))?.unwrap_or(0.0); + let min_ang_deg = anal_domain + .contains_periodic_value(min_ang_rad * 180.0 / PI, 360.0, -1.0) + .periodic_value; + let mut max_ang_deg = anal_domain + .contains_periodic_value(max_ang_rad * 180.0 / PI, 360.0, -1.0) + .periodic_value; + + // when both values are on a closed interval seam + if anal_domain.is_closed(360.0) + && anal_domain + .is_value_on_periodic_boundary(min_ang_deg, 360.0, EFF_ZERO) + .0 + && anal_domain + .is_value_on_periodic_boundary(max_ang_deg, 360.0, EFF_ZERO) + .0 + { + // set interval to whole domain + anal_domain + } else { + // set isoCurve analytic domain accounting for m_binside reversals + if (max_ang_deg - min_ang_deg).abs() < anal_domain.length() - EFF_ZERO { + max_ang_deg = min_ang_deg + anal_domain.length(); + } + let mut ivl = Extent1d::new(); + let _ = ivl.set_min_max( + min2(min_ang_deg, max_ang_deg), + max2(min_ang_deg, max_ang_deg), + ); + ivl + } + }; + + // create the circle using the isoCurve parameterization + let circle = Ellipse::new_circle( + k, + circle_center, + x_axis, + y_axis, + circle_anal_domain, + radius, + &EllipseOptions { + dim: 3, + nurb: Some(&iso.curve), + inside_out, + }, + )?; + Ok(RevolutionIsoCurve { + curve: Curve::from(circle), + max_gap_3d: iso.max_gap_3d, + uv_iso_curve: iso.uv_iso_curve, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn canonical(k: &Kernel, swap: bool, inside_out: bool) -> Torus { + let frame = Axis2Placement::new( + Vec3::new(1.0, 2.0, 0.5), + Vec3::new(1.0, 0.0, 0.0), + Vec3::new(0.0, 1.0, 0.0), + ); + Torus::create_canonical( + k, + &frame, + 3.0, + 1.0, + TorusOptions { + swap_uv: swap, + inside_out, + circle_nurb: None, + }, + None, + ) + .expect("torus") + } + + #[test] + fn step_and_nurb_points_agree() { + let k = Kernel::default(); + for swap in [false, true] { + for inside_out in [false, true] { + let t = canonical(&k, swap, inside_out); + assert!(t.base.have_polar_conversion()); + for (u, v) in [(10.0, 20.0), (100.0, 250.0), (359.0, 1.0), (0.0, 0.0)] { + let step = Point2::new(u, v); + let p = t.evaluate_step_point(step).expect("eval"); + let nurb = t.convert_uv_from_step_to_nurbs(step).expect("to nurbs"); + let q = t.base.base.evaluate_point(nurb).expect("nurb eval"); + assert!((p - q).length() < 1e-9, "{swap} {inside_out} {u} {v}"); + let back = t.convert_uv_from_nurbs_to_step(&k, nurb).expect("to step"); + let r = t.evaluate_step_point(back).expect("eval back"); + assert!((p - r).length() < 1e-9); + } + } + } + } + + #[test] + fn rejects_degenerate_radii() { + let k = Kernel::default(); + let frame = Axis2Placement::IDENTITY; + for (a, b) in [(0.0, 1.0), (1.0, 0.0), (f64::NAN, 1.0), (-1.0, 0.5)] { + let r = Torus::create_canonical(&k, &frame, a, b, TorusOptions::default(), None); + assert!(matches!(r, Err(SolidError::Upstream { .. }))); + } + } + + #[test] + fn offset_keeps_major_radius() { + let k = Kernel::default(); + let t = canonical(&k, false, false); + let off = t + .create_offset_surface(&k, 0.25, 1e-6) + .expect("offset") + .expect("surface"); + assert!((off.minor_radius() - 1.25).abs() < 1e-15); + let nurb = off + .convert_uv_from_step_to_nurbs(Point2::new(30.0, 60.0)) + .expect("uv"); + let p = off.base.base.evaluate_point(nurb).expect("eval"); + let q = off + .evaluate_step_point(Point2::new(30.0, 60.0)) + .expect("step"); + assert!((p - q).length() < 1e-9); + assert!(t + .create_offset_surface(&k, -2.0, 1e-6) + .expect("offset") + .is_none()); + } + + #[test] + fn rebuild_recovers_the_torus() { + let k = Kernel::default(); + let t = canonical(&k, false, false); + let mut r = t.clone(); + r.rebuild_step_from_nurb_parameters(&k).expect("rebuild"); + assert!((r.major_radius() - 3.0).abs() < 1e-9); + assert!((r.minor_radius() - 1.0).abs() < 1e-9); + assert!(t.approx_eq(&r)); + } + + #[test] + fn read_reaches_the_base_record() { + let mut db = DatabaseReader::new(); + let r = Torus::read_from_db(&mut db, 0); + assert!(r.is_err()); + } +} diff --git a/fixtures/surface.torus/cases.json b/fixtures/surface.torus/cases.json new file mode 100644 index 0000000000..74b13ca2b7 --- /dev/null +++ b/fixtures/surface.torus/cases.json @@ -0,0 +1,1917 @@ +{ + "cases": [ + { + "name": "general_build", + "upstream_ref": "tests/SM_API_test/src/TestSmGeneral.cpp:1356 (SmTorus::CreateCanonical(identity placement, 5, 1); sm_CheckSTEPDerivatives at (u, 40) for u = 20, 110, 250)", + "inputs": { + "torus": { + "origin": [ + 0, + 0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 5.0, + "minor": 1.0 + }, + "op": "build" + } + }, + { + "name": "general_eval_u20_k1", + "upstream_ref": "tests/SM_API_test/src/TestSmGeneral.cpp:1356 (SmTorus::CreateCanonical(identity placement, 5, 1); sm_CheckSTEPDerivatives at (u, 40) for u = 20, 110, 250)", + "inputs": { + "torus": { + "origin": [ + 0, + 0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 5.0, + "minor": 1.0 + }, + "op": "eval_step", + "u": 20.0, + "v": 40.0, + "k": 1 + } + }, + { + "name": "general_eval_u20_k2", + "upstream_ref": "tests/SM_API_test/src/TestSmGeneral.cpp:1356 (SmTorus::CreateCanonical(identity placement, 5, 1); sm_CheckSTEPDerivatives at (u, 40) for u = 20, 110, 250)", + "inputs": { + "torus": { + "origin": [ + 0, + 0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 5.0, + "minor": 1.0 + }, + "op": "eval_step", + "u": 20.0, + "v": 40.0, + "k": 2 + } + }, + { + "name": "general_eval_u20_k3", + "upstream_ref": "tests/SM_API_test/src/TestSmGeneral.cpp:1356 (SmTorus::CreateCanonical(identity placement, 5, 1); sm_CheckSTEPDerivatives at (u, 40) for u = 20, 110, 250)", + "inputs": { + "torus": { + "origin": [ + 0, + 0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 5.0, + "minor": 1.0 + }, + "op": "eval_step", + "u": 20.0, + "v": 40.0, + "k": 3 + } + }, + { + "name": "general_to_nurbs_u20", + "upstream_ref": "tests/SM_API_test/src/TestSmGeneral.cpp:1356 (SmTorus::CreateCanonical(identity placement, 5, 1); sm_CheckSTEPDerivatives at (u, 40) for u = 20, 110, 250)", + "inputs": { + "torus": { + "origin": [ + 0, + 0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 5.0, + "minor": 1.0 + }, + "op": "to_nurbs", + "u": 20.0, + "v": 40.0 + } + }, + { + "name": "general_eval_u110_k1", + "upstream_ref": "tests/SM_API_test/src/TestSmGeneral.cpp:1356 (SmTorus::CreateCanonical(identity placement, 5, 1); sm_CheckSTEPDerivatives at (u, 40) for u = 20, 110, 250)", + "inputs": { + "torus": { + "origin": [ + 0, + 0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 5.0, + "minor": 1.0 + }, + "op": "eval_step", + "u": 110.0, + "v": 40.0, + "k": 1 + } + }, + { + "name": "general_eval_u110_k2", + "upstream_ref": "tests/SM_API_test/src/TestSmGeneral.cpp:1356 (SmTorus::CreateCanonical(identity placement, 5, 1); sm_CheckSTEPDerivatives at (u, 40) for u = 20, 110, 250)", + "inputs": { + "torus": { + "origin": [ + 0, + 0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 5.0, + "minor": 1.0 + }, + "op": "eval_step", + "u": 110.0, + "v": 40.0, + "k": 2 + } + }, + { + "name": "general_eval_u110_k3", + "upstream_ref": "tests/SM_API_test/src/TestSmGeneral.cpp:1356 (SmTorus::CreateCanonical(identity placement, 5, 1); sm_CheckSTEPDerivatives at (u, 40) for u = 20, 110, 250)", + "inputs": { + "torus": { + "origin": [ + 0, + 0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 5.0, + "minor": 1.0 + }, + "op": "eval_step", + "u": 110.0, + "v": 40.0, + "k": 3 + } + }, + { + "name": "general_to_nurbs_u110", + "upstream_ref": "tests/SM_API_test/src/TestSmGeneral.cpp:1356 (SmTorus::CreateCanonical(identity placement, 5, 1); sm_CheckSTEPDerivatives at (u, 40) for u = 20, 110, 250)", + "inputs": { + "torus": { + "origin": [ + 0, + 0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 5.0, + "minor": 1.0 + }, + "op": "to_nurbs", + "u": 110.0, + "v": 40.0 + } + }, + { + "name": "general_eval_u250_k1", + "upstream_ref": "tests/SM_API_test/src/TestSmGeneral.cpp:1356 (SmTorus::CreateCanonical(identity placement, 5, 1); sm_CheckSTEPDerivatives at (u, 40) for u = 20, 110, 250)", + "inputs": { + "torus": { + "origin": [ + 0, + 0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 5.0, + "minor": 1.0 + }, + "op": "eval_step", + "u": 250.0, + "v": 40.0, + "k": 1 + } + }, + { + "name": "general_eval_u250_k2", + "upstream_ref": "tests/SM_API_test/src/TestSmGeneral.cpp:1356 (SmTorus::CreateCanonical(identity placement, 5, 1); sm_CheckSTEPDerivatives at (u, 40) for u = 20, 110, 250)", + "inputs": { + "torus": { + "origin": [ + 0, + 0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 5.0, + "minor": 1.0 + }, + "op": "eval_step", + "u": 250.0, + "v": 40.0, + "k": 2 + } + }, + { + "name": "general_eval_u250_k3", + "upstream_ref": "tests/SM_API_test/src/TestSmGeneral.cpp:1356 (SmTorus::CreateCanonical(identity placement, 5, 1); sm_CheckSTEPDerivatives at (u, 40) for u = 20, 110, 250)", + "inputs": { + "torus": { + "origin": [ + 0, + 0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 5.0, + "minor": 1.0 + }, + "op": "eval_step", + "u": 250.0, + "v": 40.0, + "k": 3 + } + }, + { + "name": "general_to_nurbs_u250", + "upstream_ref": "tests/SM_API_test/src/TestSmGeneral.cpp:1356 (SmTorus::CreateCanonical(identity placement, 5, 1); sm_CheckSTEPDerivatives at (u, 40) for u = 20, 110, 250)", + "inputs": { + "torus": { + "origin": [ + 0, + 0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 5.0, + "minor": 1.0 + }, + "op": "to_nurbs", + "u": 250.0, + "v": 40.0 + } + }, + { + "name": "analytic_build", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "build" + } + }, + { + "name": "analytic_eval_0_0", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "eval_step", + "u": 0.0, + "v": 0.0, + "k": 2 + } + }, + { + "name": "analytic_point_0_0", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "eval_step", + "u": 0.0, + "v": 0.0, + "k": 0, + "point": true + } + }, + { + "name": "analytic_to_nurbs_0_0", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "to_nurbs", + "u": 0.0, + "v": 0.0 + } + }, + { + "name": "analytic_eval_90_45", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "eval_step", + "u": 90.0, + "v": 45.0, + "k": 2 + } + }, + { + "name": "analytic_point_90_45", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "eval_step", + "u": 90.0, + "v": 45.0, + "k": 0, + "point": true + } + }, + { + "name": "analytic_to_nurbs_90_45", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "to_nurbs", + "u": 90.0, + "v": 45.0 + } + }, + { + "name": "analytic_eval_180_180", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "eval_step", + "u": 180.0, + "v": 180.0, + "k": 2 + } + }, + { + "name": "analytic_point_180_180", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "eval_step", + "u": 180.0, + "v": 180.0, + "k": 0, + "point": true + } + }, + { + "name": "analytic_to_nurbs_180_180", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "to_nurbs", + "u": 180.0, + "v": 180.0 + } + }, + { + "name": "analytic_eval_270_315", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "eval_step", + "u": 270.0, + "v": 315.0, + "k": 2 + } + }, + { + "name": "analytic_point_270_315", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "eval_step", + "u": 270.0, + "v": 315.0, + "k": 0, + "point": true + } + }, + { + "name": "analytic_to_nurbs_270_315", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "to_nurbs", + "u": 270.0, + "v": 315.0 + } + }, + { + "name": "analytic_eval_360_360", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "eval_step", + "u": 360.0, + "v": 360.0, + "k": 2 + } + }, + { + "name": "analytic_point_360_360", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "eval_step", + "u": 360.0, + "v": 360.0, + "k": 0, + "point": true + } + }, + { + "name": "analytic_to_nurbs_360_360", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "to_nurbs", + "u": 360.0, + "v": 360.0 + } + }, + { + "name": "analytic_eval_400_-20", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "eval_step", + "u": 400.0, + "v": -20.0, + "k": 2 + } + }, + { + "name": "analytic_point_400_-20", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "eval_step", + "u": 400.0, + "v": -20.0, + "k": 0, + "point": true + } + }, + { + "name": "analytic_to_nurbs_400_-20", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "to_nurbs", + "u": 400.0, + "v": -20.0 + } + }, + { + "name": "analytic_iso_p0_0.0", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "iso", + "param": 0, + "iso": 0.0 + } + }, + { + "name": "analytic_iso_p0_0.3", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "iso", + "param": 0, + "iso": 0.3 + } + }, + { + "name": "analytic_iso_p0_0.5", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "iso", + "param": 0, + "iso": 0.5 + } + }, + { + "name": "analytic_iso_p0_1.0", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "iso", + "param": 0, + "iso": 1.0 + } + }, + { + "name": "analytic_iso_p1_0.0", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "iso", + "param": 1, + "iso": 0.0 + } + }, + { + "name": "analytic_iso_p1_0.3", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "iso", + "param": 1, + "iso": 0.3 + } + }, + { + "name": "analytic_iso_p1_0.5", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "iso", + "param": 1, + "iso": 0.5 + } + }, + { + "name": "analytic_iso_p1_1.0", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "iso", + "param": 1, + "iso": 1.0 + } + }, + { + "name": "analytic_offset_out", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "offset", + "dist": 0.1, + "tol": 1e-06 + } + }, + { + "name": "analytic_offset_in_rebuild", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "offset", + "dist": -0.1, + "tol": 1e-06, + "rebuild": true + } + }, + { + "name": "analytic_offset_collapse", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "offset", + "dist": -0.3, + "tol": 1e-06 + } + }, + { + "name": "analytic_rebuild", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "rebuild" + } + }, + { + "name": "analytic_copy", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "copy" + } + }, + { + "name": "analytic_eq_self", + "upstream_ref": "tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360])", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "eq", + "other": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + } + } + }, + { + "name": "topo_build", + "upstream_ref": "tests/prog_test/src/smTopoTest.cpp:4398 (my_test_doubly_periodic_vertexuse_corners: SmTorus::CreateCanonical(SmAxis2Placement(), 3, 1))", + "inputs": { + "torus": { + "origin": [ + 0, + 0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 3.0, + "minor": 1.0 + }, + "op": "build" + } + }, + { + "name": "topo_to_step_corner", + "upstream_ref": "tests/prog_test/src/smTopoTest.cpp:4398 (my_test_doubly_periodic_vertexuse_corners: SmTorus::CreateCanonical(SmAxis2Placement(), 3, 1))", + "inputs": { + "torus": { + "origin": [ + 0, + 0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 3.0, + "minor": 1.0 + }, + "op": "to_step", + "u": 0.0, + "v": 0.0 + } + }, + { + "name": "topo_rebuild", + "upstream_ref": "tests/prog_test/src/smTopoTest.cpp:4398 (my_test_doubly_periodic_vertexuse_corners: SmTorus::CreateCanonical(SmAxis2Placement(), 3, 1))", + "inputs": { + "torus": { + "origin": [ + 0, + 0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 3.0, + "minor": 1.0 + }, + "op": "rebuild" + } + }, + { + "name": "api_radii_0.0_1.0", + "upstream_ref": "source/SM_API/src/SmApiPrimitives.cpp:633 (SmApiCreateTorus: placement (origin, X, Y), degenerate radii rejected by CreateCanonical)", + "inputs": { + "torus": { + "origin": [ + 1.0, + -1.0, + 2.0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 0.0, + "minor": 1.0 + }, + "op": "build" + } + }, + { + "name": "api_radii_2.0_0.0", + "upstream_ref": "source/SM_API/src/SmApiPrimitives.cpp:633 (SmApiCreateTorus: placement (origin, X, Y), degenerate radii rejected by CreateCanonical)", + "inputs": { + "torus": { + "origin": [ + 1.0, + -1.0, + 2.0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 2.0, + "minor": 0.0 + }, + "op": "build" + } + }, + { + "name": "api_radii_-1.0_0.5", + "upstream_ref": "source/SM_API/src/SmApiPrimitives.cpp:633 (SmApiCreateTorus: placement (origin, X, Y), degenerate radii rejected by CreateCanonical)", + "inputs": { + "torus": { + "origin": [ + 1.0, + -1.0, + 2.0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": -1.0, + "minor": 0.5 + }, + "op": "build" + } + }, + { + "name": "api_radii_1.0_1.0", + "upstream_ref": "source/SM_API/src/SmApiPrimitives.cpp:633 (SmApiCreateTorus: placement (origin, X, Y), degenerate radii rejected by CreateCanonical)", + "inputs": { + "torus": { + "origin": [ + 1.0, + -1.0, + 2.0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 1.0 + }, + "op": "build" + } + }, + { + "name": "api_radii_1.0_1.5", + "upstream_ref": "source/SM_API/src/SmApiPrimitives.cpp:633 (SmApiCreateTorus: placement (origin, X, Y), degenerate radii rejected by CreateCanonical)", + "inputs": { + "torus": { + "origin": [ + 1.0, + -1.0, + 2.0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 1.5 + }, + "op": "build" + } + }, + { + "name": "edge_swap_inside_build", + "upstream_ref": "torus.rs edge cases: swapped and inside-out NURBs, partial domains, explicit generator", + "inputs": { + "torus": { + "origin": [ + 0, + 0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 4.0, + "minor": 1.5, + "swap": true, + "inside_out": true, + "dom": [ + 30.0, + -90.0, + 200.0, + 120.0 + ] + }, + "op": "build" + } + }, + { + "name": "edge_swap_inside_rebuild", + "upstream_ref": "torus.rs edge cases: swapped and inside-out NURBs, partial domains, explicit generator", + "inputs": { + "torus": { + "origin": [ + 0, + 0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 4.0, + "minor": 1.5, + "swap": true, + "inside_out": true, + "dom": [ + 30.0, + -90.0, + 200.0, + 120.0 + ] + }, + "op": "rebuild" + } + }, + { + "name": "edge_swap_inside_offset", + "upstream_ref": "torus.rs edge cases: swapped and inside-out NURBs, partial domains, explicit generator", + "inputs": { + "torus": { + "origin": [ + 0, + 0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 4.0, + "minor": 1.5, + "swap": true, + "inside_out": true, + "dom": [ + 30.0, + -90.0, + 200.0, + 120.0 + ] + }, + "op": "offset", + "dist": 0.5, + "tol": 1e-06, + "rebuild": true + } + }, + { + "name": "edge_swap_inside_iso_dom", + "upstream_ref": "torus.rs edge cases: swapped and inside-out NURBs, partial domains, explicit generator", + "inputs": { + "torus": { + "origin": [ + 0, + 0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 4.0, + "minor": 1.5, + "swap": true, + "inside_out": true, + "dom": [ + 30.0, + -90.0, + 200.0, + 120.0 + ] + }, + "op": "iso", + "param": 1, + "iso": 0.4, + "dom": [ + 0.1, + 0.1, + 0.9, + 0.9 + ] + } + }, + { + "name": "edge_given_circle_build", + "upstream_ref": "torus.rs edge cases: swapped and inside-out NURBs, partial domains, explicit generator", + "inputs": { + "torus": { + "origin": [ + 0, + 0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 2.0, + "minor": 0.5, + "circle": { + "c": [ + 2.0, + 0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 0, + 1.0 + ], + "d": [ + 0.0, + 360.0 + ], + "r": 0.5 + } + }, + "op": "build" + } + }, + { + "name": "edge_given_circle_offset", + "upstream_ref": "torus.rs edge cases: swapped and inside-out NURBs, partial domains, explicit generator", + "inputs": { + "torus": { + "origin": [ + 0, + 0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 2.0, + "minor": 0.5, + "circle": { + "c": [ + 2.0, + 0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 0, + 1.0 + ], + "d": [ + 0.0, + 360.0 + ], + "r": 0.5 + } + }, + "op": "offset", + "dist": 0.25, + "tol": 1e-06 + } + }, + { + "name": "edge_transform_identity", + "upstream_ref": "torus.rs edge cases: swapped and inside-out NURBs, partial domains, explicit generator", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "transform", + "origin": [ + 0, + 0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ] + } + }, + { + "name": "edge_transform_nonuniform", + "upstream_ref": "torus.rs edge cases: swapped and inside-out NURBs, partial domains, explicit generator", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "transform", + "origin": [ + 0, + 0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "scale": [ + 1.0, + 2.0, + 1.0 + ] + } + }, + { + "name": "edge_transform_uniform", + "upstream_ref": "torus.rs edge cases: swapped and inside-out NURBs, partial domains, explicit generator", + "inputs": { + "torus": { + "origin": [ + 2.0, + 2.0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "major": 1.0, + "minor": 0.25 + }, + "op": "transform", + "origin": [ + 1, + 0, + 0 + ], + "x": [ + 1.0, + 0, + 0 + ], + "y": [ + 0, + 1.0, + 0 + ], + "scale": [ + 2.0, + 2.0, + 2.0 + ] + } + } + ] +} \ No newline at end of file diff --git a/harness/cpp/shim/surface_torus.cpp b/harness/cpp/shim/surface_torus.cpp new file mode 100644 index 0000000000..92d8402c58 --- /dev/null +++ b/harness/cpp/shim/surface_torus.cpp @@ -0,0 +1,417 @@ +// SPDX-License-Identifier: Apache-2.0 +// difftest shim for unit `surface.torus`. +// Wraps the SmTorus members owned by the unit (source/SMLib/src/SmTorus.cpp): CreateCanonical +// (the constructor and MakeNurb), operator==, Copy, CreateOffsetSurface, +// ConvertUVFromSTEPToNURBS, ConvertUVFromNURBSToSTEP, EvaluateSTEP, EvaluateSTEPPoint, +// CreateIsoParametricCurve, RebuildSTEPFromNURBParameters, Transform, GetMaxAnalyticDomain, +// GetInsideOut and IsKindOf. +// +// One entry point, sc_surface_torus_eval(op, in, n_in, out, cap, out_len). Layouts are +// documented in crates/solidcore-ffi/src/units/surface_torus.rs, which validates every input +// so upstream only touches valid memory. `out` starts with the status of CreateCanonical. +#include "sc_shim.h" + +#include "StdAfx.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace +{ +// Ops (keep in sync with crates/solidcore-ffi/src/units/surface_torus.rs). +enum : int32_t +{ + OP_BUILD = 0, + OP_EVAL_STEP = 1, + OP_TO_STEP = 2, + OP_TO_NURBS = 3, + OP_ISO = 4, + OP_OFFSET = 5, + OP_REBUILD = 6, + OP_TRANSFORM = 7, + OP_EQ = 8, + OP_COPY = 9, +}; + +// Reads the protected members through pointers to members named in a derived class. +struct Peek : SmTorus +{ + static SmBoolean SmSurfOfRevolution::*planar() { return &Peek::m_bPlanarGenerator; } + static SmPolarConversion SmSurfOfRevolution::*polar() { return &Peek::m_vPolarConverter; } +}; + +struct Rd +{ + const double* in; + int64_t at; + double num() { return in[at++]; } + int64_t idx() { return (int64_t)in[at++]; } + bool flag() { return in[at++] != 0.0; } + SmVector3d vec() + { + double x = num(); + double y = num(); + double z = num(); + return SmVector3d(x, y, z); + } + SmExtent2d domain() + { + double a = num(); + double b = num(); + double c = num(); + double d = num(); + return SmExtent2d(a, b, c, d); + } +}; + +double st(SmStatus s) +{ + return s == SM_SUCCESS ? 0.0 : (double)s; +} + +double flg(bool b) +{ + return b ? 1.0 : 0.0; +} + +void put_vec(std::vector& o, const SmVector3d& v) +{ + o.push_back(v.x); + o.push_back(v.y); + o.push_back(v.z); +} + +// A torus block `O[3] X[3] Y[3] major minor swap inside_out has_dom D[4] has_circle +// [c[3] x[3] y[3] d0 d1 r]`: SmTorus::CreateCanonical(SmAxis2Placement(O, X, Y), ..). +// Writes the status; NULL on failure. +SmTorus* read_torus(Rd& rd, const SmContext& ctx, std::vector& o) +{ + SmPoint3d org = rd.vec(); + SmVector3d x = rd.vec(); + SmVector3d y = rd.vec(); + double major = rd.num(); + double minor = rd.num(); + bool swap = rd.flag(); + bool inside = rd.flag(); + bool has_dom = rd.flag(); + SmExtent2d dom = rd.domain(); + bool has_circle = rd.flag(); + SmCircle* circle = NULL; + if (has_circle) + { + SmPoint3d c = rd.vec(); + SmVector3d cx = rd.vec(); + SmVector3d cy = rd.vec(); + double d0 = rd.num(); + double d1 = rd.num(); + double r = rd.num(); + circle = new (ctx) SmCircle(c, cx, cy, SmExtent1d(d0, d1), r, 3, &ctx); + } + else + { + for (int i = 0; i < 12; i++) + rd.num(); + } + SmTorus* t = NULL; + SmStatus stt = SmTorus::CreateCanonical(ctx, SmAxis2Placement(org, x, y), major, minor, t, + swap ? TRUE : FALSE, inside ? TRUE : FALSE, circle, + has_dom ? &dom : NULL); + o.push_back(st(stt)); + if (stt != SM_SUCCESS || t == NULL) + { + // the torus owns the circle once constructed (and deletes it on failure); before + // that (a degenerate radius) nobody does + if (t == NULL && circle != NULL && !(minor > SM_EFF_ZERO && major > SM_EFF_ZERO)) + delete circle; + delete t; + return NULL; + } + return t; +} + +// `has [type step0 step1 has_nurb [p n cpts(4 n) nk knots]] [ellipse O X Y rx ry inside]`. +void put_curve(std::vector& o, const SmCurve* c) +{ + o.push_back(flg(c != NULL)); + if (!c) + return; + o.push_back((double)c->GetType()); + SmExtent1d ivl = c->GetSTEPInterval(); + o.push_back(ivl.GetMin()); + o.push_back(ivl.GetMax()); + const SmBSplineCurve* b = + c->IsKindOf(SmBSplineCurve_TYPE) ? (const SmBSplineCurve*)c : NULL; + const gw_CURVE* cur = b ? b->GetGwNurbPointer() : NULL; + o.push_back(flg(cur != NULL)); + if (cur) + { + o.push_back((double)cur->p); + o.push_back((double)(cur->pol->n + 1)); + for (gw_INDEX i = 0; i <= cur->pol->n; i++) + { + o.push_back(cur->pol->Pw[i].x); + o.push_back(cur->pol->Pw[i].y); + o.push_back(cur->pol->Pw[i].z); + o.push_back(cur->pol->Pw[i].w); + } + o.push_back((double)(cur->knt->m + 1)); + for (gw_INDEX i = 0; i <= cur->knt->m; i++) + o.push_back(cur->knt->U[i]); + } + const SmEllipse* e = c->IsKindOf(SmEllipse_TYPE) ? (const SmEllipse*)c : NULL; + o.push_back(flg(e != NULL)); + if (e) + { + const SmAxis2Placement& p = e->GetPosition(); + put_vec(o, p.GetOriginRef()); + put_vec(o, p.GetXAxisRef()); + put_vec(o, p.GetYAxisRef()); + o.push_back(e->GetXRadius()); + o.push_back(e->GetYRadius()); + o.push_back(flg(e->GetInsideOut())); + } +} + +// The torus state. +void put_state(std::vector& o, const SmTorus* s) +{ + o.push_back(flg(s->IsKindOf(SmTorus_TYPE))); + o.push_back(flg(s->IsKindOf(SmSurfOfRevolution_TYPE))); + o.push_back(flg(s->IsKindOf(SmBSplineSurface_TYPE))); + o.push_back(flg(s->IsKindOf(SmSurface_TYPE))); + o.push_back((double)s->GetType()); + o.push_back(flg(s->GetSwapUV())); + o.push_back(flg(s->*Peek::planar())); + o.push_back(flg(s->GetInsideOut())); + o.push_back(s->GetMajorRadius()); + o.push_back(s->GetMinorRadius()); + const SmAxis2Placement& pos = s->GetPosition(); + put_vec(o, pos.GetOriginRef()); + put_vec(o, pos.GetXAxisRef()); + put_vec(o, pos.GetYAxisRef()); + SmExtent2d d = s->GetSTEPUVDomain(); + o.push_back(d.GetMin().x); + o.push_back(d.GetMin().y); + o.push_back(d.GetMax().x); + o.push_back(d.GetMax().y); + SmExtent2d mx = s->GetMaxAnalyticDomain(); + o.push_back(mx.GetMin().x); + o.push_back(mx.GetMin().y); + o.push_back(mx.GetMax().x); + o.push_back(mx.GetMax().y); + const SmPolarConversion& pc = s->*Peek::polar(); + o.push_back(flg(s->HavePolarConversion())); + ULONG cnt = pc.GetAngleCount(); + o.push_back((double)cnt); + if (cnt > 0) + { + o.push_back(pc.GetFirstAngle()); + o.push_back(pc.GetLastAngle()); + } + put_curve(o, pc.GetCurve()); + put_curve(o, s->GetGenCurve()); + const gw_SURFACE* sur = s->GetGwNurbPointer(); + o.push_back(flg(sur != NULL)); + if (!sur) + return; + 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]); +} + +void torus_op(int32_t op, Rd& rd, const SmContext& ctx, SmTorus* s, std::vector& o) +{ + switch (op) + { + case OP_BUILD: put_state(o, s); break; + case OP_EVAL_STEP: + { + double u = rd.num(); + double v = rd.num(); + ULONG k = (ULONG)rd.idx(); + bool point = rd.flag(); + if (point) + { + SmPoint3d p(0.0, 0.0, 0.0); + SmStatus stt = s->EvaluateSTEPPoint(SmPoint2d(u, v), p); + o.push_back(st(stt)); + if (stt == SM_SUCCESS) + put_vec(o, p); + break; + } + std::vector d((k + 1) * (k + 1), SmVector3d(0.0, 0.0, 0.0)); + SmStatus stt = s->EvaluateSTEP(SmPoint2d(u, v), k, k, TRUE, TRUE, TRUE, d.data()); + o.push_back(st(stt)); + if (stt == SM_SUCCESS) + for (size_t i = 0; i < d.size(); i++) + put_vec(o, d[i]); + break; + } + case OP_TO_STEP: + case OP_TO_NURBS: + { + double u = rd.num(); + double v = rd.num(); + SmPoint2d r(0.0, 0.0); + SmStatus stt = op == OP_TO_STEP ? s->ConvertUVFromNURBSToSTEP(SmPoint2d(u, v), r) + : s->ConvertUVFromSTEPToNURBS(SmPoint2d(u, v), r); + o.push_back(st(stt)); + if (stt == SM_SUCCESS) + { + o.push_back(r.x); + o.push_back(r.y); + } + break; + } + case OP_ISO: + { + SmSurfParamType param = rd.idx() == 0 ? SM_SP_U : SM_SP_V; + double iso = rd.num(); + bool has_dom = rd.flag(); + SmExtent2d dom = rd.domain(); + // the override ignores the status of the base extraction and then reads its + // curve: reject the arguments it fails on + SmBSplineCurve* probe = NULL; + SmStatus pst = s->SmBSplineSurface::CreateIsoParametricCurve( + ctx, param, iso, 0.0, probe, has_dom ? &dom : NULL, NULL, NULL); + bool ok = pst == SM_SUCCESS && probe != NULL; + delete probe; + if (!ok) + { + o.push_back((double)SC_ERR_ARG); + break; + } + SmBSplineCurve* c = NULL; + double gap = -1.0; + SmStatus stt = s->CreateIsoParametricCurve(ctx, param, iso, 0.0, c, + has_dom ? &dom : NULL, &gap, NULL); + o.push_back(st(stt)); + if (stt == SM_SUCCESS) + { + o.push_back(gap); + put_curve(o, c); + } + delete c; + break; + } + case OP_OFFSET: + { + double dist = rd.num(); + double tol = rd.num(); + bool rebuild = rd.flag(); + SmSurface* off = NULL; + SmStatus stt = s->CreateOffsetSurface(ctx, dist, tol, off); + o.push_back(st(stt)); + if (stt == SM_SUCCESS) + { + o.push_back(flg(off != NULL)); + if (off) + { + SmTorus* t = (SmTorus*)off; + if (rebuild) + o.push_back(st(t->RebuildSTEPFromNURBParameters())); + put_state(o, t); + } + } + delete off; + break; + } + case OP_REBUILD: + { + SmStatus stt = s->RebuildSTEPFromNURBParameters(); + o.push_back(st(stt)); + put_state(o, s); + break; + } + case OP_TRANSFORM: + { + SmPoint3d org = rd.vec(); + SmVector3d x = rd.vec(); + SmVector3d y = rd.vec(); + bool has_scale = rd.flag(); + SmVector3d sc = rd.vec(); + SmAxis2Placement a2p(org, x, y); + SmStatus stt = s->Transform(a2p, has_scale ? &sc : NULL); + o.push_back(st(stt)); + put_state(o, s); + break; + } + case OP_EQ: + { + std::vector o2; + SmTorus* other = read_torus(rd, ctx, o2); + o.push_back(o2.empty() ? -1.0 : o2[0]); + if (other) + { + o.push_back(flg(*s == *other)); + o.push_back(flg(*other == *s)); + delete other; + } + break; + } + case OP_COPY: + { + SmSurface* c = NULL; + SmStatus stt = s->Copy(ctx, c); + o.push_back(st(stt)); + if (stt == SM_SUCCESS && c) + { + put_state(o, (SmTorus*)c); + o.push_back(flg(*s == *c)); + } + delete c; + break; + } + default: o.push_back((double)SC_ERR_ARG); break; + } +} +} // namespace + +SC_EXPORT int32_t sc_surface_torus_eval(int32_t op, const double* in, int64_t n_in, double* out, + int64_t cap, int64_t* out_len) +{ + (void)n_in; + SC_TRY + SmContext ctx; + std::vector o; + Rd rd{in, 0}; + SmTorus* s = read_torus(rd, ctx, o); + if (s) + { + torus_op(op, rd, ctx, s, o); + delete s; + } + return sc_copy_out(o.data(), (int64_t)o.size(), out, cap, out_len); + SC_CATCH +} From 106839961f94872074e1ec69d09ddceffab16940 Mon Sep 17 00:00:00 2001 From: Andreas <64703083+froglemonade2@users.noreply.github.com> Date: Wed, 14 Oct 2026 07:46:24 +0200 Subject: [PATCH 3/5] surface.torus: mark partial (545 pass, 18 deferred on surf_of_revolution_p3) Co-Authored-By: Claude Opus 5.5 --- plan/modules.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plan/modules.toml b/plan/modules.toml index de010ab4a3..12a60c6551 100644 --- a/plan/modules.toml +++ b/plan/modules.toml @@ -1705,10 +1705,10 @@ soft_depends_on = ["curve.curve_p2", "geom.geom_utility_p1", "nurbs.b_spline_cur est_lines = 1180 layer = 24 tests = ["prog_test:my_test_extent3d", "prog_test:my_test_vector2d", "prog_test:my_test_surface_closest_point", "prog_test:my_test_drop_point", "prog_test:my_test_matrix", "prog_test:my_test_reversed_line_join"] -status = "todo" +status = "partial" claimed_by = "" claimed_at = "" -notes = "21 fns; largest: SmTorus::RebuildSTEPFromNURBParameters, SmTorus::CreateOffsetSurface, SmTorus::CreateIsoParametricCurve, SmTorus::EvaluateSTEP; cycle back-edges (call into later units; stub or co-port): surface.surf_of_revolution_p3(2), surface.surf_of_revolution_p2(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit" +notes = "PARTIAL (PR #23): difftest 563 cases (63 fixture: TestSmGeneral torus STEP derivatives, smSurfaceTest analytic torus, smTopoTest torus, SmApiCreateTorus radii, swap/inside-out/partial-domain/explicit-generator edges; + 500 generated) over CreateCanonical, EvaluateSTEP(Point), ConvertUVFromSTEPToNURBS/NURBSToSTEP, CreateIsoParametricCurve, CreateOffsetSurface (+rebuild), RebuildSTEPFromNURBParameters, Transform, operator==, Copy; 545 pass bit-identical, 18 deferred (surface.surf_of_revolution_p3: SmSurfOfRevolution::Transform). ReadFromDB is ported up to SmSurfOfRevolution::ReadFromDB (unported; unit test only). SurfOfRevolution::make_nurb_with_sweep_params_using / rebuild_polar_converter take the iso-curve maker so the torus override is used (upstream virtual CreateIsoParametricCurve). 21 fns; cycle back-edges: surface.surf_of_revolution_p3(2), surface.surf_of_revolution_p2(1)" [[unit]] id = "surface.unbend_volume" From 0671115e392f91953b6ac496c2fd763199fb54c6 Mon Sep 17 00:00:00 2001 From: Andreas <64703083+froglemonade2@users.noreply.github.com> Date: Wed, 14 Oct 2026 17:11:46 +0200 Subject: [PATCH 4/5] surface.torus: address review (report, deviation note, read_from_db cleanup) Co-Authored-By: Claude Sonnet 5.5 --- crates/solidcore/src/surface/surface/p2.rs | 9 +- crates/solidcore/src/surface/torus.rs | 5 +- reports/difftest/surface.torus.json | 4595 ++++++++++++++++++++ 3 files changed, 4602 insertions(+), 7 deletions(-) create mode 100644 reports/difftest/surface.torus.json diff --git a/crates/solidcore/src/surface/surface/p2.rs b/crates/solidcore/src/surface/surface/p2.rs index 6460afbea1..a096a72047 100644 --- a/crates/solidcore/src/surface/surface/p2.rs +++ b/crates/solidcore/src/surface/surface/p2.rs @@ -915,13 +915,12 @@ impl Surface { )), // (SmTorus::ReadFromDB reads the torus record, then needs the unported // SmSurfOfRevolution record; the torus is not a SurfaceKind yet) - TORUS_TYPE => Torus::read_from_db(db, version).and_then(|_| { - // UNPORTED(surface.surf_of_revolution_p3): SmSurfOfRevolution::ReadFromDB - Err(SolidError::unported( + TORUS_TYPE => Err(Torus::read_from_db(db, version).err().unwrap_or_else(|| { + SolidError::unported( "SmSurfOfRevolution::ReadFromDB", "surface.surf_of_revolution_p3", - )) - }), + ) + })), CURVE_BOUNDED_SURFACE_TYPE => Ok(Self::from(CurveBoundedSurface::read_from_db( k, db, version, )?)), diff --git a/crates/solidcore/src/surface/torus.rs b/crates/solidcore/src/surface/torus.rs index c544aac3ed..5e5af60a7c 100644 --- a/crates/solidcore/src/surface/torus.rs +++ b/crates/solidcore/src/surface/torus.rs @@ -41,8 +41,9 @@ //! [`Torus::read_from_db`] set it. //! - **Arguments.** Out-params become return values; `SmContext` arguments and the //! arguments upstream marks `NotUsed` (`EvaluateSTEP`'s side flags, -//! `CreateIsoParametricCurve`'s approximation tolerance and UV iso line, -//! `ReadFromDB`'s type) are dropped. `EvaluateSTEP` entries it does not write are +//! `CreateIsoParametricCurve`'s approximation tolerance, `ReadFromDB`'s type) are +//! dropped. `CreateIsoParametricCurve`'s optional UV iso line is forwarded to the +//! B-spline surface but always requested as `false` (no caller can ask for it yet). `EvaluateSTEP` entries it does not write are //! `0`. `CreateOffsetSurface` returns `None` where upstream succeeds without a surface //! (a new minor radius below the approximation tolerance). //! - **Statuses.** Calls whose status upstream ignores keep upstream's values, except diff --git a/reports/difftest/surface.torus.json b/reports/difftest/surface.torus.json new file mode 100644 index 0000000000..c83680b055 --- /dev/null +++ b/reports/difftest/surface.torus.json @@ -0,0 +1,4595 @@ +{ + "abs_floors": [], + "cases": [ + { + "max_rel_err": 0.0, + "name": "general_build", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#0 (upstream tests/SM_API_test/src/TestSmGeneral.cpp:1356 (SmTorus::CreateCanonical(identity placement, 5, 1); sm_CheckSTEPDerivatives at (u, 40) for u = 20, 110, 250))", + "values_compared": 525 + }, + { + "max_rel_err": 0.0, + "name": "general_eval_u20_k1", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#1 (upstream tests/SM_API_test/src/TestSmGeneral.cpp:1356 (SmTorus::CreateCanonical(identity placement, 5, 1); sm_CheckSTEPDerivatives at (u, 40) for u = 20, 110, 250))", + "values_compared": 14 + }, + { + "max_rel_err": 0.0, + "name": "general_eval_u20_k2", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#2 (upstream tests/SM_API_test/src/TestSmGeneral.cpp:1356 (SmTorus::CreateCanonical(identity placement, 5, 1); sm_CheckSTEPDerivatives at (u, 40) for u = 20, 110, 250))", + "values_compared": 29 + }, + { + "max_rel_err": 0.0, + "name": "general_eval_u20_k3", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#3 (upstream tests/SM_API_test/src/TestSmGeneral.cpp:1356 (SmTorus::CreateCanonical(identity placement, 5, 1); sm_CheckSTEPDerivatives at (u, 40) for u = 20, 110, 250))", + "values_compared": 50 + }, + { + "max_rel_err": 0.0, + "name": "general_to_nurbs_u20", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#4 (upstream tests/SM_API_test/src/TestSmGeneral.cpp:1356 (SmTorus::CreateCanonical(identity placement, 5, 1); sm_CheckSTEPDerivatives at (u, 40) for u = 20, 110, 250))", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "general_eval_u110_k1", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#5 (upstream tests/SM_API_test/src/TestSmGeneral.cpp:1356 (SmTorus::CreateCanonical(identity placement, 5, 1); sm_CheckSTEPDerivatives at (u, 40) for u = 20, 110, 250))", + "values_compared": 14 + }, + { + "max_rel_err": 0.0, + "name": "general_eval_u110_k2", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#6 (upstream tests/SM_API_test/src/TestSmGeneral.cpp:1356 (SmTorus::CreateCanonical(identity placement, 5, 1); sm_CheckSTEPDerivatives at (u, 40) for u = 20, 110, 250))", + "values_compared": 29 + }, + { + "max_rel_err": 0.0, + "name": "general_eval_u110_k3", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#7 (upstream tests/SM_API_test/src/TestSmGeneral.cpp:1356 (SmTorus::CreateCanonical(identity placement, 5, 1); sm_CheckSTEPDerivatives at (u, 40) for u = 20, 110, 250))", + "values_compared": 50 + }, + { + "max_rel_err": 0.0, + "name": "general_to_nurbs_u110", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#8 (upstream tests/SM_API_test/src/TestSmGeneral.cpp:1356 (SmTorus::CreateCanonical(identity placement, 5, 1); sm_CheckSTEPDerivatives at (u, 40) for u = 20, 110, 250))", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "general_eval_u250_k1", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#9 (upstream tests/SM_API_test/src/TestSmGeneral.cpp:1356 (SmTorus::CreateCanonical(identity placement, 5, 1); sm_CheckSTEPDerivatives at (u, 40) for u = 20, 110, 250))", + "values_compared": 14 + }, + { + "max_rel_err": 0.0, + "name": "general_eval_u250_k2", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#10 (upstream tests/SM_API_test/src/TestSmGeneral.cpp:1356 (SmTorus::CreateCanonical(identity placement, 5, 1); sm_CheckSTEPDerivatives at (u, 40) for u = 20, 110, 250))", + "values_compared": 29 + }, + { + "max_rel_err": 0.0, + "name": "general_eval_u250_k3", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#11 (upstream tests/SM_API_test/src/TestSmGeneral.cpp:1356 (SmTorus::CreateCanonical(identity placement, 5, 1); sm_CheckSTEPDerivatives at (u, 40) for u = 20, 110, 250))", + "values_compared": 50 + }, + { + "max_rel_err": 0.0, + "name": "general_to_nurbs_u250", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#12 (upstream tests/SM_API_test/src/TestSmGeneral.cpp:1356 (SmTorus::CreateCanonical(identity placement, 5, 1); sm_CheckSTEPDerivatives at (u, 40) for u = 20, 110, 250))", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "analytic_build", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#13 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 525 + }, + { + "max_rel_err": 0.0, + "name": "analytic_eval_0_0", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#14 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 29 + }, + { + "max_rel_err": 0.0, + "name": "analytic_point_0_0", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#15 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "analytic_to_nurbs_0_0", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#16 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "analytic_eval_90_45", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#17 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 29 + }, + { + "max_rel_err": 0.0, + "name": "analytic_point_90_45", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#18 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "analytic_to_nurbs_90_45", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#19 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "analytic_eval_180_180", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#20 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 29 + }, + { + "max_rel_err": 0.0, + "name": "analytic_point_180_180", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#21 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "analytic_to_nurbs_180_180", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#22 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "analytic_eval_270_315", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#23 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 29 + }, + { + "max_rel_err": 0.0, + "name": "analytic_point_270_315", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#24 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "analytic_to_nurbs_270_315", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#25 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "analytic_eval_360_360", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#26 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 29 + }, + { + "max_rel_err": 0.0, + "name": "analytic_point_360_360", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#27 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "analytic_to_nurbs_360_360", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#28 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "analytic_eval_400_-20", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#29 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 29 + }, + { + "max_rel_err": 0.0, + "name": "analytic_point_400_-20", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#30 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "analytic_to_nurbs_400_-20", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#31 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "analytic_iso_p0_0.0", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#32 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "analytic_iso_p0_0.3", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#33 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "analytic_iso_p0_0.5", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#34 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "analytic_iso_p0_1.0", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#35 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "analytic_iso_p1_0.0", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#36 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "analytic_iso_p1_0.3", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#37 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "analytic_iso_p1_0.5", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#38 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "analytic_iso_p1_1.0", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#39 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "analytic_offset_out", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#40 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 527 + }, + { + "max_rel_err": 0.0, + "name": "analytic_offset_in_rebuild", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#41 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 528 + }, + { + "max_rel_err": 0.0, + "name": "analytic_offset_collapse", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#42 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 3 + }, + { + "max_rel_err": 0.0, + "name": "analytic_rebuild", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#43 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "analytic_copy", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#44 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 527 + }, + { + "max_rel_err": 0.0, + "name": "analytic_eq_self", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#45 (upstream tests/prog_test/src/smSurfaceTest.cpp:5513 (torus at (2,2,0), radii 1 and 0.25, my_test_analytic_surface over [0,360]x[0,360]))", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "topo_build", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#46 (upstream tests/prog_test/src/smTopoTest.cpp:4398 (my_test_doubly_periodic_vertexuse_corners: SmTorus::CreateCanonical(SmAxis2Placement(), 3, 1)))", + "values_compared": 525 + }, + { + "max_rel_err": 0.0, + "name": "topo_to_step_corner", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#47 (upstream tests/prog_test/src/smTopoTest.cpp:4398 (my_test_doubly_periodic_vertexuse_corners: SmTorus::CreateCanonical(SmAxis2Placement(), 3, 1)))", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "topo_rebuild", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#48 (upstream tests/prog_test/src/smTopoTest.cpp:4398 (my_test_doubly_periodic_vertexuse_corners: SmTorus::CreateCanonical(SmAxis2Placement(), 3, 1)))", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "api_radii_0.0_1.0", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#49 (upstream source/SM_API/src/SmApiPrimitives.cpp:633 (SmApiCreateTorus: placement (origin, X, Y), degenerate radii rejected by CreateCanonical))", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "api_radii_2.0_0.0", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#50 (upstream source/SM_API/src/SmApiPrimitives.cpp:633 (SmApiCreateTorus: placement (origin, X, Y), degenerate radii rejected by CreateCanonical))", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "api_radii_-1.0_0.5", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#51 (upstream source/SM_API/src/SmApiPrimitives.cpp:633 (SmApiCreateTorus: placement (origin, X, Y), degenerate radii rejected by CreateCanonical))", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "api_radii_1.0_1.0", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#52 (upstream source/SM_API/src/SmApiPrimitives.cpp:633 (SmApiCreateTorus: placement (origin, X, Y), degenerate radii rejected by CreateCanonical))", + "values_compared": 525 + }, + { + "max_rel_err": 0.0, + "name": "api_radii_1.0_1.5", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#53 (upstream source/SM_API/src/SmApiPrimitives.cpp:633 (SmApiCreateTorus: placement (origin, X, Y), degenerate radii rejected by CreateCanonical))", + "values_compared": 525 + }, + { + "max_rel_err": 0.0, + "name": "edge_swap_inside_build", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#54 (upstream torus.rs edge cases: swapped and inside-out NURBs, partial domains, explicit generator)", + "values_compared": 305 + }, + { + "max_rel_err": 0.0, + "name": "edge_swap_inside_rebuild", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#55 (upstream torus.rs edge cases: swapped and inside-out NURBs, partial domains, explicit generator)", + "values_compared": 306 + }, + { + "max_rel_err": 0.0, + "name": "edge_swap_inside_offset", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#56 (upstream torus.rs edge cases: swapped and inside-out NURBs, partial domains, explicit generator)", + "values_compared": 308 + }, + { + "max_rel_err": 0.0, + "name": "edge_swap_inside_iso_dom", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#57 (upstream torus.rs edge cases: swapped and inside-out NURBs, partial domains, explicit generator)", + "values_compared": 62 + }, + { + "max_rel_err": 0.0, + "name": "edge_given_circle_build", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#58 (upstream torus.rs edge cases: swapped and inside-out NURBs, partial domains, explicit generator)", + "values_compared": 525 + }, + { + "max_rel_err": 0.0, + "name": "edge_given_circle_offset", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#59 (upstream torus.rs edge cases: swapped and inside-out NURBs, partial domains, explicit generator)", + "values_compared": 527 + }, + { + "max_rel_err": 0.0, + "name": "edge_transform_identity", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#60 (upstream torus.rs edge cases: swapped and inside-out NURBs, partial domains, explicit generator)", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "edge_transform_nonuniform", + "outcome": "pass", + "pass": true, + "source": "fixture:fixtures/surface.torus/cases.json#61 (upstream torus.rs edge cases: swapped and inside-out NURBs, partial domains, explicit generator)", + "values_compared": 526 + }, + { + "deferred": { + "symbol": "SmSurfOfRevolution::Transform", + "unit": "surface.surf_of_revolution_p3" + }, + "max_rel_err": 0.0, + "name": "edge_transform_uniform", + "outcome": "deferred", + "pass": false, + "source": "fixture:fixtures/surface.torus/cases.json#62 (upstream torus.rs edge cases: swapped and inside-out NURBs, partial domains, explicit generator)", + "values_compared": 0 + }, + { + "max_rel_err": 0.0, + "name": "gen0000", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366779", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0001", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366780", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0002", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366781", + "values_compared": 528 + }, + { + "max_rel_err": 0.0, + "name": "gen0003", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366782", + "values_compared": 202 + }, + { + "max_rel_err": 0.0, + "name": "gen0004", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366783", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0005", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366784", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0006", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366785", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0007", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366786", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "gen0008", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366787", + "values_compared": 50 + }, + { + "max_rel_err": 0.0, + "name": "gen0009", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366788", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "gen0010", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366789", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0011", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366790", + "values_compared": 527 + }, + { + "max_rel_err": 0.0, + "name": "gen0012", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366791", + "values_compared": 525 + }, + { + "max_rel_err": 0.0, + "name": "gen0013", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366792", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0014", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366793", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0015", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366794", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0016", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366795", + "values_compared": 528 + }, + { + "max_rel_err": 0.0, + "name": "gen0017", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366796", + "values_compared": 527 + }, + { + "max_rel_err": 0.0, + "name": "gen0018", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366797", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0019", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366798", + "values_compared": 357 + }, + { + "max_rel_err": 0.0, + "name": "gen0020", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366799", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0021", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366800", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0022", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366801", + "values_compared": 50 + }, + { + "max_rel_err": 0.0, + "name": "gen0023", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366802", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0024", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366803", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0025", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366804", + "values_compared": 204 + }, + { + "max_rel_err": 0.0, + "name": "gen0026", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366805", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0027", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366806", + "values_compared": 42 + }, + { + "max_rel_err": 0.0, + "name": "gen0028", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366807", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "gen0029", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366808", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0030", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366809", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0031", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366810", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0032", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366811", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0033", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366812", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0034", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366813", + "values_compared": 527 + }, + { + "max_rel_err": 0.0, + "name": "gen0035", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366814", + "values_compared": 2 + }, + { + "max_rel_err": 0.0, + "name": "gen0036", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366815", + "values_compared": 50 + }, + { + "max_rel_err": 0.0, + "name": "gen0037", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366816", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0038", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366817", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0039", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366818", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0040", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366819", + "values_compared": 50 + }, + { + "max_rel_err": 0.0, + "name": "gen0041", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366820", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0042", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366821", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "gen0043", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366822", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0044", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366823", + "values_compared": 274 + }, + { + "max_rel_err": 0.0, + "name": "gen0045", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366824", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0046", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366825", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0047", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366826", + "values_compared": 306 + }, + { + "max_rel_err": 0.0, + "name": "gen0048", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366827", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "gen0049", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366828", + "values_compared": 525 + }, + { + "deferred": { + "symbol": "SmSurfOfRevolution::Transform", + "unit": "surface.surf_of_revolution_p3" + }, + "max_rel_err": 0.0, + "name": "gen0050", + "outcome": "deferred", + "pass": false, + "source": "generated:seed=3877976718798366829", + "values_compared": 0 + }, + { + "max_rel_err": 0.0, + "name": "gen0051", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366830", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0052", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366831", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "gen0053", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366832", + "values_compared": 527 + }, + { + "max_rel_err": 0.0, + "name": "gen0054", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366833", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0055", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366834", + "values_compared": 238 + }, + { + "max_rel_err": 0.0, + "name": "gen0056", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366835", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0057", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366836", + "values_compared": 525 + }, + { + "max_rel_err": 0.0, + "name": "gen0058", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366837", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0059", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366838", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0060", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366839", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0061", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366840", + "values_compared": 2 + }, + { + "max_rel_err": 0.0, + "name": "gen0062", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366841", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0063", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366842", + "values_compared": 441 + }, + { + "max_rel_err": 0.0, + "name": "gen0064", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366843", + "values_compared": 443 + }, + { + "max_rel_err": 0.0, + "name": "gen0065", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366844", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0066", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366845", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "gen0067", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366846", + "values_compared": 525 + }, + { + "deferred": { + "symbol": "SmSurfOfRevolution::Transform", + "unit": "surface.surf_of_revolution_p3" + }, + "max_rel_err": 0.0, + "name": "gen0068", + "outcome": "deferred", + "pass": false, + "source": "generated:seed=3877976718798366847", + "values_compared": 0 + }, + { + "max_rel_err": 0.0, + "name": "gen0069", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366848", + "values_compared": 42 + }, + { + "max_rel_err": 0.0, + "name": "gen0070", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366849", + "values_compared": 525 + }, + { + "max_rel_err": 0.0, + "name": "gen0071", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366850", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0072", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366851", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0073", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366852", + "values_compared": 14 + }, + { + "max_rel_err": 0.0, + "name": "gen0074", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366853", + "values_compared": 29 + }, + { + "max_rel_err": 0.0, + "name": "gen0075", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366854", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0076", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366855", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0077", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366856", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0078", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366857", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0079", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366858", + "values_compared": 14 + }, + { + "max_rel_err": 0.0, + "name": "gen0080", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366859", + "values_compared": 50 + }, + { + "max_rel_err": 0.0, + "name": "gen0081", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366860", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0082", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366861", + "values_compared": 2 + }, + { + "max_rel_err": 0.0, + "name": "gen0083", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366862", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0084", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366863", + "values_compared": 14 + }, + { + "max_rel_err": 0.0, + "name": "gen0085", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366864", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "gen0086", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366865", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0087", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366866", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0088", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366867", + "values_compared": 165 + }, + { + "max_rel_err": 0.0, + "name": "gen0089", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366868", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0090", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366869", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0091", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366870", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0092", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366871", + "values_compared": 525 + }, + { + "max_rel_err": 0.0, + "name": "gen0093", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366872", + "values_compared": 50 + }, + { + "max_rel_err": 0.0, + "name": "gen0094", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366873", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0095", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366874", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0096", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366875", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0097", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366876", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "gen0098", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366877", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0099", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366878", + "values_compared": 203 + }, + { + "max_rel_err": 0.0, + "name": "gen0100", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366879", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0101", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366880", + "values_compared": 2 + }, + { + "max_rel_err": 0.0, + "name": "gen0102", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366881", + "values_compared": 29 + }, + { + "max_rel_err": 0.0, + "name": "gen0103", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366882", + "values_compared": 29 + }, + { + "max_rel_err": 0.0, + "name": "gen0104", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366883", + "values_compared": 2 + }, + { + "max_rel_err": 0.0, + "name": "gen0105", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366884", + "values_compared": 14 + }, + { + "max_rel_err": 0.0, + "name": "gen0106", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366885", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0107", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366886", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0108", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366887", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0109", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366888", + "values_compared": 50 + }, + { + "max_rel_err": 0.0, + "name": "gen0110", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366889", + "values_compared": 527 + }, + { + "max_rel_err": 0.0, + "name": "gen0111", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366890", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0112", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366891", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0113", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366892", + "values_compared": 52 + }, + { + "max_rel_err": 0.0, + "name": "gen0114", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366893", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0115", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366894", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0116", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366895", + "values_compared": 62 + }, + { + "max_rel_err": 0.0, + "name": "gen0117", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366896", + "values_compared": 42 + }, + { + "max_rel_err": 0.0, + "name": "gen0118", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366897", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0119", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366898", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "gen0120", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366899", + "values_compared": 50 + }, + { + "max_rel_err": 0.0, + "name": "gen0121", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366900", + "values_compared": 527 + }, + { + "max_rel_err": 0.0, + "name": "gen0122", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366901", + "values_compared": 1 + }, + { + "deferred": { + "symbol": "SmSurfOfRevolution::Transform", + "unit": "surface.surf_of_revolution_p3" + }, + "max_rel_err": 0.0, + "name": "gen0123", + "outcome": "deferred", + "pass": false, + "source": "generated:seed=3877976718798366902", + "values_compared": 0 + }, + { + "max_rel_err": 0.0, + "name": "gen0124", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366903", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0125", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366904", + "values_compared": 305 + }, + { + "max_rel_err": 0.0, + "name": "gen0126", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366905", + "values_compared": 62 + }, + { + "max_rel_err": 0.0, + "name": "gen0127", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366906", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0128", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366907", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "gen0129", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366908", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0130", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366909", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0131", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366910", + "values_compared": 29 + }, + { + "max_rel_err": 0.0, + "name": "gen0132", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366911", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0133", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366912", + "values_compared": 254 + }, + { + "max_rel_err": 0.0, + "name": "gen0134", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366913", + "values_compared": 3 + }, + { + "max_rel_err": 0.0, + "name": "gen0135", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366914", + "values_compared": 525 + }, + { + "max_rel_err": 0.0, + "name": "gen0136", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366915", + "values_compared": 357 + }, + { + "max_rel_err": 0.0, + "name": "gen0137", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366916", + "values_compared": 525 + }, + { + "max_rel_err": 0.0, + "name": "gen0138", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366917", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0139", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366918", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0140", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366919", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0141", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366920", + "values_compared": 50 + }, + { + "max_rel_err": 0.0, + "name": "gen0142", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366921", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0143", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366922", + "values_compared": 165 + }, + { + "max_rel_err": 0.0, + "name": "gen0144", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366923", + "values_compared": 527 + }, + { + "max_rel_err": 0.0, + "name": "gen0145", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366924", + "values_compared": 50 + }, + { + "max_rel_err": 0.0, + "name": "gen0146", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366925", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0147", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366926", + "values_compared": 50 + }, + { + "max_rel_err": 0.0, + "name": "gen0148", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366927", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0149", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366928", + "values_compared": 527 + }, + { + "max_rel_err": 0.0, + "name": "gen0150", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366929", + "values_compared": 5 + }, + { + "deferred": { + "symbol": "SmSurfOfRevolution::Transform", + "unit": "surface.surf_of_revolution_p3" + }, + "max_rel_err": 0.0, + "name": "gen0151", + "outcome": "deferred", + "pass": false, + "source": "generated:seed=3877976718798366930", + "values_compared": 0 + }, + { + "max_rel_err": 0.0, + "name": "gen0152", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366931", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "gen0153", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366932", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0154", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366933", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0155", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366934", + "values_compared": 42 + }, + { + "max_rel_err": 0.0, + "name": "gen0156", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366935", + "values_compared": 2 + }, + { + "max_rel_err": 0.0, + "name": "gen0157", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366936", + "values_compared": 50 + }, + { + "deferred": { + "symbol": "SmSurfOfRevolution::Transform", + "unit": "surface.surf_of_revolution_p3" + }, + "max_rel_err": 0.0, + "name": "gen0158", + "outcome": "deferred", + "pass": false, + "source": "generated:seed=3877976718798366937", + "values_compared": 0 + }, + { + "max_rel_err": 0.0, + "name": "gen0159", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366938", + "values_compared": 527 + }, + { + "max_rel_err": 0.0, + "name": "gen0160", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366939", + "values_compared": 2 + }, + { + "max_rel_err": 0.0, + "name": "gen0161", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366940", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0162", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366941", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0163", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366942", + "values_compared": 527 + }, + { + "max_rel_err": 0.0, + "name": "gen0164", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366943", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0165", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366944", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0166", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366945", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0167", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366946", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0168", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366947", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0169", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366948", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0170", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366949", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0171", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366950", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0172", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366951", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0173", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366952", + "values_compared": 3 + }, + { + "max_rel_err": 0.0, + "name": "gen0174", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366953", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0175", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366954", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0176", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366955", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0177", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366956", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0178", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366957", + "values_compared": 527 + }, + { + "max_rel_err": 0.0, + "name": "gen0179", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366958", + "values_compared": 525 + }, + { + "max_rel_err": 0.0, + "name": "gen0180", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366959", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0181", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366960", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0182", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366961", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0183", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366962", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0184", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366963", + "values_compared": 14 + }, + { + "max_rel_err": 0.0, + "name": "gen0185", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366964", + "values_compared": 238 + }, + { + "max_rel_err": 0.0, + "name": "gen0186", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366965", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0187", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366966", + "values_compared": 50 + }, + { + "max_rel_err": 0.0, + "name": "gen0188", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366967", + "values_compared": 62 + }, + { + "max_rel_err": 0.0, + "name": "gen0189", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366968", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0190", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366969", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0191", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366970", + "values_compared": 29 + }, + { + "max_rel_err": 0.0, + "name": "gen0192", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366971", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0193", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366972", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0194", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366973", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0195", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366974", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0196", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366975", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0197", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366976", + "values_compared": 273 + }, + { + "max_rel_err": 0.0, + "name": "gen0198", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366977", + "values_compared": 201 + }, + { + "max_rel_err": 0.0, + "name": "gen0199", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366978", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0200", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366979", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0201", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366980", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0202", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366981", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0203", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366982", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0204", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366983", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0205", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366984", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0206", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366985", + "values_compared": 5 + }, + { + "deferred": { + "symbol": "SmSurfOfRevolution::Transform", + "unit": "surface.surf_of_revolution_p3" + }, + "max_rel_err": 0.0, + "name": "gen0207", + "outcome": "deferred", + "pass": false, + "source": "generated:seed=3877976718798366986", + "values_compared": 0 + }, + { + "max_rel_err": 0.0, + "name": "gen0208", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366987", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "gen0209", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366988", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0210", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366989", + "values_compared": 29 + }, + { + "max_rel_err": 0.0, + "name": "gen0211", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366990", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0212", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366991", + "values_compared": 14 + }, + { + "max_rel_err": 0.0, + "name": "gen0213", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366992", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0214", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366993", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0215", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366994", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0216", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366995", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0217", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366996", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "gen0218", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366997", + "values_compared": 203 + }, + { + "max_rel_err": 0.0, + "name": "gen0219", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366998", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0220", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798366999", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "gen0221", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367000", + "values_compared": 14 + }, + { + "max_rel_err": 0.0, + "name": "gen0222", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367001", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0223", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367002", + "values_compared": 29 + }, + { + "max_rel_err": 0.0, + "name": "gen0224", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367003", + "values_compared": 1 + }, + { + "deferred": { + "symbol": "SmSurfOfRevolution::Transform", + "unit": "surface.surf_of_revolution_p3" + }, + "max_rel_err": 0.0, + "name": "gen0225", + "outcome": "deferred", + "pass": false, + "source": "generated:seed=3877976718798367004", + "values_compared": 0 + }, + { + "max_rel_err": 0.0, + "name": "gen0226", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367005", + "values_compared": 14 + }, + { + "max_rel_err": 0.0, + "name": "gen0227", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367006", + "values_compared": 42 + }, + { + "max_rel_err": 0.0, + "name": "gen0228", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367007", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0229", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367008", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0230", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367009", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0231", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367010", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0232", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367011", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0233", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367012", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0234", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367013", + "values_compared": 203 + }, + { + "max_rel_err": 0.0, + "name": "gen0235", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367014", + "values_compared": 527 + }, + { + "max_rel_err": 0.0, + "name": "gen0236", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367015", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0237", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367016", + "values_compared": 14 + }, + { + "max_rel_err": 0.0, + "name": "gen0238", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367017", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0239", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367018", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0240", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367019", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0241", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367020", + "values_compared": 274 + }, + { + "max_rel_err": 0.0, + "name": "gen0242", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367021", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0243", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367022", + "values_compared": 29 + }, + { + "max_rel_err": 0.0, + "name": "gen0244", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367023", + "values_compared": 42 + }, + { + "max_rel_err": 0.0, + "name": "gen0245", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367024", + "values_compared": 167 + }, + { + "max_rel_err": 0.0, + "name": "gen0246", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367025", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0247", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367026", + "values_compared": 167 + }, + { + "max_rel_err": 0.0, + "name": "gen0248", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367027", + "values_compared": 42 + }, + { + "max_rel_err": 0.0, + "name": "gen0249", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367028", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0250", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367029", + "values_compared": 525 + }, + { + "max_rel_err": 0.0, + "name": "gen0251", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367030", + "values_compared": 3 + }, + { + "max_rel_err": 0.0, + "name": "gen0252", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367031", + "values_compared": 2 + }, + { + "max_rel_err": 0.0, + "name": "gen0253", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367032", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "gen0254", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367033", + "values_compared": 525 + }, + { + "max_rel_err": 0.0, + "name": "gen0255", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367034", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0256", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367035", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0257", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367036", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "gen0258", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367037", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0259", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367038", + "values_compared": 525 + }, + { + "max_rel_err": 0.0, + "name": "gen0260", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367039", + "values_compared": 29 + }, + { + "max_rel_err": 0.0, + "name": "gen0261", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367040", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0262", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367041", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "gen0263", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367042", + "values_compared": 29 + }, + { + "max_rel_err": 0.0, + "name": "gen0264", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367043", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0265", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367044", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0266", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367045", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0267", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367046", + "values_compared": 203 + }, + { + "max_rel_err": 0.0, + "name": "gen0268", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367047", + "values_compared": 29 + }, + { + "max_rel_err": 0.0, + "name": "gen0269", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367048", + "values_compared": 527 + }, + { + "deferred": { + "symbol": "SmSurfOfRevolution::Transform", + "unit": "surface.surf_of_revolution_p3" + }, + "max_rel_err": 0.0, + "name": "gen0270", + "outcome": "deferred", + "pass": false, + "source": "generated:seed=3877976718798367049", + "values_compared": 0 + }, + { + "max_rel_err": 0.0, + "name": "gen0271", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367050", + "values_compared": 4 + }, + { + "deferred": { + "symbol": "SmSurfOfRevolution::Transform", + "unit": "surface.surf_of_revolution_p3" + }, + "max_rel_err": 0.0, + "name": "gen0272", + "outcome": "deferred", + "pass": false, + "source": "generated:seed=3877976718798367051", + "values_compared": 0 + }, + { + "max_rel_err": 0.0, + "name": "gen0273", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367052", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0274", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367053", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "gen0275", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367054", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0276", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367055", + "values_compared": 527 + }, + { + "max_rel_err": 0.0, + "name": "gen0277", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367056", + "values_compared": 525 + }, + { + "max_rel_err": 0.0, + "name": "gen0278", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367057", + "values_compared": 254 + }, + { + "max_rel_err": 0.0, + "name": "gen0279", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367058", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0280", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367059", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0281", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367060", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0282", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367061", + "values_compared": 358 + }, + { + "max_rel_err": 0.0, + "name": "gen0283", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367062", + "values_compared": 4 + }, + { + "deferred": { + "symbol": "SmSurfOfRevolution::Transform", + "unit": "surface.surf_of_revolution_p3" + }, + "max_rel_err": 0.0, + "name": "gen0284", + "outcome": "deferred", + "pass": false, + "source": "generated:seed=3877976718798367063", + "values_compared": 0 + }, + { + "max_rel_err": 0.0, + "name": "gen0285", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367064", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0286", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367065", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0287", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367066", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "gen0288", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367067", + "values_compared": 50 + }, + { + "max_rel_err": 0.0, + "name": "gen0289", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367068", + "values_compared": 528 + }, + { + "max_rel_err": 0.0, + "name": "gen0290", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367069", + "values_compared": 2 + }, + { + "max_rel_err": 0.0, + "name": "gen0291", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367070", + "values_compared": 62 + }, + { + "max_rel_err": 0.0, + "name": "gen0292", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367071", + "values_compared": 5 + }, + { + "deferred": { + "symbol": "SmSurfOfRevolution::Transform", + "unit": "surface.surf_of_revolution_p3" + }, + "max_rel_err": 0.0, + "name": "gen0293", + "outcome": "deferred", + "pass": false, + "source": "generated:seed=3877976718798367072", + "values_compared": 0 + }, + { + "max_rel_err": 0.0, + "name": "gen0294", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367073", + "values_compared": 14 + }, + { + "max_rel_err": 0.0, + "name": "gen0295", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367074", + "values_compared": 3 + }, + { + "max_rel_err": 0.0, + "name": "gen0296", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367075", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0297", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367076", + "values_compared": 525 + }, + { + "max_rel_err": 0.0, + "name": "gen0298", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367077", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0299", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367078", + "values_compared": 527 + }, + { + "max_rel_err": 0.0, + "name": "gen0300", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367079", + "values_compared": 357 + }, + { + "max_rel_err": 0.0, + "name": "gen0301", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367080", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0302", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367081", + "values_compared": 239 + }, + { + "max_rel_err": 0.0, + "name": "gen0303", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367082", + "values_compared": 527 + }, + { + "max_rel_err": 0.0, + "name": "gen0304", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367083", + "values_compared": 50 + }, + { + "max_rel_err": 0.0, + "name": "gen0305", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367084", + "values_compared": 4 + }, + { + "deferred": { + "symbol": "SmSurfOfRevolution::Transform", + "unit": "surface.surf_of_revolution_p3" + }, + "max_rel_err": 0.0, + "name": "gen0306", + "outcome": "deferred", + "pass": false, + "source": "generated:seed=3877976718798367085", + "values_compared": 0 + }, + { + "max_rel_err": 0.0, + "name": "gen0307", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367086", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0308", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367087", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0309", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367088", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0310", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367089", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0311", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367090", + "values_compared": 358 + }, + { + "max_rel_err": 0.0, + "name": "gen0312", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367091", + "values_compared": 14 + }, + { + "max_rel_err": 0.0, + "name": "gen0313", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367092", + "values_compared": 357 + }, + { + "max_rel_err": 0.0, + "name": "gen0314", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367093", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0315", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367094", + "values_compared": 29 + }, + { + "max_rel_err": 0.0, + "name": "gen0316", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367095", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0317", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367096", + "values_compared": 527 + }, + { + "max_rel_err": 0.0, + "name": "gen0318", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367097", + "values_compared": 2 + }, + { + "max_rel_err": 0.0, + "name": "gen0319", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367098", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0320", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367099", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0321", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367100", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0322", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367101", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0323", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367102", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0324", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367103", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0325", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367104", + "values_compared": 525 + }, + { + "max_rel_err": 0.0, + "name": "gen0326", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367105", + "values_compared": 29 + }, + { + "deferred": { + "symbol": "SmSurfOfRevolution::Transform", + "unit": "surface.surf_of_revolution_p3" + }, + "max_rel_err": 0.0, + "name": "gen0327", + "outcome": "deferred", + "pass": false, + "source": "generated:seed=3877976718798367106", + "values_compared": 0 + }, + { + "max_rel_err": 0.0, + "name": "gen0328", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367107", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0329", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367108", + "values_compared": 3 + }, + { + "max_rel_err": 0.0, + "name": "gen0330", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367109", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0331", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367110", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0332", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367111", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0333", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367112", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0334", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367113", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0335", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367114", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0336", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367115", + "values_compared": 2 + }, + { + "max_rel_err": 0.0, + "name": "gen0337", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367116", + "values_compared": 442 + }, + { + "max_rel_err": 0.0, + "name": "gen0338", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367117", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0339", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367118", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0340", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367119", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0341", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367120", + "values_compared": 525 + }, + { + "max_rel_err": 0.0, + "name": "gen0342", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367121", + "values_compared": 167 + }, + { + "max_rel_err": 0.0, + "name": "gen0343", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367122", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "gen0344", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367123", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0345", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367124", + "values_compared": 14 + }, + { + "max_rel_err": 0.0, + "name": "gen0346", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367125", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0347", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367126", + "values_compared": 5 + }, + { + "deferred": { + "symbol": "SmSurfOfRevolution::Transform", + "unit": "surface.surf_of_revolution_p3" + }, + "max_rel_err": 0.0, + "name": "gen0348", + "outcome": "deferred", + "pass": false, + "source": "generated:seed=3877976718798367127", + "values_compared": 0 + }, + { + "max_rel_err": 0.0, + "name": "gen0349", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367128", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0350", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367129", + "values_compared": 527 + }, + { + "max_rel_err": 0.0, + "name": "gen0351", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367130", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0352", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367131", + "values_compared": 527 + }, + { + "max_rel_err": 0.0, + "name": "gen0353", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367132", + "values_compared": 527 + }, + { + "max_rel_err": 0.0, + "name": "gen0354", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367133", + "values_compared": 14 + }, + { + "max_rel_err": 0.0, + "name": "gen0355", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367134", + "values_compared": 166 + }, + { + "max_rel_err": 0.0, + "name": "gen0356", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367135", + "values_compared": 3 + }, + { + "max_rel_err": 0.0, + "name": "gen0357", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367136", + "values_compared": 202 + }, + { + "max_rel_err": 0.0, + "name": "gen0358", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367137", + "values_compared": 360 + }, + { + "max_rel_err": 0.0, + "name": "gen0359", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367138", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0360", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367139", + "values_compared": 29 + }, + { + "max_rel_err": 0.0, + "name": "gen0361", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367140", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0362", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367141", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0363", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367142", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0364", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367143", + "values_compared": 273 + }, + { + "max_rel_err": 0.0, + "name": "gen0365", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367144", + "values_compared": 527 + }, + { + "max_rel_err": 0.0, + "name": "gen0366", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367145", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0367", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367146", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0368", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367147", + "values_compared": 52 + }, + { + "max_rel_err": 0.0, + "name": "gen0369", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367148", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0370", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367149", + "values_compared": 527 + }, + { + "max_rel_err": 0.0, + "name": "gen0371", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367150", + "values_compared": 50 + }, + { + "max_rel_err": 0.0, + "name": "gen0372", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367151", + "values_compared": 50 + }, + { + "max_rel_err": 0.0, + "name": "gen0373", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367152", + "values_compared": 2 + }, + { + "max_rel_err": 0.0, + "name": "gen0374", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367153", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "gen0375", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367154", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0376", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367155", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0377", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367156", + "values_compared": 167 + }, + { + "max_rel_err": 0.0, + "name": "gen0378", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367157", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0379", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367158", + "values_compared": 52 + }, + { + "max_rel_err": 0.0, + "name": "gen0380", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367159", + "values_compared": 2 + }, + { + "max_rel_err": 0.0, + "name": "gen0381", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367160", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0382", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367161", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0383", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367162", + "values_compared": 237 + }, + { + "max_rel_err": 0.0, + "name": "gen0384", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367163", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0385", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367164", + "values_compared": 3 + }, + { + "max_rel_err": 0.0, + "name": "gen0386", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367165", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0387", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367166", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0388", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367167", + "values_compared": 29 + }, + { + "max_rel_err": 0.0, + "name": "gen0389", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367168", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "gen0390", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367169", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0391", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367170", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0392", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367171", + "values_compared": 525 + }, + { + "max_rel_err": 0.0, + "name": "gen0393", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367172", + "values_compared": 274 + }, + { + "max_rel_err": 0.0, + "name": "gen0394", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367173", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0395", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367174", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0396", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367175", + "values_compared": 525 + }, + { + "max_rel_err": 0.0, + "name": "gen0397", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367176", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0398", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367177", + "values_compared": 273 + }, + { + "max_rel_err": 0.0, + "name": "gen0399", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367178", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0400", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367179", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0401", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367180", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0402", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367181", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0403", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367182", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0404", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367183", + "values_compared": 14 + }, + { + "max_rel_err": 0.0, + "name": "gen0405", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367184", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "gen0406", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367185", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0407", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367186", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0408", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367187", + "values_compared": 14 + }, + { + "max_rel_err": 0.0, + "name": "gen0409", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367188", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0410", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367189", + "values_compared": 52 + }, + { + "max_rel_err": 0.0, + "name": "gen0411", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367190", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0412", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367191", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0413", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367192", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0414", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367193", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0415", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367194", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0416", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367195", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0417", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367196", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0418", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367197", + "values_compared": 14 + }, + { + "max_rel_err": 0.0, + "name": "gen0419", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367198", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0420", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367199", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "gen0421", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367200", + "values_compared": 4 + }, + { + "deferred": { + "symbol": "SmSurfOfRevolution::Transform", + "unit": "surface.surf_of_revolution_p3" + }, + "max_rel_err": 0.0, + "name": "gen0422", + "outcome": "deferred", + "pass": false, + "source": "generated:seed=3877976718798367201", + "values_compared": 0 + }, + { + "max_rel_err": 0.0, + "name": "gen0423", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367202", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0424", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367203", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0425", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367204", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0426", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367205", + "values_compared": 527 + }, + { + "max_rel_err": 0.0, + "name": "gen0427", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367206", + "values_compared": 203 + }, + { + "max_rel_err": 0.0, + "name": "gen0428", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367207", + "values_compared": 14 + }, + { + "max_rel_err": 0.0, + "name": "gen0429", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367208", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0430", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367209", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0431", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367210", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0432", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367211", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0433", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367212", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0434", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367213", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0435", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367214", + "values_compared": 525 + }, + { + "max_rel_err": 0.0, + "name": "gen0436", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367215", + "values_compared": 166 + }, + { + "max_rel_err": 0.0, + "name": "gen0437", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367216", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0438", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367217", + "values_compared": 50 + }, + { + "max_rel_err": 0.0, + "name": "gen0439", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367218", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0440", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367219", + "values_compared": 14 + }, + { + "max_rel_err": 0.0, + "name": "gen0441", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367220", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0442", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367221", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0443", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367222", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0444", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367223", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0445", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367224", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0446", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367225", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0447", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367226", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0448", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367227", + "values_compared": 29 + }, + { + "deferred": { + "symbol": "SmSurfOfRevolution::Transform", + "unit": "surface.surf_of_revolution_p3" + }, + "max_rel_err": 0.0, + "name": "gen0449", + "outcome": "deferred", + "pass": false, + "source": "generated:seed=3877976718798367228", + "values_compared": 0 + }, + { + "max_rel_err": 0.0, + "name": "gen0450", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367229", + "values_compared": 29 + }, + { + "max_rel_err": 0.0, + "name": "gen0451", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367230", + "values_compared": 50 + }, + { + "max_rel_err": 0.0, + "name": "gen0452", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367231", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0453", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367232", + "values_compared": 29 + }, + { + "max_rel_err": 0.0, + "name": "gen0454", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367233", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0455", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367234", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0456", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367235", + "values_compared": 52 + }, + { + "max_rel_err": 0.0, + "name": "gen0457", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367236", + "values_compared": 50 + }, + { + "max_rel_err": 0.0, + "name": "gen0458", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367237", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0459", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367238", + "values_compared": 42 + }, + { + "max_rel_err": 0.0, + "name": "gen0460", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367239", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0461", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367240", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0462", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367241", + "values_compared": 50 + }, + { + "max_rel_err": 0.0, + "name": "gen0463", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367242", + "values_compared": 443 + }, + { + "max_rel_err": 0.0, + "name": "gen0464", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367243", + "values_compared": 50 + }, + { + "max_rel_err": 0.0, + "name": "gen0465", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367244", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "gen0466", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367245", + "values_compared": 14 + }, + { + "max_rel_err": 0.0, + "name": "gen0467", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367246", + "values_compared": 165 + }, + { + "max_rel_err": 0.0, + "name": "gen0468", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367247", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0469", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367248", + "values_compared": 52 + }, + { + "max_rel_err": 0.0, + "name": "gen0470", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367249", + "values_compared": 256 + }, + { + "max_rel_err": 0.0, + "name": "gen0471", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367250", + "values_compared": 42 + }, + { + "max_rel_err": 0.0, + "name": "gen0472", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367251", + "values_compared": 42 + }, + { + "max_rel_err": 0.0, + "name": "gen0473", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367252", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0474", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367253", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0475", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367254", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0476", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367255", + "values_compared": 202 + }, + { + "deferred": { + "symbol": "SmSurfOfRevolution::Transform", + "unit": "surface.surf_of_revolution_p3" + }, + "max_rel_err": 0.0, + "name": "gen0477", + "outcome": "deferred", + "pass": false, + "source": "generated:seed=3877976718798367256", + "values_compared": 0 + }, + { + "max_rel_err": 0.0, + "name": "gen0478", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367257", + "values_compared": 14 + }, + { + "max_rel_err": 0.0, + "name": "gen0479", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367258", + "values_compared": 527 + }, + { + "max_rel_err": 0.0, + "name": "gen0480", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367259", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0481", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367260", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0482", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367261", + "values_compared": 168 + }, + { + "max_rel_err": 0.0, + "name": "gen0483", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367262", + "values_compared": 203 + }, + { + "max_rel_err": 0.0, + "name": "gen0484", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367263", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0485", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367264", + "values_compared": 203 + }, + { + "max_rel_err": 0.0, + "name": "gen0486", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367265", + "values_compared": 50 + }, + { + "max_rel_err": 0.0, + "name": "gen0487", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367266", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0488", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367267", + "values_compared": 202 + }, + { + "max_rel_err": 0.0, + "name": "gen0489", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367268", + "values_compared": 442 + }, + { + "max_rel_err": 0.0, + "name": "gen0490", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367269", + "values_compared": 72 + }, + { + "max_rel_err": 0.0, + "name": "gen0491", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367270", + "values_compared": 29 + }, + { + "max_rel_err": 0.0, + "name": "gen0492", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367271", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0493", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367272", + "values_compared": 5 + }, + { + "max_rel_err": 0.0, + "name": "gen0494", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367273", + "values_compared": 1 + }, + { + "max_rel_err": 0.0, + "name": "gen0495", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367274", + "values_compared": 527 + }, + { + "max_rel_err": 0.0, + "name": "gen0496", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367275", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0497", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367276", + "values_compared": 526 + }, + { + "max_rel_err": 0.0, + "name": "gen0498", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367277", + "values_compared": 4 + }, + { + "max_rel_err": 0.0, + "name": "gen0499", + "outcome": "pass", + "pass": true, + "source": "generated:seed=3877976718798367278", + "values_compared": 50 + } + ], + "cases_both_errored": 0, + "cases_deferred": 18, + "cases_failed": 0, + "cases_passed": 545, + "cases_total": 563, + "deferred_by_unit": { + "surface.surf_of_revolution_p3": 18 + }, + "max_rel_err": 0.0, + "pass": true, + "rel_tol": 1e-9, + "seeds": 16, + "status": "partial", + "unit": "surface.torus" +} From 771aef5a8c99befe8e54f9bd4fb1e50d94085328 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 14 Oct 2026 15:35:41 +0000 Subject: [PATCH 5/5] plan/PROGRESS.md: regenerate [skip ci] --- plan/PROGRESS.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plan/PROGRESS.md b/plan/PROGRESS.md index fec228e305..01637a52b1 100644 --- a/plan/PROGRESS.md +++ b/plan/PROGRESS.md @@ -2,7 +2,7 @@ Generated from `plan/modules.toml` by `harness/plan/progress.py`; do not edit by hand. -**126 / 261 units ported** (83 green, 43 partial), **161,740 / 387,903 upstream lines (41.7%)**. 18 claimed, 0 blocked, 117 todo. +**127 / 261 units ported** (83 green, 44 partial), **162,920 / 387,903 upstream lines (42.0%)**. 17 claimed, 0 blocked, 117 todo. Legend: โœ… green (identical to C++) ยท ๐ŸŸก partial (passes; some cases wait on unported code) ยท ๐Ÿ”จ claimed ยท โ–ถ๏ธ ready (all dependencies done) ยท โณ claimable, dependencies still missing ยท โ›” blocked. @@ -138,7 +138,6 @@ Anything `todo` can be claimed. These need stubs for, or co-porting of, the list - [ ] ๐Ÿ”จ `surface.cone_p1` (1835 lines) โ€” in progress: #31 @bherbruck (ready for review) - [ ] ๐Ÿ”จ `surface.surf_of_extrusion_p1` (1865 lines) โ€” in progress: #28 @bherbruck (ready for review) - [ ] ๐Ÿ”จ `surface.surf_of_revolution_p3` (1620 lines) โ€” in progress: #29 @bherbruck (ready for review) -- [ ] ๐Ÿ”จ `surface.torus` (1180 lines) โ€” in progress: #23 @froglemonade2 (ready for review) - [ ] ๐Ÿ”จ `surface.surf_of_revolution_p2` (1369 lines) โ€” in progress: #51 @froglemonade2 (ready for review) - [ ] ๐Ÿ”จ `surface.volume_p2` (1490 lines) โ€” in progress: #26 @bherbruck (ready for review) - [ ] ๐Ÿ”จ `surface.plane_p2` (1627 lines) โ€” in progress: #40 @bherbruck (ready for review) @@ -265,7 +264,7 @@ Anything `todo` can be claimed. These need stubs for, or co-porting of, the list - [ ] ๐ŸŸก `curve.curve_p5` (2299 lines) โ€” partial, 3097 cases - [ ] ๐Ÿ”จ `curve.curve_p9` (1783 lines) โ€” in progress: #13 @froglemonade2 (ready for review) -### surface (15/40) +### surface (16/40) - [ ] ๐ŸŸก `surface.volume_p1` (2014 lines) โ€” partial, 2886 cases - [x] โœ… `surface.b_spline_volume` (1226 lines) โ€” green, 2333 cases @@ -283,7 +282,7 @@ Anything `todo` can be claimed. These need stubs for, or co-porting of, the list - [ ] ๐Ÿ”จ `surface.surf_of_extrusion_p1` (1865 lines) โ€” in progress: #28 @bherbruck (ready for review) - [ ] ๐Ÿ”จ `surface.surf_of_revolution_p3` (1620 lines) โ€” in progress: #29 @bherbruck (ready for review) - [ ] ๐ŸŸก `surface.surface_cache_p2` (1245 lines) โ€” partial, 905 cases -- [ ] ๐Ÿ”จ `surface.torus` (1180 lines) โ€” in progress: #23 @froglemonade2 (ready for review) +- [ ] ๐ŸŸก `surface.torus` (1180 lines) โ€” partial, 563 cases - [ ] ๐ŸŸก `surface.unbend_volume` (920 lines) โ€” partial, 1874 cases - [ ] โณ `surface.cone_p2` (1656 lines) โ€” waiting - [ ] ๐Ÿ”จ `surface.surf_of_revolution_p2` (1369 lines) โ€” in progress: #51 @froglemonade2 (ready for review)