Rolling-ball blends: a strip ends on its joint where its neighbour is too short to lend sections (#1667) (#1702)
Thanks @erkexzcx.
This commit is contained in:
1 parent
37ebe24839
commit
77b33efd8f
2 files changed
+53
-14
No files matched your search
@@ -1723,10 +1723,14 @@ fn curve_blend_sized(solid: &Solid, ids: &[mt::EdgeID], size: f64, size_law: Siz
|
||||
continue;
|
||||
}
|
||||
let h = length(k) / (samples - 1) as f64;
|
||||
let spaced = |nb: usize, from_end: bool| -> Result<Option<Vec<Section>>> {
|
||||
// A neighbour shorter than two of the strip's steps lends none: its own sections are
|
||||
// spaced far closer (a small round beside a long line), and borrowing them made the
|
||||
// strip overshoot and fold back on itself past the joint, which its faces' meshes
|
||||
// then folded with (#1667). The strip ends on the joint's section on its own.
|
||||
let spaced = |nb: usize, from_end: bool| -> Result<Vec<Section>> {
|
||||
let l = length(nb);
|
||||
if !(l > 2.0 * h && h > 0.0) {
|
||||
return Ok(None);
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let mut out = Vec::with_capacity(2);
|
||||
for j in [1.0, 2.0] {
|
||||
@@ -1735,7 +1739,7 @@ fn curve_blend_sized(solid: &Solid, ids: &[mt::EdgeID], size: f64, size_law: Siz
|
||||
if from_end {
|
||||
out.reverse();
|
||||
}
|
||||
Ok(Some(out))
|
||||
Ok(out)
|
||||
};
|
||||
let (prev, next) = ((k + n - 1) % n, (k + 1) % n);
|
||||
let list = esec.get(k).ok_or_else(|| KernelError::Failed("section".into()))?;
|
||||
@@ -1748,23 +1752,22 @@ fn curve_blend_sized(solid: &Solid, ids: &[mt::EdgeID], size: f64, size_law: Siz
|
||||
_ => false,
|
||||
};
|
||||
let before = match (closed || k > 0) && rolls(prev) {
|
||||
true => spaced(prev, true)?
|
||||
.map(|b| if smooth([list.first(), list.get(1), list.get(2)], [list.first(), b.get(1), b.first()]) { b } else { Vec::new() }),
|
||||
false => Some(Vec::new()),
|
||||
true => {
|
||||
let b = spaced(prev, true)?;
|
||||
if smooth([list.first(), list.get(1), list.get(2)], [list.first(), b.get(1), b.first()]) { b } else { Vec::new() }
|
||||
}
|
||||
false => Vec::new(),
|
||||
};
|
||||
let after = match (closed || k + 1 < n) && rolls(next) {
|
||||
true => spaced(next, false)?.map(|a| {
|
||||
true => {
|
||||
let a = spaced(next, false)?;
|
||||
let m = list.len();
|
||||
let tail = [list.last(), m.checked_sub(2).and_then(|i| list.get(i)), m.checked_sub(3).and_then(|i| list.get(i))];
|
||||
if smooth(tail, [list.last(), a.first(), a.get(1)]) { a } else { Vec::new() }
|
||||
}),
|
||||
false => Some(Vec::new()),
|
||||
}
|
||||
false => Vec::new(),
|
||||
};
|
||||
let fit = match (before, after) {
|
||||
(Some(b), Some(a)) => crate::guard("fit curve blend", || fit_strip(list, &b, &a, round))?,
|
||||
_ => crate::guard("fit curve blend", || fit_sections(&esec, k, closed, round))?,
|
||||
};
|
||||
let mut fit = fit;
|
||||
let mut fit = crate::guard("fit curve blend", || fit_strip(list, &before, &after, round))?;
|
||||
fit.span = guides.get(k).and_then(Option::as_ref).map(|g| g.span).unwrap_or((0.0, 1.0));
|
||||
fits.push(Some(fit));
|
||||
}
|
||||
|
||||
@@ -693,3 +693,39 @@ fn chamfer_a_chain_of_a_line_and_an_arc_into_a_slanted_end() {
|
||||
let got = volume(&body) - volume(&made);
|
||||
assert!((got - want).abs() < want * 1e-3, "removed {got} vs {want}");
|
||||
}
|
||||
|
||||
/// #1667: an R1 round around the end of a long bar whose four long edges are R5. The chain is
|
||||
/// 290 mm lines and quarter turns of 7.85 mm, so the lines' sections are spaced far wider than
|
||||
/// the turns'; borrowing the turns' sections for the lines' ends made their strips overshoot and
|
||||
/// fold back past the joints, the strips' meshes folded with them, and the measured volume came
|
||||
/// out thousands of mm³ off. The R1 pipe is exact: the corner's section, `1 - π/4`, along the
|
||||
/// lines, and round each turn's axis at the section's centroid (Pappus).
|
||||
#[test]
|
||||
fn round_along_long_lines_and_small_turns_measures_its_volume() {
|
||||
use std::f64::consts::{FRAC_PI_2, FRAC_PI_4};
|
||||
let (w, d, h) = (300.0, 60.0, 40.0);
|
||||
let bar = crate::build::box_solid(Vec3::new(-w / 2.0, 0.0, 0.0), Vec3::new(w / 2.0, d, h)).unwrap();
|
||||
let long = [Vec3::new(-w / 2.0, d / 2.0, 0.0), Vec3::new(w / 2.0, d / 2.0, 0.0), Vec3::new(-w / 2.0, d / 2.0, h), Vec3::new(w / 2.0, d / 2.0, h)];
|
||||
let rounded = fillet(&bar, &long, 5.0).unwrap();
|
||||
// The rolling ball along the end face's loop (the exact blends would take this loop; a
|
||||
// loop next to free-form faces, as in the issue, comes here).
|
||||
let ids: Vec<mt::EdgeID> = crate::body::Body::unique_edges(&rounded.solid)
|
||||
.into_iter()
|
||||
.filter(|e| [e.front(), e.back()].iter().all(|v| crate::blend::vtx(v).y.abs() < 1e-9))
|
||||
.map(|e| e.id())
|
||||
.collect();
|
||||
assert_eq!(ids.len(), 8);
|
||||
let (solid, _) = crate::guard("test", || curve_blend(&rounded.solid, &ids, rounded.size(), 1.0, true)).unwrap();
|
||||
let body = Body::new(solid).unwrap();
|
||||
assert!(body.validity().is_empty(), "{:?}", body.validity());
|
||||
let section = 1.0 - FRAC_PI_4;
|
||||
// The section's centroid lies this far from its corner along each side.
|
||||
let inset = (5.0 / 6.0 - FRAC_PI_4) / section;
|
||||
let lines = 2.0 * (w - 10.0) + 2.0 * (h - 10.0);
|
||||
let turns = 4.0 * FRAC_PI_2 * (5.0 - inset);
|
||||
let want = w * d * h - 4.0 * 25.0 * section * d - section * (lines + turns);
|
||||
let m = crate::measure(&body).unwrap();
|
||||
assert!((m.volume - want).abs() < 1e-5 * want, "{} vs {want}", m.volume);
|
||||
// Centred across the bar, as the part is.
|
||||
assert!(m.centroid.x.abs() < 1e-3, "centre of mass x {}", m.centroid.x);
|
||||
}
|
||||
Reference in new issue
Block a user