Sheet metal: tear as a fourth corner relief shape (#809)
A sheet metal rule's corner relief shape can now be tear; saved under format 17 with a note. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bfbf5f9432
commit
dd084ec9e1
11 files changed
+751
-56
No files matched your search
@@ -43,7 +43,8 @@ pub static COMMANDS: &[CommandSpec] = &[
|
||||
CommandSpec::new("sheet.manage_rules", "Sheet Metal Rules", rules).at("SHEET METAL", "MODIFY").icon("sheet_rules").params(
|
||||
"name (new or existing); thickness?, k_factor?, bend_radius?, relief_width?, relief_depth?, corner_relief?, hem_gap?, gap? (expressions; `Thickness` is the rule's; \
|
||||
gap: between flanges that meet at a corner, whose ends are closed toward each other at any angle); \
|
||||
corner_relief_shape?: notch|round|rectangular (what is cut where two bends meet at a corner of the base, `corner_relief` across; default notch); \
|
||||
corner_relief_shape?: notch|round|rectangular|tear (what is cut where two bends meet at a corner of the base: round and rectangular a relief \
|
||||
`corner_relief` across; tear none, each bend's end is cut straight from the corner to its flange's end; default notch); \
|
||||
active?: bool; delete?: bool",
|
||||
),
|
||||
CommandSpec::new("sheet.rules", "List Sheet Metal Rules", list_rules).noundo().doc_only().params("→ rules with their expressions and values, and the active one"),
|
||||
@@ -323,7 +324,7 @@ fn rules(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
if let Some(shape) = p.get("corner_relief_shape").filter(|v| !v.is_null()) {
|
||||
let shape = shape.as_str().and_then(solidcraft_doc::sheet::ReliefShape::parse);
|
||||
rule.corner_relief_shape = shape.ok_or_else(|| bad(cmd, "`corner_relief_shape` must be notch, round or rectangular"))?.name().into();
|
||||
rule.corner_relief_shape = shape.ok_or_else(|| bad(cmd, "`corner_relief_shape` must be notch, round, rectangular or tear"))?.name().into();
|
||||
}
|
||||
let (vals, _) = s.doc.param_values();
|
||||
let values = s.doc.rule_values(&vals, &rule).map_err(|e| bad(cmd, e.to_string()))?;
|
||||
|
||||
@@ -2478,6 +2478,76 @@ fn corner_reliefs_of_the_rule_reach_the_part_the_flat_pattern_and_the_dxf() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_tear_of_the_rule_reaches_the_part_the_flat_pattern_and_the_dxf() {
|
||||
use std::f64::consts::SQRT_2;
|
||||
// The tray at 90° and 72° (see the doc tests): its bends meet at (s0, s0), s0 = 3·tan(θ/2), and
|
||||
// are BA = 2θ wide in the flat. The flange on y = 0 is closed to x_end(w) = g/√2 + T·sinθ − w·cosθ.
|
||||
// Torn, its bend's end is cut from (s0, s0) straight to (x_end(s0), s0 − BA), where the flange's
|
||||
// end starts: grow = s0 − x_end(s0) = sinθ − 0.5/√2 past the corner, instead of square across the
|
||||
// bend and back along the flange's edge (the notch's step).
|
||||
for deg in [90.0f64, 72.0] {
|
||||
let th = deg.to_radians();
|
||||
let (s0, ba) = (3.0 * (th / 2.0).tan(), th * 2.0);
|
||||
let x_end = |w: f64| 0.5 / SQRT_2 + 2.0 * th.sin() - w * th.cos();
|
||||
let grow = s0 - x_end(s0);
|
||||
assert!((grow - (th.sin() - 0.5 / SQRT_2)).abs() < 1e-12);
|
||||
let points = |f: &Value| -> Vec<(f64, f64)> {
|
||||
let line = f["outline"][0].as_array();
|
||||
line.map(|l| l.iter().map(|p| (p[0].as_f64().unwrap_or(f64::NAN), p[1].as_f64().unwrap_or(f64::NAN))).collect()).unwrap_or_default()
|
||||
};
|
||||
let mut notch = Session::default();
|
||||
alu_tray(&mut notch, deg, json!({}));
|
||||
let (v0, stepped) = (volume(&mut notch), points(&flat(&mut notch)));
|
||||
run(&mut notch, "sheet.unfold", json!({}));
|
||||
let flat0 = volume(&mut notch);
|
||||
let mut s = Session::default();
|
||||
alu_tray(&mut s, deg, json!({"corner_relief_shape": "tear"}));
|
||||
assert!(s.model.results.iter().all(|r| r.error.is_none() && r.warning.is_none()), "{deg}: {:?}", s.model.results);
|
||||
let listed = run(&mut s, "sheet.rules", json!({}));
|
||||
assert_eq!(listed["rules"][0]["rule"]["corner_relief_shape"], "tear");
|
||||
assert_eq!(listed["rules"][0]["values"]["corner_relief_shape"], "tear");
|
||||
// The flat pattern, at the corner at the origin: the flange's end, the cut, the corner of the
|
||||
// bends, and the same on the flange on x = 0. The step is gone: a corner less at each of the
|
||||
// eight bend ends.
|
||||
let f = flat(&mut s);
|
||||
let outline = points(&f);
|
||||
let at = |pts: &[(f64, f64)], x: f64, y: f64| pts.iter().any(|p| (p.0 - x).abs() < 1e-6 && (p.1 - y).abs() < 1e-6);
|
||||
assert_eq!(f["outline"].as_array().map(Vec::len), Some(1), "{deg}: {f}");
|
||||
for (x, y) in [(x_end(38.6), s0 - ba - (38.6 - s0)), (x_end(s0), s0 - ba), (s0, s0)] {
|
||||
assert!(at(&outline, x, y) && at(&outline, y, x), "{deg}: ({x}, {y}) not in {outline:?}");
|
||||
}
|
||||
assert!(at(&stepped, s0, s0 - ba) && !at(&outline, s0, s0 - ba) && !at(&outline, s0 - ba, s0), "{deg}");
|
||||
assert_eq!(outline.len() + 8, stepped.len(), "{deg}");
|
||||
// The bend table: every centre line runs between the middles of its bend's two ends.
|
||||
let lengths: Vec<f64> =
|
||||
f["bends"].as_array().map(|b| b.iter().map(|b| b["length"].as_f64().unwrap_or(f64::NAN)).collect()).unwrap_or_default();
|
||||
for (got, edge) in lengths.iter().zip([120.0, 90.0, 120.0, 90.0]) {
|
||||
assert!(lengths.len() == 4 && (got - (edge - 2.0 * s0 + grow)).abs() < 1e-6, "{deg}: {lengths:?}");
|
||||
}
|
||||
// The folded part gains what the flat pattern gains: the triangle BA·grow/2 at each of the
|
||||
// eight bend ends (K = 0.5 keeps the volume of a bend).
|
||||
let (v, want) = (volume(&mut s), 8.0 * ba * grow / 2.0 * 2.0);
|
||||
assert!((v - v0 - want).abs() < 0.05, "{deg}: {v} - {v0} vs {want}");
|
||||
// The DXF: the outline in straight lines, each cut one of them, and four bend lines.
|
||||
let dxf = run(&mut s, "sheet.export_dxf", json!({}))["dxf"].as_str().unwrap_or_default().to_string();
|
||||
assert_eq!(dxf.matches("0\nLINE\n").count(), outline.len() + 4, "{deg}");
|
||||
assert!(dxf.contains(&format!("{:.6}\n20\n{:.6}\n", x_end(s0), s0 - ba)), "{deg}");
|
||||
// Unfold and refold keep it, and a cut made folded still goes through: a Ø20 hole in the base.
|
||||
run(&mut s, "sheet.unfold", json!({}));
|
||||
assert!((volume(&mut s) - flat0 - want).abs() < 1e-6, "{deg}: unfolded {} - {flat0} vs {want}", volume(&mut s));
|
||||
run(&mut s, "sheet.refold", json!({}));
|
||||
assert!((volume(&mut s) - v).abs() < 1e-6 * v, "{deg}");
|
||||
run(&mut s, "sketch.create", json!({"plane": "XY", "name": "Hole"}));
|
||||
run(&mut s, "sketch.circle.center", json!({"center": [60, 45], "diameter": 20}));
|
||||
run(&mut s, "sketch.finish", json!({}));
|
||||
run(&mut s, "solid.extrude", json!({"sketch": "Hole", "distance": 10, "operation": "cut", "through_all": true}));
|
||||
assert!((v - volume(&mut s) - PI * 100.0 * 2.0).abs() < 0.5, "{deg}: {v} - {}", volume(&mut s));
|
||||
let mut reopened = Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap());
|
||||
assert!((volume(&mut reopened) - volume(&mut s)).abs() < 1e-6, "{deg}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_corner_relief_shape_is_a_rule_setting_checked_and_undone() {
|
||||
let mut s = Session::default();
|
||||
@@ -2485,10 +2555,10 @@ fn the_corner_relief_shape_is_a_rule_setting_checked_and_undone() {
|
||||
let shape = |s: &mut Session| run(s, "sheet.rules", json!({}))["rules"][0]["rule"]["corner_relief_shape"].clone();
|
||||
let v0 = volume(&mut s);
|
||||
assert_eq!(shape(&mut s), "notch");
|
||||
// Only the three names are shapes; a refused one changes nothing.
|
||||
// Only the four names are shapes; a refused one changes nothing.
|
||||
for wrong in [json!("star"), json!("square"), json!(""), json!(2)] {
|
||||
let e = s.execute("sheet.manage_rules", &json!({"name": "AL2", "corner_relief_shape": wrong})).map(|_| ()).unwrap_err().to_string();
|
||||
assert!(e.contains("`corner_relief_shape` must be notch, round or rectangular"), "{e}");
|
||||
assert!(e.contains("`corner_relief_shape` must be notch, round, rectangular or tear"), "{e}");
|
||||
}
|
||||
assert!(shape(&mut s) == "notch" && volume(&mut s) == v0);
|
||||
// Changing the rule changes the parts made with it; other settings leave the shape alone.
|
||||
|
||||
Reference in new issue
Block a user