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:
jyviko
2026-10-28 22:25:13 -04:00
committed by Irakli Kourtis
parent c93b3ad5ab
commit b6b59e7b3b
+46 -34
View File
@@ -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.