mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-06 20:02:16 +00:00
kernel: a cornered sweep carries on from where SMLib placed the profile
Brep::sweep_placed (smlib_sweep_placed, fork f6f428b) returns the rigid motions carrying the profile onto the sweep's first, middle and last sections. Each run of a path with corners now takes its end pose from the last motion (less the straight lead-out along the end tangent) and its inside seeds from the middle one, for every path segment kind: the 3D sketch spline next to a corner is no longer refused. Deleted: the predicted carry along a run (carried) and point_path::cubic_split, which only it used. SMLIB_REV at f6f428b. Fork: build + ctest, only smlib_flat_roundtrip's known incline failure. (not built: base in progress) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017itGh5fpMEousnZ91cYcVt
This commit is contained in:
@@ -133,16 +133,6 @@ pub(crate) fn cubic_derivative(p: &[Vec3; 4], x: f64) -> Vec3 {
|
||||
(p[1] - p[0]) * (3.0 * y * y) + (p[2] - p[1]) * (6.0 * x * y) + (p[3] - p[2]) * (3.0 * x * x)
|
||||
}
|
||||
|
||||
/// The cubic split at `x` (de Casteljau): the part before and the part after.
|
||||
#[cfg_attr(not(feature = "smlib"), allow(dead_code))]
|
||||
pub(crate) fn cubic_split(p: &[Vec3; 4], x: f64) -> ([Vec3; 4], [Vec3; 4]) {
|
||||
let [a, b, c, d] = *p;
|
||||
let (ab, bc, cd) = (a.lerp(b, x), b.lerp(c, x), c.lerp(d, x));
|
||||
let (abc, bcd) = (ab.lerp(bc, x), bc.lerp(cd, x));
|
||||
let m = abc.lerp(bcd, x);
|
||||
([a, ab, abc, m], [m, bcd, cd, d])
|
||||
}
|
||||
|
||||
#[cfg_attr(not(feature = "smlib"), allow(dead_code))]
|
||||
fn cubic_curve(p: &[Vec3; 4]) -> CurveN<3> {
|
||||
CurveN { degree: 3, knots: vec![0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0], ctrl: p.iter().map(|q| [q.x, q.y, q.z]).collect() }
|
||||
|
||||
@@ -487,17 +487,31 @@ mod imp {
|
||||
if runs.len() > 1 {
|
||||
return cornered(plane, pieces, path, &runs, translate);
|
||||
}
|
||||
let brep = swept(plane, pieces, path, translate)?;
|
||||
let (brep, _) = swept(plane, pieces, path, translate)?;
|
||||
let body = body_of(&brep, "sweep")?;
|
||||
let roles = roles(&body, plane, None, &seeds(plane, pieces, |q| q), &[], true)?;
|
||||
with_history(body, &roles, pieces)
|
||||
}
|
||||
|
||||
fn swept(plane: &Plane, pieces: &[Piece], path: &[crate::PathSeg], translate: bool) -> Result<Brep> {
|
||||
/// The sweep of the profile on `plane` along `path`, with the rigid motions SMLib reports
|
||||
/// carrying the profile onto its first, middle and last sections.
|
||||
fn swept(plane: &Plane, pieces: &[Piece], path: &[crate::PathSeg], translate: bool) -> Result<(Brep, [Affine; 3])> {
|
||||
let c = curves(plane, pieces)?;
|
||||
let route = path.iter().map(path_curve).collect::<Result<Vec<_>>>()?;
|
||||
let frame = if translate { SweepFrame::Translate } else { SweepFrame::Follow };
|
||||
Brep::sweep(&c, &route, frame, true).map_err(|e| failed("sweep", e))
|
||||
Brep::sweep_placed(&c, &route, frame, true).map_err(|e| failed("sweep", e))
|
||||
}
|
||||
|
||||
/// `q` moved by the motion `a`.
|
||||
fn apply(a: &Affine, q: Vec3) -> Vec3 {
|
||||
let col = |i: usize| a.m.get(i).map_or(Vec3::ZERO, |c| Vec3::new(c[0], c[1], c[2]));
|
||||
col(0) * q.x + col(1) * q.y + col(2) * q.z + Vec3::new(a.t[0], a.t[1], a.t[2])
|
||||
}
|
||||
|
||||
/// `pl` carried by the motion `a` (a rigid motion: its directions turn with it).
|
||||
fn placed(pl: &Plane, a: &Affine) -> Plane {
|
||||
let o = apply(a, Vec3::ZERO);
|
||||
Plane { origin: apply(a, pl.origin), x: apply(a, pl.x) - o, y: apply(a, pl.y) - o }
|
||||
}
|
||||
|
||||
/// The path's segments in runs along which its direction carries on (within a thousandth of
|
||||
@@ -529,37 +543,6 @@ mod imp {
|
||||
Plane { origin: pl.origin + by, ..*pl }
|
||||
}
|
||||
|
||||
/// The profile's plane carried along `segs` as the sweep carries it: along a line it moves,
|
||||
/// round an arc it turns about the arc's axis; only moving with `translate`.
|
||||
fn carried(pl: &Plane, segs: &[crate::PathSeg], translate: bool) -> Result<Plane> {
|
||||
let mut pl = *pl;
|
||||
for s in segs {
|
||||
pl = match *s {
|
||||
crate::PathSeg::Line { a, b } => moved_plane(&pl, b - a),
|
||||
crate::PathSeg::Arc { a, center, axis, angle } => {
|
||||
let k = axis.normalized().ok_or_else(|| KernelError::Invalid("path arc axis".into()))?;
|
||||
if translate {
|
||||
moved_plane(&pl, crate::build::rotate_about(a, center, k, angle) - a)
|
||||
} else {
|
||||
turned_plane(&pl, center, k, angle)
|
||||
}
|
||||
}
|
||||
// A planar curve turns the profile in its plane as its direction turns.
|
||||
crate::PathSeg::Curve { plane, .. } => {
|
||||
let (a, b) = seg_ends(s)?;
|
||||
let moved = moved_plane(&pl, b - a);
|
||||
let (t0, t1) = s.tangents().ok_or_else(|| KernelError::Invalid("a path segment has no direction".into()))?;
|
||||
let n = plane.normal();
|
||||
if translate { moved } else { turned_plane(&moved, b, n, t0.cross(t1).dot(n).atan2(t0.dot(t1))) }
|
||||
}
|
||||
// How SMLib's sweep turns the profile along a curve in space is its own frame's.
|
||||
crate::PathSeg::Cubic { p: ctrl } if translate => moved_plane(&pl, ctrl[3] - ctrl[0]),
|
||||
crate::PathSeg::Cubic { .. } => return Err(refuse("sweep round a corner next to a 3D sketch spline, the profile turning with it")),
|
||||
};
|
||||
}
|
||||
Ok(pl)
|
||||
}
|
||||
|
||||
/// Where `s` starts and ends.
|
||||
fn seg_ends(s: &crate::PathSeg) -> Result<(Vec3, Vec3)> {
|
||||
Ok(match *s {
|
||||
@@ -653,28 +636,19 @@ mod imp {
|
||||
let (_, t1) = last.tangents().ok_or_else(|| KernelError::Invalid("a path segment has no direction".into()))?;
|
||||
let (a0, _) = seg_ends(first)?;
|
||||
let (_, b1) = seg_ends(last)?;
|
||||
// Half-way along the run (the middle of its middle segment), each wall over its piece.
|
||||
let mid = run.start + segs.len() / 2;
|
||||
let before = path.get(run.start..mid).ok_or_else(|| KernelError::Invalid("path run".into()))?;
|
||||
let at_mid = carried(&pose, before, translate)?;
|
||||
if let Some(m) = path.get(mid) {
|
||||
let half = match *m {
|
||||
crate::PathSeg::Line { a, b } => crate::PathSeg::Line { a, b: a + (b - a) * 0.5 },
|
||||
crate::PathSeg::Arc { a, center, axis, angle } => crate::PathSeg::Arc { a, center, axis, angle: angle * 0.5 },
|
||||
crate::PathSeg::Curve { plane, seg } => crate::PathSeg::Curve { plane, seg: seg.split_half().0 },
|
||||
crate::PathSeg::Cubic { p: ctrl } => crate::PathSeg::Cubic { p: crate::point_path::cubic_split(&ctrl, 0.5).0 },
|
||||
};
|
||||
let there = carried(&at_mid, &[half], translate)?;
|
||||
inside.extend(seeds(&there, pieces, |q| q));
|
||||
}
|
||||
let next = match runs.get(ri + 1).and_then(|r| path.get(r.start)) {
|
||||
Some(s) => Some(tangent(Some(s))?.0),
|
||||
None => closing,
|
||||
};
|
||||
// The plane halving the corner at the run's end, and the turn there.
|
||||
let exit = next.map(|t2| mitre(t1, t2)).transpose()?;
|
||||
let piece = if translate {
|
||||
swept(&pose, pieces, segs, true)?
|
||||
// Each piece's sweep says where it left the profile: half-way along it each wall lies
|
||||
// over its piece, and at its end (less any straight lead-out past the corner) the
|
||||
// profile is where the next run takes it on.
|
||||
let (piece, at_end) = if translate {
|
||||
let (piece, [_, middle, last]) = swept(&pose, pieces, segs, true)?;
|
||||
inside.extend(seeds(&pose, pieces, |q| apply(&middle, q)));
|
||||
(piece, placed(&pose, &last))
|
||||
} else {
|
||||
// On past each mitre: the profile's reach times the tangent of half the turn takes
|
||||
// every point of it to the mitre's plane; a little further, so the cut back runs
|
||||
@@ -690,21 +664,23 @@ mod imp {
|
||||
if post > 0.0 {
|
||||
route.push(crate::PathSeg::Line { a: b1, b: b1 + t1 * post });
|
||||
}
|
||||
let mut piece = swept(&moved_plane(&pose, t0 * -pre), pieces, &route, false)?;
|
||||
let from = moved_plane(&pose, t0 * -pre);
|
||||
let (mut piece, [_, middle, last]) = swept(&from, pieces, &route, false)?;
|
||||
inside.extend(seeds(&from, pieces, |q| apply(&middle, q)));
|
||||
if let Some((at, toward, _)) = entry {
|
||||
piece = combined(&piece, &half_space(at, -toward, size)?, solidcraft_smlib::BooleanOp::Subtract, "sweep mitre")?;
|
||||
}
|
||||
if let Some((m, _)) = exit {
|
||||
piece = combined(&piece, &half_space(b1, m, size)?, solidcraft_smlib::BooleanOp::Subtract, "sweep mitre")?;
|
||||
}
|
||||
piece
|
||||
(piece, moved_plane(&placed(&from, &last), t1 * -post))
|
||||
};
|
||||
body = Some(match body {
|
||||
None => piece,
|
||||
Some(b) => combined(&b, &piece, solidcraft_smlib::BooleanOp::Union, "sweep corner")?,
|
||||
});
|
||||
// The profile at the run's end, then turned through the corner onto the next run.
|
||||
pose = carried(&pose, segs, translate)?;
|
||||
pose = at_end;
|
||||
entry = None;
|
||||
if let (Some(t2), Some((m, turn)), false) = (next, exit, translate) {
|
||||
let k = t1.cross(t2).normalized().ok_or_else(|| KernelError::Invalid("path corner".into()))?;
|
||||
|
||||
@@ -1 +1 @@
|
||||
530ee936a67ef5b91ab2eb507c5d0938afdbe447
|
||||
f6f428bfa2588f9d163ea06dfcf05c3777d9ce57
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! The sweeps block of `capi/include/smlib.h` (`smlib_extrude`, `smlib_revolve`, `smlib_sweep`,
|
||||
//! `smlib_loft`, `smlib_loft_to_point`, `smlib_screw_sweep`, `smlib_sweep_stations`; the fork's
|
||||
//! `smlib_sweep_placed`, `smlib_loft`, `smlib_loft_to_point`, `smlib_screw_sweep`, `smlib_sweep_stations`; the fork's
|
||||
//! `capi/src/smlib_sweeps.cpp`), declared by hand.
|
||||
//! Part of the FFI boundary, like its parent: called only through [`crate::sweeps`], which builds
|
||||
//! the curve lists.
|
||||
@@ -89,6 +89,20 @@ unsafe extern "C" {
|
||||
cap: i32,
|
||||
out: *mut *mut RawBrep,
|
||||
) -> i32;
|
||||
fn smlib_sweep_placed(
|
||||
kinds: *const i64,
|
||||
n_kinds: i64,
|
||||
reals: *const f64,
|
||||
n_reals: i64,
|
||||
path_kinds: *const i64,
|
||||
n_path_kinds: i64,
|
||||
path_reals: *const f64,
|
||||
n_path_reals: i64,
|
||||
frame: i32,
|
||||
cap: i32,
|
||||
out: *mut *mut RawBrep,
|
||||
placed: *mut f64,
|
||||
) -> i32;
|
||||
fn smlib_loft(
|
||||
kinds: *const i64,
|
||||
n_kinds: i64,
|
||||
@@ -152,6 +166,16 @@ pub fn sweep(profile: &List, path: &List, frame: i32, cap: bool) -> Result<Handl
|
||||
new_brep(|out| unsafe { smlib_sweep(k, nk, r, nr, pk, npk, pr, npr, frame, i32::from(cap), out) })
|
||||
}
|
||||
|
||||
/// [`sweep`], with the three rigid motions placing the profile (12 numbers each: a matrix by
|
||||
/// columns, then a translation) written to `placed`.
|
||||
#[cfg(feature = "native")]
|
||||
pub fn sweep_placed(profile: &List, path: &List, frame: i32, cap: bool, placed: &mut [f64; 36]) -> Result<Handle, i32> {
|
||||
let (k, nk, r, nr) = raw(profile)?;
|
||||
let (pk, npk, pr, npr) = raw(path)?;
|
||||
let at = placed.as_mut_ptr();
|
||||
new_brep(|out| unsafe { smlib_sweep_placed(k, nk, r, nr, pk, npk, pr, npr, frame, i32::from(cap), out, at) })
|
||||
}
|
||||
|
||||
#[cfg(feature = "native")]
|
||||
pub fn loft(sections: &List, sizes: &[i64], degree: i32, cap: bool) -> Result<Handle, i32> {
|
||||
let (k, nk, r, nr) = raw(sections)?;
|
||||
@@ -197,6 +221,11 @@ pub fn sweep(_: &List, _: &List, _: i32, _: bool) -> Result<Handle, i32> {
|
||||
Err(NOT_BUILT)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "native"))]
|
||||
pub fn sweep_placed(_: &List, _: &List, _: i32, _: bool, _: &mut [f64; 36]) -> Result<Handle, i32> {
|
||||
Err(NOT_BUILT)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "native"))]
|
||||
pub fn loft(_: &List, _: &[i64], _: i32, _: bool) -> Result<Handle, i32> {
|
||||
Err(NOT_BUILT)
|
||||
|
||||
@@ -160,6 +160,27 @@ impl Brep {
|
||||
ffi::sweeps::sweep(&l.raw(), &p.raw(), frame, cap).map(Brep).map_err(error)
|
||||
}
|
||||
|
||||
/// [`Brep::sweep`], with where the sweep placed the profile: the rigid motions carrying the
|
||||
/// profile as given onto its first section, a section about half way along and its last
|
||||
/// section. Only the sweep knows them (the curve sweep turns the profile on a frame it works
|
||||
/// out from point to point along the path); a sweep that carries on from where this one left
|
||||
/// the profile starts from the last.
|
||||
pub fn sweep_placed(profile: &[Curve], path: &[Curve], frame: SweepFrame, cap: bool) -> Result<(Brep, [Affine; 3])> {
|
||||
init()?;
|
||||
let (l, p) = (List::of(profile)?, List::of(path)?);
|
||||
let frame = match frame {
|
||||
SweepFrame::Follow => ffi::sweeps::SWEEP_FOLLOW,
|
||||
SweepFrame::Translate => ffi::sweeps::SWEEP_TRANSLATE,
|
||||
};
|
||||
let mut placed = [0.0; 36];
|
||||
let brep = ffi::sweeps::sweep_placed(&l.raw(), &p.raw(), frame, cap, &mut placed).map(Brep).map_err(error)?;
|
||||
let motion = |i: usize| {
|
||||
let at = |k: usize| placed.get(12 * i + k).copied().unwrap_or(0.0);
|
||||
Affine { m: [[at(0), at(1), at(2)], [at(3), at(4), at(5)], [at(6), at(7), at(8)]], t: [at(9), at(10), at(11)] }
|
||||
};
|
||||
Ok((brep, [motion(0), motion(1), motion(2)]))
|
||||
}
|
||||
|
||||
/// A loft (skin) through `sections` (at least two closed loops), `degree` (1 ruled, 2 or 3)
|
||||
/// across them.
|
||||
pub fn loft(sections: &[Vec<Curve>], degree: u32, cap: bool) -> Result<Brep> {
|
||||
|
||||
Reference in New Issue
Block a user