mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-08 13:01:52 +00:00
Merge origin/main into unit/surface.surface_p1
Brings in surface.torus (PR #23). Conflict in surface/surface/p2.rs imports: keep both StepSurface and Torus. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BY8UpXKuMci15rAWGXsbsm
This commit is contained in:
@@ -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<String>) -> 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<CircleIn>,
|
||||
}
|
||||
|
||||
#[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<Output, HarnessError> {
|
||||
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<Output, HarnessError> {
|
||||
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<f64>, 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<solidcore::Result<Torus>, 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<f64>, 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<f64>, 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<f64>,
|
||||
) -> Result<OpRun, HarnessError> {
|
||||
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<Output, HarnessError> {
|
||||
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<Inputs> {
|
||||
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<Value> {
|
||||
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<Vec<Case>, HarnessError> {
|
||||
let ctx = CaseContext {
|
||||
seeds: ctx.seeds.max(MIN_GENERATED),
|
||||
..ctx.clone()
|
||||
};
|
||||
default_cases(self, &ctx)
|
||||
}
|
||||
|
||||
fn run_cpp(&self, case: &Case) -> Result<Output, HarnessError> {
|
||||
cpp(&case.parse()?)
|
||||
}
|
||||
|
||||
fn run_rust(&self, case: &Case) -> Result<Output, HarnessError> {
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<CircleIn>,
|
||||
}
|
||||
|
||||
/// 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<TorusIn>,
|
||||
},
|
||||
/// `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<String>) -> 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<f64>, 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<f64>, 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<Vec<f64>, 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,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -16,6 +16,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 unbend_volume;
|
||||
|
||||
@@ -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<Curve> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<Curve>;
|
||||
|
||||
// ---- 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<Curve> {
|
||||
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)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -171,6 +171,7 @@ use crate::nurbs::knots::Side;
|
||||
use crate::surface::curve_bounded_surface::CurveBoundedSurface;
|
||||
use crate::surface::plane::Plane;
|
||||
use crate::surface::surface::p1::StepSurface;
|
||||
use crate::surface::torus::Torus;
|
||||
use crate::topology::database_io::DatabaseReader;
|
||||
|
||||
// ---- type codes ---------------------------------------------------------------------
|
||||
@@ -939,8 +940,14 @@ 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 => 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,
|
||||
)?)),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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 <SmAxis2Placement.h>
|
||||
#include <SmBSplineCurve.h>
|
||||
#include <SmBSplineSurface.h>
|
||||
#include <SmCircle.h>
|
||||
#include <SmContext.h>
|
||||
#include <SmCurve.h>
|
||||
#include <SmEllipse.h>
|
||||
#include <SmExtent1d.h>
|
||||
#include <SmExtent2d.h>
|
||||
#include <SmNurbsCrv.h>
|
||||
#include <SmNurbsSrf.h>
|
||||
#include <SmSurfOfRevolution.h>
|
||||
#include <SmSurface.h>
|
||||
#include <SmTorus.h>
|
||||
#include <SmVector2d.h>
|
||||
#include <SmVector3d.h>
|
||||
#include <nurbs.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
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<double>& 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<double>& 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<double>& 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<double>& 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<double>& 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<SmVector3d> 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<double> 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<double> 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
|
||||
}
|
||||
+3
-4
@@ -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)
|
||||
|
||||
+2
-2
@@ -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"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user