A feature that names its body finds its named face there first (#1637) (#1643)

Thanks @rlartot.
This commit is contained in:
rlartot authored and GitHub committed 2026-10-16 06:26:23 -04:00
1 parent 64b0b674d2
commit 3126962cd4
3 files changed
+127 -2

No files matched your search

+88 -2
View File
@@ -1171,7 +1171,8 @@ impl Model {
b.made = crate::lineage::Made::Before;
}
let mut warning = None;
// Face references by name: move each face point onto its named face.
// Face references by name: move each face point onto its named face (on the body the
// feature names, when it names one and the face is there).
let renamed;
// Cylindrical emboss resolves its named face in the selected body's frame
// and certifies an analytic anchor; mesh projection here would undo that.
@@ -1191,7 +1192,7 @@ impl Model {
if n.is_empty() {
continue;
}
match crate::naming::locate_face(&fst, n, *p) {
match crate::name_lookup::locate_face_on_body(&fst, f.kind.face_body(), n, *p) {
Some(hit) => {
if let Some(note) = hit.note(n) {
notes.push(note);
@@ -5294,3 +5295,88 @@ pub fn sketch_line_plane(st: &ModelState, sketch: u64, line: &str) -> Result<Pla
let x = (wb - wa).normalized().ok_or_else(|| DocError::Invalid(format!("`{line}` has no length")))?;
Ok(Plane { origin: wa, x, y: ss.plane.normal() })
}
#[cfg(test)]
mod named_face_lookup_tests {
use super::*;
use crate::naming::FaceHit;
/// A row of `n` 10 mm cubes `Part0`, `Part1`… 20 mm apart along x, without history: their
/// faces are named from geometry, which meshes them.
fn parts(n: usize) -> ModelState {
let mut st = ModelState::default();
for i in 0..n {
let x = 20.0 * i as f64;
let cube = kernel::box_solid(Vec3::new(x, 0.0, 0.0), Vec3::new(x + 10.0, 10.0, 10.0)).unwrap();
st.bodies.push(ModelBody::new(format!("Part{i}"), cube, 1));
}
st
}
/// The name of the face of `body` at `p`.
fn name_at(st: &ModelState, body: &str, p: Vec3) -> String {
let b = st.body(body).unwrap();
let face = crate::appearance::face_index_at(b, p).unwrap();
crate::naming::face_names(b)[face].clone()
}
/// Everything a lookup found, to compare two lookups.
fn found(hit: Option<FaceHit>) -> String {
format!("{hit:?}")
}
/// A shell late in a long timeline spent seconds meshing every body of the model to find its
/// face by name (#287). A feature that names its body finds the face there without naming
/// the other bodies, and finds what looking over every body finds.
#[test]
fn a_face_is_found_on_the_body_the_feature_names_without_meshing_the_others() {
let st = parts(12);
let top = Vec3::new(145.0, 5.0, 10.0);
let name = name_at(&st, "Part7", top);
let hit = crate::name_lookup::locate_face_on_body(&st, Some("Part7"), &name, top);
for b in st.bodies.iter().filter(|b| b.name != "Part7") {
assert!(b.mesh.get().is_none(), "{} was meshed", b.name);
}
let h = hit.unwrap();
assert_eq!((h.body, h.split, h.merged), (7, false, false));
assert_eq!(found(hit), found(crate::naming::locate_face(&st, &name, top)));
// Looking over every body names (and so meshes) them all.
assert!(st.bodies.iter().all(|b| b.mesh.get().is_some()));
}
/// A name the named body has no face of (it moved to another body: a combine took the face),
/// a body that is not there, no body, a name saved before format 14 or a name no face has:
/// looked for over every body, as without a body.
#[test]
fn a_name_not_on_the_named_body_is_looked_for_over_every_body() {
let st = parts(4);
let top = Vec3::new(45.0, 5.0, 10.0);
let name = name_at(&st, "Part2", top);
for name in [name.clone(), format!("{}{name}", crate::naming::LEGACY_MARK), "Part2:face:99".into()] {
let everywhere = found(crate::naming::locate_face(&st, &name, top));
for body in [Some("Part1"), Some("Part2"), Some("Nope"), None] {
assert_eq!(found(crate::name_lookup::locate_face_on_body(&st, body, &name, top)), everywhere, "{name} on {body:?}");
}
}
let moved = crate::name_lookup::locate_face_on_body(&st, Some("Part1"), &name, top).unwrap();
assert_eq!((moved.body, moved.split), (2, false));
}
/// A copy of a body has its source's face names. Over every body the name finds the face on
/// both, and the one nearest the point is taken as a piece of a split face. A feature on the
/// copy takes the copy's face, whole.
#[test]
fn the_same_face_on_a_copy_is_not_a_piece_of_the_named_bodys_face() {
let mut st = parts(2);
let source = st.bodies[0].clone();
st.bodies[1].names = crate::naming::NameCell::same(&source);
let top = Vec3::new(25.0, 5.0, 10.0);
let name = name_at(&st, "Part1", top);
assert_eq!(name, name_at(&st, "Part0", Vec3::new(5.0, 5.0, 10.0)));
let on_copy = crate::name_lookup::locate_face_on_body(&st, Some("Part1"), &name, top).unwrap();
let everywhere = crate::naming::locate_face(&st, &name, top).unwrap();
assert_eq!((on_copy.body, on_copy.face, on_copy.point), (everywhere.body, everywhere.face, everywhere.point));
assert_eq!((on_copy.split, everywhere.split), (false, true));
assert_eq!(on_copy.note(&name), None);
}
}