Pattern on Path (PatternOnPath: spacing or first-to-last distance, flip, identical or path orientation; instance k moves by path(k·d) − path(0)), rectangular/circular/path patterns of bodies; recipes: path_pattern (oracle 53 passes)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
1 parent
02de01f87c
commit
075808e7ee
8 files changed
+278
-12
No files matched your search
@@ -0,0 +1,75 @@
|
||||
use std::f64::consts::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)
|
||||
}
|
||||
|
||||
/// A 100 x 50 x 5 plate with a hole at the start of a straight path (10,10) → (90,10).
|
||||
fn plate_with_hole() -> Session {
|
||||
let mut s = Session::default();
|
||||
run(&mut s, "PrimitiveBox", json!({"length": 100, "width": 50, "height": 5, "corner": [0, 0, 0]}));
|
||||
run(&mut s, "SketchCreate", json!({"plane": "XY", "name": "Path"}));
|
||||
run(&mut s, "DrawPolyline", json!({"points": [[10, 10], [90, 10]], "ids": ["l1"]}));
|
||||
run(&mut s, "CircleCenterRadius", json!({"center": [10, 10], "radius": 2, "id": "c1"}));
|
||||
run(&mut s, "SketchStop", json!({}));
|
||||
run(&mut s, "Extrude", json!({"sketch": "Path", "through_all": true, "operation": "cut", "name": "Hole1"}));
|
||||
s
|
||||
}
|
||||
|
||||
const PLATE: f64 = 100.0 * 50.0 * 5.0;
|
||||
const HOLE: f64 = PI * 4.0 * 5.0;
|
||||
|
||||
#[test]
|
||||
fn pattern_on_path_spacing_extent_and_flip() {
|
||||
let mut s = plate_with_hole();
|
||||
assert!(rel(volume(&mut s), PLATE - HOLE) < 1e-4);
|
||||
run(&mut s, "PatternOnPath", json!({"features": ["Hole1"], "path_sketch": "Path", "path": ["l1"], "count": 5, "spacing": 20}));
|
||||
assert!(rel(volume(&mut s), PLATE - 5.0 * HOLE) < 1e-4);
|
||||
// The count is a parameter like any other input.
|
||||
let n = s.doc.features.last().and_then(|f| f.param_names.first().cloned()).unwrap_or_default();
|
||||
run(&mut s, "ChangeParameterCommand", json!({"name": n, "expression": "3"}));
|
||||
assert!(rel(volume(&mut s), PLATE - 3.0 * HOLE) < 1e-4);
|
||||
// Instances past the end of the path are an error.
|
||||
run(&mut s, "ChangeParameterCommand", json!({"name": n, "expression": "6"}));
|
||||
let err = s.model.results.last().and_then(|r| r.error.clone()).unwrap_or_default();
|
||||
assert!(err.contains("past the end of the path"), "{err}");
|
||||
|
||||
// Distance = first to last instance.
|
||||
let mut s = plate_with_hole();
|
||||
run(&mut s, "PatternOnPath", json!({"features": ["Hole1"], "path_sketch": "Path", "path": ["l1"], "count": 3, "distance": 80}));
|
||||
assert!(rel(volume(&mut s), PLATE - 3.0 * HOLE) < 1e-4);
|
||||
|
||||
// Flipped, the path runs from (90,10) back toward (10,10).
|
||||
let mut s = plate_with_hole();
|
||||
run(&mut s, "PatternOnPath", json!({"features": ["Hole1"], "path_sketch": "Path", "path": ["l1"], "count": 2, "spacing": 30, "flip": true}));
|
||||
// The copy moves by path(30) − path(0) = (60,10) − (90,10) = −30: off the plate side → only 1 hole.
|
||||
assert!(rel(volume(&mut s), PLATE - HOLE) < 1e-4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn patterns_of_bodies() {
|
||||
let mut s = Session::default();
|
||||
run(&mut s, "PrimitiveBox", json!({"length": 10, "width": 10, "height": 10, "corner": [20, 0, 0], "body_name": "Cube"}));
|
||||
run(&mut s, "PatternCircular", json!({"bodies": ["Cube"], "axis": "Z", "count": 4}));
|
||||
let m = run(&mut s, "MeasureCommand", json!({}));
|
||||
assert_eq!(m["body_count"], 4, "{m}");
|
||||
assert!(rel(volume(&mut s), 4000.0) < 1e-4);
|
||||
run(&mut s, "PatternRectangular", json!({"bodies": ["Cube"], "dir1": [0, 0, 1], "count1": 3, "spacing1": 20}));
|
||||
assert!(rel(volume(&mut s), 6000.0) < 1e-4);
|
||||
assert!(s.execute("PatternRectangular", &json!({"bodies": ["Nope"], "dir1": [1, 0, 0], "count1": 2, "spacing1": 5})).is_err());
|
||||
}
|
||||
Reference in new issue
Block a user