Modelled threads (Thread with modeled: true): the ISO 68-1 profile cut along a helix, M3 to M20, on shafts and in bores; running out past a free end of the face, stopping square at a set length. The groove is a watertight screw-sweep mesh (kernel::sweep_helix_mesh) cut by kernel::faceted_boolean (BSP on the faceted body, tolerance ladder checked by A−B + A∩B = A); the polygon rebuild welds vertices across grid cells, splits pinched loops, finds T-junctions through a grid, and takes the group's plane for a loop a hair off flat

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
bherbruckandClaude Opus 5.5 committed 2026-10-09 12:47:56 +00:00
1 parent 1f619e49cc
commit 141a5fea8a
11 files changed
+421 -34

No files matched your search

+40 -1
View File
@@ -1,7 +1,7 @@
//! Helical sweeps (coils, springs) and lofts to a point (pyramids, cones).
use mt::builder;
use solvecraft_geom::{Loop2, Plane, Region2, Seg2, Vec3};
use solvecraft_geom::{Loop2, Mesh, Plane, Region2, Seg2, Vec2, Vec3};
use truck_modeling as mt;
use crate::body::{Body, Solid, p3};
@@ -59,6 +59,45 @@ pub fn sweep_helix(plane: &Plane, region: &Region2, axis_origin: Vec3, axis: Vec
})
}
/// A screw sweep of a convex polygon (in `plane`'s coordinates) as a closed triangle mesh,
/// wound outward: `stations` sections per turn, flat end caps. Watertight by construction, for
/// faceted booleans (modelled threads).
pub fn sweep_helix_mesh(plane: &Plane, polygon: &[Vec2], axis_origin: Vec3, axis: Vec3, pitch: f64, turns: f64, stations: usize) -> Result<Mesh> {
let k = axis.normalized().ok_or_else(|| KernelError::Invalid("helix axis".into()))?;
let m = polygon.len();
if !(pitch.is_finite() && turns.is_finite() && turns > 0.0 && turns <= 200.0 && (3..=64).contains(&m) && (8..=720).contains(&stations)) {
return Err(KernelError::Invalid("helix mesh: turns in (0, 200], 3…64 corners, 8…720 stations".into()));
}
let n = (turns * stations as f64).ceil() as usize;
let total = std::f64::consts::TAU * turns;
let mut mesh = Mesh::default();
for i in 0..=n {
let a = total * i as f64 / n as f64;
for q in polygon {
let w = plane.to_world(*q);
mesh.positions.push(axis_origin + rot(w - axis_origin, k, a) + k * (pitch * a / std::f64::consts::TAU));
}
}
let id = |i: usize, j: usize| u32::try_from(i * m + j % m).unwrap_or(0);
for i in 0..n {
for j in 0..m {
mesh.triangles.push([id(i, j), id(i + 1, j), id(i + 1, j + 1)]);
mesh.triangles.push([id(i, j), id(i + 1, j + 1), id(i, j + 1)]);
}
}
for j in 1..m - 1 {
mesh.triangles.push([id(0, 0), id(0, j), id(0, j + 1)]);
mesh.triangles.push([id(n, 0), id(n, j + 1), id(n, j)]);
}
mesh.tri_face = vec![0; mesh.triangles.len()];
if mesh.measure().volume < 0.0 {
for t in &mut mesh.triangles {
t.swap(1, 2);
}
}
Ok(mesh)
}
/// Samples per profile segment and stations per turn of the smooth helical walls.
const SEG_SAMPLES: usize = 12;
const STATIONS_PER_TURN: f64 = 12.0;