Skip to main content

geop_core_part/
describe.rs

1//! [`PartDescription`]: a part's topology in terms of names only.
2
3use std::collections::BTreeMap;
4
5use geop_core_math::{
6    geop_error::{GeopError, GeopResult},
7    scalars::Scalar,
8};
9use geop_core_topology::{CoedgeGeometry, Sense, boundary::BoundaryType};
10use serde::{Deserialize, Serialize};
11
12use crate::{ids::RefId, part::Part};
13
14/// A face's boundary loops, each as the coedges it runs through: `+E` / `-E`
15/// for edge `E` traversed forwards / backwards, `@V` for a degenerate
16/// coedge sitting at vertex `V` (a pole).
17#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
18pub struct FaceDescription {
19    pub outer: Vec<String>,
20    pub holes: Vec<Vec<String>>,
21}
22
23#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
24pub struct EdgeDescription {
25    pub start: String,
26    pub end: String,
27}
28
29/// Everything about a part's topology that its names can express, and the
30/// positions of its vertices — with no internal id anywhere, so two parts
31/// built by the same program describe identically however their ids came
32/// out, and a description diffs well as text. Loops start at their smallest
33/// entry and holes are sorted, since neither has a first element of its own.
34#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
35pub struct PartDescription {
36    /// Every solid's shells, each as its sorted face names.
37    pub solids: BTreeMap<String, Vec<Vec<String>>>,
38    pub faces: BTreeMap<String, FaceDescription>,
39    pub edges: BTreeMap<String, EdgeDescription>,
40    pub vertices: BTreeMap<String, [f64; 3]>,
41    pub sketches: Vec<String>,
42    pub datums: Vec<String>,
43}
44
45impl PartDescription {
46    pub fn of<S: Scalar>(part: &Part<S>) -> GeopResult<Self> {
47        let model = part.topology();
48        let name = |id: RefId| -> GeopResult<String> {
49            part.name_of(id)
50                .map(str::to_string)
51                .ok_or_else(|| GeopError::new(format!("PartDescription: {id} has no name")))
52        };
53        let describe_loop = |boundary: BoundaryType| -> GeopResult<Vec<String>> {
54            let mut entries = match boundary {
55                BoundaryType::Vertex(v) => vec![format!("@{}", name(v.into())?)],
56                BoundaryType::Loop(anchor) => model
57                    .iterate_loop_coedges(anchor)
58                    .map(|c| {
59                        let coedge = model.get_coedge(c)?;
60                        Ok(match coedge.geometry {
61                            CoedgeGeometry::Edge(e) => match coedge.sense {
62                                Sense::Forward => format!("+{}", name(e.into())?),
63                                Sense::Reversed => format!("-{}", name(e.into())?),
64                            },
65                            CoedgeGeometry::Vertex(v) => format!("@{}", name(v.into())?),
66                        })
67                    })
68                    .collect::<GeopResult<Vec<_>>>()?,
69            };
70            if let Some(first) = (0..entries.len()).min_by_key(|&i| &entries[i]) {
71                entries.rotate_left(first);
72            }
73            Ok(entries)
74        };
75
76        let mut solids = BTreeMap::new();
77        for (&id, solid) in &model.solids {
78            let shells = solid
79                .shells
80                .iter()
81                .map(|&shell| {
82                    let mut faces = model
83                        .get_shell(shell)?
84                        .faces
85                        .iter()
86                        .map(|&f| name(f.into()))
87                        .collect::<GeopResult<Vec<_>>>()?;
88                    faces.sort();
89                    Ok(faces)
90                })
91                .collect::<GeopResult<Vec<_>>>()?;
92            solids.insert(name(id.into())?, shells);
93        }
94        let mut faces = BTreeMap::new();
95        for (&id, face) in &model.faces {
96            let mut holes = face
97                .holes
98                .iter()
99                .map(|&h| describe_loop(h))
100                .collect::<GeopResult<Vec<_>>>()?;
101            holes.sort();
102            faces.insert(
103                name(id.into())?,
104                FaceDescription {
105                    outer: describe_loop(face.outer)?,
106                    holes,
107                },
108            );
109        }
110        let mut edges = BTreeMap::new();
111        for (&id, edge) in &model.edges {
112            edges.insert(
113                name(id.into())?,
114                EdgeDescription {
115                    start: name(edge.start_vertex.into())?,
116                    end: name(edge.end_vertex.into())?,
117                },
118            );
119        }
120        let mut vertices = BTreeMap::new();
121        for (&id, vertex) in &model.vertices {
122            let p = vertex.point;
123            vertices.insert(
124                name(id.into())?,
125                [p[0].to_f64(), p[1].to_f64(), p[2].to_f64()],
126            );
127        }
128        let mut sketches = part
129            .sketches()
130            .map(|(id, _)| name(id.into()))
131            .collect::<GeopResult<Vec<_>>>()?;
132        sketches.sort();
133        let mut datums = part
134            .datums()
135            .map(|(id, _)| name(id.into()))
136            .collect::<GeopResult<Vec<_>>>()?;
137        datums.sort();
138        Ok(Self {
139            solids,
140            faces,
141            edges,
142            vertices,
143            sketches,
144            datums,
145        })
146    }
147}