fix: an edge flange runs along what cut-outs leave of its edge (#702)
An edge flange picked on an edge that cut-outs have partly removed is built along what is left of that edge (the stretch nearest the pick), so each side of a notch can get its own flange.
This commit is contained in:
1 parent
c166c64b8e
commit
812669e861
3 files changed
+378
-22
No files matched your search
@@ -1029,6 +1029,152 @@ fn the_bend_table_and_the_dxf_follow_what_flat_cuts_leave_of_a_bend() {
|
||||
assert!(close(&rows(&mut s), &[(1, 40.0, 100.0, 140.0), (1, 40.0, 160.0, 200.0)]), "{:?}", rows(&mut s));
|
||||
}
|
||||
|
||||
/// The flat pattern's area: its outline loops less its cut-outs.
|
||||
fn flat_area(f: &Value) -> f64 {
|
||||
let loops = |k: &str| -> f64 {
|
||||
let all = f[k].as_array().cloned().unwrap_or_default();
|
||||
all.iter()
|
||||
.map(|l| {
|
||||
let pts: Vec<(f64, f64)> = l
|
||||
.as_array()
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.map(|q| (q[0].as_f64().unwrap_or(f64::NAN), q[1].as_f64().unwrap_or(f64::NAN)))
|
||||
.collect();
|
||||
polygon_area(&pts)
|
||||
})
|
||||
.sum()
|
||||
};
|
||||
loops("outline") - loops("cutouts")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_edge_flange_made_after_a_cut_runs_along_the_edge_the_cut_left() {
|
||||
// A 200 x 100 plate, its bottom left 100 x 60 cut away, then a flange picked on what is left of
|
||||
// the bottom edge (x 100 … 200): it used to span the whole original edge, x 0 … 200, its left
|
||||
// half standing beside nothing.
|
||||
let mut s = Session::default();
|
||||
run(&mut s, "sheet.manage_rules", json!({"name": "R", "thickness": "2 mm", "k_factor": "0.5", "bend_radius": "2 mm", "active": true}));
|
||||
base(&mut s, 200.0, 100.0);
|
||||
rectangle_tool(&mut s, "Corner", [-10.0, -10.0], [100.0, 60.0]);
|
||||
run(&mut s, "solid.extrude", json!({"sketch": "Corner", "through_all": true, "direction": "symmetric", "operation": "cut"}));
|
||||
assert!((volume(&mut s) - 14_000.0 * 2.0).abs() < 1e-6);
|
||||
run(&mut s, "sheet.flange", json!({"edges": [[150, 0, 2]], "height": 40, "angle": 90, "radius": 1, "position": "outside"}));
|
||||
assert!(no_errors_or_warnings(&s), "{:?}", s.model.results);
|
||||
let f = flat(&mut s);
|
||||
let bends = f["bends"].as_array().cloned().unwrap_or_default();
|
||||
assert_eq!(bends.len(), 1, "{f}");
|
||||
let x = |k: &str| bends[0][k][0].as_f64().unwrap_or(f64::NAN);
|
||||
assert!(
|
||||
(x("start") - 100.0).abs() < 1e-9 && (x("end") - 200.0).abs() < 1e-9 && (bends[0]["length"].as_f64().unwrap_or(0.0) - 100.0).abs() < 1e-9,
|
||||
"{f}"
|
||||
);
|
||||
assert_eq!(f["outline"].as_array().map(Vec::len), Some(1), "{f}");
|
||||
// The flat pattern: the plate less the cut, and the flange (π of bend, 37 of panel) on 100 mm.
|
||||
assert!((flat_area(&f) - (14_000.0 + 100.0 * (PI + 37.0))).abs() < 1e-6, "{}", flat_area(&f));
|
||||
// Folded: the bend keeps its volume (K 0.5); the flange stands on x 100 … 200 only.
|
||||
let want = (14_000.0 + 100.0 * (PI + 37.0)) * 2.0;
|
||||
assert!((volume(&mut s) - want).abs() < 1e-3 * want, "{} vs {want}", volume(&mut s));
|
||||
assert!(contains(&s, [150.0, -2.0, 20.0]) && !contains(&s, [50.0, -2.0, 20.0]));
|
||||
let state = s.model.state();
|
||||
let body = &state.bodies[0].body;
|
||||
assert!(body.validity().is_empty() && body.shell_count() == 1);
|
||||
for command in ["sheet.unfold", "sheet.refold"] {
|
||||
run(&mut s, command, json!({}));
|
||||
assert!((volume(&mut s) - want).abs() < 1e-3 * want, "{command}: {}", volume(&mut s));
|
||||
}
|
||||
}
|
||||
|
||||
/// A 200 x 100 plate (2 mm, K 0.5) with `cut`, a rectangle between two corners, cut through it.
|
||||
fn plate_with_a_cut(s: &mut Session, cut: [[f64; 2]; 2]) {
|
||||
run(s, "sheet.manage_rules", json!({"name": "R", "thickness": "2 mm", "k_factor": "0.5", "bend_radius": "1 mm", "active": true}));
|
||||
base(s, 200.0, 100.0);
|
||||
rectangle_tool(s, "Cut", cut[0], cut[1]);
|
||||
run(s, "solid.extrude", json!({"sketch": "Cut", "through_all": true, "direction": "symmetric", "operation": "cut"}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edge_flanges_on_either_side_of_a_notch_span_the_side_they_were_picked_on() {
|
||||
// A notch 40 wide and 30 deep in the middle of the bottom edge leaves x 0 … 80 and 120 … 200 of
|
||||
// it. A 90° flange 40 high set inside (setback 3, BA π, panel 37) spans the side it was picked
|
||||
// on and takes its setback off that side alone; picked on both sides, each has its own.
|
||||
for (edges, sides) in
|
||||
[(json!([[160, 0, 2]]), [false, true]), (json!([[40, 0, 2]]), [true, false]), (json!([[40, 0, 2], [160, 0, 2]]), [true, true])]
|
||||
{
|
||||
let mut s = Session::default();
|
||||
plate_with_a_cut(&mut s, [[80.0, -10.0], [120.0, 30.0]]);
|
||||
run(&mut s, "sheet.flange", json!({"edges": edges, "height": 40, "angle": 90}));
|
||||
assert!(no_errors_or_warnings(&s), "{edges}: {:?}", s.model.results);
|
||||
let f = flat(&mut s);
|
||||
let want: Vec<(f64, f64)> = [(0.0, 80.0), (120.0, 200.0)].into_iter().zip(sides).filter_map(|(span, on)| on.then_some(span)).collect();
|
||||
let bends: Vec<(f64, f64)> = f["bends"]
|
||||
.as_array()
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.map(|b| (b["start"][0].as_f64().unwrap_or(f64::NAN), b["end"][0].as_f64().unwrap_or(f64::NAN)))
|
||||
.collect();
|
||||
assert!(
|
||||
bends.len() == want.len() && bends.iter().zip(&want).all(|(b, w)| (b.0 - w.0).abs() < 1e-9 && (b.1 - w.1).abs() < 1e-9),
|
||||
"{edges}: {f}"
|
||||
);
|
||||
assert_eq!(f["outline"].as_array().map(Vec::len), Some(1), "{edges}: {f}");
|
||||
// The plate less the notch, and for each flange 80 × (π + 37) less the 80 × 3 it set back.
|
||||
let area = 20_000.0 - 40.0 * 30.0 + want.len() as f64 * 80.0 * (PI + 37.0 - 3.0);
|
||||
assert!((flat_area(&f) - area).abs() < 1e-6, "{edges}: {}", flat_area(&f));
|
||||
assert!(rel(volume(&mut s), area * 2.0) < 1e-4, "{edges}: {} vs {}", volume(&mut s), area * 2.0);
|
||||
// A flange stands with its outer face on the edge (y 0 … 2); a side without one keeps the
|
||||
// base up to the edge, which the bend's round outer corner leaves free.
|
||||
for (x, on) in [40.0, 160.0].into_iter().zip(sides) {
|
||||
assert_eq!(contains(&s, [x, 1.0, 20.0]), on, "{edges}: a flange at x {x}");
|
||||
assert_eq!(contains(&s, [x, 0.2, 0.2]), !on, "{edges}: the base's own edge at x {x}");
|
||||
}
|
||||
let state = s.model.state();
|
||||
let body = &state.bodies[0].body;
|
||||
assert!(body.validity().is_empty() && body.shell_count() == 1, "{edges}");
|
||||
for command in ["sheet.unfold", "sheet.refold"] {
|
||||
run(&mut s, command, json!({}));
|
||||
assert!(rel(volume(&mut s), area * 2.0) < 1e-4, "{edges}, {command}: {}", volume(&mut s));
|
||||
}
|
||||
}
|
||||
// An edge that a cut has taken whole is refused.
|
||||
let mut s = Session::default();
|
||||
plate_with_a_cut(&mut s, [[-10.0, -10.0], [210.0, 30.0]]);
|
||||
let e = s.execute("sheet.flange", &json!({"edges": [[100, 0, 2]], "height": 40})).map(|_| ()).unwrap_err().to_string();
|
||||
assert!(e.contains("a cut-out has taken that edge away"), "{e}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_edge_flange_on_a_flange_runs_along_what_a_flat_cut_left_of_its_far_edge() {
|
||||
// A 40 high wall on the plate's bottom edge (outside: BA π, panel 37), its far edge at
|
||||
// y = -(π + 37) in the flat. Unfolded, a cut takes x 0 … 60 of that edge, up to y = -30. A
|
||||
// 15 high flange picked on the rest of the edge (the wall's inner face is y = -1, its top
|
||||
// z = 40) spans x 60 … 200.
|
||||
let mut s = Session::default();
|
||||
run(&mut s, "sheet.manage_rules", json!({"name": "R", "thickness": "2 mm", "k_factor": "0.5", "bend_radius": "1 mm", "active": true}));
|
||||
base(&mut s, 200.0, 100.0);
|
||||
run(&mut s, "sheet.flange", json!({"edges": [[100, 0, 2]], "height": 40, "angle": 90, "position": "outside"}));
|
||||
run(&mut s, "sheet.unfold", json!({}));
|
||||
rectangle_tool(&mut s, "Corner", [-10.0, -50.0], [60.0, -30.0]);
|
||||
run(&mut s, "solid.extrude", json!({"sketch": "Corner", "through_all": true, "direction": "symmetric", "operation": "cut"}));
|
||||
run(&mut s, "sheet.refold", json!({}));
|
||||
run(&mut s, "sheet.flange", json!({"edges": [[130, -1, 40]], "height": 15, "angle": 90, "position": "outside"}));
|
||||
assert!(no_errors_or_warnings(&s), "{:?}", s.model.results);
|
||||
let f = flat(&mut s);
|
||||
let bends = f["bends"].as_array().cloned().unwrap_or_default();
|
||||
let spans: Vec<(f64, f64)> =
|
||||
bends.iter().map(|b| (b["start"][0].as_f64().unwrap_or(f64::NAN), b["end"][0].as_f64().unwrap_or(f64::NAN))).collect();
|
||||
let close = |a: (f64, f64), b: (f64, f64)| (a.0 - b.0).abs() < 1e-9 && (a.1 - b.1).abs() < 1e-9;
|
||||
assert!(spans.len() == 2 && close(spans[0], (0.0, 200.0)) && close(spans[1], (60.0, 200.0)), "{f}");
|
||||
// The plate, the wall less the cut's 60 × (π + 7), and 140 × (π + 12) of the flange on it.
|
||||
let area = 20_000.0 + 200.0 * (PI + 37.0) - 60.0 * (PI + 7.0) + 140.0 * (PI + 12.0);
|
||||
assert!((flat_area(&f) - area).abs() < 1e-6, "{}", flat_area(&f));
|
||||
assert!(rel(volume(&mut s), area * 2.0) < 1e-4, "{} vs {}", volume(&mut s), area * 2.0);
|
||||
// It lies over the plate from the wall (z 41 … 43, y 0 … 12), on x 60 … 200 only.
|
||||
assert!(contains(&s, [130.0, 6.0, 42.0]) && !contains(&s, [30.0, 6.0, 42.0]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bend_table_exports_shop_dimensions_and_tracks_parameter_changes() {
|
||||
let mut s = Session::default();
|
||||
|
||||
Reference in new issue
Block a user