//! Recording the exact history of a boolean's result, whichever path made it (see //! [`crate::ops::boolean`]): for each result face, the operand faces it is a region of; for each //! result edge, the operand edges it lies along or the operand faces whose meeting it is. The //! operands are `[a, b]`: the target, then the tool. Paths compose the histories of the steps //! they take ([`History::over`], [`History::substitute`]); a path that cannot leaves the result //! without history. use std::collections::HashMap; use solidcraft_geom::Vec3; use truck_modeling as mt; use crate::Result; use crate::body::{Body, Solid, edge_order}; use crate::history::{Change, EdgeOrigin, FaceOrigin, FaceProv, History}; use crate::record; /// A composition step's history, or none (logged) where the lineage can't be expressed. pub(crate) fn composed(h: Result) -> Option { h.map_err(|e| log::debug!("no history: {e}")).ok() } /// `h`, the history of an operation over one operand, as over `operands` with that one at /// `operand`. pub(crate) fn placed(h: Option, operand: usize, operands: usize) -> Option { composed(h?.renumbered(&[Some(operand)], operands)) } /// The history of `body` as operand `operand` of `operands`: every face and edge kept. pub(crate) fn kept(body: &Body, operand: usize, operands: usize) -> Option { placed(Some(History::identity(body)), operand, operands) } /// The history of `result`, made from operands of which operand `k` was made by the operation /// `inner` describes, with `inner`'s operands in `k`'s place. pub(crate) fn through(result: &Body, k: usize, inner: Option<&History>) -> Option { composed(result.history()?.clone().substituted(k, inner?)) } /// Operand `operand` (with `counts` giving every operand's faces and edges) as the result, with /// the history that says so. pub(crate) fn operand(body: &Body, operand: usize, counts: &[(usize, usize)]) -> Body { body.clone().with_checked_history(kept(body, operand, counts.len()), counts) } /// The histories of `parts`, each made of shells of `whole` (its lumps), over `[whole]`: their /// faces and edges are the whole's own (by identity). pub(crate) fn parts_of(parts: &[Body], whole: &Body) -> Vec> { let (faces, edges) = (record::face_index(&whole.solid), record::edge_origins(&whole.solid, 0)); parts .iter() .map(|part| { let faces = part.solid.face_iter().map(|f| Some(record::from_face(0, *faces.get(&f.id())?, Change::Kept))).collect::>>()?; Some(History { operands: 1, faces, edges: record::edge_provs(part, &edges)? }) }) .collect() } /// A body with its history over the operation's operands (`None`: not known). pub(crate) type Traced = (Body, Option); /// The history over the operation's operands of `result`, made from `x` and `y` (in that order) /// whose histories over them are given. pub(crate) fn of_pair(result: &Body, x: &Option, y: &Option) -> Option { composed(result.history()?.clone().over(&[x.as_ref()?, y.as_ref()?])) } /// Bodies apart from each other, each with its history over the operation's operands (with /// `counts` faces and edges), as one body of several pieces whose history is theirs side by side. pub(crate) fn join(pieces: Vec, counts: &[(usize, usize)]) -> Result> { if pieces.len() < 2 { return Ok(pieces.into_iter().next().map(|(b, h)| b.with_checked_history(h, counts))); } // The pieces' shells in order: their faces in order, their edges themselves. let mut faces = Some(Vec::new()); let mut edges = HashMap::new(); for (p, h) in &pieces { let (Some(h), Some(all)) = (h, faces.as_mut()) else { faces = None; break; }; all.extend(h.faces.iter().cloned()); edges.extend(record::edges_by_identity(p, h)); } if let Some(faces) = faces.as_mut() { record::mark_shared_split(faces); } let joined = crate::ops::join_pieces(pieces.into_iter().map(|(p, _)| p).collect())?; Ok(joined.map(|body| record::traced(body, counts, faces, Some(&edges)))) } /// A truck boolean's history in our terms, in the order of the solid it made: each face a /// region of one operand face (Kept when that very face), each edge's origins. pub(crate) type Shapeops = (Vec, Vec>); /// A truck boolean's history in our terms ([`Shapeops`]). `tool` is the /// tool as passed (after a cut turned it inside out, which keeps its faces' order but walks each /// face's edges the other way, so its edge order changes) and `tool_edges` its edges by identity /// in its own order (before turning). pub(crate) fn shapeops(h: &truck_shapeops::History, tool: &Solid, tool_edges: Option<&HashMap>>) -> Option { let face = |f: truck_shapeops::FaceIndex| FaceOrigin::Face { operand: f.operand, face: f.face }; let faces = h.faces.iter().map(|o| record::from_face(o.source.operand, o.source.face, if o.kept { Change::Kept } else { Change::Modified })).collect(); let passed: Vec = edge_order(tool.face_iter()).0.iter().map(|e| e.id()).collect(); let edges = h .edges .iter() .map(|o| match *o { truck_shapeops::EdgeOrigin::Edge { operand: 1, edge } if tool_edges.is_some() => { tool_edges.and_then(|t| t.get(passed.get(edge)?)).cloned() } truck_shapeops::EdgeOrigin::Edge { operand, edge } => Some(vec![EdgeOrigin::Edge { operand, edge }]), truck_shapeops::EdgeOrigin::Meet { a, b } => Some(vec![EdgeOrigin::meet(face(a), face(b))]), }) .collect::>>()?; Some((faces, edges)) } /// The body made of `solid` healed (keeping faces apart along `keep`), with the history whose /// faces and edges (in `solid`'s orders) are given before healing, carried through it. /// Operands with `counts` faces and edges. pub(crate) fn heal_build( solid: Solid, size: f64, keep: &[Vec], faces: Option>, edges: Option>>, counts: &[(usize, usize)], ) -> Result { let (healed, map) = crate::heal::heal_traced(solid, size, keep); let carried = faces.zip(edges).zip(map).and_then(|((f, e), m)| m.carry(&f, &e)); let (faces, edges) = carried.unzip(); record::build(healed, counts, faces, edges.as_ref()) } /// The body truck's result `s` makes, healed (see [`heal_build`]), with its history `h`. pub(crate) fn truck_result(s: Solid, h: Option, size: f64, keep: &[Vec], counts: &[(usize, usize)]) -> Result { let (faces, edges) = h.unzip(); heal_build(s, size, keep, faces, edges, counts) } /// `body` healed (see [`heal_build`]), its history carried through: `solid` is `body`'s solid /// or a copy of it (`Body::deep_copy`, which keeps its order). pub(crate) fn heal_body(body: &Body, solid: Solid, size: f64, keep: &[Vec], counts: &[(usize, usize)]) -> Result { let (faces, edges) = body.history().map(|h| (h.faces.clone(), h.edges.iter().map(|e| e.origins.clone()).collect())).unzip(); heal_build(solid, size, keep, faces, edges, counts) } /// A prism boolean's result `body` with the coplanar pieces its slabs leave merged (nothing /// else healed: its edges are exact as they are), its history carried through, and what else it carries kept: each face's /// construction source where every piece merged into it had that same one, its Split Face /// lines and its regularization protection. pub(crate) fn heal_prism_result(body: &Body, size: f64, keep: &[Vec], counts: &[(usize, usize)]) -> Result { let (faces, edges): (Option>, Option>>) = body.history().map(|h| (h.faces.clone(), h.edges.iter().map(|e| e.origins.clone()).collect())).unzip(); let (healed, map) = crate::heal::merge_faces_traced((*body.solid).clone(), size, keep); let sources = match (body.face_sources(), &map) { (Some(sources), Some(m)) => Some( m.faces .iter() .map(|under| { let mut each = under.iter().map(|i| sources.get(*i).cloned().flatten()); let first = each.next().flatten()?; each.all(|s| s.as_ref() == Some(&first)).then_some(first) }) .collect::>(), ), _ => None, }; let carried = faces.zip(edges).zip(map).and_then(|((f, e), m)| m.carry(&f, &e)); let (faces, edges) = carried.unzip(); let mut out = record::build(healed, counts, faces, edges.as_ref())?; out = out.with_face_sources(sources.map(std::sync::Arc::new)).inherit_regularization_protection(body); out.splits = body.splits.clone(); Ok(out) } /// `body` healed as the local modifiers take it (coplanar neighbours as one face, as after a /// join; faces split on purpose kept apart), with its history over `[body]`. pub(crate) fn healed(body: &Body) -> Result { let counts = record::counts(&[body]); heal_body(&operand(body, 0, &counts), body.deep_copy(), body.size(), body.split_keep(), &counts) } /// `body` copied as it is, unhealed (`Body::deep_copy`, which keeps its face and edge order), /// with its history over `[body]`: every face and edge kept. pub(crate) fn copied(body: &Body) -> Result { let copy = Body::new(body.deep_copy())?; Ok(operand(©, 0, &record::counts(&[body]))) } /// The faces and edges of `a`, then of the tool `b` turned inside out as a void (`inner`, a /// copy of `b`, with `inner_edges` its edges by identity before turning): the history of /// `a` with `b`'s inside taken out where `b` lies wholly within it. pub(crate) fn void( a: &Body, inner_edges: HashMap>, b_faces: usize, ) -> (Vec, HashMap>) { let faces = (0..a.face_count()) .map(|i| record::from_face(0, i, Change::Kept)) .chain((0..b_faces).map(|i| record::from_face(1, i, Change::Kept))) .collect(); let mut edges = record::edge_origins(&a.solid, 0); edges.extend(inner_edges); (faces, edges) } #[cfg(test)] #[path = "boolean_history_tests.rs"] pub(crate) mod tests;