mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 10:35:58 +00:00
Naming tests: pieces of one name on a grooved disc, the merged boss wall by its point
SMLib keeps a whole cylinder one face and the arm's round end and the boss over it (one cylinder) one face, named for the arm's end. Three naming tests relied on truck's pieces: - a_cut_through_one_of_two_bosses_names_only_its_own_walls finds the boss's wall by a point on it (the centroid of truck's half cylinder lies on nothing now) and checks it keeps its name through the cut and the fillets; - split_pieces_keep_their_numbers_across_evaluations keeps the torus (its sweep's walls are whole now, their names stable) and adds a disc whose top a round groove splits into F1:top#1 and #2; - a_design_saved_before_pieces_were_numbered_by_place_keeps_their_numbers uses that disc, whose kernel order numbers the pieces the other way round from their place (the post's side of multi-body-join-cut is one face now). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -414,10 +414,10 @@ fn named_at(faces: &[(String, bool, [f64; 3])], at: [f64; 3]) -> String {
|
||||
}
|
||||
|
||||
/// #1209: an arm with a boss at each end (each boss the radius of the arm's round end under it)
|
||||
/// and a square cut through one. The second boss and the cut are prism booleans sewn slab by
|
||||
/// slab, where the arm's round end and the boss over it are two pieces of one cylinder: every
|
||||
/// face keeps exact history, so the cut's walls are its own four faces and the boss's outer
|
||||
/// cylinder keeps its name, through the fillets on the bore's corners too.
|
||||
/// and a square cut through one. The arm's round end and the boss over it lie on one cylinder,
|
||||
/// which SMLib keeps one face, named for the arm's end it began as: every face keeps exact
|
||||
/// history, so the cut's walls are its own four faces and the boss's outer cylinder keeps its
|
||||
/// name, through the fillets on the bore's corners too.
|
||||
#[test]
|
||||
fn a_cut_through_one_of_two_bosses_names_only_its_own_walls() {
|
||||
let mut s = Session::default();
|
||||
@@ -437,8 +437,10 @@ fn a_cut_through_one_of_two_bosses_names_only_its_own_walls() {
|
||||
run(&mut s, "sketch.finish", json!({}));
|
||||
run(&mut s, "solid.extrude", json!({"distance": 16, "body_name": "Arm"}));
|
||||
run(&mut s, "solid.cylinder", json!({"diameter": 46.0, "height": 22, "base": [0, 0, 0], "operation": "join", "targets": ["Arm"]}));
|
||||
let boss = named_at(&listed(&mut s, "Arm"), [19.98, -4.24, 19.0]);
|
||||
assert!(boss.starts_with("F3:side"), "{boss}");
|
||||
// A point on the boss's outer wall, above the arm.
|
||||
let wall = |s: &Session| solidcraft_doc::naming::face_names_at(&s.model.state(), &[Vec3::new(-23.0, 0.0, 19.0)]).pop().unwrap_or_default();
|
||||
let boss = wall(&s);
|
||||
assert!(boss.starts_with("F2:side:a"), "{boss}");
|
||||
run(&mut s, "solid.cylinder", json!({"diameter": 30.0, "height": 20, "base": [170.0, 0, 0], "operation": "join", "targets": ["Arm"]}));
|
||||
let joined = listed(&mut s, "Arm");
|
||||
assert!(joined.iter().all(|(_, exact, _)| *exact), "{joined:?}");
|
||||
@@ -453,47 +455,57 @@ fn a_cut_through_one_of_two_bosses_names_only_its_own_walls() {
|
||||
for (at, line) in [([0.0, -6.35, 11.0], "l1"), ([6.35, 0.0, 11.0], "l2"), ([0.0, 6.35, 11.0], "l3"), ([-6.35, 0.0, 11.0], "l4")] {
|
||||
assert_eq!(named_at(&cut, at), format!("F6:side:{line}"));
|
||||
}
|
||||
assert_eq!(named_at(&cut, [19.98, -4.24, 19.0]), boss);
|
||||
assert_eq!(wall(&s), boss);
|
||||
run(&mut s, "solid.fillet", json!({"edges": [[6.35, 6.35, 10], [-6.35, 6.35, 10], [-6.35, -6.35, 10], [6.35, -6.35, 10]], "radius": 1.5}));
|
||||
let filleted = listed(&mut s, "Arm");
|
||||
let blends = filleted.iter().filter(|(n, _, _)| n.starts_with("F7:blend:")).count();
|
||||
assert_eq!(blends, 4, "{filleted:?}");
|
||||
assert_eq!(named_at(&filleted, [19.98, -4.24, 19.0]), boss);
|
||||
assert_eq!(wall(&s), boss);
|
||||
}
|
||||
|
||||
/// #3902: a torus with a closed, twisted square sweep cut inside it splits the sweep's walls into
|
||||
/// pieces the kernel returns in a different order on each evaluation. The pieces are numbered by
|
||||
/// where they lie, so every fresh evaluation gives each piece the same name.
|
||||
/// A disc whose top a round groove along x splits into two pieces of one name, numbered by
|
||||
/// place: `F1:top#1` on the -y side, `F1:top#2` on the +y side.
|
||||
fn grooved_disc() -> Value {
|
||||
json!([
|
||||
{"command": "solid.cylinder", "params": {"radius": 20, "height": 10, "body_name": "Disc"}},
|
||||
{"command": "solid.cylinder", "params": {"axis": [1, 0, 0], "base": [-25, 0, 10], "radius": 3, "height": 60, "operation": "cut"}}
|
||||
])
|
||||
}
|
||||
|
||||
/// #3902: a torus with a closed, twisted square sweep cut inside it split the sweep's walls into
|
||||
/// pieces truck returned in a different order on each evaluation (SMLib keeps those walls
|
||||
/// whole), and a grooved disc's top is two pieces of one name. Pieces are numbered by where they
|
||||
/// lie, so every fresh evaluation gives each face the same name.
|
||||
#[test]
|
||||
fn split_pieces_keep_their_numbers_across_evaluations() {
|
||||
let script: Value = serde_json::from_str(include_str!("../../../examples/regressions/closed_sweep_torus_cut.json")).unwrap();
|
||||
let mut s = Session::default();
|
||||
s.run_script(&script).unwrap();
|
||||
let body = s.model.state().bodies.first().map(|b| b.name.clone()).expect("a body");
|
||||
let named = |s: &mut Session| -> Vec<(String, [i64; 3])> {
|
||||
let mut faces: Vec<(String, [i64; 3])> = listed(s, &body).into_iter().map(|(n, _, c)| (n, c.map(|x| x.round() as i64))).collect();
|
||||
faces.sort();
|
||||
faces
|
||||
};
|
||||
let first = named(&mut s);
|
||||
assert!(first.iter().any(|(n, _)| n.contains('#')), "no split pieces: {first:?}");
|
||||
for run in 0..10 {
|
||||
let mut fresh = Session::new(solidcraft_doc::Document::clone(&s.doc));
|
||||
fresh.refresh();
|
||||
assert_eq!(named(&mut fresh), first, "evaluation {run}");
|
||||
let torus: Value = serde_json::from_str(include_str!("../../../examples/regressions/closed_sweep_torus_cut.json")).unwrap();
|
||||
for (script, pieces) in [(torus, false), (grooved_disc(), true)] {
|
||||
let mut s = Session::default();
|
||||
s.run_script(&script).unwrap();
|
||||
let body = s.model.state().bodies.first().map(|b| b.name.clone()).expect("a body");
|
||||
let named = |s: &mut Session| -> Vec<(String, [i64; 3])> {
|
||||
let mut faces: Vec<(String, [i64; 3])> = listed(s, &body).into_iter().map(|(n, _, c)| (n, c.map(|x| x.round() as i64))).collect();
|
||||
faces.sort();
|
||||
faces
|
||||
};
|
||||
let first = named(&mut s);
|
||||
assert_eq!(first.iter().any(|(n, _)| n.contains('#')), pieces, "split pieces: {first:?}");
|
||||
for run in 0..10 {
|
||||
let mut fresh = Session::new(solidcraft_doc::Document::clone(&s.doc));
|
||||
fresh.refresh();
|
||||
assert_eq!(named(&mut fresh), first, "evaluation {run}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// #3902: a design saved before the pieces of a face were numbered by place opens with the
|
||||
/// numbers it was saved with, so a reference to `…#1` stays on the piece it was made on. The
|
||||
/// block cut by the second block here has its side split in two, numbered the other way round
|
||||
/// by place.
|
||||
/// grooved disc's top is split in two, numbered the other way round by place.
|
||||
#[test]
|
||||
fn a_design_saved_before_pieces_were_numbered_by_place_keeps_their_numbers() {
|
||||
let script: Value = serde_json::from_str(include_str!("../../../tests/action_matrix/designs/multi-body-join-cut.json")).unwrap();
|
||||
let mut s = Session::default();
|
||||
s.run_script(&script).unwrap();
|
||||
let by_place = listed(&mut s, "Block");
|
||||
s.run_script(&grooved_disc()).unwrap();
|
||||
let by_place = listed(&mut s, "Disc");
|
||||
// As a build before the change saved it: its features carry no mark, and the file does not
|
||||
// list the change among those it has.
|
||||
let mut file: Value = serde_json::from_str(&s.doc.to_json()).unwrap();
|
||||
@@ -509,16 +521,16 @@ fn a_design_saved_before_pieces_were_numbered_by_place_keeps_their_numbers() {
|
||||
assert!(old.features.iter().all(|f| f.kernel_piece_order), "the features of an older design keep the old numbering");
|
||||
let mut reopened = Session::new(old);
|
||||
reopened.refresh();
|
||||
let kept = listed(&mut reopened, "Block");
|
||||
let kept = listed(&mut reopened, "Disc");
|
||||
let at = |faces: &[(String, bool, [f64; 3])], name: &str| faces.iter().find(|(n, _, _)| n == name).map(|(_, _, c)| *c);
|
||||
// The pieces are numbered differently by place, and the older design keeps its own numbers.
|
||||
let (old1, new1) = (at(&kept, "F2:side#1").unwrap(), at(&by_place, "F2:side#1").unwrap());
|
||||
let (old1, new1) = (at(&kept, "F1:top#1").unwrap(), at(&by_place, "F1:top#1").unwrap());
|
||||
assert!((0..3).any(|k| (old1[k] - new1[k]).abs() > 1.0), "{kept:?} {by_place:?}");
|
||||
let mut legacy = solidcraft_doc::Document::clone(&s.doc);
|
||||
legacy.features.iter_mut().for_each(|f| f.kernel_piece_order = true);
|
||||
let mut saved_with = Session::new(legacy);
|
||||
saved_with.refresh();
|
||||
assert_eq!(listed(&mut saved_with, "Block"), kept);
|
||||
assert_eq!(listed(&mut saved_with, "Disc"), kept);
|
||||
}
|
||||
|
||||
/// The volumes of the design's bodies, by name.
|
||||
|
||||
Reference in New Issue
Block a user