Merge pull request #247 from brett19/unit/blend.fillet_geom_p6

port: blend.fillet_geom_p6
This commit is contained in:
bherbruck
2026-10-21 22:55:07 +00:00
committed by GitHub
42 changed files with 32108 additions and 967 deletions
@@ -26,7 +26,7 @@
//! `sm_FindConcaveEdgeuses` (`blend.fillet_corner_p2`); every patch builder past its state
//! checks (`MakeAnalyticCornerSurface`, `CreateEndPatch`, `SmSurface::DropCurve`); the
//! Nx2 adjustments where they change the topology (`MakeFilletEdge`,
//! `GetRailIndexOfVertexuse`), the big-radius test of blending corners and the `NxN` /
//! `SplitFilletGeomAtSideEdge` once a vertex drops inside a rail), the big-radius test of blending corners and the `NxN` /
//! closed Nx1 overrides. The shim does not call upstream on those paths (`-3`; a Rust
//! side that is not deferred there fails the case) and refuses (`-2`, a rejected case)
//! the inputs that make upstream dereference null or free the harness's objects. A
@@ -695,6 +695,8 @@ fn corner_rust(k: &Kernel, op: CornerOp, model: &Model, ci: &CornerIn) -> Result
pseudo: &mut rm.fb,
corner: None,
tsect_surfaces: &surfaces,
// the model's geometries have no solver (as the shim's)
solvers: &mut [],
};
match op {
CornerOp::CalcGeom => c.calc_corner_geom(k, &mut env),
@@ -361,6 +361,17 @@ impl ExecutiveAccess<Handle> for Access {
) -> SolidResult<()> {
missing()
}
fn trim_original_surfaces(
&mut self,
_: &mut Kernel,
_: Handle,
_: &mut Vec<Handle>,
_: &mut Vec<Handle>,
_: &[ContextMark],
_: &mut FilletExecutive<Handle>,
) -> SolidResult<()> {
missing()
}
fn calc_corner_geom(
&mut self,
_: &mut Kernel,
@@ -62,9 +62,9 @@ fn missing<T>() -> SolidResult<T> {
impl ExecutiveAccess<Handle> for Access {
// This empty-B-rep fixture has no face handles or intersector world. These
// required adapters reject nonexistent objects; they are not UNPORTED calls
// to the now-ported face p3, topology intersector p2, stitch p2, fillet geom p4,
// fillet corner p3 or fillet solver p1 implementations. `set_brep_tolerance` runs
// the brep p2 port for real.
// to the now-ported face p3, topology intersector p2, stitch p2, fillet geom p4
// and p6, fillet corner p3 or fillet solver p1 implementations.
// `set_brep_tolerance` runs the brep p2 port for real.
fn notify_face(
&mut self,
_: &mut Kernel,
@@ -122,6 +122,17 @@ impl ExecutiveAccess<Handle> for Access {
) -> SolidResult<()> {
missing()
}
fn trim_original_surfaces(
&mut self,
_: &mut Kernel,
_: Handle,
_: &mut Vec<Handle>,
_: &mut Vec<Handle>,
_: &[ContextMark],
_: &mut FilletExecutive<Handle>,
) -> SolidResult<()> {
missing()
}
fn calc_corner_geom(
&mut self,
_: &mut Kernel,
@@ -590,7 +590,8 @@ fn rust(i: &Inputs) -> Result<Rec, HarnessError> {
};
let (fs, of, nf) = (face(*set)?, face(*f1)?, face(*f2)?);
rm.fb.fillet_edge_mut(eid).map_err(h)?.set_original_face(fs);
let r = rm.fb.update_edge_split_face(eid, of, nf);
// the shim refuses runs reaching ShouldSwapFaces (its corners carry no data)
let r = rm.fb.update_edge_split_face(&k, eid, of, nf, &mut rm.target, &[]);
o.push(status_of!(r));
let index = |f: Option<FaceId>| {
f.and_then(|f| rm.target_faces.iter().position(|&x| x == f))
File diff suppressed because it is too large Load Diff
@@ -15,10 +15,8 @@
//! points and knots), for `rail_index` the index, for the vertex calculators the
//! vertex's point and fillet status.
//!
//! Deferred on the Rust side (unported callees): `rail_index` of a vertex
//! (`GetRailIndexOfVertexuse`, `blend.fillet_geom_p6`), `make_face` past its rail
//! checks (`JoinTwoFilletVertsByCurves`, `blend.fillet_geom_p6`), `blending` of a
//! blending rail (`SmFilletEdge::CalcBlendingRail`, `blend.fillet_geom_p6`) and the
//! Deferred on the Rust side (unported callees): the face makers' unported callees in
//! `make_face` (`topology.brep_p3`) and the
//! unported callees of `sm_SrfSrfIntersection` (`SmSurface::DropCurve`,
//! `intersect.surface_drop_lib_p3`, ...) where reached. The shim refuses the inputs that would make upstream dereference
//! what the harness cannot build (a real fillet corner, a solver or rail it needs);
@@ -421,7 +419,9 @@ fn rust(i: &Inputs) -> Result<Rec, HarnessError> {
}
Query::MakeFace { g } => {
let gid: FilletGeomId = rm.geom(*g)?;
let mut brep = Brep::new();
// `new (ctx) SmBrep()`: the constructor makes the void infinite region the
// face makers put the face in
let mut brep = Brep::new_with_infinite_region().map_err(h)?;
let mut records = std::collections::BTreeMap::new();
let mut attributes = solidcore::geom::attribute::AttributeStore::default();
// distinct identities for the face makers' notifications, keyed as the
@@ -38,9 +38,9 @@
//! the constructor).
//!
//! Deferred on the Rust side (unported callees): every builder past its checks (the edge
//! use constructor needs `SmBrep()`'s infinite region, `topology.brep_p3`); `SM_BT_BLEND`
//! end curves at `FindEdgeTangentAtPoint` (`blend.fillet_geom_p6`). The split-face update
//! and the surface/surface intersections (`blend.fillet_geom_p2`) are wired. The shim
//! use constructor needs `SmBrep()`'s infinite region, `topology.brep_p3`). The split-face
//! update, `FindEdgeTangentAtPoint` (`blend.fillet_geom_p6`) and the surface/surface
//! intersections (`blend.fillet_geom_p2`) are wired. The shim
//! refuses exactly the inputs that would make upstream dereference what the harness cannot
//! build (a real fillet corner, a solver or rail it needs, a type-confused classification
//! object; the list is in the FFI module docs); the generator keeps only inputs both sides
@@ -486,7 +486,9 @@ fn rust(i: &Inputs) -> Result<Rec, HarnessError> {
let eid = rm.edge(ei)?;
rm.fb.fillet_edge_mut(eid).map_err(h)?.set_original_face(of);
}
let r = rm.fb.update_geom_split_face(gid, Some(face1), Some(face2));
let r = rm
.fb
.update_geom_split_face(&k, gid, Some(face1), Some(face2), &mut rm.target, &[]);
o.push(status_of!(r));
let index = |f: Option<solidcore::topology::FaceId>| {
f.and_then(|f| rm.target_faces.iter().position(|&x| x == f))
@@ -527,7 +529,7 @@ fn rust(i: &Inputs) -> Result<Rec, HarnessError> {
let gid = rm.geom(*g)?;
let r = rm
.fb
.create_end_curve(&k, gid, *end, trim_type(*trim), &solvers);
.create_end_curve(&k, gid, *end, trim_type(*trim), &solvers, &rm.target);
o.push(status_of!(r));
o.push(rm.fb.edge_list().len() as f64);
if r.is_ok() {
File diff suppressed because it is too large Load Diff
@@ -67,7 +67,7 @@ use solidcore::intersect::point_class::{
use solidcore::intersect::surface_intersector::p1::TsectPnt;
use solidcore::kernel::Kernel;
use solidcore::math::Point3;
use solidcore::nurbs::b_spline_surface::SurfParamType;
use solidcore::nurbs::b_spline_surface::{BSplineSurface, SurfParamType};
use solidcore::nurbs::math::NOW;
use solidcore::surface::cone::Cone;
use solidcore::surface::offset_surface::SS_UNKNOWN;
@@ -77,7 +77,7 @@ use solidcore::surface::torus::{Torus, TorusOptions};
use solidcore::surface::volume::Volume;
use solidcore::topology::edgeuse::p1 as eu;
use solidcore::topology::{Brep, Edge, EdgeId, EdgeuseId, FaceId, Vertex};
use solidcore::SolidError;
use solidcore::{EntityRef, SolidError};
use solidcore_ffi::units::blend_fillet_solver_p1::{self as ffi, Op, StaticOp};
use solidcore_ffi::units::topology_edgeuse_p1 as ffi1;
@@ -196,6 +196,10 @@ enum Inputs {
Fsg {
kind: u8,
args: [f64; 4],
/// Kind 1 (linear) only: the two rail curves `CreateSurface` is given (empty:
/// no `CreateSurface` call).
#[serde(default)]
rails: Vec<CrvIn>,
},
TolAng {
has: bool,
@@ -579,9 +583,16 @@ fn cpp_input(i: &Inputs) -> Result<Vec<f64>, HarnessError> {
o.push(b(has[1]));
o.push(*off);
}
Inputs::Fsg { kind, args } => {
Inputs::Fsg { kind, args, rails } => {
o.push(f64::from(*kind));
o.extend_from_slice(args);
if *kind == 1 {
// (the shim reads the rail count of the linear kind, then the rails)
o.push(rails.len() as f64);
for r in rails {
push_curve(&mut o, r)?;
}
}
}
Inputs::TolAng { has, ang } => o.extend([0.0, b(*has), *ang]),
Inputs::InitialValues { do2, rail, idx2 } => {
@@ -823,6 +834,24 @@ fn shim_with(s: &Surfs, load: bool, found: bool, setup: bool) -> Result<ShimSolv
Ok(fs)
}
/// The shim's `put_bss`: `has_nurb [n m p q r s cpts(x y z w) knots_u knots_v]`.
fn put_bss(o: &mut Vec<f64>, s: &BSplineSurface) {
let Some(sur) = s.gw_nurb() else {
o.push(0.0);
return;
};
o.push(1.0);
let ix = sur.indices();
o.extend([ix.n, ix.m, ix.p, ix.q, ix.r, ix.s].map(|x| x as f64));
for row in sur.rows() {
for p in row {
o.extend([p.x, p.y, p.z, p.w]);
}
}
o.extend_from_slice(sur.u_knots());
o.extend_from_slice(sur.v_knots());
}
/// `put_crv` of the static shim: `1 t0 t1 P(t0) P(mid) P(t1)`, `-1` for none.
fn put_crv(k: &Kernel, o: &mut Vec<f64>, c: Option<&Curve>) {
let Some(c) = c else {
@@ -1386,7 +1415,7 @@ fn rust(k: &Kernel, i: &Inputs) -> Result<Output, HarnessError> {
Inputs::Split => {
let k = Kernel::default();
let mut pseudo = FilletBrep::new();
let cc: CurveClassification<H> = CurveClassification::new(&k);
let cc: CurveClassification<EntityRef> = CurveClassification::new(&k);
let r = (|| -> Result<usize, SolidError> {
let mut fs =
FilletSolver::new_in(&k, &mut pseudo, FilletSolverId(0), 1.0e-6, 0.1, 0.01)?;
@@ -1455,7 +1484,7 @@ fn rust(k: &Kernel, i: &Inputs) -> Result<Output, HarnessError> {
}
r
}
Inputs::Fsg { kind, args } => {
Inputs::Fsg { kind, args, rails } => {
let [a, bb, c, d] = *args;
let g = match kind {
0 => FilletSurfaceGenerator::Base,
@@ -1486,6 +1515,22 @@ fn rust(k: &Kernel, i: &Inputs) -> Result<Output, HarnessError> {
Ok(_) => 0.0,
Err(e) => status(&e),
});
} else if let (1, [c1, c2]) = (*kind, rails.as_slice()) {
// the linear one: the ruled surface of two real rails, `status` then the
// surface block (as the shim's `put_bss`)
let k = Kernel::default();
let mut r1 = bspline_of(c1)?;
let mut r2 = bspline_of(c2)?;
match g.create_surface(&k, 1.0e-6, &mut r1, &mut r2, &[]) {
Ok(s) => {
o.push(0.0);
put_bss(&mut o, &s);
}
Err(e) => {
o.push(status(&e));
o.push(0.0);
}
}
} else {
o.push(-1.0);
}
@@ -1641,17 +1686,17 @@ impl ClassObject for H {
/// intervals classifies nothing).
struct NoGeometry;
impl ClassGeometry<H> for NoGeometry {
fn curve(&self, _o: H) -> Option<&Curve> {
impl<O> ClassGeometry<O> for NoGeometry {
fn curve(&self, _o: O) -> Option<&Curve> {
None
}
fn surface(&self, _o: H) -> Option<&Surface> {
fn surface(&self, _o: O) -> Option<&Surface> {
None
}
fn volume(&self, _o: H) -> Option<&Volume> {
fn volume(&self, _o: O) -> Option<&Volume> {
None
}
fn point(&self, _o: H) -> Option<Point3> {
fn point(&self, _o: O) -> Option<Point3> {
None
}
}
@@ -1898,6 +1943,34 @@ fn gen_sphere(rng: &mut Rng) -> Inputs {
}
}
/// The two rails of a linear `fsg` case (`SmLinearCrossSectionFSG::CreateSurface`):
/// real 3D B-spline curves, either a rail and a copy of it moved by an offset (same
/// degree, knots and domain: the ruled surface needs no reparametrisation), or two
/// independent curves (different degrees, knot vectors, domains, rational or not:
/// `CreateRuledSurface` makes them compatible, or fails).
fn gen_fsg_rails(rng: &mut Rng) -> Vec<CrvIn> {
let scale = *rng.choose(&[1e-3, 1.0, 1.0, 1e3]).unwrap_or(&1.0);
let first = gen_curve(rng, 3, scale);
let second = if rng.chance(0.5) {
let d = [
rng.uniform(-1.0, 1.0) * scale,
rng.uniform(-1.0, 1.0) * scale,
rng.uniform(0.5, 2.0) * scale,
];
let mut c = first.clone();
for p in &mut c.cpts {
let w = if p[3] == NOW { 1.0 } else { p[3] };
p[0] += d[0] * w;
p[1] += d[1] * w;
p[2] += d[2] * w;
}
c
} else {
gen_curve(rng, 3, scale)
};
vec![first, second]
}
fn gen_rail_uv(rng: &mut Rng) -> Inputs {
let scale = *rng.choose(&[1e-3, 1.0, 1e3]).unwrap_or(&1.0);
let dim = 2 + rng.below(2);
@@ -2376,7 +2449,12 @@ fn gen_case(rng: &mut Rng) -> Inputs {
off: rng.uniform(-1.0, 1.0),
},
20 => {
let kind = rng.below(6) as u8;
// half the cases are the linear kind (the one with a real `CreateSurface`)
let kind = if rng.chance(0.5) {
1
} else {
rng.below(6) as u8
};
let args = if kind == 5 {
[rng.uniform(0.1, 3.0), rng.below(4) as f64, 0.0, 0.0]
} else {
@@ -2387,7 +2465,13 @@ fn gen_case(rng: &mut Rng) -> Inputs {
b(rng.chance(0.5)),
]
};
Inputs::Fsg { kind, args }
// the linear kind (1) is given two rails for `CreateSurface` in 3 of 4 cases
let rails = if kind == 1 && rng.chance(0.75) {
gen_fsg_rails(rng)
} else {
Vec::new()
};
Inputs::Fsg { kind, args, rails }
}
21 => {
if rng.chance(0.5) {
@@ -38,9 +38,10 @@
//! corner for its filleted vertex right after its null check), a `RailRail` edge whose
//! chosen rail curve (the mate edge use's UV curve or the rail's 3D curve, picked as
//! upstream picks it) is `NULL` (dropped onto unchecked), a `SplitFace` whose edge's
//! original face is the split face (`ShouldSwapFaces`, unit `blend.fillet_geom_p6`,
//! then dereferences the edge's curve, curve classification or corner and both faces
//! unchecked; the Rust side defers there anyway), and, as in
//! original face is the split face (`ShouldSwapFaces` then dereferences the edge's
//! curve, curve classification or corner and both faces unchecked; the block's
//! corners carry no data, and part 6's own difftest compares `ShouldSwapFaces`), and,
//! as in
//! `blend.fillet_geom_p1`, surfaces whose constructed v knot vector would make the
//! surface cache write out of bounds (UB-166).
@@ -0,0 +1,293 @@
//! FFI wrappers for unit `blend.fillet_geom_p3`.
//!
//! Shim: `harness/cpp/shim/blend_fillet_geom_p3.cpp` (`sc_blend_fillet_geom_p3_eval`).
//! Upstream: `source/SMLib/src/SmFilletGeom.cpp` (`SmFilletVertex::CalcCornerVertGeom`,
//! `CalcMateGeom`, `SmFilletEdge::CalcCornerEdgeGeom`, `CalcFilletIntFillet`,
//! `SmFilletGeom::ReCalcFilletGeom`).
//!
//! Every op's input starts with a B-rep block of unit `topology.edgeuse_p1` (the target
//! B-rep; `path` is its `.smb` file for a file block), whose objects are addressed by
//! export index. Surface and curve blocks are that unit's. Then the model:
//!
//! - `ns` solvers: `tol ang tang eu0 eu1` (target edge uses, `-1` none), per rail
//! `has [surface dist]` (an `SmOffsetSurface(dist, base)` installed with `SetSurface`),
//! `extend_before extend_after setup` (`setup`: `SetupOffsetValues` succeeds);
//! - `nv` fillet vertices: `x y z type status corner pc_class pc_kind pc_index pc_face t u
//! v` (`pc_kind` `0` none, `1` target vertex, `2` target edge use, `3` target face
//! `pc_index`; `pc_face` a target face or `-1`; `corner` an opaque tag or `-1`);
//! - `ng` geometries: `type status solver has_surface [surface]` (`SmFilletGeom(solver,
//! type)`, `solver` `-1` for none);
//! - `ne` edges (`MakeFilletEdge`, in creation order): `vs ve type status corner
//! has_curve [curve] has_ivl [t0 t1]`, then per edge use (primary, mate) `has_uv
//! [curve] geom u v` and its vertex use's intersection point `point_type u0 v0 u1 v1
//! crv[3] deriv[3] curve_parameter`;
//! - per geometry `rail0 rail1 n1 edges.. n2 edges.. nsides (edge which)..`;
//! - per vertex `mate0 mate1`.
//!
//! | [`Op`] | upstream | arguments after the model |
//! |------------------|----------------------------------------|---------------------------|
//! | `Export` | (the built model) | |
//! | `CornerVert` | `SmFilletVertex::CalcCornerVertGeom` | `v` |
//! | `Mate` | `SmFilletVertex::CalcMateGeom` | `v` |
//! | `CornerEdge` | `SmFilletEdge::CalcCornerEdgeGeom` | `e` |
//! | `FilletIntFillet`| `SmFilletEdge::CalcFilletIntFillet(ctx, corner)` | `e` |
//! | `ReCalc` | `SmFilletGeom::ReCalcFilletGeom` | `g analytic has_dir dir[3] has_ref [curve]` |
//!
//! Output: `1 status` when the target B-rep cannot be built; else `0`, the op's status
//! and the export: `nv` then per vertex `x y z type status u v` (original UV); `ne` then
//! per edge `type status curve t0 t1` and per edge use `uvcurve geom point_type u0 v0 u1
//! v1 crv[3] curve_parameter`; `ng` then per geometry `status nsides (edge which).. nrefs
//! (edge which)..`. A curve is `has dim n p m cpts(x y z w) knots`.
//!
//! Beyond the block checks here, the shim refuses (`SC_ERR_ARG`) a model on which the op
//! would read through `NULL` or a blind cast upstream, or run a vertex or edge calculator
//! of another unit (parts 2, 4, 5 and 6 read corner and executive data the shim does not
//! build; the fillet/fillet vertex and surface/surface intersections of
//! `CalcFilletIntFillet` are allowed): mates recursing through a cycle,
//! an `SM_FV_ON_VERTEX` vertex not classified to a target vertex, a mate's main vertex classified
//! to a non-edge-use or the mate to a non-face, a solver or side edge use that
//! `FindIndexOfRailXSideEdgeuse` would dereference while `NULL`, a missing main rail-end
//! vertex use, a fillet/fillet edge whose geometries lack solvers or (on the trimming
//! path) UV trim curves, and a `ReCalc` geometry without a solver, offset surfaces or
//! (not a corner fillet) first solver edge use.
use crate::units::topology_edgeuse_p1::{self as eu1, bad, brep_block, curve, surface, Reader};
use crate::{call_with_buffer, FfiError};
/// Largest number of solvers, vertices, geometries, edges or list entries.
pub const MAX_LIST: usize = 64;
/// The shim's ops.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Op {
/// The built model.
Export,
/// `SmFilletVertex::CalcCornerVertGeom`.
CornerVert,
/// `SmFilletVertex::CalcMateGeom`.
Mate,
/// `SmFilletEdge::CalcCornerEdgeGeom`.
CornerEdge,
/// `SmFilletEdge::CalcFilletIntFillet`.
FilletIntFillet,
/// `SmFilletGeom::ReCalcFilletGeom`.
ReCalc,
}
impl Op {
/// The shim's op code.
#[must_use]
pub fn code(self) -> i32 {
match self {
Self::Export => 0,
Self::CornerVert => 1,
Self::Mate => 2,
Self::CornerEdge => 3,
Self::FilletIntFillet => 4,
Self::ReCalc => 5,
}
}
}
mod sys {
extern "C" {
pub fn sc_blend_fillet_geom_p3_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;
}
}
/// An index below `count` or `-1`; `None` for `-1`.
fn opt_below(rd: &mut Reader<'_>, count: usize) -> Result<Option<usize>, FfiError> {
let v = rd.num()?;
if v == -1.0 {
return Ok(None);
}
rd.at -= 1;
rd.below(count).map(Some)
}
/// A curve block of dimension `dim`.
fn curve_of_dim(rd: &mut Reader<'_>, dim: f64, what: &str) -> Result<(), FfiError> {
if rd.input.get(rd.at).copied() != Some(dim) {
return Err(bad(format!("{what}: curve of the wrong dimension")));
}
curve(rd).map(|_| ())
}
/// Validates the model after the B-rep block (counts from the B-rep's export).
fn validate_model(rd: &mut Reader<'_>, counts: [usize; 10]) -> Result<[usize; 4], FfiError> {
let [_, _, _, n_f, _, _, n_eu, _, _, n_v] = counts;
let ns = rd.index(MAX_LIST)?;
for _ in 0..ns {
// any finite tolerances: zero and negative ones run through every op upstream
// (the fillet x fillet drop, the mate and the intersector of ReCalcFilletGeom)
rd.finites(3)?;
opt_below(rd, n_eu)?;
opt_below(rd, n_eu)?;
for _ in 0..2 {
if rd.flag()? {
surface(rd)?;
rd.finite()?;
}
}
rd.flag()?;
rd.flag()?;
rd.flag()?;
}
let nv = rd.index(MAX_LIST)?;
for _ in 0..nv {
rd.finites(3)?;
rd.index(13)?; // SmFilletVertexType
rd.index(12)?; // SmFilStatus
opt_below(rd, MAX_LIST)?; // corner tag
// SmPointClassificationType: SM_PC_UNKNOWN (100) ..= SM_PC_NOTHING (110)
if rd.index(110)? < 100 {
return Err(bad("vertex: point class below SM_PC_UNKNOWN"));
}
let pc_kind = rd.index(3)?;
let pc_index = opt_below(rd, MAX_LIST * MAX_LIST)?;
let in_range = match (pc_kind, pc_index) {
(0, _) => true,
(1, Some(i)) => i < n_v,
(2, Some(i)) => i < n_eu,
(3, Some(i)) => i < n_f,
_ => false,
};
if !in_range {
return Err(bad("vertex: classification object out of range"));
}
opt_below(rd, n_f)?; // pc_face
rd.finites(3)?; // t u v
}
let ng = rd.index(MAX_LIST)?;
for _ in 0..ng {
rd.index(6)?; // SmFilletGeomType
rd.index(12)?; // SmFilStatus
opt_below(rd, ns)?;
if rd.flag()? {
surface(rd)?;
}
}
let ne = rd.index(MAX_LIST)?;
for _ in 0..ne {
rd.below(nv)?;
rd.below(nv)?;
rd.index(14)?; // SmFilletEdgeType
rd.index(12)?; // SmFilStatus
opt_below(rd, MAX_LIST)?; // corner tag
if rd.flag()? {
curve_of_dim(rd, 3.0, "edge")?;
}
if rd.flag()? {
let t = rd.finites(2)?;
if t.first() > t.get(1) {
return Err(bad("edge: inverted interval"));
}
}
for _ in 0..2 {
if rd.flag()? {
curve_of_dim(rd, 2.0, "uv trim curve")?;
}
opt_below(rd, ng)?;
rd.finites(2)?; // vertex use uv
rd.index(7)?; // SmIntersectionPointType
rd.finites(4 + 3 + 3 + 1)?;
}
}
for _ in 0..ng {
opt_below(rd, ne)?;
opt_below(rd, ne)?;
for _ in 0..2 {
let n = rd.index(MAX_LIST)?;
for _ in 0..n {
rd.below(ne)?;
}
}
let n = rd.index(MAX_LIST)?;
for _ in 0..n {
rd.below(ne)?;
rd.index(1)?;
}
}
for _ in 0..nv {
opt_below(rd, nv)?;
opt_below(rd, nv)?;
}
Ok([ns, nv, ng, ne])
}
fn raw(op: Op, path: &str, input: &[f64]) -> Result<Vec<f64>, FfiError> {
let c_path = std::ffi::CString::new(path).map_err(|_| bad("path with NUL"))?;
call_with_buffer(4096, |buf, len| {
// SAFETY: c_path is a valid NUL-terminated string and input is valid for
// input.len() reads, holding what the shim reads for `op` (validated by eval:
// well-formed blocks, indices inside the B-rep's export and the model's lists; a
// B-rep block alone when the B-rep cannot be built, after which the shim reads
// nothing more); buf is valid for buf.len() writes and the shim honours the
// capacity; len is valid.
unsafe {
sys::sc_blend_fillet_geom_p3_eval(
op.code(),
c_path.as_ptr(),
input.as_ptr(),
input.len() as i64,
buf.as_mut_ptr(),
buf.len() as i64,
&raw mut *len,
)
}
})
}
/// Runs `op` on `input` (layouts in the module docs; `path` is the `.smb` file of a file
/// B-rep block, else empty).
///
/// # Errors
///
/// [`FfiError::InvalidArgument`] for input the shim must not see; [`FfiError::Status`]
/// for a shim failure (`SC_ERR_ARG` for a model the shim refuses, see the module docs).
pub fn eval(op: Op, path: &str, input: &[f64]) -> Result<Vec<f64>, FfiError> {
let mut rd = Reader { input, at: 0 };
brep_block(&mut rd, path)?;
let block = input.get(..rd.at).ok_or_else(|| bad("input too short"))?;
let export = eu1::eval(eu1::Op::Export, path, block)?;
let Some(counts) = eu1::counts(&export) else {
// the B-rep cannot be built: the shim reports the same failure
return raw(op, path, block);
};
let [_, nv, ng, ne] = validate_model(&mut rd, counts)?;
match op {
Op::Export => {}
Op::CornerVert | Op::Mate => {
rd.below(nv)?;
}
Op::CornerEdge | Op::FilletIntFillet => {
rd.below(ne)?;
}
Op::ReCalc => {
rd.below(ng)?;
rd.flag()?;
rd.flag()?;
rd.vec3()?;
if rd.flag()? {
curve_of_dim(&mut rd, 3.0, "reference curve")?;
}
}
}
rd.done()?;
raw(op, path, input)
}
@@ -0,0 +1,423 @@
//! FFI wrappers for unit `blend.fillet_geom_p6`.
//!
//! Shim: `harness/cpp/shim/blend_fillet_geom_p6.cpp` (`sc_blend_fillet_geom_p6_eval`).
//! Upstream: `source/SMLib/src/SmFilletGeom.cpp` (`SmFilletEdge::CalcBlendingRail`,
//! `CalcEdgeOnEdge`, `CalcFilletEnd`, `CalcSetBackRail`, `ShouldSwapFaces`,
//! `SmFilletGeom::Copy`, `FindEdgeTangentAtPoint`, `GetRailIndexOfSideEU`,
//! `GetRailIndexOfVertexuse`, `GetRailVertex`, `SetUpRailPoint`, `RailEdgeuseIntersect`,
//! `RecordFilletError`, and the file-statics `JoinTwoFilletVertsByCurves`, `sm_MoveTSP`).
//!
//! Every op's input starts with a B-rep block of unit `topology.edgeuse_p1` (the target
//! B-rep; `path` is its `.smb` file for a file block), whose objects are addressed by
//! export index. Surface and curve blocks are that unit's. Then the model:
//!
//! - `ns` solvers: `tol ang tang eu0 eu1` (target edge uses, `-1` none), per rail
//! `has [surface dist]` (an `SmOffsetSurface(dist, base)` installed with `SetSurface`),
//! `has_exec reverse_trim` (`has_exec`: the solver's executive is the model's
//! `SmFilletExecutive` on the target);
//! - `nc` corners: `vertex type tol` (the filleted target vertex or `-1`, the
//! `SmFilletCornerType`, the corner's tolerance);
//! - `nv` fillet vertices: `x y z type status corner pc_class pc_kind pc_index pc_face t u
//! v` (`corner` a corner index or `-1`; `pc_kind` `0` none, `1` target vertex, `2` target
//! edge use, `3` target face, `4` target edge `pc_index`; `pc_face` a target face or `-1`);
//! - `ng` geometries: `type status solver has_surface [surface] has_center [curve] dev0
//! dev1`, per rail `has [surface dist]` (`SetOffsetSurface`);
//! - `ne` edges (`MakeFilletEdge`, in creation order): `vs ve type status corner
//! has_curve [curve] has_ivl [t0 t1] orig_edge orig_face` (target, `-1` none), then per
//! edge use (primary, mate) `has_uv [curve] geom u v` and its vertex use's intersection
//! point `point_type u0 v0 u1 v1 crv[3] deriv[3] curve_parameter`;
//! - per geometry `rail0 rail1 n1 edges.. n2 edges.. nsides (edge which)..`;
//! - per vertex `mate0 mate1`.
//!
//! | [`Op`] | upstream | arguments after the model |
//! |---------------------|---------------------------------------------|---------------------------|
//! | `Export` | (the built model) | |
//! | `BlendingRail` | `SmFilletEdge::CalcBlendingRail` | `e use_corner` |
//! | `EdgeOnEdge` | `SmFilletEdge::CalcEdgeOnEdge` | `e use_corner` |
//! | `FilletEnd` | `SmFilletEdge::CalcFilletEnd` | `e use_corner` |
//! | `SetBackRail` | `SmFilletEdge::CalcSetBackRail` | `e use_corner` |
//! | `ShouldSwap` | `SmFilletEdge::ShouldSwapFaces` | `e face1 face2` |
//! | `Join` | `JoinTwoFilletVertsByCurves` | `g vs ve uv_tol n (e which with_uv orient)..` |
//! | `Copy` | `SmFilletGeom::Copy` | `g other` |
//! | `FindTangent` | `SmFilletGeom::FindEdgeTangentAtPoint` | `g rail end` |
//! | `RailIndexSideEu` | `SmFilletGeom::GetRailIndexOfSideEU` | `g target_eu` |
//! | `RailIndexVu` | `SmFilletGeom::GetRailIndexOfVertexuse` | `g e which` |
//! | `RailVertex` | `SmFilletGeom::GetRailVertex` | `g rail end` |
//! | `SetUpRailPoint` | `SmFilletGeom::SetUpRailPoint` | `g rail end param has_pc pc_class pc_kind pc_index pc_t` |
//! | `MoveTsp` | `sm_MoveTSP` | `n (crv[3] deriv[3]).. from to` |
//! | `RailEuIntersect` | `SmFilletGeom::RailEdgeuseIntersect` | `g rail target_eu guess` |
//! | `RecordError` | `SmFilletGeom::RecordFilletError` | `g type` |
//!
//! `use_corner` passes the edge's own corner (else `NULL`). The `Join` lists start with
//! the curves of the given edges (the UV entry the edge use's own trim curve when
//! `with_uv`, else `NULL`); `orient` is an `SmOrientType`.
//!
//! Output: `1 status` when the target B-rep cannot be built; else `0`, the op's status,
//! the op's results (`ShouldSwap`: the flag; `Join`: `made n (curve uvcurve orient)..`;
//! `FindTangent`: `found tan[3]`; `RailIndex*`: the index (`99` on an error status);
//! `RailVertex`: the vertex index; `MoveTsp`: `n` and the new order; `RailEuIntersect`:
//! `found param`), then the export: `nv` then per vertex `x y z type status u v pc_class
//! t`; `ne` then per edge `type status curve t0 t1 orig_edge orig_face` and per edge use
//! `uvcurve geom u v`; `ng` then per geometry `status nsides (edge which).. nrefs (edge
//! which).. surface centre_line dev0 dev1` and per rail `has [dist base_surface]`. A
//! curve is `has dim n p m cpts(x y z w) knots`, a surface `has n m p q r s cpts u v`.
//!
//! Beyond the block checks here, the shim refuses (`SC_ERR_ARG`) a model on which the op
//! would read through `NULL` or a blind cast upstream: missing rails, primary edge uses,
//! mates or rail curves the calculators dereference; classification objects of the
//! wrong kind; geometries without the solver (or executive) an op reads; a `Join`
//! without curves, side edges or the fillet surface it evaluates.
use crate::units::topology_edgeuse_p1::{self as eu1, bad, brep_block, curve, surface, Reader};
use crate::{call_with_buffer, FfiError};
/// Largest number of solvers, corners, vertices, geometries, edges or list entries.
pub const MAX_LIST: usize = 64;
/// The shim's ops.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Op {
/// The built model.
Export,
/// `SmFilletEdge::CalcBlendingRail`.
BlendingRail,
/// `SmFilletEdge::CalcEdgeOnEdge`.
EdgeOnEdge,
/// `SmFilletEdge::CalcFilletEnd`.
FilletEnd,
/// `SmFilletEdge::CalcSetBackRail`.
SetBackRail,
/// `SmFilletEdge::ShouldSwapFaces`.
ShouldSwap,
/// `JoinTwoFilletVertsByCurves`.
Join,
/// `SmFilletGeom::Copy`.
Copy,
/// `SmFilletGeom::FindEdgeTangentAtPoint`.
FindTangent,
/// `SmFilletGeom::GetRailIndexOfSideEU`.
RailIndexSideEu,
/// `SmFilletGeom::GetRailIndexOfVertexuse`.
RailIndexVu,
/// `SmFilletGeom::GetRailVertex`.
RailVertex,
/// `SmFilletGeom::SetUpRailPoint`.
SetUpRailPoint,
/// `sm_MoveTSP`.
MoveTsp,
/// `SmFilletGeom::RailEdgeuseIntersect`.
RailEuIntersect,
/// `SmFilletGeom::RecordFilletError`.
RecordError,
}
impl Op {
/// The shim's op code.
#[must_use]
pub fn code(self) -> i32 {
match self {
Self::Export => 0,
Self::BlendingRail => 1,
Self::EdgeOnEdge => 2,
Self::FilletEnd => 3,
Self::SetBackRail => 4,
Self::ShouldSwap => 5,
Self::Join => 6,
Self::Copy => 7,
Self::FindTangent => 8,
Self::RailIndexSideEu => 9,
Self::RailIndexVu => 10,
Self::RailVertex => 11,
Self::SetUpRailPoint => 12,
Self::MoveTsp => 13,
Self::RailEuIntersect => 14,
Self::RecordError => 15,
}
}
}
mod sys {
extern "C" {
pub fn sc_blend_fillet_geom_p6_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;
}
}
/// An index below `count` or `-1`; `None` for `-1`.
fn opt_below(rd: &mut Reader<'_>, count: usize) -> Result<Option<usize>, FfiError> {
let v = rd.num()?;
if v == -1.0 {
return Ok(None);
}
rd.at -= 1;
rd.below(count).map(Some)
}
/// A curve block of dimension `dim`.
fn curve_of_dim(rd: &mut Reader<'_>, dim: f64, what: &str) -> Result<(), FfiError> {
if rd.input.get(rd.at).copied() != Some(dim) {
return Err(bad(format!("{what}: curve of the wrong dimension")));
}
curve(rd).map(|_| ())
}
/// Two offset surface slots: `has [surface dist]` each.
fn offsets(rd: &mut Reader<'_>) -> Result<(), FfiError> {
for _ in 0..2 {
if rd.flag()? {
surface(rd)?;
rd.finite()?;
}
}
Ok(())
}
/// Validates the model after the B-rep block (counts from the B-rep's export).
fn validate_model(rd: &mut Reader<'_>, counts: [usize; 10]) -> Result<[usize; 4], FfiError> {
let [_, _, _, n_f, _, _, n_eu, n_e, _, n_v] = counts;
let ns = rd.index(MAX_LIST)?;
for _ in 0..ns {
let tols = rd.finites(3)?;
if tols.iter().take(2).any(|&t| t <= 0.0) {
return Err(bad("solver: tolerances must be positive"));
}
opt_below(rd, n_eu)?;
opt_below(rd, n_eu)?;
offsets(rd)?;
rd.flag()?;
rd.flag()?;
}
let nc = rd.index(MAX_LIST)?;
for _ in 0..nc {
opt_below(rd, n_v)?;
rd.index(12)?; // SmFilletCornerType
if rd.finite()? <= 0.0 {
return Err(bad("corner: tolerance must be positive"));
}
}
let nv = rd.index(MAX_LIST)?;
for _ in 0..nv {
rd.finites(3)?;
rd.index(13)?; // SmFilletVertexType
rd.index(12)?; // SmFilStatus
opt_below(rd, nc)?; // corner
// SmPointClassificationType: SM_PC_UNKNOWN (100) ..= SM_PC_NOTHING (110)
if rd.index(110)? < 100 {
return Err(bad("vertex: point class below SM_PC_UNKNOWN"));
}
pc_object(rd, [n_v, n_eu, n_f, n_e])?;
opt_below(rd, n_f)?; // pc_face
rd.finites(3)?; // t u v
}
let ng = rd.index(MAX_LIST)?;
for _ in 0..ng {
rd.index(6)?; // SmFilletGeomType
rd.index(12)?; // SmFilStatus
opt_below(rd, ns)?;
if rd.flag()? {
surface(rd)?;
}
if rd.flag()? {
curve_of_dim(rd, 3.0, "centre line")?;
}
rd.finites(2)?;
offsets(rd)?;
}
let ne = rd.index(MAX_LIST)?;
for _ in 0..ne {
rd.below(nv)?;
rd.below(nv)?;
rd.index(14)?; // SmFilletEdgeType
rd.index(12)?; // SmFilStatus
opt_below(rd, nc)?; // corner
if rd.flag()? {
curve_of_dim(rd, 3.0, "edge")?;
}
if rd.flag()? {
let t = rd.finites(2)?;
if t.first() > t.get(1) {
return Err(bad("edge: inverted interval"));
}
}
opt_below(rd, n_e)?;
opt_below(rd, n_f)?;
for _ in 0..2 {
if rd.flag()? {
curve_of_dim(rd, 2.0, "uv trim curve")?;
}
opt_below(rd, ng)?;
rd.finites(2)?; // vertex use uv
rd.index(7)?; // SmIntersectionPointType
rd.finites(4 + 3 + 3 + 1)?;
}
}
for _ in 0..ng {
opt_below(rd, ne)?;
opt_below(rd, ne)?;
for _ in 0..2 {
let n = rd.index(MAX_LIST)?;
for _ in 0..n {
rd.below(ne)?;
}
}
let n = rd.index(MAX_LIST)?;
for _ in 0..n {
rd.below(ne)?;
rd.index(1)?;
}
}
for _ in 0..nv {
opt_below(rd, nv)?;
opt_below(rd, nv)?;
}
Ok([ns, nv, ng, ne])
}
/// A classification object `kind index` (`kind` `0` none; `1` .. `4` a target vertex,
/// edge use, face, edge below the given counts).
fn pc_object(rd: &mut Reader<'_>, counts: [usize; 4]) -> Result<(), FfiError> {
let kind = rd.index(4)?;
let index = opt_below(rd, MAX_LIST * MAX_LIST * MAX_LIST)?;
let ok = match (kind, index) {
(0, _) => true,
(k, Some(i)) => counts.get(k - 1).is_some_and(|&n| i < n),
_ => false,
};
if ok {
Ok(())
} else {
Err(bad("classification object out of range"))
}
}
fn raw(op: Op, path: &str, input: &[f64]) -> Result<Vec<f64>, FfiError> {
let c_path = std::ffi::CString::new(path).map_err(|_| bad("path with NUL"))?;
call_with_buffer(4096, |buf, len| {
// SAFETY: c_path is a valid NUL-terminated string and input is valid for
// input.len() reads, holding what the shim reads for `op` (validated by eval:
// well-formed blocks, indices inside the B-rep's export and the model's lists; a
// B-rep block alone when the B-rep cannot be built, after which the shim reads
// nothing more); buf is valid for buf.len() writes and the shim honours the
// capacity; len is valid.
unsafe {
sys::sc_blend_fillet_geom_p6_eval(
op.code(),
c_path.as_ptr(),
input.as_ptr(),
input.len() as i64,
buf.as_mut_ptr(),
buf.len() as i64,
&raw mut *len,
)
}
})
}
/// Runs `op` on `input` (layouts in the module docs; `path` is the `.smb` file of a file
/// B-rep block, else empty).
///
/// # Errors
///
/// [`FfiError::InvalidArgument`] for input the shim must not see; [`FfiError::Status`]
/// for a shim failure (`SC_ERR_ARG` for a model the shim refuses, see the module docs).
pub fn eval(op: Op, path: &str, input: &[f64]) -> Result<Vec<f64>, FfiError> {
let mut rd = Reader { input, at: 0 };
brep_block(&mut rd, path)?;
let block = input.get(..rd.at).ok_or_else(|| bad("input too short"))?;
let export = eu1::eval(eu1::Op::Export, path, block)?;
let Some(counts) = eu1::counts(&export) else {
// the B-rep cannot be built: the shim reports the same failure
return raw(op, path, block);
};
let [_, _, _, n_f, _, _, n_eu, n_e, _, n_v] = counts;
let [_, nv, ng, ne] = validate_model(&mut rd, counts)?;
match op {
Op::Export => {}
Op::BlendingRail | Op::EdgeOnEdge | Op::FilletEnd | Op::SetBackRail => {
rd.below(ne)?;
rd.flag()?;
}
Op::ShouldSwap => {
rd.below(ne)?;
rd.below(n_f)?;
rd.below(n_f)?;
}
Op::Join => {
rd.below(ng)?;
opt_below(&mut rd, nv)?;
opt_below(&mut rd, nv)?;
rd.finite()?;
let n = rd.index(MAX_LIST)?;
for _ in 0..n {
rd.below(ne)?;
rd.index(1)?;
rd.flag()?;
rd.index(2)?; // SmOrientType
}
}
Op::Copy => {
rd.below(ng)?;
rd.below(ng)?;
}
Op::FindTangent | Op::RailVertex => {
rd.below(ng)?;
rd.index(1)?;
rd.index(1)?;
}
Op::RailIndexSideEu => {
rd.below(ng)?;
rd.below(n_eu)?;
}
Op::RailIndexVu => {
rd.below(ng)?;
rd.below(ne)?;
rd.index(1)?;
}
Op::SetUpRailPoint => {
rd.below(ng)?;
rd.index(1)?;
rd.index(1)?;
rd.finite()?;
rd.flag()?;
if rd.index(110)? < 100 {
return Err(bad("point class below SM_PC_UNKNOWN"));
}
pc_object(&mut rd, [n_v, n_eu, n_f, n_e])?;
rd.finite()?;
}
Op::MoveTsp => {
let n = rd.index(MAX_LIST)?;
for _ in 0..n {
rd.finites(6)?;
}
rd.index(MAX_LIST)?;
rd.index(MAX_LIST)?;
}
Op::RailEuIntersect => {
rd.below(ng)?;
rd.index(1)?;
rd.below(n_eu)?;
rd.finite()?;
}
Op::RecordError => {
rd.below(ng)?;
rd.index(7)?; // SmFilletErrorType
}
}
rd.done()?;
raw(op, path, input)
}
@@ -38,7 +38,9 @@
//! and the domain; `Sphere`, `RailUv` `status found f[nf]` and the Jacobian; others the
//! status (`Split` plus the geometry count, `UvVectors` plus both UV derivatives); `Fsg`
//! `type approximate tolerance% mirror complement scale continuity create_status` (`-1`
//! without a `CreateSurface` call); `Misc` the tolerance, or `status do2 idx2
//! without a `CreateSurface` call; the linear kind given two rails (kind `1` is followed
//! by `n_rails`, 0 or 2, and the rails as for `RailUv`) calls the virtual `CreateSurface`
//! and appends the ruled surface `has_nurb [n m p q r s cpts knots_u knots_v]`); `Misc` the tolerance, or `status do2 idx2
//! surface2_null n_periodic n_guess`.
//!
//! [`eval_static`] runs [`StaticOp`]s on the copy of `SmFilletSolver.cpp` compiled by
@@ -279,6 +281,18 @@ fn validate(op: Op, input: &[f64]) -> Result<(), FfiError> {
rd.finite()?;
}
rd.finites(2)?;
if kind == 1 {
// the linear kind: no rails, or the two rails of its CreateSurface
// (`CreateRuledSurface` reads their poles and knots, so well-formed
// curve blocks as for `RailUv`)
let rails = rd.index(2)?;
if rails == 1 {
return Err(bad("fsg: the linear kind takes 0 or 2 rails"));
}
for _ in 0..rails {
curve(&mut rd)?;
}
}
}
Op::Misc => {
if rd.index(1)? == 0 {
+74 -21
View File
@@ -44,11 +44,11 @@
//! `AdjustConcaveCornerTopology` and the `SmFilletConvexNxNCorner` /
//! `SmFilletNx1ClosedCorner` overrides (`blend.fillet_corner_p2`),
//! `TestAndConvertBigRadFillet` and `NoteFilletError` (`blend.fillet_executive_p1`),
//! `SmFilletEdge::MakeFilletEdge` (`blend.fillet_geom_p5`), `GetRailVertex`
//! (`blend.fillet_geom_p6`), `ReCalcFilletGeom` (`blend.fillet_geom_p3`),
//! `SplitFilletGeomAtSideEdge` (ported in `blend.fillet_solver_p1` on its own fillet
//! geometry handles; its first call `GetRailIndexOfVertexuse` is
//! `blend.fillet_geom_p6`, so the stub carries that tag) and `SmSurface::DropCurve`
//! `SmFilletEdge::MakeFilletEdge` (`blend.fillet_geom_p5`), `ReCalcFilletGeom`
//! (`blend.fillet_geom_p3`), `SplitFilletGeomAtSideEdge` (ported in
//! `blend.fillet_solver_p1`, but on the solver's own fillet geometry handles, which
//! carry no geometry data for its part 6 calls, so the stub carries that unit's tag)
//! and `SmSurface::DropCurve`
//! (`intersect.surface_drop_lib_p3`) return [`SolidError::Unported`] at the call site.
//! - **Intersection-point user pointers.** `SmTsectPnt::m_apUserPointer[0..2]` (the two
//! surfaces of a fillet vertex use's intersection point, read by
@@ -75,8 +75,8 @@
use std::collections::BTreeMap;
use crate::blend::fillet_geom::p1::{
FilStatus, FilletBrep, FilletCornerId, FilletCornerType, FilletEdgeType, FilletGeomId,
FilletGeomType, FilletSolverId, FilletVertex, FilletVertexType,
FilStatus, FilletBrep, FilletCornerId, FilletCornerType, FilletEdgeType, FilletGeomType,
FilletSolverId, FilletVertex, FilletVertexType,
};
use crate::blend::fillet_solver::p1::{FilletSolver, FilletSurfaceGeneratorType};
use crate::curve::curve::{Curve, CurveKind};
@@ -91,7 +91,7 @@ use crate::nurbs::b_spline_surface::BSplineSurface;
use crate::nurbs::b_spline_surface_n_lib::p2::OrientType;
use crate::surface::surface::{Surface, SurfaceKind};
use crate::topology::edgeuse::{p1 as eu1, p2 as eu2, p3 as eu3};
use crate::topology::{edge, Brep, EdgeId, EdgeuseId, FaceId, VertexId, VertexuseId};
use crate::topology::{edge, vertex, Brep, EdgeId, EdgeuseId, FaceId, VertexId, VertexuseId};
const OP_CREATE: &str = "blend::fillet_corner::p3::create";
const OP_PATCH: &str = "blend::fillet_corner::p3::create_corner_patch";
@@ -245,6 +245,8 @@ pub struct CornerEnv<'a> {
/// `m_apUserPointer[0..2]` of the intersection point of each fillet vertex use (see
/// the module docs).
pub tsect_surfaces: &'a BTreeMap<VertexuseId, [Option<Surface>; 2]>,
/// The fillet solvers the geometries' `GetFilletSolver()` ids index (`id.0`).
pub solvers: &'a mut [FilletSolver],
}
fn invalid(op: &'static str, reason: &'static str) -> SolidError {
@@ -1574,16 +1576,32 @@ impl FilletCorner {
.set_fillet_geom_type(FilletGeomType::TangentRollover);
}
// SmFilletSolver::SplitFilletGeomAtSideEdge is ported
// (`FilletSolver::split_fillet_geom_at_side_edge`, blend.fillet_solver_p1,
// on this pseudo B-rep's `FilletGeomId`s), but its first call is
// GetRailIndexOfVertexuse (blend.fillet_geom_p6). Wire it once
// fillet_geom_p6 lands (it needs the geometry's solver object).
// UNPORTED(blend.fillet_geom_p6): SmFilletSolver::SplitFilletGeomAtSideEdge
let new_geom: FilletGeomId = Err(unported(
"SmFilletSolver::SplitFilletGeomAtSideEdge",
"blend.fillet_geom_p6",
))?;
// pOrigGeom->GetFilletSolver()->SplitFilletGeomAtSideEdge(..): upstream
// dereferences a NULL solver
let si = env
.pseudo
.geom(orig_geom)?
.fillet_solver()
.ok_or_else(|| SolidError::null_pointer(OP_TANGENT))?;
let fs = usize::try_from(si.0)
.ok()
.and_then(|i| env.solvers.get_mut(i))
.ok_or_else(|| {
invalid(OP_TANGENT, "fillet solver id not in the solver table")
})?;
// a NULL fillet vertex use fails GetRailIndexOfVertexuse's NER
let split = match this_fv_use {
Some(vu) => fs.split_fillet_geom_at_side_edge(
env.k, env.target, env.pseudo, orig_geom, vu, which_end,
),
None => Err(SolidError::null_pointer(OP_TANGENT)),
};
// If failure, the whole fillet could still succeed, so don't SER.
let new_geom = match split {
Ok(g) => g,
Err(e) if e.is_unported() => return Err(e),
Err(_) => continue,
};
env.pseudo
.fillet_vertex_mut(this_fv)?
@@ -1635,10 +1653,44 @@ impl FilletCorner {
.set_status(FilStatus::Unprocessed);
}
// UNPORTED(blend.fillet_geom_p6): SmFilletGeom::GetRailVertex
// Also: there are now two new FilletVertex's, which belong
// to this FilletCorner. Add them to our list, and set
// their back-pointer to us.
// 'Same' is on the same side as pThisFV, 'Opp' as pOppositeFV.
// Also recall that the new FilletVertex's are at the high end
// of the original FG -- hence the 2nd arguments:
let new_fv_same = env.pseudo.rail_vertex(orig_geom, rail_idx, 1)?;
let new_fv_opp = env.pseudo.rail_vertex(orig_geom, other_rail, 1)?;
// pNewFVSame should be all set, it's what pThisFV used to be.
// But pNewFVOpp is now in a face.
let opp_rail = env
.pseudo
.geom(orig_geom)?
.rail(other_rail)
.ok_or_else(|| SolidError::null_pointer(OP_TANGENT))?;
let opp_base_face = env.pseudo.fillet_edge(opp_rail)?.original_face();
env.pseudo
.fillet_vertex_mut(new_fv_opp)?
.set_point_class(PointClassType::Face, opp_base_face.map(EntityRef::Face));
// We also have to take responsibility for the new edge.
let new_edges =
vertex::common_edges(env.pseudo.brep(), new_fv_same, Some(new_fv_opp), None)?;
if let Some(&this_fil_edge) = new_edges.first() {
self.edges.push(this_fil_edge);
env.pseudo
.fillet_edge_mut(this_fil_edge)?
.set_fillet_corner(env.corner);
}
// We have to update the fillet surfaces in the fillet geoms here.
// A prerequisite to that is recalculating the vertex geom (its status is
// ignored upstream).
// UNPORTED(blend.fillet_corner_p1): SmFilletCorner::CalcCornerVertGeom
return Err(unported(
"SmFilletGeom::GetRailVertex",
"blend.fillet_geom_p6",
"SmFilletCorner::CalcCornerVertGeom",
"blend.fillet_corner_p1",
));
}
@@ -1822,6 +1874,7 @@ mod tests {
pseudo: &mut pseudo,
corner: None,
tsect_surfaces: &surfaces,
solvers: &mut [],
};
let mut c = FilletCorner::new(FilletCornerKind::FourByThree, None, None, None, 1e-3, 0.1);
assert!(c.calc_corner_geom(&k, &mut env).is_ok());
@@ -18,7 +18,7 @@
//! | [`SecantConstantRadiusFs::load_jacobian`] | `SmSecantConstantRadiusFS::LoadJacobian` |
//! | [`ConstantRadiusAssistedFs`] (CO-PORTED) | `SmConstantRadiusFS` / `SmConstantRadiusAssistedFS` over the `SmFilletSolver` base ([`FilletSolver`]) |
//! | `impl FilletSolverOps for ConstantRadiusAssistedFs` | the same inherited overrides plus `SmConstantRadiusFS::LoadJacobian` (`blend.fillet_standard_solver_p1`'s methods); `IsKindOf` and `LoadInitialValues` (`_p2`) return [`SolidError::Unported`] (the class `blend.fillet_intersector` runs `ComputeSurfaceValues` on) |
//! | `impl FilletSolverOps` for both solvers | their `LoadJacobian` / `LoadInitialValues` overrides; the inherited `SmConstantRadiusFS` / `SmConstantRadiusAssistedFS` overrides (`GetJacobianSize`, `SetupOffsetValues`, `OffsetRadiiCanChange`, `GetType`); `IsKindOf` returns [`SolidError::Unported`] (unit `blend.fillet_standard_solver_p2`) |
//! | `impl FilletSolverOps` for both solvers | their `LoadJacobian` / `LoadInitialValues` overrides; the inherited `SmConstantRadiusFS` / `SmConstantRadiusAssistedFS` overrides `GetJacobianSize`, `SetupOffsetValues`, `OffsetRadiiCanChange`, `GetType` call `blend.fillet_standard_solver_p1`'s; `IsKindOf` (`_p2`) returns [`SolidError::Unported`] |
//! | [`OffsetSurface`], [`SurfaceId`] | an `SmOffsetSurface*` rail surface (unit `surface.offset_surface`) and the identity of its base pointer |
//! | [`JacobianLoad`], [`InitialValues`] | the in/out arguments of `LoadJacobian`, `LoadInitialValues` |
//!
@@ -1608,33 +1608,30 @@ macro_rules! fillet_solver_ops {
&mut self.base.solver
}
/// `SmConstantRadiusFS::GetJacobianSize`.
/// `SmConstantRadiusFS::GetJacobianSize` (inherited).
fn jacobian_size(&self) -> Result<usize> {
Ok(self.base.jacobian_size())
Ok(ConstantRadiusAssistedFs::jacobian_size(&self.base))
}
/// `SmConstantRadiusFS::SetupOffsetValues`.
/// `SmConstantRadiusFS::SetupOffsetValues` (inherited).
fn setup_offset_values(
&mut self,
tsect: &TsectPnt,
offset_dist: Option<&mut f64>,
) -> Result<()> {
let d = self.base.setup_offset_values(tsect)?;
if let Some(o) = offset_dist {
*o = d;
}
Ok(())
constant_radius_setup_offset_values(&mut self.base, tsect, offset_dist)
}
/// `SmConstantRadiusFS::OffsetRadiiCanChange`.
/// `SmConstantRadiusFS::OffsetRadiiCanChange` (inherited).
fn offset_radii_can_change(&self) -> Result<bool> {
Ok(self.base.offset_radii_can_change())
Ok(ConstantRadiusAssistedFs::offset_radii_can_change(
&self.base,
))
}
/// `SmConstantRadiusAssistedFS::GetType` (`SM_COMMON`: the derived solvers
/// declare none of their own).
/// `SmConstantRadiusAssistedFS::GetType` (inherited, `SM_COMMON`).
fn type_code(&self) -> Result<i64> {
Ok(crate::blend::fillet_standard_solver::p1::CONSTANT_RADIUS_ASSISTED_FS_TYPE)
Ok(CONSTANT_RADIUS_ASSISTED_FS_TYPE)
}
/// `SmConstantRadiusAssistedFS::IsKindOf` (unit
@@ -622,22 +622,21 @@ pub trait ExecutiveAccess<O: ClassObject + SolutionObject + Ord> {
intersector: &mut TopologyIntersector<O>,
) -> Result<()>;
/// `SmFilletGeom::TrimOriginalSurfaces`; waiting for `blend.fillet_geom_p6`.
/// `SmFilletGeom::TrimOriginalSurfaces(rKept, rDeleted, rMarkLocks)`: delegate to
/// the already ported `blend::fillet_geom::p6::FilletBrep::trim_original_surfaces`
/// (fillet geom p6) on the fillet geometry `geom` resolves to, with the fillet
/// B-rep (`executive.fillet`), the target B-rep (`executive.target`), the
/// executive's topology-intersector mates and the solver table; the faces it
/// collects resolve back to handles in `kept_faces` / `deleted_faces`.
fn trim_original_surfaces(
&mut self,
_k: &mut Kernel,
_geom: O,
_kept_faces: &mut Vec<O>,
_deleted_faces: &mut Vec<O>,
_marks: &[ContextMark],
_executive: &mut FilletExecutive<O>,
) -> Result<()> {
// UNPORTED(blend.fillet_geom_p6): SmFilletGeom::TrimOriginalSurfaces
Err(SolidError::unported(
"SmFilletGeom::TrimOriginalSurfaces",
"blend.fillet_geom_p6",
))
}
k: &mut Kernel,
geom: O,
kept_faces: &mut Vec<O>,
deleted_faces: &mut Vec<O>,
marks: &[ContextMark],
executive: &mut FilletExecutive<O>,
) -> Result<()>;
/// Delegate to the live intersector's already ported
/// `TopologyIntersector::delete_faces` (boolean intersector p2), using this
@@ -8,5 +8,7 @@
pub mod p1;
pub mod p2;
pub mod p3;
pub mod p4;
pub mod p5;
pub mod p6;
+50 -56
View File
@@ -62,9 +62,8 @@
//! stubs (the four vertex calculators stop at the filleted vertex, right after the
//! corner's null check; `CalcRailRailInterpolation` only with a corner).
//! - **Unported callees.** `SmFilletSolver::RailRailIntersect` and `PointOnPlaneSolve`
//! (`blend.fillet_solver_p2`); `SmFilletGeom::GetRailIndexOfSideEU`,
//! `RailEdgeuseIntersect` and `SmFilletEdge::ShouldSwapFaces`
//! (`blend.fillet_geom_p6`); `SmTemporaryRadiusChange` (`blend.fillet_executive_p2`)
//! (`blend.fillet_solver_p2`, also behind part 6's `RailEdgeuseIntersect`);
//! `SmTemporaryRadiusChange` (`blend.fillet_executive_p2`)
//! for a solver (its set-up is a no-op for a `NULL` solver); `SmSurface::DropCurve`
//! (`intersect.surface_drop_lib_p3`) on anything but a plane (the `SmPlane` override
//! is ported). Each stub replaces only its call; the code behind it is ported.
@@ -77,6 +76,7 @@
use std::borrow::Cow;
use crate::blend::fillet_corner::p3::FilletCorner;
use crate::blend::fillet_custom_solver::ConstantRadiusAssistedFs;
use crate::blend::fillet_solver::p1::{FilletSolver, FilletSurfaceGeneratorType};
use crate::curve::curve::p3::PointSolveOptions;
@@ -454,38 +454,26 @@ fn point_on_plane_solve(
)
}
/// `SmFilletGeom::GetRailIndexOfSideEU(pSideEU, rlRailIndex)` (unit
/// `blend.fillet_geom_p6`).
fn rail_index_of_side_eu(
fb: &FilletBrep,
g: FilletGeomId,
side_eu: Option<EdgeuseId>,
) -> Result<usize> {
// UNPORTED(blend.fillet_geom_p6): SmFilletGeom::GetRailIndexOfSideEU
unported_on(
(fb, g, side_eu),
"SmFilletGeom::GetRailIndexOfSideEU",
"blend.fillet_geom_p6",
)
}
/// `SmFilletGeom::RailEdgeuseIntersect(lRailIndex, pSideEU, dGuess, rbFound,
/// rTsectPnt, rdEdgeuseParameter)` (unit `blend.fillet_geom_p6`): whether the rail
/// meets the side edge use, the intersection point and the edge use parameter.
/// rTsectPnt, rdEdgeuseParameter)` (part 6) on geometry `g`'s solver in `solvers`:
/// whether the rail meets the side edge use, the intersection point and the edge use
/// parameter. While it runs the solver holds `g` as its geometry for surfaces, as upstream's `SetFilletGeomForSurfaces(this)`.
#[allow(clippy::too_many_arguments)]
fn rail_edgeuse_intersect(
fb: &FilletBrep,
k: &Kernel,
g: FilletGeomId,
solvers: &mut [FilletSolver],
target: &Brep,
rail_index: usize,
side_eu: EdgeuseId,
guess: f64,
tsect_pnt: &TsectPnt,
) -> Result<(bool, TsectPnt, f64)> {
// UNPORTED(blend.fillet_geom_p6): SmFilletGeom::RailEdgeuseIntersect
unported_on(
(fb, g, rail_index, side_eu, guess, tsect_pnt),
"SmFilletGeom::RailEdgeuseIntersect",
"blend.fillet_geom_p6",
)
let si = fb.geom_solver_index(g, solvers, OP_RAIL_EU)?;
let fs = solvers.get_mut(si).ok_or_else(|| {
SolidError::invalid_input(OP_RAIL_EU, "fillet solver id not in the solver table")
})?;
fb.rail_edgeuse_intersect(k, g, fs, target, rail_index, side_eu, guess)
}
/// `SmTemporaryRadiusChange(pFS, lRailIndex, pSideEU)` (unit
@@ -509,21 +497,6 @@ fn temporary_radius_change(
}
}
/// `SmFilletEdge::ShouldSwapFaces(pOrigFace, pNewFace)` (unit `blend.fillet_geom_p6`).
fn should_swap_faces(
fb: &FilletBrep,
e: EdgeId,
orig_face: Option<FaceId>,
new_face: Option<FaceId>,
) -> Result<bool> {
// UNPORTED(blend.fillet_geom_p6): SmFilletEdge::ShouldSwapFaces
unported_on(
(fb, e, orig_face, new_face),
"SmFilletEdge::ShouldSwapFaces",
"blend.fillet_geom_p6",
)
}
// ---- sm_SrfSrfIntersection --------------------------------------------------------------
/// The curves of [`srf_srf_intersection`]: the 3D intersection curve (`None` only on a
@@ -1137,7 +1110,7 @@ impl FilletBrep {
/// `SM_ERR` without a fillet geometry and, for a blending corner, when no
/// intersection is found; those of the evaluations and solves;
/// [`SolidError::Unported`] for the corner (`blend.fillet_corner_p1`),
/// `GetRailIndexOfSideEU` / `RailEdgeuseIntersect` (`blend.fillet_geom_p6`) and the
/// `SmFilletSolver::RailEdgeuseIntersect` (`blend.fillet_solver_p2`) and the
/// temporary radius change of a solver (`blend.fillet_executive_p2`).
#[allow(clippy::too_many_lines)] // one upstream routine
pub fn calc_rail_int_edgeuse(
@@ -1234,7 +1207,8 @@ impl FilletBrep {
if let Some(g1) = fillet_geom1 {
// its status is ignored upstream (lRailIndex stays 0)
let rail_index =
ignore_status(rail_index_of_side_eu(self, g1, Some(side_eu1)))?.unwrap_or(0);
ignore_status(self.rail_index_of_side_eu(g1, side_eu1, target, solvers))?
.unwrap_or(0);
let vu1 = vu1.ok_or_else(|| SolidError::null_pointer(OP))?;
let mut tsect1 = self.fillet_vertexuse(vu1)?.tsect_pnt().clone();
@@ -1250,8 +1224,9 @@ impl FilletBrep {
};
temporary_radius_change(fs_for_change, rail_index, side_eu1)?;
let (f, t, p) =
rail_edgeuse_intersect(self, g1, rail_index, side_eu1, guess, &tsect1)?;
let (f, t, p) = rail_edgeuse_intersect(
self, k, g1, solvers, target, rail_index, side_eu1, guess,
)?;
found = f;
tsect1 = t;
edgeuse_parameter = p;
@@ -1275,8 +1250,9 @@ impl FilletBrep {
rail_index,
side_eu1,
)?;
let (f, t, p) =
rail_edgeuse_intersect(self, g1, rail_index, side_eu1, guess, &tsect1)?;
let (f, t, p) = rail_edgeuse_intersect(
self, k, g1, solvers, target, rail_index, side_eu1, guess,
)?;
found = f;
tsect1 = t;
edgeuse_parameter = p;
@@ -1371,7 +1347,12 @@ impl FilletBrep {
let side_eu2 = eu1::radial(target, side_eu1)?;
// its status is ignored upstream
let other_rail_index =
ignore_status(rail_index_of_side_eu(self, g2, side_eu2))?.unwrap_or(0);
// upstream dereferences a NULL side edge use there
ignore_status(match side_eu2 {
Some(eu) => self.rail_index_of_side_eu(g2, eu, target, solvers),
None => Err(SolidError::null_pointer(OP)),
})?
.unwrap_or(0);
let side_eu2 = side_eu2.ok_or_else(|| SolidError::null_pointer(OP))?;
let (vu2, mut tsect2) = self.rail_end_tsect(v, Some(g2), vu1, OP)?;
@@ -1385,8 +1366,16 @@ impl FilletBrep {
other_rail_index,
side_eu2,
)?;
let (f, t, p) =
rail_edgeuse_intersect(self, g2, other_rail_index, side_eu2, guess, &tsect2)?;
let (f, t, p) = rail_edgeuse_intersect(
self,
k,
g2,
solvers,
target,
other_rail_index,
side_eu2,
guess,
)?;
found = f;
tsect2 = t;
edgeuse_parameter2 = p;
@@ -1790,7 +1779,7 @@ impl FilletBrep {
// ---- fillet edges ----------------------------------------------------------------
/// The first `SM_FE_RAIL` edge at fillet vertex `v` (`GetEdges` order).
fn first_rail_at(&self, v: VertexId) -> Result<Option<EdgeId>> {
pub(crate) fn first_rail_at(&self, v: VertexId) -> Result<Option<EdgeId>> {
for e in vertex::edges(self.brep(), v, None)? {
if self.fillet_edge(e)?.fillet_edge_type() == FilletEdgeType::Rail {
return Ok(Some(e));
@@ -2149,22 +2138,27 @@ impl FilletBrep {
///
/// # Errors
///
/// [`SolidError::NotFound`] when `e` is not a fillet edge;
/// [`SolidError::Unported`] for `ShouldSwapFaces` (`blend.fillet_geom_p6`).
/// [`SolidError::NotFound`] when `e` is not a fillet edge; `SM_ERR_NULL_POINTER`
/// for a `NULL` face (upstream's `ShouldSwapFaces` dereferences both); those of
/// `ShouldSwapFaces` (`target` holds the faces, `corners` resolves the edge's
/// corner).
pub fn update_edge_split_face(
&mut self,
k: &Kernel,
e: EdgeId,
orig_face: Option<FaceId>,
new_face: Option<FaceId>,
target: &mut Brep,
corners: &[FilletCorner],
) -> Result<()> {
let rail_face = self.fillet_edge(e)?.original_face();
if rail_face == orig_face {
// This FilletEdge was in the Face that got split.
let swap = should_swap_faces(self, e, orig_face, new_face)?;
// upstream stores the pointers as given; a NULL face has no id here
// a NULL face has no id here (sm_ClassifyFacePoint would dereference it)
let (Some(orig), Some(new)) = (orig_face, new_face) else {
return Err(SolidError::null_pointer(OP_SPLIT));
};
let swap = self.should_swap_faces(k, e, orig, new, target, corners)?;
if swap {
let fe = self.fillet_edge_mut(e)?;
fe.set_original_face(Some(new));
File diff suppressed because it is too large Load Diff
+57 -91
View File
@@ -55,15 +55,16 @@
//! `blend.fillet_geom_p2`'s, called with the fillet vertices' points; its curves are
//! used through their B-spline part (an `SmLine` / `SmEllipse` intersection curve
//! loses its analytic overrides in the later evaluations here).
//! - **Unported callees.** `GetRailIndexOfVertexuse` and `JoinTwoFilletVertsByCurves` and
//! `SmFilletEdge::CalcBlendingRail` (`blend.fillet_geom_p6`); the fillet executive's
//! - **Part 6.** `GetRailIndexOfVertexuse`, `JoinTwoFilletVertsByCurves`,
//! `SmFilletEdge::CalcBlendingRail` and `RecordFilletError` are `blend.fillet_geom_p6`'s;
//! `MakeFaceBrep`'s 3D curve list holds `Option<Curve>` as the join appends them.
//! - **Unported callees.** The fillet executive's
//! topology intersector, attribute users and topology bookkeeping
//! (`blend.fillet_executive_p1`), `SmTopologyIntersector::MergeCurveOnSurfaces`
//! (`boolean.topology_intersector_p3`), `RegisterExistingSSI` (ported in
//! `boolean.topology_intersector_p2`, but it needs the executive's intersector, so its
//! stub is tagged `blend.fillet_executive_p1`) and `RecordFilletError`
//! (`blend.fillet_geom_p6`) in `InsertIntersectionTopology`. Each stub replaces only
//! its call; the code behind it is ported. The 777/778 marker attributes
//! stub is tagged `blend.fillet_executive_p1`) in `InsertIntersectionTopology`. Each
//! stub replaces only its call; the code behind it is ported. The 777/778 marker attributes
//! `InsertIntersectionTopology` puts on a face for the executive's split check travel
//! with the `UpdateTopologyChanges` stub.
//! - **B-spline casts.** `SM_CAST_PTR(SmBSplineCurve, ..)` succeeds for every kind
@@ -117,8 +118,10 @@ use crate::topology::{Brep, EdgeId, EdgeuseId, FaceId, VertexId};
use super::p1::{
cleanup_noise, join_verts_by_capping, CurveObjectHandle, FilStatus, FilletBrep, FilletCornerId,
FilletCornerType, FilletEdgeType, FilletGeomId, FilletGeomType, FilletSolverId,
FilletCornerType, FilletEdgeType, FilletErrorType, FilletGeomId, FilletGeomType,
FilletSolverId,
};
use super::p6::join_two_fillet_verts_by_curves;
const OP_UV: &str = "blend::fillet_geom::p4::create_uv_curve";
const OP_NX2: &str = "blend::fillet_geom::p4::FilletBrep::calc_nx2_vert_fillet_x_fillet";
@@ -135,7 +138,7 @@ fn unported_on<A, T>(_args: A, upstream: &'static str, unit: &'static str) -> Re
}
/// The solver of a fillet geometry in the caller's table (index `id.0`).
fn solver_of<'s>(
pub(crate) fn solver_of<'s>(
solvers: &'s [FilletSolver],
id: FilletSolverId,
op: &'static str,
@@ -347,59 +350,16 @@ fn corner_extended_surface(corner: FilletCornerId, face: FaceId) -> Result<Optio
)
}
/// `SmFilletGeom::GetRailIndexOfVertexuse` (unit `blend.fillet_geom_p6`).
fn rail_index_of_vertexuse(
fb: &FilletBrep,
g: FilletGeomId,
fvu: Option<crate::topology::VertexuseId>,
) -> Result<usize> {
// UNPORTED(blend.fillet_geom_p6): SmFilletGeom::GetRailIndexOfVertexuse
unported_on(
(fb, g, fvu),
"SmFilletGeom::GetRailIndexOfVertexuse",
"blend.fillet_geom_p6",
)
}
/// The curve lists `MakeFaceBrep` grows: 3D curves, UV curves (`NULL` entries for a
/// cliff side patch) and orientations.
/// The curve lists `MakeFaceBrep` grows: 3D curves (`NULL` entries where a side edge
/// has no curve, as `JoinTwoFilletVertsByCurves` appends them), UV curves (`NULL`
/// entries for a cliff side patch) and orientations.
#[derive(Debug, Default)]
struct CurveLists {
curves_3d: Vec<Curve>,
curves_3d: Vec<Option<Curve>>,
uv_curves: Vec<Option<Curve>>,
orients: Vec<OrientType>,
}
/// `JoinTwoFilletVertsByCurves` (unit `blend.fillet_geom_p6`): appends the side edge
/// uses' curves from `start_fv` to `end_fv`; whether the connection was made.
#[allow(clippy::too_many_arguments)]
fn join_two_fillet_verts_by_curves(
fb: &FilletBrep,
g: FilletGeomId,
start_fv: Option<VertexId>,
end_fv: Option<VertexId>,
side_eus: &[EdgeuseId],
uv_tol: f64,
lists: &mut CurveLists,
) -> Result<bool> {
// UNPORTED(blend.fillet_geom_p6): JoinTwoFilletVertsByCurves
unported_on(
(fb, g, start_fv, end_fv, side_eus, uv_tol, lists),
"JoinTwoFilletVertsByCurves",
"blend.fillet_geom_p6",
)
}
/// `SmFilletEdge::CalcBlendingRail` (unit `blend.fillet_geom_p6`).
fn calc_blending_rail(k: &Kernel, fb: &mut FilletBrep, rail: EdgeId) -> Result<()> {
// UNPORTED(blend.fillet_geom_p6): SmFilletEdge::CalcBlendingRail
unported_on(
(k, fb, rail),
"SmFilletEdge::CalcBlendingRail",
"blend.fillet_geom_p6",
)
}
/// The fillet executive's topology intersector (`GetTopologyIntersector`, the inline
/// `SmMerge` accessor of the `SmFilletExecutive` object, which has no Rust object yet:
/// the solver holds an opaque [`crate::blend::fillet_solver::p1::FilletExecutiveRef`];
@@ -476,22 +436,6 @@ fn update_topology_changes(solver: &FilletSolver, attr: u32, face: FaceId) -> Re
)
}
/// `SmFilletGeom::RecordFilletError(SM_FILERR_BAD_MERGE, ..)` (unit
/// `blend.fillet_geom_p6`).
fn record_bad_merge(
g: FilletGeomId,
edge: Option<EdgeId>,
face: FaceId,
rail: EdgeId,
) -> Result<()> {
// UNPORTED(blend.fillet_geom_p6): SmFilletGeom::RecordFilletError
unported_on(
(g, edge, face, rail),
"SmFilletGeom::RecordFilletError",
"blend.fillet_geom_p6",
)
}
// ---- sm_CreateUVCurve -----------------------------------------------------------------
/// The curves [`create_uv_curve`] builds: `rp3DCurve` (only when the UV curve is not
@@ -1568,8 +1512,8 @@ impl FilletBrep {
/// # Errors
///
/// `SM_ERR_NULL_POINTER` for a `None` vertex or a missing rail vertex use (upstream
/// dereferences it); [`SolidError::Unported`] for `GetRailIndexOfVertexuse`
/// (`blend.fillet_geom_p6`).
/// dereferences it); `SM_ERR` from `GetRailIndexOfVertexuse` when no rail end
/// matches.
pub fn rail_index_of_vertex(&self, g: FilletGeomId, fvtx: Option<VertexId>) -> Result<usize> {
let fvtx = fvtx.ok_or_else(|| SolidError::null_pointer(OP_RAIL_IDX))?;
@@ -1587,7 +1531,7 @@ impl FilletBrep {
}
}
rail_index_of_vertexuse(self, g, fvu_to_use)
self.rail_index_of_vertexuse(g, fvu_to_use)
}
/// `SmFilletGeom::MakeFaceBrep`: makes the face of geometry `g` in `brep` (the
@@ -1601,9 +1545,8 @@ impl FilletBrep {
/// # Errors
///
/// `SM_ERR_NULL_POINTER` without fillet surface, solver, rails, edge uses or UV
/// curves; those of the evaluations, the capping and the face makers;
/// [`SolidError::Unported`] for `JoinTwoFilletVertsByCurves`
/// (`blend.fillet_geom_p6`).
/// curves; those of the evaluations, `JoinTwoFilletVertsByCurves`, the capping and
/// the face makers.
#[allow(clippy::too_many_lines)] // one upstream routine
pub fn make_face_brep(
&mut self,
@@ -1678,7 +1621,7 @@ impl FilletBrep {
&geom.other_rails1
};
lists1.curves_3d.push(rail_curve(self, rail0)?);
lists1.curves_3d.push(Some(rail_curve(self, rail0)?));
lists1.uv_curves.push(uv_curve0);
lists1.orients.push(OrientType::Same);
@@ -1697,7 +1640,7 @@ impl FilletBrep {
let total = other_rails.len();
for (ii, &other_rail) in other_rails.iter().enumerate() {
prim = primary_edgeuse(self.brep(), other_rail)?.ok_or_else(nul)?;
lists1.curves_3d.push(rail_curve(self, other_rail)?);
lists1.curves_3d.push(Some(rail_curve(self, other_rail)?));
lists1.uv_curves.push(eu_uv_curve(self, prim)?);
lists1.orients.push(OrientType::Same);
@@ -1721,7 +1664,7 @@ impl FilletBrep {
// For this side go backwards
for (ii, &other_rail) in other_rails1.iter().enumerate().rev() {
prim = primary_edgeuse(self.brep(), other_rail)?.ok_or_else(nul)?;
lists2.curves_3d.push(rail_curve(self, other_rail)?);
lists2.curves_3d.push(Some(rail_curve(self, other_rail)?));
lists2.uv_curves.push(eu_uv_curve(self, prim)?);
lists2.orients.push(OrientType::Opposite);
@@ -1734,7 +1677,7 @@ impl FilletBrep {
// Finally load the MaxV rail into OrderedEdge List2.
prim = primary_edgeuse(self.brep(), rail1)?.ok_or_else(nul)?;
lists2.curves_3d.push(rail_curve(self, rail1)?);
lists2.curves_3d.push(Some(rail_curve(self, rail1)?));
lists2.uv_curves.push(eu_uv_curve(self, prim)?);
lists2.orients.push(OrientType::Opposite);
@@ -1745,13 +1688,17 @@ impl FilletBrep {
// Add a sequence of m_vSideEUs->Curves to traverse from pEndFV0 to pEndFV1
let made_connection = join_two_fillet_verts_by_curves(
k,
self,
g,
end_fv0,
end_fv1,
&geom.side_eus,
uv_tol,
&mut lists1,
&mut lists1.curves_3d,
&mut lists1.uv_curves,
&mut lists1.orients,
solvers,
)?;
// when railCurveEnd/FilletEdgeStart has too big a gap: cap it
@@ -1783,13 +1730,17 @@ impl FilletBrep {
// Add in m_vSideEUs Edge sequence to traverse from pStartFV1 to pStartFV0
let made_connection = join_two_fillet_verts_by_curves(
k,
self,
g,
start_fv1,
start_fv0,
&geom.side_eus,
uv_tol,
&mut lists1,
&mut lists1.curves_3d,
&mut lists1.uv_curves,
&mut lists1.orients,
solvers,
)?;
self.geom_mut(g)?.capped[0] = false;
@@ -1818,6 +1769,7 @@ impl FilletBrep {
let degenerate = lists1
.curves_3d
.get(m)
.and_then(Option::as_ref)
.ok_or_else(nul)?
.is_degenerate(k, EFF_ZERO, None)?;
if degenerate {
@@ -1839,6 +1791,7 @@ impl FilletBrep {
let mut curves_3d = Vec::with_capacity(lists1.curves_3d.len());
let mut uv_curves = Vec::with_capacity(lists1.uv_curves.len());
for (c, uv) in lists1.curves_3d.iter().zip(&lists1.uv_curves) {
let c = c.as_ref().ok_or_else(nul)?;
as_b_spline(c).ok_or_else(nul)?;
let copy = c.copy(k)?;
as_b_spline(&copy).ok_or_else(nul)?;
@@ -1920,7 +1873,7 @@ impl FilletBrep {
&mut uv_curves,
&mut orients,
)?;
caps.curves_3d.append(&mut curves_3d);
caps.curves_3d.extend(curves_3d.into_iter().map(Some));
caps.uv_curves.extend(uv_curves.into_iter().map(Some));
caps.orients.append(&mut orients);
Ok(())
@@ -2019,7 +1972,17 @@ impl FilletBrep {
return Err(e);
}
if let Some(eu) = fs.edgeuse(0)? {
record_bad_merge(g, eu1::edge(target, eu)?, base_face, this_rail)?;
let fillet_surface = self.geom(g)?.fillet_surface().cloned();
self.record_fillet_error(
g,
FilletErrorType::BadMerge,
eu1::edge(target, eu)?, // filleted edge
Some(base_face),
Some(this_rail),
fillet_surface.as_ref(),
Some("SmFilletGeom::InsertIntersectionTopology(): Failure to merge fillet rail curve into side face of Target Brep"),
solvers,
)?;
}
return Err(e);
}
@@ -2096,9 +2059,8 @@ impl FilletBrep {
///
/// `SM_ERR` for a rail that is not a blending rail; `SM_ERR_NULL_POINTER` for a
/// missing solver, rail, curve, UV curve, face, surface or cross-section edge;
/// those of the length, arc-length, interpolation, lift and surface creations;
/// [`SolidError::Unported`] for `SmFilletEdge::CalcBlendingRail`
/// (`blend.fillet_geom_p6`).
/// those of the length, arc-length, interpolation, lift and surface creations and
/// of `SmFilletEdge::CalcBlendingRail`.
#[allow(clippy::too_many_lines)] // one upstream routine
pub fn calc_blending_geom(
&mut self,
@@ -2110,17 +2072,17 @@ impl FilletBrep {
let nul = || SolidError::null_pointer(OP_BLEND);
let sid = self.geom(g)?.fillet_solver().ok_or_else(nul)?;
let fil_solver = solver_of(solvers, sid, OP_BLEND)?;
let rail1 = self.geom(g)?.rail(0).ok_or_else(nul)?;
let rail2 = self.geom(g)?.rail(1).ok_or_else(nul)?;
let approx_tol_3d = fil_solver.approx_tol_3d;
let tangency_tol_radians = fil_solver.tangency_tolerance;
// Recreate each rail curve by sampling points evenly spaced in arc length
for rail in [rail1, rail2] {
// (m_vRails[i] is read as the loop reaches it: a failure on rail 0 comes first)
for i in 0..2 {
let rail = self.geom(g)?.rail(i).ok_or_else(nul)?;
if self.fillet_edge(rail)?.fillet_edge_type() != FilletEdgeType::BlendingRail {
return Err(SolidError::sm_err(OP_BLEND));
}
calc_blending_rail(k, self, rail)?;
self.calc_blending_rail(k, rail, target)?;
let rail_curve = self.brep().edge(rail)?.curve.clone().ok_or_else(nul)?;
as_b_spline(&rail_curve).ok_or_else(nul)?;
let (_, mate) = self.prim_and_mate(rail, OP_BLEND)?;
@@ -2182,6 +2144,10 @@ impl FilletBrep {
self.set_edgeuse_uv_curve(mate, Some(Curve::from(new_uv_curve)), true, true)?;
}
// pRail1, pRail2 (both processed above)
let rail1 = self.geom(g)?.rail(0).ok_or_else(nul)?;
let rail2 = self.geom(g)?.rail(1).ok_or_else(nul)?;
// Create ruled surface if we have linear cross sections
let fsg = fil_solver.fsg.ok_or_else(nul)?;
if fsg.fillet_surface_generator_type() == FilletSurfaceGeneratorType::Linear {
+51 -40
View File
@@ -73,9 +73,9 @@
//! (SmFilletGeom.cpp:10580-10645); [`Curve::drop_point`] and the solve here each build
//! their own. The results are bit-identical in every difftest case; a future
//! cache-dependent disagreement should look here first.
//! - **Unported callees.** `SmFilletGeom::FindEdgeTangentAtPoint` and
//! `SmFilletGeom::Copy` (`blend.fillet_geom_p6`); `SmFilletExecutive::GetPseudoBrep`
//! (`blend.fillet_executive_p1`); the corner members above. Each stub replaces only
//! - **Unported callees.** `SmFilletExecutive::GetPseudoBrep`
//! (`blend.fillet_executive_p1`); the corner members above. `FindEdgeTangentAtPoint`
//! and `SmFilletGeom::Copy` are part 6's (`blend.fillet_geom_p6`). Each stub replaces only
//! its call.
//! - **User pointers.** `MakeRailEdgeuse` stores the two faces' surface pointers in the
//! new vertex use's `SmTsectPnt::m_apUserPointer` (never read in SMLib; not modelled
@@ -84,6 +84,7 @@
//! - **Arguments.** Out-params become return values; `iDebugLevel` and debug drawing
//! are dropped. No recursion.
use crate::blend::fillet_corner::p3::FilletCorner;
use crate::blend::fillet_solver::p1::FilletSolver;
use crate::curve::curve::p1::{ApproximateCurveOptions, GlobalPointSolveOptions};
use crate::curve::curve::{Curve, CurveKind};
@@ -418,37 +419,14 @@ fn corner_record_fillet_error(
/// its status (SmFilletGeom.cpp:11865, 11877).
fn fillet_edge_update_split_face(
fb: &mut FilletBrep,
k: &Kernel,
e: EdgeId,
orig_face: Option<FaceId>,
new_face: Option<FaceId>,
target: &mut Brep,
corners: &[FilletCorner],
) -> Result<()> {
ignore_non_unported(fb.update_edge_split_face(e, orig_face, new_face))
}
/// `SmFilletGeom::FindEdgeTangentAtPoint(lRailIndex, lEndIndex, rbFound, rTangent)`
/// (unit `blend.fillet_geom_p6`): whether a tangent was found, and the tangent.
fn find_edge_tangent_at_point(
fb: &FilletBrep,
g: FilletGeomId,
rail_index: usize,
end_index: usize,
) -> Result<(bool, Vec3)> {
// UNPORTED(blend.fillet_geom_p6): SmFilletGeom::FindEdgeTangentAtPoint
unported_on(
(fb.edge_list().len(), g, rail_index, end_index),
"SmFilletGeom::FindEdgeTangentAtPoint",
"blend.fillet_geom_p6",
)
}
/// `SmFilletGeom::Copy(pFilletGeomToCopy)` (unit `blend.fillet_geom_p6`).
fn fillet_geom_copy(fb: &mut FilletBrep, g: FilletGeomId, src: FilletGeomId) -> Result<()> {
// UNPORTED(blend.fillet_geom_p6): SmFilletGeom::Copy
unported_on(
(fb.edge_list().len(), g, src),
"SmFilletGeom::Copy",
"blend.fillet_geom_p6",
)
ignore_non_unported(fb.update_edge_split_face(k, e, orig_face, new_face, target, corners))
}
/// `pFilletSolver->m_pExecutive->GetPseudoBrep()` (unit `blend.fillet_executive_p1`):
@@ -1176,7 +1154,7 @@ impl FilletBrep {
///
/// `SM_ERR_NULL_POINTER` for a source without solver or a solver without executive
/// (upstream dereferences them); [`SolidError::Unported`] for the executive's
/// pseudo B-rep (`blend.fillet_executive_p1`) and `Copy` (`blend.fillet_geom_p6`).
/// pseudo B-rep (`blend.fillet_executive_p1`).
pub fn copy_fillet_geom(
&mut self,
k: &Kernel,
@@ -1203,7 +1181,7 @@ impl FilletBrep {
for i in 0..2 {
ignore_non_unported(self.make_rail_edge(k, g, i))?;
}
ignore_non_unported(fillet_geom_copy(self, g, src))?;
ignore_non_unported(self.copy_geom(k, g, src, solvers))?;
let gtype = self.geom(src)?.fillet_geom_type();
self.geom_mut(g)?.set_fillet_geom_type(gtype);
Ok(g)
@@ -1286,6 +1264,26 @@ impl FilletBrep {
) -> Result<EdgeuseId> {
// Check input
let solver = self.geom_solver(g, solvers, OP_RAIL_EU)?;
self.make_rail_edgeuse_on(g, rail_index, fv, orient, belonging_rail, solver, target)
}
/// [`FilletBrep::make_rail_edgeuse`] with geometry `g`'s solver already resolved
/// (`GetFilletSolver()`, checked non-`NULL` by the caller).
///
/// # Errors
///
/// Those of [`FilletBrep::make_rail_edgeuse`] after its solver lookup.
#[allow(clippy::too_many_arguments)]
pub fn make_rail_edgeuse_on(
&mut self,
g: FilletGeomId,
rail_index: usize,
fv: Option<VertexId>,
orient: OrientType,
belonging_rail: Option<EdgeId>,
solver: &FilletSolver,
target: &Brep,
) -> Result<EdgeuseId> {
let fv = fv.ok_or_else(|| SolidError::null_pointer(OP_RAIL_EU))?;
self.fillet_vertex(fv)?;
if rail_index > 1 {
@@ -1382,8 +1380,9 @@ impl FilletBrep {
/// # Errors
///
/// `SM_ERR_NULL_POINTER` without surface, rails, uses, solver; those of the
/// curve creations, the lift and [`FilletBrep::make_fillet_edge`];
/// [`SolidError::Unported`] for `FindEdgeTangentAtPoint` (`blend.fillet_geom_p6`).
/// curve creations, the lift, [`FilletBrep::make_fillet_edge`] and (for
/// `SM_BT_BLEND`) `FindEdgeTangentAtPoint`, which reads the rail end vertices'
/// classification edges in `target`.
#[allow(clippy::too_many_lines)] // one upstream routine
pub fn create_end_curve(
&mut self,
@@ -1392,6 +1391,7 @@ impl FilletBrep {
end_index: usize,
trim_type: BoundaryTrimmingType,
solvers: &[FilletSolver],
target: &Brep,
) -> Result<EdgeId> {
// Create a line between the two UV points and lift the curve.
let fillet_surface = self
@@ -1447,8 +1447,12 @@ impl FilletBrep {
.distance(self.vertex_point(fv_end)?);
// Find Edge Tangents
let (found1, mut tangent1) = find_edge_tangent_at_point(self, g, 0, end_index)?;
let (found2, mut tangent2) = find_edge_tangent_at_point(self, g, 1, end_index)?;
// (rbFound, rTangent): a tangent not found leaves SmVector3d() undefined
let found = |t: Option<Vec3>| (t.is_some(), t.unwrap_or(Vec3::UNDEF));
let (found1, mut tangent1) =
found(self.find_edge_tangent_at_point(k, g, 0, end_index, target)?);
let (found2, mut tangent2) =
found(self.find_edge_tangent_at_point(k, g, 1, end_index, target)?);
// when 1st tangent was found
if found1 {
@@ -2065,12 +2069,16 @@ impl FilletBrep {
///
/// `SM_ERR_NULL_POINTER` for a missing rail (upstream calls through `NULL`); those of
/// `SmFilletEdge::UpdateSplitFace` (`blend.fillet_geom_p2`, wired; its status is
/// ignored upstream, so only [`SolidError::Unported`] propagates).
/// ignored upstream, so only [`SolidError::Unported`] propagates). `target` and
/// `corners` are those of [`FilletBrep::update_edge_split_face`].
pub fn update_geom_split_face(
&mut self,
k: &Kernel,
g: FilletGeomId,
orig_face: Option<FaceId>,
new_face: Option<FaceId>,
target: &mut Brep,
corners: &[FilletCorner],
) -> Result<()> {
// For both rails:
for ii in 0..2 {
@@ -2085,7 +2093,7 @@ impl FilletBrep {
&geom.other_rails2
});
for rail in all_rails {
fillet_edge_update_split_face(self, rail, orig_face, new_face)?;
fillet_edge_update_split_face(self, k, rail, orig_face, new_face, target, corners)?;
}
}
@@ -2093,7 +2101,9 @@ impl FilletBrep {
for feu in self.geom(g)?.side_eus.clone() {
if let Some(fe) = self.brep().edgeuse(feu)?.edge {
if self.fillet_edge(fe).is_ok() {
fillet_edge_update_split_face(self, fe, orig_face, new_face)?;
fillet_edge_update_split_face(
self, k, fe, orig_face, new_face, target, corners,
)?;
}
}
}
@@ -2211,7 +2221,8 @@ mod tests {
let mut fb = FilletBrep::new();
let g = fb.add_geom(FilletGeom::new(&k, None, FilletGeomType::Default).unwrap());
assert!(matches!(
fb.update_geom_split_face(g, None, None).unwrap_err(),
fb.update_geom_split_face(&k, g, None, None, &mut Brep::new(), &[])
.unwrap_err(),
SolidError::Upstream { .. }
));
}
File diff suppressed because it is too large Load Diff
+41 -59
View File
@@ -320,8 +320,8 @@ impl FilletSurfaceGenerator {
///
/// `SM_ERR` for [`FilletSurfaceGenerator::Base`]; those of
/// `SmEiffelTowerCrossSectionFSG::CreateSurface` and
/// `SmLinearCrossSectionFSG::CreateSurface`; [`SolidError::Unported`] for the
/// circular and blend-curve generators.
/// `SmLinearCrossSectionFSG::CreateSurface` ([`LinearCrossSectionFsg::create_surface`]);
/// [`SolidError::Unported`] for the circular and blend-curve generators.
pub fn create_surface(
&self,
k: &Kernel,
@@ -336,6 +336,7 @@ impl FilletSurfaceGenerator {
crate::blend::fillet_custom_solver::EiffelTowerCrossSectionFsg::new()
.create_surface(k, approx_tol_3d, rail1, rail2, points)
}
// the ruled surface between the rails (SER)
Self::Linear => LinearCrossSectionFsg.create_surface(rail1, rail2),
// UNPORTED(blend.fillet_standard_solver_p2): SmCircularCrossSectionFSG::CreateSurface
Self::Circular(_) => Err(SolidError::unported(
@@ -1067,11 +1068,11 @@ impl FilletSolver {
///
/// # Errors
///
/// [`SolidError::Unported`] from its first call (`SmFilletGeom::GetRailIndexOfVertexuse`,
/// unit `blend.fillet_geom_p6`), the split (`SmFilletGeom::TopologySplit`,
/// `blend.fillet_geom_p3`) and the rail vertex lookup (`GetRailVertex`,
/// `blend.fillet_geom_p6`); `SM_ERR_NULL_POINTER` (`NER`) for a missing fillet
/// vertex, side edge use, original face or adjacent face;
/// Those of [`FilletBrep::rail_index_of_vertexuse`] (`SM_ERR` when the vertex use
/// ends neither rail), [`FilletBrep::rail_vertex`] and
/// [`FilletBrep::topology_split`]; `SM_ERR_NULL_POINTER` (`NER`) for a geometry
/// without solver, a missing fillet vertex, side edge use, original face or
/// adjacent face;
/// [`SolidError::InvalidInput`] when the faces across the side edge are not tangent
/// (`SM_ERR_INVALID_INPUT`), for a classification object that is not an edge use
/// (upstream casts it) and where upstream dereferences `NULL`;
@@ -1090,12 +1091,7 @@ impl FilletSolver {
let nul = || SolidError::null_pointer(OP_S);
// Get the rail being split.
// UNPORTED(blend.fillet_geom_p6): SmFilletGeom::GetRailIndexOfVertexuse
let rail_idx: usize = unported_on(
(fillet_geom, split_vertexuse),
"SmFilletGeom::GetRailIndexOfVertexuse",
"blend.fillet_geom_p6",
)?;
let rail_idx = pseudo.rail_index_of_vertexuse(fillet_geom, Some(split_vertexuse))?;
let other_idx = 1usize
.checked_sub(rail_idx)
.ok_or_else(|| invalid("rail index out of range"))?;
@@ -1187,13 +1183,11 @@ impl FilletSolver {
.set_fillet_geom_type(FilletGeomType::TangentRollover);
}
// Do the split.
// UNPORTED(blend.fillet_geom_p3): SmFilletGeom::TopologySplit
let new_fillet_geom: FilletGeomId = unported_on(
fillet_geom,
"SmFilletGeom::TopologySplit",
"blend.fillet_geom_p3",
)?;
// Do the split (default eNewGeomType = SM_FG_DEFAULT; the geometry's solver is
// this one).
let solver_id = pseudo.geom(fillet_geom)?.fillet_solver().ok_or_else(nul)?;
let new_fillet_geom =
pseudo.topology_split(k, fillet_geom, FilletGeomType::Default, solver_id, self, b)?;
// The new fillet geom comes out as the 'high end'. Update offset surfaces,
// orig face, and type in the two FG's. Copy offset surface from non-split side
@@ -1307,20 +1301,10 @@ impl FilletSolver {
pseudo
.fillet_vertex_mut(fil_vtx)?
.set_status(FilStatus::Unprocessed);
let opp_fv: VertexId = if which_end == 0 {
// UNPORTED(blend.fillet_geom_p6): SmFilletGeom::GetRailVertex
unported_on(
(fillet_geom, 0usize, other_idx),
"SmFilletGeom::GetRailVertex",
"blend.fillet_geom_p6",
)?
let opp_fv = if which_end == 0 {
pseudo.rail_vertex(fillet_geom, 0, other_idx)?
} else {
// UNPORTED(blend.fillet_geom_p6): SmFilletGeom::GetRailVertex
unported_on(
(new_fillet_geom, 1usize, other_idx),
"SmFilletGeom::GetRailVertex",
"blend.fillet_geom_p6",
)?
pseudo.rail_vertex(new_fillet_geom, 1, other_idx)?
};
pseudo
.fillet_vertex_mut(opp_fv)?
@@ -1340,21 +1324,21 @@ impl FilletSolver {
/// [`SolidError::InvalidInput`] for a geometry index past the solver's geometries or
/// fewer second intervals than first ones (upstream only asserts equal sizes, then
/// reads `crIvls2[i]` for every `i` of `crIvls1`); [`SolidError::Unported`] for the
/// geometry copies (`SmFilletGeom::SmFilletGeom`), the rail vertices
/// (`SmFilletGeom::GetRailVertex`), the rail points and end curves
/// (`SetUpRailPoint`, `CreateEndCurve`); [`SolidError::NotFound`] for a vertex
/// without its fillet record; those of `FindPointClass` and [`trim_rail`].
pub fn split_fillet_geom<O: ClassObject>(
/// geometry copies (`SmFilletGeom::SmFilletGeom`) and the end curves
/// (`CreateEndCurve`); [`SolidError::NotFound`] for a vertex without its fillet
/// record; those of [`FilletBrep::rail_vertex`], `FindPointClass`,
/// [`FilletBrep::set_up_rail_point`] and [`trim_rail`].
pub fn split_fillet_geom(
&mut self,
k: &Kernel,
pseudo: &mut FilletBrep,
geometry: &impl ClassGeometry<O>,
geometry: &impl ClassGeometry<EntityRef>,
fillet_geom_index: usize,
trim_type: i32,
ivls1: &[Extent1d],
ivls2: &[Extent1d],
cc1: &CurveClassification<O>,
cc2: &CurveClassification<O>,
cc1: &CurveClassification<EntityRef>,
cc2: &CurveClassification<EntityRef>,
) -> Result<()> {
// SM_ASSERT(sizes equal) is a no-op; a shorter crIvls2 is read past its end.
if ivls2.len() < ivls1.len() {
@@ -1392,18 +1376,10 @@ impl FilletSolver {
{
// If we split things then set the processed flag for interior vertexuses
// to FALSE.
let rail_vertex = |rail: usize, end: usize| -> Result<VertexId> {
// UNPORTED(blend.fillet_geom_p6): SmFilletGeom::GetRailVertex
unported_on(
(fillet_geom, rail, end),
"SmFilletGeom::GetRailVertex",
"blend.fillet_geom_p6",
)
};
let v00 = rail_vertex(0, 0)?;
let v01 = rail_vertex(0, 1)?;
let v10 = rail_vertex(1, 0)?;
let v11 = rail_vertex(1, 1)?;
let v00 = pseudo.rail_vertex(fillet_geom, 0, 0)?;
let v01 = pseudo.rail_vertex(fillet_geom, 0, 1)?;
let v10 = pseudo.rail_vertex(fillet_geom, 1, 0)?;
let v11 = pseudo.rail_vertex(fillet_geom, 1, 1)?;
if i > 0 {
for v in [v00, v10] {
pseudo
@@ -1432,12 +1408,7 @@ impl FilletSolver {
(1, 0, ivl2.min(), pc10),
(1, 1, ivl2.max(), pc11),
] {
// UNPORTED(blend.fillet_geom_p6): SmFilletGeom::SetUpRailPoint
unported_on::<_, ()>(
(new_fillet_geom, rail, end, t, pc),
"SmFilletGeom::SetUpRailPoint",
"blend.fillet_geom_p6",
)?;
pseudo.set_up_rail_point(k, new_fillet_geom, rail, end, t, Some(&pc))?;
}
// Create the ending curve on the fillet
@@ -1789,6 +1760,17 @@ pub struct FilletSphereSolveEnfo<'a> {
}
impl<'a> FilletSphereSolveEnfo<'a> {
/// `m_crFilletSolver`.
#[must_use]
pub fn solver(&self) -> &dyn FilletSolverOps {
&*self.solver
}
/// `m_crFilletSolver`, for the virtuals that write to the solver.
pub fn solver_mut(&mut self) -> &mut dyn FilletSolverOps {
&mut *self.solver
}
/// `SmFilletSphereSolveENFO(crSphereCenter, dSphereRadius, crFilletSolver)`: the
/// plane starts as `(centre, radius)`, not adjusting.
pub fn new(
File diff suppressed because it is too large Load Diff
+937
View File
@@ -4211,6 +4211,943 @@
"g": 0
}
}
},
{
"name": "blending_missing_second_rail_g0",
"upstream_ref": "source/SMLib/src/SmFilletGeom.cpp",
"inputs": {
"geom_solvers": [
0
],
"model": {
"edges": [
{
"corner": -1,
"curve": {
"cpts": [
[
0.03204596292120246,
-0.0986865269870627,
-0.4384200158469018,
1.2214457507815517
],
[
0.5985165008118499,
-0.006562671929171482,
-0.2205802188490984,
1.2124695156358343
],
[
1.0506556660916582,
-0.14606854537362146,
0.4109855947281864,
1.8696575179118753
]
],
"dim": 3,
"p": 1,
"u": [
0.0,
0.0,
1.8091909848102568,
3.3477315499193545,
3.3477315499193545
]
},
"etype": 9,
"eus": [
{
"geom": 0,
"uv_curve": {
"cpts": [
[
0.0,
0.1,
0.0,
-1.7976931348623157e+308
],
[
1.0,
0.14902083214174705,
0.0,
-1.7976931348623157e+308
]
],
"dim": 2,
"p": 1,
"u": [
0.0,
0.0,
1.0,
1.0
]
},
"vu_uv": [
0.0,
0.0
]
},
{
"geom": -1,
"vu_uv": [
0.0,
0.0
]
}
],
"fbe": [
-1,
-1
],
"interval": [
0.0,
1.0
],
"status": 0,
"ve": 2,
"vs": 3
},
{
"corner": -1,
"curve": {
"cpts": [
[
-0.02001762539178671,
-0.029185989994649386,
-0.4671310059025263,
-1.7976931348623157e+308
],
[
0.2583113098133204,
-0.4411659869824637,
0.08875691394202712,
-1.7976931348623157e+308
],
[
0.6449549481228303,
-0.3224989213190811,
0.32244662380471967,
-1.7976931348623157e+308
],
[
1.0370206861533904,
0.04833195239204302,
0.40458811290571484,
-1.7976931348623157e+308
]
],
"dim": 3,
"p": 1,
"u": [
0.0,
0.0,
0.4326736454485701,
0.5895538532073098,
1.0,
1.0
]
},
"etype": 9,
"eus": [
{
"geom": 0,
"uv_curve": {
"cpts": [
[
0.0,
0.9,
0.0,
-1.7976931348623157e+308
],
[
1.0,
0.9052811626886675,
0.0,
-1.7976931348623157e+308
]
],
"dim": 2,
"p": 1,
"u": [
0.0,
0.0,
1.0,
1.0
]
},
"vu_uv": [
0.0,
0.0
]
},
{
"geom": -1,
"vu_uv": [
0.0,
0.0
]
}
],
"fbe": [
-1,
-1
],
"interval": [
0.0,
1.0
],
"status": 0,
"ve": 1,
"vs": 0
}
],
"faces": [],
"fillet_brep": [],
"geoms": [
{
"gtype": 4,
"other1": [],
"other2": [],
"rails": [
0,
-1
],
"sides": [],
"status": 0,
"surface": {
"net": [
[
[
-0.03274516446769393,
-0.03250592273412811,
-0.18565725029875935,
-1.7976931348623157e+308
],
[
-0.020225059524501182,
0.5309119394692008,
0.16119845355869195,
-1.7976931348623157e+308
],
[
0.025659184698584803,
1.061907417255935,
0.28779671085934483,
-1.7976931348623157e+308
]
],
[
[
0.26688741974373553,
0.06159404034632132,
-0.08947609335054521,
-1.7976931348623157e+308
],
[
0.3432604864691049,
0.42535123331386204,
0.037547829845161584,
-1.7976931348623157e+308
],
[
0.29670072767644906,
0.9011620238912565,
-0.09860917874470643,
-1.7976931348623157e+308
]
],
[
[
0.5125426875361516,
0.022657289839339345,
-0.09671352184764939,
-1.7976931348623157e+308
],
[
0.47263570556498263,
0.44225919347422016,
0.238625180893063,
-1.7976931348623157e+308
],
[
0.41184912724360717,
0.9789678982967961,
-0.13264518670760864,
-1.7976931348623157e+308
]
],
[
[
0.8398224423396548,
-0.07188819212259437,
0.19363156298208106,
-1.7976931348623157e+308
],
[
0.6938996195384811,
0.5998683945362994,
-0.0002727920739731271,
-1.7976931348623157e+308
],
[
0.7992258369438359,
1.094495483701051,
-0.1373444101742063,
-1.7976931348623157e+308
]
],
[
[
1.019531686246336,
-0.08083063206536363,
0.13901905927715658,
-1.7976931348623157e+308
],
[
0.990499002643255,
0.41224587404330837,
-0.12179207168860257,
-1.7976931348623157e+308
],
[
0.9779958412376945,
1.0454390181418525,
0.0431870271434992,
-1.7976931348623157e+308
]
]
],
"p": 2,
"q": 2,
"u": [
0.0,
0.0,
0.0,
0.3330478239299306,
0.7855318697274623,
1.0,
1.0,
1.0
],
"v": [
0.0,
0.0,
0.0,
1.0,
1.0,
1.0
]
}
}
],
"targets": [],
"vertices": [
{
"corner": -1,
"mates": [
-1,
-1
],
"pc_class": 100,
"pc_face": -1,
"pc_index": -1,
"pc_kind": 0,
"pc_sub": 0,
"point": [
0.20116559879261242,
-0.21728360519792456,
-0.413454420824255
],
"status": 0,
"t_param": 0.0,
"uv": [
0.0,
0.0
],
"vtype": 12
},
{
"corner": -1,
"mates": [
-1,
-1
],
"pc_class": 100,
"pc_face": -1,
"pc_index": -1,
"pc_kind": 0,
"pc_sub": 0,
"point": [
0.4703137259242749,
0.7106004465525304,
0.7057078888354815
],
"status": 1,
"t_param": 0.0,
"uv": [
0.0,
0.0
],
"vtype": 8
},
{
"corner": -1,
"mates": [
-1,
-1
],
"pc_class": 100,
"pc_face": -1,
"pc_index": -1,
"pc_kind": 0,
"pc_sub": 0,
"point": [
-0.17340417780706252,
-0.84940576309862,
-0.5235266783983834
],
"status": 1,
"t_param": 0.0,
"uv": [
0.0,
0.0
],
"vtype": 12
},
{
"corner": -1,
"mates": [
-1,
-1
],
"pc_class": 100,
"pc_face": -1,
"pc_index": -1,
"pc_kind": 0,
"pc_sub": 0,
"point": [
0.9844701755975609,
0.028556938055111836,
-0.6447185602868946
],
"status": 2,
"t_param": 0.0,
"uv": [
0.0,
0.0
],
"vtype": 0
}
]
},
"query": {
"g": 0,
"q": "blending"
},
"solvers": [
{
"ang": 0.33973788870384547,
"approx": 1e-06,
"tang": 0.07296487960805757
}
]
}
},
{
"name": "blending_missing_second_rail_g1",
"upstream_ref": "source/SMLib/src/SmFilletGeom.cpp",
"inputs": {
"geom_solvers": [
-1,
0
],
"model": {
"edges": [
{
"corner": -1,
"etype": 1,
"eus": [
{
"geom": 1,
"uv_curve": {
"cpts": [
[
0.0,
0.1,
0.0,
-1.7976931348623157e+308
],
[
1.0,
0.0675880162796339,
0.0,
-1.7976931348623157e+308
]
],
"dim": 2,
"p": 1,
"u": [
0.0,
0.0,
1.0,
1.0
]
},
"vu_uv": [
0.0,
0.0
]
},
{
"geom": -1,
"vu_uv": [
0.0,
0.0
]
}
],
"fbe": [
-1,
-1
],
"interval": [
0.0,
1.0
],
"status": 0,
"ve": 2,
"vs": 1
},
{
"corner": -1,
"curve": {
"cpts": [
[
-0.03883460535508028,
0.24277173343539915,
-0.013327460845436012,
1.8159099025023409
],
[
0.5332907189640317,
-0.30107238666588143,
0.39429370762675253,
1.60617924685439
],
[
0.9553166897588158,
0.4412686848724081,
0.1913955038385896,
1.0432780607668817
]
],
"dim": 3,
"p": 1,
"u": [
0.0,
0.0,
1.031480379586288,
1.7679549912851564,
1.7679549912851564
]
},
"etype": 1,
"eus": [
{
"geom": 1,
"uv_curve": {
"cpts": [
[
0.0,
0.9,
0.0,
-1.7976931348623157e+308
],
[
1.0,
0.8938827239590716,
0.0,
-1.7976931348623157e+308
]
],
"dim": 2,
"p": 1,
"u": [
0.0,
0.0,
1.0,
1.0
]
},
"vu_uv": [
0.0,
0.0
]
},
{
"geom": -1,
"vu_uv": [
0.0,
0.0
]
}
],
"fbe": [
-1,
-1
],
"interval": [
0.0,
1.0
],
"status": 0,
"ve": 2,
"vs": 2
}
],
"faces": [],
"fillet_brep": [],
"geoms": [
{
"gtype": 0,
"other1": [],
"other2": [],
"rails": [
-1,
-1
],
"sides": [],
"status": 0,
"surface": {
"net": [
[
[
-0.05315133026504659,
-0.0011849934172603466,
0.2280847528739151,
-1.7976931348623157e+308
],
[
-0.08450193001373213,
0.25408974756326763,
0.009257819591983796,
-1.7976931348623157e+308
],
[
-0.06166636752505557,
0.5688800027366948,
0.2281787596722859,
-1.7976931348623157e+308
],
[
0.024471475789672195,
1.083924750690753,
0.1733503985616141,
-1.7976931348623157e+308
]
],
[
[
0.31558249305115693,
-0.05679901482231216,
0.07249101149273357,
-1.7976931348623157e+308
],
[
0.4318117366751431,
0.27150781361901855,
-0.00997836656647727,
-1.7976931348623157e+308
],
[
0.41548030876122755,
0.6720047517994926,
-0.2884801267513127,
-1.7976931348623157e+308
],
[
0.413198859747636,
1.0451193977014368,
0.12740853222922804,
-1.7976931348623157e+308
]
],
[
[
0.6272540330293511,
-0.09981948692299303,
-0.08652797893017355,
-1.7976931348623157e+308
],
[
0.6506570547320231,
0.3628506872402346,
0.2563895399422586,
-1.7976931348623157e+308
],
[
0.737126156836055,
0.7347053096123417,
0.07923842023277844,
-1.7976931348623157e+308
],
[
0.766449121717689,
0.9572161815018428,
-0.06535963889916757,
-1.7976931348623157e+308
]
],
[
[
0.9077742350423509,
0.09171699630779803,
-0.0630346375542907,
-1.7976931348623157e+308
],
[
1.0613228522399276,
0.3706537538510186,
0.23725096881811442,
-1.7976931348623157e+308
],
[
1.0283233101634415,
0.7120796238031861,
0.22666595657698724,
-1.7976931348623157e+308
],
[
0.9700723698736866,
0.9664948248646851,
0.08342316451540766,
-1.7976931348623157e+308
]
]
],
"p": 2,
"q": 1,
"u": [
0.0,
0.0,
0.0,
0.9173751911766385,
1.142563820960917,
1.142563820960917,
1.142563820960917
],
"v": [
0.0,
0.0,
0.4257993112498017,
0.48505991853359204,
1.3002122079063376,
1.3002122079063376
]
}
},
{
"gtype": 5,
"other1": [],
"other2": [],
"rails": [
0,
-1
],
"sides": [],
"status": 0,
"surface": {
"net": [
[
[
-0.006777995956634128,
-0.09261158803756141,
0.21149667017484536,
1.1676180118475321
],
[
0.017293275660673513,
0.599192955889427,
-0.08616268552259293,
1.4299905492931642
],
[
0.09543527440630314,
1.01278775073712,
-0.28144022242285116,
1.848658750945778
]
],
[
[
0.2385078220955933,
0.006874937461919625,
0.24010235207375646,
1.9681771235570842
],
[
0.35086619978995814,
0.4792945038826235,
0.14628108121838979,
0.8399608312092537
],
[
0.26241736943901844,
0.9955726046923956,
-0.2387926223194691,
1.260522462242414
]
],
[
[
0.5728936314483886,
-0.028305729515653646,
0.20779334674615374,
1.28654510327396
],
[
0.7247483159035846,
0.4732156343228251,
0.2189512358442412,
0.8849078108093252
],
[
0.7523740216717428,
0.9975245479214893,
0.28758218543535335,
0.8657005369890742
]
],
[
[
0.9643662104271465,
0.09314669594590172,
0.04320049975137924,
1.2492180148262941
],
[
0.9011248210663841,
0.5305264562695966,
-0.2225786729475857,
0.6538081690084028
],
[
0.9776184072662629,
0.9897991433657353,
-0.10999497646741788,
1.390321872381962
]
]
],
"p": 2,
"q": 1,
"u": [
0.0,
0.0,
0.0,
0.4959518711011715,
1.0,
1.0,
1.0
],
"v": [
0.0,
0.0,
0.8696122339057678,
1.0,
1.0
]
}
}
],
"targets": [],
"vertices": [
{
"corner": -1,
"mates": [
-1,
-1
],
"pc_class": 100,
"pc_face": -1,
"pc_index": -1,
"pc_kind": 0,
"pc_sub": 0,
"point": [
0.637544226969595,
-0.23882935976241537,
0.7835894028606132
],
"status": 0,
"t_param": 0.0,
"uv": [
0.0,
0.0
],
"vtype": 13
},
{
"corner": -1,
"mates": [
-1,
-1
],
"pc_class": 100,
"pc_face": -1,
"pc_index": -1,
"pc_kind": 0,
"pc_sub": 0,
"point": [
0.39061885826493725,
0.921001591275159,
0.2650524197813309
],
"status": 2,
"t_param": 0.0,
"uv": [
0.0,
0.0
],
"vtype": 0
},
{
"corner": -1,
"mates": [
-1,
-1
],
"pc_class": 100,
"pc_face": -1,
"pc_index": -1,
"pc_kind": 0,
"pc_sub": 0,
"point": [
-0.45450252074359754,
-0.5724306384541917,
-0.5510607012614746
],
"status": 1,
"t_param": 0.0,
"uv": [
0.0,
0.0
],
"vtype": 8
}
]
},
"query": {
"g": 1,
"q": "blending"
},
"solvers": [
{
"ang": 0.48141437798793024,
"approx": 0.0001,
"tang": 0.026141733633546577
}
]
}
}
]
}
@@ -785,6 +785,40 @@
"ivl": [0.0, 1.0],
"arrays": {"x": [0.3, 1.0, 1.0], "f": [0.0, 0.0], "jac": null}
}
},
{
"name": "fsg_linear_two_line_rails",
"inputs": {
"op": "fsg", "kind": 1, "args": [0.0, 0.0, 0.0, 0.0],
"rails": [
{"dim": 3, "cpts": [[0.0, 0.0, 0.0, -1.7976931348623157e308], [2.0, 0.0, 0.0, -1.7976931348623157e308]], "p": 1, "u": [0.0, 0.0, 1.0, 1.0]},
{"dim": 3, "cpts": [[0.0, 1.0, 0.5, -1.7976931348623157e308], [2.0, 1.0, 0.5, -1.7976931348623157e308]], "p": 1, "u": [0.0, 0.0, 1.0, 1.0]}
]
}
},
{
"name": "fsg_linear_quadratic_and_line_rails",
"inputs": {
"op": "fsg", "kind": 1, "args": [0.0, 0.0, 0.0, 0.0],
"rails": [
{"dim": 3, "cpts": [[0.0, 0.0, 0.0, -1.7976931348623157e308], [1.0, 1.0, 0.0, -1.7976931348623157e308], [2.0, 0.0, 0.0, -1.7976931348623157e308]], "p": 2, "u": [0.0, 0.0, 0.0, 1.0, 1.0, 1.0]},
{"dim": 3, "cpts": [[0.0, 0.0, 1.0, -1.7976931348623157e308], [2.0, 0.0, 1.5, -1.7976931348623157e308]], "p": 1, "u": [0.0, 0.0, 3.0, 3.0]}
]
}
},
{
"name": "fsg_linear_rational_quarter_arc_rails",
"inputs": {
"op": "fsg", "kind": 1, "args": [0.0, 0.0, 0.0, 0.0],
"rails": [
{"dim": 3, "cpts": [[1.0, 0.0, 0.0, 1.0], [0.7071067811865476, 0.7071067811865476, 0.0, 0.7071067811865476], [0.0, 1.0, 0.0, 1.0]], "p": 2, "u": [0.0, 0.0, 0.0, 1.0, 1.0, 1.0]},
{"dim": 3, "cpts": [[2.0, 0.0, 1.0, 1.0], [1.4142135623730951, 1.4142135623730951, 0.7071067811865476, 0.7071067811865476], [0.0, 2.0, 1.0, 1.0]], "p": 2, "u": [0.0, 0.0, 0.0, 1.0, 1.0, 1.0]}
]
}
},
{
"name": "fsg_linear_without_rails",
"inputs": {"op": "fsg", "kind": 1, "args": [0.0, 0.0, 0.0, 0.0]}
}
]
}
@@ -285,6 +285,28 @@ double p3_check_topo(int64_t kind, SmFilletCorner* c, Model& M)
return 0.0;
}
// Whether SmFilletGeom::GetRailVertexuse(r, e) can run for every rail end of g without
// dereferencing NULL (the rail, its primary edge use, the mates it walks to).
bool p3_rail_vus_ok(SmFilletGeom* g)
{
for (ULONG r = 0; r < 2; ++r)
{
SmFilletEdge* rail = g->GetRail(r);
if (rail == NULL)
return false;
SmEdgeuse* eu = rail->GetPrimaryEdgeuse();
if (eu == NULL)
return false;
if (eu->GetOrientation() == SM_OT_OPPOSITE)
eu = eu->GetMate();
if (eu == NULL || eu->GetMate() == NULL)
return false;
if (eu->GetVertexuse() == NULL || eu->GetMate()->GetVertexuse() == NULL)
return false;
}
return true;
}
double p3_check_tangent(int64_t kind, SmFilletCorner* c, Model& M)
{
if (kind == 2)
@@ -305,6 +327,7 @@ double p3_check_tangent(int64_t kind, SmFilletCorner* c, Model& M)
return 0.0;
SmTArray<SmFilletVertex*>& V = c->*P3CornerPeek::verts();
double tol = c->*P3CornerPeek::tol();
bool close_enough = true;
for (ULONG ii = 0; ii < V.GetSize(); ++ii)
{
SmFilletVertex* v = V[ii];
@@ -334,11 +357,63 @@ double p3_check_tangent(int64_t kind, SmFilletCorner* c, Model& M)
SmPoint3d a = p3_eval_oob((SmSurface*)t1.m_apUserPointer[0], t1.UVPos(0));
SmPoint3d b = p3_eval_oob((SmSurface*)t2.m_apUserPointer[0], t2.UVPos(0));
if (a.DistanceBetween(b) > tol)
return P3C_NOT_RUN; // GetRailIndexOfVertex -> GetRailIndexOfVertexuse
{
close_enough = false;
break;
}
a = p3_eval_oob((SmSurface*)t1.m_apUserPointer[1], t1.UVPos(1));
b = p3_eval_oob((SmSurface*)t2.m_apUserPointer[1], t2.UVPos(1));
if (a.DistanceBetween(b) > tol)
return P3C_NOT_RUN;
{
close_enough = false;
break;
}
}
if (close_enough)
return 0.0;
// The split loop, up to SmFilletSolver::SplitFilletGeomAtSideEdge: a vertex's position
// drops onto each of its two geometries' rails; a drop inside the rail splits the
// geometry through pOrigGeom->GetFilletSolver(), which the model's geometries do not
// have (upstream then dereferences NULL). Without a split nothing is removed from the
// list, so every vertex is visited.
for (ULONG ii = 0; ii < V.GetSize(); ++ii)
{
SmFilletVertex* v = V[ii];
if (v->GetFilletVertexType() != SM_FV_RAIL_X_EDGEUSE)
continue;
SmTArray<SmFilletGeom*> gs;
v->GetFilletGeoms(gs);
if (gs.GetSize() != 2)
continue;
SmPoint3d pos = v->GetPoint();
for (ULONG g = 0; g < 2; ++g)
{
// GetRailIndexOfVertex walks every rail end unchecked; its status is ignored
// and a failure leaves lRailIdx uninitialised
if (!p3_rail_vus_ok(gs[g]))
return P3C_REFUSE;
ULONG idx = 0;
if (gs[g]->GetRailIndexOfVertex(v, idx) != SM_SUCCESS)
return P3C_REFUSE;
SmFilletEdge* rail = gs[g]->GetRail(idx);
if (rail->GetCurve() == NULL)
return P3C_REFUSE;
SmExtent1d ivl = rail->GetInterval();
SmBoolean ok = FALSE;
double param = 0.0, dist = 0.0;
// a failed drop leaves bSuccess uninitialised upstream
if (rail->GetCurve()->DropPoint(ivl, pos, NULL, SM_BIG_DOUBLE, NULL, ok, param,
dist, SM_SO_MINIMIZE) != SM_SUCCESS)
return P3C_REFUSE;
if (!ok)
continue;
if (ivl.IsValueOnBoundary(param, tol))
continue;
if (gs[g]->GetFilletSolver() == NULL)
return P3C_REFUSE; // GetFilletSolver()->SplitFilletGeomAtSideEdge
return P3C_NOT_RUN; // SmFilletSolver::SplitFilletGeomAtSideEdge
}
}
return 0.0;
}
@@ -1684,3 +1684,5 @@ SC_EXPORT int32_t sc_blend_fillet_geom_p1_eval(int32_t op, const double* in, int
// The difftest shim of unit blend.fillet_geom_p5 (this file's Model, p4's base solvers).
#include "blend_fillet_geom_p5_static.inc"
// Wrappers of SmFilletGeom.cpp file-statics for the shim of unit blend.fillet_geom_p6.
#include "blend_fillet_geom_p6_static.inc"
+764
View File
@@ -0,0 +1,764 @@
// SPDX-License-Identifier: Apache-2.0
// difftest shim for unit `blend.fillet_geom_p3`: SmFilletVertex::CalcCornerVertGeom,
// SmFilletVertex::CalcMateGeom, SmFilletEdge::CalcCornerEdgeGeom,
// SmFilletEdge::CalcFilletIntFillet and SmFilletGeom::ReCalcFilletGeom of upstream
// source/SMLib/src/SmFilletGeom.cpp (class members, linked from the library).
//
// One entry point, sc_blend_fillet_geom_p3_eval(op, path, in, n_in, out, cap, out_len). Layouts
// are documented in crates/solidcore-ffi/src/units/blend_fillet_geom_p3.rs, which validates the
// blocks; the shim also refuses (SC_ERR_ARG) a model on which the op would dereference an object
// of the wrong kind or NULL inside upstream (checks below), or recurse through a cycle of mates.
//
// Each call builds a target SmBrep from a B-rep block of unit `topology.edgeuse_p1` (objects are
// addressed by their traversal-order export index), fillet solvers (a minimal SmFilletSolver
// subclass: every virtual is the base version, except that SetupOffsetValues may succeed without
// changing anything), and a pseudo SmFilletBrep of fillet vertices, geometries and edges
// (SmFilletEdge::MakeFilletEdge). The export reads raw members, never the functions under test.
#include "sc_shim.h"
#include "StdAfx.h"
#include <SmBSplineCurve.h>
#include <SmBSplineSurface.h>
#include <SmContext.h>
#include <SmExtent1d.h>
#include <SmExtent2d.h>
#include <SmFilletCorner.h>
#include <SmFilletExecutive.h>
#include <SmFilletGeom.h>
#include <SmFilletSolver.h>
#include <SmOffsetSurface.h>
#include <SmSurfaceIntersector.h>
#include <SmTArray.h>
#include <nurbs.h>
#include <cstdint>
#include <new>
#include <set>
#include <vector>
#include "sc_edgeuse_graph.h"
namespace
{
// Ops (keep in sync with crates/solidcore-ffi/src/units/blend_fillet_geom_p3.rs).
enum : int32_t
{
OP_EXPORT = 0,
OP_CORNER_VERT = 1,
OP_MATE = 2,
OP_CORNER_EDGE = 3,
OP_FILLET_INT_FILLET = 4,
OP_RECALC = 5,
};
// A base SmFilletSolver; `setup` makes SetupOffsetValues succeed (the base fails).
struct ShimFS : SmFilletSolver
{
bool setup = false;
ShimFS(const SmContext& ctx, double tol, double ang, double tang) : SmFilletSolver(ctx, tol, ang, tang)
{
m_pEdgeuses[0] = m_pEdgeuses[1] = NULL;
m_pExecutive = NULL;
m_dOrientations[0] = m_dOrientations[1] = 1.0;
m_dSavedOffsets[0] = m_dSavedOffsets[1] = 0.0;
m_bExtendBefore = m_bExtendAfter = FALSE;
}
virtual SmFilletSolverType GetSolverType() { return SM_FS_UNKNOWN; }
virtual void GetName(TCHAR* pc, size_t n)
{
if (n > 0)
pc[0] = 0;
}
virtual SmStatus SetupOffsetValues(const SmTsectPnt& t, double* d = NULL)
{
if (!setup)
return SmFilletSolver::SetupOffsetValues(t, d);
return SM_SUCCESS;
}
void set_edgeuses(SmEdgeuse* a, SmEdgeuse* b)
{
m_pEdgeuses[0] = a;
m_pEdgeuses[1] = b;
}
void set_extend(bool before, bool after)
{
m_bExtendBefore = before ? TRUE : FALSE;
m_bExtendAfter = after ? TRUE : FALSE;
}
};
// A fillet corner that only carries its context (CalcCornerEdgeGeom reads GetContext()).
// The SmFilletCorner constructor reads pExec->m_pPseudoBrep and nothing else of the
// executive, so it gets zeroed storage standing in for one (a NULL pseudo B-rep).
struct ShimCorner : SmFilletCorner
{
ShimCorner(const SmContext& ctx, SmFilletExecutive* exec) : SmFilletCorner(ctx, NULL, NULL, NULL, exec, 1.0e-6, 0.01) {}
virtual SmStatus MakeCornerTopology() { return SM_ERR; }
virtual SmFilletCornerType GetCornerType() const { return SM_FCR_UNKNOWN; }
virtual void GetName(TCHAR* pc, size_t n) const
{
if (n > 0)
pc[0] = 0;
}
};
alignas(SmFilletExecutive) unsigned char g_exec_storage[sizeof(SmFilletExecutive)] = {};
// The executive storage with a constructed error info: a failed fillet x fillet intersection
// records its error through the corner (RecordFilletError -> NoteFilletError ->
// GetFilletErrorInfo()->NoteError, which adds to the info's surface array).
SmFilletExecutive* shim_executive()
{
static bool made = false;
SmFilletExecutive* exec = (SmFilletExecutive*)(void*)g_exec_storage;
if (!made)
{
::new ((void*)exec->GetFilletErrorInfo()) SmFilletErrorInfo(exec);
made = true;
}
return exec;
}
struct GeomPeek : SmFilletGeom
{
static SmTArray<SmFilletEdge*> SmFilletGeom::*other1() { return &GeomPeek::m_vOtherRails1; }
static SmTArray<SmFilletEdge*> SmFilletGeom::*other2() { return &GeomPeek::m_vOtherRails2; }
static SmTArray<SmFilletEdgeuse*> SmFilletGeom::*refs() { return &GeomPeek::m_vRefEUs; }
static SmTArray<SmFilletEdgeuse*> SmFilletGeom::*sides() { return &GeomPeek::m_vSideEUs; }
};
double ix(int64_t i) { return (double)i; }
template <class T> T* at(std::vector<T*>& v, int64_t i)
{
return (i >= 0 && i < (int64_t)v.size()) ? v[(size_t)i] : NULL;
}
template <class T> int64_t index_of(const std::vector<T*>& v, const T* x)
{
for (size_t i = 0; i < v.size(); ++i)
if (v[i] == x)
return (int64_t)i;
return -1;
}
// `has dim n p m cpts knots` (has = 0 for a null or non-B-spline curve).
void put_bcurve(std::vector<double>& o, const SmCurve* c)
{
const SmBSplineCurve* bc =
(c != NULL && c->IsKindOf(SmBSplineCurve_TYPE)) ? (const SmBSplineCurve*)c : NULL;
const gw_CURVE* cur = bc ? bc->GetGwNurbPointer() : NULL;
o.push_back(cur != NULL ? 1.0 : 0.0);
if (!cur)
return;
gw_INDEX n = cur->pol->n;
gw_INDEX m = cur->knt->m;
o.push_back((double)bc->GetDim());
o.push_back((double)n);
o.push_back((double)cur->p);
o.push_back((double)m);
for (gw_INDEX i = 0; i <= n; i++)
{
o.push_back(cur->pol->Pw[i].x);
o.push_back(cur->pol->Pw[i].y);
o.push_back(cur->pol->Pw[i].z);
o.push_back(cur->pol->Pw[i].w);
}
for (gw_INDEX i = 0; i <= m; i++)
o.push_back(cur->knt->U[i]);
}
struct Model
{
const SmContext& ctx;
Graph g;
SmBrep* target = NULL;
SmFilletBrep* pseudo = NULL;
std::vector<ShimFS*> solvers;
std::vector<SmSurface*> bases; // owned here: nobody owns an offset's base surface
std::vector<SmFilletVertex*> verts;
std::vector<SmFilletGeom*> geoms;
std::vector<SmSurface*> geomSurfaces; // owned here: SmFilletGeom does not delete its surface
std::vector<SmFilletEdge*> edges;
std::vector<ShimCorner*> corners; // by tag index, made on first use
explicit Model(const SmContext& c) : ctx(c) {}
~Model()
{
for (size_t i = 0; i < edges.size(); ++i)
delete edges[i];
delete pseudo;
for (size_t i = 0; i < verts.size(); ++i)
{
verts[i]->SetMate(0, NULL);
verts[i]->SetMate(1, NULL);
}
for (size_t i = 0; i < verts.size(); ++i)
delete verts[i];
for (size_t i = 0; i < geoms.size(); ++i)
delete geoms[i];
for (size_t i = 0; i < geomSurfaces.size(); ++i)
delete geomSurfaces[i];
for (size_t i = 0; i < solvers.size(); ++i)
delete solvers[i];
for (size_t i = 0; i < bases.size(); ++i)
delete bases[i];
for (size_t i = 0; i < corners.size(); ++i)
delete corners[i];
delete target;
}
SmFilletCorner* corner_tag(int64_t i)
{
if (i < 0)
return NULL;
while ((int64_t)corners.size() <= i)
corners.push_back(NULL);
if (corners[(size_t)i] == NULL)
corners[(size_t)i] = new (ctx) ShimCorner(ctx, shim_executive());
return corners[(size_t)i];
}
static SmFilletEdgeuse* eu_of(SmFilletEdge* e, int64_t which)
{
if (e == NULL)
return NULL;
SmEdgeuse* eu = e->GetPrimaryEdgeuse();
if (eu != NULL && which == 1)
eu = eu->GetMate();
return (SmFilletEdgeuse*)eu;
}
SmObject* pc_object(int64_t kind, int64_t index)
{
switch (kind)
{
case 1: return at(g.vertices, index);
case 2: return at(g.edgeuses, index);
case 3: return at(g.faces, index);
default: return NULL;
}
}
void read_tsect(Rd& rd, SmTsectPnt& t)
{
t.m_ePointType = (SmIntersectionPointType)rd.idx();
t.UVPos(0) = rd.uv();
t.UVPos(1) = rd.uv();
t.CrvPos() = rd.vec();
t.CrvDeriv() = rd.vec();
t.m_dCurveParameter = rd.num();
}
// Reads the model after the B-rep block (layout in the Rust wrapper).
bool build(Rd& rd)
{
pseudo = new (ctx) SmFilletBrep();
int64_t ns = rd.idx();
for (int64_t i = 0; i < ns; ++i)
{
double tol = rd.num(), ang = rd.num(), tang = rd.num();
ShimFS* fs = new (ctx) ShimFS(ctx, tol, ang, tang);
solvers.push_back(fs);
SmEdgeuse* e0 = at(g.edgeuses, rd.idx());
SmEdgeuse* e1 = at(g.edgeuses, rd.idx());
fs->set_edgeuses(e0, e1);
for (ULONG k = 0; k < 2; ++k)
{
if (rd.flag())
{
SmSurface* base = read_surface(rd, ctx);
bases.push_back(base);
double dist = rd.num();
fs->SetSurface(k, new (ctx) SmOffsetSurface(dist, *base));
}
}
bool before = rd.flag(), after = rd.flag();
fs->set_extend(before, after);
fs->setup = rd.flag();
}
int64_t nv = rd.idx();
for (int64_t i = 0; i < nv; ++i)
{
SmPoint3d p = rd.vec();
int64_t vtype = rd.idx();
int64_t status = rd.idx();
int64_t corner = rd.idx();
int64_t pc_class = rd.idx();
int64_t pc_kind = rd.idx();
int64_t pc_index = rd.idx();
int64_t pc_face = rd.idx();
double t = rd.num();
SmPoint2d uv = rd.uv();
SmFilletVertex* fv = new (pseudo) SmFilletVertex(corner_tag(corner));
fv->SetPoint(p);
fv->SetFilletVertexType((SmFilletVertexType)vtype);
fv->SetStatus((SmFilStatus)status);
fv->SetPointClass((SmPointClassificationType)pc_class, pc_object(pc_kind, pc_index));
if (pc_face >= 0)
fv->GetPointClassification().SetFace(at(g.faces, pc_face));
fv->SetOriginalTParam(t);
fv->SetOriginalUV(uv);
verts.push_back(fv);
}
int64_t ng = rd.idx();
for (int64_t i = 0; i < ng; ++i)
{
int64_t gtype = rd.idx();
int64_t status = rd.idx();
int64_t solver = rd.idx();
SmFilletGeom* geom = new SmFilletGeom(at(solvers, solver), (SmFilletGeomType)gtype);
geom->SetStatus((SmFilStatus)status);
if (rd.flag())
{
SmSurface* s = read_surface(rd, ctx);
geomSurfaces.push_back(s);
geom->SetFilletSurface(s);
}
geoms.push_back(geom);
}
int64_t ne = rd.idx();
for (int64_t i = 0; i < ne; ++i)
{
SmFilletVertex* vs = at(verts, rd.idx());
SmFilletVertex* ve = at(verts, rd.idx());
int64_t etype = rd.idx();
int64_t status = rd.idx();
int64_t corner = rd.idx();
if (vs == NULL || ve == NULL)
return false;
SmFilletEdge* e = NULL;
if (SmFilletEdge::MakeFilletEdge(pseudo, vs, ve, e, corner_tag(corner)) != SM_SUCCESS || e == NULL)
return false;
edges.push_back(e);
e->SetFilletEdgeType((SmFilletEdgeType)etype);
e->SetStatus((SmFilStatus)status);
if (rd.flag())
e->SetCurve(read_curve(rd, ctx), TRUE);
if (rd.flag())
{
double t0 = rd.num();
double t1 = rd.num();
e->SetInterval(SmExtent1d(t0, t1));
}
for (int64_t which = 0; which < 2; ++which)
{
SmFilletEdgeuse* eu = eu_of(e, which);
if (eu == NULL)
return false;
if (rd.flag())
eu->SetUVCurve(read_curve(rd, ctx));
SmFilletGeom* pg = at(geoms, rd.idx());
if (pg != NULL)
eu->SetFilletGeom(pg);
SmFilletVertexuse* vu = (SmFilletVertexuse*)eu->GetVertexuse();
SmPoint2d uv = rd.uv();
vu->SetUVPoint(uv);
read_tsect(rd, vu->GetTsectPnt());
}
}
for (int64_t i = 0; i < ng; ++i)
{
SmFilletGeom* geom = geoms[(size_t)i];
for (ULONG r = 0; r < 2; ++r)
geom->SetRail(r, at(edges, rd.idx()));
int64_t n1 = rd.idx();
for (int64_t k = 0; k < n1; ++k)
(geom->*GeomPeek::other1()).Add(at(edges, rd.idx()));
int64_t n2 = rd.idx();
for (int64_t k = 0; k < n2; ++k)
(geom->*GeomPeek::other2()).Add(at(edges, rd.idx()));
int64_t nsd = rd.idx();
for (int64_t k = 0; k < nsd; ++k)
{
int64_t e = rd.idx();
int64_t which = rd.idx();
SmFilletEdgeuse* eu = eu_of(at(edges, e), which);
if (eu == NULL)
return false;
geom->AddSideFilletEdgeuse(eu);
}
}
for (int64_t i = 0; i < nv; ++i)
for (ULONG m = 0; m < 2; ++m)
verts[(size_t)i]->SetMate(m, at(verts, rd.idx()));
return true;
}
// ---- refusal checks: inputs on which upstream reads through NULL or a blind cast ----
// FindIndexOfRailXSideEdgeuse(side) of `fs` dereferences: the side edge use's edge, edge
// use 0, on a closed side edge both vertex uses and (vertices differing) edge use 0's
// counter-clockwise neighbour.
static bool rail_x_side_safe(SmFilletSolver* fs, SmEdgeuse* side)
{
SmEdgeuse* eu0 = fs->GetEdgeuse(0);
if (side == NULL || side->GetEdge() == NULL || eu0 == NULL)
return false;
if (side->GetEdge()->IsClosed())
{
if (eu0->GetVertexuse() == NULL || side->GetVertexuse() == NULL)
return false;
if (side->GetVertexuse()->GetVertex() != eu0->GetVertexuse()->GetVertex() &&
eu0->GetCCWEdgeuse() == NULL)
return false;
}
return true;
}
// The vertex calculator of an unported unit (part 5): it reads corner and executive data
// the shim does not build.
static bool other_unit_vertex(SmFilletVertexType t) { return t == SM_FV_FILLET_X_FILLET; }
// The vertex calculators of parts 2 and 4 (ported, called by the dispatcher). Each asks
// the vertex's corner for its filleted vertex, which the shim's corners do not have, so
// they run only on vertices without a corner, as in the shims of those units.
static bool ported_unit_vertex(SmFilletVertexType t)
{
switch (t)
{
case SM_FV_RAIL_X_RAIL:
case SM_FV_RAIL_X_EDGEUSE:
case SM_FV_RAIL_X_EXTENDED_EDGEUSE:
case SM_FV_FILLET_X2_FILLETS:
case SM_FV_ON_CROSS_SECTION:
case SM_FV_SETBACK: return true;
default: return false;
}
}
// The edge calculators of unported units (parts 5, 6), likewise. CalcRailRailInterpolation
// (part 2) runs: the shim's corners have no fillet edges, so its corner branch returns
// SM_ERR before it reads anything else of them.
static bool other_unit_edge(SmFilletEdgeType t)
{
switch (t)
{
case SM_FE_CROSS_SECTION:
case SM_FE_FILLET_X_SIDE_FACE:
case SM_FE_ON_EDGE:
case SM_FE_ON_EXTEND_EDGE:
case SM_FE_FILLET_END:
case SM_FE_SETBACK_RAIL:
case SM_FE_RAIL_EXTENSION: return true;
default: return false;
}
}
// CalcCornerVertGeom(v) / CalcMateGeom(v) on the vertex types this unit handles.
bool vertex_safe(SmFilletVertex* v, bool as_mate, std::set<SmFilletVertex*>& seen)
{
if (!as_mate && v->IsProcessed())
return true;
SmFilletVertexType t = v->GetFilletVertexType();
if (!as_mate && t == SM_FV_ON_VERTEX)
{
// CalcVertOnVert dereferences the classification object as an SmVertex
SmObject* o = v->GetPointClassObject();
return o != NULL && o->IsKindOf(SmVertex_TYPE);
}
if (!as_mate && ported_unit_vertex(t))
return v->GetFilletCorner() == NULL;
if (!as_mate && t != SM_FV_MATE)
return !other_unit_vertex(t);
if (seen.count(v) != 0)
return false; // a cycle of mates recurses without end
seen.insert(v);
SmFilletVertex* main = v->GetMate(0);
if (main == NULL)
return true;
if (!main->IsProcessed() && !vertex_safe(main, false, seen))
return false;
// the main vertex's object is cast to an SmEdgeuse, ours to an SmFace
SmObject* side = main->GetPointClassObject();
if (side != NULL && !side->IsKindOf(SmEdgeuse_TYPE))
return false;
SmObject* face = v->GetPointClassObject();
if (face != NULL && !face->IsKindOf(SmFace_TYPE))
return false;
SmTArray<SmFilletGeom*> fgs;
v->GetFilletGeoms(fgs);
if (fgs.GetSize() < 1 || side == NULL)
return true;
SmFilletSolver* fs = fgs[0]->GetFilletSolver();
if (fs == NULL)
return true;
if (!rail_x_side_safe(fs, (SmEdgeuse*)side))
return false;
// the main vertex's rail-end vertex use is dereferenced
return main->GetVUAtRailEnd(fgs[0]) != NULL;
}
// CalcFilletIntFillet(e): the solvers of both edge uses' geometries are dereferenced, on
// the trimming path both UV trim curves, and without a 3D curve the edge's corner.
bool fif_safe(SmFilletEdge* e)
{
SmFilletEdgeuse* p = eu_of(e, 0);
SmFilletEdgeuse* m = eu_of(e, 1);
if (p == NULL || m == NULL)
return false;
SmFilletGeom* g1 = p->GetFilletGeom();
SmFilletGeom* g2 = m->GetFilletGeom();
if (g1 == NULL || g2 == NULL)
return true;
SmFilletVertex* vs = (SmFilletVertex*)e->GetVertex();
SmFilletVertex* ve = (SmFilletVertex*)e->GetOtherVertex(vs);
if (vs == NULL || ve == NULL)
return false;
if (!vs->IsProcessed() || !ve->IsProcessed())
return true;
if (g1->GetFilletSolver() == NULL || g2->GetFilletSolver() == NULL)
return false;
if (g1->GetFilletSurface() == NULL || g2->GetFilletSurface() == NULL)
return true;
// without a B-spline 3D curve the surface/surface intersection is called with
// m_cpCorner->GetContext() (UB-210)
if (e->GetCurve() == NULL || !e->GetCurve()->IsKindOf(SmBSplineCurve_TYPE))
return e->GetFilletCorner() != NULL;
return p->GetUVTrimCurvePointer() != NULL && m->GetUVTrimCurvePointer() != NULL;
}
// ReCalcFilletGeom without a march direction, when rail 0 has no curve, reads
// sTsectPnts[0]->CrvDeriv() unchecked (SmFilletGeom.cpp:11648). True when its sort loop
// (:11431-11507) runs to the end without sorting a rail-end point: every vertex there is
// unprocessed, a mate or an extended-edge-use end, or its tsect point is unset
// (-SM_BIG_DOUBLE). A loop that returns an error first (NER, a rail without two edge uses)
// never gets there, and a point that reaches the curve solve is either sorted or an error.
static bool recalc_sorts_nothing(SmFilletGeom* geom, SmFilletSolver* fs)
{
SmFilletGeomType t = geom->GetFilletGeomType();
bool b3d = t == SM_FG_CORNER_FILLET || t == SM_FG_TANGENT_ROLLOVER;
for (ULONG jj = 0; jj < 2; ++jj)
{
if (!b3d && (fs->GetEdgeuse(jj) == NULL || fs->GetEdgeuse(jj)->GetUVTrimCurve() == NULL))
return false;
SmTArray<SmFilletEdge*> rails;
rails.Add(geom->GetRail(jj));
rails.Append(jj == 0 ? geom->*GeomPeek::other1() : geom->*GeomPeek::other2());
for (ULONG r = 0; r < rails.GetSize(); ++r)
{
if (rails[r] == NULL)
return false;
SmTArray<SmEdgeuse*> eus;
rails[r]->GetEdgeuses(eus);
if (eus.GetSize() != 2)
return false;
for (ULONG k = 0; k < 2; ++k)
{
SmFilletVertexuse* vu = (SmFilletVertexuse*)eus[k]->GetVertexuse();
SmFilletVertex* fv = vu == NULL ? NULL : (SmFilletVertex*)vu->GetVertex();
if (fv == NULL)
return false;
if (fv->GetStatus() == SM_FIL_UNPROCESSED || fv->GetFilletVertexType() == SM_FV_MATE ||
fv->GetFilletVertexType() == SM_FV_RAIL_X_EXTENDED_EDGEUSE)
continue;
SmTsectPnt& tp = vu->GetTsectPnt();
if (!b3d)
{
SmPoint2d uv = tp.UVPos(jj);
if (uv.x == -SM_BIG_DOUBLE || uv.y == -SM_BIG_DOUBLE)
continue;
return false;
}
if (tp.m_ePointType == SM_IP_UNKNOWN || tp.CrvPos().IsUndef())
{
SmPoint2d uv = tp.UVPos(0);
if (uv.x == -SM_BIG_DOUBLE || uv.y == -SM_BIG_DOUBLE)
continue;
}
return false;
}
}
}
return true;
}
// ---- export (layout in the Rust wrapper) ----
void put_eu(std::vector<double>& o, const SmEdgeuse* eu) const
{
for (size_t i = 0; i < edges.size(); ++i)
{
const SmEdgeuse* p = edges[i]->GetPrimaryEdgeuse();
if (p != NULL && p == eu)
{
o.push_back(ix((int64_t)i));
o.push_back(0.0);
return;
}
if (p != NULL && p->GetMate() == eu)
{
o.push_back(ix((int64_t)i));
o.push_back(1.0);
return;
}
}
o.push_back(-1.0);
o.push_back(-1.0);
}
void put(std::vector<double>& o) const
{
o.push_back(ix((int64_t)verts.size()));
for (SmFilletVertex* v : verts)
{
SmPoint3d p = v->GetPoint();
o.push_back(p.x);
o.push_back(p.y);
o.push_back(p.z);
o.push_back((double)v->GetFilletVertexType());
o.push_back((double)v->GetStatus());
SmPoint2d uv = v->GetOriginalUV();
o.push_back(uv.x);
o.push_back(uv.y);
}
o.push_back(ix((int64_t)edges.size()));
for (SmFilletEdge* e : edges)
{
o.push_back((double)e->GetFilletEdgeType());
o.push_back((double)e->GetStatus());
put_bcurve(o, e->GetCurve());
SmExtent1d ivl = e->GetInterval();
o.push_back(ivl.GetMin());
o.push_back(ivl.GetMax());
for (int64_t which = 0; which < 2; ++which)
{
SmFilletEdgeuse* eu = eu_of(e, which);
put_bcurve(o, eu->GetUVTrimCurvePointer());
o.push_back(ix(index_of(geoms, eu->GetFilletGeom())));
SmFilletVertexuse* vu = (SmFilletVertexuse*)eu->GetVertexuse();
SmTsectPnt& t = vu->GetTsectPnt();
o.push_back((double)t.m_ePointType);
o.push_back(t.UVPos(0).x);
o.push_back(t.UVPos(0).y);
o.push_back(t.UVPos(1).x);
o.push_back(t.UVPos(1).y);
o.push_back(t.CrvPos().x);
o.push_back(t.CrvPos().y);
o.push_back(t.CrvPos().z);
o.push_back(t.m_dCurveParameter);
}
}
o.push_back(ix((int64_t)geoms.size()));
for (SmFilletGeom* geom : geoms)
{
o.push_back((double)geom->GetStatus());
const SmTArray<SmFilletEdgeuse*>& sides = geom->*GeomPeek::sides();
o.push_back((double)sides.GetSize());
for (ULONG k = 0; k < sides.GetSize(); ++k)
put_eu(o, sides[k]);
const SmTArray<SmFilletEdgeuse*>& refs = geom->*GeomPeek::refs();
o.push_back((double)refs.GetSize());
for (ULONG k = 0; k < refs.GetSize(); ++k)
put_eu(o, refs[k]);
}
}
};
int32_t run(int32_t op, Rd& rd, const char* path, std::vector<double>& o)
{
const SmContext& ctx = the_context();
Model m(ctx);
SmStatus bst = SM_SUCCESS;
m.target = build(rd, path, bst);
if (m.target == NULL)
{
o.push_back(1.0); // the target B-rep cannot be built
o.push_back(st(bst));
return SC_OK;
}
walk(m.target, m.g);
if (!m.build(rd))
return SC_ERR_ARG;
o.push_back(0.0);
SmStatus s = SM_SUCCESS;
switch (op)
{
case OP_EXPORT: break;
case OP_CORNER_VERT:
case OP_MATE:
{
SmFilletVertex* v = at(m.verts, rd.idx());
if (v == NULL)
return SC_ERR_ARG;
std::set<SmFilletVertex*> seen;
if (!m.vertex_safe(v, op == OP_MATE, seen))
return SC_ERR_ARG;
s = op == OP_MATE ? v->CalcMateGeom() : v->CalcCornerVertGeom();
break;
}
case OP_CORNER_EDGE:
case OP_FILLET_INT_FILLET:
{
SmFilletEdge* e = at(m.edges, rd.idx());
if (e == NULL)
return SC_ERR_ARG;
if (op == OP_CORNER_EDGE && Model::other_unit_edge(e->GetFilletEdgeType()))
return SC_ERR_ARG;
bool fif = op == OP_FILLET_INT_FILLET || e->GetFilletEdgeType() == SM_FE_FILLET_X_FILLET;
if (fif && !m.fif_safe(e))
return SC_ERR_ARG;
if (op == OP_FILLET_INT_FILLET)
s = e->CalcFilletIntFillet(ctx, e->GetFilletCorner());
else
s = e->CalcCornerEdgeGeom();
break;
}
case OP_RECALC:
{
SmFilletGeom* geom = at(m.geoms, rd.idx());
bool analytic = rd.flag();
bool has_dir = rd.flag();
SmVector3d dir = rd.vec();
SmBSplineCurve* ref = NULL;
if (rd.flag())
ref = read_curve(rd, ctx);
// upstream dereferences the solver, its offset surfaces and (but for a corner
// fillet) its first edge use; the analytic fillets (CalcConeFilletGeom, unit
// blend.fillet_solver_p2) read solver data this shim does not build; without a
// march direction it may read the first of no sorted points (UB-208)
SmFilletSolver* fs = geom == NULL ? NULL : geom->GetFilletSolver();
if (fs == NULL || fs->GetSurface(0) == NULL || fs->GetSurface(1) == NULL ||
(geom->GetFilletGeomType() != SM_FG_CORNER_FILLET && fs->GetEdgeuse(0) == NULL) ||
(analytic && geom->GetFilletGeomType() != SM_FG_TANGENT_ROLLOVER) ||
(!has_dir && geom->GetRail(0) != NULL && geom->GetRail(0)->GetCurve() == NULL &&
Model::recalc_sorts_nothing(geom, fs)))
{
delete ref;
return SC_ERR_ARG;
}
s = geom->ReCalcFilletGeom(analytic ? TRUE : FALSE, has_dir ? &dir : NULL, ref);
delete ref;
break;
}
default: return SC_ERR_ARG;
}
o.push_back(st(s));
m.put(o);
return SC_OK;
}
} // namespace
SC_EXPORT int32_t sc_blend_fillet_geom_p3_eval(int32_t op, const char* path, const double* in, int64_t n_in,
double* out, int64_t cap, int64_t* out_len)
{
if (in == NULL || out_len == NULL || path == NULL)
return SC_ERR_ARG;
SC_TRY
Rd rd{in, 0};
std::vector<double> o;
int32_t r = run(op, rd, path, o);
if (r != SC_OK)
return r;
(void)n_in;
return sc_copy_out(o.data(), (int64_t)o.size(), out, cap, out_len);
SC_CATCH
}
+992
View File
@@ -0,0 +1,992 @@
// SPDX-License-Identifier: Apache-2.0
// difftest shim for unit `blend.fillet_geom_p6`: SmFilletEdge::CalcBlendingRail,
// CalcEdgeOnEdge, CalcFilletEnd, CalcSetBackRail, ShouldSwapFaces, SmFilletGeom::Copy,
// FindEdgeTangentAtPoint, GetRailIndexOfSideEU, GetRailIndexOfVertexuse, GetRailVertex,
// SetUpRailPoint, RailEdgeuseIntersect, RecordFilletError and the file-statics
// JoinTwoFilletVertsByCurves and sm_MoveTSP of upstream source/SMLib/src/SmFilletGeom.cpp.
//
// The class members are linked from the translation unit of blend_fillet_geom_p1.cpp, which
// compiles SmFilletGeom.cpp itself; the two file-statics are reached through the wrappers that
// file includes from blend_fillet_geom_p6_static.inc (declared in blend_fillet_geom_p6.h).
//
// One entry point, sc_blend_fillet_geom_p6_eval(op, path, in, n_in, out, cap, out_len). Layouts
// are documented in crates/solidcore-ffi/src/units/blend_fillet_geom_p6.rs, which validates the
// blocks; the shim also refuses (SC_ERR_ARG) a model on which the op would dereference NULL or
// an object of the wrong kind inside upstream (checks below).
//
// Each call builds a target SmBrep from a B-rep block of unit `topology.edgeuse_p1` (objects are
// addressed by their traversal-order export index), a fillet executive on it (corners and
// solvers point at it), fillet solvers (a minimal SmFilletSolver subclass: every virtual is the
// base version), fillet corners (a minimal SmFilletCorner subclass: the filleted vertex, the
// tolerance and a constant corner type), and a pseudo SmFilletBrep of fillet vertices,
// geometries and edges (SmFilletEdge::MakeFilletEdge). The export reads raw members, never the
// functions under test.
#include "sc_shim.h"
#include "StdAfx.h"
#include <SmBSplineCurve.h>
#include <SmBSplineSurface.h>
#include <SmContext.h>
#include <SmExtent1d.h>
#include <SmExtent2d.h>
#include <SmFilletCorner.h>
#include <SmFilletExecutive.h>
#include <SmFilletGeom.h>
#include <SmFilletSolver.h>
#include <SmOffsetSurface.h>
#include <SmPointClass.h>
#include <SmSurfaceIntersector.h>
#include <SmTArray.h>
#include <SmTol.h>
#include <nurbs.h>
#include <cstdint>
#include <set>
#include <vector>
#include "blend_fillet_geom_p6.h"
#include "sc_edgeuse_graph.h"
namespace
{
// Ops (keep in sync with crates/solidcore-ffi/src/units/blend_fillet_geom_p6.rs).
enum : int32_t
{
OP_EXPORT = 0,
OP_BLENDING_RAIL = 1,
OP_EDGE_ON_EDGE = 2,
OP_FILLET_END = 3,
OP_SETBACK_RAIL = 4,
OP_SHOULD_SWAP = 5,
OP_JOIN = 6,
OP_COPY = 7,
OP_FIND_TANGENT = 8,
OP_RAIL_INDEX_SIDE_EU = 9,
OP_RAIL_INDEX_VU = 10,
OP_RAIL_VERTEX = 11,
OP_SETUP_RAIL_POINT = 12,
OP_MOVE_TSP = 13,
OP_RAIL_EU_INTERSECT = 14,
OP_RECORD_ERROR = 15,
};
// A base SmFilletSolver.
struct ShimFS : SmFilletSolver
{
ShimFS(const SmContext& ctx, double tol, double ang, double tang) : SmFilletSolver(ctx, tol, ang, tang)
{
m_pEdgeuses[0] = m_pEdgeuses[1] = NULL;
m_pExecutive = NULL;
m_dOrientations[0] = m_dOrientations[1] = 1.0;
m_dSavedOffsets[0] = m_dSavedOffsets[1] = 0.0;
m_bExtendBefore = m_bExtendAfter = FALSE;
}
virtual SmFilletSolverType GetSolverType() { return SM_FS_UNKNOWN; }
virtual void GetName(TCHAR* pc, size_t n)
{
if (n > 0)
pc[0] = 0;
}
void set_edgeuses(SmEdgeuse* a, SmEdgeuse* b)
{
m_pEdgeuses[0] = a;
m_pEdgeuses[1] = b;
}
void set_executive(SmFilletExecutive* e) { m_pExecutive = e; }
void set_reverse_trim(bool r) { m_bReverseTrim = r ? TRUE : FALSE; }
};
// A fillet corner: the filleted vertex, the tolerance and a constant corner type.
struct ShimCorner : SmFilletCorner
{
SmFilletCornerType type;
ShimCorner(const SmContext& ctx, const SmVertex* v, SmFilletExecutive* exec, double tol, SmFilletCornerType t)
: SmFilletCorner(ctx, v, NULL, NULL, exec, tol, 0.01), type(t)
{
}
virtual SmStatus MakeCornerTopology() { return SM_ERR; }
virtual SmFilletCornerType GetCornerType() const { return type; }
virtual void GetName(TCHAR* pc, size_t n) const
{
if (n > 0)
pc[0] = 0;
}
};
struct GeomPeek : SmFilletGeom
{
static SmTArray<SmFilletEdge*> SmFilletGeom::*other1() { return &GeomPeek::m_vOtherRails1; }
static SmTArray<SmFilletEdge*> SmFilletGeom::*other2() { return &GeomPeek::m_vOtherRails2; }
static SmTArray<SmFilletEdgeuse*> SmFilletGeom::*refs() { return &GeomPeek::m_vRefEUs; }
static SmTArray<SmFilletEdgeuse*> SmFilletGeom::*sides() { return &GeomPeek::m_vSideEUs; }
static double (SmFilletGeom::*devs())[2] { return &GeomPeek::m_dDeviations; }
static SmOffsetSurface* (SmFilletGeom::*offs())[2] { return &GeomPeek::m_pOffSurfs; }
static SmSurface* SmFilletGeom::*surface() { return &GeomPeek::m_pFilletSurface; }
};
double ix(int64_t i) { return (double)i; }
template <class T> T* at(std::vector<T*>& v, int64_t i)
{
return (i >= 0 && i < (int64_t)v.size()) ? v[(size_t)i] : NULL;
}
template <class T> int64_t index_of(const std::vector<T*>& v, const T* x)
{
for (size_t i = 0; i < v.size(); ++i)
if (v[i] == x)
return (int64_t)i;
return -1;
}
// `has dim n p m cpts knots` (has = 0 for a null or non-B-spline curve).
void put_bcurve(std::vector<double>& o, const SmCurve* c)
{
const SmBSplineCurve* bc =
(c != NULL && c->IsKindOf(SmBSplineCurve_TYPE)) ? (const SmBSplineCurve*)c : NULL;
const gw_CURVE* cur = bc ? bc->GetGwNurbPointer() : NULL;
o.push_back(cur != NULL ? 1.0 : 0.0);
if (!cur)
return;
gw_INDEX n = cur->pol->n;
gw_INDEX m = cur->knt->m;
o.push_back((double)bc->GetDim());
o.push_back((double)n);
o.push_back((double)cur->p);
o.push_back((double)m);
for (gw_INDEX i = 0; i <= n; i++)
{
o.push_back(cur->pol->Pw[i].x);
o.push_back(cur->pol->Pw[i].y);
o.push_back(cur->pol->Pw[i].z);
o.push_back(cur->pol->Pw[i].w);
}
for (gw_INDEX i = 0; i <= m; i++)
o.push_back(cur->knt->U[i]);
}
// `has n m p q r s cpts u v` (has = 0 for a null surface or one that is not an
// SmBSplineSurface or an SmSurfOfRevolution).
void put_bsurface(std::vector<double>& o, const SmSurface* s)
{
const SmBSplineSurface* b =
(s != NULL && s->IsKindOf(SmBSplineSurface_TYPE) && s->GetType() != SmPlane_TYPE)
? (const SmBSplineSurface*)s
: NULL;
gw_SURFACE* sur = b ? b->GetGwNurbPointer() : NULL;
o.push_back(sur != NULL ? 1.0 : 0.0);
if (!sur)
return;
o.push_back((double)sur->net->n);
o.push_back((double)sur->net->m);
o.push_back((double)sur->p);
o.push_back((double)sur->q);
o.push_back((double)sur->knu->m);
o.push_back((double)sur->knv->m);
for (gw_INDEX i = 0; i <= sur->net->n; i++)
for (gw_INDEX j = 0; j <= sur->net->m; j++)
{
o.push_back(sur->net->Pw[i][j].x);
o.push_back(sur->net->Pw[i][j].y);
o.push_back(sur->net->Pw[i][j].z);
o.push_back(sur->net->Pw[i][j].w);
}
for (gw_INDEX i = 0; i <= sur->knu->m; i++)
o.push_back(sur->knu->U[i]);
for (gw_INDEX i = 0; i <= sur->knv->m; i++)
o.push_back(sur->knv->U[i]);
}
struct Model
{
const SmContext& ctx;
Graph g;
SmBrep* target = NULL;
SmFilletExecutive* exec = NULL;
SmFilletBrep* pseudo = NULL;
std::vector<ShimFS*> solvers;
std::vector<SmSurface*> bases; // owned here: nobody owns an offset's base surface
std::vector<ShimCorner*> corners;
std::vector<SmFilletVertex*> verts;
std::vector<SmFilletGeom*> geoms;
std::vector<SmSurface*> geomSurfaces; // owned here: SmFilletGeom does not delete its surface
std::vector<SmFilletEdge*> edges;
explicit Model(const SmContext& c) : ctx(c) {}
~Model()
{
for (size_t i = 0; i < edges.size(); ++i)
delete edges[i];
delete pseudo;
for (size_t i = 0; i < verts.size(); ++i)
{
verts[i]->SetMate(0, NULL);
verts[i]->SetMate(1, NULL);
}
for (size_t i = 0; i < verts.size(); ++i)
delete verts[i];
// fillet surfaces: the ones made here and copies made by SmFilletGeom::Copy
std::set<SmSurface*> surfs(geomSurfaces.begin(), geomSurfaces.end());
for (size_t i = 0; i < geoms.size(); ++i)
if (geoms[i]->*GeomPeek::surface() != NULL)
surfs.insert(geoms[i]->*GeomPeek::surface());
for (size_t i = 0; i < geoms.size(); ++i)
delete geoms[i];
for (SmSurface* s : surfs)
delete s;
for (size_t i = 0; i < solvers.size(); ++i)
delete solvers[i];
for (size_t i = 0; i < bases.size(); ++i)
delete bases[i];
for (size_t i = 0; i < corners.size(); ++i)
delete corners[i];
delete exec;
delete target;
}
static SmFilletEdgeuse* eu_of(SmFilletEdge* e, int64_t which)
{
if (e == NULL)
return NULL;
SmEdgeuse* eu = e->GetPrimaryEdgeuse();
if (eu != NULL && which == 1)
eu = eu->GetMate();
return (SmFilletEdgeuse*)eu;
}
SmObject* pc_object(int64_t kind, int64_t index)
{
switch (kind)
{
case 1: return at(g.vertices, index);
case 2: return at(g.edgeuses, index);
case 3: return at(g.faces, index);
case 4: return at(g.edges, index);
default: return NULL;
}
}
void read_tsect(Rd& rd, SmTsectPnt& t)
{
t.m_ePointType = (SmIntersectionPointType)rd.idx();
t.UVPos(0) = rd.uv();
t.UVPos(1) = rd.uv();
t.CrvPos() = rd.vec();
t.CrvDeriv() = rd.vec();
t.m_dCurveParameter = rd.num();
}
// Reads the model after the B-rep block (layout in the Rust wrapper).
bool build(Rd& rd)
{
exec = new (ctx) SmFilletExecutive(ctx, target);
pseudo = new (ctx) SmFilletBrep();
int64_t ns = rd.idx();
for (int64_t i = 0; i < ns; ++i)
{
double tol = rd.num(), ang = rd.num(), tang = rd.num();
ShimFS* fs = new (ctx) ShimFS(ctx, tol, ang, tang);
solvers.push_back(fs);
SmEdgeuse* e0 = at(g.edgeuses, rd.idx());
SmEdgeuse* e1 = at(g.edgeuses, rd.idx());
fs->set_edgeuses(e0, e1);
for (ULONG k = 0; k < 2; ++k)
{
if (rd.flag())
{
SmSurface* base = read_surface(rd, ctx);
bases.push_back(base);
double dist = rd.num();
fs->SetSurface(k, new (ctx) SmOffsetSurface(dist, *base));
}
}
if (rd.flag())
fs->set_executive(exec);
fs->set_reverse_trim(rd.flag());
}
int64_t nc = rd.idx();
for (int64_t i = 0; i < nc; ++i)
{
SmVertex* v = at(g.vertices, rd.idx());
int64_t type = rd.idx();
double tol = rd.num();
corners.push_back(new (ctx) ShimCorner(ctx, v, exec, tol, (SmFilletCornerType)type));
}
int64_t nv = rd.idx();
for (int64_t i = 0; i < nv; ++i)
{
SmPoint3d p = rd.vec();
int64_t vtype = rd.idx();
int64_t status = rd.idx();
int64_t corner = rd.idx();
int64_t pc_class = rd.idx();
int64_t pc_kind = rd.idx();
int64_t pc_index = rd.idx();
int64_t pc_face = rd.idx();
double t = rd.num();
SmPoint2d uv = rd.uv();
SmFilletVertex* fv = new (pseudo) SmFilletVertex(at(corners, corner));
fv->SetPoint(p);
fv->SetFilletVertexType((SmFilletVertexType)vtype);
fv->SetStatus((SmFilStatus)status);
fv->SetPointClass((SmPointClassificationType)pc_class, pc_object(pc_kind, pc_index));
if (pc_face >= 0)
fv->GetPointClassification().SetFace(at(g.faces, pc_face));
fv->SetOriginalTParam(t);
fv->SetOriginalUV(uv);
verts.push_back(fv);
}
int64_t ng = rd.idx();
for (int64_t i = 0; i < ng; ++i)
{
int64_t gtype = rd.idx();
int64_t status = rd.idx();
int64_t solver = rd.idx();
SmFilletGeom* geom = new SmFilletGeom(at(solvers, solver), (SmFilletGeomType)gtype);
geom->SetStatus((SmFilStatus)status);
if (rd.flag())
{
SmSurface* s = read_surface(rd, ctx);
geomSurfaces.push_back(s);
geom->SetFilletSurface(s);
}
if (rd.flag())
geom->SetCenterLineCurve(read_curve(rd, ctx));
(geom->*GeomPeek::devs())[0] = rd.num();
(geom->*GeomPeek::devs())[1] = rd.num();
for (ULONG k = 0; k < 2; ++k)
{
if (rd.flag())
{
SmSurface* base = read_surface(rd, ctx);
bases.push_back(base);
double dist = rd.num();
geom->SetOffsetSurface(k, new (ctx) SmOffsetSurface(dist, *base));
}
}
geoms.push_back(geom);
}
int64_t ne = rd.idx();
for (int64_t i = 0; i < ne; ++i)
{
SmFilletVertex* vs = at(verts, rd.idx());
SmFilletVertex* ve = at(verts, rd.idx());
int64_t etype = rd.idx();
int64_t status = rd.idx();
int64_t corner = rd.idx();
if (vs == NULL || ve == NULL)
return false;
SmFilletEdge* e = NULL;
if (SmFilletEdge::MakeFilletEdge(pseudo, vs, ve, e, at(corners, corner)) != SM_SUCCESS || e == NULL)
return false;
edges.push_back(e);
e->SetFilletEdgeType((SmFilletEdgeType)etype);
e->SetStatus((SmFilStatus)status);
if (rd.flag())
e->SetCurve(read_curve(rd, ctx), TRUE);
if (rd.flag())
{
double t0 = rd.num();
double t1 = rd.num();
e->SetInterval(SmExtent1d(t0, t1));
}
e->SetOriginalEdge(at(g.edges, rd.idx()));
e->SetOriginalFace(at(g.faces, rd.idx()));
for (int64_t which = 0; which < 2; ++which)
{
SmFilletEdgeuse* eu = eu_of(e, which);
if (eu == NULL)
return false;
if (rd.flag())
eu->SetUVCurve(read_curve(rd, ctx));
SmFilletGeom* pg = at(geoms, rd.idx());
if (pg != NULL)
eu->SetFilletGeom(pg);
SmFilletVertexuse* vu = (SmFilletVertexuse*)eu->GetVertexuse();
SmPoint2d uv = rd.uv();
vu->SetUVPoint(uv);
read_tsect(rd, vu->GetTsectPnt());
}
}
for (int64_t i = 0; i < ng; ++i)
{
SmFilletGeom* geom = geoms[(size_t)i];
for (ULONG r = 0; r < 2; ++r)
geom->SetRail(r, at(edges, rd.idx()));
int64_t n1 = rd.idx();
for (int64_t k = 0; k < n1; ++k)
(geom->*GeomPeek::other1()).Add(at(edges, rd.idx()));
int64_t n2 = rd.idx();
for (int64_t k = 0; k < n2; ++k)
(geom->*GeomPeek::other2()).Add(at(edges, rd.idx()));
int64_t nsd = rd.idx();
for (int64_t k = 0; k < nsd; ++k)
{
int64_t e = rd.idx();
int64_t which = rd.idx();
SmFilletEdgeuse* eu = eu_of(at(edges, e), which);
if (eu == NULL)
return false;
geom->AddSideFilletEdgeuse(eu);
}
}
for (int64_t i = 0; i < nv; ++i)
for (ULONG m = 0; m < 2; ++m)
verts[(size_t)i]->SetMate(m, at(verts, rd.idx()));
return true;
}
// ---- refusal checks: inputs on which upstream reads through NULL or a blind cast ----
// The edge's start vertex and its other vertex (always set by MakeFilletEdge).
static void ends(SmFilletEdge* e, SmFilletVertex*& s, SmFilletVertex*& t)
{
s = (SmFilletVertex*)e->GetVertex();
t = (SmFilletVertex*)e->GetOtherVertex(s);
}
// The first SM_FE_RAIL edge at a fillet vertex (CalcBlendingRail).
static SmFilletEdge* rail_at(SmFilletVertex* v)
{
SmTArray<SmEdge*> es;
v->GetEdges(es);
for (ULONG j = 0; j < es.GetSize(); j++)
if (((SmFilletEdge*)es[j])->GetFilletEdgeType() == SM_FE_RAIL)
return (SmFilletEdge*)es[j];
return NULL;
}
// CalcBlendingRail: the rails' primary edge uses' mates, curves are dereferenced.
static bool blending_safe(SmFilletEdge* e)
{
SmFilletVertex *s, *t;
ends(e, s, t);
SmFace* f = e->GetOriginalFace();
if (f == NULL || f->GetSurface() == NULL)
return true; // NER
SmFilletEdgeuse* p = eu_of(e, 0);
if (p == NULL || p->GetMate() == NULL)
return false;
SmFilletEdge* rails[2] = {rail_at(s), rail_at(t)};
for (SmFilletEdge* r : rails)
{
if (r == NULL)
return true; // NER, before the other rail is read
if (r->GetPrimaryEdgeuse() == NULL || r->GetPrimaryEdgeuse()->GetMate() == NULL ||
r->GetCurve() == NULL)
return false;
}
return true;
}
// CalcEdgeOnEdge: the original edge and its curve are dereferenced.
static bool on_edge_safe(SmFilletEdge* e)
{
SmEdge* o = e->GetOriginalEdge();
return o != NULL && o->GetCurve() != NULL;
}
// CalcFilletEnd: the vertices' classification objects are cast to edge uses and their
// edges dereferenced; the solver of the primary edge use's geometry is dereferenced.
static bool fillet_end_safe(SmFilletEdge* e)
{
SmFilletVertex *s, *t;
ends(e, s, t);
SmFilletEdgeuse* p = eu_of(e, 0);
if (p == NULL)
return false;
SmFilletGeom* fg = p->GetFilletGeom();
if (fg != NULL && fg->GetFilletSolver() == NULL)
return false;
SmFilletVertex* vs[2] = {s, t};
for (SmFilletVertex* v : vs)
{
if (v->GetPointClass() != SM_PC_EDGEUSE)
continue;
SmObject* o = v->GetPointClassObject();
if (o == NULL)
continue; // NER
if (!o->IsKindOf(SmEdgeuse_TYPE) || ((SmEdgeuse*)o)->GetEdge() == NULL ||
((SmEdgeuse*)o)->GetEdge()->GetPrimaryEdgeuse() == NULL)
return false;
}
return true;
}
// CalcSetBackRail: the geometry's solver, the rail (by FindIndexOfRailOnFace) and the
// rail's edge uses are dereferenced.
static bool setback_safe(SmFilletEdge* e)
{
SmFilletEdgeuse* p = eu_of(e, 0);
if (e->GetOriginalFace() == NULL || p == NULL)
return true; // NER
if (p->GetMate() == NULL)
return false;
SmFilletGeom* fg = p->GetFilletGeom();
if (fg == NULL)
return true; // NER
SmFilletSolver* fs = fg->GetFilletSolver();
if (fs == NULL)
return false;
SmEdgeuse* eu0 = fs->GetEdgeuse(0);
if (eu0 != NULL && (eu0->GetLoopuse() == NULL || eu0->GetLoopuse()->GetFaceuse() == NULL))
return false;
ULONG idx = fs->FindIndexOfRailOnFace(e->GetOriginalFace());
SmFilletEdge* r = fg->GetRail(idx);
if (r == NULL)
return false;
if (r->GetCurve() == NULL || !r->GetCurve()->IsKindOf(SmBSplineCurve_TYPE))
return true; // NER
return r->GetPrimaryEdgeuse() != NULL && r->GetPrimaryEdgeuse()->GetMate() != NULL;
}
// ShouldSwapFaces: the curve (with an interval or a classification) or the corner's
// vertex is dereferenced.
static bool swap_safe(SmFilletEdge* e)
{
SmExtent1d ivl = e->GetInterval();
if (!(ivl.IsInit() || ivl.HasNegativeLength()) || e->GetCurveClass() != NULL)
return e->GetCurve() != NULL;
if (e->GetFilletCorner() != NULL)
return e->GetFilletCorner()->GetFilletedVertex() != NULL;
return true;
}
// SmFilletGeom::Copy: the solver and both geometries' rails (and primary edge uses and
// mates) are dereferenced past the surface/centre-line checks.
static bool copy_safe(SmFilletGeom* g, SmFilletGeom* other)
{
if (g->GetFilletSolver() != other->GetFilletSolver())
return true; // SM_ERR
if (g->GetFilletSolver() == NULL)
return false;
for (ULONG i = 0; i < 2; ++i)
{
SmFilletEdge* a = other->GetRail(i);
SmFilletEdge* b = g->GetRail(i);
if (a == NULL || b == NULL || a->GetPrimaryEdgeuse() == NULL || a->GetPrimaryEdgeuse()->GetMate() == NULL)
return false;
}
return true;
}
// The rail-end vertex use of GetRailVertexuse: the rail, its primary edge use and the
// mate taken are dereferenced.
static bool rail_vu_safe(SmFilletGeom* g, ULONG rail)
{
SmFilletEdge* r = rail < 2 ? g->GetRail(rail) : NULL;
if (r == NULL || r->GetPrimaryEdgeuse() == NULL || r->GetPrimaryEdgeuse()->GetMate() == NULL)
return false;
return true;
}
// ---- export (layout in the Rust wrapper) ----
void put_eu(std::vector<double>& o, const SmEdgeuse* eu) const
{
for (size_t i = 0; i < edges.size(); ++i)
{
const SmEdgeuse* p = edges[i]->GetPrimaryEdgeuse();
if (p != NULL && p == eu)
{
o.push_back(ix((int64_t)i));
o.push_back(0.0);
return;
}
if (p != NULL && p->GetMate() == eu)
{
o.push_back(ix((int64_t)i));
o.push_back(1.0);
return;
}
}
o.push_back(-1.0);
o.push_back(-1.0);
}
void put(std::vector<double>& o) const
{
o.push_back(ix((int64_t)verts.size()));
for (SmFilletVertex* v : verts)
{
SmPoint3d p = v->GetPoint();
o.push_back(p.x);
o.push_back(p.y);
o.push_back(p.z);
o.push_back((double)v->GetFilletVertexType());
o.push_back((double)v->GetStatus());
SmPoint2d uv = v->GetOriginalUV();
o.push_back(uv.x);
o.push_back(uv.y);
o.push_back((double)v->GetPointClass());
o.push_back(v->GetOriginalTParam());
}
o.push_back(ix((int64_t)edges.size()));
for (SmFilletEdge* e : edges)
{
o.push_back((double)e->GetFilletEdgeType());
o.push_back((double)e->GetStatus());
put_bcurve(o, e->GetCurve());
SmExtent1d ivl = e->GetInterval();
o.push_back(ivl.GetMin());
o.push_back(ivl.GetMax());
o.push_back(ix(g.find(7, e->GetOriginalEdge())));
o.push_back(ix(g.find(3, e->GetOriginalFace())));
for (int64_t which = 0; which < 2; ++which)
{
SmFilletEdgeuse* eu = eu_of(e, which);
put_bcurve(o, eu->GetUVTrimCurvePointer());
o.push_back(ix(index_of(geoms, eu->GetFilletGeom())));
SmFilletVertexuse* vu = (SmFilletVertexuse*)eu->GetVertexuse();
SmPoint2d vuv = vu->GetUVPoint();
o.push_back(vuv.x);
o.push_back(vuv.y);
}
}
o.push_back(ix((int64_t)geoms.size()));
for (SmFilletGeom* geom : geoms)
{
o.push_back((double)geom->GetStatus());
const SmTArray<SmFilletEdgeuse*>& sides = geom->*GeomPeek::sides();
o.push_back((double)sides.GetSize());
for (ULONG k = 0; k < sides.GetSize(); ++k)
put_eu(o, sides[k]);
const SmTArray<SmFilletEdgeuse*>& refs = geom->*GeomPeek::refs();
o.push_back((double)refs.GetSize());
for (ULONG k = 0; k < refs.GetSize(); ++k)
put_eu(o, refs[k]);
put_bsurface(o, geom->*GeomPeek::surface());
put_bcurve(o, geom->GetCenterLineCurve());
o.push_back((geom->*GeomPeek::devs())[0]);
o.push_back((geom->*GeomPeek::devs())[1]);
for (ULONG k = 0; k < 2; ++k)
{
SmOffsetSurface* off = (geom->*GeomPeek::offs())[k];
o.push_back(off != NULL ? 1.0 : 0.0);
if (off != NULL)
{
o.push_back(off->GetOffsetDistance());
put_bsurface(o, off->GetBaseSurface());
}
}
}
}
};
int32_t run(int32_t op, Rd& rd, const char* path, std::vector<double>& o)
{
const SmContext& ctx = the_context();
Model m(ctx);
SmStatus bst = SM_SUCCESS;
m.target = build(rd, path, bst);
if (m.target == NULL)
{
o.push_back(1.0); // the target B-rep cannot be built
o.push_back(st(bst));
return SC_OK;
}
walk(m.target, m.g);
if (!m.build(rd))
return SC_ERR_ARG;
o.push_back(0.0);
// op results that come before the model export (layout in the Rust wrapper)
std::vector<double> r;
SmStatus s = SM_SUCCESS;
switch (op)
{
case OP_EXPORT: break;
case OP_BLENDING_RAIL:
case OP_EDGE_ON_EDGE:
case OP_FILLET_END:
case OP_SETBACK_RAIL:
{
SmFilletEdge* e = at(m.edges, rd.idx());
bool use_corner = rd.flag();
if (e == NULL)
return SC_ERR_ARG;
SmFilletCorner* corner = use_corner ? e->GetFilletCorner() : NULL;
if (op == OP_BLENDING_RAIL)
{
if (!Model::blending_safe(e))
return SC_ERR_ARG;
s = e->CalcBlendingRail(ctx);
}
else if (op == OP_EDGE_ON_EDGE)
{
if (!Model::on_edge_safe(e))
return SC_ERR_ARG;
s = e->CalcEdgeOnEdge(ctx, corner);
}
else if (op == OP_FILLET_END)
{
if (!Model::fillet_end_safe(e))
return SC_ERR_ARG;
s = e->CalcFilletEnd(ctx, corner);
}
else
{
if (!Model::setback_safe(e))
return SC_ERR_ARG;
s = e->CalcSetBackRail(ctx, corner);
}
break;
}
case OP_SHOULD_SWAP:
{
SmFilletEdge* e = at(m.edges, rd.idx());
SmFace* f1 = at(m.g.faces, rd.idx());
SmFace* f2 = at(m.g.faces, rd.idx());
if (e == NULL || f1 == NULL || f2 == NULL || !Model::swap_safe(e))
return SC_ERR_ARG;
r.push_back(flg(e->ShouldSwapFaces(f1, f2)));
break;
}
case OP_JOIN:
{
SmFilletGeom* geom = at(m.geoms, rd.idx());
SmFilletVertex* vs = at(m.verts, rd.idx());
SmFilletVertex* ve = at(m.verts, rd.idx());
double uv_tol = rd.num();
int64_t n = rd.idx();
SmTArray<SmCurve*> c3;
SmTArray<SmBSplineCurve*> cuv;
SmTArray<SmOrientType> orients;
for (int64_t k = 0; k < n; ++k)
{
SmFilletEdge* e = at(m.edges, rd.idx());
int64_t which = rd.idx();
bool with_uv = rd.flag();
int64_t orient = rd.idx();
SmFilletEdgeuse* eu = Model::eu_of(e, which);
if (eu == NULL)
return SC_ERR_ARG;
c3.Add(e->GetCurve());
cuv.Add(with_uv ? eu->GetUVTrimCurvePointer() : NULL);
orients.Add((SmOrientType)orient);
}
if (geom == NULL || n < 1 || geom->GetFilletSolver() == NULL)
return SC_ERR_ARG;
SmTArray<SmFilletEdgeuse*>& sides = geom->*GeomPeek::sides();
// the last curve of the list tested (UV or 3D) is evaluated, and every side edge use's
// edge; with UV testing a gap above uv_tol evaluates the fillet surface
bool all_uv = true;
for (ULONG k = 0; k < cuv.GetSize(); ++k)
all_uv = all_uv && cuv[k] != NULL;
if ((all_uv ? (SmCurve*)cuv.GetLast() : c3.GetLast()) == NULL)
return SC_ERR_ARG;
for (ULONG k = 0; k < sides.GetSize(); ++k)
if (sides[k]->GetEdge() == NULL)
return SC_ERR_ARG;
if (all_uv && geom->GetFilletSurface() == NULL)
return SC_ERR_ARG;
SmBoolean made = FALSE;
s = sc_p6_join_two_fillet_verts_by_curves(geom, vs, ve, sides, uv_tol, c3, cuv, orients, made);
r.push_back(flg(made));
r.push_back((double)c3.GetSize());
for (ULONG k = 0; k < c3.GetSize(); ++k)
{
put_bcurve(r, c3[k]);
put_bcurve(r, cuv[k]);
r.push_back((double)orients[k]);
}
break;
}
case OP_COPY:
{
SmFilletGeom* geom = at(m.geoms, rd.idx());
SmFilletGeom* other = at(m.geoms, rd.idx());
if (geom == NULL || other == NULL || !Model::copy_safe(geom, other))
return SC_ERR_ARG;
s = geom->Copy(other);
break;
}
case OP_FIND_TANGENT:
{
SmFilletGeom* geom = at(m.geoms, rd.idx());
int64_t rail = rd.idx();
int64_t end = rd.idx();
if (geom == NULL || rail > 1 || geom->GetRail((ULONG)rail) == NULL)
return SC_ERR_ARG;
SmFilletEdge* re = geom->GetRail((ULONG)rail);
SmFilletVertex* fv = (SmFilletVertex*)re->GetVertex();
if (end != 0)
fv = (SmFilletVertex*)re->GetOtherVertex(fv);
if (fv == NULL)
return SC_ERR_ARG;
if (fv->GetPointClass() == SM_PC_EDGE)
{
SmObject* ob = fv->GetPointClassObject();
if (ob != NULL && !ob->IsKindOf(SmEdge_TYPE))
return SC_ERR_ARG;
}
SmBoolean found = FALSE;
SmVector3d tan(0.0, 0.0, 0.0);
s = geom->FindEdgeTangentAtPoint((ULONG)rail, (ULONG)end, found, tan);
r.push_back(flg(found));
put_vec(r, tan);
break;
}
case OP_RAIL_INDEX_SIDE_EU:
{
SmFilletGeom* geom = at(m.geoms, rd.idx());
SmEdgeuse* eu = at(m.g.edgeuses, rd.idx());
if (geom == NULL || eu == NULL)
return SC_ERR_ARG;
if (geom->GetRail(0) == NULL || geom->GetRail(1) == NULL)
{
SmFilletSolver* fs = geom->GetFilletSolver();
SmEdgeuse* eu0 = fs == NULL ? NULL : fs->GetEdgeuse(0);
if (fs == NULL || eu0 == NULL || eu->GetEdge() == NULL)
return SC_ERR_ARG;
if (eu->GetEdge()->IsClosed())
{
if (eu0->GetVertexuse() == NULL || eu->GetVertexuse() == NULL)
return SC_ERR_ARG;
if (eu->GetVertexuse()->GetVertex() != eu0->GetVertexuse()->GetVertex() &&
eu0->GetCCWEdgeuse() == NULL)
return SC_ERR_ARG;
}
}
ULONG idx = 99;
s = geom->GetRailIndexOfSideEU(eu, idx);
r.push_back(s == SM_SUCCESS ? (double)idx : 99.0); // the out-param only on success
break;
}
case OP_RAIL_INDEX_VU:
{
SmFilletGeom* geom = at(m.geoms, rd.idx());
int64_t ei = rd.idx();
int64_t which = rd.idx();
SmFilletEdgeuse* eu = Model::eu_of(at(m.edges, ei), which);
if (geom == NULL || eu == NULL)
return SC_ERR_ARG;
SmFilletVertexuse* vu = (SmFilletVertexuse*)eu->GetVertexuse();
// GetRailVertexuse dereferences rail 0, and rail 1 when the use is not at rail 0
if (!Model::rail_vu_safe(geom, 0))
return SC_ERR_ARG;
if (geom->GetRailVertexuse(0, 0) != vu && geom->GetRailVertexuse(0, 1) != vu &&
!Model::rail_vu_safe(geom, 1))
return SC_ERR_ARG;
ULONG idx = 99;
s = geom->GetRailIndexOfVertexuse(vu, idx);
r.push_back(s == SM_SUCCESS ? (double)idx : 99.0); // the out-param only on success
break;
}
case OP_RAIL_VERTEX:
{
SmFilletGeom* geom = at(m.geoms, rd.idx());
int64_t rail = rd.idx();
int64_t end = rd.idx();
if (geom == NULL || rail > 1 || end > 1 || !Model::rail_vu_safe(geom, (ULONG)rail))
return SC_ERR_ARG;
SmFilletVertex* v = geom->GetRailVertex((ULONG)rail, (ULONG)end);
r.push_back(ix(index_of(m.verts, v)));
break;
}
case OP_SETUP_RAIL_POINT:
{
SmFilletGeom* geom = at(m.geoms, rd.idx());
int64_t rail = rd.idx();
int64_t end = rd.idx();
double param = rd.num();
bool has_pc = rd.flag();
int64_t pc_class = rd.idx();
int64_t pc_kind = rd.idx();
int64_t pc_index = rd.idx();
double pc_t = rd.num();
if (geom == NULL || rail > 1 || end > 1 || !Model::rail_vu_safe(geom, (ULONG)rail))
return SC_ERR_ARG;
SmFilletVertexuse* vu = geom->GetRailVertexuse((ULONG)rail, (ULONG)end);
SmFilletVertex* fv = (SmFilletVertex*)vu->GetVertex();
if (!fv->IsProcessed() && geom->GetRail((ULONG)rail)->GetCurve() == NULL)
return SC_ERR_ARG;
SmPointClassification pc(SmTol::GetZoneTol3d());
pc.SetClassObject((SmPointClassificationType)pc_class, m.pc_object(pc_kind, pc_index));
pc.SetTParam(pc_t);
s = geom->SetUpRailPoint((ULONG)rail, (ULONG)end, param, has_pc ? &pc : NULL);
break;
}
case OP_MOVE_TSP:
{
int64_t n = rd.idx();
std::vector<SmTsectPnt> pnts((size_t)n);
SmTArray<SmTsectPnt*> arr;
for (int64_t k = 0; k < n; ++k)
{
pnts[(size_t)k].CrvPos() = rd.vec();
pnts[(size_t)k].CrvDeriv() = rd.vec();
}
for (int64_t k = 0; k < n; ++k)
arr.Add(&pnts[(size_t)k]);
int64_t from = rd.idx();
int64_t to = rd.idx();
s = sc_p6_move_tsp(arr, (ULONG)from, (ULONG)to);
r.push_back((double)arr.GetSize());
for (ULONG k = 0; k < arr.GetSize(); ++k)
r.push_back((double)(arr[k] - pnts.data()));
break;
}
case OP_RAIL_EU_INTERSECT:
{
SmFilletGeom* geom = at(m.geoms, rd.idx());
int64_t rail = rd.idx();
SmEdgeuse* eu = at(m.g.edgeuses, rd.idx());
double guess = rd.num();
if (geom == NULL || eu == NULL || rail > 1)
return SC_ERR_ARG;
SmFilletSolver* fs = geom->GetFilletSolver();
// without a solver: NER(pFS); else the base solver reads both of its surfaces
// (through the geometry), the side edge use's edge and its UV trim curve (made on
// demand)
if (fs != NULL && (eu->GetEdge() == NULL || eu->GetFace() == NULL))
return SC_ERR_ARG;
SmBoolean found = FALSE;
SmTsectPnt t;
double param = 0.0;
s = geom->RailEdgeuseIntersect((ULONG)rail, eu, guess, found, t, param);
r.push_back(flg(found));
r.push_back(param);
break;
}
case OP_RECORD_ERROR:
{
SmFilletGeom* geom = at(m.geoms, rd.idx());
int64_t type = rd.idx();
if (geom == NULL || geom->GetFilletSolver() == NULL || geom->GetFilletSolver()->GetFilletExecutive() == NULL)
return SC_ERR_ARG;
geom->RecordFilletError((SmFilletErrorType)type, NULL, NULL, NULL, NULL, NULL);
break;
}
default: return SC_ERR_ARG;
}
o.push_back(st(s));
o.insert(o.end(), r.begin(), r.end());
m.put(o);
return SC_OK;
}
} // namespace
SC_EXPORT int32_t sc_blend_fillet_geom_p6_eval(int32_t op, const char* path, const double* in, int64_t n_in,
double* out, int64_t cap, int64_t* out_len)
{
if (in == NULL || out_len == NULL || path == NULL)
return SC_ERR_ARG;
SC_TRY
Rd rd{in, 0};
std::vector<double> o;
int32_t r = run(op, rd, path, o);
if (r != SC_OK)
return r;
(void)n_in;
return sc_copy_out(o.data(), (int64_t)o.size(), out, cap, out_len);
SC_CATCH
}
+22
View File
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: Apache-2.0
// Wrappers of two file-statics of upstream source/SMLib/src/SmFilletGeom.cpp for the difftest
// shim of unit `blend.fillet_geom_p6` (blend_fillet_geom_p6.cpp). Static functions cannot be
// linked against and SmFilletGeom.cpp can only be compiled into one translation unit (that of
// blend_fillet_geom_p1.cpp), so the wrappers are defined there, in
// blend_fillet_geom_p6_static.inc.
#pragma once
#include "StdAfx.h"
#include <SmFilletGeom.h>
#include <SmSurfaceIntersector.h>
#include <SmTArray.h>
// JoinTwoFilletVertsByCurves(pFilletGeom, pStartFV, pEndFV, rSideEUs, dUVTol, r3DCurves,
// rUVCurves, rCurveOrients, rbMadeConnection, 0).
SmStatus sc_p6_join_two_fillet_verts_by_curves(SmFilletGeom* pFilletGeom, SmVertex* pStartFV, SmVertex* pEndFV,
SmTArray<SmFilletEdgeuse*>& rSideEUs, double dUVTol,
SmTArray<SmCurve*>& r3DCurves, SmTArray<SmBSplineCurve*>& rUVCurves,
SmTArray<SmOrientType>& rCurveOrients, SmBoolean& rbMadeConnection);
// sm_MoveTSP(rTsectPnts, lFrom, lTo).
SmStatus sc_p6_move_tsp(SmTArray<SmTsectPnt*>& rTsectPnts, ULONG lFrom, ULONG lTo);
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: Apache-2.0
// Included at the end of blend_fillet_geom_p1.cpp, the translation unit that compiles upstream
// SmFilletGeom.cpp: wrappers of its file-statics for the shim of unit `blend.fillet_geom_p6`
// (declarations and rationale in blend_fillet_geom_p6.h). Not compiled on its own.
#include "blend_fillet_geom_p6.h"
SmStatus sc_p6_join_two_fillet_verts_by_curves(SmFilletGeom* pFilletGeom, SmVertex* pStartFV, SmVertex* pEndFV,
SmTArray<SmFilletEdgeuse*>& rSideEUs, double dUVTol,
SmTArray<SmCurve*>& r3DCurves, SmTArray<SmBSplineCurve*>& rUVCurves,
SmTArray<SmOrientType>& rCurveOrients, SmBoolean& rbMadeConnection)
{
return JoinTwoFilletVertsByCurves(pFilletGeom, pStartFV, pEndFV, rSideEUs, dUVTol, r3DCurves, rUVCurves,
rCurveOrients, rbMadeConnection, 0);
}
SmStatus sc_p6_move_tsp(SmTArray<SmTsectPnt*>& rTsectPnts, ULONG lFrom, ULONG lTo)
{
return sm_MoveTSP(rTsectPnts, lFrom, lTo);
}
@@ -485,6 +485,14 @@ int32_t run_fsg(Rd& rd, const SmContext& ctx, std::vector<double>& o)
int64_t kind = rd.idx();
double a = rd.num(), b = rd.num(), c = rd.num(), d = rd.num();
double v[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
// the linear kind: `n_rails` (0 or 2) then the rails its CreateSurface is given
SmBSplineCurve* rail1 = NULL;
SmBSplineCurve* rail2 = NULL;
if (kind == 1 && rd.idx() == 2)
{
rail1 = read_curve(rd, ctx);
rail2 = read_curve(rd, ctx);
}
SmFilletSurfaceGenerator* g = NULL;
switch (kind)
{
@@ -523,8 +531,49 @@ int32_t run_fsg(Rd& rd, const SmContext& ctx, std::vector<double>& o)
SmBSplineSurface* out = NULL;
o.push_back(st(g->CreateSurface(fs, NULL, NULL, NULL, pts, NULL, NULL, NULL, NULL, out)));
}
else if (rail1 != NULL)
{
// SmLinearCrossSectionFSG::CreateSurface: the ruled surface of the two rails
// (the solver, centre line, points and UV curves are not used): `status` then
// `has_nurb [n m p q r s cpts knots_u knots_v]`
ShimFS fs(ctx, 1.0e-6, 0.1, 0.01);
SmTArray<SmTsectPnt*> pts;
SmBSplineSurface* out = NULL;
SmStatus s = g->CreateSurface(fs, NULL, rail1, rail2, pts, NULL, NULL, NULL, NULL, out);
o.push_back(st(s));
const gw_SURFACE* sur = (s == SM_SUCCESS && out != NULL) ? out->GetGwNurbPointer() : NULL;
o.push_back(flg(sur != NULL));
if (sur != NULL)
{
gw_INDEX n = sur->net->n;
gw_INDEX m = sur->net->m;
gw_INDEX r = sur->knu->m;
gw_INDEX ss = sur->knv->m;
o.push_back((double)n);
o.push_back((double)m);
o.push_back((double)sur->p);
o.push_back((double)sur->q);
o.push_back((double)r);
o.push_back((double)ss);
for (gw_INDEX i = 0; i <= n; i++)
for (gw_INDEX j = 0; j <= m; j++)
{
o.push_back(sur->net->Pw[i][j].x);
o.push_back(sur->net->Pw[i][j].y);
o.push_back(sur->net->Pw[i][j].z);
o.push_back(sur->net->Pw[i][j].w);
}
for (gw_INDEX i = 0; i <= r; i++)
o.push_back(sur->knu->U[i]);
for (gw_INDEX i = 0; i <= ss; i++)
o.push_back(sur->knv->U[i]);
}
delete out;
}
else
o.push_back(-1.0);
delete rail1;
delete rail2;
delete g;
return 0;
}
+88
View File
@@ -10404,6 +10404,33 @@ rejected: nothing runs on either side.
`CreateUVTrimCurves` must still return this clipped domain (callers in
`boolean.topology_intersector_p1` keep it on success).
## blend.fillet_geom_p2: sm_SplitRail's fillet vertex
`sm_SplitRail` (`blend.fillet_solver_p1`) builds `SmFilletVertex(pFG->..->GetFilletCorner())`
for the vertex it splits the rail at. The constructor is ported (`FilletVertex::new` with
`FilletBrep::insert_fillet_vertex`, `blend.fillet_geom_p2`, unit `blend.fillet_geom_p1`
owns the class), but `split_rail` works on the bare pseudo `Brep` and has no `FilletBrep`
to insert into. The call site stays an `UNPORTED(blend.fillet_geom_p1)` stub (tag
retargeted from the stale `blend.fillet_geom_p2`, which does not own the symbol); it
is wired together with the other `blend.fillet_geom_p1` stubs once `&mut FilletBrep` is
threaded through the solver (see "blend.fillet_solver_p1: stubs naming
blend.fillet_geom_p1"). The `split_rail` body is unchanged.
## blend.fillet_solver_p1: FilletSolverOps::load_initial_values takes the surface by value of reference
`FilletSolverOps::load_initial_values` (`fillet_solver/p1.rs`) takes `crSurface1` as
`&mut OffsetSurface`. Upstream's callers pass `*GetSurface(lRailIndex)`, the solver's own
rail surface, so a derived `LoadInitialValues` that changes it (`SetOffsets`, as
`SmConstantDistanceFS::LoadInitialValues` does) changes the solver's. A caller that owns
the solver (`blend.fillet_standard_solver_p2`) can only pass a clone and write it back,
which overwrites such a change. `#168`'s `ConstantDistanceFs::load_initial_values` already
models this with `InitialSurface::{Rail(i), Other(&mut)}`. The fix is to make the trait
method take `InitialSurface` and give `ConstantDistanceFs` a `FilletSolverOps` impl; it
touches the trait, `EiffelTowerFs`, `SecantConstantRadiusFs` (they read `surface1` only
through `&self.base`, so `Rail(i)` resolves there), the three runners and the
shims' callers, so it is deferred to a follow-up rather than done in this round. Until
then a caller must write the clone back only when the callee did not call `SetOffsets`.
## blend.fillet_solver_p1: pseudo-B-rep mutations
`SplitFilletGeomAtSideEdge`, `sm_SplitRail` and `sm_ComputeSupportEdge` (PR #192) move
@@ -12112,6 +12139,67 @@ The unit stays `blocked` until these are isolated (start with a curve-level diff
classification alone would compare. The `bspline_surface()` stub tags
`nurbs.b_spline_surface_n_lib_p2` (partial), following the `topology::brep::p1` precedent.
## blend.fillet_geom_p6 (`SmFilletGeom.cpp` part 6, PR #247, 2026-10-19; partial)
- **`sm_IntersectAndSplit` handle (fixed, unit `surface.surface_p2`).** `intersect_and_split`
ran `GlobalPropertyAnalysis` with no curve handle. Upstream's `LocalSolve` names the curve
as each solution's object, so `IdenticalSolutions` scales its duplicate test by the curve's
interval (`dScaledZero` at least 1e-4) and keeps the root with the smaller residual. With
no object the port's scale was about 1e-10, so two roots ~1e-10 apart both survived and
`insert_split` kept the smaller parameter. `LiftCurve` then split UV curves at C0 surface
knot lines ~1e-10 off upstream, and every interior knot of the joined 3D curve moved by
~3e-10 relative (control points up to 1.4e-7). Found by this unit's `CalcFilletEnd` cases
(1 of 3000 generated); `LiftCurve` had never been compared by a difftest before
(`surface.surface_p11` has no runner for it). Fixed on main by #444 (the solver solutions
name their operands; `intersect_and_split` takes the curve's handle), which this PR now
uses in place of its own equivalent fix.
- **Analytic target geometry.** Ops that evaluate a target face surface or edge curve of an
analytic kind are deferred under `harness:analytic-geometry-export` (the shared B-rep
export carries those as their B-spline part; see the harness entry above). Before the
deferral, `CalcBlendingRail` on `vase_rev.smb` faces differed by ~1e-13 at the end points
dropped onto the face (`GlobalPointSolve` on the surface of revolution's B-spline part vs
the analytic surface), and on `cone.smb` / `box_with_large_fillet.smb` faces `LiftCurve`
produced curves of different sizes (909 vs 4650 control points).
- **Casts.** `SM_CAST_PTR(SmBSplineCurve, ...)` keeps `SmLine`, `SmEllipse` / `SmCircle`,
`SmParabola` and `SmHyperbola` (all `SmBSplineCurve` subclasses); the port tests
`Curve::is_kind_of(BSPLINE_CURVE_TYPE)` (a `CurveKind::BSpline` match missed line edges
in `CalcEdgeOnEdge`). `blend::fillet_geom::p3::calc_fillet_int_fillet`'s
`has_3d_curve` still matches `CurveKind::BSpline` only: a line or conic fillet/fillet
curve takes the `sm_SrfSrfIntersection` path there, where upstream trims it (not
reached by p3's difftest, which generates B-spline curves).
- **Coverage.** `TrimOriginalSurfaces` is kernel-tested only: it needs a fillet B-rep,
the executive's topology-intersector mates and per-context marks, which the shim does not
build (its control paths are unit-tested; the binormal comparison and face collection
are not compared). The curve-classification branch of `ShouldSwapFaces` is not generated
(the shim builds no `SmCurveClassification`). `RailEdgeuseIntersect` (unit
`blend.fillet_solver_p2`) and `RecordFilletError` (`NoteFilletError`, unit
`blend.fillet_executive_p1`) defer; the shim gives each model a real `SmFilletExecutive`
so the C++ side runs them.
- **File-statics.** `JoinTwoFilletVertsByCurves` and `sm_MoveTSP` are reached through
wrappers defined in the translation unit of `harness/cpp/shim/blend_fillet_geom_p1.cpp`
(the only one that compiles `SmFilletGeom.cpp`), included from
`blend_fillet_geom_p6_static.inc`.
- **Small scales.** At scale 1e-4 the Rust `LiftCurve` in `CalcBlendingRail` returns
`SM_ERR_NULL_POINTER` where C++ lifts the curve: the surface cache refuses to subdivide past
`MAX_SUBDIVISION_NODES` (see `surface.surface_p11` above; reproduced on main with a plain
3x3 quadratic patch scaled by 1e-4, review of PR #247). The generator keeps this op at
scale 1 and above; extending it below needs that limit raised or the cases deferred.
- **Wiring part 6 into parts 2/4, the corner and the executive exposed three harness or
ordering gaps, all fixed (PR #247).** (1) `blend.fillet_geom_p4`'s `MakeFace` runner built
a bare `Brep::new()`; upstream's `new SmBrep()` has a void infinite region, so
`TrimSurfaceWithModelSpaceCurves` went on in C++ (`SM_ERR`) but stopped at `NER(pRegion)`
in Rust. (2) `CalcBlendingGeom` read both rails before its loop; upstream reads
`m_vRails[i]` per iteration, so a missing second rail after a failing first one is
`SM_ERR`, not `SM_ERR_NULL_POINTER` (`--seeds 2000`, 6 cases). (3) `blend.fillet_corner_p3`'s
shim stopped `AdjustTangentCorner` at `GetRailIndexOfVertex`; it now mirrors the split loop
up to `SplitFilletGeomAtSideEdge`. Also `GetRailIndexOfVertexuse(NULL)` is
`SM_ERR_NULL_POINTER` (`NER(pFVU)`), not a rail-end comparison.
- **Back-edges not wired.** `GetRailIndexOfVertexuse`, `GetRailVertex` and `SetUpRailPoint`
in `blend/fillet_solver/p1.rs` (`split_fillet_geom_at_side_edge`, `split_fillet_geom`)
stay stubs: that code holds the solver's opaque `FilletGeomRef`s and a plain pseudo
`Brep`, not a `FilletBrep` (as for part 3's `TopologySplit`).
## primitive.api_primitives: review fixes (PR #160, 2026-10-19; no disagreements)
- **Torus surface guard was too wide (AGENTS.md section 4 step 5 item 6, "no more").** The FFI guard refused every
+10 -10
View File
@@ -2044,7 +2044,7 @@ tests = ["prog_test:my_test_surface_closest_point", "prog_test:my_test_drop_poin
status = "green"
claimed_by = ""
claimed_at = ""
notes = "#76 kernel-aware Surface dispatch (PR #313, 2026-10-21): 600 cases, 600 pass, 0 failed, 0 deferred, max_rel_err 0; before 559 pass / 41 deferred. PARTIAL: difftest 600 generated cases (LocalSurfaceIntersect over Bezier patches and planes: crossing, standing, nearly parallel, tiny and huge scales, sub-domains, guesses on boundaries, plane normals (also zero), previous points, boundary testing; IntersectSurfaceBoundaries of coincident and crossing patches and planes), 498 pass bit-identical, 0 failed, 102 deferred (surface.surface_p8 102: sm_IntersectSrfBndryCrv); --perturb SENSITIVE 498/498; SmCSGlobalSolver/SmSSGlobalSolver::LocalSolve are compared through surface.surface_p7's GlobalCurveSolve/GlobalSurfaceSolve cases (first 16 solutions; minimize, maximize, normalize, signed directed minimize, intersect for nodes) and the intersectors; LiftCurve defers at sm_ExtractIsoCurve (surface.surface_p8) and past it has a dispatch gap for non-B-spline surfaces (CrvOnSurf holds a B-spline, findings.md; marked UNPORTED(curve.crv_on_surf)). Coverage acknowledged, not closed: the shim builds only Bezier patches and planes (the leaf solves are not compared on revolution, curve-bounded, Step or SrfInVolume surfaces); SM_SO_3D_SIGNED_DIRECTED_MAXIMIZE is not generated because sm_SCLocalSolve asserts on it (the Rust side returns InvalidInput, the release C++ continues); LiftCurve has no shim or runner after its first line; some passes compare no values; MAX_SUBDIVISION_NODES (2^20) stays (a surface.surface_p1 case at 1e-4 scale needs more than 2^18). Wired UNPORTED(surface.surface_p11): SmSurface::LocalSurfaceIntersect (intersect.surface_intersector_p1, used by adv_surface_intersector/p3/p4/p5), SmCSGlobalSolver/SmSSGlobalSolver::LocalSolve (surface.surface_p7: global_curve_solve/global_surface_solve now take the kernel and handles; intersect.topology_solver_p2 updated), GlobalSurfaceSolve SM_SR_NODES (intersect.surface_intersector_p4 FindInteriorCurves, via global_surface_solve_with). Wired UNPORTED(surface.surface_p3) after merging main: SmSurface::LocalSurfaceSolve. Bounded surface cache subdivision (surface.surface_cache_p3 MAX_SUBDIVISION_NODES, the 3.5 GB abort). Upstream issues UB-121, WR-99; plane boundary curve type mismatch with surface.surface_p9 recorded in findings.md. 5 fns; largest: SmSurface::LocalSurfaceIntersect, SmSSGlobalSolver::LocalSolve, SmSurface::LiftCurve, SmCSGlobalSolver::LocalSolve; cycle back-edges (call into later units; stub or co-port): surface.surface_p8(2); unit-SCC #22 (220 units); 317 upstream test entry points reach this unit Re-run (PR #118, sm_IntersectSrfBndryCrv and sm_ExtractIsoCurve wired to surface.surface_p8): 600 cases, 559 pass, 41 deferred (curve.iso_curve 41), 0 failed; --perturb SENSITIVE 559/559; the surface.surface_p8 deferrals are gone."
notes = "#76 kernel-aware Surface dispatch (PR #313, 2026-10-21): 600 cases, 600 pass, 0 failed, 0 deferred, max_rel_err 0; before 559 pass / 41 deferred. PARTIAL: difftest 600 generated cases (LocalSurfaceIntersect over Bezier patches and planes: crossing, standing, nearly parallel, tiny and huge scales, sub-domains, guesses on boundaries, plane normals (also zero), previous points, boundary testing; IntersectSurfaceBoundaries of coincident and crossing patches and planes), 498 pass bit-identical, 0 failed, 102 deferred (surface.surface_p8 102: sm_IntersectSrfBndryCrv); --perturb SENSITIVE 498/498; SmCSGlobalSolver/SmSSGlobalSolver::LocalSolve are compared through surface.surface_p7's GlobalCurveSolve/GlobalSurfaceSolve cases (first 16 solutions; minimize, maximize, normalize, signed directed minimize, intersect for nodes) and the intersectors; LiftCurve defers at sm_ExtractIsoCurve (surface.surface_p8) and past it has a dispatch gap for non-B-spline surfaces (CrvOnSurf holds a B-spline, findings.md; marked UNPORTED(curve.crv_on_surf)). Coverage acknowledged, not closed: the shim builds only Bezier patches and planes (the leaf solves are not compared on revolution, curve-bounded, Step or SrfInVolume surfaces); SM_SO_3D_SIGNED_DIRECTED_MAXIMIZE is not generated because sm_SCLocalSolve asserts on it (the Rust side returns InvalidInput, the release C++ continues); LiftCurve has no shim or runner after its first line; some passes compare no values; MAX_SUBDIVISION_NODES (2^20) stays (a surface.surface_p1 case at 1e-4 scale needs more than 2^18). Wired UNPORTED(surface.surface_p11): SmSurface::LocalSurfaceIntersect (intersect.surface_intersector_p1, used by adv_surface_intersector/p3/p4/p5), SmCSGlobalSolver/SmSSGlobalSolver::LocalSolve (surface.surface_p7: global_curve_solve/global_surface_solve now take the kernel and handles; intersect.topology_solver_p2 updated), GlobalSurfaceSolve SM_SR_NODES (intersect.surface_intersector_p4 FindInteriorCurves, via global_surface_solve_with). Wired UNPORTED(surface.surface_p3) after merging main: SmSurface::LocalSurfaceSolve. Bounded surface cache subdivision (surface.surface_cache_p3 MAX_SUBDIVISION_NODES, the 3.5 GB abort). Upstream issues UB-121, WR-99; plane boundary curve type mismatch with surface.surface_p9 recorded in findings.md. 5 fns; largest: SmSurface::LocalSurfaceIntersect, SmSSGlobalSolver::LocalSolve, SmSurface::LiftCurve, SmCSGlobalSolver::LocalSolve; cycle back-edges (call into later units; stub or co-port): surface.surface_p8(2); unit-SCC #22 (220 units); 317 upstream test entry points reach this unit Re-run (PR #118, sm_IntersectSrfBndryCrv and sm_ExtractIsoCurve wired to surface.surface_p8): 600 cases, 559 pass, 41 deferred (curve.iso_curve 41), 0 failed; --perturb SENSITIVE 559/559; the surface.surface_p8 deferrals are gone.; LiftCurve is now compared through blend.fillet_geom_p6 (PR #247: CalcFilletEnd and CalcBlendingRail lifts of up to ~5000 control points, bit-identical with sm_IntersectAndSplit's curve handle, fixed on main by #444)"
[[unit]]
id = "surface.surface_p8"
@@ -3304,7 +3304,7 @@ tests = ["SM_API_test:TestSmCircularFillet", "SM_API_test:TestSmChamferFillet",
status = "partial"
claimed_by = ""
claimed_at = ""
notes = "PR #244: 3/3 control-flow bodies, complete continuations; difftest 291 cases: 116 state/error-path passes, 0 failures, 175 deferred (executive_p1 46, topology.brep_p4 111 and query.brep 18 since PR #267 wired ProcessSmoothEdgeSequences and the merged topology.brep_p2 SetTolerance; executive_p2 129 before); with_values=116 no_values=0; 0 successful geometry/topology parity cases. Includes 7 actual upstream API examples, Rust setup deferred to executive_p1. 19 core tests +4 FFI guard tests. 3 fns; largest: SmFilletExecutive::DoFilleting, SmFilletExecutive::CreateFilletBrep, SmFilletExecutive::DoFillet; cycle back-edges (call into later units; stub or co-port): blend.fillet_corner_p1(13), blend.fillet_executive_p1(12), blend.fillet_executive_p2(2), blend.fillet_geom_p4(2), blend.fillet_corner_p3(1), blend.fillet_geom_p6(1); unit-SCC #22 (220 units); 25 upstream test entry points reach this unit"
notes = "PR #244: 3/3 control-flow bodies, complete continuations; difftest 291 cases: 116 state/error-path passes, 0 failures, 175 deferred (executive_p1 46, topology.brep_p4 111 and query.brep 18 since PR #267 wired ProcessSmoothEdgeSequences and the merged topology.brep_p2 SetTolerance; executive_p2 129 before); with_values=116 no_values=0; 0 successful geometry/topology parity cases. Includes 7 actual upstream API examples, Rust setup deferred to executive_p1. 19 core tests +4 FFI guard tests. 3 fns; largest: SmFilletExecutive::DoFilleting, SmFilletExecutive::CreateFilletBrep, SmFilletExecutive::DoFillet; cycle back-edges (call into later units; stub or co-port): blend.fillet_corner_p1(13), blend.fillet_executive_p1(12), blend.fillet_executive_p2(2), blend.fillet_geom_p4(2), blend.fillet_corner_p3(1), blend.fillet_geom_p6(1); unit-SCC #22 (220 units); 25 upstream test entry points reach this unit; UPDATE (PR #247): ExecutiveAccess::trim_original_surfaces is a required adapter delegating to blend::fillet_geom::p6::FilletBrep::trim_original_surfaces (as insert_geom_topology for p4); no case reaches it, re-run unchanged"
[[unit]]
id = "boolean.merge_p2"
@@ -3521,10 +3521,10 @@ soft_depends_on = ["blend.fillet_corner_p1", "blend.fillet_executive_p1", "blend
est_lines = 1032
layer = 60
tests = ["SM_API_test:TestSmFilletPreview", "SM_API_test:TestSmSurfaceSurfaceFillet", "SM_API_test:TestSmCircularFillet", "SM_API_test:TestSmChamferFillet", "SM_API_test:TestSmFilletEdges", "SM_API_test:TestSmFilletEdgesPerEdge"]
status = "todo"
status = "partial"
claimed_by = ""
claimed_at = ""
notes = "6 fns; largest: SmFilletGeom::ReCalcFilletGeom, SmFilletGeom::TopologySplit, SmFilletEdge::CalcFilletIntFillet, SmFilletVertex::CalcMateGeom; cycle back-edges (call into later units; stub or co-port): blend.fillet_geom_p5(9), blend.fillet_geom_p2(7), blend.fillet_geom_p6(4), blend.fillet_geom_p4(2), blend.fillet_solver_p2(2), blend.fillet_corner_p2(1); unit-SCC #22 (220 units); 31 upstream test entry points reach this unit"
notes = "6 fns; largest: SmFilletGeom::ReCalcFilletGeom, SmFilletGeom::TopologySplit, SmFilletEdge::CalcFilletIntFillet, SmFilletVertex::CalcMateGeom; cycle back-edges (call into later units; stub or co-port): blend.fillet_geom_p5(9), blend.fillet_geom_p2(7), blend.fillet_geom_p6(4), blend.fillet_geom_p4(2), blend.fillet_solver_p2(2), blend.fillet_corner_p2(1); unit-SCC #22 (220 units); 31 upstream test entry points reach this unit; PR #242 partial: 325 cases (25 fixture in fixtures/blend.fillet_geom_p3/cases.json, 7 of them expected_rejection regressions; 300 generated: mates, on-vertex, fillet x fillet edges (with and without a 3D curve, crossing planes and patches), corner-edge dispatch (incl. CalcRailRailInterpolation), the part-2/4 vertex calculators through CalcCornerVertGeom and as a mate's main vertex, ReCalcFilletGeom with unset (-SM_BIG_DOUBLE) rail-end uvs), 275 passed, 43 deferred (intersect.surface_intersector_p4 31: DoPointIntersection on the SmFilletIntersector in ReCalcFilletGeom, a DISPATCH GAP: the base driver does not reach the fillet intersector's overrides and FilletSolver does not implement fillet_intersector::FilletSolverOps, the intersector's constructor is wired (#179); and the point-based intersection inside sm_SrfSrfIntersection of two B-spline fillet surfaces; blend.fillet_geom_p5 8: CalcCrossSection, the vertex CalcFilletIntFillet; blend.fillet_corner_p1 2: GetFilletEdges in CalcRailRailInterpolation with a corner; blend.fillet_corner_p2 2: RecordFilletError after a failed intersection), 7 rejected, 0 failed, max_rel_err=4.6e-15, no absolute floors; 2000-seed sweep 1750 pass / 268 deferred / 0 failed; --perturb SENSITIVE 275/275; --fuzz 3000 FUZZ-PASS. The stubs into blend.fillet_geom_p2 and p4 (#263) are wired: CalcRailIntRail, CalcRailIntEdgeuse, CalcVertOnCrossSection, CalcSetBackVert, CalcRailIntExtendedEdgeuse, CalcFilletInt2Fillets, CalcRailRailInterpolation and sm_SrfSrfIntersection (then SetCurve/SetUVCurve); the shim runs the vertex calculators on vertices without a corner, as #263's shims do. The edge's CalcFilletIntFillet is FilletBrep::calc_edge_fillet_int_fillet (the vertex one is blend.fillet_geom_p5's, #264). Carries #192 at 3fe34880. TopologySplit is kernel-tested only (upstream needs a real SmFilletExecutive). ReCalcFilletGeom never reaches success until the intersector dispatch gap is closed. Back-edge UNPORTED(blend.fillet_geom_p3) TopologySplit in blend/fillet_solver/p1.rs (split_fillet_geom_at_side_edge) is not wired: it sits behind an earlier blend.fillet_geom_p6 stub (GetRailIndexOfVertexuse) and uses the solver's opaque FilletGeomRef; wire it with the &mut FilletBrep threading that #192's findings describe for the blend.fillet_geom_p1 sites (and #303). Upstream issues UB-211 (CalcVertOnVert NULL/wrong-kind object), UB-185 (unbounded mate recursion; MAX_MATE_DEPTH = 64), UB-208 (ReCalcFilletGeom reads the first of no sorted points without a march direction), UB-210 (CalcFilletIntFillet reads the context of a missing corner); the shim refuses exactly those models.; UPDATE (PR #247): the part 5 calculators are wired in CalcCornerVertGeom / CalcCornerEdgeGeom (CalcFilletIntFillet, CalcCrossSection, CalcFilletIntSideFace, CalcEdgeOnExtendedEdge) and in the edge's CalcFilletIntFillet (now taking the target B-rep); ReCalcFilletGeom's CalcCrossSection stays UNPORTED(blend.fillet_geom_p5) (it needs a solver table ReCalcFilletGeom does not get; unreachable today). Re-run: 325 cases, 283 pass, 0 failed, 35 deferred, 7 rejected"
[[unit]]
id = "blend.fillet_geom_p4"
@@ -3538,7 +3538,7 @@ tests = ["SM_API_test:TestSmFilletPreview", "SM_API_test:TestSmSurfaceSurfaceFil
status = "partial"
claimed_by = ""
claimed_at = ""
notes = "PARTIAL (edited by PR #264 blend.fillet_geom_p5, difftest re-run with identical results: sm_CreateUVCurve's surface solves name the surface via UvSurfaceHandle; calc_fillet_int_2_fillets names its two curves via blend.fillet_geom_p1's CurveObjectHandle; srf_srf_intersection / SrfSrfCurves are pub(super)): difftest 398 cases (98 fixture incl. 42 review adversarial sm_CreateUVCurve cases and 6 MakeFaceBrep solver-tolerance cases, 300 generated), 362 pass, 0 failed, 36 deferred (blend.fillet_geom_p6 36: JoinTwoFilletVertsByCurves in MakeFaceBrep, SmFilletEdge::CalcBlendingRail, GetRailIndexOfVertexuse); sm_CreateUVCurve compared fully (bit-identical); sm_SrfSrfIntersection (blend.fillet_geom_p2, wired by that unit) in the vertex calculators and the plane branch of sm_CreateUVCurve are not reached by the shim's models; RegisterExistingSSI (ported in boolean.topology_intersector_p2) stays stubbed as UNPORTED(blend.fillet_executive_p1) because it runs on the executive's intersector; InsertIntersectionTopology unit tests only (needs the fillet executive, blend.fillet_executive_p1). Only sm_CreateUVCurve compares real values; MakeFaceBrep, CalcBlendingGeom and the three vertex calculators compare early exits only (everything after JoinTwoFilletVertsByCurves or sm_SrfSrfIntersection is deferred, and the p1 model cannot put an original face on a fillet edge, so CalcRailIntExtendedEdgeuse never passes NER(pSideFace)): the porter of blend.fillet_geom_p6 must extend this runner. Built on PR #147 (blend.fillet_geom_p1) and PR #192 (blend.fillet_solver_p1), both merged. Was: 8 fns; largest: sm_CreateUVCurve, SmFilletGeom::MakeFaceBrep, SmFilletGeom::InsertIntersectionTopology, SmFilletVertex::CalcRailIntExtendedEdgeuse; cycle back-edges (call into later units; stub or co-port): blend.fillet_geom_p2(4), blend.fillet_geom_p6(4); unit-SCC #22 (220 units); 31 upstream test entry points reach this unit"
notes = "PARTIAL (edited by PR #264 blend.fillet_geom_p5, difftest re-run with identical results: sm_CreateUVCurve's surface solves name the surface via UvSurfaceHandle; calc_fillet_int_2_fillets names its two curves via blend.fillet_geom_p1's CurveObjectHandle; srf_srf_intersection / SrfSrfCurves are pub(super)): difftest 398 cases (98 fixture incl. 42 review adversarial sm_CreateUVCurve cases and 6 MakeFaceBrep solver-tolerance cases, 300 generated), 362 pass, 0 failed, 36 deferred (blend.fillet_geom_p6 36: JoinTwoFilletVertsByCurves in MakeFaceBrep, SmFilletEdge::CalcBlendingRail, GetRailIndexOfVertexuse); sm_CreateUVCurve compared fully (bit-identical); sm_SrfSrfIntersection (blend.fillet_geom_p2, wired by that unit) in the vertex calculators and the plane branch of sm_CreateUVCurve are not reached by the shim's models; RegisterExistingSSI (ported in boolean.topology_intersector_p2) stays stubbed as UNPORTED(blend.fillet_executive_p1) because it runs on the executive's intersector; InsertIntersectionTopology unit tests only (needs the fillet executive, blend.fillet_executive_p1). Only sm_CreateUVCurve compares real values; MakeFaceBrep, CalcBlendingGeom and the three vertex calculators compare early exits only (everything after JoinTwoFilletVertsByCurves or sm_SrfSrfIntersection is deferred, and the p1 model cannot put an original face on a fillet edge, so CalcRailIntExtendedEdgeuse never passes NER(pSideFace)): the porter of blend.fillet_geom_p6 must extend this runner. Built on PR #147 (blend.fillet_geom_p1) and PR #192 (blend.fillet_solver_p1), both merged. Was: 8 fns; largest: sm_CreateUVCurve, SmFilletGeom::MakeFaceBrep, SmFilletGeom::InsertIntersectionTopology, SmFilletVertex::CalcRailIntExtendedEdgeuse; cycle back-edges (call into later units; stub or co-port): blend.fillet_geom_p2(4), blend.fillet_geom_p6(4); unit-SCC #22 (220 units); 31 upstream test entry points reach this unit; UPDATE (PR #247, blend.fillet_geom_p6): GetRailIndexOfVertexuse, JoinTwoFilletVertsByCurves, CalcBlendingRail and RecordFilletError wired to part 6 (MakeFaceBrep's 3D curve list is Option<Curve>, as the join appends). The runner's MakeFace B-rep now has the void infinite region SmBrep() makes (Brep::new_with_infinite_region; before, TrimSurfaceWithModelSpaceCurves stopped at NER(pRegion) where C++ goes on). Fix: CalcBlendingGeom reads m_vRails[i] as its loop reaches it (a NULL second rail after a failing first rail is SM_ERR, not SM_ERR_NULL_POINTER; 2 fixtures in review.json). Re-run: 400 cases, 382 pass, 0 failed, 18 deferred (topology.brep_p3 18); --seeds 2000: 2038 pass, 0 failed, 60 deferred; --perturb SENSITIVE 382/382"
[[unit]]
id = "blend.fillet_geom_p6"
@@ -3549,10 +3549,10 @@ soft_depends_on = ["blend.fillet_corner_p1", "blend.fillet_executive_p1", "blend
est_lines = 1813
layer = 60
tests = ["SM_API_test:TestSmFilletPreview", "SM_API_test:TestSmSurfaceSurfaceFillet", "SM_API_test:TestSmCircularFillet", "SM_API_test:TestSmChamferFillet", "SM_API_test:TestSmFilletEdges", "SM_API_test:TestSmFilletEdgesPerEdge"]
status = "todo"
status = "partial"
claimed_by = ""
claimed_at = ""
notes = "17 fns; largest: SmFilletGeom::TrimOriginalSurfaces, SmFilletEdge::CalcBlendingRail, SmFilletEdge::CalcSetBackRail, JoinTwoFilletVertsByCurves; unit-SCC #22 (220 units); 31 upstream test entry points reach this unit"
notes = "17 fns; largest: SmFilletGeom::TrimOriginalSurfaces, SmFilletEdge::CalcBlendingRail, SmFilletEdge::CalcSetBackRail, JoinTwoFilletVertsByCurves; unit-SCC #22 (220 units); 31 upstream test entry points reach this unit; PR #247 partial: 400 generated cases (edge on edge, fillet end, set-back rail, blending rail, ShouldSwapFaces, JoinTwoFilletVertsByCurves, SmFilletGeom::Copy, the rail lookups and SetUpRailPoint, sm_MoveTSP, RailEdgeuseIntersect, RecordFilletError), 304 pass bit-identical, 0 failed, 96 deferred (harness:analytic-geometry-export 61: ops evaluating cone/cylinder/torus/revolution faces or analytic edges the B-rep export carries as their B-spline part; topology.face_p4 11: sm_ClassifyFacePoint's face queries (the stub owner moved from topology.face_p3 when that unit landed); blend.fillet_executive_p1 9: NoteFilletError; blend.fillet_solver_p2 8: RailEdgeuseIntersect; intersect.surface_drop_lib_p3 7: DropCurve), max_rel_err=0, no absolute floors; --seeds 3000: 2396 pass, 604 deferred, 0 failed; --perturb SENSITIVE 304/304; --fuzz 2000 FUZZ-PASS. After topology.face_p4 (PR #239) landed: 315 pass, 0 failed, 85 deferred (its 11 sm_ClassifyFacePoint cases compare bit-identical); --perturb SENSITIVE 315/315. Built on #242 (blend.fillet_geom_p3, which merges #147 and #192; re-merged at eb6560b7). No co-ports: the corner members part 6 reads come from blend::fillet_corner::p3::FilletCorner (#256) and the solver lookup is blend::fillet_geom::p4::solver_of. TrimOriginalSurfaces is not compared against the C++, kernel-tested only (needs a fillet B-rep and executive topology-intersector mates the shim does not build); the curve-classification branch of ShouldSwapFaces is not generated. Wired UNPORTED(blend.fillet_geom_p6) in blend/fillet_geom/p3.rs (CalcEdgeOnEdge, CalcFilletEnd, CalcSetBackRail through CalcCornerEdgeGeom; sm_MoveTSP in ReCalcFilletGeom), p2.rs (GetRailIndexOfSideEU, RailEdgeuseIntersect, ShouldSwapFaces), p4.rs (GetRailIndexOfVertexuse, JoinTwoFilletVertsByCurves, CalcBlendingRail, RecordFilletError), fillet_corner/p3.rs (GetRailVertex) and fillet_executive/p3.rs (TrimOriginalSurfaces adapter); p4 deferred 36 -> 0 on part 6, corner_p3 33 -> 0. GetRailIndexOfVertexuse takes an Option and keeps upstream's NER(pFVU). Not wired: GetRailIndexOfVertexuse, GetRailVertex, SetUpRailPoint in blend/fillet_solver/p1.rs (split_fillet_geom_at_side_edge, split_fillet_geom): those take the solver's opaque FilletGeomRef and a plain pseudo Brep, not a FilletBrep, as for p3's TopologySplit. Found on the way: surface.surface_p2's sm_IntersectAndSplit passed no curve handle, so near-duplicate plane roots were not merged as upstream and LiftCurve split UV curves ~1e-10 off (findings.md); main's #444 fixed it the same way and this PR now uses main's fix. Moved onto #303 (blend.fillet_solver_p1 green: FilletGeomRef replaced by pseudo-B-rep FilletGeomIds; merge e67e390e): wired the solver's p6 calls (GetRailIndexOfVertexuse and GetRailVertex in SplitFilletGeomAtSideEdge, GetRailVertex/SetUpRailPoint in SplitFilletGeom) and SmFilletGeom::TopologySplit (blend.fillet_geom_p3); FilletBrep::rail_edgeuse_intersect drops its FilletGeomRef argument, re_calc_fillet_geom its geometry_id resolver. Numbers unchanged: 400 cases, 315 pass, 0 failed, 85 deferred; blend.fillet_corner_p3's geom_p6(33) deferrals now run (592 pass, 0 failed); adjust_tangent_corner's SplitFilletGeomAtSideEdge is wired through a new CornerEnv::solvers table; its one case (gen0067) is now rejected: the model's geometries have no solver and upstream dereferences NULL (shim refuses; corner_p3 592 pass, 357 deferred, 56 rejected). After merging main 5c3adfbe (#264 blend.fillet_geom_p5): 400 cases, 309 pass, 0 failed, 91 deferred; 6 generated cases (gen0044/0056/0093/0147/0281/0380) moved from pass to deferred at SmSurface::DropCurve (intersect.surface_drop_lib_p3), reached now that #264's p1/p2 solves name their objects as upstream does; TopologySplit's MakeRailEdgeuse/MakeFilletEdge call p5's (FilletBrep::make_rail_edgeuse_on)."
[[unit]]
id = "blend.fillet_standard_solver_p2"
@@ -3594,7 +3594,7 @@ tests = ["SM_API_test:TestSmFilletPreview", "SM_API_test:TestSmCircularFillet",
status = "partial"
claimed_by = ""
claimed_at = ""
notes = "14 fns; largest: SmFilletNx2Corner::AdjustTangentCorner, SmFilletCorner::Create, SmFilletCorner::CreateCornerPatch, SmFilletNx2Corner::AdjustTopology; cycle back-edges (call into later units; stub or co-port): blend.fillet_corner_p2(11), blend.fillet_geom_p2(1), blend.fillet_geom_p5(1), blend.fillet_geom_p6(1); unit-SCC #22 (220 units); 26 upstream test entry points reach this unit; PARTIAL (PR #256, built on PR #252 = #147 + #192): 1005 cases (308 upstream-file fixtures from TestFiles/pt_TestFiles/Fillet, 400 generated, 60 review cases with SmPlane user surfaces, 156 review cases with real SmLine 3x2 edge curves, 81 with real SmCircle 3x2 edge curves), 560 passed, 390 deferred (blend.fillet_corner_p1 111, blend.fillet_corner_p2 126, blend.fillet_executive_p1 6, blend.fillet_geom_p5 13, blend.fillet_geom_p6 33, intersect.surface_drop_lib_p3 78, surface.cone_p1 11, surface.surf_of_extrusion_p2 12), 55 rejected (40 of them review joins that succeed without an original face, where upstream would delete harness objects); every patch builder past its state checks is deferred (MakeAnalyticCornerSurface / CreateEndPatch / DropCurve), so no CreateCornerBlend default-patch values are compared; SmFilletSolver::SplitFilletGeomAtSideEdge (ported in blend.fillet_solver_p1 on FilletGeomRef) is stubbed in AdjustTangentCorner as UNPORTED(blend.fillet_geom_p6), its first blocker; the 3x2 curve join dispatches on the curve kind (SmLine/SmEllipse JoinWith overrides), as upstream's virtual JoinWith does; no CreateCornerPatch implementation compares surface output yet (corner_p1/p2 porters must extend the runner)"
notes = "14 fns; largest: SmFilletNx2Corner::AdjustTangentCorner, SmFilletCorner::Create, SmFilletCorner::CreateCornerPatch, SmFilletNx2Corner::AdjustTopology; cycle back-edges (call into later units; stub or co-port): blend.fillet_corner_p2(11), blend.fillet_geom_p2(1), blend.fillet_geom_p5(1), blend.fillet_geom_p6(1); unit-SCC #22 (220 units); 26 upstream test entry points reach this unit; PARTIAL (PR #256, built on PR #252 = #147 + #192): 1005 cases (308 upstream-file fixtures from TestFiles/pt_TestFiles/Fillet, 400 generated, 60 review cases with SmPlane user surfaces, 156 review cases with real SmLine 3x2 edge curves, 81 with real SmCircle 3x2 edge curves), 560 passed, 390 deferred (blend.fillet_corner_p1 111, blend.fillet_corner_p2 126, blend.fillet_executive_p1 6, blend.fillet_geom_p5 13, blend.fillet_geom_p6 33, intersect.surface_drop_lib_p3 78, surface.cone_p1 11, surface.surf_of_extrusion_p2 12), 55 rejected (40 of them review joins that succeed without an original face, where upstream would delete harness objects); every patch builder past its state checks is deferred (MakeAnalyticCornerSurface / CreateEndPatch / DropCurve), so no CreateCornerBlend default-patch values are compared; SmFilletSolver::SplitFilletGeomAtSideEdge (ported in blend.fillet_solver_p1 on FilletGeomRef) is stubbed in AdjustTangentCorner as UNPORTED(blend.fillet_geom_p6), its first blocker; the 3x2 curve join dispatches on the curve kind (SmLine/SmEllipse JoinWith overrides), as upstream's virtual JoinWith does; no CreateCornerPatch implementation compares surface output yet (corner_p1/p2 porters must extend the runner); UPDATE (PR #247, blend.fillet_geom_p6): GetRailIndexOfVertex reaches part 6 and AdjustTangentCorner's GetRailVertex / SetPointClass / GetCommonEdges after the split are ported up to CalcCornerVertGeom (UNPORTED(blend.fillet_corner_p1)); the SplitFilletGeomAtSideEdge stub is retagged UNPORTED(blend.fillet_solver_p1) (its port works on FilletGeomRef). The shim's tangent pre-check now mirrors the split loop (GetRailIndexOfVertex, DropPoint, IsValueOnBoundary) and stops only where a geometry would be split (refusing the uninitialised lRailIdx / bSuccess cases). Re-run: 1005 cases, 592 pass, 0 failed, 358 deferred (blend.fillet_solver_p1 1), 55 rejected; --perturb SENSITIVE 592/592"
[[unit]]
id = "blend.fillet_executive_p2"
@@ -3622,7 +3622,7 @@ tests = ["prog_test:my_test_surface_closest_point", "prog_test:my_test_drop_poin
status = "partial"
claimed_by = ""
claimed_at = ""
notes = "PARTIAL (PR #263): difftest 350 cases (50 fixture in fixtures/blend.fillet_geom_p2/cases.json, 300 generated at 1e-6..1e6 scales incl. coincident/parallel/disjoint-domain planes, tiny/negative tolerances, zero directions, reversed and coincident points, sub-domain rails), 219 pass bit-identical (max_rel_err 0, with_values 219, no_values 0; 125 compare a status-0 run: sm_SrfSrfIntersection plane/plane 55 and B-spline/B-spline 34, CalcRailRailInterpolation 24, UpdateSplitFace 12; 94 an upstream error status), 0 failed, 131 deferred (surface.surface_cache_p1 54, intersect.surface_drop_lib_p3 33, intersect.surface_intersector_p2 23, intersect.surface_intersector_p4 19, curve.crv_on_surf 2); --perturb SENSITIVE 219/219. The four vertex calculators (CalcRailIntRail, CalcRailIntEdgeuse, CalcVertOnCrossSection, CalcSetBackVert) are compared on their no-corner null-pointer exit only (35 cases): past it they read the corner, UNPORTED(blend.fillet_corner_p1); their bodies are ported and stop at stubs for blend.fillet_solver_p2 (RailRailIntersect, PointOnPlaneSolve), blend.fillet_geom_p6 (GetRailIndexOfSideEU, RailEdgeuseIntersect, ShouldSwapFaces), blend.fillet_executive_p2 (SmTemporaryRadiusChange::SetUp) and intersect.surface_drop_lib_p3 (SmSurface::DropCurve off planes). Built on PR #256 (blend.fillet_corner_p3, carrying #252, #147, #192); wires the sm_SrfSrfIntersection stubs of blend.fillet_geom_p4 and blend.fillet_solver_p1 (re-run: both partial, 0 failed). Merge fix: SmFilletGeom::MakeFaceBrep (p4) takes the NotifyEnv the face makers now need. No co-ports. Shim is compiled into blend_fillet_geom_p1.cpp's translation unit for the file-static helpers. Plan notes: 10 fns; largest: SmFilletVertex::CalcRailIntEdgeuse, SmFilletEdge::CalcRailRailInterpolation, sm_SrfSrfIntersection, SmFilletVertex::CalcVertOnCrossSection; unit-SCC #22 (220 units); 317 upstream test entry points reach this unit"
notes = "PARTIAL (PR #263): difftest 350 cases (50 fixture in fixtures/blend.fillet_geom_p2/cases.json, 300 generated at 1e-6..1e6 scales incl. coincident/parallel/disjoint-domain planes, tiny/negative tolerances, zero directions, reversed and coincident points, sub-domain rails), 219 pass bit-identical (max_rel_err 0, with_values 219, no_values 0; 125 compare a status-0 run: sm_SrfSrfIntersection plane/plane 55 and B-spline/B-spline 34, CalcRailRailInterpolation 24, UpdateSplitFace 12; 94 an upstream error status), 0 failed, 131 deferred (surface.surface_cache_p1 54, intersect.surface_drop_lib_p3 33, intersect.surface_intersector_p2 23, intersect.surface_intersector_p4 19, curve.crv_on_surf 2); --perturb SENSITIVE 219/219. The four vertex calculators (CalcRailIntRail, CalcRailIntEdgeuse, CalcVertOnCrossSection, CalcSetBackVert) are compared on their no-corner null-pointer exit only (35 cases): past it they read the corner, UNPORTED(blend.fillet_corner_p1); their bodies are ported and stop at stubs for blend.fillet_solver_p2 (RailRailIntersect, PointOnPlaneSolve), blend.fillet_geom_p6 (GetRailIndexOfSideEU, RailEdgeuseIntersect, ShouldSwapFaces), blend.fillet_executive_p2 (SmTemporaryRadiusChange::SetUp) and intersect.surface_drop_lib_p3 (SmSurface::DropCurve off planes). Built on PR #256 (blend.fillet_corner_p3, carrying #252, #147, #192); wires the sm_SrfSrfIntersection stubs of blend.fillet_geom_p4 and blend.fillet_solver_p1 (re-run: both partial, 0 failed). Merge fix: SmFilletGeom::MakeFaceBrep (p4) takes the NotifyEnv the face makers now need. No co-ports. Shim is compiled into blend_fillet_geom_p1.cpp's translation unit for the file-static helpers. Plan notes: 10 fns; largest: SmFilletVertex::CalcRailIntEdgeuse, SmFilletEdge::CalcRailRailInterpolation, sm_SrfSrfIntersection, SmFilletVertex::CalcVertOnCrossSection; unit-SCC #22 (220 units); 317 upstream test entry points reach this unit; UPDATE (PR #247, blend.fillet_geom_p6): GetRailIndexOfSideEU, RailEdgeuseIntersect (now stopping at SmFilletSolver::RailEdgeuseIntersect, blend.fillet_solver_p2) and ShouldSwapFaces wired; UpdateSplitFace takes the kernel, target and corner table. Re-run unchanged: 350 cases, 219 pass, 0 failed, 131 deferred"
[[unit]]
id = "blend.fillet_geom_p5"
@@ -3636,7 +3636,7 @@ tests = ["SM_API_test:TestSmFilletPreview", "SM_API_test:TestSmSurfaceSurfaceFil
status = "partial"
claimed_by = ""
claimed_at = ""
notes = "PARTIAL (PR #264, unstacked: #252 blend.fillet_geom_p4 is merged): all 13 fns ported (SmFilletGeom(SmFilletSolver*, type) was CO-PORTED in blend.fillet_geom_p1 as FilletGeom::new; the other 12 in blend::fillet_geom::p5 with UNPORTED stubs at each unported call: blend.fillet_corner_p1 (corner type/flags/filleted vertex/extended surfaces/tolerance), blend.fillet_corner_p2 (RecordFilletError), blend.fillet_geom_p6 (FindEdgeTangentAtPoint, Copy), blend.fillet_executive_p1 (GetPseudoBrep), blend.fillet_geom_p1 sm_ExamineNFixFilletGeoms (justified: findings.md 'blend.fillet_geom_p5: examine solvers'); the builders stop at FilletBrep::new_fillet_edgeuse's UNPORTED(topology.brep_p3) infinite region). blend.fillet_geom_p2 wired (sm_SrfSrfIntersection through p4's adapter, SmFilletEdge::UpdateSplitFace with its status ignored). The UNPORTED(blend.fillet_geom_p5) stubs in blend.fillet_solver_p1 / blend.fillet_corner_p3 stay (justified: findings.md 'blend.fillet_geom_p5: solver and corner callers'; follow-up for blend.fillet_solver_p1 / blend.fillet_corner_p3: address geometries in a FilletBrep). difftest 300 generated cases: 245 pass, 0 fail, 55 deferred (blend.fillet_geom_p6 37: 26 SplitFace cases at SmFilletEdge::ShouldSwapFaces, 11 SM_BT_BLEND end curves at FindEdgeTangentAtPoint; intersect.surface_intersector_p2 7 and _p4 1: the cliff-rail and side-face surface intersections of TrimRails; topology.brep_p3 10: SmBrep::SmBrep of 5 MakeFilletEdge and 5 MakeRailEdge), max_rel_err 0; --perturb SENSITIVE 245/245. Per op: IntFillet 3, CrossSection 54, SideFace 7, ExtEdge 4, TrimRails 140, SplitFace 32 (6 pass), MakeEdge 5, RailEdge 5, EndCurve 49 (11 BLEND, all deferred), RailEdgeuse 1. Paths (breakpoint count per case): 32 cases reach UpdateSplitFace, 26 of them ShouldSwapFaces (all deferred; the 6 that do not reach it compare original and split faces); 44 reach TrimCliffRails (36 pass, 8 defer at the intersection) and 7 of those CalcCliffRailIntEdge; 11 reach FindEdgeTangentAtPoint (SM_BT_BLEND, all deferred). Of the 245 passes 202 compare 3+ values (trim_rails, cross_section, split_face), 39 compare the status and one value and 4 only the status (CalcFilletIntFillet/CalcEdgeOnExtendedEdge stop at the first NER of their corner and the builders at their early exits, so the bodies of calc_fillet_int_fillet and calc_edge_on_extended_edge are compared by no case until blend.fillet_corner_p1 lands). Review rounds: the rail solves name the rail curve in their solutions (CurveObjectHandle), p4's sm_CreateUVCurve its surface (UvSurfaceHandle), p1's sm_TwoPntsIntersection / p2's CalcSetBackVert their surfaces (SurfaceHandle) and p4's CalcFilletInt2Fillets its curves, so SmGlobalSolver::IdenticalSolutions merges near-duplicates as upstream (findings.md 'rail trims and cross sections disagree in the tolerance-dominated regime (resolved)'); upstream-issues UB-213 (CalcCrossSection lifts *pUVCurve1) and UB-214 (CalcFilletIntSideFace pCorner) and UB-238 (MakeRailEdgeuse leaves a deleted vertex use linked; the RailEdgeuse op refuses that path). Shim: harness/cpp/shim/blend_fillet_geom_p5_static.inc, included by the p1 shim after p4's; p1's model gained pc_kind 4 (an SmEdge for SM_PC_EDGE). Plan notes: 13 fns; largest: SmFilletGeom::TrimCliffRails, SmFilletGeom::TrimRailCurves, SmFilletEdge::CalcCrossSection, SmFilletVertex::CalcFilletIntFillet; cycle back-edges (call into later units; stub or co-port): blend.fillet_corner_p2(1); unit-SCC #22 (220 units); 31 upstream test entry points reach this unit"
notes = "PARTIAL (PR #264, unstacked: #252 blend.fillet_geom_p4 is merged): all 13 fns ported (SmFilletGeom(SmFilletSolver*, type) was CO-PORTED in blend.fillet_geom_p1 as FilletGeom::new; the other 12 in blend::fillet_geom::p5 with UNPORTED stubs at each unported call: blend.fillet_corner_p1 (corner type/flags/filleted vertex/extended surfaces/tolerance), blend.fillet_corner_p2 (RecordFilletError), blend.fillet_geom_p6 (FindEdgeTangentAtPoint, Copy), blend.fillet_executive_p1 (GetPseudoBrep), blend.fillet_geom_p1 sm_ExamineNFixFilletGeoms (justified: findings.md 'blend.fillet_geom_p5: examine solvers'); the builders stop at FilletBrep::new_fillet_edgeuse's UNPORTED(topology.brep_p3) infinite region). blend.fillet_geom_p2 wired (sm_SrfSrfIntersection through p4's adapter, SmFilletEdge::UpdateSplitFace with its status ignored). The UNPORTED(blend.fillet_geom_p5) stubs in blend.fillet_solver_p1 / blend.fillet_corner_p3 stay (justified: findings.md 'blend.fillet_geom_p5: solver and corner callers'; follow-up for blend.fillet_solver_p1 / blend.fillet_corner_p3: address geometries in a FilletBrep). difftest 300 generated cases: 245 pass, 0 fail, 55 deferred (blend.fillet_geom_p6 37: 26 SplitFace cases at SmFilletEdge::ShouldSwapFaces, 11 SM_BT_BLEND end curves at FindEdgeTangentAtPoint; intersect.surface_intersector_p2 7 and _p4 1: the cliff-rail and side-face surface intersections of TrimRails; topology.brep_p3 10: SmBrep::SmBrep of 5 MakeFilletEdge and 5 MakeRailEdge), max_rel_err 0; --perturb SENSITIVE 245/245. Per op: IntFillet 3, CrossSection 54, SideFace 7, ExtEdge 4, TrimRails 140, SplitFace 32 (6 pass), MakeEdge 5, RailEdge 5, EndCurve 49 (11 BLEND, all deferred), RailEdgeuse 1. Paths (breakpoint count per case): 32 cases reach UpdateSplitFace, 26 of them ShouldSwapFaces (all deferred; the 6 that do not reach it compare original and split faces); 44 reach TrimCliffRails (36 pass, 8 defer at the intersection) and 7 of those CalcCliffRailIntEdge; 11 reach FindEdgeTangentAtPoint (SM_BT_BLEND, all deferred). Of the 245 passes 202 compare 3+ values (trim_rails, cross_section, split_face), 39 compare the status and one value and 4 only the status (CalcFilletIntFillet/CalcEdgeOnExtendedEdge stop at the first NER of their corner and the builders at their early exits, so the bodies of calc_fillet_int_fillet and calc_edge_on_extended_edge are compared by no case until blend.fillet_corner_p1 lands). Review rounds: the rail solves name the rail curve in their solutions (CurveObjectHandle), p4's sm_CreateUVCurve its surface (UvSurfaceHandle), p1's sm_TwoPntsIntersection / p2's CalcSetBackVert their surfaces (SurfaceHandle) and p4's CalcFilletInt2Fillets its curves, so SmGlobalSolver::IdenticalSolutions merges near-duplicates as upstream (findings.md 'rail trims and cross sections disagree in the tolerance-dominated regime (resolved)'); upstream-issues UB-213 (CalcCrossSection lifts *pUVCurve1) and UB-214 (CalcFilletIntSideFace pCorner) and UB-238 (MakeRailEdgeuse leaves a deleted vertex use linked; the RailEdgeuse op refuses that path). Shim: harness/cpp/shim/blend_fillet_geom_p5_static.inc, included by the p1 shim after p4's; p1's model gained pc_kind 4 (an SmEdge for SM_PC_EDGE). Plan notes: 13 fns; largest: SmFilletGeom::TrimCliffRails, SmFilletGeom::TrimRailCurves, SmFilletEdge::CalcCrossSection, SmFilletVertex::CalcFilletIntFillet; cycle back-edges (call into later units; stub or co-port): blend.fillet_corner_p2(1); unit-SCC #22 (220 units); 31 upstream test entry points reach this unit; UPDATE (PR #247, blend.fillet_geom_p6): FindEdgeTangentAtPoint (CreateEndCurve now takes the target B-rep) and SmFilletGeom::Copy wired to part 6. Re-run: 300 cases, 274 pass, 0 failed, 26 deferred (intersect.surface_intersector_p2 7, _p4 1, topology.brep_p3 18; none on blend.fillet_geom_p6)"
[[unit]]
id = "blend.fillet_corner_p2"
+47
View File
@@ -1762,6 +1762,7 @@ meantime, whoever merges renumbers the new entry and every reference to it (here
- **Confirmed:** yes.
- **Severity:** crash/UB, hang.
- **Rust port:** not reached yet (deferred); the difftest guard passes only radii in `[3e-4, 3e8]` on to the C++.
#### UB-191. `CombineCoincidentVertices` deletes its isolated surviving vertex when inputs alias
- **Location:** `source/SMLib/src/SmBrep.cpp:1354-1366`, `SmBrep::CombineCoincidentVertices`.
- **What happens:** The zero-incident-edge branch notifies, sets the surviving vertex's point/tolerance, then calls `DeleteVertex(pVertexToDelete)` and returns `SM_SUCCESS`. If both input pointers are the same, the supposedly surviving vertex is deleted too. Nothing inside the function touches it afterwards, so the call itself is defined; a caller that keeps using the survivor pointer reads freed memory.
@@ -1850,6 +1851,43 @@ meantime, whoever merges renumbers the new entry and every reference to it (here
- **Severity:** crash (use after free).
- **Rust port:** `PrimitiveCreationAccess::planar_face` takes the curves by value, so a failed cap frees nothing an edge uses. The `primitive.primitive_creation_p2` FFI refuses, with the start cap, curves that close a loop while an end meets no other end or more than one within `2 * SM_ZONE_TOL_3D` (repeats of another curve left out); it still refuses some that run natively (a tail ending inside the square, a square with a diagonal, two squares at a corner in another order).
#### UB-211. `SmFilletVertex::CalcVertOnVert` dereferences a missing or non-vertex classification object
- **Location:** `source/SMLib/src/SmFilletGeom.cpp:2337-2345` (`SmFilletVertex::CalcVertOnVert`), reached from `SmFilletVertex::CalcCornerVertGeom` (`:1434`) for an `SM_FV_ON_VERTEX` vertex.
- **What happens:** `(SmVertex*)GetPointClassObject()` is used without a check: a vertex whose point classification has no object dereferences `NULL` in `pVert->GetPoint()`, and an object of another kind (an edge use or face) is cast blindly and read as a vertex. There is no `NER` as in the sibling calculators.
- **Trigger:** `CalcCornerVertGeom()` on an `SM_FV_ON_VERTEX` fillet vertex whose classification object is `NULL` or not an `SmVertex`.
- **How found:** read during porting (`blend.fillet_geom_p3`, PR #242).
- **Confirmed:** no (the difftest shim refuses such models, `SC_ERR_ARG`).
- **Severity:** crash (NULL dereference) / UB (wrong-type cast).
- **Rust port:** `FilletBrep::calc_vert_on_vert` returns `SM_ERR_NULL_POINTER` without an object and `SolidError::InvalidInput` for an object that is not a vertex.
#### UB-185. `SmFilletVertex::CalcMateGeom` recurses without bound through a cycle of mates
- **Location:** `source/SMLib/src/SmFilletGeom.cpp:3480-3490` (`SmFilletVertex::CalcMateGeom`: `pMainFV->CalcCornerVertGeom()`), with `CalcCornerVertGeom` (`:1434`) calling `CalcMateGeom` for an `SM_FV_MATE` vertex.
- **What happens:** A mate computes its main vertex (`GetMate(0)`) first, unprocessed, through `CalcCornerVertGeom`; when the main vertex is itself an unprocessed mate whose `GetMate(0)` leads back (a cycle of `SM_FV_MATE` vertices, including a vertex that is its own mate), the two calls recurse until the stack overflows. Nothing marks a vertex as in progress.
- **Trigger:** two unprocessed `SM_FV_MATE` fillet vertices that are each other's `GetMate(0)`.
- **How found:** read during porting (`blend.fillet_geom_p3`, PR #242).
- **Confirmed:** no (the difftest shim refuses mate cycles, `SC_ERR_ARG`).
- **Severity:** crash (stack overflow).
- **Rust port:** the nesting is bounded by `blend::fillet_geom::p3::MAX_MATE_DEPTH` (64; upstream's corners nest it at most once); deeper nesting is `SolidError::InvalidInput`.
#### UB-208. `SmFilletGeom::ReCalcFilletGeom` reads the first of no sorted rail-end points
- **Location:** `source/SMLib/src/SmFilletGeom.cpp:11648` (`SmFilletGeom::ReCalcFilletGeom`: `sDir = sTsectPnts[0]->CrvDeriv();`), after the sort loop of `:11431-11507`.
- **What happens:** Without a march direction (`pOptMarchDir == NULL`) and with no curve on rail 0, the direction is taken from the first sorted rail-end point without checking that there is one. The sort loop skips every vertex that is unprocessed, `SM_FV_MATE` or `SM_FV_RAIL_X_EXTENDED_EDGEUSE`, and every point whose uv is unset (`-SM_BIG_DOUBLE`, `:11494` and `:11505`). When it skips them all, `sTsectPnts` is empty, so `sTsectPnts[0]` reads past the array (its stack buffer) and dereferences that pointer.
- **Trigger:** `ReCalcFilletGeom(FALSE, NULL, NULL)` on a geometry whose rails' end vertices are all unprocessed (or all mates), both rails present and rail 0 without a curve: the `recalc_setup_fails` fixture with every vertex `status` 0 and a solver whose `SetupOffsetValues` succeeds (`regress_recalc_no_dir_all_unprocessed`), or with every vertex a processed mate (`regress_recalc_no_dir_all_mates`).
- **How found:** reviewer's adversarial fixtures on PR #242 (core dump in the difftest process).
- **Confirmed:** yes (both regression fixtures segfault the reference build, SIGSEGV in the difftest process, when the shim's guard is disabled).
- **Severity:** crash (out-of-range read, then a wild pointer dereference).
- **Rust port:** unreachable today (`FilletBrep::re_calc_fillet_geom` stops at the fillet intersector's point intersection first); the march direction there takes the first point through `.first()` and returns `SolidError::InvalidInput` when there is none. The `blend.fillet_geom_p3` shim refuses (`SC_ERR_ARG`) exactly that model: no march direction, rail 0 without a curve, and a sort loop that would finish without a point.
#### UB-210. `SmFilletEdge::CalcFilletIntFillet` reads the context of a missing fillet corner
- **Location:** `source/SMLib/src/SmFilletGeom.cpp:6051` (`SmFilletEdge::CalcFilletIntFillet`, from `:5959`: `sm_SrfSrfIntersection(m_cpCorner->GetContext(), ...)`); the failure branch after it calls `pCorner->RecordFilletError` (`:6072`).
- **What happens:** When the fillet/fillet edge has no B-spline 3D curve yet, the surface/surface intersection is called with the context of the edge's own fillet corner, which is never checked. An edge without corner (`m_cpCorner == NULL`) dereferences `NULL`. Upstream's corners make these edges with a corner, so only a caller that builds or calls one without it reaches this.
- **Trigger:** `CalcFilletIntFillet(crContext, pCorner)` on an `SM_FE_FILLET_X_FILLET` edge without curve or corner, both end vertices processed and both geometries with a solver and a fillet surface: fixture `regress_fif_no_curve_no_corner` (generated seed 748 of the 2000-seed sweep).
- **How found:** generated difftest case (the 2000-seed sweep while addressing the PR #242 review); gdb puts the SIGSEGV in `SmFilletEdge::CalcFilletIntFillet`.
- **Confirmed:** yes (SIGSEGV in the reference build before the shim refused it).
- **Severity:** crash (NULL dereference).
- **Rust port:** `FilletBrep::calc_fillet_int_fillet` returns `SM_ERR_NULL_POINTER` for an edge without corner on that path, before the unported intersection. The `blend.fillet_geom_p3` shim refuses (`SC_ERR_ARG`) a fillet/fillet edge without corner when it has no B-spline curve.
#### UB-232. `SmApiCreatePipeSweep` raises `SIGFPE` for a path with a `1e300` control point
- **Location:** `source/SM_API/src/SmApiBrep.cpp:1376` `SmApiCreatePipeSweep` -> `SmPrimitiveCreation::CreatePipeSweep`. The faulting instruction was not located (an integer division or `INT_MIN / -1`).
- **What happens:** The process is killed by signal 8.
@@ -2696,6 +2734,15 @@ meantime, whoever merges renumbers the new entry and every reference to it (here
### Curves
#### WR-190. `SmFilletEdge::CalcBlendingRail` builds its Hermite fallback as a degree-2 curve and drops a control point
- **Location:** `source/SMLib/src/SmFilletGeom.cpp:6537-6565` (`SmFilletEdge::CalcBlendingRail`, the `dT1*dT2 < 0.0` branch: `SmBSplineCurve::CreateCanonical(crContext, lCurveDim, 2, sCntrlPoly, ..., sKnotMult = {4, 4}, ...)`).
- **What happens:** The four Bezier points of the cubic Hermite (`GetBezierPoints`) are passed with knot multiplicities 4 at both ends but degree 2. `CreateCanonical` accepts it and makes a quadratic of three points: start, the third Bezier point and end. The second point (start + start tangent / 3) is dropped, so the rail leaves its start in the wrong direction and is not tangent to the first rail. The same branch in `sm_InterpolateEndptsAndTangents` passes degree 3.
- **Trigger:** A blending rail whose end tangents' closest-approach parameters have opposite signs (`smgu_LineLineClosestPoint`: `dT1 * dT2 < 0`).
- **How found:** read during porting (`blend.fillet_geom_p6`, PR #247).
- **Confirmed:** yes: the difftest reaches the branch (83 of 3000 generated cases) and C++ and Rust agree bit for bit on the three-point quadratic.
- **Severity:** wrong result (tangency lost).
- **Rust port:** faithful (degree 2); `blend::fillet_geom::p6` tests pin the three-point result.
#### WR-25. `SmBSplineCurve` and `SmCurve` declare different default `bNonZeroTangents`
- **Location:** `source/SMLib/inc/SmBSplineCurve.h:481` (`bNonZeroTangents=FALSE`) vs `source/SMLib/inc/SmCurve.h:396` (`TRUE`); used in `SmBSplineCurve.cpp:2997` `SmBSplineCurve::Evaluate`.
- **What happens:** Default arguments bind by static type. The recursion inside `SmBSplineCurve::Evaluate` for a zero first derivative with one derivative asked runs with `FALSE` and returns the zero derivative, so the non-zero-tangent fix only works when 2+ derivatives are asked.
+464 -232
View File
@@ -8801,64 +8801,44 @@
"values_compared": 363
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_002",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#2",
"values_compared": 0
"values_compared": 363
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_003",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#3",
"values_compared": 0
"values_compared": 363
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_004",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#4",
"values_compared": 0
"values_compared": 363
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_005",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#5",
"values_compared": 0
"values_compared": 363
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_006",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#6",
"values_compared": 0
"values_compared": 363
},
{
"max_rel_err": 0.0,
@@ -8877,28 +8857,20 @@
"values_compared": 363
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_009",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#9",
"values_compared": 0
"values_compared": 363
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_010",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#10",
"values_compared": 0
"values_compared": 363
},
{
"max_rel_err": 0.0,
@@ -8909,16 +8881,12 @@
"values_compared": 363
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_012",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#12",
"values_compared": 0
"values_compared": 363
},
{
"max_rel_err": 0.0,
@@ -8993,16 +8961,12 @@
"values_compared": 0
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_021",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#21",
"values_compared": 0
"values_compared": 363
},
{
"deferred": {
@@ -9017,16 +8981,12 @@
"values_compared": 0
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_023",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#23",
"values_compared": 0
"values_compared": 363
},
{
"max_rel_err": 0.0,
@@ -9037,40 +8997,28 @@
"values_compared": 363
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_025",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#25",
"values_compared": 0
"values_compared": 363
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_026",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#26",
"values_compared": 0
"values_compared": 363
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_027",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#27",
"values_compared": 0
"values_compared": 363
},
{
"max_rel_err": 0.0,
@@ -9081,64 +9029,44 @@
"values_compared": 363
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_029",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#29",
"values_compared": 0
"values_compared": 363
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_030",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#30",
"values_compared": 0
"values_compared": 363
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_031",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#31",
"values_compared": 0
"values_compared": 363
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_032",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#32",
"values_compared": 0
"values_compared": 363
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_033",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#33",
"values_compared": 0
"values_compared": 363
},
{
"max_rel_err": 0.0,
@@ -9161,16 +9089,12 @@
"values_compared": 0
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_036",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#36",
"values_compared": 0
"values_compared": 363
},
{
"max_rel_err": 0.0,
@@ -9181,28 +9105,20 @@
"values_compared": 363
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_038",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#38",
"values_compared": 0
"values_compared": 363
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_039",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#39",
"values_compared": 0
"values_compared": 363
},
{
"max_rel_err": 0.0,
@@ -9213,52 +9129,36 @@
"values_compared": 363
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_041",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#41",
"values_compared": 0
"values_compared": 363
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_042",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#42",
"values_compared": 0
"values_compared": 363
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_043",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#43",
"values_compared": 0
"values_compared": 363
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_044",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#44",
"values_compared": 0
"values_compared": 363
},
{
"deferred": {
@@ -9273,16 +9173,12 @@
"values_compared": 0
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_046",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#46",
"values_compared": 0
"values_compared": 363
},
{
"max_rel_err": 0.0,
@@ -9321,52 +9217,36 @@
"values_compared": 363
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_051",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#51",
"values_compared": 0
"values_compared": 363
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_052",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#52",
"values_compared": 0
"values_compared": 363
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_053",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#53",
"values_compared": 0
"values_compared": 363
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_054",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#54",
"values_compared": 0
"values_compared": 363
},
{
"max_rel_err": 0.0,
@@ -9405,16 +9285,12 @@
"values_compared": 363
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "review_plane_oob_059",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_corner_p3/review_plane_oob.json#59",
"values_compared": 0
"values_compared": 363
},
{
"deferred": {
@@ -13249,14 +13125,375 @@
"values_compared": 259
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
"inputs": {
"case": "corner",
"corner": {
"adjusted": false,
"all": [
[0, 0],
[1, 0],
[2, 0],
[3, 0]
],
"arc_edges": [],
"bevel": false,
"blending": true,
"chamfer": false,
"degenerate": false,
"edges": [4, 5],
"ext": true,
"kind": 7,
"line_edges": [],
"orig_faces": [],
"setback": 0.0,
"solver": [
[1, 0],
[2, 1]
],
"tan": 0.1255646970612361,
"tol": 0.0,
"tsects": [
{
"e": 0,
"s0": 0,
"s1": 1,
"t": 1.0,
"uv0": [0.05383253179686409, 0.6267227754586333],
"uv1": [0.514913349745509, 0.7640998321185527],
"which": 0
},
{
"e": 1,
"s0": 0,
"s1": 1,
"t": 1.0000000005531793,
"uv0": [0.05383253179686409, 0.6267227754586333],
"uv1": [0.5699965191317435, 0.07420850864852074],
"which": 0
}
],
"v_edge": -1,
"v_sub": 0,
"verts": [0, 3, 4]
},
"model": {
"edges": [
{
"corner": -1,
"curve": {
"cpts": [
[0.0, 0.0, 0.01470696531058578, 1.0],
[0.1, 0.0, 0.01470696531058578, 1.0]
],
"dim": 3,
"p": 1,
"u": [0.0, 0.0, 1.0, 1.0]
},
"etype": 9,
"eus": [
{
"geom": 0,
"vu_uv": [0.0, 0.0]
},
{
"geom": 0,
"vu_uv": [0.0, 0.0]
}
],
"fbe": [-1, -1],
"status": 0,
"ve": 1,
"vs": 0
},
{
"corner": -1,
"curve": {
"cpts": [
[0.0, 0.0, 0.01470696531058578, 1.0],
[-0.1, 0.0, 0.01470696531058578, 1.0]
],
"dim": 3,
"p": 1,
"u": [0.0, 0.0, 1.0, 1.0]
},
"etype": 9,
"eus": [
{
"geom": 1,
"vu_uv": [0.0, 0.0]
},
{
"geom": 1,
"vu_uv": [0.0, 0.0]
}
],
"fbe": [-1, -1],
"status": 0,
"ve": 2,
"vs": 0
},
{
"corner": -1,
"curve": {
"cpts": [
[0.0, 0.01470696531058578, 0.0, 1.0],
[0.1, 0.01470696531058578, 0.0, 1.0]
],
"dim": 3,
"p": 1,
"u": [0.0, 0.0, 1.0, 1.0]
},
"etype": 9,
"eus": [
{
"geom": 0,
"vu_uv": [0.0, 0.0]
},
{
"geom": 0,
"vu_uv": [0.0, 0.0]
}
],
"fbe": [-1, -1],
"status": 0,
"ve": 1,
"vs": 3
},
{
"corner": -1,
"curve": {
"cpts": [
[0.0, -0.01470696531058578, 0.0, 1.0],
[-0.1, -0.01470696531058578, 0.0, 1.0]
],
"dim": 3,
"p": 1,
"u": [0.0, 0.0, 1.0, 1.0]
},
"etype": 9,
"eus": [
{
"geom": 1,
"vu_uv": [0.0, 0.0]
},
{
"geom": 1,
"vu_uv": [0.0, 0.0]
}
],
"fbe": [-1, -1],
"status": 0,
"ve": 2,
"vs": 4
},
{
"corner": -1,
"etype": 5,
"eus": [
{
"geom": -1,
"vu_uv": [0.0, 0.0]
},
{
"geom": -1,
"vu_uv": [0.0, 0.0]
}
],
"fbe": [-1, -1],
"status": 0,
"ve": 3,
"vs": 0
},
{
"corner": -1,
"etype": 4,
"eus": [
{
"geom": -1,
"vu_uv": [0.0, 0.0]
},
{
"geom": -1,
"vu_uv": [0.0, 0.0]
}
],
"fbe": [-1, -1],
"status": 0,
"ve": 4,
"vs": 3
}
],
"faces": [
{
"net": [
[
[0.0, 0.0, 0.0, 1.0],
[0.0, 0.0, 0.1, 1.0]
],
[
[0.1, 0.0, 0.0, 1.0],
[0.1, 0.0, 0.1, 1.0]
]
],
"p": 1,
"q": 1,
"u": [0.0, 0.0, 1.0, 1.0],
"v": [0.0, 0.0, 1.0, 1.0]
},
{
"net": [
[
[0.0, 0.0, 0.0, 1.0],
[0.0, 0.0, 0.1, 1.0]
],
[
[0.0, 0.1, 0.0, 1.0],
[0.0, 0.1, 0.1, 1.0]
]
],
"p": 1,
"q": 1,
"u": [0.0, 0.0, 1.0, 1.0],
"v": [0.0, 0.0, 1.0, 1.0]
}
],
"fillet_brep": [],
"geoms": [
{
"gtype": 0,
"other1": [],
"other2": [],
"rails": [0, 2],
"sides": [],
"status": 0
},
{
"gtype": 0,
"other1": [],
"other2": [],
"rails": [1, 3],
"sides": [],
"status": 0
}
],
"targets": [
{
"cpts": [
[0.0, 0.0, 0.0, 1.0],
[0.0, 0.0, 0.1, 1.0]
],
"dim": 3,
"p": 1,
"u": [0.0, 0.0, 1.0, 1.0]
},
{
"cpts": [
[0.0, 0.0, 0.0, 1.0],
[0.1, 0.0, 0.0, 1.0]
],
"dim": 3,
"p": 1,
"u": [0.0, 0.0, 1.0, 1.0]
},
{
"cpts": [
[0.0, 0.0, 0.0, 1.0],
[-0.1, -0.0, 0.0, 1.0]
],
"dim": 3,
"p": 1,
"u": [0.0, 0.0, 1.0, 1.0]
},
{
"cpts": [
[0.0, 0.0, 0.0, 1.0],
[0.0, -0.1, 0.0, 1.0]
],
"dim": 3,
"p": 1,
"u": [0.0, 0.0, 1.0, 1.0]
}
],
"vertices": [
{
"corner": -1,
"mates": [-1, -1],
"pc_class": 109,
"pc_face": -1,
"pc_index": 0,
"pc_kind": 2,
"pc_sub": 0,
"point": [0.0, 0.0, 0.01470696531058578],
"status": 0,
"t_param": 0.0,
"uv": [0.0, 0.0],
"vtype": 9
},
{
"corner": -1,
"mates": [-1, -1],
"pc_class": 100,
"pc_face": -1,
"pc_index": -1,
"pc_kind": 0,
"pc_sub": 0,
"point": [0.1, 0.0, 0.01470696531058578],
"status": 0,
"t_param": 0.0,
"uv": [0.0, 0.0],
"vtype": 7
},
{
"corner": -1,
"mates": [-1, -1],
"pc_class": 100,
"pc_face": -1,
"pc_index": -1,
"pc_kind": 0,
"pc_sub": 0,
"point": [-0.1, 0.0, 0.01470696531058578],
"status": 0,
"t_param": 0.0,
"uv": [0.0, 0.0],
"vtype": 7
},
{
"corner": -1,
"mates": [-1, -1],
"pc_class": 100,
"pc_face": -1,
"pc_index": -1,
"pc_kind": 0,
"pc_sub": 0,
"point": [0.0, 0.01470696531058578, 0.0],
"status": 0,
"t_param": 0.0,
"uv": [0.0, 0.0],
"vtype": 8
},
{
"corner": -1,
"mates": [-1, -1],
"pc_class": 100,
"pc_face": -1,
"pc_index": -1,
"pc_kind": 0,
"pc_sub": 0,
"point": [0.0, -0.01470696531058578, 0.0],
"status": 0,
"t_param": 0.0,
"uv": [0.0, 0.0],
"vtype": 13
}
]
},
"op": "adjust_tangent"
},
"max_rel_err": 0.0,
"name": "gen0067",
"outcome": "deferred",
"outcome": "rejected",
"pass": false,
"rejected": "upstream would dereference null",
"source": "generated:seed=10924742201818549685",
"values_compared": 0
},
@@ -15445,16 +15682,12 @@
"values_compared": 0
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0232",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=10924742201818549850",
"values_compared": 0
"values_compared": 361
},
{
"max_rel_err": 0.0,
@@ -18519,17 +18752,16 @@
}
],
"cases_both_errored": 0,
"cases_deferred": 390,
"cases_deferred": 357,
"cases_failed": 0,
"cases_passed": 560,
"cases_rejected": 55,
"cases_passed": 592,
"cases_rejected": 56,
"cases_total": 1005,
"deferred_by_unit": {
"blend.fillet_corner_p1": 111,
"blend.fillet_corner_p2": 126,
"blend.fillet_executive_p1": 6,
"blend.fillet_geom_p5": 13,
"blend.fillet_geom_p6": 33,
"intersect.surface_drop_lib_p3": 78,
"surface.cone_p1": 11,
"surface.surf_of_extrusion_p2": 12
File diff suppressed because it is too large Load Diff
+110 -166
View File
@@ -355,8 +355,8 @@
},
{
"deferred": {
"symbol": "JoinTwoFilletVertsByCurves",
"unit": "blend.fillet_geom_p6"
"symbol": "SmBrep::MakeWireEdgeVertex",
"unit": "topology.brep_p3"
},
"max_rel_err": 0.0,
"name": "make_face_join",
@@ -367,8 +367,8 @@
},
{
"deferred": {
"symbol": "JoinTwoFilletVertsByCurves",
"unit": "blend.fillet_geom_p6"
"symbol": "SmBrep::MakeWireEdgeVertex",
"unit": "topology.brep_p3"
},
"max_rel_err": 0.0,
"name": "make_face_cliff_join",
@@ -386,16 +386,12 @@
"values_compared": 1
},
{
"deferred": {
"symbol": "SmFilletEdge::CalcBlendingRail",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "blending_rail",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_geom_p4/cases.json#47 (upstream source/SMLib/src/SmFilletGeom.cpp)",
"values_compared": 0
"values_compared": 1
},
{
"max_rel_err": 0.0,
@@ -406,16 +402,12 @@
"values_compared": 2
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "rail_index_vertex",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_geom_p4/cases.json#49 (upstream source/SMLib/src/SmFilletGeom.cpp)",
"values_compared": 0
"values_compared": 2
},
{
"max_rel_err": 0.0,
@@ -755,8 +747,8 @@
},
{
"deferred": {
"symbol": "JoinTwoFilletVertsByCurves",
"unit": "blend.fillet_geom_p6"
"symbol": "SmBrep::MakeWireEdgeVertex",
"unit": "topology.brep_p3"
},
"max_rel_err": 0.0,
"name": "make_face_join_negtol",
@@ -767,8 +759,8 @@
},
{
"deferred": {
"symbol": "JoinTwoFilletVertsByCurves",
"unit": "blend.fillet_geom_p6"
"symbol": "SmBrep::MakeWireEdgeVertex",
"unit": "topology.brep_p3"
},
"max_rel_err": 0.0,
"name": "make_face_join_negtol_big",
@@ -779,8 +771,8 @@
},
{
"deferred": {
"symbol": "JoinTwoFilletVertsByCurves",
"unit": "blend.fillet_geom_p6"
"symbol": "SmBrep::MakeWireEdgeVertex",
"unit": "topology.brep_p3"
},
"max_rel_err": 0.0,
"name": "make_face_join_usedefault",
@@ -791,8 +783,8 @@
},
{
"deferred": {
"symbol": "JoinTwoFilletVertsByCurves",
"unit": "blend.fillet_geom_p6"
"symbol": "SmBrep::MakeWireEdgeVertex",
"unit": "topology.brep_p3"
},
"max_rel_err": 0.0,
"name": "make_face_cliff_join_negtol",
@@ -803,8 +795,8 @@
},
{
"deferred": {
"symbol": "JoinTwoFilletVertsByCurves",
"unit": "blend.fillet_geom_p6"
"symbol": "SmBrep::MakeWireEdgeVertex",
"unit": "topology.brep_p3"
},
"max_rel_err": 0.0,
"name": "make_face_cliff_join_negtol_big",
@@ -815,8 +807,8 @@
},
{
"deferred": {
"symbol": "JoinTwoFilletVertsByCurves",
"unit": "blend.fillet_geom_p6"
"symbol": "SmBrep::MakeWireEdgeVertex",
"unit": "topology.brep_p3"
},
"max_rel_err": 0.0,
"name": "make_face_cliff_join_usedefault",
@@ -825,6 +817,22 @@
"source": "fixture:fixtures/blend.fillet_geom_p4/review.json#27 (upstream source/SMLib/src/SmFilletGeom.cpp)",
"values_compared": 0
},
{
"max_rel_err": 0.0,
"name": "blending_missing_second_rail_g0",
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_geom_p4/review.json#28 (upstream source/SMLib/src/SmFilletGeom.cpp)",
"values_compared": 1
},
{
"max_rel_err": 0.0,
"name": "blending_missing_second_rail_g1",
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_geom_p4/review.json#29 (upstream source/SMLib/src/SmFilletGeom.cpp)",
"values_compared": 1
},
{
"max_rel_err": 0.0,
"name": "gen0000",
@@ -915,8 +923,8 @@
},
{
"deferred": {
"symbol": "JoinTwoFilletVertsByCurves",
"unit": "blend.fillet_geom_p6"
"symbol": "SmBrep::MakeWireEdgeVertex",
"unit": "topology.brep_p3"
},
"max_rel_err": 0.0,
"name": "gen0011",
@@ -1078,16 +1086,12 @@
"values_compared": 21
},
{
"deferred": {
"symbol": "SmFilletEdge::CalcBlendingRail",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0031",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207371918134190359",
"values_compared": 0
"values_compared": 1
},
{
"max_rel_err": 0.0,
@@ -1234,16 +1238,12 @@
"values_compared": 21
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0050",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207371918134190378",
"values_compared": 0
"values_compared": 2
},
{
"max_rel_err": 0.0,
@@ -1295,8 +1295,8 @@
},
{
"deferred": {
"symbol": "JoinTwoFilletVertsByCurves",
"unit": "blend.fillet_geom_p6"
"symbol": "SmBrep::MakeWireEdgeVertex",
"unit": "topology.brep_p3"
},
"max_rel_err": 0.0,
"name": "gen0057",
@@ -1394,16 +1394,12 @@
"values_compared": 2
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0069",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207371918134190397",
"values_compared": 0
"values_compared": 2
},
{
"max_rel_err": 0.0,
@@ -1446,16 +1442,12 @@
"values_compared": 21
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0075",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207371918134190403",
"values_compared": 0
"values_compared": 2
},
{
"max_rel_err": 0.0,
@@ -1690,28 +1682,20 @@
"values_compared": 1
},
{
"deferred": {
"symbol": "SmFilletEdge::CalcBlendingRail",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0105",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207371918134190433",
"values_compared": 0
"values_compared": 1
},
{
"deferred": {
"symbol": "SmFilletEdge::CalcBlendingRail",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0106",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207371918134190434",
"values_compared": 0
"values_compared": 1
},
{
"max_rel_err": 0.0,
@@ -1795,8 +1779,8 @@
},
{
"deferred": {
"symbol": "JoinTwoFilletVertsByCurves",
"unit": "blend.fillet_geom_p6"
"symbol": "SmBrep::MakeWireEdgeVertex",
"unit": "topology.brep_p3"
},
"max_rel_err": 0.0,
"name": "gen0117",
@@ -1838,16 +1822,12 @@
"values_compared": 5
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0122",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207371918134190450",
"values_compared": 0
"values_compared": 2
},
{
"max_rel_err": 0.0,
@@ -1882,16 +1862,12 @@
"values_compared": 21
},
{
"deferred": {
"symbol": "SmFilletEdge::CalcBlendingRail",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0127",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207371918134190455",
"values_compared": 0
"values_compared": 1
},
{
"max_rel_err": 0.0,
@@ -2150,16 +2126,12 @@
"values_compared": 5
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0160",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207371918134190488",
"values_compared": 0
"values_compared": 2
},
{
"max_rel_err": 0.0,
@@ -2179,8 +2151,8 @@
},
{
"deferred": {
"symbol": "JoinTwoFilletVertsByCurves",
"unit": "blend.fillet_geom_p6"
"symbol": "SmBrep::MakeWireEdgeVertex",
"unit": "topology.brep_p3"
},
"max_rel_err": 0.0,
"name": "gen0163",
@@ -2198,16 +2170,12 @@
"values_compared": 5
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0165",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207371918134190493",
"values_compared": 0
"values_compared": 2
},
{
"max_rel_err": 0.0,
@@ -2267,8 +2235,8 @@
},
{
"deferred": {
"symbol": "JoinTwoFilletVertsByCurves",
"unit": "blend.fillet_geom_p6"
"symbol": "SmBrep::MakeWireEdgeVertex",
"unit": "topology.brep_p3"
},
"max_rel_err": 0.0,
"name": "gen0173",
@@ -2326,16 +2294,12 @@
"values_compared": 21
},
{
"deferred": {
"symbol": "SmFilletEdge::CalcBlendingRail",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0180",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207371918134190508",
"values_compared": 0
"values_compared": 1
},
{
"max_rel_err": 0.0,
@@ -2394,21 +2358,17 @@
"values_compared": 21
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0188",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207371918134190516",
"values_compared": 0
"values_compared": 2
},
{
"deferred": {
"symbol": "JoinTwoFilletVertsByCurves",
"unit": "blend.fillet_geom_p6"
"symbol": "SmBrep::MakeWireEdgeVertex",
"unit": "topology.brep_p3"
},
"max_rel_err": 0.0,
"name": "gen0189",
@@ -2451,8 +2411,8 @@
},
{
"deferred": {
"symbol": "JoinTwoFilletVertsByCurves",
"unit": "blend.fillet_geom_p6"
"symbol": "SmBrep::MakeWireEdgeVertex",
"unit": "topology.brep_p3"
},
"max_rel_err": 0.0,
"name": "gen0194",
@@ -2494,16 +2454,12 @@
"values_compared": 5
},
{
"deferred": {
"symbol": "JoinTwoFilletVertsByCurves",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0199",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207371918134190527",
"values_compared": 0
"values_compared": 1
},
{
"max_rel_err": 0.0,
@@ -2666,16 +2622,12 @@
"values_compared": 21
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0220",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207371918134190548",
"values_compared": 0
"values_compared": 2
},
{
"max_rel_err": 0.0,
@@ -2846,16 +2798,12 @@
"values_compared": 5
},
{
"deferred": {
"symbol": "SmFilletGeom::GetRailIndexOfVertexuse",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0242",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207371918134190570",
"values_compared": 0
"values_compared": 2
},
{
"max_rel_err": 0.0,
@@ -2931,8 +2879,8 @@
},
{
"deferred": {
"symbol": "JoinTwoFilletVertsByCurves",
"unit": "blend.fillet_geom_p6"
"symbol": "SmBrep::MakeWireEdgeVertex",
"unit": "topology.brep_p3"
},
"max_rel_err": 0.0,
"name": "gen0252",
@@ -3199,8 +3147,8 @@
},
{
"deferred": {
"symbol": "JoinTwoFilletVertsByCurves",
"unit": "blend.fillet_geom_p6"
"symbol": "SmBrep::MakeWireEdgeVertex",
"unit": "topology.brep_p3"
},
"max_rel_err": 0.0,
"name": "gen0285",
@@ -3259,8 +3207,8 @@
},
{
"deferred": {
"symbol": "JoinTwoFilletVertsByCurves",
"unit": "blend.fillet_geom_p6"
"symbol": "SmBrep::MakeWireEdgeVertex",
"unit": "topology.brep_p3"
},
"max_rel_err": 0.0,
"name": "gen0292",
@@ -3270,16 +3218,12 @@
"values_compared": 0
},
{
"deferred": {
"symbol": "SmFilletEdge::CalcBlendingRail",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0293",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207371918134190621",
"values_compared": 0
"values_compared": 1
},
{
"max_rel_err": 0.0,
@@ -3331,12 +3275,12 @@
}
],
"cases_both_errored": 0,
"cases_deferred": 36,
"cases_deferred": 18,
"cases_failed": 0,
"cases_passed": 362,
"cases_total": 398,
"cases_passed": 382,
"cases_total": 400,
"deferred_by_unit": {
"blend.fillet_geom_p6": 36
"topology.brep_p3": 18
},
"max_rel_err": 0.0,
"pass": true,
+106 -223
View File
@@ -163,8 +163,8 @@
},
{
"deferred": {
"symbol": "SmFilletGeom::FindEdgeTangentAtPoint",
"unit": "blend.fillet_geom_p6"
"symbol": "SmBrep::SmBrep",
"unit": "topology.brep_p3"
},
"max_rel_err": 0.0,
"name": "gen0020",
@@ -387,8 +387,8 @@
},
{
"deferred": {
"symbol": "SmFilletGeom::FindEdgeTangentAtPoint",
"unit": "blend.fillet_geom_p6"
"symbol": "SmBrep::SmBrep",
"unit": "topology.brep_p3"
},
"max_rel_err": 0.0,
"name": "gen0047",
@@ -438,16 +438,12 @@
"values_compared": 79
},
{
"deferred": {
"symbol": "SmFilletGeom::FindEdgeTangentAtPoint",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0053",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207373017645818592",
"values_compared": 0
"values_compared": 2
},
{
"max_rel_err": 0.0,
@@ -530,16 +526,12 @@
"values_compared": 301
},
{
"deferred": {
"symbol": "SmFilletEdge::ShouldSwapFaces",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0064",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207373017645818603",
"values_compared": 0
"values_compared": 6
},
{
"deferred": {
@@ -734,16 +726,12 @@
"values_compared": 135
},
{
"deferred": {
"symbol": "SmFilletEdge::ShouldSwapFaces",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0087",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207373017645818626",
"values_compared": 0
"values_compared": 6
},
{
"max_rel_err": 0.0,
@@ -878,16 +866,12 @@
"values_compared": 271
},
{
"deferred": {
"symbol": "SmFilletGeom::FindEdgeTangentAtPoint",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0104",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207373017645818643",
"values_compared": 0
"values_compared": 2
},
{
"max_rel_err": 0.0,
@@ -978,16 +962,12 @@
"values_compared": 381
},
{
"deferred": {
"symbol": "SmFilletEdge::ShouldSwapFaces",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0116",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207373017645818655",
"values_compared": 0
"values_compared": 6
},
{
"max_rel_err": 0.0,
@@ -1006,16 +986,12 @@
"values_compared": 171
},
{
"deferred": {
"symbol": "SmFilletEdge::ShouldSwapFaces",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0119",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207373017645818658",
"values_compared": 0
"values_compared": 9
},
{
"max_rel_err": 0.0,
@@ -1042,16 +1018,12 @@
"values_compared": 54
},
{
"deferred": {
"symbol": "SmFilletEdge::ShouldSwapFaces",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0123",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207373017645818662",
"values_compared": 0
"values_compared": 16
},
{
"max_rel_err": 0.0,
@@ -1183,8 +1155,8 @@
},
{
"deferred": {
"symbol": "SmFilletGeom::FindEdgeTangentAtPoint",
"unit": "blend.fillet_geom_p6"
"symbol": "SmBrep::SmBrep",
"unit": "topology.brep_p3"
},
"max_rel_err": 0.0,
"name": "gen0139",
@@ -1203,8 +1175,8 @@
},
{
"deferred": {
"symbol": "SmFilletGeom::FindEdgeTangentAtPoint",
"unit": "blend.fillet_geom_p6"
"symbol": "SmBrep::SmBrep",
"unit": "topology.brep_p3"
},
"max_rel_err": 0.0,
"name": "gen0141",
@@ -1258,28 +1230,20 @@
"values_compared": 64
},
{
"deferred": {
"symbol": "SmFilletEdge::ShouldSwapFaces",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0147",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207373017645818686",
"values_compared": 0
"values_compared": 7
},
{
"deferred": {
"symbol": "SmFilletEdge::ShouldSwapFaces",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0148",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207373017645818687",
"values_compared": 0
"values_compared": 11
},
{
"deferred": {
@@ -1342,16 +1306,12 @@
"values_compared": 76
},
{
"deferred": {
"symbol": "SmFilletGeom::FindEdgeTangentAtPoint",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0156",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207373017645818695",
"values_compared": 0
"values_compared": 2
},
{
"max_rel_err": 0.0,
@@ -1402,21 +1362,17 @@
"values_compared": 82
},
{
"deferred": {
"symbol": "SmFilletEdge::ShouldSwapFaces",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0163",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207373017645818702",
"values_compared": 0
"values_compared": 7
},
{
"deferred": {
"symbol": "SmFilletGeom::FindEdgeTangentAtPoint",
"unit": "blend.fillet_geom_p6"
"symbol": "SmBrep::SmBrep",
"unit": "topology.brep_p3"
},
"max_rel_err": 0.0,
"name": "gen0164",
@@ -1467,8 +1423,8 @@
},
{
"deferred": {
"symbol": "SmFilletGeom::FindEdgeTangentAtPoint",
"unit": "blend.fillet_geom_p6"
"symbol": "SmBrep::SmBrep",
"unit": "topology.brep_p3"
},
"max_rel_err": 0.0,
"name": "gen0169",
@@ -1523,8 +1479,8 @@
},
{
"deferred": {
"symbol": "SmFilletGeom::FindEdgeTangentAtPoint",
"unit": "blend.fillet_geom_p6"
"symbol": "SmBrep::SmBrep",
"unit": "topology.brep_p3"
},
"max_rel_err": 0.0,
"name": "gen0175",
@@ -1598,16 +1554,12 @@
"values_compared": 54
},
{
"deferred": {
"symbol": "SmFilletEdge::ShouldSwapFaces",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0184",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207373017645818723",
"values_compared": 0
"values_compared": 10
},
{
"max_rel_err": 0.0,
@@ -1618,16 +1570,12 @@
"values_compared": 2
},
{
"deferred": {
"symbol": "SmFilletEdge::ShouldSwapFaces",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0186",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207373017645818725",
"values_compared": 0
"values_compared": 16
},
{
"max_rel_err": 0.0,
@@ -1638,16 +1586,12 @@
"values_compared": 298
},
{
"deferred": {
"symbol": "SmFilletEdge::ShouldSwapFaces",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0188",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207373017645818727",
"values_compared": 0
"values_compared": 12
},
{
"max_rel_err": 0.0,
@@ -1746,16 +1690,12 @@
"values_compared": 66
},
{
"deferred": {
"symbol": "SmFilletEdge::ShouldSwapFaces",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0201",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207373017645818740",
"values_compared": 0
"values_compared": 11
},
{
"max_rel_err": 0.0,
@@ -1766,16 +1706,12 @@
"values_compared": 2134
},
{
"deferred": {
"symbol": "SmFilletEdge::ShouldSwapFaces",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0203",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207373017645818742",
"values_compared": 0
"values_compared": 6
},
{
"max_rel_err": 0.0,
@@ -1794,16 +1730,12 @@
"values_compared": 2
},
{
"deferred": {
"symbol": "SmFilletEdge::ShouldSwapFaces",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0206",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207373017645818745",
"values_compared": 0
"values_compared": 10
},
{
"max_rel_err": 0.0,
@@ -1922,16 +1854,12 @@
"values_compared": 5
},
{
"deferred": {
"symbol": "SmFilletEdge::ShouldSwapFaces",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0221",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207373017645818760",
"values_compared": 0
"values_compared": 8
},
{
"max_rel_err": 0.0,
@@ -2014,16 +1942,12 @@
"values_compared": 516
},
{
"deferred": {
"symbol": "SmFilletEdge::ShouldSwapFaces",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0232",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207373017645818771",
"values_compared": 0
"values_compared": 6
},
{
"max_rel_err": 0.0,
@@ -2042,21 +1966,17 @@
"values_compared": 125
},
{
"deferred": {
"symbol": "SmFilletEdge::ShouldSwapFaces",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0235",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207373017645818774",
"values_compared": 0
"values_compared": 6
},
{
"deferred": {
"symbol": "SmFilletGeom::FindEdgeTangentAtPoint",
"unit": "blend.fillet_geom_p6"
"symbol": "SmBrep::SmBrep",
"unit": "topology.brep_p3"
},
"max_rel_err": 0.0,
"name": "gen0236",
@@ -2138,16 +2058,12 @@
"values_compared": 121
},
{
"deferred": {
"symbol": "SmFilletEdge::ShouldSwapFaces",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0246",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207373017645818785",
"values_compared": 0
"values_compared": 6
},
{
"max_rel_err": 0.0,
@@ -2174,28 +2090,20 @@
"values_compared": 12949
},
{
"deferred": {
"symbol": "SmFilletEdge::ShouldSwapFaces",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0250",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207373017645818789",
"values_compared": 0
"values_compared": 7
},
{
"deferred": {
"symbol": "SmFilletEdge::ShouldSwapFaces",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0251",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207373017645818790",
"values_compared": 0
"values_compared": 7
},
{
"max_rel_err": 0.0,
@@ -2214,16 +2122,12 @@
"values_compared": 2
},
{
"deferred": {
"symbol": "SmFilletEdge::ShouldSwapFaces",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0254",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207373017645818793",
"values_compared": 0
"values_compared": 7
},
{
"max_rel_err": 0.0,
@@ -2298,16 +2202,12 @@
"values_compared": 2
},
{
"deferred": {
"symbol": "SmFilletEdge::ShouldSwapFaces",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0264",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207373017645818803",
"values_compared": 0
"values_compared": 6
},
{
"max_rel_err": 0.0,
@@ -2466,28 +2366,20 @@
"values_compared": 0
},
{
"deferred": {
"symbol": "SmFilletEdge::ShouldSwapFaces",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0283",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207373017645818822",
"values_compared": 0
"values_compared": 14
},
{
"deferred": {
"symbol": "SmFilletEdge::ShouldSwapFaces",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0284",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207373017645818823",
"values_compared": 0
"values_compared": 6
},
{
"max_rel_err": 0.0,
@@ -2514,16 +2406,12 @@
"values_compared": 171
},
{
"deferred": {
"symbol": "SmFilletEdge::ShouldSwapFaces",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0288",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207373017645818827",
"values_compared": 0
"values_compared": 11
},
{
"deferred": {
@@ -2578,16 +2466,12 @@
"values_compared": 1
},
{
"deferred": {
"symbol": "SmFilletEdge::ShouldSwapFaces",
"unit": "blend.fillet_geom_p6"
},
"max_rel_err": 0.0,
"name": "gen0295",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=9207373017645818834",
"values_compared": 0
"values_compared": 13
},
{
"max_rel_err": 0.0,
@@ -2623,15 +2507,14 @@
}
],
"cases_both_errored": 0,
"cases_deferred": 55,
"cases_deferred": 26,
"cases_failed": 0,
"cases_passed": 245,
"cases_passed": 274,
"cases_total": 300,
"deferred_by_unit": {
"blend.fillet_geom_p6": 37,
"intersect.surface_intersector_p2": 7,
"intersect.surface_intersector_p4": 1,
"topology.brep_p3": 10
"topology.brep_p3": 18
},
"max_rel_err": 0.0,
"pass": true,
File diff suppressed because it is too large Load Diff
+40 -8
View File
@@ -209,6 +209,38 @@
"source": "fixture:fixtures/blend.fillet_solver_p1/cases.json#25",
"values_compared": 4
},
{
"max_rel_err": 0.0,
"name": "fsg_linear_two_line_rails",
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_solver_p1/cases.json#26",
"values_compared": 39
},
{
"max_rel_err": 0.0,
"name": "fsg_linear_quadratic_and_line_rails",
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_solver_p1/cases.json#27",
"values_compared": 49
},
{
"max_rel_err": 0.0,
"name": "fsg_linear_rational_quarter_arc_rails",
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_solver_p1/cases.json#28",
"values_compared": 49
},
{
"max_rel_err": 0.0,
"name": "fsg_linear_without_rails",
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/blend.fillet_solver_p1/cases.json#29",
"values_compared": 8
},
{
"max_rel_err": 0.0,
"name": "gen0000",
@@ -887,7 +919,7 @@
"outcome": "pass",
"pass": true,
"source": "generated:seed=2561849588495854298",
"values_compared": 8
"values_compared": 76
},
{
"max_rel_err": 0.0,
@@ -1359,7 +1391,7 @@
"outcome": "pass",
"pass": true,
"source": "generated:seed=2561849588495854357",
"values_compared": 8
"values_compared": 58
},
{
"max_rel_err": 0.0,
@@ -1407,7 +1439,7 @@
"outcome": "pass",
"pass": true,
"source": "generated:seed=2561849588495854363",
"values_compared": 8
"values_compared": 76
},
{
"max_rel_err": 0.0,
@@ -1839,7 +1871,7 @@
"outcome": "pass",
"pass": true,
"source": "generated:seed=2561849588495854417",
"values_compared": 8
"values_compared": 66
},
{
"max_rel_err": 0.0,
@@ -2295,7 +2327,7 @@
"outcome": "pass",
"pass": true,
"source": "generated:seed=2561849588495854474",
"values_compared": 8
"values_compared": 58
},
{
"max_rel_err": 0.0,
@@ -2439,7 +2471,7 @@
"outcome": "pass",
"pass": true,
"source": "generated:seed=2561849588495854492",
"values_compared": 8
"values_compared": 86
},
{
"max_rel_err": 0.0,
@@ -2773,8 +2805,8 @@
"cases_both_errored": 0,
"cases_deferred": 0,
"cases_failed": 0,
"cases_passed": 346,
"cases_total": 346,
"cases_passed": 350,
"cases_total": 350,
"deferred_by_unit": {},
"max_rel_err": 0.0,
"pass": true,