Files
OneCAD/crates/engine/src/cmd/sheet_tests.rs
T

144 lines
7.6 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use std::f64::consts::{FRAC_PI_2, PI};
use serde_json::{Value, json};
use crate::Session;
fn run(s: &mut Session, id: &str, p: Value) -> Value {
match s.execute(id, &p) {
Ok(v) => v,
Err(e) => panic!("{id} {p}: {e}"),
}
}
fn volume(s: &mut Session) -> f64 {
run(s, "MeasureCommand", json!({}))["total"]["volume_mm3"].as_f64().unwrap_or(f64::NAN)
}
fn rel(a: f64, b: f64) -> f64 {
(a - b).abs() / b.abs().max(1e-12)
}
fn flat(s: &mut Session) -> Value {
run(s, "FusionSheetMetalFlatPatternCmd", json!({}))
}
/// A w x h base flange on XY.
fn base(s: &mut Session, w: f64, h: f64) {
run(s, "SketchCreate", json!({"plane": "XY", "name": "Base"}));
run(s, "ShapeRectangleTwoPoint", json!({"p0": [0, 0], "p1": [w, h]}));
run(s, "SketchStop", json!({}));
run(s, "FusionSheetMetalFlangeCommand", json!({"sketch": "Base"}));
}
#[test]
fn base_and_edge_flange_follow_the_k_factor() {
let mut s = Session::default();
base(&mut s, 100.0, 60.0);
assert!(rel(volume(&mut s), 15000.0) < 1e-9);
run(&mut s, "FusionSheetMetalFlangeCommand", json!({"edges": [[50, 0, 2.5]], "height": 20}));
// Oracle 68: flat = 60 − 5 + (π/2)(2.5 + 0.44·2.5) + 20 − 5.
let want = 60.0 - 5.0 + FRAC_PI_2 * (2.5 + 0.44 * 2.5) + 15.0;
let f = flat(&mut s);
assert!((f["flat_size_mm"][1].as_f64().unwrap_or(0.0) - want).abs() < 1e-6, "{f}");
assert_eq!(f["bends"][0]["direction"], "up");
assert!(rel(volume(&mut s), 18972.622) < 1e-4, "{}", volume(&mut s));
let m = run(&mut s, "MeasureCommand", json!({}));
assert_eq!(m["total"]["faces"], 14, "{m}");
// The angle is a parameter: 45° (oracle 70 uses height 15).
let names = s.doc.features.last().map(|f| f.param_names.clone()).unwrap_or_default();
run(&mut s, "ChangeParameterCommand", json!({"name": names[0], "expression": "15"}));
run(&mut s, "ChangeParameterCommand", json!({"name": names[1], "expression": "45 deg"}));
assert!(rel(volume(&mut s), 18450.777) < 1e-4, "{}", volume(&mut s));
assert!((flat(&mut s)["flat_size_mm"][1].as_f64().unwrap_or(0.0) - 73.685298).abs() < 1e-5);
// DXF: outline and an up bend line.
let d = run(&mut s, "sheet.export_dxf", json!({}));
let text = d["dxf"].as_str().unwrap_or_default();
assert!(text.contains("BEND_UP") && text.contains("OUTLINE"), "{text}");
assert!(s.execute("FusionSheetMetalFlangeCommand", &json!({"edges": [[50, 30, 40]], "height": 20})).is_err(), "not on an edge");
}
#[test]
fn rules_are_parameters() {
let mut s = Session::default();
run(&mut s, "ChangeParameterCommand", json!({"name": "Thick", "expression": "1.5 mm"}));
run(&mut s, "FusionSheetMetalRulesCommand", json!({"name": "Custom", "thickness": "Thick", "k_factor": 0.33, "bend_radius": "3 mm"}));
base(&mut s, 80.0, 50.0);
run(&mut s, "FusionSheetMetalFlangeCommand", json!({"edges": [[40, 50, 1.5], [40, 0, 1.5]], "height": 20}));
// Oracle 71: 50 − 9 + 2·(π/2)(3 + 0.495) + 2·15.5 = 82.980.
let want = 50.0 - 2.0 * 4.5 + 2.0 * FRAC_PI_2 * (3.0 + 0.33 * 1.5) + 2.0 * 15.5;
assert!((flat(&mut s)["flat_size_mm"][1].as_f64().unwrap_or(0.0) - want).abs() < 1e-6);
assert!(rel(volume(&mut s), 10053.717) < 1e-4, "{}", volume(&mut s));
// Thicker: everything follows.
run(&mut s, "ChangeParameterCommand", json!({"name": "Thick", "expression": "2 mm"}));
let want = 50.0 - 2.0 * 5.0 + 2.0 * FRAC_PI_2 * (3.0 + 0.33 * 2.0) + 2.0 * 15.0;
assert!((flat(&mut s)["flat_size_mm"][1].as_f64().unwrap_or(0.0) - want).abs() < 1e-6);
let l = run(&mut s, "sheet.rules", json!({}));
assert_eq!(l["active"], "Custom", "{l}");
assert!(s.execute("FusionSheetMetalRulesCommand", &json!({"name": "Bad", "k_factor": 3})).is_err());
}
#[test]
fn contour_flange_hem_and_tray() {
// Oracle 72.
let mut s = Session::default();
run(&mut s, "SketchCreate", json!({"plane": {"origin": [0, 0, 0], "x_dir": [1, 0, 0], "y_dir": [0, 0, -1]}, "name": "Contour"}));
run(&mut s, "DrawPolyline", json!({"points": [[0, 0], [40, 0], [40, -30], [70, -30]], "ids": ["l1", "l2", "l3"]}));
run(&mut s, "SketchStop", json!({}));
run(&mut s, "FusionSheetMetalFlangeCommand", json!({"sketch": "Contour", "curves": ["l1", "l2", "l3"], "distance": 50}));
assert!(rel(volume(&mut s), 12097.622) < 1e-4, "{}", volume(&mut s));
assert!((flat(&mut s)["flat_size_mm"][0].as_f64().unwrap_or(0.0) - 96.309734).abs() < 1e-5);
// Oracle 73: flange, then a flat hem of 8.
let mut s = Session::default();
base(&mut s, 100.0, 60.0);
run(&mut s, "FusionSheetMetalFlangeCommand", json!({"edges": [[50, 0, 2.5]], "height": 20}));
run(&mut s, "FusionSheetMetalHemFlangeCommand", json!({"edges": [[50, 0, 20]], "length": 8}));
assert!(rel(volume(&mut s), 21334.723) < 1e-4, "{}", volume(&mut s));
let b = flat(&mut s)["bends"].clone();
assert!(b.as_array().is_some_and(|a| a.iter().any(|x| (x["angle_deg"].as_f64().unwrap_or(0.0) - 180.0).abs() < 1e-9)), "{b}");
// A tray: four flanges at once stop at each other's bends.
let mut s = Session::default();
base(&mut s, 100.0, 60.0);
run(&mut s, "FusionSheetMetalFlangeCommand", json!({"edges": [[50, 60, 2.5], [100, 30, 2.5], [50, 0, 2.5], [0, 30, 2.5]], "height": 25}));
let f = flat(&mut s);
// Oracle 69: 100 − 10 + 2·5.655 + 2·20 = 141.310 across.
assert!((f["flat_size_mm"][0].as_f64().unwrap_or(0.0) - (90.0 + 2.0 * FRAC_PI_2 * 3.6 + 40.0)).abs() < 1e-6, "{f}");
let v = volume(&mut s);
// Each flange runs on past its bend toward the corner until the inner corners are the
// rule's gap apart (diagonally): 5 − (2.5 + 2.5/√2) = 0.732 at each of 8 ends.
let section = PI / 4.0 * (25.0 - 6.25) + 2.5 * 20.0;
let ext = 5.0 - (2.5 + 2.5 / std::f64::consts::SQRT_2);
let want = 90.0 * 50.0 * 2.5 + section * (2.0 * 90.0 + 2.0 * 50.0) + 8.0 * ext * 2.5 * 20.0;
assert!(rel(v, want) < 1e-4 && rel(v, 29666.234) < 1e-4, "{v} {want}");
}
#[test]
fn unfold_cut_refold_and_convert() {
// Oracle 74: two flanges, unfold, a slot across the bend, refold.
let mut s = Session::default();
base(&mut s, 80.0, 50.0);
run(&mut s, "FusionSheetMetalFlangeCommand", json!({"edges": [[40, 50, 2.5], [40, 0, 2.5]], "height": 20}));
let folded = volume(&mut s);
run(&mut s, "FusionSheetmetalUnfoldCommand", json!({}));
let m = run(&mut s, "MeasureCommand", json!({}));
assert!((m["bodies"][0]["bbox"]["max"][2].as_f64().unwrap_or(0.0) - 2.5).abs() < 1e-6, "{m}");
run(&mut s, "SketchCreate", json!({"plane": "XY", "name": "Slot"}));
run(&mut s, "ShapeRectangleTwoPoint", json!({"p0": [35, -12], "p1": [45, 10]}));
run(&mut s, "SketchStop", json!({}));
run(&mut s, "Extrude", json!({"sketch": "Slot", "through_all": true, "operation": "cut", "direction": "symmetric"}));
run(&mut s, "sheet.refold", json!({}));
let v = volume(&mut s);
assert!(v < folded - 300.0, "{v} {folded}");
assert!(rel(v, 15800.304) < 1e-4, "{v}");
assert_eq!(flat(&mut s)["cutouts"].as_array().map(Vec::len), Some(1));
// Convert a plate.
let mut s = Session::default();
run(&mut s, "PrimitiveBox", json!({"length": 100, "width": 60, "height": 2.5, "body_name": "Plate"}));
run(&mut s, "ConvertToSheetMetalCmd", json!({"body": "Plate", "face": [50, 30, 2.5]}));
run(&mut s, "FusionSheetMetalFlangeCommand", json!({"edges": [[50, 0, 2.5]], "height": 20}));
assert!(rel(volume(&mut s), 18972.622) < 1e-4, "{}", volume(&mut s));
let mut t = Session::default();
run(&mut t, "PrimitiveCylinder", json!({"radius": 10, "height": 30, "body_name": "Rod"}));
assert!(t.execute("ConvertToSheetMetalCmd", &json!({"body": "Rod", "face": [0, 0, 30]})).is_err());
}