//! Face names from exact history, and how a stored name matches a face. //! //! A face's name says what made it (see `naming` for the vocabulary). Where the feature that made //! a body reports its history (a [`Lineage`]), each face is named after the faces it came from: //! kept, trimmed or moved faces keep their names, a face made from a profile segment or cap is //! named by that role, a fillet face by the edge it replaces, a shell's inner wall by the face it //! offsets. Nothing is sampled. Where a face's origin cannot be named exactly the face is named //! from geometry, as a body without history is, and marked unverified. //! //! Two faces with the same name are pieces of one face (a cut split it). Each piece is told apart //! by the faces around it that the others do not have: `F2:top#[F2:side:l4,F4:side:l4]` is the //! piece of `F2:top` bounded by the block's left wall and the slot's left wall. Pieces nothing //! around them tells apart are numbered (`#1`, `#2`): in the order of what they came from when //! that differs (exact), else in the kernel's order (unverified). A face made of several (a //! coplanar join) takes one name and answers to the others (aliases). use std::collections::{BTreeMap, BTreeSet}; use std::sync::{Arc, OnceLock}; use solvecraft_kernel::history::{FaceOrigin, FaceProv, History}; use crate::ModelBody; use crate::lineage::{Lineage, Operand}; use crate::naming::NamingOrigin; /// The names of a body's faces, with what finding them again needs. #[derive(Debug, Default)] pub struct Names { pub faces: Arc>, pub(crate) info: Vec, } /// What a face answers to besides its name, and how sure its name is. #[derive(Clone, Debug, Default)] pub(crate) struct FaceInfo { /// Names of the other faces merged into it (a reference to one of them finds this face). pub aliases: Vec, /// The stems of the faces around it: what tells it apart from other pieces of its face. pub bounds: BTreeSet, /// Whether the name follows exact history (`false`: worked out from geometry, or a piece /// numbered because the faces around it did not tell it apart). pub verified: bool, } impl Names { /// Names worked out from geometry: the same names, none of them verified, pieces told apart /// by the faces around them (`adjacent`: pairs of faces sharing an edge). pub(crate) fn unverified(names: Vec, adjacent: &[(usize, usize)]) -> Names { let named = names .into_iter() .map(|name| Named { name: crate::naming::strip_piece(&name).to_string(), aliases: Vec::new(), verified: false, origins: None }) .collect(); qualify(named, adjacent) } /// Names given as they are (a moved copy keeps its source's), taken as exact. pub(crate) fn given(names: Arc>) -> Names { let info = vec![FaceInfo { verified: true, ..FaceInfo::default() }; names.len()]; Names { faces: names, info } } /// Whether face `index`'s name follows exact history. pub fn verified(&self, index: usize) -> bool { self.info.get(index).is_some_and(|i| i.verified) } } /// A face's name before pieces are told apart. #[derive(Clone, Debug)] struct Named { name: String, aliases: Vec, verified: bool, /// What it came from (`None`: not known): faces with one name but different origins (an /// extrude's two caps both above its sketch plane) are numbered in this order. origins: Option>, } /// Names of a body (`b`, made by `o`'s feature) from its lineage; `None` when the lineage does /// not describe it (then it is named from geometry). pub(crate) fn derive(b: &ModelBody, o: &NamingOrigin, lineage: &Lineage) -> Option { let h = &lineage.history; if h.faces.len() != b.body.face_count() || h.edges.len() != b.body.edge_count() { return None; } let operands: Vec = lineage.operands.iter().map(|op| OperandNames::new(op, o)).collect::>()?; let fid = o.feature.id; let mut named: Vec> = h.faces.iter().map(|f| from_origins(f, &operands, fid)).collect(); if named.iter().any(Option::is_none) { let geometric = crate::naming::geometric_names(b, o, named.len()); for (slot, name) in named.iter_mut().zip(geometric) { if slot.is_none() { *slot = Some(Named { name: crate::naming::strip_piece(&name).to_string(), aliases: Vec::new(), verified: false, origins: None }); } } } let named: Vec = named.into_iter().collect::>()?; Some(qualify(named, &adjacency(h))) } /// Pairs of faces that share an edge, from a history. fn adjacency(h: &History) -> Vec<(usize, usize)> { face_pairs(h.edges.iter().map(|e| e.faces.iter().copied())) } /// The two faces of each edge (edges given by their faces) that has exactly two. pub(crate) fn face_pairs>(edges: impl Iterator) -> Vec<(usize, usize)> { edges .filter_map(|mut faces| match (faces.next(), faces.next(), faces.next()) { (Some(a), Some(b), None) => Some((a, b)), _ => None, }) .collect() } /// An operand's faces' names. struct OperandNames<'a> { operand: &'a Operand, /// The model body (or a stand-in for a stage) the operand's names belong to. body: Option, names: Arc, /// Its edges' names, worked out on first use (for the faces that blend them). edges: OnceLock>, } impl<'a> OperandNames<'a> { fn new(operand: &'a Operand, o: &NamingOrigin) -> Option> { match operand { Operand::Body(name) | Operand::Copy { body: name, .. } | Operand::Inner(name) => { let body = o.before.body(name)?; Some(OperandNames { operand, names: crate::naming::names(body), body: Some(body.clone()), edges: OnceLock::new() }) } Operand::Made(_) | Operand::CopiedTool { .. } => { Some(OperandNames { operand, body: None, names: Arc::default(), edges: OnceLock::new() }) } Operand::Stage(stage) => { let body = &stage.body; let names = stage .names .get_or_init(|| { Arc::new(derive(body, o, &stage.lineage).unwrap_or_else(|| { let names = crate::naming::geometric_names(body, o, body.body.face_count()); Names::unverified(names, &crate::naming::mesh_adjacency(body)) })) }) .clone(); Some(OperandNames { operand, body: Some(body.clone()), names, edges: OnceLock::new() }) } } } /// The name of face `face`. fn face(&self, face: usize, fid: u64) -> Option { let given = |names: &Names| { let info = names.info.get(face)?; Some(Named { name: names.faces.get(face)?.clone(), aliases: info.aliases.clone(), verified: info.verified, origins: None }) }; match self.operand { Operand::Body(_) | Operand::Stage(_) => given(&self.names), Operand::Copy { instance, .. } => given(&self.names).map(|n| Named { name: format!("{}@F{fid}.{instance}", n.name), aliases: n.aliases.iter().map(|a| format!("{a}@F{fid}.{instance}")).collect(), verified: n.verified, origins: None, }), Operand::Inner(_) => given(&self.names).map(|n| Named { name: format!("F{fid}:inner:{}", n.name), aliases: Vec::new(), verified: n.verified, origins: None, }), Operand::Made(roles) => { let (first, rest) = roles.get(face)?.split_first()?; Some(Named { name: format!("F{fid}:{first}"), aliases: rest.iter().map(|r| format!("F{fid}:{r}")).collect(), verified: true, origins: None, }) } Operand::CopiedTool { roles, feature, instance } => { let named = |role: &String| format!("F{feature}:{role}@F{fid}.{instance}"); let (first, rest) = roles.get(face)?.split_first()?; Some(Named { name: named(first), aliases: rest.iter().map(named).collect(), verified: true, origins: None }) } } } /// The name of edge `edge`, and whether it is exact. fn edge(&self, edge: usize) -> Option<(String, bool)> { match self.operand { Operand::Body(_) | Operand::Stage(_) => { let body = self.body.as_ref()?; let names = self.edges.get_or_init(|| crate::naming::edge_names(body)); let faces = body.mesh().edge_faces.get(edge).cloned().unwrap_or_default(); let verified = faces.iter().all(|f| self.names.verified(*f as usize)); Some((names.get(edge)?.clone(), verified)) } Operand::Copy { .. } | Operand::Inner(_) | Operand::Made(_) | Operand::CopiedTool { .. } => None, } } } /// A face's name from what it came from; `None` when an origin cannot be named exactly. fn from_origins(face: &FaceProv, operands: &[OperandNames], fid: u64) -> Option { let mut origins: Vec = face.origins.clone(); origins.sort_unstable(); let mut names: Vec = Vec::with_capacity(origins.len()); for origin in origins.iter().copied() { let named = match origin { FaceOrigin::Face { operand, face } => operands.get(operand)?.face(face, fid)?, FaceOrigin::Edge { operand, edge } => { let (edge, verified) = operands.get(operand)?.edge(edge)?; Named { name: format!("F{fid}:blend:{edge}"), aliases: Vec::new(), verified, origins: None } } FaceOrigin::Offset { operand, face } => { let source = operands.get(operand)?.face(face, fid)?; Named { name: format!("F{fid}:inner:{}", source.name), aliases: Vec::new(), verified: source.verified, origins: None } } // A profile or cap of a body made from nothing: only a built body's own roles name // these, and those come in as `Made` operands. FaceOrigin::Profile { .. } | FaceOrigin::Cap { .. } => return None, }; if !names.iter().any(|n| n.name == named.name) { names.push(named); } } merged(names).map(|n| Named { origins: Some(origins), ..n }) } /// One name for a face made of faces with these names: the face they are all pieces of, else the /// first (the target's before a tool's); the others become aliases. fn merged(names: Vec) -> Option { let verified = names.iter().all(|n| n.verified); let first = names.first()?.name.clone(); let mut common = Some(first.as_str()); while let Some(c) = common { if names.iter().all(|n| descends(&n.name, c)) { break; } common = crate::naming::parent_piece(c); } let name = common.map(str::to_string).unwrap_or(first); let mut aliases: Vec = Vec::new(); for n in names { for alias in std::iter::once(n.name).chain(n.aliases) { if alias != name && !aliases.contains(&alias) { aliases.push(alias); } } } Some(Named { name, aliases, verified, origins: None }) } /// Whether `name` is `stem` or a piece of it (at any depth). pub(crate) fn descends(name: &str, stem: &str) -> bool { let mut current = Some(name); while let Some(n) = current { if n == stem { return true; } current = crate::piece_names::parent_piece(n); } false } /// Tell pieces apart: faces sharing a name are named by the faces around them that the others /// lack (`adjacent`: pairs of faces sharing an edge). fn qualify(named: Vec, adjacent: &[(usize, usize)]) -> Names { let n = named.len(); let mut bounds: Vec> = vec![BTreeSet::new(); n]; for &(a, b) in adjacent { let (Some(na), Some(nb)) = (named.get(a), named.get(b)) else { continue }; let (sa, sb) = (crate::naming::strip_piece(&na.name), crate::naming::strip_piece(&nb.name)); if a == b || sa == sb { continue; } if let Some(set) = bounds.get_mut(a) { set.insert(sb.to_string()); } if let Some(set) = bounds.get_mut(b) { set.insert(sa.to_string()); } } let mut groups: BTreeMap<&str, Vec> = BTreeMap::new(); for (i, x) in named.iter().enumerate() { groups.entry(x.name.as_str()).or_default().push(i); } let mut names: Vec = named.iter().map(|x| x.name.clone()).collect(); let mut verified: Vec = named.iter().map(|x| x.verified).collect(); for (base, members) in groups.iter().filter(|(_, m)| m.len() > 1) { let empty = BTreeSet::new(); let common: BTreeSet<&String> = members .iter() .map(|i| bounds.get(*i).unwrap_or(&empty)) .fold(None::>, |acc, set| { Some(match acc { None => set.iter().collect(), Some(acc) => acc.into_iter().filter(|x| set.contains(*x)).collect(), }) }) .unwrap_or_default(); let own = |i: usize| -> Vec<&String> { bounds.get(i).unwrap_or(&empty).iter().filter(|x| !common.contains(x)).collect() }; let mut by_key: BTreeMap> = BTreeMap::new(); for &i in members { let key: Vec<&str> = own(i).into_iter().map(String::as_str).collect(); // Nothing around it of its own: the piece is only numbered (below). let name = if key.is_empty() { base.to_string() } else { format!("{base}#[{}]", key.join(",")) }; by_key.entry(name).or_default().push(i); } for (key, tied) in by_key { if let [i] = tied.as_slice() { if let Some(slot) = names.get_mut(*i) { *slot = key; } continue; } // The faces around them don't tell these apart: numbered by what they came from when // that differs (exact), else in the kernel's order (unverified). let mut tied = tied; let origins: Vec>> = tied.iter().map(|i| named.get(*i).and_then(|x| x.origins.as_ref())).collect(); let distinct = origins.iter().all(Option::is_some) && origins.iter().collect::>().len() == origins.len(); if distinct { tied.sort_by_key(|i| named.get(*i).and_then(|x| x.origins.clone())); } for (k, i) in tied.iter().enumerate() { if let Some(slot) = names.get_mut(*i) { *slot = format!("{key}#{}", k + 1); } if let Some(v) = verified.get_mut(*i).filter(|_| !distinct) { *v = false; } } } } // A piece's new name can meet a face that already had it: numbered too, never shared. let mut seen: BTreeMap = BTreeMap::new(); for name in &names { *seen.entry(name.clone()).or_default() += 1; } let mut counter: BTreeMap = BTreeMap::new(); for (i, name) in names.iter_mut().enumerate() { if seen.get(name.as_str()).is_some_and(|c| *c > 1) { let k = counter.entry(name.clone()).or_default(); *k += 1; *name = format!("{name}#{k}"); if let Some(v) = verified.get_mut(i) { *v = false; } } } let info = named.into_iter().zip(bounds).zip(verified).map(|((x, bounds), verified)| FaceInfo { aliases: x.aliases, bounds, verified }).collect(); Names { faces: Arc::new(names), info } } /// How a stored face name matches a current face. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub(crate) enum Match { /// Only its root stem is the same: some piece of the same face (the caller picks by point). Stem, /// A piece of the named face, or the named piece told apart by the faces around it. Piece, /// One of the faces merged into this one. Alias, /// The very name. Exact, } /// How the stored name `name` matches face `index` (`None`: not at all). pub(crate) fn matches(names: &Names, index: usize, name: &str) -> Option { let current = names.faces.get(index)?; let info = names.info.get(index); if current == name { return Some(Match::Exact); } if info.is_some_and(|i| i.aliases.iter().any(|a| a == name)) { return Some(Match::Alias); } // The named face (or piece) was split further: this is one of its pieces. if descends(current, name) { return Some(Match::Piece); } // The named piece, told apart by the faces around it: they still bound this piece. if let Some((stem, around)) = crate::piece_names::piece_parts(name) && crate::naming::parent_piece(current) == Some(stem) && info.is_some_and(|i| around.iter().all(|f| i.bounds.contains(*f))) { return Some(Match::Piece); } let root = crate::naming::strip_piece(name); (crate::naming::strip_piece(current) == root).then_some(Match::Stem) } #[cfg(test)] #[path = "history_names_tests.rs"] mod tests;