Sheet metal: a cut names the pieces it parts from the base (#1338)
A sheet-metal cut that parts pieces from the base names them, and the guide explains it.
This commit is contained in:
1 parent
6fa9e0d2dd
commit
e95e32d72a
5 files changed
+341
-15
No files matched your search
@@ -4147,3 +4147,149 @@ fn perforate_supports_a_square_pattern_and_refuses_impossible_values() {
|
||||
assert!(s.execute("sheet.perforate", &bad).is_err(), "{bad} must be refused");
|
||||
}
|
||||
}
|
||||
|
||||
/// A 100 x 60 tray with one 45° flange 40 high on its y = 0 edge (2.5 mm, R 2.5, K 0.44), and on
|
||||
/// it a return 10 high when asked. Returns the far edge of the last panel in the flat pattern:
|
||||
/// the base starts at the setback s = (t + R)·tan 22.5°, each bend's zone is (π/4)·(R + K·t) long,
|
||||
/// and a panel h high is h − s long, or h − 2s with a return on its far edge.
|
||||
fn tray_with_a_45_degree_flange(s: &mut Session, with_return: bool) -> f64 {
|
||||
base(s, 100.0, 60.0);
|
||||
run(s, "sheet.flange", json!({"edges": [[50, 0, 2.5]], "height": 40, "angle": 45}));
|
||||
let (setback, ba) = (5.0 * (PI / 8.0).tan(), PI / 4.0 * (2.5 + 0.44 * 2.5));
|
||||
if with_return {
|
||||
run(s, "sheet.flange", json!({"edges": [[50, -27.5, 26.5]], "height": 10, "angle": 45}));
|
||||
return setback - ba - (40.0 - 2.0 * setback) - ba - (10.0 - setback);
|
||||
}
|
||||
setback - ba - (40.0 - setback)
|
||||
}
|
||||
|
||||
/// A point on that flange's inside face, the plane y + z = 2.5·√2 (tangent to the bend's inside
|
||||
/// radius, 2.5 about y = 5·tan 22.5°, z = 5).
|
||||
const FLANGE_FACE: [f64; 3] = [50.0, 2.5 * std::f64::consts::SQRT_2 - 15.0, 15.0];
|
||||
|
||||
fn last_warning(s: &Session) -> String {
|
||||
s.model.results.last().and_then(|r| r.warning.clone()).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn parted_note(area: f64, at: [f64; 2]) -> String {
|
||||
format!("1 piece of `Body1` is no longer joined to its base ({area:.2} mm², near ({:.1}, {:.1}) in the flat pattern); it is kept", at[0], at[1])
|
||||
}
|
||||
|
||||
fn flat_pieces(s: &Session) -> Vec<f64> {
|
||||
s.model.state().sheets[0].flat_regions().unwrap().iter().map(solidcraft_geom::Region2::area).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_cut_that_parts_a_flange_from_the_base_keeps_it_and_says_so_folded_or_flat() {
|
||||
// A slot across the flange beside its bend (flat y −3 to −1.5) leaves the flange's panel past
|
||||
// it, with any return on it, joined to nothing: 100 × (−3 − far). Cut on the folded flange's
|
||||
// face or in the unfolded sheet, the piece stays, and the cut names it in its reply and on the
|
||||
// timeline, with the middle of the piece in the flat pattern.
|
||||
for (with_return, unfolded) in [(false, false), (false, true), (true, false), (true, true)] {
|
||||
let what = format!("return {with_return}, unfolded {unfolded}");
|
||||
let mut s = Session::default();
|
||||
let far = tray_with_a_45_degree_flange(&mut s, with_return);
|
||||
let out = if unfolded {
|
||||
run(&mut s, "sheet.unfold", json!({}));
|
||||
rectangle_tool(&mut s, "Slot", [-5.0, -3.0], [105.0, -1.5]);
|
||||
run(&mut s, "solid.extrude", json!({"sketch": "Slot", "through_all": true, "direction": "symmetric", "operation": "cut"}))
|
||||
} else {
|
||||
run(&mut s, "sketch.create", json!({"plane": {"face": FLANGE_FACE}, "name": "Slot"}));
|
||||
run(&mut s, "sketch.rectangle.two_point", json!({"p0": [-5, -3], "p1": [105, -1.5]}));
|
||||
run(&mut s, "sketch.finish", json!({}));
|
||||
run(&mut s, "solid.extrude", json!({"sketch": "Slot", "through_all": true, "direction": "negative", "operation": "cut"}))
|
||||
};
|
||||
let note = parted_note(100.0 * (-3.0 - far), [50.0, (far - 3.0) / 2.0]);
|
||||
assert!(last_warning(&s).contains(¬e), "{what}: {}", last_warning(&s));
|
||||
assert_eq!(out["warning"], json!(last_warning(&s)), "{what}: {out}");
|
||||
// Both pieces stay, folded and flat: the base with its bend left, and the flange.
|
||||
let pieces = flat_pieces(&s);
|
||||
assert!(pieces.len() == 2 && pieces.iter().any(|a| (a - 100.0 * (-3.0 - far)).abs() < 1e-6), "{what}: {pieces:?}");
|
||||
for command in if unfolded { ["sheet.refold", "sheet.unfold"] } else { ["sheet.unfold", "sheet.refold"] } {
|
||||
run(&mut s, command, json!({}));
|
||||
assert_eq!(flat(&mut s)["outline"].as_array().map(Vec::len), Some(2), "{what} {command}");
|
||||
assert_eq!(flat_pieces(&s), pieces, "{what} {command}");
|
||||
if command == "sheet.unfold" {
|
||||
assert!((volume(&mut s) - pieces.iter().sum::<f64>() * 2.5).abs() < 1e-3, "{what}: {}", volume(&mut s));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_ring_over_a_flanges_edge_names_the_piece_of_its_inside_on_the_flange_but_not_an_island() {
|
||||
// A ring Ø16 / Ø10 cut on the folded flange's face, centred on its far edge: the half of its
|
||||
// inside on the flange (π·5²/2, its centroid 4·5/3π inside the edge) has nothing round it,
|
||||
// and is named. Centred on the flange, its inside is an island, and nothing is named.
|
||||
for (y, named) in [(None, true), (Some(-20.0), false)] {
|
||||
let mut s = Session::default();
|
||||
let far = tray_with_a_45_degree_flange(&mut s, false);
|
||||
let y = y.unwrap_or(far);
|
||||
run(&mut s, "sketch.create", json!({"plane": {"face": FLANGE_FACE}, "name": "Ring"}));
|
||||
run(&mut s, "sketch.circle.center", json!({"center": [50, y], "diameter": 16}));
|
||||
run(&mut s, "sketch.circle.center", json!({"center": [50, y], "diameter": 10}));
|
||||
run(&mut s, "sketch.finish", json!({}));
|
||||
let out = run(
|
||||
&mut s,
|
||||
"solid.extrude",
|
||||
json!({"sketch": "Ring", "profiles": [{"point": [56.5, y]}], "through_all": true, "direction": "negative", "operation": "cut"}),
|
||||
);
|
||||
let warning = out["warning"].as_str().unwrap_or_default().to_string();
|
||||
let note = parted_note(12.5 * PI, [50.0, far + 20.0 / (3.0 * PI)]);
|
||||
assert_eq!(warning.contains(¬e), named, "{y}: {warning}");
|
||||
assert!(named || !warning.contains("no longer joined"), "{y}: {warning}");
|
||||
assert_eq!(flat_pieces(&s).len(), 2, "{y}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cuts_that_split_a_sheet_or_cut_it_beside_the_base_name_only_the_pieces_they_part() {
|
||||
// Unfolded, a strip across tray and flange leaves two pieces that each keep their part of
|
||||
// the base: nothing to name. A box over the smaller piece's base and bend then leaves its
|
||||
// flange panel, 26 × (−3 − far), named; a hole in the base after it names nothing again.
|
||||
let mut s = Session::default();
|
||||
let far = tray_with_a_45_degree_flange(&mut s, false);
|
||||
run(&mut s, "sheet.unfold", json!({}));
|
||||
rectangle_tool(&mut s, "Strip", [70.0, -60.0], [74.0, 70.0]);
|
||||
let out = run(&mut s, "solid.extrude", json!({"sketch": "Strip", "through_all": true, "direction": "symmetric", "operation": "cut"}));
|
||||
assert!(out["warning"].is_null() && last_warning(&s).is_empty(), "{out}");
|
||||
rectangle_tool(&mut s, "Box", [72.0, -3.0], [101.0, 61.0]);
|
||||
let out = run(&mut s, "solid.extrude", json!({"sketch": "Box", "through_all": true, "direction": "symmetric", "operation": "cut"}));
|
||||
assert_eq!(out["warning"], json!(parted_note(26.0 * (-3.0 - far), [87.0, (far - 3.0) / 2.0])), "{out}");
|
||||
run(&mut s, "sketch.create", json!({"plane": "XY", "name": "Hole"}));
|
||||
run(&mut s, "sketch.circle.center", json!({"center": [30, 30], "diameter": 8}));
|
||||
run(&mut s, "sketch.finish", json!({}));
|
||||
let out = run(&mut s, "solid.extrude", json!({"sketch": "Hole", "through_all": true, "direction": "symmetric", "operation": "cut"}));
|
||||
assert!(out["warning"].is_null(), "{out}");
|
||||
run(&mut s, "sheet.refold", json!({}));
|
||||
assert_eq!(flat(&mut s)["outline"].as_array().map(Vec::len), Some(2));
|
||||
// Folded, a slot across the base beside the bend leaves the flange with a strip of the base;
|
||||
// a plain plate cut in two halves. Neither names anything.
|
||||
let mut s = Session::default();
|
||||
tray_with_a_45_degree_flange(&mut s, false);
|
||||
rectangle_tool(&mut s, "Slot", [-5.0, 3.0], [105.0, 6.0]);
|
||||
let out = run(&mut s, "solid.extrude", json!({"sketch": "Slot", "through_all": true, "direction": "symmetric", "operation": "cut"}));
|
||||
assert!(out["warning"].is_null() && flat_pieces(&s).len() == 2, "{out}");
|
||||
let mut s = Session::default();
|
||||
base(&mut s, 100.0, 60.0);
|
||||
rectangle_tool(&mut s, "Strip", [45.0, -5.0], [55.0, 65.0]);
|
||||
let out = run(&mut s, "solid.extrude", json!({"sketch": "Strip", "through_all": true, "direction": "symmetric", "operation": "cut"}));
|
||||
assert!(out["warning"].is_null() && flat_pieces(&s).len() == 2, "{out}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_hole_that_parts_a_flange_from_the_base_names_it() {
|
||||
// A 10 x 60 plate with a 90° flange 40 high on its y = 0 edge: the flange's panel runs from
|
||||
// flat y = 5 − BA (world z = 5) for 35 mm. A Ø12 hole drilled into it at z = 15 (flat
|
||||
// y = 5 − BA − 10) takes its whole width, and leaves the 25 mm below the hole's centre less
|
||||
// the half disc there: 10 × 25 − ∫ √(36 − u²) du over |u| ≤ 5 = 250 − (5√11 + 36·asin(5/6)).
|
||||
let mut s = Session::default();
|
||||
base(&mut s, 10.0, 60.0);
|
||||
run(&mut s, "sheet.flange", json!({"edges": [[5, 0, 2.5]], "height": 40}));
|
||||
let out = run(&mut s, "solid.hole", json!({"position": [5, 0, 15], "diameter": 12}));
|
||||
let area = 250.0 - (5.0 * 11f64.sqrt() + 36.0 * (5.0f64 / 6.0).asin());
|
||||
let warning = out["warning"].as_str().unwrap_or_default();
|
||||
assert!(warning.starts_with(&format!("1 piece of `Body1` is no longer joined to its base ({area:.2} mm², near (5.0, ")), "{warning}");
|
||||
assert_eq!(out["warning"], json!(last_warning(&s)));
|
||||
assert_eq!(flat_pieces(&s).len(), 2);
|
||||
}
|
||||
Reference in new issue
Block a user