mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-04 02:55:54 +00:00
Kernel: a sweep along a smooth path names every wall, and a closed one has no caps
The smooth-path sweep named walls only by the profile's edges in the start plane and took any planar face nothing claimed for the end cap. A wall the start plane meets along no edge got no name or became a cap: round a closed path, a square tube's planar top and bottom (whole annuli, no seam) were both called end caps; along a line then an arc, the walls SMLib makes along the arc were unnamed or caps. Like the cornered and controlled sweeps, it now also names walls by the profile carried half way along, takes the end cap from where SMLib carries the profile last, and round a closed path has no caps at all. The issue #2005 tube check counted V - E + F without the ring term: SMLib's annular planar faces have two loops each, so it checks V - E + F - (L - F) = 2 - 2g (Euler-Poincare). Along a line then an arc SMLib makes a wall per profile segment per path segment (two, where truck made three). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -31,8 +31,10 @@ fn circle_path() -> [PathSeg; 2] {
|
||||
fn tube(body: &Body, volume: f64, genus: usize) {
|
||||
let m = measure(body).unwrap();
|
||||
assert!((m.volume - volume).abs() < volume * 0.01, "{m:?}, expected {volume}");
|
||||
// A closed orientable tube has Euler characteristic 2 - 2g. Coincident caps gave 2.
|
||||
assert_eq!(m.vertices + m.faces + 2 * genus, m.edges + 2, "{m:?}");
|
||||
// A closed orientable tube of genus g: V - E + F - (L - F) = 2 - 2g (Euler-Poincare, L the
|
||||
// loops: an annular face has two). Coincident caps gave 2 - 2g + 2.
|
||||
let loops: usize = body.record().faces.iter().map(|f| f.loops.len()).sum();
|
||||
assert_eq!(m.vertices + 2 * m.faces + 2 * genus, m.edges + loops + 2, "{m:?}, {loops} loops");
|
||||
// Every shell closed: each edge bounds two faces.
|
||||
assert!(body.record().edges.iter().all(|e| e.uses.len() >= 2));
|
||||
let h = body.history().expect("sweep provenance");
|
||||
|
||||
@@ -439,13 +439,13 @@ fn check_sweep(b: &Body, plane: &Plane, r: &Region2, faces_per_segment: Option<u
|
||||
#[test]
|
||||
fn sweeps_along_paths() {
|
||||
let r = Region2 { outer: rotated(&rect(-2.0, -2.0, 2.0, 2.0).reversed(), 1), holes: vec![] };
|
||||
// A line then a quarter turn (two parts): three faces per segment.
|
||||
// A line then a quarter turn: a face per segment along each path segment.
|
||||
let path = [
|
||||
PathSeg::Line { a: Vec3::ZERO, b: Vec3::new(0.0, 0.0, 10.0) },
|
||||
PathSeg::Arc { a: Vec3::new(0.0, 0.0, 10.0), center: Vec3::new(20.0, 0.0, 10.0), axis: Vec3::Y, angle: FRAC_PI_2 },
|
||||
];
|
||||
let b = crate::sweep(&Plane::XY, &r, &path).unwrap();
|
||||
check_sweep(&b, &Plane::XY, &r, Some(3));
|
||||
check_sweep(&b, &Plane::XY, &r, Some(2));
|
||||
// Along a smooth 3D path: one wall per segment.
|
||||
let curve: Vec<Vec3> = (0..=40)
|
||||
.map(|i| {
|
||||
|
||||
@@ -565,9 +565,18 @@ mod imp {
|
||||
if runs.len() > 1 {
|
||||
return cornered(plane, pieces, path, &runs, translate);
|
||||
}
|
||||
let (brep, _) = swept(plane, pieces, path, translate)?;
|
||||
let (brep, [_, middle, last]) = swept(plane, pieces, path, translate)?;
|
||||
let body = body_of(&brep, "sweep")?;
|
||||
let roles = roles(&body, plane, None, &seeds(plane, pieces, |q| q), &[], true)?;
|
||||
// The walls known by the profile carried half way (past the start's edges: a wall the
|
||||
// start touches by no edge, as round a closed path, and walls along later path segments).
|
||||
let inside = seeds(plane, pieces, |q| apply(&middle, q));
|
||||
let start = path.first().map(crate::PathSeg::start);
|
||||
let end = path.last().map(seg_ends).transpose()?.map(|(_, b)| b);
|
||||
let roles = match (start, end) {
|
||||
// Round a closed path there are no caps, and the start holds seams only.
|
||||
(Some(a), Some(b)) if a.dist(b) <= 1e-6 => roles(&body, &plane.offset(body.size().max(1e-9) * 10.0), None, &[], &inside, false)?,
|
||||
_ => roles(&body, plane, Some(&placed(plane, &last)), &seeds(plane, pieces, |q| q), &inside, false)?,
|
||||
};
|
||||
with_history(body, &roles, pieces)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user