Convert to Sheet Metal takes a long thin plate (#1040)
Convert to Sheet Metal without bend edges refused every plate whose bounding-box diagonal reached a thousand times its thickness ("no outline on that face"), and just under that limit a hole smaller than the mesh's sag fell out of the outline and the DXF. The outline's tolerance is now at most a tenth of the thickness: a thin plate is meshed again at that tolerance. Nothing new is stored. Tests: long thin strips, sheets thousands of times longer than thick, and a strip keeping holes down to 1 mm.
This commit is contained in:
1 parent
3341e2d60b
commit
0f4cda0510
2 files changed
+71
-4
No files matched your search
@@ -590,11 +590,22 @@ pub(super) fn sheet_eval(
|
||||
FeatureKind::SheetConvert { body, face, rule, .. } => {
|
||||
let mb = st.body(body).cloned().ok_or_else(|| DocError::Unknown(format!("body `{body}`")))?;
|
||||
let n = planar_normal_at(&mb.body, *face).ok_or_else(|| DocError::Invalid("pick a planar face of the plate".into()))?;
|
||||
let tol = (mb.body.size() * 1e-3).max(1e-3);
|
||||
let mesh = mb.body.tessellate(tol)?;
|
||||
// Thickness: the body's extent along the face normal.
|
||||
let ds: Vec<f64> = mesh.positions.iter().map(|p| (*p - *face).dot(n)).collect();
|
||||
let (lo, hi) = (ds.iter().cloned().fold(f64::MAX, f64::min), ds.iter().cloned().fold(f64::MIN, f64::max));
|
||||
let extent = |mesh: &solidcraft_geom::Mesh| {
|
||||
mesh.positions.iter().map(|p| (*p - *face).dot(n)).fold((f64::MAX, f64::MIN), |(lo, hi), d| (lo.min(d), hi.max(d)))
|
||||
};
|
||||
// The tolerance (the mesh's chords, and how far off the face's plane a point of it may
|
||||
// be) is at most a tenth of the thickness, not only a thousandth of the size. On a long
|
||||
// thin plate that thousandth passes the thickness: the far side's triangles counted as
|
||||
// this face's (no outline was left), and a hole of a smaller radius than that fell out.
|
||||
let mut tol = (mb.body.size() * 1e-3).max(1e-3);
|
||||
let mut mesh = mb.body.tessellate(tol)?;
|
||||
let (mut lo, mut hi) = extent(&mesh);
|
||||
if hi - lo > 0.0 && tol > (hi - lo) * 0.1 {
|
||||
tol = ((hi - lo) * 0.1).max(1e-6);
|
||||
mesh = mb.body.tessellate(tol)?;
|
||||
(lo, hi) = extent(&mesh);
|
||||
}
|
||||
let t = hi - lo;
|
||||
// The face outline: boundary edges of the triangles on that face's plane, where it runs
|
||||
// along a round wall an arc of the wall's circle (not chords: the plate check below
|
||||
|
||||
@@ -745,3 +745,59 @@ fn a_plate_with_holes_drilled_here_converts_with_round_holes() {
|
||||
assert!(circles.len() == 9 && circles.iter().all(|(radius, _)| (radius - r).abs() < 1e-9), "{circles:?}");
|
||||
assert_eq!(dxf_entities(&mut s), (4, 0, 9));
|
||||
}
|
||||
|
||||
/// A box `l` x `w` x `t` with round holes (centre, diameter) drilled through it, converted,
|
||||
/// picked on its top face.
|
||||
fn convert_box(l: f64, w: f64, t: f64, holes: &[([f64; 2], f64)]) -> Session {
|
||||
let mut s = Session::default();
|
||||
run(&mut s, "solid.box", json!({"length": l, "width": w, "height": t, "body_name": "Plate"}));
|
||||
for (c, d) in holes {
|
||||
run(&mut s, "solid.hole", json!({"position": [c[0], c[1], t], "diameter": d}));
|
||||
}
|
||||
run(&mut s, "sheet.convert", json!({"body": "Plate", "face": [l * 0.4, w * 0.25, t]}));
|
||||
s
|
||||
}
|
||||
|
||||
/// A long thin strip converts. 1200 x 100 x 1 was refused with "no outline on that face": the
|
||||
/// outline's tolerance was a thousandth of the body's size (1.2), more than the thickness, so the
|
||||
/// far side's triangles counted as the picked face's and no boundary was left.
|
||||
#[test]
|
||||
fn a_long_thin_strip_converts() {
|
||||
let mut s = convert_box(1200.0, 100.0, 1.0, &[]);
|
||||
assert!((volume(&mut s) - 120_000.0).abs() < 1e-6 * 120_000.0, "{}", volume(&mut s));
|
||||
assert!(same_size(flat_size(&mut s), (1200.0, 100.0), 1e-6), "{:?}", flat_size(&mut s));
|
||||
let t = flat(&mut s)["flat_size_mm"][2].as_f64().unwrap_or(f64::NAN);
|
||||
assert!((t - 1.0).abs() < 1e-9, "{t}");
|
||||
assert_eq!(dxf_entities(&mut s), (4, 0, 0));
|
||||
}
|
||||
|
||||
/// Plates further past that, a thousandth of their size two to six times their thickness: a
|
||||
/// full sheet, a foil, a long bar.
|
||||
#[test]
|
||||
fn plates_thousands_of_times_longer_than_thick_convert() {
|
||||
for (l, w, t) in [(3000.0, 1500.0, 0.5), (400.0, 100.0, 0.2), (6000.0, 20.0, 1.0)] {
|
||||
let mut s = convert_box(l, w, t, &[]);
|
||||
let want = l * w * t;
|
||||
assert!((volume(&mut s) - want).abs() < 1e-6 * want, "{l} x {w} x {t}: {} vs {want}", volume(&mut s));
|
||||
assert!(same_size(flat_size(&mut s), (l, w), 1e-6), "{l} x {w} x {t}: {:?}", flat_size(&mut s));
|
||||
assert_eq!(dxf_entities(&mut s), (4, 0, 0), "{l} x {w} x {t}");
|
||||
}
|
||||
}
|
||||
|
||||
/// The strip with round holes down to its thickness across keeps every one as a circle. At 1200
|
||||
/// long it was refused; just under the old limit, 990 long, it converted without the 1 mm hole:
|
||||
/// the outline's chords could sag 0.99, more than that hole's radius, and it fell out.
|
||||
#[test]
|
||||
fn a_long_thin_strip_keeps_its_small_round_holes() {
|
||||
let d = [10.0, 4.0, 2.0, 1.0];
|
||||
for l in [990.0, 1200.0] {
|
||||
let holes: Vec<([f64; 2], f64)> = d.iter().enumerate().map(|(i, d)| ([l * (i as f64 + 1.0) / 5.0, 50.0], *d)).collect();
|
||||
let mut s = convert_box(l, 100.0, 1.0, &holes);
|
||||
let want = l * 100.0 - PI / 4.0 * d.iter().map(|d| d * d).sum::<f64>();
|
||||
assert!((volume(&mut s) - want).abs() < 1e-5 * want, "{l}: {} vs {want}", volume(&mut s));
|
||||
let mut radii: Vec<f64> = flat_circles(&mut s).iter().map(|c| c.0).collect();
|
||||
radii.sort_by(f64::total_cmp);
|
||||
assert!(radii.len() == 4 && radii.iter().zip([0.5, 1.0, 2.0, 5.0]).all(|(a, b)| (a - b).abs() < 1e-9), "{l}: {radii:?}");
|
||||
assert_eq!(dxf_entities(&mut s), (4, 0, 4), "{l}");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user