geop_core_topology/wire.rs
1use std::collections::{BTreeMap, HashSet};
2
3use geop_core_math::geop_error::{GeopError, GeopResult};
4
5use super::ids::{EdgeId, VertexId, WireId};
6use crate::{Model, Sense};
7
8/// Edges and vertices standing on their own, bounding no face: curves in
9/// space, such as the lines, arcs and splines of a 3-D sketch, or where two
10/// bodies' boundaries cross.
11///
12/// A wire owns its entities as a shell owns its faces: `edges` are used by
13/// no coedge, and `vertices` are every vertex of the wire — the ends of its
14/// edges, and points on their own, which no edge ends at. An edge or a
15/// vertex belongs to one wire at most, and a vertex of a wire to nothing
16/// else.
17///
18/// A wire is no [`crate::Body`]: nothing is cut or combined with it. Its
19/// edges must not cross each other except at a shared vertex, as the edges
20/// of a body must not (see [`crate::validation`]).
21#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
22pub struct Wire {
23 pub vertices: Vec<VertexId>,
24 pub edges: Vec<EdgeId>,
25}
26
27impl Model {
28 /// The edges of the wire `wire` in chains: each a run of edges, in
29 /// order, each starting where the one before ends, with the sense it
30 /// runs in along its chain — open between two vertices where the run
31 /// does not go on through (an end, or a branch), or closed. Ordered by
32 /// the smallest edge id each holds, which follows the order the edges
33 /// were made in.
34 pub fn wire_chains(&self, wire: WireId) -> GeopResult<Vec<Vec<(EdgeId, Sense)>>> {
35 let wire = (self.wires.get(&wire))
36 .ok_or_else(|| GeopError::new(format!("Model::wire_chains: no wire {wire}")))?;
37 let mut at: BTreeMap<VertexId, Vec<EdgeId>> = BTreeMap::new();
38 for &e in &wire.edges {
39 let edge = self.get_edge(e)?;
40 at.entry(edge.start_vertex).or_default().push(e);
41 at.entry(edge.end_vertex).or_default().push(e);
42 }
43 // The other edge of a run through `v` from `edge`: none at an end,
44 // at a branch, or where an edge closes on itself.
45 let through = |v: VertexId, edge: EdgeId| match at.get(&v).map(Vec::as_slice) {
46 Some(&[a, b]) if a != b => Some(if a == edge { b } else { a }),
47 _ => None,
48 };
49 // Where `edge` starts and ends, run in `sense`.
50 let ends = |edge: EdgeId, sense: Sense| {
51 let e = self.get_edge(edge)?;
52 Ok::<_, GeopError>(match sense {
53 Sense::Forward => (e.start_vertex, e.end_vertex),
54 Sense::Reversed => (e.end_vertex, e.start_vertex),
55 })
56 };
57 let mut edges = wire.edges.clone();
58 edges.sort();
59 let mut used = HashSet::new();
60 let mut chains = Vec::new();
61 for &first in &edges {
62 if used.contains(&first) {
63 continue;
64 }
65 // Back from `first` to where its run starts, or round to it.
66 let (mut edge, mut sense) = (first, Sense::Forward);
67 loop {
68 let (start, _) = ends(edge, sense)?;
69 let Some(previous) = through(start, edge).filter(|&p| p != first) else {
70 break;
71 };
72 sense = if self.get_edge(previous)?.end_vertex == start {
73 Sense::Forward
74 } else {
75 Sense::Reversed
76 };
77 edge = previous;
78 }
79 // Forward from there.
80 let mut chain = Vec::new();
81 loop {
82 used.insert(edge);
83 chain.push((edge, sense));
84 let (_, end) = ends(edge, sense)?;
85 let Some(next) = through(end, edge).filter(|n| !used.contains(n)) else {
86 break;
87 };
88 sense = if self.get_edge(next)?.start_vertex == end {
89 Sense::Forward
90 } else {
91 Sense::Reversed
92 };
93 edge = next;
94 }
95 chains.push(chain);
96 }
97 Ok(chains)
98 }
99}