Merge origin/main into unit/intersect.curve_class_p1

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:
bherbruck
2026-10-14 19:48:13 -04:00
33 changed files with 238969 additions and 103 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,622 @@
//! FFI wrappers for unit `intersect.surface_intersector_p4`.
//!
//! Shim: `harness/cpp/shim/intersect_surface_intersector_p4.cpp`
//! (`sc_intersect_surface_intersector_p4_run`). Upstream:
//! `source/SMLib/src/SmSurfaceIntersector.cpp` (`TraceCurve`, `FindInteriorCurves`,
//! `DoPointIntersection`, `DoLawIntersection`).
//!
//! [`run`] builds surfaces A and B (planes, B-splines or curve-bounded surfaces over
//! either; B may be A itself), the intersector
//! `SmSurfaceIntersector(A, dom1, B, dom2, fillet)` (its context set as
//! `DoIntersection` does), the program's curves and its points (a point arena; each op
//! names points by index), then runs the [`Op`]s in order. The `FlushCurve` /
//! `FindInteriorCurves` output arrays persist over the program; the 3D curve array
//! starts with the program's curves and is the `r3DCurves` of `TraceCurve`. The record
//! is, per op, `op status [values]`, then the state `approx_tol trace_dir start_tol
//! step_tol ang_tol last_through ext_dist ext_clip closed clipped interior start_point
//! end_point` (point indices, `-3` for none, `-1` for a point the intersector
//! allocated), the curve, start and through lists (`count refs`) and `npoints points`.
//! A list ref is a program point's index, or `-1` followed by the point.
//!
//! A point is 80 values: `type ptype[4] (uv pos, uv tangent) x 2, surface matrix [3][3]
//! x 2 (row major, 3 values each), curve pos, curve tangent, parameter, angle,
//! deviation, user[4]`. Curves are written as `type dim knot_type form has [n p m
//! cpts(x y z w) knots] ivl[2]`; curve outputs are `count curves`. The output arrays are
//! `3d curves` (those after the program's), `uv curves 1`, `uv curves 2`, `ntypes
//! types`, `ndevs devs`.
//!
//! | [`Op`] | upstream | values |
//! |---------------|----------------------------------------------------------------|--------|
//! | `State` | the protected members | state |
//! | `Point` | the point | point |
//! | `AddCurve`, `AddStart`, `AddThrough` | `AddPointToCurve`, `AddStartPoint`, `AddThroughPoint` | |
//! | `Cpv` | `ComputePointValues(uvs, p, prev, step)` | point (also on error) |
//! | `Members` | sets the trace direction and the tolerances | |
//! | `Ends` | sets `m_pStartPoint`, `m_pEndPoint` | |
//! | `Ext` | sets `m_bDoingExtensionClipping`, `m_dLastThroughParmeter`, `m_dExtensionDistance` | |
//! | `Trace` | `TraceCurve(p, 3d curves)` | |
//! | `Flush` | `FlushCurve(output arrays)` | outputs (also on error) |
//! | `Fic` | `FindInteriorCurves(output arrays)` | outputs (also on error) |
//! | `Dpi` | `DoPointIntersection(ctx, points, before, after, start dir, end dir, tol, ang tol, ..)` | single curve (status 0 only) |
//! | `Dli` | `DoLawIntersection(ctx, points, SmFilletLaw(), orient, curve, ivl, orientations, avg, FALSE, tol, NULL, ..)` | single curve (status 0 only) |
//!
//! A single curve is `has [curve]` for the 3D curve and each UV curve, then the curve
//! type and the deviation (`-7` where upstream leaves them unset).
//!
//! [`run`] accepts only finite inputs of magnitude at most [`MAX_COORD`], well-formed
//! clamped B-splines, at most [`MAX_POINTS`] points, [`MAX_CURVES`] curves, [`MAX_OPS`]
//! ops and [`MAX_OP_POINTS`] points per `Dpi`/`Dli`, no `Cpv` whose previous point is its
//! point and no `Dli` without curves. The shim does not run (status [`REJECTED`]):
//! `Add*` of a point already on a list (it corrupts the intrusive lists); `Trace` from a
//! start point on a list when there are no curve points (it is appended); `Dpi`/`Dli`
//! with a point twice or a point on a list (they are linked in place).
use crate::{call_with_buffer, FfiError};
mod sys {
extern "C" {
pub fn sc_intersect_surface_intersector_p4_run(
input: *const f64,
n_in: i64,
out: *mut f64,
cap: i64,
out_len: *mut i64,
) -> i32;
}
}
/// Largest input magnitude.
pub const MAX_COORD: f64 = 1e9;
/// Most points per program.
pub const MAX_POINTS: usize = 64;
/// Most curves per program.
pub const MAX_CURVES: usize = 8;
/// Most ops per program.
pub const MAX_OPS: usize = 64;
/// Most points of a `Dpi` or `Dli`.
pub const MAX_OP_POINTS: usize = 8;
/// `NL_NOW`: the weight of a non-rational control point.
pub const NOW: f64 = -f64::MAX;
/// The op status of an op the shim does not run.
pub const REJECTED: f64 = -2.0;
/// Values per point.
pub const POINT_LEN: usize = 80;
/// A surface.
#[derive(Debug, Clone, PartialEq)]
pub enum SurfaceIn {
/// `SmPlane(o, x, y, SmVector2d(scale), SmExtent2d(domain), ctx)`.
Plane {
/// Origin.
o: [f64; 3],
/// X axis.
x: [f64; 3],
/// Y axis.
y: [f64; 3],
/// u and v scales.
scale: [f64; 2],
/// `umin vmin umax vmax`.
domain: [f64; 4],
},
/// `SmBSplineSurface(gw_SURFACE*, FALSE, ctx)`.
BSpline {
/// u degree.
p: usize,
/// v degree.
q: usize,
/// Rows (u index) of `[x, y, z, w]` (`w` = [`NOW`] when non-rational).
net: Vec<Vec<[f64; 4]>>,
/// u knots.
u: Vec<f64>,
/// v knots.
v: Vec<f64>,
},
/// `SmCurveBoundedSurface(basis, {}, {}, TRUE)` over a plane or B-spline surface
/// (implicit outer, no boundary curves).
CurveBounded(Box<SurfaceIn>),
}
/// A curve.
#[derive(Debug, Clone, PartialEq)]
pub enum CurveIn {
/// `SmLine::CreateLineSegment(ctx, 3, a, b, l)`.
Segment([f64; 3], [f64; 3]),
/// `SmBSplineCurve(dim, n, m, p, Pw, U, ctx)`.
BSpline {
/// Image dimension, 2 or 3.
dim: u32,
/// Degree.
p: usize,
/// Control points (`x y z w`, `w` = [`NOW`] when non-rational).
cpts: Vec<[f64; 4]>,
/// Knots (`n + p + 2`).
knots: Vec<f64>,
},
}
/// An op (see the module docs).
#[derive(Debug, Clone, PartialEq)]
pub enum Op {
/// The state.
State,
/// Point `i`.
Point(usize),
/// `AddPointToCurve`.
AddCurve(usize),
/// `AddStartPoint`.
AddStart(usize),
/// `AddThroughPoint`.
AddThrough(usize),
/// `ComputePointValues`.
Cpv {
/// UV points.
uvs: [[f64; 2]; 2],
/// The point.
i: usize,
/// The previous point (not `i`).
prev: Option<usize>,
/// The step size.
step: Option<f64>,
},
/// Sets the trace direction and the tolerances.
Members {
/// `m_dCurveTraceDirection`.
trace_dir: f64,
/// `m_dThisApproxTol3d`.
approx_tol: f64,
/// `m_dStartParallelTolRadians`.
start_tol: f64,
/// `m_dSteppingParallelTolRadians`.
step_tol: f64,
/// `m_dThisAngTolRad`.
ang_tol: f64,
},
/// Sets `m_pStartPoint` and `m_pEndPoint`.
Ends {
/// The start point.
start: Option<usize>,
/// The end point.
end: Option<usize>,
},
/// Sets the extension clipping members.
Ext {
/// `m_bDoingExtensionClipping`.
clip: bool,
/// `m_dLastThroughParmeter`.
last_through: f64,
/// `m_dExtensionDistance`.
distance: f64,
},
/// `TraceCurve` from point `i`.
Trace(usize),
/// `FlushCurve`.
Flush,
/// `FindInteriorCurves`.
Fic,
/// `DoPointIntersection`.
Dpi(DpiOp),
/// `DoLawIntersection`.
Dli(DliOp),
}
/// The arguments of [`Op::Dpi`].
#[derive(Debug, Clone, PartialEq, Default)]
pub struct DpiOp {
/// `crTsectPoints`.
pub points: Vec<usize>,
/// `bExtendBeforeStart`.
pub before: bool,
/// `bExtendAfterEnd`.
pub after: bool,
/// `pOptStartDirection`.
pub start_dir: Option<[f64; 3]>,
/// `pOptEndDirection`.
pub end_dir: Option<[f64; 3]>,
/// `pOptApproxTol3d`.
pub tol: Option<f64>,
/// `pdOptAngTolRad`.
pub ang_tol: Option<f64>,
}
/// The arguments of [`Op::Dli`].
#[derive(Debug, Clone, PartialEq)]
pub struct DliOp {
/// `crTsectPoints`.
pub points: Vec<usize>,
/// `bLawOrient`.
pub orient: bool,
/// Index of the point curve among the program's curves.
pub curve: usize,
/// `crPointCurveInterval` (`SmExtent1d(a, b)`).
pub interval: [f64; 2],
/// `dSurfaceOrientations`.
pub orientations: [f64; 2],
/// `bUseNormalPlaneAveraging`.
pub average: bool,
/// `pOptApproxTol3d`.
pub tol: Option<f64>,
}
/// A program (see the module docs).
#[derive(Debug, Clone, PartialEq)]
pub struct Program {
/// Surface A.
pub a: SurfaceIn,
/// Surface B, `None` for A itself.
pub b: Option<SurfaceIn>,
/// Domain of A.
pub dom1: [f64; 4],
/// Domain of B.
pub dom2: [f64; 4],
/// `bFromFilletIntersector`.
pub fillet: bool,
/// Curves.
pub curves: Vec<CurveIn>,
/// Points ([`POINT_LEN`] values each).
pub points: Vec<Vec<f64>>,
/// Ops.
pub ops: Vec<Op>,
}
fn bad(msg: &str) -> FfiError {
FfiError::InvalidArgument(msg.into())
}
fn finite(x: f64) -> Result<f64, FfiError> {
if x.is_finite() && x.abs() <= MAX_COORD {
Ok(x)
} else {
Err(bad("value not finite or too large"))
}
}
/// Clamped, non-decreasing knots with interior multiplicity at most `p`.
fn check_knots(knots: &[f64], ncp: usize, p: usize) -> Result<(), FfiError> {
if knots.len() != ncp + p + 1 {
return Err(bad("knot count"));
}
if knots.windows(2).any(|w| w.first() > w.last()) {
return Err(bad("knots decreasing"));
}
let (Some(&k0), Some(&km)) = (knots.first(), knots.last()) else {
return Err(bad("knot count"));
};
if km <= k0
|| knots.iter().take(p + 1).any(|&k| k != k0)
|| knots.iter().rev().take(p + 1).any(|&k| k != km)
{
return Err(bad("knots not clamped"));
}
let mut run = 0usize;
let mut last = f64::NAN;
for &k in knots.iter().filter(|&&k| k != k0 && k != km) {
run = if k == last { run + 1 } else { 1 };
last = k;
if run > p {
return Err(bad("interior knot multiplicity"));
}
}
Ok(())
}
/// All [`NOW`] or all in `(0, MAX_COORD]`.
fn check_weights<'a>(mut ws: impl Iterator<Item = &'a [f64; 4]>) -> Result<(), FfiError> {
let Some(first) = ws.next() else {
return Err(bad("no control points"));
};
let rational = first[3] != NOW;
let ok = |w: f64| {
if rational {
w > 0.0 && w <= MAX_COORD
} else {
w == NOW
}
};
if !ok(first[3]) || ws.any(|c| !ok(c[3])) {
return Err(bad("weights"));
}
Ok(())
}
struct Enc {
v: Vec<f64>,
npoints: usize,
ncurves: usize,
}
impl Enc {
fn num(&mut self, x: f64) -> Result<(), FfiError> {
self.v.push(finite(x)?);
Ok(())
}
fn nums(&mut self, xs: &[f64]) -> Result<(), FfiError> {
xs.iter().try_for_each(|&x| self.num(x))
}
fn int(&mut self, x: usize) {
self.v.push(x as f64);
}
fn flag(&mut self, b: bool) {
self.v.push(if b { 1.0 } else { 0.0 });
}
fn cpt(&mut self, c: &[f64; 4]) -> Result<(), FfiError> {
self.nums(&c[..3])?;
self.v.push(c[3]);
Ok(())
}
fn pt(&mut self, i: usize) -> Result<(), FfiError> {
if i >= self.npoints {
return Err(bad("point index"));
}
self.int(i);
Ok(())
}
fn surface(&mut self, s: &SurfaceIn) -> Result<(), FfiError> {
match s {
SurfaceIn::Plane {
o,
x,
y,
scale,
domain,
} => {
self.int(0);
self.nums(o)?;
self.nums(x)?;
self.nums(y)?;
self.nums(scale)?;
self.nums(domain)
}
SurfaceIn::BSpline { p, q, net, u, v } => {
let nu = net.len();
let nv = net.first().map_or(0, Vec::len);
if !(1..=8).contains(p) || !(1..=8).contains(q) {
return Err(bad("surface degree"));
}
if !(2..=17).contains(&nu) || !(2..=17).contains(&nv) || *p >= nu || *q >= nv {
return Err(bad("surface size"));
}
if net.iter().any(|r| r.len() != nv) {
return Err(bad("ragged net"));
}
check_knots(u, nu, *p)?;
check_knots(v, nv, *q)?;
check_weights(net.iter().flatten())?;
self.int(1);
self.int(*p);
self.int(*q);
self.int(nu);
self.int(nv);
for c in net.iter().flatten() {
self.cpt(c)?;
}
self.nums(u)?;
self.nums(v)
}
SurfaceIn::CurveBounded(basis) => {
if matches!(**basis, SurfaceIn::CurveBounded(_)) {
return Err(bad("nested curve-bounded surface"));
}
self.int(3);
self.surface(basis)
}
}
}
fn curve(&mut self, c: &CurveIn) -> Result<(), FfiError> {
match c {
CurveIn::Segment(a, b) => {
if a == b {
return Err(bad("zero-length segment"));
}
self.int(0);
self.nums(a)?;
self.nums(b)
}
CurveIn::BSpline { dim, p, cpts, knots } => {
if !(2..=3).contains(dim) || !(1..=8).contains(p) {
return Err(bad("curve dim or degree"));
}
if !(2..=33).contains(&cpts.len()) || *p >= cpts.len() {
return Err(bad("curve size"));
}
check_knots(knots, cpts.len(), *p)?;
check_weights(cpts.iter())?;
self.int(1);
self.int(*dim as usize);
self.int(*p);
self.int(cpts.len());
for c in cpts {
self.cpt(c)?;
}
self.nums(knots)
}
}
}
fn point(&mut self, p: &[f64]) -> Result<(), FfiError> {
if p.len() != POINT_LEN {
return Err(bad("point length"));
}
let code = |x: f64, hi: f64| x >= 0.0 && x <= hi && x.fract() == 0.0;
let ok = p
.iter()
.take(5)
.enumerate()
.all(|(k, &t)| code(t, if k == 0 { 7.0 } else { 5.0 }));
if !ok {
return Err(bad("point type codes"));
}
self.nums(p)
}
fn pts(&mut self, ids: &[usize]) -> Result<(), FfiError> {
if ids.len() > MAX_OP_POINTS {
return Err(bad("too many op points"));
}
self.int(ids.len());
ids.iter().try_for_each(|&i| self.pt(i))
}
fn opt_num(&mut self, x: Option<f64>) -> Result<(), FfiError> {
self.flag(x.is_some());
self.num(x.unwrap_or(0.0))
}
fn opt_vec(&mut self, v: Option<[f64; 3]>) -> Result<(), FfiError> {
self.flag(v.is_some());
self.nums(&v.unwrap_or([0.0; 3]))
}
fn opt_pt(&mut self, i: Option<usize>) -> Result<(), FfiError> {
self.flag(i.is_some());
self.pt(i.unwrap_or(0))
}
fn op(&mut self, op: &Op) -> Result<(), FfiError> {
match op {
Op::State => self.int(1),
Op::Point(i) => {
self.int(2);
self.pt(*i)?;
}
Op::AddCurve(i) | Op::AddStart(i) | Op::AddThrough(i) => {
self.int(match op {
Op::AddCurve(_) => 3,
Op::AddStart(_) => 4,
_ => 5,
});
self.pt(*i)?;
}
Op::Cpv { uvs, i, prev, step } => {
if *prev == Some(*i) {
return Err(bad("previous point is the point"));
}
self.int(6);
self.nums(&uvs[0])?;
self.nums(&uvs[1])?;
self.pt(*i)?;
self.opt_pt(*prev)?;
self.flag(step.is_some());
self.num(step.unwrap_or(0.0))?;
}
Op::Members {
trace_dir,
approx_tol,
start_tol,
step_tol,
ang_tol,
} => {
self.int(7);
self.nums(&[*trace_dir, *approx_tol, *start_tol, *step_tol, *ang_tol])?;
}
Op::Ends { start, end } => {
self.int(8);
self.opt_pt(*start)?;
self.opt_pt(*end)?;
}
Op::Ext {
clip,
last_through,
distance,
} => {
self.int(9);
self.flag(*clip);
self.num(*last_through)?;
self.num(*distance)?;
}
Op::Trace(i) => {
self.int(10);
self.pt(*i)?;
}
Op::Flush => self.int(11),
Op::Fic => self.int(14),
Op::Dpi(d) => {
self.int(12);
self.pts(&d.points)?;
self.flag(d.before);
self.flag(d.after);
self.opt_vec(d.start_dir)?;
self.opt_vec(d.end_dir)?;
self.opt_num(d.tol)?;
self.opt_num(d.ang_tol)?;
}
Op::Dli(d) => {
if d.curve >= self.ncurves {
return Err(bad("curve index"));
}
self.int(13);
self.pts(&d.points)?;
self.flag(d.orient);
self.int(d.curve);
self.nums(&d.interval)?;
self.nums(&d.orientations)?;
self.flag(d.average);
self.opt_num(d.tol)?;
}
}
Ok(())
}
}
/// Encodes `prog` for the shim.
///
/// # Errors
///
/// [`FfiError::InvalidArgument`] for a rejected program.
pub fn encode(prog: &Program) -> Result<Vec<f64>, FfiError> {
if prog.ops.len() > MAX_OPS || prog.curves.len() > MAX_CURVES || prog.points.len() > MAX_POINTS
{
return Err(bad("too many ops, curves or points"));
}
let mut e = Enc {
v: Vec::new(),
npoints: prog.points.len(),
ncurves: prog.curves.len(),
};
e.surface(&prog.a)?;
match &prog.b {
None => e.int(2),
Some(b) => e.surface(b)?,
}
e.nums(&prog.dom1)?;
e.nums(&prog.dom2)?;
e.flag(prog.fillet);
e.int(prog.curves.len());
for c in &prog.curves {
e.curve(c)?;
}
e.int(prog.points.len());
for p in &prog.points {
e.point(p)?;
}
e.int(prog.ops.len());
for op in &prog.ops {
e.op(op)?;
}
Ok(e.v)
}
/// Runs `prog` (see the module docs) and returns the record.
///
/// # Errors
///
/// [`FfiError::InvalidArgument`] for rejected inputs; [`FfiError::Status`] for a shim
/// failure.
pub fn run(prog: &Program) -> Result<Vec<f64>, FfiError> {
let input = encode(prog)?;
let n_in = i64::try_from(input.len()).map_err(|_| bad("program too long"))?;
call_with_buffer(4096, |buf: &mut [f64], len| {
// SAFETY: `input` is valid for `n_in` reads; `buf` for `buf.len()` writes; `len`
// is a valid i64; nothing is retained.
unsafe {
sys::sc_intersect_surface_intersector_p4_run(
input.as_ptr(),
n_in,
buf.as_mut_ptr(),
buf.len() as i64,
&raw mut *len,
)
}
})
}
@@ -0,0 +1,461 @@
//! FFI wrappers for unit `query.loop`.
//!
//! Shim: `harness/cpp/shim/query_loop.cpp`.
//! Upstream: `source/SMLib/src/SmLoop.cpp` (`SmLoop::ContainsUVPoint`) on a one-face
//! B-rep built with `SmBrepConstructor` (`source/SMLib/src/SmBrepData.cpp`).
//!
//! | Rust | shim | upstream |
//! |--------------|--------------------------|---------------------------|
//! | [`contains`] | `sc_query_loop_contains` | `SmLoop::ContainsUVPoint` |
//!
//! Input layout (all `f64`): `face_tol vertex_loop u v tol3d`, the surface block, `n_edges`
//! then per edge `curve-block d0 d1 tol`, `n_edgeuses` then per edge use
//! `edge orient(1 = SAME, 2 = OPPOSITE) n p m cpts(x y w) knots` (a 2D B-spline UV trim
//! curve). Surface block: `0 O[3] X[3] Y[3] su sv d[4]` (`SmPlane`), `1 n m p q r s
//! net(x y z w) U V` (`SmBSplineSurface`), `2 gen P[3] A[3] swap`
//! (`SmSurfOfRevolution::CreateCanonical`, generator `0 a[3] b[3]` line or
//! `1 c[3] x[3] y[3] d0 d1 r` circle). Curve block: `0 a[3] b[3]` (`SmLine`),
//! `1 c[3] x[3] y[3] d0 d1 r` (`SmCircle`), `2 n p m cpts(x y z w) knots`
//! (`SmBSplineCurve`).
//!
//! Guards (inputs that are undefined upstream): NURB sizes must be consistent (degree
//! `1..=MAX_DEGREE`, `n >= p`, `m = n + p + 1`, non-decreasing knots with a non-empty
//! domain, positive weights or `NL_NOW`), every value finite; an edge is used at most twice (a third
//! use takes the non-manifold radial-sector path of the constructor); every edge use's
//! interval lies in its UV trim curve's domain (evaluations outside it fail and
//! `ContainsUVPoint` then reads uninitialised outputs); the first edge use's interval is
//! longer than `SM_EFF_ZERO` (otherwise the end of the walk is read before it is ever
//! written); a vertex loop has no edge uses. The shim itself rejects a point the surface
//! cannot evaluate (build status `5`).
use crate::FfiError;
/// Largest degree accepted for NURB curves and surfaces.
pub const MAX_DEGREE: usize = 8;
/// Largest control point count per direction.
pub const MAX_POINTS: usize = 64;
/// Largest number of edges or edge uses.
pub const MAX_EDGES: usize = 64;
/// Upstream `NL_NOW`: the weight of a non-rational control point.
pub const NOW: f64 = -f64::MAX;
/// Upstream `SM_EFF_ZERO`.
const EFF_ZERO: f64 = 1.0e-12;
mod sys {
extern "C" {
pub fn sc_query_loop_contains(input: *const f64, n_in: i64, out: *mut f64) -> i32;
}
}
/// A NURB curve: control points `[x, y, z, w]` (for a 2D curve `z` is ignored), degree
/// and knots.
#[derive(Debug, Clone, PartialEq)]
pub struct Nurb {
/// Control points (`Pw`, homogeneous as stored).
pub cpts: Vec<[f64; 4]>,
/// Degree.
pub p: usize,
/// Knots (`m + 1 = n + p + 2` values).
pub knots: Vec<f64>,
}
/// A 3D edge curve.
#[derive(Debug, Clone, PartialEq)]
pub enum Curve3 {
/// `SmLine(a, b, 3)`.
Line {
/// Start point.
a: [f64; 3],
/// End point.
b: [f64; 3],
},
/// `SmCircle(c, x, y, [d0, d1], r, 3)`.
Circle {
/// Centre.
c: [f64; 3],
/// X axis.
x: [f64; 3],
/// Y axis.
y: [f64; 3],
/// Domain.
d: [f64; 2],
/// Radius.
r: f64,
},
/// `SmBSplineCurve(nurb, 3)`.
BSpline(Nurb),
}
/// The generator of a surface of revolution.
#[derive(Debug, Clone, PartialEq)]
pub enum Gen {
/// `SmLine(a, b, 3)` with its NURB made.
Line {
/// Start point.
a: [f64; 3],
/// End point.
b: [f64; 3],
},
/// `SmCircle(c, x, y, [d0, d1], r, 3)`.
Circle {
/// Centre.
c: [f64; 3],
/// X axis.
x: [f64; 3],
/// Y axis.
y: [f64; 3],
/// Domain.
d: [f64; 2],
/// Radius.
r: f64,
},
}
/// The face's surface.
#[derive(Debug, Clone, PartialEq)]
pub enum Surf {
/// `SmPlane(o, x, y, scale, SmExtent2d(d0, d1, d2, d3))`.
Plane {
/// Origin.
o: [f64; 3],
/// X axis.
x: [f64; 3],
/// Y axis.
y: [f64; 3],
/// UV scale.
scale: [f64; 2],
/// Domain `[umin, vmin, umax, vmax]`.
domain: [f64; 4],
},
/// `SmBSplineSurface(nurb)`: net rows in u.
BSpline {
/// Control net `[x, y, z, w]`, `(n + 1)` rows of `(m + 1)`.
net: Vec<Vec<[f64; 4]>>,
/// Degree in u.
p: usize,
/// Degree in v.
q: usize,
/// U knots.
u: Vec<f64>,
/// V knots.
v: Vec<f64>,
},
/// `SmSurfOfRevolution::CreateCanonical(gen, point, axis, swap)`.
Revolution {
/// Generator.
gen: Gen,
/// Axis point.
point: [f64; 3],
/// Axis direction.
axis: [f64; 3],
/// Swap u and v.
swap: bool,
},
}
/// An edge: 3D curve, interval and zone tolerance.
#[derive(Debug, Clone, PartialEq)]
pub struct Edge {
/// The curve.
pub curve: Curve3,
/// Interval `[min, max]` (`SetLimits` parameters).
pub interval: [f64; 2],
/// Zone tolerance (`SetTolerance(t, FALSE, FALSE)`).
pub tol: f64,
}
/// An edge use of the loop, in loop order.
#[derive(Debug, Clone, PartialEq)]
pub struct Edgeuse {
/// Index into [`Case::edges`].
pub edge: usize,
/// `SM_OT_SAME` (else `SM_OT_OPPOSITE`).
pub same: bool,
/// The 2D B-spline UV trim curve.
pub trim: Nurb,
}
/// One `ContainsUVPoint` call.
#[derive(Debug, Clone, PartialEq)]
pub struct Case {
/// The face zone tolerance (`SetTolerance(t, FALSE, FALSE)`).
pub face_tol: f64,
/// A single-vertex loop (no edge uses).
pub vertex_loop: bool,
/// The point.
pub uv: [f64; 2],
/// `dTol3d` (unused upstream).
pub tol3d: f64,
/// The surface.
pub surface: Surf,
/// The edges.
pub edges: Vec<Edge>,
/// The edge uses in loop order.
pub edgeuses: Vec<Edgeuse>,
}
/// The shim's answer.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Answer {
/// `0` built and classified; else the failing build step (`1` surface, `3` a
/// constructor step, `4` the realised loop differs from the request, `5` the point
/// cannot be evaluated).
pub build: i32,
/// `SmPointObjectContainmentType`.
pub containment: i32,
/// Index of the on-edge, `-1` for none.
pub on_edge: i64,
/// The edge parameter.
pub edge_param: f64,
/// `SmOrientType`.
pub orientation: i32,
}
fn bad(m: impl Into<String>) -> FfiError {
FfiError::InvalidArgument(m.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")))
}
}
/// Checks a knot vector for `n + 1` points of degree `p`; returns its domain.
fn knots_ok(knots: &[f64], n_pts: usize, p: usize, what: &str) -> Result<[f64; 2], FfiError> {
if !(1..=MAX_DEGREE).contains(&p) || n_pts < p + 1 || n_pts > MAX_POINTS {
return Err(bad(format!("{what}: degree {p} with {n_pts} points")));
}
if knots.len() != n_pts + p + 1 {
return Err(bad(format!("{what}: {} knots", knots.len())));
}
finite(knots, what)?;
if knots.windows(2).any(|w| w.get(1) < w.first()) {
return Err(bad(format!("{what}: decreasing knots")));
}
let lo = knots.get(p).copied().unwrap_or(0.0);
let hi = knots.get(n_pts).copied().unwrap_or(0.0);
if hi <= lo {
return Err(bad(format!("{what}: empty domain")));
}
Ok([lo, hi])
}
fn weights_ok(pts: &[[f64; 4]], what: &str) -> Result<(), FfiError> {
for c in pts {
finite(c, what)?;
// `NL_NOW` (-DBL_MAX) marks a non-rational point
if c[3] <= 0.0 && c[3] != NOW {
return Err(bad(format!("{what}: non-positive weight")));
}
}
Ok(())
}
/// Checks a NURB curve; returns its domain.
fn nurb_ok(c: &Nurb, what: &str) -> Result<[f64; 2], FfiError> {
let d = knots_ok(&c.knots, c.cpts.len(), c.p, what)?;
weights_ok(&c.cpts, what)?;
Ok(d)
}
fn push_nurb(o: &mut Vec<f64>, c: &Nurb, dim: usize) {
let n = c.cpts.len().saturating_sub(1);
let m = c.knots.len().saturating_sub(1);
o.extend([n as f64, c.p as f64, m as f64]);
for p in &c.cpts {
if dim == 3 {
o.extend_from_slice(p);
} else {
o.extend([p[0], p[1], p[3]]);
}
}
o.extend_from_slice(&c.knots);
}
fn push_circle(o: &mut Vec<f64>, c: &[f64; 3], x: &[f64; 3], y: &[f64; 3], d: &[f64; 2], r: f64) {
o.extend_from_slice(c);
o.extend_from_slice(x);
o.extend_from_slice(y);
o.extend([d[0], d[1], r]);
}
fn push_surface(o: &mut Vec<f64>, s: &Surf) -> Result<(), FfiError> {
match s {
Surf::Plane {
o: org,
x,
y,
scale,
domain,
} => {
for v in [&org[..], x, y, scale, domain] {
finite(v, "plane")?;
}
o.push(0.0);
o.extend_from_slice(org);
o.extend_from_slice(x);
o.extend_from_slice(y);
o.extend_from_slice(scale);
o.extend_from_slice(domain);
}
Surf::BSpline { net, p, q, u, v } => {
let rows = net.len();
let cols = net.first().map_or(0, Vec::len);
if net.iter().any(|r| r.len() != cols) {
return Err(bad("surface: ragged net"));
}
knots_ok(u, rows, *p, "surface u")?;
knots_ok(v, cols, *q, "surface v")?;
for r in net {
weights_ok(r, "surface")?;
}
o.push(1.0);
o.extend([
(rows - 1) as f64,
(cols - 1) as f64,
*p as f64,
*q as f64,
(u.len() - 1) as f64,
(v.len() - 1) as f64,
]);
for r in net {
for c in r {
o.extend_from_slice(c);
}
}
o.extend_from_slice(u);
o.extend_from_slice(v);
}
Surf::Revolution {
gen,
point,
axis,
swap,
} => {
o.push(2.0);
match gen {
Gen::Line { a, b } => {
finite(a, "generator")?;
finite(b, "generator")?;
o.push(0.0);
o.extend_from_slice(a);
o.extend_from_slice(b);
}
Gen::Circle { c, x, y, d, r } => {
for v in [&c[..], x, y, d, &[*r]] {
finite(v, "generator")?;
}
o.push(1.0);
push_circle(o, c, x, y, d, *r);
}
}
finite(point, "axis point")?;
finite(axis, "axis")?;
o.extend_from_slice(point);
o.extend_from_slice(axis);
o.push(if *swap { 1.0 } else { 0.0 });
}
}
Ok(())
}
fn push_curve(o: &mut Vec<f64>, c: &Curve3) -> Result<(), FfiError> {
match c {
Curve3::Line { a, b } => {
finite(a, "line")?;
finite(b, "line")?;
o.push(0.0);
o.extend_from_slice(a);
o.extend_from_slice(b);
}
Curve3::Circle { c, x, y, d, r } => {
for v in [&c[..], x, y, d, &[*r]] {
finite(v, "circle")?;
}
o.push(1.0);
push_circle(o, c, x, y, d, *r);
}
Curve3::BSpline(n) => {
nurb_ok(n, "edge curve")?;
o.push(2.0);
push_nurb(o, n, 3);
}
}
Ok(())
}
/// The flat shim input of `case` (validated).
fn flatten(case: &Case) -> Result<Vec<f64>, FfiError> {
finite(&[case.face_tol, case.tol3d, case.uv[0], case.uv[1]], "case")?;
if case.edges.len() > MAX_EDGES || case.edgeuses.len() > MAX_EDGES {
return Err(bad("too many edges"));
}
if case.vertex_loop != case.edgeuses.is_empty() {
return Err(bad("a vertex loop has no edge uses, an edge loop has some"));
}
let mut uses = vec![0usize; case.edges.len()];
for (i, eu) in case.edgeuses.iter().enumerate() {
let edge = case
.edges
.get(eu.edge)
.ok_or_else(|| bad(format!("edge use {i}: edge {}", eu.edge)))?;
if let Some(u) = uses.get_mut(eu.edge) {
*u += 1;
if *u > 2 {
return Err(bad(format!("edge {} used more than twice", eu.edge)));
}
}
let [lo, hi] = nurb_ok(&eu.trim, "trim")?;
let [a, b] = edge.interval;
finite(&[a, b, edge.tol], "edge")?;
if a > b || a < lo || b > hi {
return Err(bad(format!("edge use {i}: interval outside its trim curve")));
}
if i == 0 && (b - a).abs() <= EFF_ZERO {
return Err(bad("first edge use: degenerate interval"));
}
}
let mut o = vec![
case.face_tol,
if case.vertex_loop { 1.0 } else { 0.0 },
case.uv[0],
case.uv[1],
case.tol3d,
];
push_surface(&mut o, &case.surface)?;
o.push(case.edges.len() as f64);
for e in &case.edges {
push_curve(&mut o, &e.curve)?;
finite(&e.interval, "edge interval")?;
o.extend([e.interval[0], e.interval[1], e.tol]);
}
o.push(case.edgeuses.len() as f64);
for eu in &case.edgeuses {
o.extend([eu.edge as f64, if eu.same { 1.0 } else { 2.0 }]);
push_nurb(&mut o, &eu.trim, 2);
}
Ok(o)
}
/// Builds the face of `case` and calls upstream `SmLoop::ContainsUVPoint` on its loop.
pub fn contains(case: &Case) -> Result<Answer, FfiError> {
let input = flatten(case)?;
let n_in = i64::try_from(input.len()).map_err(|_| bad("input too long"))?;
let mut out = [0.0f64; 5];
// SAFETY: `input` is valid for `n_in` reads and laid out as documented (validated
// above); `out` is valid for the 5 writes the shim makes.
let st = unsafe { sys::sc_query_loop_contains(input.as_ptr(), n_in, out.as_mut_ptr()) };
crate::check(st)?;
let [build, cls, on, param, orient] = out;
Ok(Answer {
build: build as i32,
containment: cls as i32,
on_edge: on as i64,
edge_param: param,
orientation: orient as i32,
})
}
@@ -0,0 +1,553 @@
//! FFI wrappers for unit `topology.edgeuse_p1`.
//!
//! Shim: `harness/cpp/shim/topology_edgeuse_p1.cpp` (`sc_topology_edgeuse_p1_eval`).
//! Upstream: `source/SMLib/src/SmEdgeuse.cpp`, `source/SMLib/src/SmFaceuse.cpp`.
//!
//! B-rep ops ([`Op::is_brep`]) start with a B-rep block (the shim builds a fresh
//! `SmBrep` per call and deletes it after):
//!
//! | block | B-rep |
//! |-----------------------------------------------|-------------------------------------------|
//! | `0 rebuild validate_only init_gaps` | `ReadFromFile(path, SM_ASCII, rebuild)` |
//! | `1 curve t0 t1 validate_only init_gaps` | `CreateWireEdgeFromCurve(curve, [t0, t1])`|
//! | `2 surface u0 v0 u1 v1 validate_only init_gaps` | `CreateFaceFromSurface(surface, dom)` |
//! | `3 validate_only init_gaps` | the wire network of `prog_test` `my_test_topology` |
//!
//! A surface block is `0 oob form n m p q r s cpts(x y z w) u v` (`SmBSplineSurface`) or
//! `1 O[3] N[3]` (`SmPlane(O, N)`); a curve block `dim n p m cpts(x y z w) knots`
//! (`SmBSplineCurve`). Indices refer to the export's traversal order. Every B-rep op's
//! output starts with `0`, or with `-1 status` when the B-rep could not be built
//! (nothing else follows). `-2` in an output is "upstream would dereference null"
//! (the shim did not call it).
//!
//! | [`Op`] | parameters after the block | outputs |
//! |-------------|-----------------------------------------|---------|
//! | `Export` | | the graph (see [`counts`] and the difftest runner) |
//! | `EuQuery` | `eu vertex` | `edge orient cw ccw has_ef has_ccw ef_init loop shell_eu vu lu fu face shell brep tol mate radial strut has_uv uv_owner has_fail fail_owner own_uv corner type kind_of*9` |
//! | `Connected` | `eu type index` | `connected` |
//! | `FuQuery` | `fu tgt_only has_loops n loops*n` | `shell region face mate orient type kind_of*9`, then lists: loop uses, edge uses, neighbours, neighbours skipping lamina edges |
//! | `Collect` | `fu has_threshold threshold` | `status` list |
//! | `UvLoopGap` | `eu` | `gap` |
//! | `SetUv` | `eu kind [curve] dmax delete has_fail fail_status` | `0` or `-2`, then the edge use's and its mate's state, the edge tolerance, the argument record's B-rep flag |
//! | `Gaps` | `eu which flag want_out` | per `which` (`0` `GetMaxEdgeFaceGap`, `1` `GetEdgeCCWEdgeGap`, `2` `CalcMaxEdgeFaceGap`, `3` `CalcEdgeCCWEdgeGap`) |
//! | `SetOrient` | `eu orientation` | `orient mate radial` |
//!
//! Geometry ops: `StepOff` (`surface dist P[3] N[3] u v jac` -> `status found F0 F1
//! J00 J01 J10 J11`), `DistDev` (`surface P[3] u v value obj want_dev` -> `dist dev`;
//! `obj` 0 null, 1 the surface, 2 a line), `Seam` (`n curve*n a b called` -> `seam`).
//!
//! [`eval`] accepts only what upstream handles without undefined behaviour: existing
//! `.smb` files, well-formed clamped B-splines, planes with a non-zero normal,
//! parameters within 100 domain lengths of a surface's domain, indices inside the
//! B-rep's export (it builds the B-rep once more to count), finite inputs.
use std::path::Path;
use crate::{call_with_buffer, FfiError};
/// Largest control point index.
pub const MAX_INDEX: usize = 32;
/// Largest degree.
pub const MAX_DEGREE: usize = 5;
/// Largest coordinate magnitude.
pub const MAX_COORD: f64 = 1.0e300;
/// Largest number of curves for `Seam` and of target loops for `FuQuery`.
pub const MAX_LIST: usize = 64;
const NOW: f64 = -f64::MAX;
const NOZ: f64 = f64::MAX;
/// Shim operations.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum Op {
Export = 0,
EuQuery = 1,
Connected = 2,
FuQuery = 3,
Collect = 4,
UvLoopGap = 5,
SetUv = 6,
Gaps = 7,
StepOff = 8,
DistDev = 9,
Seam = 10,
SetOrient = 11,
}
impl Op {
/// The shim's op code.
#[must_use]
pub fn code(self) -> i32 {
self as i32
}
/// True for the ops starting with a B-rep block.
#[must_use]
pub fn is_brep(self) -> bool {
!matches!(self, Op::StepOff | Op::DistDev | Op::Seam)
}
}
mod sys {
extern "C" {
pub fn sc_topology_edgeuse_p1_eval(
op: i32,
path: *const std::ffi::c_char,
input: *const f64,
n_in: i64,
out: *mut f64,
cap: i64,
out_len: *mut i64,
) -> i32;
}
}
fn bad(msg: impl Into<String>) -> FfiError {
FfiError::InvalidArgument(msg.into())
}
/// Sequential reader over the input.
struct Reader<'a> {
input: &'a [f64],
at: usize,
}
impl Reader<'_> {
fn num(&mut self) -> Result<f64, FfiError> {
let v = self
.input
.get(self.at)
.copied()
.ok_or_else(|| bad("input too short"))?;
self.at += 1;
Ok(v)
}
fn finite(&mut self) -> Result<f64, FfiError> {
let v = self.num()?;
if !v.is_finite() || v.abs() > MAX_COORD {
return Err(bad("non-finite or huge value"));
}
Ok(v)
}
fn finites(&mut self, k: usize) -> Result<Vec<f64>, FfiError> {
(0..k).map(|_| self.finite()).collect()
}
fn vec3(&mut self) -> Result<[f64; 3], FfiError> {
Ok([self.finite()?, self.finite()?, self.finite()?])
}
fn nonzero3(&mut self) -> Result<[f64; 3], FfiError> {
let v = self.vec3()?;
if v.iter().map(|c| c * c).sum::<f64>() < 1e-12 {
return Err(bad("zero vector"));
}
Ok(v)
}
fn index(&mut self, max: usize) -> Result<usize, FfiError> {
let v = self.num()?;
if v.fract() != 0.0 || v < 0.0 || v > max as f64 {
return Err(bad(format!("bad index/count {v} (max {max})")));
}
Ok(v as usize)
}
/// An index below `count`.
fn below(&mut self, count: usize) -> Result<usize, FfiError> {
let i = self.index(usize::MAX >> 12)?;
if i >= count {
return Err(bad(format!("index {i} out of range ({count})")));
}
Ok(i)
}
/// An index below `count` or `-1`.
fn below_or_none(&mut self, count: usize) -> Result<(), FfiError> {
let v = self.num()?;
if v == -1.0 {
return Ok(());
}
self.at -= 1;
self.below(count).map(|_| ())
}
fn flag(&mut self) -> Result<bool, FfiError> {
Ok(self.index(1)? == 1)
}
fn done(&self) -> Result<(), FfiError> {
if self.at != self.input.len() {
return Err(bad("trailing input"));
}
Ok(())
}
}
/// Checks a clamped knot vector of degree `p`.
fn check_knots(knots: &[f64], p: usize, what: &str) -> Result<(f64, f64), FfiError> {
let m = knots
.len()
.checked_sub(1)
.ok_or_else(|| bad(format!("{what}: no knots")))?;
if knots.windows(2).any(|w| w.first() > w.last()) {
return Err(bad(format!("{what}: knots not non-decreasing")));
}
let (Some(&a), Some(&b)) = (knots.first(), knots.last()) else {
return Err(bad(format!("{what}: empty knots")));
};
let short = || bad(format!("{what}: knots too short"));
if m < 2 * p + 1 {
return Err(short());
}
let head = knots.get(..=p).ok_or_else(short)?;
let tail = knots.get(m - p..).ok_or_else(short)?;
let inner = knots.get(p + 1..m - p).ok_or_else(short)?;
if a >= b || head.iter().any(|&k| k != a) || tail.iter().any(|&k| k != b) {
return Err(bad(format!("{what}: knots not clamped")));
}
if b - a < 1e-6 * a.abs().max(b.abs()).max(1.0) {
return Err(bad(format!("{what}: parameter range too small")));
}
if inner.iter().any(|&k| k == a || k == b) {
return Err(bad(format!("{what}: end multiplicity above degree + 1")));
}
let mut run = 1;
for w in inner.windows(2) {
run = if w.first() == w.last() { run + 1 } else { 1 };
if run > p {
return Err(bad(format!("{what}: interior multiplicity above the degree")));
}
}
Ok((a, b))
}
/// Checks `k` control points sharing their rational marker (`z = NOZ` allowed for
/// curves when `planar`).
fn check_points(rd: &mut Reader<'_>, k: usize, planar: bool, what: &str) -> Result<(), FfiError> {
let mut w0 = None;
for _ in 0..k {
let x = rd.num()?;
let y = rd.num()?;
let z = rd.num()?;
let w = rd.num()?;
if z == NOZ && !planar {
return Err(bad(format!("{what}: 2D data")));
}
let rational = w != NOW;
if *w0.get_or_insert(rational) != rational {
return Err(bad(format!("{what}: mixed rationality")));
}
if rational && !(1e-3..=1e3).contains(&w) {
return Err(bad(format!("{what}: weight out of range")));
}
let z = if z == NOZ { 0.0 } else { z };
if [x, y, z].iter().any(|c| !c.is_finite() || c.abs() > MAX_COORD) {
return Err(bad(format!("{what}: point out of range")));
}
}
Ok(())
}
/// The parameter domain `[u0, v0, u1, v1]` of a surface block, `None` for a plane.
type Domain = Option<[f64; 4]>;
/// A surface block; returns its domain.
fn surface(rd: &mut Reader<'_>) -> Result<Domain, FfiError> {
let kind = rd.index(1)?;
if kind == 1 {
rd.vec3()?;
rd.nonzero3()?;
return Ok(None);
}
rd.flag()?;
rd.index(11)?;
let n = rd.index(MAX_INDEX)?;
let m = rd.index(MAX_INDEX)?;
let p = rd.index(MAX_DEGREE)?;
let q = rd.index(MAX_DEGREE)?;
let r = rd.index(MAX_INDEX + MAX_DEGREE + 1)?;
let s = rd.index(MAX_INDEX + MAX_DEGREE + 1)?;
if p == 0 || p > n || q == 0 || q > m || r != n + p + 1 || s != m + q + 1 {
return Err(bad(
"surface: need 1 <= p <= n, 1 <= q <= m, r = n+p+1, s = m+q+1",
));
}
check_points(rd, (n + 1) * (m + 1), false, "surface")?;
let u = rd.finites(r + 1)?;
let v = rd.finites(s + 1)?;
let (u0, u1) = check_knots(&u, p, "surface u")?;
let (v0, v1) = check_knots(&v, q, "surface v")?;
Ok(Some([u0, v0, u1, v1]))
}
/// A B-spline curve block; returns its parameter range.
fn curve(rd: &mut Reader<'_>) -> Result<(f64, f64), FfiError> {
let dim = rd.index(3)?;
if dim < 2 {
return Err(bad("curve: dimension must be 2 or 3"));
}
let n = rd.index(MAX_INDEX)?;
let p = rd.index(MAX_DEGREE)?;
let m = rd.index(MAX_INDEX + MAX_DEGREE + 1)?;
if p == 0 || p > n || m != n + p + 1 {
return Err(bad("curve: need 1 <= p <= n and m = n + p + 1"));
}
check_points(rd, n + 1, dim == 2, "curve")?;
let knots = rd.finites(m + 1)?;
check_knots(&knots, p, "curve")
}
/// A `uv` point within 100 domain lengths of `dom` (anywhere bounded for a plane).
fn uv(rd: &mut Reader<'_>, dom: Domain) -> Result<(), FfiError> {
let u = rd.finite()?;
let v = rd.finite()?;
if let Some([u0, v0, u1, v1]) = dom {
let (du, dv) = (u1 - u0, v1 - v0);
if u < u0 - 100.0 * du || u > u1 + 100.0 * du || v < v0 - 100.0 * dv || v > v1 + 100.0 * dv {
return Err(bad("uv too far outside the domain"));
}
} else if u.abs() > 1e6 || v.abs() > 1e6 {
return Err(bad("plane uv too large"));
}
Ok(())
}
/// A B-rep block.
fn brep_block(rd: &mut Reader<'_>, path: &str) -> Result<(), FfiError> {
match rd.index(3)? {
0 => {
let p = Path::new(path);
if p.extension().and_then(|e| e.to_str()) != Some("smb") || !p.is_file() {
return Err(bad("B-rep file: need an existing .smb file"));
}
rd.flag()?;
}
1 => {
let (a, b) = curve(rd)?;
let t0 = rd.finite()?;
let t1 = rd.finite()?;
if !(a <= t0 && t0 < t1 && t1 <= b) {
return Err(bad("wire edge interval outside the curve's"));
}
}
2 => {
let dom = surface(rd)?;
let d = rd.finites(4)?;
let (Some(&a), Some(&b2), Some(&c), Some(&e)) = (d.first(), d.get(1), d.get(2), d.get(3))
else {
return Err(bad("domain too short"));
};
if a >= c || b2 >= e {
return Err(bad("face domain not ordered"));
}
match dom {
Some([u0, v0, u1, v1]) => {
if a < u0 || c > u1 || b2 < v0 || e > v1 {
return Err(bad("face domain outside the surface's"));
}
}
None => {
if d.iter().any(|x| x.abs() > 1e4) {
return Err(bad("plane face domain too large"));
}
}
}
}
_ => {}
}
rd.flag()?;
rd.flag()?;
Ok(())
}
/// The entity counts of an export, in the shim's order: regions, shells, face uses,
/// faces, loop uses, loops, edge uses, edges, vertex uses, vertices. `None` when the
/// export is a build failure.
#[must_use]
pub fn counts(export: &[f64]) -> Option<[usize; 10]> {
if export.first() != Some(&0.0) {
return None;
}
let mut c = [0usize; 10];
for (slot, v) in c.iter_mut().zip(export.get(2..12)?) {
*slot = *v as usize;
}
Some(c)
}
/// `SmTopoTypes.h` codes `IsConnectedTo` dispatches on, with the export slot of their
/// kind.
const TARGET_TYPES: [(i64, usize); 10] = [
(16012, 0), // SmRegion_TYPE
(16013, 1), // SmShell_TYPE
(16022, 2), // SmFaceuse_TYPE
(16002, 3), // SmFace_TYPE
(16004, 4), // SmLoopuse_TYPE
(16003, 5), // SmLoop_TYPE
(16005, 6), // SmEdgeuse_TYPE
(16016, 7), // SmEdge_TYPE
(16006, 8), // SmVertexuse_TYPE
(16021, 9), // SmVertex_TYPE
];
/// Checks the arguments after the B-rep block against the B-rep's counts.
fn brep_args(op: Op, rd: &mut Reader<'_>, c: [usize; 10]) -> Result<(), FfiError> {
let [_, _, n_fu, _, _, n_loop, n_eu, _, _, n_v] = c;
match op {
Op::Export => {}
Op::EuQuery => {
rd.below(n_eu)?;
rd.below_or_none(n_v)?;
}
Op::Connected => {
rd.below(n_eu)?;
let t = rd.num()?;
let i = rd.num()?;
if i.fract() != 0.0 || i < -1.0 {
return Err(bad("bad target index"));
}
if let Some(&(_, slot)) = TARGET_TYPES.iter().find(|(code, _)| *code as f64 == t) {
let n = c.get(slot).copied().unwrap_or(0);
if i >= 0.0 && i as usize >= n {
return Err(bad("target index out of range"));
}
} else if t.fract() != 0.0 || !(0.0..=1.0e6).contains(&t) {
return Err(bad("bad target type"));
}
}
Op::FuQuery => {
rd.below(n_fu)?;
rd.flag()?;
rd.flag()?;
let n = rd.index(MAX_LIST)?;
for _ in 0..n {
rd.below(n_loop)?;
}
}
Op::Collect => {
rd.below(n_fu)?;
rd.flag()?;
rd.finite()?;
}
Op::UvLoopGap => {
rd.below(n_eu)?;
}
Op::SetUv => {
rd.below(n_eu)?;
let kind = rd.index(4)?;
if kind == 2 || kind == 4 {
curve(rd)?;
}
rd.finite()?;
rd.flag()?;
rd.flag()?;
let s = rd.num()?;
if s.fract() != 0.0 || s.abs() > 1.0e6 {
return Err(bad("bad fail status"));
}
}
Op::Gaps => {
rd.below(n_eu)?;
rd.index(3)?;
rd.flag()?;
rd.flag()?;
}
Op::SetOrient => {
rd.below(n_eu)?;
rd.index(2)?;
}
Op::StepOff | Op::DistDev | Op::Seam => return Err(bad("not a B-rep op")),
}
Ok(())
}
/// Checks a geometry op's input.
fn geom_args(op: Op, rd: &mut Reader<'_>) -> Result<(), FfiError> {
match op {
Op::StepOff => {
let dom = surface(rd)?;
rd.finite()?;
rd.vec3()?;
rd.vec3()?;
uv(rd, dom)?;
rd.flag()?;
}
Op::DistDev => {
let dom = surface(rd)?;
rd.vec3()?;
uv(rd, dom)?;
rd.finite()?;
rd.index(2)?;
rd.flag()?;
}
Op::Seam => {
let n = rd.index(MAX_LIST)?;
for _ in 0..n {
curve(rd)?;
}
let a = rd.finite()?;
let b = rd.finite()?;
if a > b {
return Err(bad("edge domain not ordered"));
}
rd.flag()?;
}
_ => return Err(bad("not a geometry op")),
}
Ok(())
}
fn raw(op: Op, path: &str, input: &[f64]) -> Result<Vec<f64>, FfiError> {
let cpath = std::ffi::CString::new(path).map_err(|_| bad("path has a NUL byte"))?;
call_with_buffer(4096, |buf, len| {
// SAFETY: cpath is a valid NUL-terminated string; input is valid for
// input.len() reads and holds what the shim reads for `op` (validated by the
// callers); buf is valid for buf.len() writes and the shim honours the
// capacity; len is valid.
unsafe {
sys::sc_topology_edgeuse_p1_eval(
op.code(),
cpath.as_ptr(),
input.as_ptr(),
input.len() as i64,
buf.as_mut_ptr(),
buf.len() as i64,
&raw mut *len,
)
}
})
}
/// Runs `op` on `input` (see the module docs); `path` is the `.smb` file of a file
/// B-rep block (ignored otherwise).
///
/// # Errors
///
/// [`FfiError::InvalidArgument`] for a malformed input; [`FfiError::Status`] for a shim
/// failure.
pub fn eval(op: Op, path: &str, input: &[f64]) -> Result<Vec<f64>, FfiError> {
let mut rd = Reader { input, at: 0 };
if !op.is_brep() {
geom_args(op, &mut rd)?;
rd.done()?;
return raw(op, path, input);
}
brep_block(&mut rd, path)?;
let block = input.get(..rd.at).ok_or_else(|| bad("input too short"))?;
let export = raw(Op::Export, path, block)?;
if op == Op::Export {
rd.done()?;
return Ok(export);
}
let Some(c) = counts(&export) else {
// the B-rep could not be built: the op reports the same failure
return Ok(export);
};
brep_args(op, &mut rd, c)?;
rd.done()?;
raw(op, path, input)
}
+24
View File
@@ -10,6 +10,7 @@
//! | [`Line`] | `SmLine` |
//! | [`Line::default`] | `SmLine()` |
//! | [`Line::from_points`] | `SmLine(StartPoint, EndPoint, lDimension, cpContext)` |
//! | [`Line::from_parts`] | the members as they are (an importer's constructor) |
//! | [`Line::infinite`] | `SmLine(LinePoint, NonUnitVector, lDim, bMakeNurb, cpContext, bInsideOut)` |
//! | [`Line::bounded`], [`BoundedLineOptions`] | `SmLine(LinePoint, UnitVector, AnalDomain, dScale, lDim, cpContext, pOptNurb, bInsideOut)` |
//! | [`Line::create_line_segment`] | `CreateLineSegment` |
@@ -322,6 +323,29 @@ impl Line {
}
}
/// A line from its members exactly as given (`m_vLinePoint`, the unit
/// `m_vLineVector`, `m_vAnalDomain`, `m_dScale`, `m_bInsideOut`) and its B-spline
/// part, nothing normalised or rebuilt: for importing a line another kernel made
/// (the difftest import of an upstream B-rep's curves). No upstream counterpart.
#[must_use]
pub fn from_parts(
base: BSplineCurve,
point: Point3,
vector: Vec3,
anal_domain: Extent1d,
scale: f64,
inside_out: bool,
) -> Self {
Self {
base,
point,
vector,
anal_domain,
scale,
inside_out,
}
}
/// The infinite line `point + s * vector` (domain `[-SM_INFINITE_PARAMETER,
/// SM_INFINITE_PARAMETER]`, scale `|vector|`) with its NURB (`SmLine(LinePoint,
/// NonUnitVector, lDim, bMakeNurb, cpContext, bInsideOut)`; `bMakeNurb` is not
+19 -1
View File
@@ -25,7 +25,10 @@
//! of the failing function, e.g. `"geom::vector3d::Vec3::unitize"`) so that errors surfaced to
//! callers stay actionable without a backtrace.
use crate::topology::{BrepId, EdgeId, FaceId, LoopId, ShellId, VertexId};
use crate::topology::{
BrepId, EdgeId, EdgeuseId, FaceId, FaceuseId, LoopId, LoopuseId, RegionId, ShellId, VertexId,
VertexuseId,
};
/// Shorthand for `core::result::Result<T, SolidError>`.
pub type Result<T, E = SolidError> = core::result::Result<T, E>;
@@ -61,6 +64,16 @@ pub enum EntityRef {
Edge(EdgeId),
/// A vertex.
Vertex(VertexId),
/// A region.
Region(RegionId),
/// A face use.
Faceuse(FaceuseId),
/// A loop use.
Loopuse(LoopuseId),
/// An edge use.
Edgeuse(EdgeuseId),
/// A vertex use.
Vertexuse(VertexuseId),
}
impl core::fmt::Display for EntityRef {
@@ -72,6 +85,11 @@ impl core::fmt::Display for EntityRef {
Self::Loop(id) => write!(f, "loop {}", id.index()),
Self::Edge(id) => write!(f, "edge {}", id.index()),
Self::Vertex(id) => write!(f, "vertex {}", id.index()),
Self::Region(id) => write!(f, "region {}", id.index()),
Self::Faceuse(id) => write!(f, "faceuse {}", id.index()),
Self::Loopuse(id) => write!(f, "loopuse {}", id.index()),
Self::Edgeuse(id) => write!(f, "edgeuse {}", id.index()),
Self::Vertexuse(id) => write!(f, "vertexuse {}", id.index()),
}
}
}
+2
View File
@@ -3,3 +3,5 @@
//!
//! Upstream: `SmHealData`, `SmHealProbArray`, `SmStitch`, `SmGap` in `source/SMLib`, and
//! `SM_API/SmApiHeal`.
pub mod gap;
+194
View File
@@ -0,0 +1,194 @@
//! Topology gap records (`SmGap` and its derived classes): the cached 3D gaps between
//! an edge use and its face, its counter-clockwise neighbour, and a vertex use and its
//! face. Only the data part is here so far.
//!
//! Upstream: `source/SMLib/inc/SmGap.h`.
//!
//! | Rust | Upstream |
//! |-------------------------------|--------------------------------------------------|
//! | [`EdgeFaceGap`] | `SmEdgeFaceGap` (members, `ReSet`, `IsInit`, `SetEdgeuse`) |
//! | [`EdgeFaceGap::new`] | `SmEdgeFaceGap()` (default arguments) |
//! | [`EdgeEdgeGap`] | `SmEdgeEdgeGap` (members, `ReSet`, `IsInit`) |
//! | [`EdgeEdgeGap::new`] | `SmEdgeEdgeGap()` (default arguments) |
//! | [`VertexFaceGap`] | `SmVertexFaceGap` (members, `ReSet`, `IsInit`) |
//! | [`VertexFaceGap::new`] | `SmVertexFaceGap()` (default arguments) |
//!
//! Deviations: CO-PORTED by unit `topology.edgeuse_p1` (the records its edge and vertex
//! uses cache), owned by unit `heal.gap`. The topology pointers are ids. The
//! constructors take no arguments: with upstream's defaults they reach the reset state
//! (gap `-SM_UNDEF_DOUBLE`, nothing set); setting a gap (`Set`, `SetEdgeEdgeGap`,
//! which evaluate the gap through `CacheGap`) belongs to `heal.gap`.
use crate::geom::vector2d::Point2;
use crate::math::UNDEF_DOUBLE;
use crate::topology::{EdgeuseId, VertexuseId};
/// A gap between an edge's curve and its face's surface (`SmEdgeFaceGap`).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct EdgeFaceGap {
/// `m_dGap3d`: the gap (`-SM_UNDEF_DOUBLE` when undefined).
pub gap_3d: f64,
/// `m_pEdgeuse`: the edge use connecting the edge to the face (`None`: not set).
pub edgeuse: Option<EdgeuseId>,
/// `m_dEdgeT`: the edge curve parameter bounding the gap.
pub edge_t: f64,
/// `m_sFaceUV`: the surface parameter bounding the gap.
pub face_uv: Point2,
}
impl Default for EdgeFaceGap {
fn default() -> Self {
Self::new()
}
}
impl EdgeFaceGap {
/// `SmEdgeFaceGap()`: the reset state.
#[must_use]
pub const fn new() -> Self {
Self {
gap_3d: -UNDEF_DOUBLE,
edgeuse: None,
edge_t: UNDEF_DOUBLE,
face_uv: Point2::UNDEF,
}
}
/// `IsInit`: an edge use is set.
#[must_use]
pub const fn is_init(&self) -> bool {
self.edgeuse.is_some()
}
/// `ReSet`: back to the uninitialised state.
pub fn reset(&mut self) {
self.gap_3d = -UNDEF_DOUBLE;
self.edgeuse = None;
self.edge_t = UNDEF_DOUBLE;
self.face_uv.set_uninitialized();
}
/// `SetEdgeuse`: names a different edge use (a mate sharing the gap).
pub fn set_edgeuse(&mut self, edgeuse: Option<EdgeuseId>) {
self.edgeuse = edgeuse;
}
}
/// A gap between an edge use's end and the next edge use's start
/// (`SmEdgeEdgeGap`).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct EdgeEdgeGap {
/// `m_dGap3d`: the gap (`-SM_UNDEF_DOUBLE` when undefined).
pub gap_3d: f64,
/// `m_pEdgeuse0`.
pub edgeuse0: Option<EdgeuseId>,
/// `m_pEdgeuse1`.
pub edgeuse1: Option<EdgeuseId>,
/// `m_dEdgeT0`: the first edge's curve parameter bounding the gap.
pub edge_t0: f64,
/// `m_dEdgeT1`: the second edge's curve parameter bounding the gap.
pub edge_t1: f64,
}
impl Default for EdgeEdgeGap {
fn default() -> Self {
Self::new()
}
}
impl EdgeEdgeGap {
/// `SmEdgeEdgeGap()`: the reset state.
#[must_use]
pub const fn new() -> Self {
Self {
gap_3d: -UNDEF_DOUBLE,
edgeuse0: None,
edgeuse1: None,
edge_t0: UNDEF_DOUBLE,
edge_t1: UNDEF_DOUBLE,
}
}
/// `IsInit`: both edge uses are set.
#[must_use]
pub const fn is_init(&self) -> bool {
self.edgeuse0.is_some() && self.edgeuse1.is_some()
}
/// `ReSet`: back to the uninitialised state.
pub fn reset(&mut self) {
*self = Self::new();
}
}
/// A gap between a vertex and the surface of a face it bounds (`SmVertexFaceGap`).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct VertexFaceGap {
/// `m_dGap3d`: the gap (`-SM_UNDEF_DOUBLE` when undefined).
pub gap_3d: f64,
/// `m_pVertexuse`: the vertex use connecting the vertex to the face.
pub vertexuse: Option<VertexuseId>,
/// `m_sFaceUV`: the surface parameter bounding the gap.
pub face_uv: Point2,
}
impl Default for VertexFaceGap {
fn default() -> Self {
Self::new()
}
}
impl VertexFaceGap {
/// `SmVertexFaceGap()`: the reset state.
#[must_use]
pub const fn new() -> Self {
Self {
gap_3d: -UNDEF_DOUBLE,
vertexuse: None,
face_uv: Point2::UNDEF,
}
}
/// `IsInit`: a vertex use is set.
#[must_use]
pub const fn is_init(&self) -> bool {
self.vertexuse.is_some()
}
/// `ReSet`: back to the uninitialised state.
pub fn reset(&mut self) {
self.gap_3d = -UNDEF_DOUBLE;
self.vertexuse = None;
self.face_uv.set_uninitialized();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reset_states() {
let mut g = EdgeFaceGap::new();
assert!(!g.is_init());
g.set_edgeuse(Some(EdgeuseId::from_index(3)));
g.gap_3d = 1.0;
assert!(g.is_init());
g.reset();
assert_eq!(g, EdgeFaceGap::default());
let mut e = EdgeEdgeGap::new();
e.edgeuse0 = Some(EdgeuseId::from_index(0));
assert!(!e.is_init());
e.edgeuse1 = Some(EdgeuseId::from_index(1));
assert!(e.is_init());
e.reset();
assert!(!e.is_init());
let mut v = VertexFaceGap::new();
v.vertexuse = Some(VertexuseId::from_index(2));
assert!(v.is_init());
v.reset();
assert_eq!(v, VertexFaceGap::default());
}
}
@@ -1487,6 +1487,10 @@ fn trace_curve(
_start: TsectId,
_curves_3d: &mut Vec<Curve>,
) -> Result<()> {
// (SurfaceIntersector::trace_curve, part 4, is the basic intersector's: its
// ComputePointValues calls the basic ComputeSingularityPoint, not this class's
// override, so it cannot stand in for the advanced intersector's TraceCurve until
// the intersector parts dispatch their virtual calls)
// UNPORTED(intersect.surface_intersector_p4): SmSurfaceIntersector::TraceCurve
Err(SolidError::unported(
"SmSurfaceIntersector::TraceCurve",
@@ -9,5 +9,6 @@
pub mod p1;
pub mod p2;
pub mod p3;
pub mod p4;
pub use p1::{IntersectionPointType, SrfIndex, SurfaceIntersector, TsectId, TsectPnt};
File diff suppressed because it is too large Load Diff
+2
View File
@@ -3,3 +3,5 @@
//!
//! Upstream: `SmPointClass`, `SmRayTracer`, `SmExtent*`, `SmPseudoBox`,
//! `SmTopologyTraverser` in `source/SMLib`, and `SM_API/SmApiQueries`.
pub mod r#loop;
+897
View File
@@ -0,0 +1,897 @@
//! Classification of a parameter-space point against one boundary loop of a face
//! (`SmLoop::ContainsUVPoint`): a winding-number walk along the loop's UV trim curves
//! with on-boundary detection, plus the loop corner / surface side test it uses for
//! poles.
//!
//! Upstream: `source/SMLib/src/SmLoop.cpp`, `source/SMLib/inc/SmLoop.h`.
//!
//! | Rust | Upstream |
//! |------------------------------|------------------------------------------------|
//! | [`contains_uv_point`] | `SmLoop::ContainsUVPoint` |
//! | [`UvContainment`] | its return value and out-params |
//! | [`FaceLoop`], [`LoopEdgeuse`] | the `SmLoop` / `SmLoopuse` / `SmEdgeuse` / `SmEdge` / `SmFace` data it reads (see Deviations) |
//! | [`uv_corner_boundaries`] | `SmLoop::GetUVCornerBoundaries` (CO-PORTED from `topology.loopuse`) |
//! | [`UvCornerBoundaries`] | its return value and out-params |
//! | `calc_quad_num` (private) | `sm_CalcQuadNum` (file static, CO-PORTED from `topology.loopuse`) |
//!
//! Deviations:
//! - **Topology view.** The arena topology ([`crate::topology`]) does not bind
//! geometry yet (faces have no surface, edges no curve, there are no edge uses), so
//! the loop is passed as a borrowed [`FaceLoop`]: the owning face's surface and zone
//! tolerance (`SmFace::GetTolerance`, which is what `SmTol::GetZoneTol3d(pSurface)`
//! returns for a surface with a face under `SM_USE_OLDTOL`) and the edge uses of
//! the primary loopuse in `GetEdgeuses` order, each with its edge id, 3D curve,
//! interval, zone tolerance (`SmEdge::GetTolerance`), orientation and UV trim
//! curve. A face without surface is `None` (upstream: `SM_POC_UNKNOWN`). The edge
//! out-param is the [`EdgeId`] given in the view.
//! - **UV trim curves.** `SmEdgeuse::GetOrCreateUVTrimCurve` returns the existing
//! trim curve (of the edge use or its mate) as given in [`LoopEdgeuse::uv_trim_curve`];
//! when there is none it would build one (`SmEdgeuse::CreateUVTrimCurve`, unit
//! `topology.edgeuse_p3`), which returns [`SolidError::Unported`].
//! - **Poles.** `SmSurface::GetSingularities` (unit `surface.surface_p3`) is
//! [`SolidError::Unported`]; it is only reached when the point lies on a side of the
//! surface's natural domain and an edge use end of the loop does too.
//! - **Ignored statuses.** Upstream ignores the status of `EvaluatePoint` /
//! `Evaluate` of the surface and the trim curves and then reads the (uninitialised)
//! outputs; the port returns their error instead (no valid input reaches it).
//! `DropPoint` and `LocalPointSolve` failures are "no answer" (both reset their
//! success flag first upstream). [`SolidError::Unported`] from any callee is
//! returned.
//! - `dTol3d` is unused upstream and is kept as `_tol_3d`.
//! - **Loops.** Both walk loops keep upstream's bounds (1000 steps per edge use and
//! 1000 re-evaluations per step, then `SM_POC_UNKNOWN`); the `goto Got_dThisParam`
//! re-evaluations are an inner loop. No recursion.
use crate::curve::curve::p3::PointSolveOptions;
use crate::curve::curve::Curve;
use crate::error::{Result, SolidError};
use crate::geom::extent1d::Extent1d;
use crate::geom::extent2d::{Extent2d, SS_NONE};
use crate::geom::geom_utility::p1::line_point_distance;
use crate::geom::global_solver::SolverOperation;
use crate::geom::point_set::PointContainment;
use crate::geom::solution_array::Solution;
use crate::kernel::Kernel;
use crate::math::{Point2, Point3, Vec3, EFF_ZERO};
use crate::nurbs::b_spline_surface_n_lib::p2::OrientType;
use crate::surface::surface::p1::map_to_2d;
use crate::surface::surface::Surface;
use crate::topology::EdgeId;
/// Upper bound of the steps along one edge use and of the re-evaluations of one step
/// (upstream's infinite-loop guards).
const MAX_LOOP_COUNT: u32 = 1000;
/// One edge use of a loop as [`contains_uv_point`] reads it (`SmEdgeuse` with its
/// `SmEdge`).
#[derive(Debug, Clone, Copy)]
pub struct LoopEdgeuse<'a> {
/// The edge (`GetEdge`), reported back as the on-boundary edge.
pub edge: EdgeId,
/// The edge's 3D curve (`SmEdge::GetCurve`).
pub curve: &'a Curve,
/// The edge's parameter interval (`SmEdge::GetInterval`).
pub interval: Extent1d,
/// The edge's zone tolerance (`SmEdge::GetTolerance`, the stored `m_sZoneTol3d`).
pub tolerance: f64,
/// The edge use's orientation relative to its edge (`GetOrientation`).
pub orientation: OrientType,
/// The UV trim curve of the edge use or its mate (`GetUVTrimCurve`); `None` when
/// neither has one.
pub uv_trim_curve: Option<&'a Curve>,
}
/// The face a loop bounds, as [`contains_uv_point`] reads it.
#[derive(Debug, Clone, Copy)]
pub struct LoopFace<'a> {
/// The face's surface (`SmFace::GetSurface`).
pub surface: &'a Surface,
/// The face's zone tolerance (`SmFace::GetTolerance`).
pub tolerance: f64,
}
/// A face loop (`SmLoop`) as [`contains_uv_point`] reads it.
#[derive(Debug, Clone)]
pub struct FaceLoop<'a> {
/// The owning face with its surface (`GetFace`, `GetSurface`); `None` for a loop
/// without face or a face without surface.
pub face: Option<LoopFace<'a>>,
/// The edge uses of the primary loopuse in loop order (`m_pLU->GetEdgeuses`);
/// empty for a vertex loop.
pub edgeuses: Vec<LoopEdgeuse<'a>>,
}
/// The result of [`contains_uv_point`].
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct UvContainment {
/// `SM_POC_INSIDE`, `SM_POC_OUTSIDE`, `SM_POC_ON_BOUNDARY`, or `SM_POC_UNKNOWN`
/// on failure.
pub containment: PointContainment,
/// `rpOnEdge`: the edge the point lies on (only for `OnBoundary`).
pub on_edge: Option<EdgeId>,
/// `rdEdgeParam`: its edge parameter (only for `OnBoundary`; else `0`).
pub edge_param: f64,
/// `eOrientation`: [`OrientType::Same`] for a counter-clockwise loop around an
/// inside point, [`OrientType::Opposite`] for a clockwise one (only for `Inside`;
/// else [`OrientType::Unknown`]).
pub orientation: OrientType,
}
impl UvContainment {
const fn of(containment: PointContainment) -> Self {
Self {
containment,
on_edge: None,
edge_param: 0.0,
orientation: OrientType::Unknown,
}
}
const fn on(edge: EdgeId, edge_param: f64) -> Self {
Self {
containment: PointContainment::OnBoundary,
on_edge: Some(edge),
edge_param,
orientation: OrientType::Unknown,
}
}
}
/// The result of [`uv_corner_boundaries`].
#[derive(Debug, Clone, Default, PartialEq)]
pub struct UvCornerBoundaries {
/// `SM_SS_NONE` or an OR of `SM_SS_UMIN`, `SM_SS_VMIN`, `SM_SS_UMAX`, `SM_SS_VMAX`.
pub boundaries: u32,
/// `rOnEdges`: the edges with an end on a side, one entry per end.
pub on_edges: Vec<EdgeId>,
/// `rOnBndries`: the sides each of those ends is on.
pub on_boundaries: Vec<u32>,
/// `rOnParams`: the trim curve parameter of each of those ends.
pub on_params: Vec<f64>,
}
/// `GetOrCreateUVTrimCurve` of an edge use: its existing trim curve.
fn uv_trim_curve<'a>(eu: &LoopEdgeuse<'a>) -> Result<&'a Curve> {
eu.uv_trim_curve.ok_or_else(|| {
// UNPORTED(topology.edgeuse_p3): SmEdgeuse::GetOrCreateUVTrimCurve
SolidError::unported("SmEdgeuse::GetOrCreateUVTrimCurve", "topology.edgeuse_p3")
})
}
/// `SmLoop::GetUVCornerBoundaries`: the sides of `uv_domain` touched (within `tol`)
/// by the start or end of a UV trim curve of the loop, with the edge and trim curve
/// parameter of every touching end.
///
/// # Errors
///
/// [`SolidError::Unported`] for an edge use without UV trim curve
/// (`SmEdgeuse::GetOrCreateUVTrimCurve`, `topology.edgeuse_p3`); those of the trim
/// curve evaluations.
pub fn uv_corner_boundaries(
lp: &FaceLoop<'_>,
uv_domain: &Extent2d,
tol: f64,
) -> Result<UvCornerBoundaries> {
// init return values
let mut out = UvCornerBoundaries {
boundaries: SS_NONE,
..UvCornerBoundaries::default()
};
// for every edgeuse - classify the end points
for eu in &lp.edgeuses {
// get UVTrimCurve (the recovery branch for a missing trim curve is behind
// its creation)
let trim = uv_trim_curve(eu)?;
// end points
let ivl = trim.natural_interval();
let uv_beg = trim.evaluate_point(ivl.evaluate(0.0))?;
let uv_end = trim.evaluate_point(ivl.evaluate(1.0))?;
let uv_pnt_beg = Point2::new(uv_beg.x, uv_beg.y);
let uv_pnt_end = Point2::new(uv_end.x, uv_end.y);
// classify the BegPoint
let these = uv_domain.point_boundaries(uv_pnt_beg, tol);
out.boundaries |= these;
if these != 0 {
out.on_edges.push(eu.edge);
out.on_boundaries.push(these);
out.on_params.push(ivl.evaluate(0.0));
}
// classify the endPoint - in a perfect world we can skip this, but allow for
// tolerance errors
let these = uv_domain.point_boundaries(uv_pnt_end, tol);
out.boundaries |= these;
if these != 0 {
out.on_edges.push(eu.edge);
out.on_boundaries.push(these);
out.on_params.push(ivl.evaluate(1.0));
}
}
// all done
Ok(out)
}
/// `sm_CalcQuadNum`: the quadrant of `test` in a frame centred on `origin`
/// (`1 | 0` above, `2 | 3` below).
fn calc_quad_num(origin: Point3, test: Point3) -> i32 {
if test.x >= origin.x {
if test.y < origin.y {
3
} else {
0
}
} else if test.y >= origin.y {
1
} else {
2
}
}
/// `SmSurface::GetSingularities`: the sides of the natural domain that are poles.
fn surface_singularities(_surface: &Surface) -> Result<u32> {
// UNPORTED(surface.surface_p3): SmSurface::GetSingularities
Err(SolidError::unported(
"SmSurface::GetSingularities",
"surface.surface_p3",
))
}
/// `DropPoint` of the test point onto an edge curve (`SM_SO_INTERSECT`, no snapping
/// vector, no guess): the parameter when it lies within `tol`. A failing drop is no
/// drop (its status is ignored upstream).
fn drop_on_edge(k: &Kernel, eu: &LoopEdgeuse<'_>, point: Point3, tol: f64) -> Result<Option<f64>> {
match eu.curve.drop_point(
k,
&eu.interval,
point,
None,
tol,
None,
SolverOperation::Intersect,
) {
Ok(d) => Ok(d.success.then_some(d.parameter)),
Err(e @ SolidError::Unported { .. }) => Err(e),
Err(_) => Ok(None),
}
}
/// `LocalPointSolve(SM_SO_MINIMIZE)` of the test point on a UV trim curve: whether it
/// converged and the solution. A failing solve has not converged (its status is
/// ignored upstream and the flag is reset first).
fn local_minimize(
trim: &Curve,
interval: &Extent1d,
point: Point3,
tol_uv: f64,
guess: f64,
) -> Result<Option<Solution<()>>> {
let opts = PointSolveOptions {
distance_tolerance: Some(tol_uv),
..PointSolveOptions::default()
};
match trim.local_point_solve::<()>(interval, SolverOperation::Minimize, point, &opts, guess) {
Ok((true, sol)) => Ok(Some(sol)),
Err(e @ SolidError::Unported { .. }) => Err(e),
Ok((false, _)) | Err(_) => Ok(None),
}
}
/// The point and unit tangent (in the edge use's direction) of a trim curve at `t`,
/// with the tangent's length (`1` when below `SM_EFF_ZERO`).
fn eval_with_tangent(trim: &Curve, t: f64, orient: OrientType) -> Result<(Point3, Vec3, f64)> {
let der = trim.evaluate(t, 1, false, true)?;
let pt = der.first().copied().unwrap_or(Vec3::UNDEF);
let mut tan = der.get(1).copied().unwrap_or(Vec3::UNDEF);
if orient == OrientType::Opposite {
tan = -tan;
}
let mut len = tan.length();
if len < EFF_ZERO {
len = 1.0;
}
tan /= len;
Ok((pt, tan, len))
}
/// The unit vector from `origin` to `p` (unscaled when shorter than `SM_EFF_ZERO`)
/// and the distance.
fn unit_from(origin: Point3, p: Point3) -> (Vec3, f64) {
let mut v = p - origin;
let dist = v.length();
if dist > EFF_ZERO {
v /= dist;
}
(v, dist)
}
/// `SmLoop::ContainsUVPoint`: classifies the parameter-space point `uv` against this
/// loop only (other loops of the face are not looked at) by counting the quadrant
/// changes of the loop's UV trim curves around it: `±4` is inside (counter-clockwise
/// [`OrientType::Same`], clockwise [`OrientType::Opposite`]), `0` outside, with one
/// quadrant of noise allowed. The point is on the boundary when its surface point
/// lies on an edge curve within the edge's zone tolerance, when a trim curve passes
/// within the face's zone tolerance mapped to UV space, or when it lies on a gap
/// between consecutive edge uses (a pole); a point on a pole side shared by the loop
/// is on the boundary too. A vertex loop contains nothing. `_tol_3d` is unused, as
/// upstream.
///
/// # Errors
///
/// [`SolidError::Unported`]: `SmEdgeuse::GetOrCreateUVTrimCurve` for an edge use
/// without trim curve (`topology.edgeuse_p3`), `SmSurface::GetSingularities`
/// (`surface.surface_p3`) for a point and loop end on a side of the surface's
/// natural domain, and those of the callees; the errors of the surface and trim curve
/// evaluations (see the module Deviations).
pub fn contains_uv_point(
k: &Kernel,
lp: &FaceLoop<'_>,
uv: Point2,
_tol_3d: f64,
) -> Result<UvContainment> {
// Work from Loopuses, because they will give us an ordered list of Edgeuses.
let eus = &lp.edgeuses;
let Some(last_eu) = eus.last() else {
// could be a vertex loopuse
return Ok(UvContainment::of(PointContainment::Outside));
};
// check state - must have a Face and surface
let Some(face) = lp.face else {
return Ok(UvContainment::of(PointContainment::Unknown));
};
let surface = face.surface;
let srf_uv_domain = surface.natural_uv_domain();
let point_3d = surface.evaluate_point(uv)?;
let uv_pt = Point3::new(uv.x, uv.y, 0.0);
// Get a tolerance in uv space.
// Scale the input tolerance by the UV deriv size.
let zone_tol = face.tolerance;
let mut tol_uv = map_to_2d(zone_tol, uv, surface)?;
// minimize pole checking - if UVPnt on boundary -then-> if Loop on boundary -then->
// if GetSingularities -then-> expensive check
// Is crUVPoint on Surface Natural UVBoundary (where poles are allowed)
let uv_boundaries = srf_uv_domain.point_boundaries(uv, tol_uv);
// When crUVPoint is on Surface UVBoundary - does Loop have any edgeuse ends on
// Surface Natural UVBoundary
let corners = if uv_boundaries != 0 {
uv_corner_boundaries(lp, &srf_uv_domain, tol_uv)?
} else {
UvCornerBoundaries::default()
};
let loop_boundaries = corners.boundaries;
// When point and loop touch the surface UVBoundaries - does surface have poles
let poles = if uv_boundaries & loop_boundaries != 0 {
surface_singularities(surface)?
} else {
0
};
// remember when checking for points on poles is required
let common_poles = poles & loop_boundaries & uv_boundaries;
// when crUVPoint and Loop share a common singular edge - classify the point
if common_poles != 0 {
let mut out = UvContainment::of(PointContainment::OnBoundary);
// set output with first hit on a singluar boundary
for ((edge, bnd), param) in corners
.on_edges
.iter()
.zip(&corners.on_boundaries)
.zip(&corners.on_params)
{
if bnd & common_poles != 0 {
out.on_edge = Some(*edge);
out.edge_param = *param;
break;
}
}
// UVPoints on Singular boundaries shared by the loop are always classified
// ON_BOUNDARY
return Ok(out);
}
// check input:
if tol_uv < EFF_ZERO * 100.0 {
tol_uv = EFF_ZERO * 100.0;
}
// Locals:
let mut accum: i32 = 0; // This is the main result: 0, 4 or -4 when done.
let mut this_quad_num: i32 = 0; // Quadrant numbers: 0-3 going CCW.
let mut this_param = 0.0;
// Uninitialised upstream; first written by the walk of an edge use longer than
// SM_EFF_ZERO.
let mut this_uv = Vec3::UNDEF;
// Start with the end of the final Edgeuse.
let mut last_end_eu = last_eu;
let mut last_end_param = if last_end_eu.orientation == OrientType::Same {
last_end_eu.interval.max()
} else {
last_end_eu.interval.min()
};
let last_trim = uv_trim_curve(last_end_eu)?;
let mut last_end_uv = last_trim.evaluate_point(last_end_param)?;
let mut last_end_quad_num = calc_quad_num(uv_pt, last_end_uv);
// Now start the outer loop, on Edgeuses.
for this_eu in eus {
// low work: see if TgtPoint is on this EdgeCurve
if let Some(param) = drop_on_edge(k, this_eu, point_3d, this_eu.tolerance)? {
// TgtPoint is on EdgeCurve - set output and return
return Ok(UvContainment::on(this_eu.edge, param));
}
let trim = uv_trim_curve(this_eu)?;
// Grab some info about this EU and its uv curve.
let orient = this_eu.orientation;
let domain = this_eu.interval;
let (mut prev_param, end_param) = if orient == OrientType::Opposite {
(domain.max(), domain.min())
} else {
(domain.min(), domain.max())
};
// These have to do with step size:
// (dDefaultParamStep: every step below overwrites it, so it is never used)
let _default_param_step = domain.length() / trim.number_control_points()? as f64;
let min_param_step = domain.length() / 1000.0;
let max_param_step = domain.length() / 20.0;
let curve_is_linear = trim.is_linear(tol_uv, false)?.linear;
// Eval info for start point of this EU.
let (mut prev_uv, mut prev_uv_tan, mut prev_uv_tan_len) =
eval_with_tangent(trim, prev_param, orient)?;
let (prev_vec, mut prev_dist) = unit_from(uv_pt, prev_uv);
let mut prev_cos = prev_vec.dot(prev_uv_tan);
// Note: if dPrevDist < tol, then this uv pt is on the edge.
if prev_dist < tol_uv {
// We should refine the point.
if let Some(sol) = local_minimize(trim, &this_eu.interval, uv_pt, tol_uv, prev_param)? {
// since the guess was good enough - we expect the refined solution to
// be as good or better
if sol.start.solution_value <= tol_uv {
// return the refined answer.
return Ok(UvContainment::on(this_eu.edge, sol.start.param(0)?));
}
}
// Should not arrive here... (inconsistent classification; continue)
}
// Calculate the initial quadrant.
let mut prev_quad_num = calc_quad_num(uv_pt, prev_uv);
// Check here for non-closed loops.
let delta = prev_quad_num - last_end_quad_num;
// Check for and accumulate any change in quadrant.
if delta != 0 {
// First check 'on'.
let (seg_dist, seg_param) =
line_point_distance(last_end_uv, prev_uv - last_end_uv, uv_pt);
if seg_dist <= tol_uv && seg_param > -EFF_ZERO && seg_param < 1.0 + EFF_ZERO {
// On. Return closer end point.
return Ok(if seg_param <= 0.5 {
UvContainment::on(last_end_eu.edge, last_end_param)
} else {
UvContainment::on(this_eu.edge, prev_param)
});
}
if delta == 1 || delta == -3 {
accum += 1;
} else if delta == -1 || delta == 3 {
accum -= 1;
} else {
// +- 2: diagonal. Here, we're just shooting from one point to the
// next, so treat it as a straight line. See whether we're heading CW
// or CCW around the test point: use a cross product.
let cross = (prev_uv - last_end_uv).cross(uv_pt - last_end_uv);
if cross.z > 0.0 {
accum += 2; // CCW
} else {
accum -= 2; // CW
}
}
} // end check for change from end of last curve
// A flag to be used in this next loop:
let mut did_local_solve_last_time = false;
// Now step along this Edgeuse.
let mut loop_count1 = 0;
while (end_param - prev_param).abs() > EFF_ZERO {
loop_count1 += 1;
if loop_count1 > MAX_LOOP_COUNT {
// Infinite Loop.
return Ok(UvContainment::of(PointContainment::Unknown));
}
// Get the parameter for the next sample point.
let param_step = if curve_is_linear {
// For straight lines, we can shoot right to the end.
(end_param - prev_param).abs()
} else {
// When we're far from the test point, we can take bigger steps.
// Also increase step size if we're moving away from the test point:
// a fraction of the distance to the test point, from 1/4 (directly
// towards) through 1/2 (perpendicular) to 1 (directly away).
// Fraction = 1/8 cos^2 + 3/8 cos + 1/2.
let frac = prev_cos * (prev_cos + 3.0) / 8.0 + 0.5;
let move_dist = frac * prev_dist;
let mut param_step = move_dist / prev_uv_tan_len;
if param_step < min_param_step {
param_step = min_param_step;
}
if param_step > max_param_step {
param_step = max_param_step;
}
param_step
};
// Got param step, set dThisParam
if orient == OrientType::Same {
this_param = prev_param + param_step;
if this_param > end_param {
this_param = end_param;
}
} else {
this_param = prev_param - param_step;
if this_param < end_param {
this_param = end_param;
}
}
// Here we have the next candidate curve parameter, for a step. Evaluate
// the curve and get all of the state variables we need; when a better
// value is found, come back here (upstream `goto Got_dThisParam`).
let mut loop_count2 = 0;
let (this_uv_tan, this_uv_tan_len, this_dist, this_cos) = loop {
loop_count2 += 1;
if loop_count2 > MAX_LOOP_COUNT {
// Infinite Loop2.
return Ok(UvContainment::of(PointContainment::Unknown));
}
let (uv_at, this_uv_tan, this_uv_tan_len) =
eval_with_tangent(trim, this_param, orient)?;
this_uv = uv_at;
let (this_vec, this_dist) = unit_from(uv_pt, this_uv);
let this_cos = this_vec.dot(this_uv_tan);
// A check for too big a step: if the direction changed by more than,
// say, 90 degrees. But, if the curve is linear, and it doubles back on
// itself (it happens), we don't have to step along, can just shoot to
// the end. [Fillet regression 2:237]
if !curve_is_linear
&& prev_uv_tan.dot(this_uv_tan) < 0.0
// Cusps happen. In case of a turnaround in a very short interval,
// just continue on with the next parameter value.
&& (this_param - prev_param).abs() > min_param_step
{
this_param = (this_param + prev_param) / 2.0;
continue;
}
// Here we need to check whether this segment contains the test point.
let mut check_on_curve = false;
// If we do check on-curve, we will use dSegParam to find a guess
// parameter for LocalPointSolve.
let mut seg_param = 0.5; // just in case nothing else sets it...
// First, if the current point equals the test point:
if this_dist <= tol_uv {
check_on_curve = true;
seg_param = 1.0; // end of segment
}
// If the previous point was approaching and this point is receding:
if !check_on_curve
&& !did_local_solve_last_time
&& (prev_cos < -0.001 && this_cos > 0.001)
{
let (seg_dist, sp) = line_point_distance(prev_uv, this_uv - prev_uv, uv_pt);
seg_param = sp;
// Check whether we should check whether the pt is on the curve.
check_on_curve = seg_dist <= tol_uv;
// tidy up a projection exterior to the line segment
seg_param = seg_param.clamp(0.0, 1.0);
// Check whether this curve segment could possibly reach the test pt.
if !check_on_curve && !curve_is_linear {
// Extend start & end tangents to see whether they could reach
// as far as the test point.
// We know that dPrevCos < 0...
let chk_pt = prev_uv
+ prev_uv_tan * prev_uv_tan_len * (this_param - prev_param).abs();
let (chk_pt_dist, _) =
line_point_distance(prev_uv, this_uv - prev_uv, chk_pt);
check_on_curve = chk_pt_dist >= seg_dist;
// ... and that dThisCos > 0.
if !check_on_curve {
// Try other end of segment.
let chk_pt = this_uv
- this_uv_tan * this_uv_tan_len * (this_param - prev_param).abs();
let (chk_pt_dist, _) =
line_point_distance(prev_uv, this_uv - prev_uv, chk_pt);
check_on_curve = chk_pt_dist >= seg_dist;
}
} // end more-detailed check for possible on-curve.
} // end cosine-change check, for passing the test point.
did_local_solve_last_time = false;
if check_on_curve {
// We have a very good guess parameter for a local solve.
let guess_t = prev_param + seg_param * (this_param - prev_param);
// Interval: between prev pt and this pt. Loosen up a bit.
let mut crv_ivl = Extent1d::default();
crv_ivl.add_value(prev_param);
crv_ivl.add_value(this_param);
crv_ivl.expand_absolute(EFF_ZERO);
if let Some(sol) = local_minimize(trim, &crv_ivl, uv_pt, tol_uv, guess_t)? {
if sol.start.solution_value <= tol_uv {
return Ok(UvContainment::on(this_eu.edge, sol.start.param(0)?));
}
// We have a local min/max to the curve: do another point at
// the solution value, to avoid the case where the test point
// is between the segment chord and the actual curve. The
// solution will have a cosine of zero: use that fact to avoid
// getting stuck.
let solve_t = sol.start.param(0)?;
if crv_ivl.contains_value(solve_t, 0.0001) {
this_param = solve_t;
did_local_solve_last_time = true;
continue;
}
} // end if bFoundAnswer
} // end LocalPointSolve check for test pt on curve
// Got the next sample point, find its quadrant.
this_quad_num = calc_quad_num(uv_pt, this_uv);
let delta = this_quad_num - prev_quad_num;
// Check for and accumulate any change in quadrant.
if delta == 0 {
} else if delta == 1 || delta == -3 {
accum += 1;
} else if delta == -1 || delta == 3 {
accum -= 1;
} else {
// +- 2: diagonal. Just take a smaller step; is doesn't have to be
// much smaller.
this_param = prev_param + 0.75 * (this_param - prev_param);
continue;
}
break (this_uv_tan, this_uv_tan_len, this_dist, this_cos);
};
// Update This/Prev data.
prev_param = this_param;
prev_uv = this_uv;
prev_uv_tan = this_uv_tan;
prev_uv_tan_len = this_uv_tan_len;
prev_dist = this_dist;
prev_cos = this_cos;
prev_quad_num = this_quad_num;
} // end while stepping along this EU
// Update the last-end stuff, for checking non-closed loops.
last_end_eu = this_eu;
last_end_param = this_param;
last_end_uv = this_uv;
last_end_quad_num = this_quad_num;
} // end loop on all EU's
// Check the result: iAccum
if accum == 0 {
return Ok(UvContainment::of(PointContainment::Outside));
} else if accum == 4 || accum == -4 {
return Ok(inside(accum));
}
// It's common for uv points to have 'exactly' the same u or v value as the input
// point. In that case, a single bit of noise can change the quadrant
// classification by one. So we'll allow a one-quadrant error here.
if accum == 1 || accum == -1 {
return Ok(UvContainment::of(PointContainment::Outside));
} else if accum == 3 || accum == -3 || accum == 5 || accum == -5 {
return Ok(inside(accum));
}
// This is an error. The only bit-noise error should be at the start/end of the
// loop, and it can't be off by two.
Ok(UvContainment::of(PointContainment::Unknown))
}
/// `SM_POC_INSIDE` with the loop orientation of the winding sign.
fn inside(accum: i32) -> UvContainment {
UvContainment {
containment: PointContainment::Inside,
on_edge: None,
edge_param: 0.0,
orientation: if accum > 0 {
OrientType::Same
} else {
OrientType::Opposite
},
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::curve::line::Line;
use crate::math::Vec2;
use crate::nurbs::b_spline_curve::BSplineCurve;
use crate::nurbs::crv_basic::Curve as NurbCurve;
use crate::nurbs::math::{CPoint, NOW};
use crate::surface::plane::Plane;
fn plane() -> Surface {
Surface::from(Plane::new(
Vec3::ZERO,
Vec3::X,
Vec3::Y,
Vec2::new(1.0, 1.0),
&Extent2d::from_bounds(-20.0, -20.0, 20.0, 20.0),
))
}
fn trim(a: [f64; 2], b: [f64; 2]) -> Curve {
let pw = vec![
CPoint::new(a[0], a[1], 0.0, NOW),
CPoint::new(b[0], b[1], 0.0, NOW),
];
let cur = NurbCurve::new(pw, 1, vec![0.0, 0.0, 1.0, 1.0]).unwrap();
Curve::from(BSplineCurve::from_nurb(cur, 2, false))
}
/// The square `[-5, 5]^2` counter-clockwise (or clockwise) as 4 line edges.
fn square(ccw: bool) -> (Vec<Curve>, Vec<Curve>) {
let mut v = [[-5.0, -5.0], [5.0, -5.0], [5.0, 5.0], [-5.0, 5.0]];
if !ccw {
v.reverse();
}
let mut edges = Vec::new();
let mut trims = Vec::new();
for i in 0..4 {
let (a, b) = (v[i], v[(i + 1) % 4]);
edges.push(Curve::from(Line::from_points(
Vec3::new(a[0], a[1], 0.0),
Vec3::new(b[0], b[1], 0.0),
3,
)));
trims.push(trim(a, b));
}
(edges, trims)
}
fn view<'a>(s: &'a Surface, edges: &'a [Curve], trims: &'a [Curve]) -> FaceLoop<'a> {
FaceLoop {
face: Some(LoopFace {
surface: s,
tolerance: 1e-6,
}),
edgeuses: edges
.iter()
.zip(trims)
.enumerate()
.map(|(i, (c, t))| LoopEdgeuse {
edge: EdgeId::from_index(i as u32),
curve: c,
interval: Extent1d::from_min_max(0.0, 1.0),
tolerance: 1e-6,
orientation: OrientType::Same,
uv_trim_curve: Some(t),
})
.collect(),
}
}
#[test]
fn square_inside_outside_on() {
let k = Kernel::default();
let s = plane();
let (e, t) = square(true);
let lp = view(&s, &e, &t);
let r = contains_uv_point(&k, &lp, Point2::new(0.0, 0.0), 0.0).unwrap();
assert_eq!(r.containment, PointContainment::Inside);
assert_eq!(r.orientation, OrientType::Same);
let r = contains_uv_point(&k, &lp, Point2::new(8.0, 1.0), 0.0).unwrap();
assert_eq!(r.containment, PointContainment::Outside);
assert_eq!(r.on_edge, None);
let r = contains_uv_point(&k, &lp, Point2::new(5.0, 0.0), 0.0).unwrap();
assert_eq!(r.containment, PointContainment::OnBoundary);
assert_eq!(r.on_edge, Some(EdgeId::from_index(1)));
assert!((r.edge_param - 0.5).abs() < 1e-12);
let (e, t) = square(false);
let lp = view(&s, &e, &t);
let r = contains_uv_point(&k, &lp, Point2::new(1.0, -2.0), 0.0).unwrap();
assert_eq!(r.containment, PointContainment::Inside);
assert_eq!(r.orientation, OrientType::Opposite);
}
#[test]
fn vertex_loop_face_and_missing_trim() {
let k = Kernel::default();
let s = plane();
let empty = FaceLoop {
face: None,
edgeuses: Vec::new(),
};
let r = contains_uv_point(&k, &empty, Point2::new(0.0, 0.0), 0.0).unwrap();
assert_eq!(r, UvContainment::of(PointContainment::Outside));
let (e, t) = square(true);
let mut lp = view(&s, &e, &t);
lp.face = None;
let r = contains_uv_point(&k, &lp, Point2::new(0.0, 0.0), 0.0).unwrap();
assert_eq!(r.containment, PointContainment::Unknown);
let mut lp = view(&s, &e, &t);
if let Some(eu) = lp.edgeuses.last_mut() {
eu.uv_trim_curve = None;
}
let err = contains_uv_point(&k, &lp, Point2::new(0.0, 0.0), 0.0).unwrap_err();
assert!(matches!(err, SolidError::Unported { .. }));
}
#[test]
fn corner_boundaries_and_poles() {
let k = Kernel::default();
let s = plane();
let (e, t) = square(true);
let lp = view(&s, &e, &t);
let dom = Extent2d::from_bounds(-5.0, -5.0, 5.0, 5.0);
let b = uv_corner_boundaries(&lp, &dom, 1e-9).unwrap();
assert_eq!(b.boundaries, 15);
assert_eq!(b.on_edges.len(), 8);
assert_eq!(b.on_params.first(), Some(&0.0));
// a point on a domain side with the loop on that side reaches GetSingularities
let s2 = Surface::from(Plane::new(
Vec3::ZERO,
Vec3::X,
Vec3::Y,
Vec2::new(1.0, 1.0),
&dom,
));
let lp = view(&s2, &e, &t);
let err = contains_uv_point(&k, &lp, Point2::new(-5.0, 0.0), 0.0).unwrap_err();
assert!(matches!(err, SolidError::Unported { .. }));
}
#[test]
fn quadrants() {
let o = Point3::ZERO;
assert_eq!(calc_quad_num(o, Point3::new(1.0, 1.0, 0.0)), 0);
assert_eq!(calc_quad_num(o, Point3::new(-1.0, 1.0, 0.0)), 1);
assert_eq!(calc_quad_num(o, Point3::new(-1.0, -1.0, 0.0)), 2);
assert_eq!(calc_quad_num(o, Point3::new(1.0, -1.0, 0.0)), 3);
assert_eq!(calc_quad_num(o, o), 0);
}
}
@@ -1219,6 +1219,10 @@ impl Surface {
let _ssi: AdvSurfaceIntersector<'_, '_, NoSurfaceHandle> =
AdvSurfaceIntersector::new(self, uv_domain, other, other_uv_domain, false)?;
// (SurfaceIntersector::do_point_intersection, part 4, is the basic intersector's:
// through TraceCurve and ComputePointValues it calls the basic
// ComputeSingularityPoint, not SmAdvSurfaceIntersector's override, so it cannot
// stand in here until the intersector parts dispatch their virtual calls)
// UNPORTED(intersect.surface_intersector_p4): SmSurfaceIntersector::DoPointIntersection
Err(SolidError::unported(
"SmSurfaceIntersector::DoPointIntersection",
+474 -14
View File
@@ -4,15 +4,21 @@
//! `SmLoop.h`, `SmEdge.h`, `SmVertex.h`, the use records `SmFaceuse.h`, `SmLoopuse.h`,
//! `SmEdgeuse.h`, `SmVertexuse.h`, and `SmTopology.h` / `SmOwningTopology.h`.
//!
//! | Rust | Upstream |
//! |-------------------------|-------------------------------------------------------|
//! | [`Brep`] | `SmBrep` (face/edge/vertex lists `m_p*ListHead`) |
//! | [`Shell`] / [`ShellId`] | `SmShell` |
//! | [`Face`] / [`FaceId`] | `SmFace` |
//! | [`Loop`] / [`LoopId`] | `SmLoop` |
//! | [`Edge`] / [`EdgeId`] | `SmEdge` |
//! | [`Vertex`] / [`VertexId`] | `SmVertex` |
//! | [`Arena`] | upstream's intrusive `SmListNode` linked lists |
//! | Rust | Upstream |
//! |-----------------------------|-------------------------------------------------------|
//! | [`Brep`] | `SmBrep` (face/edge/vertex lists `m_p*ListHead`, `m_bValidateOnly`) |
//! | [`Region`] / [`RegionId`] | `SmRegion` (minimal: its list owner) |
//! | [`Shell`] / [`ShellId`] | `SmShell` |
//! | [`Faceuse`] / [`FaceuseId`] | `SmFaceuse` (unit `topology.edgeuse_p1`) |
//! | [`Face`] / [`FaceId`] | `SmFace` |
//! | [`Loopuse`] / [`LoopuseId`] | `SmLoopuse` (minimal) |
//! | [`Loop`] / [`LoopId`] | `SmLoop` |
//! | [`Edgeuse`] / [`EdgeuseId`] | `SmEdgeuse` (unit `topology.edgeuse_p1`) |
//! | [`Edge`] / [`EdgeId`] | `SmEdge` |
//! | [`Vertexuse`] / [`VertexuseId`] | `SmVertexuse` (minimal) |
//! | [`Vertex`] / [`VertexId`] | `SmVertex` |
//! | [`ObjectHandle`] | an `SmObject*` a record keeps but never dereferences |
//! | [`Arena`] | upstream's intrusive `SmListNode` linked lists |
//!
//! Upstream links entities with raw pointers in intrusive lists; the port stores each
//! kind in a flat [`Arena`] owned by the [`Brep`] and links them with typed ids, so
@@ -21,16 +27,34 @@
//! below are the minimum the scaffolding needs. The topology units in
//! `plan/modules.toml` (regions, face/loop/edge/vertex uses, geometry binding,
//! tolerances, attributes) extend these records; keep the id/arena/accessor shape.
//!
//! The use records and the fields marked "CO-PORTED" were added by unit
//! `topology.edgeuse_p1` (the minimum its functions read). An owner's circular
//! `m_pList` / `m_pNext` / `m_pLast` list is a `Vec` of ids in list order from the
//! head (`m_pList`); an object's `m_pNext` / `m_pLast` are its neighbours in that
//! `Vec` (cyclically). The low-level `add_*_record` and `*_mut` accessors are the
//! friend writes of `SmBrepConstructor`: they link records without validation (a
//! B-rep graph is cyclic, so a record may name ids added later).
pub mod brep_data;
pub mod database_io;
pub mod edgeuse;
#[allow(clippy::module_inception)]
pub mod topology;
use core::marker::PhantomData;
use crate::curve::curve::Curve;
use crate::error::{EntityRef, Result, SolidError};
use crate::geom::attribute::ObjectKey;
use crate::geom::extent1d::Extent1d;
use crate::geom::solution_array::{ObjectKind, SolutionObject};
use crate::geom::tol::ZONE_TOL_3D;
use crate::heal::gap::{EdgeEdgeGap, EdgeFaceGap, VertexFaceGap};
use crate::math::Point3;
use crate::nurbs::b_spline_surface_n_lib::p2::OrientType;
use crate::surface::surface::p2::DropCurveFail;
use crate::surface::surface::Surface;
/// Implemented by every typed id: conversion to and from a dense arena index.
pub trait ArenaId: Copy + Eq + core::fmt::Debug {
@@ -101,6 +125,26 @@ typed_id!(
/// Identifies a [`Vertex`] within its [`Brep`].
VertexId => Vertex
);
typed_id!(
/// Identifies a [`Region`] within its [`Brep`].
RegionId => Region
);
typed_id!(
/// Identifies a [`Faceuse`] within its [`Brep`].
FaceuseId => Faceuse
);
typed_id!(
/// Identifies a [`Loopuse`] within its [`Brep`].
LoopuseId => Loopuse
);
typed_id!(
/// Identifies an [`Edgeuse`] within its [`Brep`].
EdgeuseId => Edgeuse
);
typed_id!(
/// Identifies a [`Vertexuse`] within its [`Brep`].
VertexuseId => Vertexuse
);
/// A dense, append-only store of `T` addressed by typed id `I`.
#[derive(Debug, Clone, PartialEq)]
@@ -172,18 +216,46 @@ impl<I: ArenaId, T> Arena<I, T> {
}
}
/// An `SmObject*` a topology record keeps but never dereferences (the surface,
/// curve and B-rep pointers of an `SmDropCurveFail`): the object's kind and the
/// identity it is attributed and notified under.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ObjectHandle {
/// What the object is.
pub kind: ObjectKind,
/// Its identity.
pub key: ObjectKey,
}
impl SolutionObject for ObjectHandle {
fn kind(&self) -> ObjectKind {
self.kind
}
}
/// A vertex (`SmVertex`): a point where edges meet.
#[derive(Debug, Clone, PartialEq)]
pub struct Vertex {
/// Position in model space.
pub point: Point3,
/// `m_sZoneTol3d` (CO-PORTED, `SM_USE_OLDTOL`): the vertex tolerance.
pub tolerance: f64,
}
/// An edge (`SmEdge`): a bounded curve between two vertices.
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone)]
pub struct Edge {
/// Start and end vertex (equal for a closed edge).
pub vertices: [VertexId; 2],
/// `m_pList` (CO-PORTED): the edge's edge uses in radial list order; the first is
/// the primary edge use (`GetPrimaryEdgeuse`).
pub edgeuses: Vec<EdgeuseId>,
/// `m_vInterval` (CO-PORTED): the used parameter interval of the curve.
pub interval: Extent1d,
/// `m_sZoneTol3d` (CO-PORTED, `SM_USE_OLDTOL`): the edge tolerance.
pub tolerance: f64,
/// `m_pCurve` (CO-PORTED): the edge's 3D curve.
pub curve: Option<Curve>,
}
/// One oriented use of an edge in a loop (upstream `SmEdgeuse`, simplified).
@@ -202,15 +274,19 @@ pub struct Loop {
pub face: FaceId,
/// Edges in traversal order.
pub edges: Vec<LoopEdge>,
/// `m_pLU` (CO-PORTED): the primary loop use (`GetLoopuse`).
pub loopuse: Option<LoopuseId>,
}
/// A face (`SmFace`): a bounded region of a surface.
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone)]
pub struct Face {
/// Shell containing this face.
pub shell: ShellId,
/// Boundary loops; upstream keeps the outer loop first.
pub loops: Vec<LoopId>,
/// `m_pSurface` (CO-PORTED): the face's surface (`GetSurface`).
pub surface: Option<Surface>,
}
/// A shell (`SmShell`): a connected set of faces.
@@ -218,17 +294,180 @@ pub struct Face {
pub struct Shell {
/// Faces of the shell, in insertion order.
pub faces: Vec<FaceId>,
/// `m_pListOwner` (CO-PORTED): the region owning the shell (`GetRegion`).
pub region: Option<RegionId>,
}
/// A region (`SmRegion`, CO-PORTED minimal record).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Region {
/// `m_pListOwner != NULL`: the region is on the B-rep's region list, so
/// `GetBrep` reaches the B-rep.
pub in_brep: bool,
}
/// One use of a face (`SmFaceuse`): the face seen from one side.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Faceuse {
/// `m_pListOwner`: the shell owning the face use (`GetShell`).
pub shell: Option<ShellId>,
/// `m_pF`: the face used (`GetFace`).
pub face: Option<FaceId>,
/// `m_pFUMate`: the face's other face use (`GetMate`).
pub mate: Option<FaceuseId>,
/// `m_eOrientation`: [`OrientType::Same`] when the face use normal is the
/// surface normal.
pub orientation: OrientType,
/// `m_pList`: the loop uses, outer loop use first.
pub loopuses: Vec<LoopuseId>,
}
/// What a loop use starts from (`m_tLoopuseType` and `m_pEUorVU`).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum LoopuseStart {
/// `SmUnknown_TYPE` (a loop use not yet built).
#[default]
Unknown,
/// `SmEdgeuse_TYPE`: the loop's start edge use.
Edgeuse(Option<EdgeuseId>),
/// `SmVertexuse_TYPE`: the single vertex use of a vertex loop.
Vertexuse(Option<VertexuseId>),
}
/// One use of a loop (`SmLoopuse`, CO-PORTED minimal record).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Loopuse {
/// `m_pListOwner` as an `SmFaceuse` (`GetFaceuse`).
pub faceuse: Option<FaceuseId>,
/// `m_pL`: the loop used (`GetLoop`).
pub loop_: Option<LoopId>,
/// `m_pLUMate`: the loop use on the face's other side (`GetOtherLoopuse`).
pub mate: Option<LoopuseId>,
/// `m_tLoopuseType` and `m_pEUorVU`.
pub start: LoopuseStart,
}
/// What an edge use connects to (`m_tEdgeuseType` and `m_pSorLU`).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum EdgeuseOwner {
/// `SmUnknown_TYPE` (the constructor's value).
#[default]
Unknown,
/// `SmLoopuse_TYPE`: an edge in a face, connected through a loop use.
Loopuse(Option<LoopuseId>),
/// `SmShell_TYPE`: a wire edge, connected to a region through a shell.
Shell(Option<ShellId>),
}
/// One use of an edge (`SmEdgeuse`): the edge seen from one face side or one wire
/// end.
#[derive(Debug, Clone)]
pub struct Edgeuse {
/// `m_pListOwner`: the edge used (`GetEdge`).
pub edge: Option<EdgeId>,
/// `m_tEdgeuseType` and `m_pSorLU`.
pub owner: EdgeuseOwner,
/// `m_pCCW`: the next edge use counter-clockwise in the loop.
pub ccw: Option<EdgeuseId>,
/// `m_pCW`: the next edge use clockwise in the loop.
pub cw: Option<EdgeuseId>,
/// `m_pVU`: the vertex use at the edge use's start.
pub vertexuse: Option<VertexuseId>,
/// `m_eOrientation`: [`OrientType::Same`] when the edge use runs with its edge's
/// curve.
pub orientation: OrientType,
/// `m_pUVTrimCurve`: the 2D trim curve, kept on the [`OrientType::Same`] member
/// of a mated pair (the edge use is its owner). Upstream's `SmBSplineCurve*`: a
/// B-spline curve or one of its derived kinds (`SmLine`, ...).
pub uv_trim_curve: Option<Curve>,
/// `m_sMaxEdgeFaceGap3d`: the cached largest edge/face gap.
pub max_edge_face_gap: EdgeFaceGap,
/// `m_sEdgeCCWEdgeGap3d`: the cached gap to the counter-clockwise edge use.
pub edge_ccw_edge_gap: EdgeEdgeGap,
/// `m_pDropCurveFail`: what the last `CreateUVTrimCurve` recorded.
pub drop_curve_fail: Option<Box<DropCurveFail<ObjectHandle>>>,
}
impl Default for Edgeuse {
/// `SmEdgeuse()`: unlinked, unknown type and orientation, gaps reset.
fn default() -> Self {
Self {
edge: None,
owner: EdgeuseOwner::Unknown,
ccw: None,
cw: None,
vertexuse: None,
orientation: OrientType::Unknown,
uv_trim_curve: None,
max_edge_face_gap: EdgeFaceGap::new(),
edge_ccw_edge_gap: EdgeEdgeGap::new(),
drop_curve_fail: None,
}
}
}
/// What a vertex use connects to (`m_tVertexuseType` and `m_pSorLUorEU`).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum VertexuseOwner {
/// `SmUnknown_TYPE`.
#[default]
Unknown,
/// `SmEdgeuse_TYPE`: the vertex at an edge use's start.
Edgeuse(Option<EdgeuseId>),
/// `SmLoopuse_TYPE`: the vertex of a vertex loop.
Loopuse(Option<LoopuseId>),
/// `SmShell_TYPE`: a vertex alone in a region.
Shell(Option<ShellId>),
}
/// One use of a vertex (`SmVertexuse`, CO-PORTED minimal record).
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Vertexuse {
/// `m_pListOwner`: the vertex used (`GetVertex`).
pub vertex: Option<VertexId>,
/// `m_tVertexuseType` and `m_pSorLUorEU`.
pub owner: VertexuseOwner,
/// `m_sVertexFaceGap3d`: the cached vertex/face gap.
pub vertex_face_gap: VertexFaceGap,
}
/// A boundary representation (`SmBrep`): arenas of shells, faces, loops, edges and
/// vertices linked by typed ids.
#[derive(Debug, Clone, PartialEq, Default)]
#[derive(Debug, Clone)]
pub struct Brep {
shells: Arena<ShellId, Shell>,
faces: Arena<FaceId, Face>,
loops: Arena<LoopId, Loop>,
edges: Arena<EdgeId, Edge>,
vertices: Arena<VertexId, Vertex>,
regions: Arena<RegionId, Region>,
faceuses: Arena<FaceuseId, Faceuse>,
loopuses: Arena<LoopuseId, Loopuse>,
edgeuses: Arena<EdgeuseId, Edgeuse>,
vertexuses: Arena<VertexuseId, Vertexuse>,
/// `m_bValidateOnly`.
validate_only: bool,
/// The identity the B-rep is attributed and notified under (`SmBrep*`).
key: ObjectKey,
}
impl Default for Brep {
fn default() -> Self {
Self {
shells: Arena::default(),
faces: Arena::default(),
loops: Arena::default(),
edges: Arena::default(),
vertices: Arena::default(),
regions: Arena::default(),
faceuses: Arena::default(),
loopuses: Arena::default(),
edgeuses: Arena::default(),
vertexuses: Arena::default(),
validate_only: false,
key: ObjectKey(0),
}
}
}
fn not_found<I: ArenaId>(operation: &'static str, id: I) -> SolidError {
@@ -310,6 +549,215 @@ impl Brep {
.ok_or_else(|| not_found("topology::Brep::vertex", id))
}
/// All regions.
#[must_use]
pub fn regions(&self) -> &Arena<RegionId, Region> {
&self.regions
}
/// All face uses.
#[must_use]
pub fn faceuses(&self) -> &Arena<FaceuseId, Faceuse> {
&self.faceuses
}
/// All loop uses.
#[must_use]
pub fn loopuses(&self) -> &Arena<LoopuseId, Loopuse> {
&self.loopuses
}
/// All edge uses.
#[must_use]
pub fn edgeuses(&self) -> &Arena<EdgeuseId, Edgeuse> {
&self.edgeuses
}
/// All vertex uses.
#[must_use]
pub fn vertexuses(&self) -> &Arena<VertexuseId, Vertexuse> {
&self.vertexuses
}
/// The region `id`.
pub fn region(&self, id: RegionId) -> Result<&Region> {
self.regions
.get(id)
.ok_or_else(|| not_found("topology::Brep::region", id))
}
/// The face use `id`.
pub fn faceuse(&self, id: FaceuseId) -> Result<&Faceuse> {
self.faceuses
.get(id)
.ok_or_else(|| not_found("topology::Brep::faceuse", id))
}
/// The loop use `id`.
pub fn loopuse(&self, id: LoopuseId) -> Result<&Loopuse> {
self.loopuses
.get(id)
.ok_or_else(|| not_found("topology::Brep::loopuse", id))
}
/// The edge use `id`.
pub fn edgeuse(&self, id: EdgeuseId) -> Result<&Edgeuse> {
self.edgeuses
.get(id)
.ok_or_else(|| not_found("topology::Brep::edgeuse", id))
}
/// The vertex use `id`.
pub fn vertexuse(&self, id: VertexuseId) -> Result<&Vertexuse> {
self.vertexuses
.get(id)
.ok_or_else(|| not_found("topology::Brep::vertexuse", id))
}
/// Mutable access to the shell `id`.
pub fn shell_mut(&mut self, id: ShellId) -> Result<&mut Shell> {
self.shells
.get_mut(id)
.ok_or_else(|| not_found("topology::Brep::shell_mut", id))
}
/// Mutable access to the face `id`.
pub fn face_mut(&mut self, id: FaceId) -> Result<&mut Face> {
self.faces
.get_mut(id)
.ok_or_else(|| not_found("topology::Brep::face_mut", id))
}
/// Mutable access to the loop `id`.
pub fn loop_mut(&mut self, id: LoopId) -> Result<&mut Loop> {
self.loops
.get_mut(id)
.ok_or_else(|| not_found("topology::Brep::loop_mut", id))
}
/// Mutable access to the edge `id`.
pub fn edge_mut(&mut self, id: EdgeId) -> Result<&mut Edge> {
self.edges
.get_mut(id)
.ok_or_else(|| not_found("topology::Brep::edge_mut", id))
}
/// Mutable access to the vertex `id`.
pub fn vertex_mut(&mut self, id: VertexId) -> Result<&mut Vertex> {
self.vertices
.get_mut(id)
.ok_or_else(|| not_found("topology::Brep::vertex_mut", id))
}
/// Mutable access to the region `id`.
pub fn region_mut(&mut self, id: RegionId) -> Result<&mut Region> {
self.regions
.get_mut(id)
.ok_or_else(|| not_found("topology::Brep::region_mut", id))
}
/// Mutable access to the face use `id`.
pub fn faceuse_mut(&mut self, id: FaceuseId) -> Result<&mut Faceuse> {
self.faceuses
.get_mut(id)
.ok_or_else(|| not_found("topology::Brep::faceuse_mut", id))
}
/// Mutable access to the loop use `id`.
pub fn loopuse_mut(&mut self, id: LoopuseId) -> Result<&mut Loopuse> {
self.loopuses
.get_mut(id)
.ok_or_else(|| not_found("topology::Brep::loopuse_mut", id))
}
/// Mutable access to the edge use `id`.
pub fn edgeuse_mut(&mut self, id: EdgeuseId) -> Result<&mut Edgeuse> {
self.edgeuses
.get_mut(id)
.ok_or_else(|| not_found("topology::Brep::edgeuse_mut", id))
}
/// Mutable access to the vertex use `id`.
pub fn vertexuse_mut(&mut self, id: VertexuseId) -> Result<&mut Vertexuse> {
self.vertexuses
.get_mut(id)
.ok_or_else(|| not_found("topology::Brep::vertexuse_mut", id))
}
/// `m_bValidateOnly`: while set, tolerances must not change.
#[must_use]
pub const fn validate_only(&self) -> bool {
self.validate_only
}
/// Sets `m_bValidateOnly`.
pub fn set_validate_only(&mut self, validate_only: bool) {
self.validate_only = validate_only;
}
/// The identity the B-rep is attributed and notified under (an `SmBrep*`).
#[must_use]
pub const fn key(&self) -> ObjectKey {
self.key
}
/// Sets the identity the B-rep is attributed and notified under.
pub fn set_key(&mut self, key: ObjectKey) {
self.key = key;
}
/// Appends a region record as is (no validation).
pub fn add_region_record(&mut self, region: Region) -> Result<RegionId> {
self.regions.insert(region)
}
/// Appends a shell record as is (no validation).
pub fn add_shell_record(&mut self, shell: Shell) -> Result<ShellId> {
self.shells.insert(shell)
}
/// Appends a face record as is (no validation; the shell's face list is not
/// touched).
pub fn add_face_record(&mut self, face: Face) -> Result<FaceId> {
self.faces.insert(face)
}
/// Appends a loop record as is (no validation; the face's loop list is not
/// touched).
pub fn add_loop_record(&mut self, loop_: Loop) -> Result<LoopId> {
self.loops.insert(loop_)
}
/// Appends an edge record as is (no validation).
pub fn add_edge_record(&mut self, edge: Edge) -> Result<EdgeId> {
self.edges.insert(edge)
}
/// Appends a vertex record as is.
pub fn add_vertex_record(&mut self, vertex: Vertex) -> Result<VertexId> {
self.vertices.insert(vertex)
}
/// Appends a face use record as is (no validation).
pub fn add_faceuse_record(&mut self, faceuse: Faceuse) -> Result<FaceuseId> {
self.faceuses.insert(faceuse)
}
/// Appends a loop use record as is (no validation).
pub fn add_loopuse_record(&mut self, loopuse: Loopuse) -> Result<LoopuseId> {
self.loopuses.insert(loopuse)
}
/// Appends an edge use record as is (no validation).
pub fn add_edgeuse_record(&mut self, edgeuse: Edgeuse) -> Result<EdgeuseId> {
self.edgeuses.insert(edgeuse)
}
/// Appends a vertex use record as is (no validation).
pub fn add_vertexuse_record(&mut self, vertexuse: Vertexuse) -> Result<VertexuseId> {
self.vertexuses.insert(vertexuse)
}
/// Adds an empty shell.
pub fn add_shell(&mut self) -> Result<ShellId> {
self.shells.insert(Shell::default())
@@ -317,7 +765,10 @@ impl Brep {
/// Adds a vertex at `point`.
pub fn add_vertex(&mut self, point: Point3) -> Result<VertexId> {
self.vertices.insert(Vertex { point })
self.vertices.insert(Vertex {
point,
tolerance: ZONE_TOL_3D,
})
}
/// Adds an edge from `start` to `end`; both vertices must exist.
@@ -330,6 +781,10 @@ impl Brep {
}
self.edges.insert(Edge {
vertices: [start, end],
edgeuses: Vec::new(),
interval: Extent1d::new(),
tolerance: ZONE_TOL_3D,
curve: None,
})
}
@@ -342,6 +797,7 @@ impl Brep {
let face = self.faces.insert(Face {
shell,
loops: Vec::new(),
surface: None,
})?;
self.shells
.get_mut(shell)
@@ -377,7 +833,11 @@ impl Brep {
reason: "loop edges do not close",
});
}
let id = self.loops.insert(Loop { face, edges })?;
let id = self.loops.insert(Loop {
face,
edges,
loopuse: None,
})?;
self.faces
.get_mut(face)
.ok_or_else(|| not_found(OP, face))?
+9
View File
@@ -0,0 +1,9 @@
//! Edge uses (`SmEdgeuse`) and face uses (`SmFaceuse`): the oriented uses of edges in
//! loops and wires and of faces in shells. The records ([`crate::topology::Edgeuse`],
//! [`crate::topology::Faceuse`]) live in the B-rep's arenas; the methods are split over
//! the plan units `topology.edgeuse_p1`, `_p2` and `_p3`.
//!
//! Upstream: `source/SMLib/src/SmEdgeuse.cpp`, `source/SMLib/inc/SmEdgeuse.h`,
//! `source/SMLib/src/SmFaceuse.cpp`, `source/SMLib/inc/SmFaceuse.h`.
pub mod p1;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,759 @@
// SPDX-License-Identifier: Apache-2.0
// difftest shim for unit `intersect.surface_intersector_p4`: the SmSurfaceIntersector members
// TraceCurve, FindInteriorCurves, DoPointIntersection and DoLawIntersection
// (source/SMLib/src/SmSurfaceIntersector.cpp), run on a real SmSurfaceIntersector over SmPlane /
// SmBSplineSurface / SmCurveBoundedSurface objects (a derived class only exposes the protected
// state), with the part 1 to 3 calls a program needs to set the state up (point lists,
// ComputePointValues, FlushCurve).
//
// One entry point, sc_intersect_surface_intersector_p4_run(in, n_in, out, cap, out_len). `in` is
// a program (layout in crates/solidcore-ffi/src/units/intersect_surface_intersector_p4.rs);
// every read is bounds checked here (a bad program is SC_ERR_ARG). Ops whose upstream behaviour
// is undefined report REJECTED (see the op cases).
#include "sc_shim.h"
#include "StdAfx.h"
#include <SmBSplineCurve.h>
#include <SmBSplineSurface.h>
#include <SmContext.h>
#include <SmCurve.h>
#include <SmCurveBoundedSurface.h>
#include <SmExtent1d.h>
#include <SmExtent2d.h>
#include <SmExtent3d.h>
#include <SmFilletExecutive.h>
#include <SmLine.h>
#include <SmNurbsCrv.h>
#include <SmNurbsSrf.h>
#include <SmPlane.h>
#include <SmSurface.h>
#include <SmSurfaceCache.h>
#include <SmSurfaceIntersector.h>
#include <SmTArray.h>
#include <SmTList.h>
#include <SmVector2d.h>
#include <SmVector3d.h>
#include <nurbs.h>
#include <cmath>
#include <stdexcept>
#include <vector>
namespace
{
enum Op : int32_t
{
OP_STATE = 1,
OP_POINT = 2,
OP_ADD_CURVE = 3,
OP_ADD_START = 4,
OP_ADD_THROUGH = 5,
OP_CPV = 6,
OP_MEMBERS = 7,
OP_ENDS = 8,
OP_EXT = 9,
OP_TRACE = 10,
OP_FLUSH = 11,
OP_DPI = 12,
OP_DLI = 13,
OP_FIC = 14,
OP_LAST = 14,
};
// Op status of an op the shim does not run.
const double REJECTED = -2.0;
struct Bad : std::runtime_error
{
Bad() : std::runtime_error("bad program") {}
};
struct Rd
{
const double* in;
int64_t n;
int64_t at;
double num()
{
if (at >= n)
throw Bad();
return in[at++];
}
int64_t idx(int64_t lo, int64_t hi)
{
double d = num();
if (!(d >= (double)lo && d <= (double)hi) || d != (double)(int64_t)d)
throw Bad();
return (int64_t)d;
}
bool flag() { return idx(0, 1) != 0; }
SmVector3d vec()
{
double x = num();
double y = num();
double z = num();
return SmVector3d(x, y, z);
}
SmPoint2d uv()
{
double x = num();
double y = num();
return SmPoint2d(x, y);
}
SmExtent2d dom4()
{
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(SmBoolean 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);
}
void put_uv(std::vector<double>& o, const SmVector2d& v)
{
o.push_back(v.x);
o.push_back(v.y);
}
// `0 o x y scale dom` (SmPlane) or `1 p q nu nv net(x y z w) u v` (SmBSplineSurface).
SmSurface* read_basic_surface(Rd& rd, const SmContext& ctx)
{
int64_t kind = rd.idx(0, 1);
if (kind == 0)
{
SmPoint3d o = rd.vec();
SmVector3d x = rd.vec();
SmVector3d y = rd.vec();
SmVector2d s = rd.uv();
SmExtent2d d = rd.dom4();
return new (ctx) SmPlane(o, x, y, s, d, &ctx);
}
gw_DEGREE p = (gw_DEGREE)rd.idx(1, 8);
gw_DEGREE q = (gw_DEGREE)rd.idx(1, 8);
gw_INDEX nu = (gw_INDEX)rd.idx(2, 17);
gw_INDEX nv = (gw_INDEX)rd.idx(2, 17);
if (p >= nu || q >= nv)
throw Bad();
gw_INDEX n = nu - 1;
gw_INDEX m = nv - 1;
gw_INDEX r = n + p + 1;
gw_INDEX s = m + q + 1;
std::vector<double> v;
for (int64_t i = 0; i < 4 * (int64_t)nu * nv + r + 1 + s + 1; i++)
v.push_back(rd.num());
gw_SURFACE* sur = sm_AllocateNurbSurface(n, m, p, q, r, s);
size_t k = 0;
for (gw_INDEX i = 0; i <= n; i++)
for (gw_INDEX j = 0; j <= m; j++)
{
sur->net->Pw[i][j].x = v[k++];
sur->net->Pw[i][j].y = v[k++];
sur->net->Pw[i][j].z = v[k++];
sur->net->Pw[i][j].w = v[k++];
}
for (gw_INDEX i = 0; i <= r; i++)
sur->knu->U[i] = v[k++];
for (gw_INDEX i = 0; i <= s; i++)
sur->knv->U[i] = v[k++];
return new (ctx) SmBSplineSurface(sur, FALSE, &ctx);
}
// A basic surface (`0 ..` / `1 ..`) or `3 basic`: an SmCurveBoundedSurface (implicit outer, no
// boundary curves) over it, which owns (deletes) its basis. (`2` marks B = A.)
SmSurface* read_surface(Rd& rd, const SmContext& ctx)
{
if (rd.at < rd.n && rd.in[rd.at] == 3.0)
{
rd.at++;
SmSurface* basis = read_basic_surface(rd, ctx);
SmTArray<SmCurve*> c3d(ctx), cuv(ctx);
return new (ctx) SmCurveBoundedSurface(*basis, &c3d, &cuv, TRUE);
}
return read_basic_surface(rd, ctx);
}
// `0 a b` (SmLine::CreateLineSegment, dim 3) or `1 dim p ncp cpts(x y z w) knots`.
SmCurve* read_curve(Rd& rd, const SmContext& ctx)
{
int64_t kind = rd.idx(0, 1);
if (kind == 0)
{
SmPoint3d a = rd.vec();
SmPoint3d b = rd.vec();
SmLine* l = NULL;
if (SmLine::CreateLineSegment(ctx, 3, a, b, l) != SM_SUCCESS || l == NULL)
throw Bad();
return l;
}
ULONG dim = (ULONG)rd.idx(2, 3);
int64_t p = rd.idx(1, 8);
int64_t ncp = rd.idx(2, 33);
if (p >= ncp)
throw Bad();
int64_t n = ncp - 1;
int64_t m = n + p + 1;
std::vector<double> v;
for (int64_t i = 0; i < 4 * ncp + m + 1; i++)
v.push_back(rd.num());
gw_CURVE* cur = sm_AllocateNurbCurve((gw_INDEX)n, (gw_DEGREE)p, (gw_INDEX)m);
for (int64_t i = 0; i <= n; i++)
{
cur->pol->Pw[i].x = v[4 * i];
cur->pol->Pw[i].y = v[4 * i + 1];
cur->pol->Pw[i].z = v[4 * i + 2];
cur->pol->Pw[i].w = v[4 * i + 3];
}
for (int64_t i = 0; i <= m; i++)
cur->knt->U[i] = v[4 * ncp + i];
SmBSplineCurve* c = new (ctx) SmBSplineCurve(dim, (ULONG)cur->pol->n, (ULONG)cur->knt->m, (short)cur->p,
cur->pol->Pw, cur->knt->U, &ctx);
smos_Free(cur);
return c;
}
// A point: `type ptype[4] uv0 duv0 uv1 duv1 srf0[3][3] srf1[3][3] crv_pos crv_deriv param
// angle deviation user[4]` (80 values).
void read_point(Rd& rd, SmTsectPnt& t)
{
t.m_ePointType = (SmIntersectionPointType)rd.idx(0, 7);
for (int s = 0; s < 2; s++)
for (int d = 0; d < 2; d++)
t.m_eUVParamType[s][d] = (SmExtentPointType)rd.idx(0, 5);
for (int s = 0; s < 2; s++)
for (int d = 0; d < 2; d++)
t.m_vUVCurvePV[s][d] = rd.uv();
for (int s = 0; s < 2; s++)
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
t.m_vSurfacePV[s][i][j] = rd.vec();
t.m_v3DCurvePV[0] = rd.vec();
t.m_v3DCurvePV[1] = rd.vec();
t.m_dCurveParameter = rd.num();
t.m_dTangentPlaneAngleRad = rd.num();
t.m_dDeviation = rd.num();
for (int i = 0; i < SM_MAX_USER_DOUBLES; i++)
t.m_adUserDoubles[i] = rd.num();
}
void put_point(std::vector<double>& o, const SmTsectPnt& t)
{
o.push_back((double)t.m_ePointType);
for (int s = 0; s < 2; s++)
for (int d = 0; d < 2; d++)
o.push_back((double)t.m_eUVParamType[s][d]);
for (int s = 0; s < 2; s++)
for (int d = 0; d < 2; d++)
put_uv(o, t.m_vUVCurvePV[s][d]);
for (int s = 0; s < 2; s++)
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
put_vec(o, t.m_vSurfacePV[s][i][j]);
put_vec(o, t.m_v3DCurvePV[0]);
put_vec(o, t.m_v3DCurvePV[1]);
o.push_back(t.m_dCurveParameter);
o.push_back(t.m_dTangentPlaneAngleRad);
o.push_back(t.m_dDeviation);
for (int i = 0; i < SM_MAX_USER_DOUBLES; i++)
o.push_back(t.m_adUserDoubles[i]);
}
// Reads the protected STEP flags through pointers to members named in a derived class.
struct PeekC : SmBSplineCurve
{
static SmKnotType SmBSplineCurve::*kt() { return &PeekC::m_eKnotType; }
static SmBSplineCurveForm SmBSplineCurve::*form() { return &PeekC::m_eBSplineCurveForm; }
};
// `type dim knot_type form has [n p m cpts(x y z w) knots] ivl[2]` of a B-spline curve.
void put_bspline(std::vector<double>& o, const SmCurve* c)
{
o.push_back((double)c->GetType());
o.push_back((double)c->GetDim());
if (!c->IsKindOf(SmBSplineCurve_TYPE))
throw Bad();
const SmBSplineCurve* b = (const SmBSplineCurve*)c;
o.push_back((double)(b->*PeekC::kt()));
o.push_back((double)(b->*PeekC::form()));
const gw_CURVE* g = b->GetGwNurbPointer();
o.push_back(g ? 1.0 : 0.0);
if (g)
{
o.push_back((double)g->pol->n);
o.push_back((double)g->p);
o.push_back((double)g->knt->m);
for (gw_INDEX i = 0; i <= g->pol->n; i++)
{
o.push_back(g->pol->Pw[i].x);
o.push_back(g->pol->Pw[i].y);
o.push_back(g->pol->Pw[i].z);
o.push_back(g->pol->Pw[i].w);
}
for (gw_INDEX i = 0; i <= g->knt->m; i++)
o.push_back(g->knt->U[i]);
}
SmExtent1d ivl = c->GetNaturalInterval();
o.push_back(ivl.GetMin());
o.push_back(ivl.GetMax());
}
// `count curves..` from index `from` of an array.
void put_curves(std::vector<double>& o, const SmTArray<SmCurve*>& a, ULONG from)
{
o.push_back((double)(a.GetSize() - from));
for (ULONG i = from; i < a.GetSize(); i++)
put_bspline(o, a[i]);
}
struct Prog
{
const SmContext& ctx;
std::vector<SmTsectPnt*> pts;
std::vector<SmCurve*> curves;
~Prog()
{
for (SmTsectPnt* p : pts)
delete p;
for (SmCurve* c : curves)
delete c;
}
SmTsectPnt* pt(Rd& rd)
{
int64_t i = rd.idx(0, (int64_t)pts.size() - 1);
return pts[(size_t)i];
}
// `has idx`: a point or NULL
SmTsectPnt* opt_pt(Rd& rd)
{
bool has = rd.flag();
SmTsectPnt* p = pt(rd);
return has ? p : NULL;
}
double index_of(const SmTsectPnt* p) const
{
for (size_t i = 0; i < pts.size(); i++)
if (pts[i] == p)
return (double)i;
return -1.0;
}
// a program point's index, or `-1` and the point's values
void put_ref(std::vector<double>& o, const SmTsectPnt* p) const
{
double i = index_of(p);
o.push_back(i);
if (i < 0.0)
put_point(o, *p);
}
};
// The intersector with its protected state reachable.
struct Peek : SmSurfaceIntersector
{
Peek(const SmSurface& a, const SmExtent2d& da, const SmSurface& b, const SmExtent2d& db, SmBoolean f)
: SmSurfaceIntersector(a, da, b, db, f)
{
}
void set_context(const SmContext& c) { m_cpContext = &c; }
void set_members(double trace_dir, double approx_tol, double start_tol, double step_tol, double ang_tol)
{
m_dCurveTraceDirection = trace_dir;
m_dThisApproxTol3d = approx_tol;
m_dStartParallelTolRadians = start_tol;
m_dSteppingParallelTolRadians = step_tol;
m_dThisAngTolRad = ang_tol;
}
void set_ends(SmTsectPnt* s, SmTsectPnt* e)
{
m_pStartPoint = s;
m_pEndPoint = e;
}
void set_ext(bool clip, double last, double dist)
{
m_bDoingExtensionClipping = clip ? TRUE : FALSE;
m_dLastThroughParmeter = last;
m_dExtensionDistance = dist;
}
bool no_curve_points() const { return m_vCurvePoints.GetFirstNode() == NULL; }
// on one of the three lists (a list's Init() leaves stale links in its former nodes)
bool on_list(SmTsectPnt* p) const
{
return m_vCurvePoints.IsInList(p) || m_vStartPoints.IsInList(p) || m_vThroughPoints.IsInList(p);
}
SmStatus trace(SmTsectPnt& p, SmTArray<SmCurve*>& c3) { return TraceCurve(p, c3); }
SmStatus flush(SmTArray<SmCurve*>& c3, SmTArray<SmCurve*>& c0, SmTArray<SmCurve*>& c1,
SmTArray<SmTsectCurveType>& t, SmTArray<double>& d)
{
return FlushCurve(c3, c0, c1, t, d);
}
SmStatus interior(SmTArray<SmCurve*>& c3, SmTArray<SmCurve*>& c0, SmTArray<SmCurve*>& c1,
SmTArray<SmTsectCurveType>& t, SmTArray<double>& d)
{
return FindInteriorCurves(c3, c0, c1, t, d);
}
void put_list(std::vector<double>& o, const Prog& pr, const SmTList<SmTsectPnt>& l) const
{
SmTsectPnt* aData[70];
SmTArray<SmTsectPnt*> a(70, aData);
l.GetAllNodes(a);
o.push_back((double)a.GetSize());
for (ULONG i = 0; i < a.GetSize(); i++)
pr.put_ref(o, a[i]);
}
// `approx_tol trace_dir start_tol step_tol ang_tol last_through ext_dist ext_clip closed
// clipped interior start_point end_point curve_list start_list through_list npoints points`
void put_state(std::vector<double>& o, const Prog& pr) const
{
o.push_back(m_dThisApproxTol3d);
o.push_back(m_dCurveTraceDirection);
o.push_back(m_dStartParallelTolRadians);
o.push_back(m_dSteppingParallelTolRadians);
o.push_back(m_dThisAngTolRad);
o.push_back(m_dLastThroughParmeter);
o.push_back(m_dExtensionDistance);
o.push_back(flg(m_bDoingExtensionClipping));
o.push_back(flg(m_bCurveIsClosed));
o.push_back(flg(m_bClippedByThroughPoint));
o.push_back(flg(m_bCheckForInteriorCurves));
o.push_back(m_pStartPoint ? pr.index_of(m_pStartPoint) : -3.0);
o.push_back(m_pEndPoint ? pr.index_of(m_pEndPoint) : -3.0);
put_list(o, pr, m_vCurvePoints);
put_list(o, pr, m_vStartPoints);
put_list(o, pr, m_vThroughPoints);
o.push_back((double)pr.pts.size());
for (const SmTsectPnt* p : pr.pts)
put_point(o, *p);
}
};
// The accumulated FlushCurve outputs: `out3d` starts with the program's curves.
struct Outs
{
SmCurve* a3[8];
SmCurve* a0[8];
SmCurve* a1[8];
SmTsectCurveType at[8];
double ad[8];
SmTArray<SmCurve*> c3;
SmTArray<SmCurve*> c0;
SmTArray<SmCurve*> c1;
SmTArray<SmTsectCurveType> types;
SmTArray<double> devs;
ULONG first;
Outs() : c3(8, a3), c0(8, a0), c1(8, a1), types(8, at), devs(8, ad), first(0) {}
~Outs()
{
for (ULONG i = first; i < c3.GetSize(); i++)
delete c3[i];
for (ULONG i = 0; i < c0.GetSize(); i++)
delete c0[i];
for (ULONG i = 0; i < c1.GetSize(); i++)
delete c1[i];
}
void put(std::vector<double>& o) const
{
put_curves(o, c3, first);
put_curves(o, c0, 0);
put_curves(o, c1, 0);
o.push_back((double)types.GetSize());
for (ULONG i = 0; i < types.GetSize(); i++)
o.push_back((double)types[i]);
o.push_back((double)devs.GetSize());
for (ULONG i = 0; i < devs.GetSize(); i++)
o.push_back(devs[i]);
}
};
void run_op(int32_t op, Rd& rd, Prog& pr, Peek& si, Outs& outs, std::vector<double>& o)
{
switch (op)
{
case OP_STATE:
o.push_back(0.0);
si.put_state(o, pr);
return;
case OP_POINT:
{
SmTsectPnt* p = pr.pt(rd);
o.push_back(0.0);
put_point(o, *p);
return;
}
case OP_ADD_CURVE:
case OP_ADD_START:
case OP_ADD_THROUGH:
{
SmTsectPnt* p = pr.pt(rd);
// linking a point that is on a list corrupts the intrusive lists
if (si.on_list(p))
{
o.push_back(REJECTED);
return;
}
SmStatus s = op == OP_ADD_CURVE ? si.AddPointToCurve(*p)
: op == OP_ADD_START ? si.AddStartPoint(*p)
: si.AddThroughPoint(*p);
o.push_back(st(s));
return;
}
case OP_CPV:
{
SmPoint2d uvs[2];
uvs[0] = rd.uv();
uvs[1] = rd.uv();
SmTsectPnt* p = pr.pt(rd);
SmTsectPnt* prev = pr.opt_pt(rd);
bool has_step = rd.flag();
double step = rd.num();
if (prev == p)
throw Bad();
o.push_back(st(si.ComputePointValues(uvs, *p, prev, has_step ? &step : NULL)));
put_point(o, *p);
return;
}
case OP_MEMBERS:
{
double td = rd.num();
double at = rd.num();
double sp = rd.num();
double stp = rd.num();
double ang = rd.num();
si.set_members(td, at, sp, stp, ang);
o.push_back(0.0);
return;
}
case OP_ENDS:
{
SmTsectPnt* s = pr.opt_pt(rd);
SmTsectPnt* e = pr.opt_pt(rd);
si.set_ends(s, e);
o.push_back(0.0);
return;
}
case OP_EXT:
{
bool clip = rd.flag();
double last = rd.num();
double dist = rd.num();
si.set_ext(clip, last, dist);
o.push_back(0.0);
return;
}
case OP_TRACE:
{
SmTsectPnt* p = pr.pt(rd);
// a start point that is appended while it is on a list corrupts the lists
if (si.no_curve_points() && si.on_list(p))
{
o.push_back(REJECTED);
return;
}
o.push_back(st(si.trace(*p, outs.c3)));
return;
}
case OP_FLUSH:
{
SmStatus s = si.flush(outs.c3, outs.c0, outs.c1, outs.types, outs.devs);
o.push_back(st(s));
outs.put(o);
return;
}
case OP_FIC:
{
SmStatus s = si.interior(outs.c3, outs.c0, outs.c1, outs.types, outs.devs);
o.push_back(st(s));
outs.put(o);
return;
}
case OP_DPI:
case OP_DLI:
{
int64_t n = rd.idx(0, 8);
std::vector<SmTsectPnt*> v;
bool bad_points = false;
for (int64_t i = 0; i < n; i++)
{
SmTsectPnt* p = pr.pt(rd);
// a point twice, or one on a list: linked (again) in place
for (SmTsectPnt* q : v)
if (q == p)
bad_points = true;
if (si.on_list(p))
bad_points = true;
v.push_back(p);
}
SmTsectPnt* aData[8];
SmTArray<SmTsectPnt*> pts(8, aData);
for (SmTsectPnt* p : v)
pts.Add(p);
SmBSplineCurve* c3 = NULL;
SmBSplineCurve* c0 = NULL;
SmBSplineCurve* c1 = NULL;
SmTsectCurveType type = (SmTsectCurveType)-7;
double dev = -7.0;
SmStatus s;
if (op == OP_DPI)
{
bool before = rd.flag();
bool after = rd.flag();
bool has_sd = rd.flag();
SmVector3d sd = rd.vec();
bool has_ed = rd.flag();
SmVector3d ed = rd.vec();
bool has_tol = rd.flag();
SmApproxTol3d tol = SM_CAST_APPROXTOL3D(rd.num());
bool has_ang = rd.flag();
double ang = rd.num();
if (bad_points)
{
o.push_back(REJECTED);
return;
}
s = si.DoPointIntersection(pr.ctx, pts, before ? TRUE : FALSE, after ? TRUE : FALSE, has_sd ? &sd : NULL,
has_ed ? &ed : NULL, has_tol ? &tol : NULL, has_ang ? &ang : NULL, c3, c0, c1,
type, dev);
}
else
{
bool orient = rd.flag();
if (pr.curves.empty())
throw Bad();
SmCurve* crv = pr.curves[(size_t)rd.idx(0, (int64_t)pr.curves.size() - 1)];
double a = rd.num();
double b = rd.num();
SmExtent1d ivl(a, b);
double so[2];
so[0] = rd.num();
so[1] = rd.num();
bool avg = rd.flag();
bool has_tol = rd.flag();
SmApproxTol3d tol = SM_CAST_APPROXTOL3D(rd.num());
if (bad_points)
{
o.push_back(REJECTED);
return;
}
SmFilletLaw law;
s = si.DoLawIntersection(pr.ctx, pts, law, orient ? TRUE : FALSE, *crv, ivl, so, avg ? TRUE : FALSE, FALSE,
has_tol ? &tol : NULL, NULL, c3, c0, c1, type, dev);
}
o.push_back(st(s));
// `has3d [curve] has0 [curve] has1 [curve] type dev` (status 0 only)
if (s == SM_SUCCESS)
{
const SmBSplineCurve* cs[3] = {c3, c0, c1};
for (const SmBSplineCurve* c : cs)
{
o.push_back(c ? 1.0 : 0.0);
if (c)
put_bspline(o, c);
}
o.push_back((double)type);
o.push_back(dev);
}
delete c3;
delete c0;
delete c1;
return;
}
default:
throw Bad();
}
}
} // namespace
// Program: `surfA surfB` (surfB `2` = the same object as A), `dom1[4] dom2[4] fillet`,
// `ncurves curves`, `npoints points`, `nops ops`. Output: per op `op status [values]`, then the
// final state.
SC_EXPORT int32_t sc_intersect_surface_intersector_p4_run(const double* in, int64_t n_in, double* out,
int64_t cap, int64_t* out_len)
{
if (in == NULL || n_in < 2)
return SC_ERR_ARG;
try
{
SmContext ctx;
std::vector<double> o;
Rd rd{in, n_in, 0};
SmSurface* a = read_surface(rd, ctx);
SmSurface* b = NULL;
bool same = false;
if (rd.at < rd.n && rd.in[rd.at] == 2.0)
{
rd.at++;
same = true;
b = a;
}
else
b = read_surface(rd, ctx);
SmExtent2d d1 = rd.dom4();
SmExtent2d d2 = rd.dom4();
bool fillet = rd.flag();
Prog pr{ctx, {}, {}};
int64_t nc = rd.idx(0, 8);
for (int64_t i = 0; i < nc; i++)
pr.curves.push_back(read_curve(rd, ctx));
int64_t np = rd.idx(0, 64);
for (int64_t i = 0; i < np; i++)
{
SmTsectPnt* t = new SmTsectPnt();
pr.pts.push_back(t);
read_point(rd, *t);
}
int64_t nops = rd.idx(0, 64);
{
Peek si(*a, d1, *b, d2, fillet ? TRUE : FALSE);
si.set_context(ctx);
Outs outs;
for (SmCurve* c : pr.curves)
outs.c3.Add(c);
outs.first = outs.c3.GetSize();
for (int64_t i = 0; i < nops; i++)
{
int32_t op = (int32_t)rd.idx(1, OP_LAST);
o.push_back((double)op);
run_op(op, rd, pr, si, outs, o);
}
si.put_state(o, pr);
// unlink every point before the lists go away
for (SmTsectPnt* t : pr.pts)
t->ReSet();
si.set_ends(NULL, NULL);
}
if (!same)
delete b;
delete a;
return sc_copy_out(o.data(), (int64_t)o.size(), out, cap, out_len);
}
catch (const Bad&)
{
return SC_ERR_ARG;
}
catch (...)
{
return SC_ERR_EXCEPTION;
}
}
+374
View File
@@ -0,0 +1,374 @@
// SPDX-License-Identifier: Apache-2.0
// difftest shim for unit `query.loop`: SmLoop::ContainsUVPoint (source/SMLib/src/SmLoop.cpp)
// on a one-face B-rep built with SmBrepConstructor (source/SMLib/src/SmBrepData.cpp), the
// way tests/prog_test/src/smTopoTest.cpp builds its periodic-seam faces: every edge gets its
// 3D curve and interval, every edge use of the loop gets an explicit 2D B-spline UV trim
// curve (SmBrepConstructor::SetEdgeCurve(.., TRUE)), and the face and edge zone tolerances
// are set explicitly (SetTolerance(t, FALSE, FALSE)).
//
// One entry point, sc_query_loop_contains(in, n_in, out): the layout is documented in
// crates/solidcore-ffi/src/units/query_loop.rs, which validates every count, index, kind
// and value before the call. `out[5]` receives: the build status (0 = built, else the
// failing step: 1 surface, 3 constructor step, 4 edge use order or orientation differs
// from the request, 5 the surface cannot evaluate the point), the
// SmPointObjectContainmentType, the index of the on-edge (-1 = NULL), the edge parameter
// and the SmOrientType.
#include "sc_shim.h"
#include "StdAfx.h"
#include <SmBSplineCurve.h>
#include <SmBSplineSurface.h>
#include <SmBrep.h>
#include <SmBrepData.h>
#include <SmCircle.h>
#include <SmContext.h>
#include <SmEdge.h>
#include <SmEdgeuse.h>
#include <SmExtent1d.h>
#include <SmExtent2d.h>
#include <SmFace.h>
#include <SmLine.h>
#include <SmLoop.h>
#include <SmLoopuse.h>
#include <SmNurbsCrv.h>
#include <SmNurbsSrf.h>
#include <SmPlane.h>
#include <SmSurfOfRevolution.h>
#include <SmTArray.h>
#include <SmVector3d.h>
#include <nurbs.h>
#include <vector>
namespace
{
struct Rd
{
const double* in;
int64_t at;
double num() { return in[at++]; }
int64_t idx() { return (int64_t)in[at++]; }
SmVector3d vec()
{
double x = num();
double y = num();
double z = num();
return SmVector3d(x, y, z);
}
};
// `n p m cpts(x y z w | x y w) knots` (`dim` 3 or 2; a 2D point has z = 0).
SmBSplineCurve* read_bspline_curve(Rd& rd, const SmContext& ctx, ULONG dim)
{
gw_INDEX n = (gw_INDEX)rd.idx();
gw_DEGREE p = (gw_DEGREE)rd.idx();
gw_INDEX m = (gw_INDEX)rd.idx();
gw_CURVE* cur = sm_AllocateNurbCurve(n, p, m);
for (gw_INDEX i = 0; i <= n; i++)
{
cur->pol->Pw[i].x = rd.num();
cur->pol->Pw[i].y = rd.num();
cur->pol->Pw[i].z = dim == 3 ? rd.num() : 0.0;
cur->pol->Pw[i].w = rd.num();
}
for (gw_INDEX i = 0; i <= m; i++)
cur->knt->U[i] = rd.num();
return new (ctx) SmBSplineCurve(cur, dim, FALSE);
}
// A 3D curve block: `0 a[3] b[3]` SmLine(a, b), `1 c[3] x[3] y[3] d0 d1 r`
// SmCircle(c, x, y, [d0, d1], r) or `2 bspline` (see read_bspline_curve).
SmCurve* read_curve(Rd& rd, const SmContext& ctx)
{
int64_t kind = rd.idx();
if (kind == 0)
{
SmPoint3d a = rd.vec();
SmPoint3d b = rd.vec();
return new (ctx) SmLine(a, b, 3, &ctx);
}
if (kind == 1)
{
SmPoint3d c = rd.vec();
SmVector3d x = rd.vec();
SmVector3d y = rd.vec();
double d0 = rd.num();
double d1 = rd.num();
double r = rd.num();
return new (ctx) SmCircle(c, x, y, SmExtent1d(d0, d1), r, 3, &ctx);
}
return read_bspline_curve(rd, ctx, 3);
}
// A surface block: `0 O[3] X[3] Y[3] s[2] d[4]` SmPlane(O, X, Y, s, SmExtent2d(d0, d1, d2, d3)), `1 n m p q r s
// net((n+1)(m+1) x y z w) U V` SmBSplineSurface, `2 gen P[3] A[3] swap`
// SmSurfOfRevolution::CreateCanonical with the generator a line `0 a[3] b[3]` (its NURB
// made) or a circle `1 c[3] x[3] y[3] d0 d1 r`. NULL on failure.
SmSurface* read_surface(Rd& rd, const SmContext& ctx)
{
int64_t kind = rd.idx();
if (kind == 0)
{
SmPoint3d o = rd.vec();
SmVector3d x = rd.vec();
SmVector3d y = rd.vec();
double su = rd.num();
double sv = rd.num();
double a = rd.num();
double b = rd.num();
double c = rd.num();
double d = rd.num();
return new (ctx) SmPlane(o, x, y, SmVector2d(su, sv), SmExtent2d(a, b, c, d), &ctx);
}
if (kind == 1)
{
gw_INDEX n = (gw_INDEX)rd.idx();
gw_INDEX m = (gw_INDEX)rd.idx();
gw_DEGREE p = (gw_DEGREE)rd.idx();
gw_DEGREE q = (gw_DEGREE)rd.idx();
gw_INDEX r = (gw_INDEX)rd.idx();
gw_INDEX s = (gw_INDEX)rd.idx();
gw_SURFACE* sur = sm_AllocateNurbSurface(n, m, p, q, r, s);
for (gw_INDEX i = 0; i <= n; i++)
for (gw_INDEX j = 0; j <= m; j++)
{
sur->net->Pw[i][j].x = rd.num();
sur->net->Pw[i][j].y = rd.num();
sur->net->Pw[i][j].z = rd.num();
sur->net->Pw[i][j].w = rd.num();
}
for (gw_INDEX i = 0; i <= r; i++)
sur->knu->U[i] = rd.num();
for (gw_INDEX i = 0; i <= s; i++)
sur->knv->U[i] = rd.num();
return new (ctx) SmBSplineSurface(sur, FALSE, &ctx);
}
int64_t gk = rd.idx();
SmBSplineCurve* gen = NULL;
if (gk == 0)
{
SmPoint3d a = rd.vec();
SmPoint3d b = rd.vec();
SmLine* l = new (ctx) SmLine(a, b, 3, &ctx);
l->GetOrCreateGwNurbPointer();
gen = l;
}
else
{
SmPoint3d c = rd.vec();
SmVector3d x = rd.vec();
SmVector3d y = rd.vec();
double d0 = rd.num();
double d1 = rd.num();
double r = rd.num();
gen = new (ctx) SmCircle(c, x, y, SmExtent1d(d0, d1), r, 3, &ctx);
}
SmPoint3d pt = rd.vec();
SmVector3d ax = rd.vec();
bool swap = rd.num() != 0.0;
SmSurfOfRevolution* rev = NULL;
if (SmSurfOfRevolution::CreateCanonical(ctx, gen, pt, ax, rev, swap ? TRUE : FALSE) != SM_SUCCESS)
{
delete rev;
return NULL;
}
return rev;
}
// Curves not (yet) handed to the B-rep; deleted on every return.
struct Owned
{
std::vector<SmCurve*> curves;
std::vector<SmBSplineCurve*> trims;
~Owned()
{
for (size_t i = 0; i < curves.size(); i++)
delete curves[i];
for (size_t i = 0; i < trims.size(); i++)
delete trims[i];
}
};
int32_t run(Rd& rd, double* out)
{
SmContext ctx;
double face_tol = rd.num();
bool vertex_loop = rd.num() != 0.0;
SmPoint2d uv;
uv.x = rd.num();
uv.y = rd.num();
double tol3d = rd.num();
SmBrepConstructor b;
SmBrep* brep = b.StartBrep(ctx);
if (brep == NULL)
return SC_ERR_ARG;
SmObjDelete clean(brep);
out[0] = 3.0;
SmShell *outer = NULL, *inner = NULL;
if (b.StartShell(outer, inner) != SM_SUCCESS)
return SC_OK;
SmFace* face = b.StartFace(SM_OT_SAME);
if (face == NULL)
return SC_OK;
SmSurface* srf = read_surface(rd, ctx);
if (srf == NULL)
{
out[0] = 1.0;
return SC_OK;
}
if (b.SetFaceSurface(srf, srf->GetNaturalUVDomain()) != SM_SUCCESS)
return SC_OK;
{
// ContainsUVPoint ignores this status and reads the point: reject a failure
SmPoint3d chk;
if (srf->EvaluatePoint(uv, chk) != SM_SUCCESS)
{
out[0] = 5.0;
return SC_OK;
}
}
// edges: curve, interval, tolerance
int64_t n_edges = rd.idx();
Owned owned;
std::vector<SmCurve*>& curves = owned.curves;
std::vector<double> ivl, tols;
for (int64_t i = 0; i < n_edges; i++)
{
curves.push_back(read_curve(rd, ctx));
ivl.push_back(rd.num());
ivl.push_back(rd.num());
tols.push_back(rd.num());
}
std::vector<SmEdge*> edges((size_t)n_edges, (SmEdge*)NULL);
// edge uses in loop order: edge index, orientation, UV trim curve
int64_t n_eus = rd.idx();
std::vector<int64_t> eu_edge;
std::vector<SmOrientType> eu_or;
std::vector<SmBSplineCurve*>& trims = owned.trims;
for (int64_t i = 0; i < n_eus; i++)
{
eu_edge.push_back(rd.idx());
eu_or.push_back(rd.idx() == 1 ? SM_OT_SAME : SM_OT_OPPOSITE);
trims.push_back(read_bspline_curve(rd, ctx, 2));
}
SmLoop* loop = NULL;
std::vector<SmEdgeuse*> made((size_t)n_eus, (SmEdgeuse*)NULL);
if (vertex_loop)
{
loop = b.StartAndEndSingleVertexLoop(SmPoint3d(0, 0, 0));
if (loop == NULL)
return SC_OK;
}
else
{
loop = b.StartLoop(SM_OT_SAME);
if (loop == NULL)
return SC_OK;
// The loopuse starts at the edge use added last and walks the others in the
// order added: add L[1], .., L[n-1], L[0] to get L.
for (int64_t k = 0; k < n_eus; k++)
{
int64_t i = (k + 1) % n_eus;
int64_t e = eu_edge[(size_t)i];
SmEdge* existing = edges[(size_t)e];
SmEdge* edge = b.StartEdge(eu_or[(size_t)i], existing, NULL, NULL);
if (edge == NULL)
return SC_OK;
if (existing == NULL)
{
edges[(size_t)e] = edge;
SmCurve* c = curves[(size_t)e];
curves[(size_t)e] = NULL;
if (b.SetEdgeCurve(c, FALSE) != SM_SUCCESS)
return SC_OK;
if (b.SetLimits(SmVertexNode(ivl[(size_t)(2 * e)]), SmVertexNode(ivl[(size_t)(2 * e + 1)])) !=
SM_SUCCESS)
return SC_OK;
}
SmBSplineCurve* t = trims[(size_t)i];
trims[(size_t)i] = NULL;
if (b.SetEdgeCurve(t, TRUE) != SM_SUCCESS)
return SC_OK;
SmTArray<SmEdgeuse*> eus;
loop->GetEdgeuses(eus);
made[(size_t)i] = NULL;
for (ULONG j = 0; j < eus.GetSize(); j++)
if (eus[j]->GetUVTrimCurvePointer() == t)
made[(size_t)i] = eus[j];
if (b.EndEdge() != SM_SUCCESS)
return SC_OK;
}
}
// (StartAndEndSingleVertexLoop already ended its loop)
if ((!vertex_loop && b.EndLoop() != SM_SUCCESS) || b.EndFace() != SM_SUCCESS || b.EndShell(FALSE) != SM_SUCCESS ||
b.EndBrep() != SM_SUCCESS)
return SC_OK;
// the realised loop must be the requested one
out[0] = 4.0;
if (!vertex_loop)
{
SmTArray<SmEdgeuse*> eus;
loop->GetEdgeuses(eus);
if ((int64_t)eus.GetSize() != n_eus)
return SC_OK;
for (int64_t i = 0; i < n_eus; i++)
{
SmEdgeuse* eu = eus[(ULONG)i];
if (eu != made[(size_t)i] || eu->GetOrientation() != eu_or[(size_t)i] ||
eu->GetEdge() != edges[(size_t)eu_edge[(size_t)i]] || eu->GetUVTrimCurve() == NULL)
return SC_OK;
}
}
else
{
SmTArray<SmEdgeuse*> eus;
loop->GetEdgeuses(eus);
if (eus.GetSize() != 0)
return SC_OK;
}
// tolerances (SM_USE_OLDTOL: stored on the face and the edges)
face->SetTolerance(face_tol, FALSE, FALSE);
for (int64_t e = 0; e < n_edges; e++)
if (edges[(size_t)e] != NULL)
edges[(size_t)e]->SetTolerance(tols[(size_t)e], FALSE, FALSE);
out[0] = 0.0;
SmEdge* on = NULL;
double param = 0.0;
SmOrientType orient = SM_OT_UNKNOWN;
SmPointObjectContainmentType cls = loop->ContainsUVPoint(uv, tol3d, on, param, orient);
int64_t on_idx = -1;
for (int64_t e = 0; e < n_edges; e++)
if (on != NULL && edges[(size_t)e] == on)
{
on_idx = e;
break;
}
if (on != NULL && on_idx < 0)
on_idx = -2;
out[1] = (double)cls;
out[2] = (double)on_idx;
out[3] = param;
out[4] = (double)orient;
return SC_OK;
}
} // namespace
SC_EXPORT int32_t sc_query_loop_contains(const double* in, int64_t n_in, double* out)
{
if (in == NULL || out == NULL || n_in < 1)
return SC_ERR_ARG;
SC_TRY
for (int i = 0; i < 5; i++)
out[i] = 0.0;
Rd rd{in, 0};
return run(rd, out);
SC_CATCH
}
File diff suppressed because it is too large Load Diff
+70 -73
View File
@@ -2,13 +2,14 @@
Generated from `plan/modules.toml` by `harness/plan/progress.py`; do not edit by hand.
**142 / 261 units ported** (89 green, 53 partial), **188,521 / 387,903 upstream lines (48.6%)**. 16 claimed, 0 blocked, 103 todo.
**145 / 261 units ported** (89 green, 56 partial), **192,078 / 387,903 upstream lines (49.5%)**. 16 claimed, 0 blocked, 100 todo.
Legend: ✅ green (identical to C++) · 🟡 partial (passes; some cases wait on unported code) · 🔨 claimed · ▶️ ready (all dependencies done) · ⏳ claimable, dependencies still missing · ⛔ blocked.
## Ready to claim now
- [ ] ▶️ `surface.sphere` (2861 lines) — ready
- [ ] ▶️ `intersect.surface_intersector_p5` (622 lines) — ready
## Claimable now, dependencies still missing
@@ -28,93 +29,89 @@ Anything `todo` can be claimed. These need stubs for, or co-porting of, the list
- [ ] `intersect.curve_class_p4` (2022 lines) — missing: `intersect.curve_class_p2`, `intersect.curve_class_p7`
- [ ] `surface.surface_point_solve_p3` (1282 lines) — missing: `surface.surface_p4`, `surface.surface_point_solve_p2`
- [ ] `intersect.curve_class_p6` (2213 lines) — missing: `intersect.curve_class_p2`, `intersect.curve_class_p3`, `intersect.curve_class_p4`, `intersect.curve_class_p7`
- [ ] `topology.face_p2` (1827 lines) — missing: `surface.offset_surface`, `topology.edgeuse_p1`
- [ ] `topology.face_p2` (1827 lines) — missing: `surface.offset_surface`
- [ ] `intersect.curve_class_p5` (2017 lines) — missing: `intersect.curve_class_p2`, `intersect.curve_class_p3`, `intersect.curve_class_p6`, `intersect.curve_class_p7`
- [ ] `intersect.surface_intersector_p5` (622 lines) — missing: `intersect.surface_intersector_p4`
- [ ] `topology.edgeuse_p2` (600 lines) — missing: `topology.edgeuse_p1`, `topology.face_p2`
- [ ] `topology.shell` (750 lines) — missing: `topology.edgeuse_p1`, `topology.proto_topology_p1`
- [ ] `topology.edgeuse_p2` (600 lines) — missing: `topology.face_p2`
- [ ] `intersect.curve_class_p8` (1900 lines) — missing: `intersect.curve_class_p3`, `intersect.curve_class_p5`
- [ ] `intersect.curve_class_p9` (1220 lines) — missing: `intersect.curve_class_p3`, `intersect.curve_class_p5`
- [ ] `topology.vertexuse` (1429 lines) — missing: `topology.edgeuse_p1`, `topology.face_p2`
- [ ] `topology.vertex` (824 lines) — missing: `topology.edgeuse_p1`, `topology.proto_topology_p1`, `topology.vertexuse`
- [ ] `topology.loopuse` (892 lines) — missing: `topology.edgeuse_p1`, `topology.face_p2`, `topology.vertexuse`
- [ ] `topology.edge` (2213 lines) — missing: `topology.edgeuse_p1`, `topology.edgeuse_p2`, `topology.face_p2`, `topology.proto_topology_p1` +1 more
- [ ] `blend.fillet_custom_solver` (1236 lines) — missing: `topology.edgeuse_p1`
- [ ] `query.edge` (1025 lines) — missing: `topology.edge`, `topology.edgeuse_p1`
- [ ] `topology.edgeuse_p3` (2273 lines) — missing: `surface.surface_p5`, `topology.edge`, `topology.edgeuse_p1`
- [ ] `topology.trimming_tools` (2878 lines) — missing: `topology.edgeuse_p1`, `topology.face_p2`, `topology.loopuse`
- [ ] `heal.gap` (1475 lines) — missing: `topology.edgeuse_p1`, `topology.vertexuse`
- [ ] `topology.face_p4` (1812 lines) — missing: `intersect.curve_class_p7`, `topology.edgeuse_p1`, `topology.face_p2`, `topology.loopuse` +2 more
- [ ] `topology.vertexuse` (1429 lines) — missing: `topology.face_p2`
- [ ] `topology.vertex` (824 lines) — missing: `topology.proto_topology_p1`, `topology.vertexuse`
- [ ] `topology.loopuse` (892 lines) — missing: `topology.face_p2`, `topology.vertexuse`
- [ ] `topology.edge` (2213 lines) — missing: `topology.edgeuse_p2`, `topology.face_p2`, `topology.proto_topology_p1`, `topology.vertexuse`
- [ ] `query.edge` (1025 lines) — missing: `topology.edge`
- [ ] `topology.edgeuse_p3` (2273 lines) — missing: `surface.surface_p5`, `topology.edge`
- [ ] `topology.trimming_tools` (2878 lines) — missing: `topology.face_p2`, `topology.loopuse`
- [ ] `heal.gap` (1475 lines) — missing: `topology.vertexuse`
- [ ] `topology.face_p4` (1812 lines) — missing: `intersect.curve_class_p7`, `topology.face_p2`, `topology.loopuse`, `topology.vertex` +1 more
- [ ] `query.face_p2` (1161 lines) — missing: `topology.face_p2`, `topology.face_p4`
- [ ] `topology.brep_p10` (1890 lines) — missing: `topology.edge`, `topology.edgeuse_p1`, `topology.proto_topology_p1`, `topology.shell` +1 more
- [ ] `topology.face_p3` (1881 lines) — missing: `topology.edge`, `topology.edgeuse_p1`, `topology.edgeuse_p3`, `topology.face_p2` +3 more
- [ ] `topology.topology_traverser` (784 lines) — missing: `topology.edge`, `topology.edgeuse_p1`, `topology.face_p2`, `topology.vertex` +1 more
- [ ] `query.face_p3` (1620 lines) — missing: `query.face_p2`, `topology.edge`, `topology.edgeuse_p1`, `topology.face_p2` +2 more
- [ ] `topology.brep_p7` (2503 lines) — missing: `topology.brep_p10`, `topology.edge`, `topology.edgeuse_p1`, `topology.face_p2` +2 more
- [ ] `topology.face_p1` (2171 lines) — missing: `topology.edgeuse_p1`, `topology.face_p2`, `topology.face_p3`, `topology.face_p4` +2 more
- [ ] `query.face_p1` (465 lines) — missing: `query.face_p2`, `query.face_p3`, `topology.edge`, `topology.edgeuse_p1` +3 more
- [ ] `topology.brep_p10` (1890 lines) — missing: `topology.edge`, `topology.proto_topology_p1`, `topology.shell`, `topology.vertex`
- [ ] `topology.face_p3` (1881 lines) — missing: `topology.edge`, `topology.edgeuse_p3`, `topology.face_p2`, `topology.face_p4` +2 more
- [ ] `topology.topology_traverser` (784 lines) — missing: `topology.edge`, `topology.face_p2`, `topology.vertex`, `topology.vertexuse`
- [ ] `query.face_p3` (1620 lines) — missing: `query.face_p2`, `topology.edge`, `topology.face_p2`, `topology.face_p4` +1 more
- [ ] `topology.brep_p7` (2503 lines) — missing: `topology.brep_p10`, `topology.edge`, `topology.face_p2`, `topology.proto_topology_p1` +1 more
- [ ] `topology.face_p1` (2171 lines) — missing: `topology.face_p2`, `topology.face_p3`, `topology.face_p4`, `topology.loopuse` +1 more
- [ ] `query.face_p1` (465 lines) — missing: `query.face_p2`, `query.face_p3`, `topology.edge`, `topology.face_p2` +2 more
- [ ] `query.face_p4` (1432 lines) — missing: `query.face_p3`, `topology.face_p2`, `topology.loopuse`
- [ ] `query.tess` (838 lines) — missing: `query.poly`
- [ ] `topology.brep_p1` (2454 lines) — missing: `topology.edge`, `topology.edgeuse_p1`, `topology.edgeuse_p3`, `topology.face_p2` +5 more
- [ ] `topology.brep_p6` (1479 lines) — missing: `topology.edge`, `topology.edgeuse_p1`, `topology.edgeuse_p2`, `topology.edgeuse_p3` +6 more
- [ ] `topology.brep_p1` (2454 lines) — missing: `topology.edge`, `topology.edgeuse_p3`, `topology.face_p2`, `topology.loopuse` +4 more
- [ ] `topology.brep_p6` (1479 lines) — missing: `topology.edge`, `topology.edgeuse_p2`, `topology.edgeuse_p3`, `topology.face_p3` +5 more
- [ ] `heal.face_props` (1410 lines) — missing: `topology.face_p2`, `topology.loopuse`
- [ ] `topology.brep_cache` (389 lines) — missing: `topology.brep_p1`, `topology.edge`
- [ ] `topology.brep_p4` (2113 lines) — missing: `topology.brep_p1`, `topology.edge`, `topology.edgeuse_p1`, `topology.face_p1` +6 more
- [ ] `topology.brep_p9` (2443 lines) — missing: `topology.brep_p1`, `topology.edge`, `topology.edgeuse_p1`, `topology.face_p1` +7 more
- [ ] `topology.brep_p2` (2443 lines) — missing: `surface.offset_surface`, `topology.brep_cache`, `topology.brep_p1`, `topology.edge` +7 more
- [ ] `topology.brep_p5` (2193 lines) — missing: `topology.brep_p1`, `topology.brep_p4`, `topology.edge`, `topology.edgeuse_p1` +6 more
- [ ] `blend.fillet_geom_p1` (2113 lines) — missing: `surface.offset_surface`, `topology.brep_p1`, `topology.brep_p2`, `topology.edge` +5 more
- [ ] `topology.brep_p4` (2113 lines) — missing: `topology.brep_p1`, `topology.edge`, `topology.face_p1`, `topology.face_p2` +5 more
- [ ] `topology.brep_p9` (2443 lines) — missing: `topology.brep_p1`, `topology.edge`, `topology.face_p1`, `topology.face_p2` +6 more
- [ ] `topology.brep_p2` (2443 lines) — missing: `surface.offset_surface`, `topology.brep_cache`, `topology.brep_p1`, `topology.edge` +6 more
- [ ] `topology.brep_p5` (2193 lines) — missing: `topology.brep_p1`, `topology.brep_p4`, `topology.edge`, `topology.face_p2` +5 more
- [ ] `blend.fillet_geom_p1` (2113 lines) — missing: `surface.offset_surface`, `topology.brep_p1`, `topology.brep_p2`, `topology.edge` +4 more
- [ ] `heal.heal_data_p2` (2160 lines) — missing: `heal.face_props`, `topology.edge`, `topology.vertex`
- [ ] `mass.integrator` (1031 lines) — missing: `topology.brep_p1`, `topology.brep_p2`, `topology.edge`, `topology.edgeuse_p1` +2 more
- [ ] `mass.integrator` (1031 lines) — missing: `topology.brep_p1`, `topology.brep_p2`, `topology.edge`, `topology.face_p1` +1 more
- [ ] `query.brep` (517 lines) — missing: `topology.brep_p1`, `topology.brep_p2`
- [ ] `topology.brep_p3` (2503 lines) — missing: `topology.brep_p1`, `topology.brep_p2`, `topology.brep_p4`, `topology.brep_p9` +11 more
- [ ] `topology.brep_p3` (2503 lines) — missing: `topology.brep_p1`, `topology.brep_p2`, `topology.brep_p4`, `topology.brep_p9` +10 more
- [ ] `topology.proto_topology_p2` (2091 lines) — missing: `topology.edge`, `topology.face_p2`, `topology.proto_topology_p1`
- [ ] `blend.fillet_solver_p1` (1861 lines) — missing: `blend.fillet_geom_p1`, `surface.offset_surface`, `topology.edge`, `topology.edgeuse_p1` +1 more
- [ ] `blend.hcr` (1875 lines) — missing: `intersect.ray_tracer`, `topology.brep_p1`, `topology.edge`, `topology.edgeuse_p1` +1 more
- [ ] `blend.fillet_solver_p1` (1861 lines) — missing: `blend.fillet_geom_p1`, `surface.offset_surface`, `topology.edge`, `topology.vertex`
- [ ] `blend.hcr` (1875 lines) — missing: `intersect.ray_tracer`, `topology.brep_p1`, `topology.edge`, `topology.face_p2`
- [ ] `heal.heal_data_p1` (922 lines) — missing: `heal.heal_data_p2`, `topology.brep_p1`
- [ ] `heal.stitch_p1` (2037 lines) — missing: `topology.brep_p1`, `topology.brep_p5`, `topology.edge`, `topology.edgeuse_p1` +3 more
- [ ] `primitive.primitive_creation_p1` (2322 lines) — missing: `topology.brep_p1`, `topology.brep_p5`, `topology.brep_p8`, `topology.edge` +2 more
- [ ] `heal.stitch_p1` (2037 lines) — missing: `topology.brep_p1`, `topology.brep_p5`, `topology.edge`, `topology.face_p2` +2 more
- [ ] `primitive.primitive_creation_p1` (2322 lines) — missing: `topology.brep_p1`, `topology.brep_p5`, `topology.brep_p8`, `topology.edge` +1 more
- [ ] `query.api_queries` (809 lines) — missing: `query.brep`, `query.edge`, `topology.brep_p1`, `topology.edge`
- [ ] `topology.proto_topology_p3` (1577 lines) — missing: `topology.edge`, `topology.proto_topology_p1`, `topology.proto_topology_p2`
- [ ] `blend.fillet_intersector` (1708 lines) — missing: `blend.fillet_geom_p1`, `blend.fillet_solver_p1`, `topology.edge`
- [ ] `blend.fillet_solver_p5` (848 lines) — missing: `blend.fillet_geom_p1`, `blend.fillet_solver_p1`, `surface.offset_surface`, `topology.edge`
- [ ] `blend.fillet_standard_solver_p1` (2336 lines) — missing: `blend.fillet_solver_p1`, `surface.offset_surface`, `topology.edgeuse_p1`
- [ ] `blend.fillet_standard_solver_p1` (2336 lines) — missing: `blend.fillet_solver_p1`, `surface.offset_surface`
- [ ] `boolean.topology_intersector_p1` (2491 lines) — missing: `topology.edge`, `topology.face_p2`, `topology.face_p4`, `topology.proto_topology_p1` +3 more
- [ ] `heal.heal_data_p3` (1967 lines) — missing: `heal.face_props`, `heal.heal_data_p1`, `heal.heal_data_p2`, `topology.brep_p5` +3 more
- [ ] `heal.stitch_p2` (1906 lines) — missing: `heal.stitch_p1`, `topology.brep_p1`, `topology.edge`, `topology.face_p3` +1 more
- [ ] `blend.fillet_solver_p4` (1404 lines) — missing: `blend.fillet_geom_p1`, `blend.fillet_intersector`, `blend.fillet_solver_p1`, `surface.offset_surface` +2 more
- [ ] `blend.fillet_solver_p4` (1404 lines) — missing: `blend.fillet_geom_p1`, `blend.fillet_intersector`, `blend.fillet_solver_p1`, `surface.offset_surface` +1 more
- [ ] `boolean.topology_intersector_p2` (2195 lines) — missing: `boolean.topology_intersector_p1`, `topology.edge`, `topology.face_p2`, `topology.face_p4` +3 more
- [ ] `offset.offset_geometry_creation` (2652 lines) — missing: `surface.offset_surface`, `topology.edge`, `topology.edgeuse_p1`, `topology.vertex`
- [ ] `sweep.topology_sweep` (2456 lines) — missing: `heal.stitch_p1`, `topology.brep_p1`, `topology.brep_p3`, `topology.brep_p5` +6 more
- [ ] `boolean.topology_intersector_p3` (1849 lines) — missing: `boolean.topology_intersector_p1`, `boolean.topology_intersector_p2`, `topology.edge`, `topology.edgeuse_p1` +1 more
- [ ] `offset.offset_geometry_creation` (2652 lines) — missing: `surface.offset_surface`, `topology.edge`, `topology.vertex`
- [ ] `sweep.topology_sweep` (2456 lines) — missing: `heal.stitch_p1`, `topology.brep_p1`, `topology.brep_p3`, `topology.brep_p5` +5 more
- [ ] `boolean.topology_intersector_p3` (1849 lines) — missing: `boolean.topology_intersector_p1`, `boolean.topology_intersector_p2`, `topology.edge`, `topology.face_p2`
- [ ] `sweep.rotational_sweep_geometry` (1542 lines) — missing: `sweep.topology_sweep`, `topology.edge`
- [ ] `sweep.trans_sweep_geometry` (817 lines) — missing: `sweep.topology_sweep`, `topology.edge`
- [ ] `boolean.merge_p1` (993 lines) — missing: `boolean.topology_intersector_p1`, `topology.brep_p1`, `topology.edge`, `topology.edgeuse_p1` +4 more
- [ ] `boolean.merge_p1` (993 lines) — missing: `boolean.topology_intersector_p1`, `topology.brep_p1`, `topology.edge`, `topology.face_p2` +3 more
- [ ] `primitive.primitive_creation_p3` (1842 lines) — missing: `primitive.primitive_creation_p1`, `sweep.topology_sweep`, `topology.brep_p1`, `topology.brep_p5` +2 more
- [ ] `blend.fillet_executive_p3` (1608 lines) — missing: `blend.fillet_solver_p1`, `boolean.merge_p1`, `boolean.topology_intersector_p1`, `heal.stitch_p1` +3 more
- [ ] `boolean.merge_p2` (1967 lines) — missing: `boolean.merge_p1`, `boolean.topology_intersector_p1`, `boolean.topology_intersector_p2`, `heal.stitch_p1` +7 more
- [ ] `boolean.merge_p2` (1967 lines) — missing: `boolean.merge_p1`, `boolean.topology_intersector_p1`, `boolean.topology_intersector_p2`, `heal.stitch_p1` +6 more
- [ ] `boolean.merge_p5` (475 lines) — missing: `boolean.merge_p1`, `boolean.merge_p2`, `boolean.topology_intersector_p1`, `topology.edge`
- [ ] `boolean.merge_p4` (2590 lines) — missing: `boolean.merge_p1`, `boolean.merge_p2`, `boolean.merge_p5`, `boolean.topology_intersector_p1` +7 more
- [ ] `boolean.merge_p4` (2590 lines) — missing: `boolean.merge_p1`, `boolean.merge_p2`, `boolean.merge_p5`, `boolean.topology_intersector_p1` +6 more
- [ ] `boolean.merge_p3` (1152 lines) — missing: `boolean.merge_p2`, `boolean.merge_p4`, `boolean.merge_p5`, `boolean.topology_intersector_p1` +2 more
- [ ] `blend.fillet_executive_p1` (2063 lines) — missing: `blend.fillet_executive_p3`, `blend.fillet_geom_p1`, `blend.fillet_solver_p1`, `blend.fillet_standard_solver_p1` +10 more
- [ ] `boolean.api_brep` (410 lines) — missing: `boolean.merge_p3`
- [ ] `blend.fillet_executive_p1` (2063 lines) — missing: `blend.fillet_executive_p3`, `blend.fillet_geom_p1`, `blend.fillet_solver_p1`, `blend.fillet_standard_solver_p1` +9 more
- [ ] `primitive.primitive_creation_p2` (2258 lines) — missing: `primitive.primitive_creation_p1`, `primitive.primitive_creation_p3`, `topology.brep_p1`, `topology.brep_p4` +3 more
- [ ] `blend.fillet_corner_p1` (1971 lines) — missing: `blend.fillet_executive_p1`, `blend.fillet_geom_p1`, `blend.fillet_solver_p1`, `blend.fillet_standard_solver_p1` +4 more
- [ ] `blend.fillet_solver_p2` (1939 lines) — missing: `blend.fillet_executive_p1`, `blend.fillet_geom_p1`, `blend.fillet_solver_p1`, `topology.edge` +2 more
- [ ] `blend.fillet_solver_p3` (1112 lines) — missing: `blend.fillet_geom_p1`, `blend.fillet_solver_p1`, `surface.offset_surface`, `topology.edge` +2 more
- [ ] `offset.offset_executive_p1` (2107 lines) — missing: `offset.offset_geometry_creation`, `surface.offset_surface`, `topology.brep_p1`, `topology.edge` +3 more
- [ ] `blend.fillet_corner_p1` (1971 lines) — missing: `blend.fillet_executive_p1`, `blend.fillet_geom_p1`, `blend.fillet_solver_p1`, `blend.fillet_standard_solver_p1` +3 more
- [ ] `blend.fillet_solver_p2` (1939 lines) — missing: `blend.fillet_executive_p1`, `blend.fillet_geom_p1`, `blend.fillet_solver_p1`, `topology.edge` +1 more
- [ ] `blend.fillet_solver_p3` (1112 lines) — missing: `blend.fillet_geom_p1`, `blend.fillet_solver_p1`, `surface.offset_surface`, `topology.edge` +1 more
- [ ] `offset.offset_executive_p1` (2107 lines) — missing: `offset.offset_geometry_creation`, `surface.offset_surface`, `topology.brep_p1`, `topology.edge` +2 more
- [ ] `primitive.api_primitives` (885 lines) — missing: `primitive.primitive_creation_p1`, `primitive.primitive_creation_p2`
- [ ] `sweep.api_brep` (529 lines) — missing: `primitive.primitive_creation_p2`, `primitive.primitive_creation_p3`
- [ ] `blend.fillet_geom_p3` (1032 lines) — missing: `blend.fillet_geom_p1`, `blend.fillet_solver_p1`, `surface.offset_surface`, `topology.edge` +3 more
- [ ] `blend.fillet_geom_p4` (2074 lines) — missing: `blend.fillet_corner_p1`, `blend.fillet_geom_p1`, `blend.fillet_solver_p1`, `topology.edge` +2 more
- [ ] `blend.fillet_geom_p6` (1813 lines) — missing: `blend.fillet_geom_p1`, `blend.fillet_solver_p1`, `topology.brep_p3`, `topology.edge` +2 more
- [ ] `blend.fillet_standard_solver_p2` (1989 lines) — missing: `blend.fillet_executive_p1`, `blend.fillet_geom_p1`, `blend.fillet_solver_p1`, `blend.fillet_solver_p3` +4 more
- [ ] `offset.offset_executive_p2` (1846 lines) — missing: `offset.offset_executive_p1`, `offset.offset_geometry_creation`, `topology.brep_p1`, `topology.brep_p2` +5 more
- [ ] `blend.fillet_corner_p3` (1697 lines) — missing: `blend.fillet_corner_p1`, `blend.fillet_executive_p1`, `blend.fillet_geom_p1`, `blend.fillet_solver_p1` +3 more
- [ ] `blend.fillet_geom_p3` (1032 lines) — missing: `blend.fillet_geom_p1`, `blend.fillet_solver_p1`, `surface.offset_surface`, `topology.edge` +2 more
- [ ] `blend.fillet_geom_p4` (2074 lines) — missing: `blend.fillet_corner_p1`, `blend.fillet_geom_p1`, `blend.fillet_solver_p1`, `topology.edge` +1 more
- [ ] `blend.fillet_geom_p6` (1813 lines) — missing: `blend.fillet_geom_p1`, `blend.fillet_solver_p1`, `topology.brep_p3`, `topology.edge` +1 more
- [ ] `blend.fillet_standard_solver_p2` (1989 lines) — missing: `blend.fillet_executive_p1`, `blend.fillet_geom_p1`, `blend.fillet_solver_p1`, `blend.fillet_solver_p3` +3 more
- [ ] `offset.offset_executive_p2` (1846 lines) — missing: `offset.offset_executive_p1`, `offset.offset_geometry_creation`, `topology.brep_p1`, `topology.brep_p2` +4 more
- [ ] `blend.fillet_corner_p3` (1697 lines) — missing: `blend.fillet_corner_p1`, `blend.fillet_executive_p1`, `blend.fillet_geom_p1`, `blend.fillet_solver_p1` +2 more
- [ ] `blend.fillet_executive_p2` (1152 lines) — missing: `blend.fillet_executive_p1`, `blend.fillet_geom_p1`, `blend.fillet_solver_p1`, `blend.fillet_standard_solver_p1` +4 more
- [ ] `blend.fillet_geom_p2` (2077 lines) — missing: `blend.fillet_corner_p1`, `blend.fillet_geom_p1`, `blend.fillet_geom_p6`, `blend.fillet_solver_p1` +5 more
- [ ] `blend.fillet_geom_p5` (1952 lines) — missing: `blend.fillet_corner_p1`, `blend.fillet_geom_p1`, `blend.fillet_geom_p2`, `blend.fillet_geom_p4` +6 more
- [ ] `blend.fillet_corner_p2` (1892 lines) — missing: `blend.fillet_corner_p1`, `blend.fillet_geom_p1`, `blend.fillet_geom_p2`, `blend.fillet_solver_p1` +3 more
- [ ] `blend.fillet_geom_p2` (2077 lines) — missing: `blend.fillet_corner_p1`, `blend.fillet_geom_p1`, `blend.fillet_geom_p6`, `blend.fillet_solver_p1` +4 more
- [ ] `blend.fillet_geom_p5` (1952 lines) — missing: `blend.fillet_corner_p1`, `blend.fillet_geom_p1`, `blend.fillet_geom_p2`, `blend.fillet_geom_p4` +5 more
- [ ] `blend.fillet_corner_p2` (1892 lines) — missing: `blend.fillet_corner_p1`, `blend.fillet_geom_p1`, `blend.fillet_geom_p2`, `blend.fillet_solver_p1` +2 more
- [ ] `audit.recursion` (0 lines) — missing: `blend.fillet_corner_p2`, `blend.fillet_corner_p3`, `blend.hcr`, `boolean.api_brep` +13 more
## In progress
@@ -126,15 +123,15 @@ Anything `todo` can be claimed. These need stubs for, or co-porting of, the list
- [ ] 🔨 `surface.srf_in_volume` (779 lines) — in progress: #53 @froglemonade2 (ready for review)
- [ ] 🔨 `surface.surface_p3` (1794 lines) — in progress: #52 @froglemonade2 (ready for review)
- [ ] 🔨 `surface.offset_surface` (1787 lines) — in progress: #62 @Pluto-XI (draft)
- [ ] 🔨 `intersect.curve_class_p7` (2248 lines) — in progress: #65 @bherbruck (draft)
- [ ] 🔨 `intersect.curve_class_p7` (2248 lines) — in progress: #65 @bherbruck (ready for review)
- [ ] 🔨 `intersect.ray_tracer` (1246 lines) — in progress: #48 @froglemonade2 (ready for review)
- [ ] 🔨 `topology.proto_topology_p1` (2746 lines) — in progress: #67 @bherbruck (draft)
- [ ] 🔨 `surface.surface_point_solve_p2` (1882 lines) — in progress: #66 @bherbruck (ready for review)
- [ ] 🔨 `topology.edgeuse_p1` (1157 lines) — in progress: #55 @bherbruck (ready for review)
- [ ] 🔨 `intersect.surface_intersector_p4` (1570 lines) — in progress: #70 @bherbruck (ready for review)
- [ ] 🔨 `topology.shell` (750 lines) — in progress: #80 @0xlillith (draft)
- [ ] 🔨 `topology.brep_p8` (1768 lines) — in progress: #72 @bherbruck (ready for review)
- [ ] 🔨 `query.loop` (830 lines) — in progress: #63 @bherbruck (ready for review)
- [ ] 🔨 `blend.fillet_custom_solver` (1236 lines) — in progress: #78 @bherbruck (ready for review)
- [ ] 🔨 `query.poly` (2119 lines) — in progress: #73 @bherbruck (ready for review)
- [ ] 🔨 `boolean.api_brep` (410 lines) — in progress: #81 @bherbruck (draft)
## By module
@@ -292,16 +289,16 @@ Anything `todo` can be claimed. These need stubs for, or co-porting of, the list
- [ ] 🔨 `surface.surface_point_solve_p2` (1882 lines) — in progress: #66 @bherbruck (ready for review)
- [ ] ⏳ `surface.surface_point_solve_p3` (1282 lines) — waiting
### topology (3/31)
### topology (4/31)
- [x] ✅ `topology.database_io` (363 lines) — green, 2059 cases
- [x] ✅ `topology.topology` (389 lines) — green, 657 cases
- [ ] 🟡 `topology.brep_data` (1466 lines) — partial, 2379 cases
- [ ] 🔨 `topology.proto_topology_p1` (2746 lines) — in progress: #67 @bherbruck (draft)
- [ ] 🔨 `topology.edgeuse_p1` (1157 lines) — in progress: #55 @bherbruck (ready for review)
- [ ] 🟡 `topology.edgeuse_p1` (1157 lines) — partial, 2722 cases
- [ ] ⏳ `topology.face_p2` (1827 lines) — waiting
- [ ] ⏳ `topology.edgeuse_p2` (600 lines) — waiting
- [ ] ⏳ `topology.shell` (750 lines) — waiting
- [ ] 🔨 `topology.shell` (750 lines) — in progress: #80 @0xlillith (draft)
- [ ] ⏳ `topology.vertexuse` (1429 lines) — waiting
- [ ] ⏳ `topology.vertex` (824 lines) — waiting
- [ ] ⏳ `topology.loopuse` (892 lines) — waiting
@@ -326,14 +323,14 @@ Anything `todo` can be claimed. These need stubs for, or co-porting of, the list
- [ ] ⏳ `topology.proto_topology_p2` (2091 lines) — waiting
- [ ] ⏳ `topology.proto_topology_p3` (1577 lines) — waiting
### intersect (10/25)
### intersect (11/25)
- [ ] 🟡 `intersect.surface_tracer` (1900 lines) — partial, 427 cases
- [ ] 🟡 `intersect.point_class` (909 lines) — partial, 2511 cases
- [ ] 🟡 `intersect.curve_class_p1` (2087 lines) — partial, 2088 cases
- [ ] ⏳ `intersect.surface_drop_lib_p1` (1478 lines) — waiting
- [ ] 🟡 `intersect.surface_silhouette` (979 lines) — partial, 709 cases
- [ ] 🔨 `intersect.curve_class_p7` (2248 lines) — in progress: #65 @bherbruck (draft)
- [ ] 🔨 `intersect.curve_class_p7` (2248 lines) — in progress: #65 @bherbruck (ready for review)
- [ ] 🔨 `intersect.ray_tracer` (1246 lines) — in progress: #48 @froglemonade2 (ready for review)
- [ ] ⏳ `intersect.surface_drop_curve` (2014 lines) — waiting
- [ ] ⏳ `intersect.surface_drop_lib_p2` (1080 lines) — waiting
@@ -347,10 +344,10 @@ Anything `todo` can be claimed. These need stubs for, or co-porting of, the list
- [ ] ⏳ `intersect.curve_class_p4` (2022 lines) — waiting
- [ ] 🟡 `intersect.surface_intersector_p3` (1932 lines) — partial, 1527 cases
- [ ] ⏳ `intersect.curve_class_p6` (2213 lines) — waiting
- [ ] 🔨 `intersect.surface_intersector_p4` (1570 lines) — in progress: #70 @bherbruck (ready for review)
- [ ] 🟡 `intersect.surface_intersector_p4` (1570 lines) — partial, 1542 cases
- [ ] 🟡 `intersect.topology_solver_p2` (1391 lines) — partial, 2429 cases
- [ ] ⏳ `intersect.curve_class_p5` (2017 lines) — waiting
- [ ] ⏳ `intersect.surface_intersector_p5` (622 lines) — waiting
- [ ] ▶️ `intersect.surface_intersector_p5` (622 lines) — ready
- [ ] ⏳ `intersect.curve_class_p8` (1900 lines) — waiting
- [ ] ⏳ `intersect.curve_class_p9` (1220 lines) — waiting
@@ -362,10 +359,10 @@ Anything `todo` can be claimed. These need stubs for, or co-porting of, the list
- [ ] ⏳ `primitive.primitive_creation_p2` (2258 lines) — waiting
- [ ] ⏳ `primitive.api_primitives` (885 lines) — waiting
### query (0/10)
### query (1/10)
- [ ] ⏳ `query.edge` (1025 lines) — waiting
- [ ] 🔨 `query.loop` (830 lines) — in progress: #63 @bherbruck (ready for review)
- [ ] 🟡 `query.loop` (830 lines) — partial, 3040 cases
- [ ] ⏳ `query.face_p2` (1161 lines) — waiting
- [ ] ⏳ `query.face_p3` (1620 lines) — waiting
- [ ] 🔨 `query.poly` (2119 lines) — in progress: #73 @bherbruck (ready for review)
@@ -413,11 +410,11 @@ Anything `todo` can be claimed. These need stubs for, or co-porting of, the list
- [ ] ⏳ `boolean.merge_p5` (475 lines) — waiting
- [ ] ⏳ `boolean.merge_p4` (2590 lines) — waiting
- [ ] ⏳ `boolean.merge_p3` (1152 lines) — waiting
- [ ] ⏳ `boolean.api_brep` (410 lines) — waiting
- [ ] 🔨 `boolean.api_brep` (410 lines) — in progress: #81 @bherbruck (draft)
### blend (0/22)
- [ ] ⏳ `blend.fillet_custom_solver` (1236 lines) — waiting
- [ ] 🔨 `blend.fillet_custom_solver` (1236 lines) — in progress: #78 @bherbruck (ready for review)
- [ ] ⏳ `blend.fillet_geom_p1` (2113 lines) — waiting
- [ ] ⏳ `blend.fillet_solver_p1` (1861 lines) — waiting
- [ ] ⏳ `blend.hcr` (1875 lines) — waiting
+56
View File
@@ -3595,6 +3595,40 @@ cases, 169 deferred; `SmSurface::EvaluateNormal` and `DropVectors` now call the
being built), and the `SmIsoCurve` / `SmCrvOnSurf` readers of non-B-spline surfaces
(those curves hold an `SmBSplineSurface`).
## topology.edgeuse_p1 (`SmEdgeuse` / `SmFaceuse` part 1, 2026-10-14; partial, no disagreements)
- **B-rep import.** No Rust B-rep reader or builder exists yet (units `topology.brep_*`,
`topology.proto_topology_*`). The shim builds real `SmBrep`s (15 upstream `.smb` files
through `SmBrep::ReadFromFile`, optionally with `RebuildUVTrimCurves`; wire edges and
faces from B-spline curves/surfaces and `SmPlane`s; the wire network of prog_test
`my_test_topology`) and exports the graph in traversal order from raw members
(pointer-to-member access for the protected ones), never through the functions under
test. The runner imports it into `topology::Brep` and compares the ported functions'
results as traversal indices. UV trim curves are imported exactly: they are often
`SmLine`s (upstream converts linear UV curves), whose evaluation differs from a plain
B-spline's by an ulp (`Mouse.smb` gave `0` vs `2.2e-16` before lines were imported as
`Line`s, via `Line::from_parts`). Edge 3D curves of other kinds are imported as their
B-spline part; they are only evaluated before `SmSurface::DropPoint`, which is deferred.
Wire that import exactly when `surface.surface_p5` lands.
- **Not wired**, still `UNPORTED(topology.edgeuse_p1)`: `surface::trim_srf_cache`
(`SmEdgeuse::GetUVTrimCurve` of the cache's edge uses), `intersect::point_class`
(`SmEdgeuse::GetBrep`, `GetEdge`) and the `SmTol` object dispatch in `geom::tol`. Those
callers hold opaque object handles and no `Brep`, so the edge use record cannot be
reached; wiring needs them to carry the B-rep (or a resolver) first.
- **Co-ports.** The use records (`Region`, `Faceuse`, `Loopuse`, `Edgeuse`, `Vertexuse`),
the new skeleton fields and the small accessors of `topology.edge`, `topology.loopuse`,
`topology.vertexuse`, `topology.shell` and `topology.proto_topology_p1` are minimal; an
owner's intrusive circular list is a `Vec` in list order from its head, and an edge use's
`m_pNext` / `m_pLast` are its neighbours there. Their porters extend these (see the
CO-PORTED notes in `plan/modules.toml`).
- **Review fixes (PR #55).** FFI guards widened to what the C++ handles: values up to
`1e300` (was `1e6`) and `uv` within 100 domain lengths of a B-spline surface's domain
(plane `uv` up to `1e6`). Every previously rejected huge-scale or far-out-of-domain case
tried (points to `1e200`, nets scaled `1e6`/`1e8`, `uv` 50 domains out) agrees exactly.
The Seam case generator copied curve 0's knots onto curve 1 of a different degree
(invalid curve; `--seeds 6000` case `gen5286` was a harness error); it now requires
equal degrees.
## surface.surf_of_extrusion_p2 (`SmSurfOfExtrusion`, part 2, 2026-10-14)
- **Not a `SurfaceKind` yet.** `SurfOfExtrusion` does not join
@@ -3718,6 +3752,28 @@ cases, 169 deferred; `SmSurface::EvaluateNormal` and `DropVectors` now call the
so the `SurfOfRevolution` arm of `out_of_bounds()` (the base-flag-only
`SmTemporaryChangeValue`) is ported but not difftested; planes and B-spline surfaces are.
## intersect.surface_intersector_p4
- **No virtual dispatch in the intersector parts.** `SmSurfaceIntersector`'s
`TraceCurve` / `DoPointIntersection` reach `ComputeNextPoint`, `ComputePointValues`
(which calls `ComputeSingularityPoint` for parallel normals), `FlushCurve` and
`TestSpanAccuracy`, all virtual upstream. Parts 1-4 port them as methods of the basic
`SurfaceIntersector`, so they always run the basic versions. `SmAdvSurfaceIntersector`
overrides `ComputeSingularityPoint` (and `SmFilletIntersector` several more), so the
part 4 functions cannot replace the advanced intersector's `TraceCurve` stub
(`intersect/adv_surface_intersector.rs`) or `SmSurface::PointBasedSurfaceIntersect`'s
`DoPointIntersection` (`surface/surface/p2.rs`, run on an `SmAdvSurfaceIntersector`).
Those stubs stay `UNPORTED(intersect.surface_intersector_p4)` until the parts take a
trait for their virtual calls.
- **Real-value coverage is narrow until `surface.surface_p11`.** The basic intersector
refines every crossing, tangent-curve and singular point with
`SmSurface::LocalSurfaceIntersect`, and the basic `ComputeSingularityPoint` fails
(`SM_ERR`) for parallel normals. So on planes the traces that run without deferral
start at raw points of the types it does not refine (unknown, tangent, coincident,
touching), and `DoPointIntersection` on coincident or parallel planes fails in its
`ComputePointValues` (`SM_ERR`, compared with all evaluated point values).
`FindInteriorCurves` always defers (global surface solve).
## intersect.topology_solver_p2 (PR #60, 2026-10-14)
- **Scope.** All 15 owned functions have Rust bodies: the shared VertexEdge and
+15 -15
View File
@@ -2184,7 +2184,7 @@ tests = ["prog_test:my_test_extent3d", "prog_test:my_test_vector2d", "prog_test:
status = "todo"
claimed_by = ""
claimed_at = ""
notes = "180 fns; largest: sm_AreOverlapping, SmProtoTopologyManager::CanCombineEdgeDefinitions, SmProtoTopologyManager::RemoveCoincidentNoEdgeProtoVertex, sm_TransformIndependentObject; entry points: SmApiRotate, SmApiScale, SmApiScaleByPt, SmApiTransform, SmApiTranslate; cycle back-edges (call into later units; stub or co-port): query.poly(9), topology.edge(7), query.face_p2(4), blend.fillet_geom_p1(3), topology.edgeuse_p1(3), topology.vertex(3), topology.shell(2), boolean.topology_intersector_p1(1), topology.brep_p1(1), topology.brep_p2(1), topology.brep_p3(1), topology.brep_p4(1), topology.brep_p5(1), topology.face_p4(1), topology.vertexuse(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit"
notes = "180 fns; largest: sm_AreOverlapping, SmProtoTopologyManager::CanCombineEdgeDefinitions, SmProtoTopologyManager::RemoveCoincidentNoEdgeProtoVertex, sm_TransformIndependentObject; entry points: SmApiRotate, SmApiScale, SmApiScaleByPt, SmApiTransform, SmApiTranslate; cycle back-edges (call into later units; stub or co-port): query.poly(9), topology.edge(7), query.face_p2(4), blend.fillet_geom_p1(3), topology.edgeuse_p1(3), topology.vertex(3), topology.shell(2), boolean.topology_intersector_p1(1), topology.brep_p1(1), topology.brep_p2(1), topology.brep_p3(1), topology.brep_p4(1), topology.brep_p5(1), topology.face_p4(1), topology.vertexuse(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit; CO-PORTED in topology.edgeuse_p1 (PR #55): SmOwningTopology::IsKindOf -> topology::edgeuse::p1::owning_topology_is_kind_of"
[[unit]]
id = "intersect.adv_surface_intersector"
@@ -2265,10 +2265,10 @@ soft_depends_on = ["geom.vector2d", "nurbs.b_spline_curve_p1", "surface.surface_
est_lines = 1157
layer = 33
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 = "57 fns; largest: SmEdgeuse::SetUVTrimCurve, SmEdgeuse::CalcMaxEdgeFaceGap, SmFaceuse::CollectAdjacentFaceuses, SmFindStepOffENFO::Evaluate; cycle back-edges (call into later units; stub or co-port): heal.gap(14), topology.edge(13), topology.loopuse(9), topology.vertexuse(4), topology.face_p2(2), topology.shell(2), topology.edgeuse_p3(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit"
notes = "PARTIAL: difftest 2722 cases (2706 fixture: 1155 on 15 upstream .smb B-reps read by SmBrep::ReadFromFile, with and without RebuildUVTrimCurves; 1115 on prog_test my_test_topology's wire network, wire edges and faces from B-spline curves/surfaces and real SmPlanes, plus the geometry statics on B-splines and SmPlanes; 436 review adversarial in fixtures/topology.edgeuse_p1/adversarial.json: pole/pinch, tiny and huge scale (to 1e200), uv up to 100 domain lengths outside, 1% seam tolerance edge, null/drop-curve SetUVTrimCurve, rebuilt .smb gaps; 16 generated, 6000 more checked once), 2449 pass, 273 deferred (heal.gap 206, topology.edge 32, surface.surface_p5 28, topology.edgeuse_p3 7), max_rel_err 0; --perturb SENSITIVE. There is no Rust B-rep reader/builder yet, so the runner imports the graph the shim exports (raw members, traversal order) into topology::Brep. Co-ports the minimal use records into the topology skeleton (see CO-PORTED notes of topology.edge, topology.loopuse, topology.vertexuse, topology.shell, topology.vertex, topology.face_p2, topology.brep_p1, heal.gap, topology.proto_topology_p1); adds curve::line::Line::from_parts (raw constructor for the import). SmBrep::ReadFromFile crashes on SmallBox.smb and Platform.smb (plan/findings.md), so they are not used. Back-edge stubs UNPORTED(topology.edgeuse_p1) in surface::trim_srf_cache (GetUVTrimCurve), intersect::point_class (GetBrep, GetEdge) and geom::tol (GetZoneTol3d dispatch) are not wired: those callers hold opaque object handles without a Brep (plan/findings.md). Plan notes: 57 fns; largest: SmEdgeuse::SetUVTrimCurve, SmEdgeuse::CalcMaxEdgeFaceGap, SmFaceuse::CollectAdjacentFaceuses, SmFindStepOffENFO::Evaluate; cycle back-edges (call into later units; stub or co-port): heal.gap(14), topology.edge(13), topology.loopuse(9), topology.vertexuse(4), topology.face_p2(2), topology.shell(2), topology.edgeuse_p3(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit"
[[unit]]
id = "intersect.curve_class_p3"
@@ -2349,10 +2349,10 @@ soft_depends_on = ["curve.curve_cache", "curve.curve_p2", "curve.hermite_curve",
est_lines = 1570
layer = 35
tests = ["prog_test:my_test_parallel_projection", "prog_test:my_test_tsurf_projection", "prog_test:my_test_surface_interface", "prog_test:my_test_ssi", "prog_test:my_test_tsurf_section", "prog_test:my_test_planar_section"]
status = "todo"
status = "partial"
claimed_by = ""
claimed_at = ""
notes = "4 fns; largest: SmSurfaceIntersector::TraceCurve, SmSurfaceIntersector::DoPointIntersection, SmSurfaceIntersector::DoLawIntersection, SmSurfaceIntersector::FindInteriorCurves; unit-SCC #22 (220 units); 150 upstream test entry points reach this unit"
notes = "4 fns; largest: SmSurfaceIntersector::TraceCurve, SmSurfaceIntersector::DoPointIntersection, SmSurfaceIntersector::DoLawIntersection, SmSurfaceIntersector::FindInteriorCurves; unit-SCC #22 (220 units); 150 upstream test entry points reach this unit; PARTIAL (PR #70): difftest 1542 cases (22 inline edge cases, 20 fixture cases from my_test_planar_section on shovel.srf, 1500 generated programs on plane pairs crossing down to 1e-6 rad, coincident, parallel a gap apart or the same object, scales 1e-6..1e5: DoPointIntersection through 0-5 points with directions/extensions/tolerances, TraceCurve from evaluated or raw points with curve points, targets and curves to avoid, then FlushCurve, DoLawIntersection point counts, FindInteriorCurves; a few on B-spline and curve-bounded surfaces), 938 pass bit-identical (max_rel_err 0), 604 deferred (surface.surface_p11 578, surface.surface_p3 26); --perturb SENSITIVE 938/938. UNPORTED stubs: SmSurface::IsSingularity of non-planes (surface.surface_p5); the SM_SR_NODES node pairs of FindInteriorCurves' GlobalSurfaceSolve (surface.surface_p11; the pair walk is find_interior_curves_from_node_pairs). DoLawIntersection past its SmOffsetSurface check is unreachable until surface.offset_surface adds the kind. Back-edges NOT wired: SmAdvSurfaceIntersector's TraceCurve (intersect/adv_surface_intersector.rs) and SmSurface::PointBasedSurfaceIntersect's DoPointIntersection (surface/surface/p2.rs) run on the advanced intersector, whose ComputeSingularityPoint override the basic part 1-4 methods cannot dispatch to yet; the stubs stay tagged UNPORTED(intersect.surface_intersector_p4) with that reason."
[[unit]]
id = "intersect.topology_solver_p2"
@@ -2380,7 +2380,7 @@ tests = ["prog_test:my_test_extent3d", "prog_test:my_test_vector2d", "prog_test:
status = "todo"
claimed_by = ""
claimed_at = ""
notes = "32 fns; largest: sm_FixUVTrimCurves, SmFace::GetPointsInFace, SmODEIsoCurveLengthIFEO::Evaluate, SmFace::CreateCurvesByTrimming; cycle back-edges (call into later units; stub or co-port): topology.edge(9), topology.vertexuse(5), topology.vertex(4), topology.loopuse(3), mass.integrator(2), query.face_p2(2), topology.edgeuse_p3(2), heal.gap(1), query.face_p3(1), topology.face_p1(1); unit-SCC #22 (220 units); hazards: clock,rng; 319 upstream test entry points reach this unit"
notes = "32 fns; largest: sm_FixUVTrimCurves, SmFace::GetPointsInFace, SmODEIsoCurveLengthIFEO::Evaluate, SmFace::CreateCurvesByTrimming; cycle back-edges (call into later units; stub or co-port): topology.edge(9), topology.vertexuse(5), topology.vertex(4), topology.loopuse(3), mass.integrator(2), query.face_p2(2), topology.edgeuse_p3(2), heal.gap(1), query.face_p3(1), topology.face_p1(1); unit-SCC #22 (220 units); hazards: clock,rng; 319 upstream test entry points reach this unit; CO-PORTED in topology.edgeuse_p1 (PR #55): SmFace::m_pSurface -> topology::Face::surface"
[[unit]]
id = "intersect.curve_class_p5"
@@ -2436,7 +2436,7 @@ tests = ["prog_test:my_test_extent3d", "prog_test:my_test_vector2d", "prog_test:
status = "todo"
claimed_by = ""
claimed_at = ""
notes = "37 fns; largest: SmShell::RayFire, SmShell::TransferChildrenTo, SmShell::GetWireEdges, SmRegion::OrderShells; cycle back-edges (call into later units; stub or co-port): topology.edge(5), query.face_p1(2), topology.brep_p1(2), query.brep(1), topology.brep_p2(1), topology.topology_traverser(1), topology.vertex(1), topology.vertexuse(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit"
notes = "37 fns; largest: SmShell::RayFire, SmShell::TransferChildrenTo, SmShell::GetWireEdges, SmRegion::OrderShells; cycle back-edges (call into later units; stub or co-port): topology.edge(5), query.face_p1(2), topology.brep_p1(2), query.brep(1), topology.brep_p2(1), topology.topology_traverser(1), topology.vertex(1), topology.vertexuse(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit; CO-PORTED in topology.edgeuse_p1 (PR #55): SmRegion (m_pListOwner != NULL only) -> topology::Region, SmShell::m_pListOwner -> topology::Shell::region; SmShell::GetRegion, GetBrep -> topology::edgeuse::p1::{shell_region, shell_brep}"
[[unit]]
id = "intersect.curve_class_p8"
@@ -2478,7 +2478,7 @@ tests = ["prog_test:my_test_extent3d", "prog_test:my_test_vector2d", "prog_test:
status = "todo"
claimed_by = ""
claimed_at = ""
notes = "29 fns; largest: SmVertexuse::ComputeUVSector, SmVertexuse::ComputeUVPoint, SmVertexuse::CalcVertexFaceGap, SmVertexuse::Notify; cycle back-edges (call into later units; stub or co-port): heal.gap(11), topology.edge(6), topology.vertex(5), topology.loopuse(4), blend.fillet_geom_p1(1), topology.edgeuse_p3(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit"
notes = "29 fns; largest: SmVertexuse::ComputeUVSector, SmVertexuse::ComputeUVPoint, SmVertexuse::CalcVertexFaceGap, SmVertexuse::Notify; cycle back-edges (call into later units; stub or co-port): heal.gap(11), topology.edge(6), topology.vertex(5), topology.loopuse(4), blend.fillet_geom_p1(1), topology.edgeuse_p3(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit; CO-PORTED in topology.edgeuse_p1 (PR #55): SmVertexuse members (owner vertex, m_tVertexuseType/m_pSorLUorEU, m_sVertexFaceGap3d) -> topology::{Vertexuse, VertexuseOwner}; SmVertexuse::GetEdgeuse -> topology::edgeuse::p1::vertexuse_edgeuse"
[[unit]]
id = "topology.vertex"
@@ -2492,7 +2492,7 @@ tests = ["prog_test:my_test_extent3d", "prog_test:my_test_vector2d", "prog_test:
status = "todo"
claimed_by = ""
claimed_at = ""
notes = "38 fns; largest: SmVertex::IsTopologicalVertex, SmVertex::Notify, SmVertex::IsConnectedTo, SmVertex::GetMaxUpDimGap3d; cycle back-edges (call into later units; stub or co-port): topology.edge(12), heal.gap(4), topology.loopuse(2), blend.fillet_geom_p1(1), topology.face_p3(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit"
notes = "38 fns; largest: SmVertex::IsTopologicalVertex, SmVertex::Notify, SmVertex::IsConnectedTo, SmVertex::GetMaxUpDimGap3d; cycle back-edges (call into later units; stub or co-port): topology.edge(12), heal.gap(4), topology.loopuse(2), blend.fillet_geom_p1(1), topology.face_p3(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit; CO-PORTED in topology.edgeuse_p1 (PR #55): SmVertex::m_sZoneTol3d -> topology::Vertex::tolerance"
[[unit]]
id = "topology.loopuse"
@@ -2506,7 +2506,7 @@ tests = ["prog_test:my_test_extent3d", "prog_test:my_test_vector2d", "prog_test:
status = "todo"
claimed_by = ""
claimed_at = ""
notes = "48 fns; largest: SmLoopuse::IsClosed3d, SmLoop::GetUVCornerBoundaries, SmLoop::FlipLoopOrientation, SmLoopuse::IsMissingSeamAtVertices; cycle back-edges (call into later units; stub or co-port): topology.edge(6), topology.edgeuse_p3(1), topology.face_p3(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit"
notes = "48 fns; largest: SmLoopuse::IsClosed3d, SmLoop::GetUVCornerBoundaries, SmLoop::FlipLoopOrientation, SmLoopuse::IsMissingSeamAtVertices; cycle back-edges (call into later units; stub or co-port): topology.edge(6), topology.edgeuse_p3(1), topology.face_p3(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit; CO-PORTED in topology.edgeuse_p1 (PR #55): SmLoopuse members (owner face use, m_pL, m_pLUMate, m_tLoopuseType/m_pEUorVU) -> topology::{Loopuse, LoopuseStart}, SmLoop::m_pLU -> topology::Loop::loopuse; SmLoopuse::GetEdgeuses (no filters), GetShell -> topology::edgeuse::p1::{loopuse_edgeuses, loopuse_shell}; CO-PORTED in query.loop (PR #63): SmLoop::GetUVCornerBoundaries, sm_CalcQuadNum -> query::r#loop::uv_corner_boundaries, query::r#loop::calc_quad_num (private); they read the borrowed FaceLoop view of query::r#loop until the topology binds geometry."
[[unit]]
id = "topology.brep_p8"
@@ -2534,7 +2534,7 @@ tests = ["prog_test:my_test_extent3d", "prog_test:my_test_vector2d", "prog_test:
status = "todo"
claimed_by = ""
claimed_at = ""
notes = "60 fns; largest: SmEdge::IsTopological, SmEdge::FindRadialSector, SmEdge::GetBlendEdgeuse, SmEdge::OrientEdgeSequence; cycle back-edges (call into later units; stub or co-port): heal.gap(12), topology.edgeuse_p3(3), query.edge(2), topology.brep_p2(2), blend.fillet_geom_p1(1), topology.brep_p1(1), topology.face_p1(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit"
notes = "60 fns; largest: SmEdge::IsTopological, SmEdge::FindRadialSector, SmEdge::GetBlendEdgeuse, SmEdge::OrientEdgeSequence; cycle back-edges (call into later units; stub or co-port): heal.gap(12), topology.edgeuse_p3(3), query.edge(2), topology.brep_p2(2), blend.fillet_geom_p1(1), topology.brep_p1(1), topology.face_p1(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit; CO-PORTED in topology.edgeuse_p1 (PR #55): SmEdge members m_pList (edge use list, primary first), m_vInterval, m_sZoneTol3d, m_pCurve -> topology::Edge::{edgeuses, interval, tolerance, curve}; SmEdge::GetPrimaryEdgeuse, IsLamina, GetBrep -> topology::edgeuse::p1::{primary_edgeuse, edge_is_lamina, edge_brep}"
[[unit]]
id = "blend.fillet_custom_solver"
@@ -2604,7 +2604,7 @@ tests = ["prog_test:my_test_surface_closest_point", "prog_test:my_test_drop_poin
status = "todo"
claimed_by = ""
claimed_at = ""
notes = "119 fns; largest: SmGapFunction::BuildSampleSet, SmCrvSrfGapFunction::Evaluate, SmCrvCrvGapFunction::Evaluate, SmSrfCrvGapFunction::Evaluate; unit-SCC #22 (220 units); 317 upstream test entry points reach this unit"
notes = "119 fns; largest: SmGapFunction::BuildSampleSet, SmCrvSrfGapFunction::Evaluate, SmCrvCrvGapFunction::Evaluate, SmSrfCrvGapFunction::Evaluate; unit-SCC #22 (220 units); 317 upstream test entry points reach this unit; CO-PORTED in topology.edgeuse_p1 (PR #55): SmEdgeFaceGap, SmEdgeEdgeGap, SmVertexFaceGap data members, ReSet, IsInit, SmEdgeFaceGap::SetEdgeuse -> heal::gap::{EdgeFaceGap, EdgeEdgeGap, VertexFaceGap}"
[[unit]]
id = "query.loop"
@@ -2615,10 +2615,10 @@ soft_depends_on = ["curve.curve_p3", "curve.curve_p5", "curve.curve_p6", "geom.g
est_lines = 830
layer = 42
tests = ["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", "SM_API_test:TestSmCreateRectangle", "SM_API_test:TestSmCreateRegularPolygon"]
status = "todo"
status = "partial"
claimed_by = ""
claimed_at = ""
notes = "1 fns; largest: SmLoop::ContainsUVPoint; unit-SCC #22 (220 units); 317 upstream test entry points reach this unit"
notes = "PARTIAL (PR #63): difftest 3040 cases (40 fixture re-expressing the TestSmCreateRectangle / TestSmCreateRegularPolygon loops (CCW, CW, mixed edge use orientations, far-centre thin rectangle, tiny and large tolerances) and smTopoTest my_test_single_periodic_vertexuse (a seamless circle loop on a cylinder) + 3000 generated over real SmPlane / SmBSplineSurface / SmSurfOfRevolution (cylinder, cone, sphere, torus-like) faces with SmLine / SmCircle / SmBSplineCurve edges and 2D B-spline UV trims (lines, collinear polylines, rational arcs, wiggly and cusp-like curves, extended trims with sub-interval edges), seam edges used twice, open and star (winding 2) loops, vertex loops, 1e-3/1e3 scales, points on and near edges, vertices and domain sides), 2780 pass bit-identical, 260 deferred (surface.surface_p3 260: SmSurface::GetSingularities for a point and a loop end on a side of the natural domain), max_rel_err 0; 20k-seed run clean; --perturb SENSITIVE. The loop is passed as a borrowed FaceLoop view (query::r#loop::{FaceLoop, LoopEdgeuse, LoopFace}) since the arena topology binds no geometry yet; an edge use without UV trim curve is UNPORTED(topology.edgeuse_p3) GetOrCreateUVTrimCurve. 1 fns; largest: SmLoop::ContainsUVPoint; unit-SCC #22 (220 units); 317 upstream test entry points reach this unit"
[[unit]]
id = "topology.face_p4"
@@ -2800,7 +2800,7 @@ tests = ["prog_test:my_test_extent3d", "prog_test:my_test_vector2d", "prog_test:
status = "todo"
claimed_by = ""
claimed_at = ""
notes = "50 fns; largest: SmBrep::HealBrep, SmBrep::CreateCornerBlend, SmBrep::CreateEdgeEdgeBlend, sm_MakeVertexSplitUVTrimCurve; cycle back-edges (call into later units; stub or co-port): query.brep(3), heal.heal_data_p1(2); unit-SCC #22 (220 units); hazards: ptr-hash; 319 upstream test entry points reach this unit"
notes = "50 fns; largest: SmBrep::HealBrep, SmBrep::CreateCornerBlend, SmBrep::CreateEdgeEdgeBlend, sm_MakeVertexSplitUVTrimCurve; cycle back-edges (call into later units; stub or co-port): query.brep(3), heal.heal_data_p1(2); unit-SCC #22 (220 units); hazards: ptr-hash; 319 upstream test entry points reach this unit; CO-PORTED in topology.edgeuse_p1 (PR #55): SmBrep::m_bValidateOnly, the region/face use/loop use/edge use/vertex use arenas -> topology::Brep (validate_only, regions(), faceuses(), ..., add_*_record, *_mut)"
[[unit]]
id = "topology.brep_p6"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff