ogeom_heal/
free_bounds.rs1use ogeom_algo::edge_vertices;
21use ogeom_core::{FastMap, OgeomResult};
22use ogeom_topo::{Model, SameKey, Shape, ShapeType, explore_unique};
23
24#[derive(Debug, Clone, Default)]
26pub struct FreeBounds {
27 pub closed: Vec<Vec<Shape>>,
30 pub open: Vec<Vec<Shape>>,
32}
33
34impl FreeBounds {
35 #[must_use]
37 pub fn is_empty(&self) -> bool {
38 self.closed.is_empty() && self.open.is_empty()
39 }
40
41 pub fn wires(&self, model: &mut Model) -> OgeomResult<Vec<Shape>> {
49 self.closed
50 .iter()
51 .chain(&self.open)
52 .map(|edges| model.add_wire(edges))
53 .collect()
54 }
55}
56
57struct Free {
59 edge: Shape,
60 from: SameKey,
61 to: SameKey,
62 next_in_wire: Option<usize>,
64}
65
66pub fn free_bounds(model: &Model, shape: &Shape) -> OgeomResult<FreeBounds> {
78 let faces = explore_unique(model, shape, ShapeType::Face)?;
79 let mut walks: Vec<Vec<Shape>> = Vec::new();
80 let mut uses: FastMap<SameKey, usize> = FastMap::default();
81 for face in &faces {
82 for wire in model.ordered_children_of(face)? {
83 let edges = model.ordered_children_of(&wire)?;
84 for edge in &edges {
85 *uses.entry(SameKey(edge.clone())).or_default() += 1;
86 }
87 walks.push(edges);
88 }
89 }
90 let is_free = |edge: &Shape| {
91 uses.get(&SameKey(edge.clone())) == Some(&1)
92 && !model
93 .node(edge)
94 .and_then(|n| n.data().as_edge())
95 .is_some_and(|d| d.degenerate)
96 };
97 let mut free: Vec<Free> = Vec::new();
98 for edges in &walks {
99 let first = free.len();
100 let mut at: Vec<Option<usize>> = Vec::with_capacity(edges.len());
101 for edge in edges {
102 if !is_free(edge) {
103 at.push(None);
104 continue;
105 }
106 let Some((start, end)) = edge_vertices(model, edge)? else {
107 at.push(None);
108 continue;
109 };
110 let (from, to) = (SameKey(start), SameKey(end));
111 at.push(Some(free.len()));
112 free.push(Free {
113 edge: edge.clone(),
114 from,
115 to,
116 next_in_wire: None,
117 });
118 }
119 if free.len() == first {
120 continue;
121 }
122 for i in 0..at.len() {
123 if let (Some(k), Some(next)) = (at[i], at[(i + 1) % at.len()]) {
124 free[k].next_in_wire = Some(next);
125 }
126 }
127 }
128
129 let mut leaving: FastMap<SameKey, Vec<usize>> = FastMap::default();
130 let mut arriving: FastMap<SameKey, usize> = FastMap::default();
131 for (k, f) in free.iter().enumerate() {
132 leaving.entry(f.from.clone()).or_default().push(k);
133 *arriving.entry(f.to.clone()).or_default() += 1;
134 }
135 let mut used = vec![false; free.len()];
136 let mut out = FreeBounds::default();
137 let heads: Vec<usize> = (0..free.len())
140 .filter(|&k| !arriving.contains_key(&free[k].from))
141 .collect();
142 let rest = 0..free.len();
143 for start in heads.into_iter().chain(rest) {
144 if used[start] {
145 continue;
146 }
147 used[start] = true;
148 let mut chain = vec![start];
149 let mut last = start;
150 while free[last].to != free[start].from {
151 let next = free[last]
152 .next_in_wire
153 .filter(|&n| !used[n] && free[n].from == free[last].to)
154 .or_else(|| {
155 leaving
156 .get(&free[last].to)
157 .and_then(|ks| ks.iter().copied().find(|&n| !used[n]))
158 });
159 let Some(next) = next else { break };
160 used[next] = true;
161 chain.push(next);
162 last = next;
163 }
164 let edges: Vec<Shape> = chain.iter().map(|&k| free[k].edge.clone()).collect();
165 if free[last].to == free[start].from {
166 out.closed.push(edges);
167 } else {
168 out.open.push(edges);
169 }
170 }
171 Ok(out)
172}