Kernel: pole-free six-patch spheres; vendored truck-shapeops with robust meshing and nearest-parameter loop projection (sphere and box booleans work); least-squares sphere classification

Oracle: 25/29 (14 and 15 now match Fusion).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
bherbruckandClaude Opus 5.5 committed 2026-10-07 21:30:18 +00:00
1 parent 7ef3426cb3
commit 043a542e29
34 files changed
+6228 -43

No files matched your search

@@ -0,0 +1,67 @@
use super::loops_store::ShapesOpStatus;
use rustc_hash::FxHashMap as HashMap;
use truck_topology::*;
#[derive(Clone, Debug)]
pub struct FacesClassification<P, C, S> {
shell: Shell<P, C, S>,
status: HashMap<FaceID<S>, ShapesOpStatus>,
}
impl<P, C, S> Default for FacesClassification<P, C, S> {
fn default() -> Self {
Self {
shell: Default::default(),
status: HashMap::default(),
}
}
}
impl<P, C, S> FacesClassification<P, C, S> {
pub fn push(&mut self, face: Face<P, C, S>, status: ShapesOpStatus) {
self.status.insert(face.id(), status);
self.shell.push(face);
}
pub fn and_or_unknown(&self) -> [Shell<P, C, S>; 3] {
let [mut and, mut or, mut unknown] = <[Shell<P, C, S>; 3]>::default();
for face in &self.shell {
match self.status.get(&face.id()).unwrap() {
ShapesOpStatus::And => and.push(face.clone()),
ShapesOpStatus::Or => or.push(face.clone()),
ShapesOpStatus::Unknown => unknown.push(face.clone()),
}
}
[and, or, unknown]
}
pub fn integrate_by_component(&mut self) {
let [and, or, unknown] = self.and_or_unknown();
let and_boundary = and.extract_boundaries();
let or_boundary = or.extract_boundaries();
let components = unknown.connected_components();
for comp in components {
let boundary = comp.extract_boundaries();
if and_boundary
.iter()
.flatten()
.any(|edge| edge.id() == boundary[0][0].id())
{
comp.iter().for_each(|face| {
*self.status.get_mut(&face.id()).unwrap() = ShapesOpStatus::And;
})
} else if or_boundary
.iter()
.flatten()
.any(|edge| edge.id() == boundary[0][0].id())
{
comp.iter().for_each(|face| {
*self.status.get_mut(&face.id()).unwrap() = ShapesOpStatus::Or;
})
}
}
}
}
#[cfg(test)]
mod tests;
@@ -0,0 +1,131 @@
use super::{
super::{divide_face, loops_store},
*,
};
use truck_geometry::prelude::*;
use truck_meshalgo::prelude::*;
use truck_topology::{shell::ShellCondition, Vertex};
const TOL: f64 = 0.05;
type AlternativeIntersection = crate::alternative::Alternative<
NurbsCurve<Vector4>,
IntersectionCurve<PolylineCurve<Point3>, AlternativeSurface>,
>;
type AlternativeSurface = crate::alternative::Alternative<BSplineSurface<Point3>, Plane>;
fn parabola_surfaces() -> (AlternativeSurface, AlternativeSurface) {
// define surfaces
#[rustfmt::skip]
let ctrl0 = vec![
vec![Point3::new(-1.0, -1.0, 3.0), Point3::new(-1.0, 0.0, -1.0), Point3::new(-1.0, 1.0, 3.0)],
vec![Point3::new(0.0, -1.0, -1.0), Point3::new(0.0, 0.0, -5.0), Point3::new(0.0, 1.0, -1.0)],
vec![Point3::new(1.0, -1.0, 3.0), Point3::new(1.0, 0.0, -1.0), Point3::new(1.0, 1.0, 3.0)],
];
#[rustfmt::skip]
let ctrl1 = vec![
vec![Point3::new(-1.0, -1.0, -3.0), Point3::new(-1.0, 0.0, 1.0), Point3::new(-1.0, 1.0, -3.0)],
vec![Point3::new(0.0, -1.0, 1.0), Point3::new(0.0, 0.0, 5.0), Point3::new(0.0, 1.0, 1.0)],
vec![Point3::new(1.0, -1.0, -3.0), Point3::new(1.0, 0.0, 1.0), Point3::new(1.0, 1.0, -3.0)],
];
(
BSplineSurface::new((KnotVec::bezier_knot(2), KnotVec::bezier_knot(2)), ctrl0).into(),
BSplineSurface::new((KnotVec::bezier_knot(2), KnotVec::bezier_knot(2)), ctrl1).into(),
)
}
#[test]
fn independent_intersection() {
// prepare geoetries
let arc00: AlternativeIntersection = NurbsCurve::new(BSplineCurve::new(
KnotVec::bezier_knot(2),
vec![
Vector4::new(1.0, 0.0, 1.0, 1.0),
Vector4::new(0.0, 1.0, 0.0, 0.0),
Vector4::new(-1.0, 0.0, 1.0, 1.0),
],
))
.into();
let arc01: AlternativeIntersection = NurbsCurve::new(BSplineCurve::new(
KnotVec::bezier_knot(2),
vec![
Vector4::new(-1.0, 0.0, 1.0, 1.0),
Vector4::new(0.0, -1.0, 0.0, 0.0),
Vector4::new(1.0, 0.0, 1.0, 1.0),
],
))
.into();
let arc10: AlternativeIntersection = NurbsCurve::new(BSplineCurve::new(
KnotVec::bezier_knot(2),
vec![
Vector4::new(1.0, 0.0, -1.0, 1.0),
Vector4::new(0.0, 1.0, 0.0, 0.0),
Vector4::new(-1.0, 0.0, -1.0, 1.0),
],
))
.into();
let arc11: AlternativeIntersection = NurbsCurve::new(BSplineCurve::new(
KnotVec::bezier_knot(2),
vec![
Vector4::new(-1.0, 0.0, -1.0, 1.0),
Vector4::new(0.0, -1.0, 0.0, 0.0),
Vector4::new(1.0, 0.0, -1.0, 1.0),
],
))
.into();
let (surface0, surface1) = parabola_surfaces();
let plane0: AlternativeSurface = Plane::new(
Point3::new(0.0, 0.0, 1.0),
Point3::new(1.0, 0.0, 1.0),
Point3::new(0.0, 1.0, 1.0),
)
.into();
let plane1: AlternativeSurface = Plane::new(
Point3::new(0.0, 0.0, -1.0),
Point3::new(1.0, 0.0, -1.0),
Point3::new(0.0, 1.0, -1.0),
)
.into();
// prepare topologies
let v00 = Vertex::new(Point3::new(1.0, 0.0, 1.0));
let v01 = Vertex::new(Point3::new(-1.0, 0.0, 1.0));
let v10 = Vertex::new(Point3::new(1.0, 0.0, -1.0));
let v11 = Vertex::new(Point3::new(-1.0, 0.0, -1.0));
let wire0: Wire<_, _> = vec![Edge::new(&v00, &v01, arc00), Edge::new(&v01, &v00, arc01)].into();
let wire1: Wire<_, _> = vec![Edge::new(&v10, &v11, arc10), Edge::new(&v11, &v10, arc11)].into();
let shell0: Shell<_, _, _> = vec![
Face::new(vec![wire0.clone()], plane0),
Face::new(vec![wire0], surface0).inverse(),
]
.into();
assert_eq!(shell0.shell_condition(), ShellCondition::Closed);
let shell1: Shell<_, _, _> = vec![
Face::new(vec![wire1.clone()], plane1).inverse(),
Face::new(vec![wire1], surface1),
]
.into();
assert_eq!(shell1.shell_condition(), ShellCondition::Closed);
let poly_shell0 = shell0.triangulation(TOL);
let poly_shell1 = shell1.triangulation(TOL);
let loops_store::LoopsStoreQuadruple {
geom_loops_store0: loops_store0,
geom_loops_store1: loops_store1,
..
} = loops_store::create_loops_stores(&shell0, &poly_shell0, &shell1, &poly_shell1, TOL)
.unwrap();
let mut cls0 = divide_face::divide_faces(&shell0, &loops_store0, TOL).unwrap();
cls0.integrate_by_component();
let mut cls1 = divide_face::divide_faces(&shell1, &loops_store1, TOL).unwrap();
cls1.integrate_by_component();
let [mut and, mut or, _] = cls0.and_or_unknown();
let [and1, or1, _] = cls1.and_or_unknown();
and.extend(and1);
or.extend(or1);
assert_eq!(and.len(), 2);
assert_eq!(or.len(), 4);
assert_eq!(and.shell_condition(), ShellCondition::Closed);
assert_eq!(or.shell_condition(), ShellCondition::Closed);
}