STEP import: a face bounded by a slit alone is the whole of its closed surface

A loop that runs out along a chain of edges and straight back (a meridian from a sphere's pole
stopping short of the other, out and back) encloses no (u, v) area, yet on a closed surface it
bounds all of it less a cut of none. The reader only read such a face when the slit already ran
pole to pole (and moved the sphere's frame onto it only then), so the 2 mm ball whose meridian
stops at 2.5 rad was refused for its empty loop.

The slit is now made the face's seams, so its loop bounds the surface's whole domain: on a
sphere, a revolution whose profile ends on its axis, or a spline closed round u that collapses
at both ends, the slit's meridian is run on to both poles, out and back (a sphere's frame put
on the slit first, whichever two points it joins); on a torus, the circle the slit lies along is
closed and the other circle through its start is the second seam. The seams on a sphere are its
own meridians (angle 0 on the equator), so their pcurves are exact lines on its form.

Tests: non_antipodal_seam_sphere_imports_as_a_whole_ball passes (its known-failing line goes);
seam_only_torus_imports_as_a_whole_ring adds a torus bounded by a slit along its tube.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
jyviko
2026-10-31 09:45:06 -04:00
committed by jyviko
parent 77f127c61e
commit 88e00486bb
5 changed files with 297 additions and 64 deletions
+13 -2
View File
@@ -146,12 +146,15 @@ fn non_antipodal_seam_sphere_imports_as_a_whole_ball() {
/// A whole ring torus (major radius 10, minor 3, about Z) written as one face bounded only by
/// seams: its tube circle at u = 0 and its ring circle at v = 0, each run out and back, in
/// either order round the loop. Each is the whole torus, volume 2π²Rr² and area 4π²Rr.
/// either order round the loop; or only a slit along the tube circle, from the outer equator 2.5
/// rad towards the top and back, the torus less a slit of no area. Each is the whole torus,
/// volume 2π²Rr² and area 4π²Rr.
#[test]
fn seam_only_torus_imports_as_a_whole_ring() {
use std::f64::consts::PI;
let (major, minor) = (10.0_f64, 3.0_f64);
let outer = major + minor;
let (end_x, end_z) = (major + minor * 2.5_f64.cos(), minor * 2.5_f64.sin());
let tube = format!(
"#14=CARTESIAN_POINT('',({major:.16},0.,0.));\n#15=DIRECTION('',(0.,-1.,0.));\n#16=AXIS2_PLACEMENT_3D('',#14,#15,#3);\n#17=CIRCLE('',#16,{minor:.16});\n"
);
@@ -159,11 +162,19 @@ fn seam_only_torus_imports_as_a_whole_ring() {
let cases = [
("tube then ring", format!("{tube}#20=EDGE_CURVE('',#11,#11,#17,.T.);\n{ring}#30=EDGE_LOOP('',(#21,#22,#23,#24));\n")),
("ring then tube", format!("{tube}#20=EDGE_CURVE('',#11,#11,#17,.T.);\n{ring}#30=EDGE_LOOP('',(#22,#21,#24,#23));\n")),
(
"a slit along the tube",
format!(
"#12=CARTESIAN_POINT('',({end_x:.16},0.,{end_z:.16}));\n#13=VERTEX_POINT('',#12);\n{tube}#20=EDGE_CURVE('',#11,#13,#17,.T.);\n#30=EDGE_LOOP('',(#21,#23));\n"
),
),
];
for (what, edges) in cases {
let mut t = String::from(HEAD);
t += "#1=CARTESIAN_POINT('',(0.,0.,0.));\n#2=DIRECTION('',(0.,0.,1.));\n#3=DIRECTION('',(1.,0.,0.));\n#4=AXIS2_PLACEMENT_3D('',#1,#2,#3);\n";
t += &format!("#5=TOROIDAL_SURFACE('',#4,{major:.16},{minor:.16});\n#10=CARTESIAN_POINT('',({outer:.16},0.,0.));\n#11=VERTEX_POINT('',#10);\n");
t += &format!(
"#5=TOROIDAL_SURFACE('',#4,{major:.16},{minor:.16});\n#10=CARTESIAN_POINT('',({outer:.16},0.,0.));\n#11=VERTEX_POINT('',#10);\n"
);
t += &edges;
t += "#21=ORIENTED_EDGE('',*,*,#20,.T.);\n#22=ORIENTED_EDGE('',*,*,#19,.T.);\n#23=ORIENTED_EDGE('',*,*,#20,.F.);\n#24=ORIENTED_EDGE('',*,*,#19,.F.);\n";
t += "#31=FACE_OUTER_BOUND('',#30,.T.);\n#32=ADVANCED_FACE('',(#31),#5,.T.);\n#33=CLOSED_SHELL('',(#32));\n#34=MANIFOLD_SOLID_BREP('Ring',#33);\n";
+4 -3
View File
@@ -943,9 +943,10 @@ impl<'b, 'a> Builder<'b, 'a> {
fn finish(mut self, item: u64, name: String, shells: Vec<Vec<(u32, bool)>>, kind: Kind) -> R<Solid> {
let _span = solidcraft_profile::span("import", "step.solid_record");
let mut notes = Vec::new();
// A whole sphere bounded by one seam has its frame on that seam, before anything is
// measured on the frame.
self.seat_spheres_on_seams()?;
// A face bounded by a slit alone is the whole of its closed surface, closed by its seams
// (a sphere's frame put on the slit), before anything is measured on the frame or split
// at its poles.
self.complete_slits()?;
let at_poles = self.split_at_poles()?;
if at_poles > 0 {
notes.push(format!("{at_poles} edges split where they pass through a face's pole"));
+1 -49
View File
@@ -28,8 +28,7 @@
use std::f64::consts::TAU;
use solidcraft_geom::Vec3;
use solidcraft_geom::brep::eval::CurveEval;
use solidcraft_geom::brep::{self, Curve as BCurve, Frame, Surface as BSurface};
use solidcraft_geom::brep::{self, Curve as BCurve};
use super::{Builder, LoopB, R};
@@ -257,50 +256,3 @@ impl Builder<'_, '_> {
Ok(turned)
}
}
impl Builder<'_, '_> {
/// Put the frame of each whole sphere bounded by one seam alone (a meridian from pole to
/// pole, used both ways) on that seam: its axis through the seam's ends, its x axis through
/// the seam's middle. A sphere's frame is the writer's choice (any right-handed frame about
/// the centre is the same surface, its natural normal outward); one whose poles are not the
/// seam's leaves the seam a slit across the form, not the form's own seam, and no kernel
/// takes that as the whole ball. Returns how many frames were moved.
pub(super) fn seat_spheres_on_seams(&mut self) -> R<usize> {
let mut seated = 0;
for fi in 0..self.faces.len() {
let Some(face) = self.faces.get(fi) else { continue };
let BSurface::Sphere { frame, radius } = &face.surface else { continue };
let (frame, radius) = (frame.clone(), *radius);
let [LoopB::Edges(uses)] = face.loops.as_slice() else { continue };
let [(e, a), (f, b)] = uses.as_slice() else { continue };
if e != f || a == b {
continue;
}
let edge = self.edges.get(*e as usize).ok_or("edge reference")?;
let point = |v: u32| self.vertices.get(v as usize).map(|x| (Vec3::from(x.point), x.tolerance));
let (Some((start, t0)), Some((end, t1))) = (point(edge.vertices[0]), point(edge.vertices[1])) else { continue };
let centre = Vec3::from(frame.origin);
let reach = self.tol.max(edge.tolerance).max(t0).max(t1);
// The seam runs between antipodes: its ends on the sphere, opposite each other.
if (start.dist(centre) - radius.abs()).abs() > reach || (start - centre).dist(centre - end) > 2.0 * reach {
continue;
}
let Some(z) = (start - end).normalized() else { continue };
let axis = Vec3::from(frame.x_axis).cross(Vec3::from(frame.y_axis));
// Already the frame's poles.
if axis.cross(z).len() * radius.abs() <= reach {
continue;
}
let c = CurveEval::new(&edge.curve).map_err(|m| m.to_string())?;
let middle = c.point(0.5 * (edge.interval[0] + edge.interval[1])).ok_or("edge point")?;
let off = middle - centre;
let Some(x) = (off - z * off.dot(z)).normalized() else { continue };
let y = z.cross(x);
let Some(face) = self.faces.get_mut(fi) else { continue };
face.surface = BSurface::Sphere { frame: Frame { origin: frame.origin, x_axis: [x.x, x.y, x.z], y_axis: [y.x, y.y, y.z] }, radius };
face.extent = None;
seated += 1;
}
Ok(seated)
}
}
+279 -9
View File
@@ -10,6 +10,10 @@
//! column (u at the start of its knots), the loops split there where they have no vertex on it;
//! the period is the width of its knots. A torus face winding without a seam, and a spline
//! closed in v or both ways, are refused for now.
//!
//! A face bounded by a slit alone (a loop out along edges and straight back, of no (u, v) area)
//! on a closed surface is all of that surface less a cut of none: its slit is made its seams
//! ([`Builder::complete_slits`]), so the loop bounds the surface's whole (u, v) domain.
use std::f64::consts::{PI, TAU};
@@ -37,6 +41,21 @@ fn wrap_by(x: f64, period: f64) -> f64 {
x - (x / period).round() * period
}
/// The path a slit runs out along: a loop that runs out along a chain of edges and straight back
/// along it, each edge used once each way, started at one of its two turns; `None` for any
/// other loop.
pub(super) fn slit_path(es: &[(u32, bool)]) -> Option<Vec<(u32, bool)>> {
let n = es.len();
if n < 2 || !n.is_multiple_of(2) || !super::seam_only(es) {
return None;
}
// A turn: a use followed by its edge the other way.
let k = (0..n).find(|&k| matches!((es.get((k + n - 1) % n), es.get(k)), (Some(a), Some(b)) if a.0 == b.0))?;
let ring: Vec<(u32, bool)> = es.iter().skip(k).chain(es.iter().take(k)).copied().collect();
let (out, back) = ring.split_at(n / 2);
back.iter().rev().zip(out).all(|(b, o)| b.0 == o.0 && b.1 != o.1).then(|| out.to_vec())
}
/// The axis frame of a surface of revolution: origin, unit axis, and the pole points at the
/// ends of its v range that have one (min v, max v).
struct Axis {
@@ -457,14 +476,23 @@ impl Builder<'_, '_> {
let (curve, interval, along) = match &ax.kind {
Kind::Ruled => (BCurve::Line { start: arr(pa), end: arr(pb) }, [0.0, 1.0], true),
Kind::Sphere(r) => {
let x = (pa - ax.origin).normalized().ok_or("a seam from the sphere's centre")?;
let to = pb - ax.origin;
let y = (to - x * to.dot(x)).normalized().ok_or("a seam between opposite points")?;
let theta = to.dot(y).atan2(to.dot(x));
if !(theta > 0.0 && theta <= PI) {
return Err("a degenerate seam".into());
// The sphere's own meridian through the end further from the axis: angle 0 on the
// equator, π/2 at the upper pole, so its angle is the surface's latitude and its
// quarters the surface's.
let (da, db) = (pa - ax.origin, pb - ax.origin);
let (ra, rb) = (da - ax.z * da.dot(ax.z), db - ax.z * db.dot(ax.z));
let x = if ra.len() >= rb.len() { ra } else { rb }.normalized().ok_or("a seam along the sphere's axis")?;
let latitude = |d: Vec3| d.dot(ax.z).atan2(d.dot(x));
let (ta, tb) = (latitude(da), latitude(db));
if !(ta.abs() <= 0.5 * PI + 1e-9 && tb.abs() <= 0.5 * PI + 1e-9) {
return Err(unsupported("a seam between loops at opposite angles"));
}
(BCurve::Circle { frame: Frame { origin: arr(ax.origin), x_axis: arr(x), y_axis: arr(y) }, radius: *r }, [0.0, theta], true)
let along = ta < tb;
(
BCurve::Circle { frame: Frame { origin: arr(ax.origin), x_axis: arr(x), y_axis: arr(ax.z) }, radius: *r },
if along { [ta, tb] } else { [tb, ta] },
along,
)
}
Kind::Iso { column, .. } => {
// Each vertex's v on the surface (a pole's from its end of the v range).
@@ -507,7 +535,14 @@ impl Builder<'_, '_> {
return Err(unsupported("a seam off its surface: loops with no vertices at one angle"));
}
}
let vertices = if along { [a, b] } else { [b, a] };
let i = self.new_edge(curve, interval, if along { [a, b] } else { [b, a] })?;
Ok((i, along))
}
/// A new edge on `curve` over `interval` between `vertices`, each vertex's tolerance raised to
/// cover its gap to the curve's end.
fn new_edge(&mut self, curve: BCurve, interval: [f64; 2], vertices: [u32; 2]) -> R<u32> {
let c = CurveEval::new(&curve).map_err(|m| m.to_string())?;
for (t, v) in interval.into_iter().zip(vertices) {
let gap = c.point(t).ok_or("seam point")?.dist(self.vpoint(v)?);
if let Some(x) = self.vertices.get_mut(v as usize) {
@@ -516,7 +551,242 @@ impl Builder<'_, '_> {
}
let i = index(self.edges.len())?;
self.edges.push(EdgeB { curve, interval, vertices, tolerance: self.tol, form: None });
Ok((i, along))
Ok(i)
}
/// Complete every face bounded by a slit alone into the whole of its surface. A slit is a
/// loop that runs out along a chain of edges and straight back ([`slit_path`]): it encloses
/// no (u, v) area, yet on a closed surface it bounds all of it less a cut of none (a ball
/// whose one face is bounded by part of a meridian, out and back). Schema 4 keeps that face
/// as the surface's whole (u, v) domain, closed by its seams, so the slit is made one: on a
/// surface of revolution closed at both poles (a sphere, a revolution whose profile ends on
/// its axis, a spline closed round u that collapses at both v ends), the meridian the slit
/// lies along is run on from its ends to the poles, out and back; on a torus, the circle the
/// slit lies along (round the tube or round the axis) is closed and the other circle through
/// its start is the second seam. Returns how many faces were completed.
pub(super) fn complete_slits(&mut self) -> R<usize> {
let mut completed = 0;
for fi in 0..self.faces.len() {
let Some(face) = self.faces.get(fi) else { continue };
let [LoopB::Edges(es)] = face.loops.as_slice() else { continue };
let Some(path) = slit_path(es) else { continue };
let (step, surface) = (face.step, face.surface.clone());
let (merged, seated) = match &surface {
BSurface::Torus { frame, major_radius, .. } => (self.torus_slit(&path, frame, *major_radius)?, None),
_ => match self.closed_at_poles(&path, &surface)? {
// An open surface: the slit bounds nothing, and is refused for that later.
None => continue,
Some(Some(m)) => (Some(m), None),
// A sphere's frame is the writer's choice (any right-handed frame about its
// centre is the same surface, its natural normal outward): one whose
// meridians miss the slit has its pole put at the slit's start instead, its x
// axis through the slit's middle.
Some(None) => match &surface {
BSurface::Sphere { frame, radius } => match self.seated(&path, frame, *radius)? {
Some(s) => (self.closed_at_poles(&path, &s)?.flatten(), Some(s)),
None => (None, None),
},
_ => (None, None),
},
},
};
let merged = merged.ok_or_else(|| format!("face #{step}: {}", unsupported("a face bounded by a slit off its surface's seam lines")))?;
let Some(face) = self.faces.get_mut(fi) else { continue };
if let Some(s) = seated {
face.surface = s;
face.extent = None;
}
if let Some(LoopB::Edges(es)) = face.loops.first_mut() {
*es = merged;
}
completed += 1;
}
Ok(completed)
}
/// The slit made the meridian seam of `surface` when it is a surface of revolution closed
/// at both poles ([`Builder::meridian_slit`]); `None` for any other surface.
fn closed_at_poles(&mut self, path: &[(u32, bool)], surface: &BSurface) -> R<Option<Option<Vec<(u32, bool)>>>> {
let eval = SurfaceEval::new(surface).map_err(|e| e.to_string())?;
match axis(surface).or_else(|| spline_axis(surface, &eval)) {
Some(ax) if ax.poles.iter().all(Option::is_some) => Ok(Some(self.meridian_slit(path, &ax, &eval)?)),
_ => Ok(None),
}
}
/// The sphere about `frame`'s centre with its upper pole at the slit's start and its x axis
/// through the middle of the slit's middle edge; `None` where those coincide.
fn seated(&self, path: &[(u32, bool)], frame: &Frame, radius: f64) -> R<Option<BSurface>> {
let centre = Vec3::from(frame.origin);
let first = *path.first().ok_or("empty slit")?;
let Some(z) = (self.vpoint(self.start(first)?)? - centre).normalized() else { return Ok(None) };
let &(e, _) = path.get(path.len() / 2).ok_or("empty slit")?;
let edge = self.edges.get(e as usize).ok_or("edge reference")?;
let middle =
CurveEval::new(&edge.curve).map_err(|m| m.to_string())?.point(0.5 * (edge.interval[0] + edge.interval[1])).ok_or("edge point")?;
let off = middle - centre;
let Some(x) = (off - z * off.dot(z)).normalized() else { return Ok(None) };
Ok(Some(BSurface::Sphere { frame: Frame { origin: frame.origin, x_axis: arr(x), y_axis: arr(z.cross(x)) }, radius }))
}
/// The vertex use `u` ends at.
fn end(&self, (e, forward): (u32, bool)) -> R<u32> {
self.start((e, !forward))
}
/// Vertex `v` is at `p`, within its tolerance.
fn at(&self, v: u32, p: Vec3) -> R<bool> {
let x = self.vertices.get(v as usize).ok_or("vertex reference")?;
Ok(Vec3::from(x.point).dist(p) <= self.tol.max(x.tolerance))
}
/// Every point of the path's edges lies on one meridian of the surface (the spline's
/// boundary column for [`Kind::Iso`]), the poles apart.
fn on_meridian(&self, path: &[(u32, bool)], ax: &Axis, eval: &SurfaceEval) -> R<bool> {
let mut u_ref = match ax.kind {
Kind::Iso { u0, .. } => Some(u0),
_ => None,
};
for &(e, _) in path {
let edge = self.edges.get(e as usize).ok_or("edge reference")?;
let c = CurveEval::new(&edge.curve).map_err(|m| m.to_string())?;
let ([lo, hi], reach) = (edge.interval, self.tol.max(edge.tolerance));
for k in 0..=SAMPLES {
let p = c.point(lo + (hi - lo) * k as f64 / SAMPLES as f64).ok_or("edge point")?;
if ax.poles.iter().flatten().any(|q| q.dist(p) <= reach) {
continue;
}
let Some(q) = eval.project(p, None, f64::INFINITY) else { return Ok(false) };
let u = *u_ref.get_or_insert(q[0]);
if !eval.point([u, q[1]]).is_some_and(|m| m.dist(p) <= reach) {
return Ok(false);
}
}
}
Ok(u_ref.is_some())
}
/// A slit along a meridian of a surface closed at both poles, run on to them: the meridian
/// from the lower pole to the upper (the slit's path, with a seam edge from each end of it
/// that is not at its pole), then back. `None` when the slit is off every meridian.
fn meridian_slit(&mut self, path: &[(u32, bool)], ax: &Axis, eval: &SurfaceEval) -> R<Option<Vec<(u32, bool)>>> {
let (Some(south), Some(north)) = (ax.poles[0], ax.poles[1]) else { return Ok(None) };
if !self.on_meridian(path, ax, eval)? {
return Ok(None);
}
// How far up the meridian a vertex is: a pole's from its end, else its v.
let height = |s: &Self, v: u32| -> R<f64> {
Ok(if s.at(v, south)? {
f64::NEG_INFINITY
} else if s.at(v, north)? {
f64::INFINITY
} else {
eval.project(s.vpoint(v)?, None, f64::INFINITY).ok_or("a slit vertex off its surface")?[1]
})
};
let (first, last) = (*path.first().ok_or("empty slit")?, *path.last().ok_or("empty slit")?);
let (ha, hb) = (height(self, self.start(first)?)?, height(self, self.end(last)?)?);
if !(ha != hb) {
return Ok(None);
}
// Up the meridian.
let mut up = path.to_vec();
if ha > hb {
super::reverse(&mut up);
}
let (a, b) = (self.start(*up.first().ok_or("empty slit")?)?, self.end(*up.last().ok_or("empty slit")?)?);
let pole_vertex = |s: &mut Self, p: Vec3| -> R<u32> {
let v = index(s.vertices.len())?;
s.vertices.push(Vertex { point: arr(p), tolerance: s.tol });
Ok(v)
};
let mut merged = Vec::with_capacity(2 * up.len() + 4);
if !self.at(a, south)? {
let s = pole_vertex(self, south)?;
let (seam, along) = self.seam_edge(a, s, ax, eval, true)?;
merged.push((seam, !along));
}
merged.extend(up);
if !self.at(b, north)? {
let n = pole_vertex(self, north)?;
merged.push(self.seam_edge(b, n, ax, eval, true)?);
}
let mut back = merged.clone();
super::reverse(&mut back);
merged.extend(back);
Ok(Some(merged))
}
/// A slit along a circle of a torus (round its tube, or round its axis) made the torus's two
/// seams: that circle closed from the slit's end round to its start, the way the slit runs,
/// and the other circle through the start; the loop runs round the first, the second, the
/// first back and the second back. `None` when the slit is off both circles.
fn torus_slit(&mut self, path: &[(u32, bool)], frame: &Frame, major: f64) -> R<Option<Vec<(u32, bool)>>> {
let (o, z) = (Vec3::from(frame.origin), Vec3::from(frame.x_axis).cross(Vec3::from(frame.y_axis)));
let (first, last) = (*path.first().ok_or("empty slit")?, *path.last().ok_or("empty slit")?);
let (a, b) = (self.start(first)?, self.end(last)?);
let (pa, pb) = (self.vpoint(a)?, self.vpoint(b)?);
let off = pa - o;
let h = off.dot(z);
let Some(radial) = (off - z * h).normalized() else { return Ok(None) };
// The circles through the slit's start, as centre and unit normal: round the tube in the
// plane through the axis, and round the axis at the start's height.
let tube = (o + radial * major, radial.cross(z));
let ring = (o + z * h, z);
let along = |s: &Self, (c, n): (Vec3, Vec3)| -> R<bool> {
let r = (pa - c).len();
for &(e, _) in path {
let edge = s.edges.get(e as usize).ok_or("edge reference")?;
let curve = CurveEval::new(&edge.curve).map_err(|m| m.to_string())?;
let ([lo, hi], reach) = (edge.interval, s.tol.max(edge.tolerance));
for k in 0..=SAMPLES {
let d = curve.point(lo + (hi - lo) * k as f64 / SAMPLES as f64).ok_or("edge point")? - c;
if !(d.dot(n).abs() <= reach && (d.len() - r).abs() <= reach) {
return Ok(false);
}
}
}
Ok(true)
};
let (circle, other) = if along(self, tube)? {
(tube, ring)
} else if along(self, ring)? {
(ring, tube)
} else {
return Ok(None);
};
let mut round = path.to_vec();
if a != b {
// On from the slit's end the way it arrives there, from a point of its last edge
// just before.
let (c, n) = circle;
let edge = self.edges.get(last.0 as usize).ok_or("edge reference")?;
let [lo, hi] = edge.interval;
let t = if last.1 { hi - (hi - lo) / SAMPLES as f64 } else { lo + (hi - lo) / SAMPLES as f64 };
let before = CurveEval::new(&edge.curve).map_err(|m| m.to_string())?.point(t).ok_or("edge point")?;
let Some(x) = (pb - c).normalized() else { return Ok(None) };
let y = n.cross(x);
let y = if (pb - before).dot(y) >= 0.0 { y } else { -y };
let d = pa - c;
let theta = d.dot(y).atan2(d.dot(x)).rem_euclid(TAU);
let radius = (pb - c).len();
if !(theta * radius > self.tol) {
return Ok(None);
}
let rest = BCurve::Circle { frame: Frame { origin: arr(c), x_axis: arr(x), y_axis: arr(y) }, radius };
round.push((self.new_edge(rest, [0.0, theta], [b, a])?, true));
}
let (c, n) = other;
let Some(x) = (pa - c).normalized() else { return Ok(None) };
let full = BCurve::Circle { frame: Frame { origin: arr(c), x_axis: arr(x), y_axis: arr(n.cross(x)) }, radius: (pa - c).len() };
let second = self.new_edge(full, [0.0, TAU], [a, a])?;
let mut back = round.clone();
super::reverse(&mut back);
let mut merged = round;
merged.push((second, true));
merged.extend(back);
merged.push((second, false));
Ok(Some(merged))
}
/// Join loop `li` of face `fi` to the pole the face covers: seam out and back.
-1
View File
@@ -16,5 +16,4 @@ solidcraft-kernel::blend::corner_tests::corner_blends_hold_at_any_scale at 0.01x
solidcraft-kernel::blend_corner::tests::issue2422_vertical_after_separate_bottom_rounds SMLib's check refuses the fillet: edge and vertex tolerance 0.97 against the body's 0.01 (#2422)
solidcraft-kernel::corner_g2::tests::a_long_setback_keeps_the_g2_patch_inside_the_box at set backs 11 to 15 the G2 corner patch bulges 2.6e-6 to 1.33e-3 past the box (the explicit corner is in progress)
solidcraft-kernel::corner_g2::tests::the_box_corner_g2_patch_is_a_closed_solid_curvature_continuous_to_its_neighbours the fitted G2 corner patch bulges 1.7e-5 past the box, which SMLib's exact tight box now sees (the explicit G2 corner builds it inside)
solidcraft-kernel::step_in::tests::non_antipodal_seam_sphere_imports_as_a_whole_ball the import refuses the ball: its face's loop, a meridian slit from the pole run out and back, encloses no (u, v) area, so the whole sphere less a slit is not read as the sphere
solidcraft-kernel::tests::fillet_tabbed_plate_outer_loop_and_partial_selection_after_hole the arc and tab rounds together remove 9.20067 against 9.20420 (3.8e-4 off, 1e-5 allowed)