1use crate::entity_tolerance::EntityTolerances;
2use crate::imprint::ImprintResultRecord;
3use crate::topology::{BrepSolid, CoedgeRecord, EdgeRecord, VertexRecord};
4use crate::{KernelRefusal, KernelStage, OrRefuse};
5use crate::{NurbsCurve, Vec3};
6use rustc_hash::FxHashMap as HashMap;
7
8fn subcurve_by_fraction(
9 curve: &NurbsCurve,
10 start_fraction: f64,
11 end_fraction: f64,
12) -> Result<NurbsCurve, KernelRefusal> {
13 let [start, end] = curve.domain().or_refuse(KernelStage::Refine, "domain")?;
14 let first = start + (end - start) * start_fraction;
15 let second = start + (end - start) * end_fraction;
16 let epsilon = (1e-9 * (end - start)).max(2e-9);
17 let mut result = curve.clone();
18 if first > start + epsilon && first < end - epsilon {
19 result = result
20 .split(first)
21 .or_refuse(KernelStage::Refine, "split")?
22 .1;
23 }
24 let domain = result.domain().or_refuse(KernelStage::Refine, "domain")?;
25 if second < domain[1] - epsilon && second > domain[0] + epsilon {
26 result = result
27 .split(second)
28 .or_refuse(KernelStage::Refine, "split")?
29 .0;
30 } else if std::env::var("BREP_DEBUG_SUBRANGE").is_ok() && second < domain[1] - epsilon {
31 eprintln!("abnormal subrange skip in edge_split subcurve");
32 }
33 Ok(result)
34}
35
36fn claim_vertex(
58 vertices: &mut Vec<VertexRecord>,
59 imprint: &ImprintResultRecord,
60 edge: &EdgeRecord,
61 point: Vec3,
62 entity_band: f64,
63 next_id: &mut u64,
64) -> u64 {
65 let floor: f64 = 1e-7;
70 let tolerance = floor.max(if entity_band.is_finite() && entity_band > 0.0 {
71 entity_band
72 } else {
73 0.0
74 });
75 let debug = std::env::var("BREP_DEBUG_SPLIT_VERTEX").is_ok();
76 if let Some(vertex) = vertices
77 .iter()
78 .find(|vertex| vertex.point.sub(point).length() <= tolerance)
79 {
80 if debug {
81 eprintln!(
82 "split-vertex ({:.7},{:.7},{:.7}) band={tolerance:.3e} entity={entity_band:.3e}: reuse v{}",
83 point.x, point.y, point.z, vertex.id
84 );
85 }
86 return vertex.id;
87 }
88 let nearest = imprint
89 .vertices
90 .iter()
91 .map(|vertex| (vertex.point, vertex.point.sub(point).length()))
92 .min_by(|a, b| a.1.total_cmp(&b.1));
93 let canonical = imprint.vertices.iter()
99 .map(|vertex| (vertex.point, vertex.point.sub(point).length()))
100 .filter(|(candidate, distance)| {
101 if *distance <= tolerance {
102 return true;
103 }
104 if *distance > tolerance.max(crate::tolerance::WELD_FLOOR) {
105 return false;
106 }
107 crate::project_point_to_curve(&edge.curve, *candidate).is_ok_and(|projection| {
108 projection.distance <= tolerance
109 && projection.u >= edge.t0.min(edge.t1)
110 && projection.u <= edge.t0.max(edge.t1)
111 })
112 })
113 .min_by(|a, b| a.1.total_cmp(&b.1))
114 .map(|(candidate, _)| candidate)
115 .unwrap_or(point);
116 if let Some(vertex) = vertices.iter()
117 .find(|vertex| vertex.point.sub(canonical).length() <= tolerance)
118 {
119 return vertex.id;
120 }
121 if debug {
122 eprintln!(
123 "split-vertex ({:.7},{:.7},{:.7}) band={tolerance:.3e} entity={entity_band:.3e}: \
124 nearest imprint junction {:.3e} -> {}",
125 point.x,
126 point.y,
127 point.z,
128 nearest.map(|(_, d)| d).unwrap_or(f64::INFINITY),
129 if canonical.sub(point).length() > 0.0 {
130 "SNAPPED"
131 } else {
132 "kept"
133 }
134 );
135 }
136 let id = *next_id;
137 *next_id += 1;
138 vertices.push(VertexRecord {
139 id,
140 point: canonical,
141 });
142 id
143}
144
145pub fn apply_edge_splits(
150 solid: &BrepSolid,
151 operand: u8,
152 imprint: &ImprintResultRecord,
153) -> Result<BrepSolid, KernelRefusal> {
154 apply_edge_splits_with_map(solid, operand, imprint).map(|(result, _)| result)
155}
156
157pub fn apply_edge_splits_with_map(
162 solid: &BrepSolid,
163 operand: u8,
164 imprint: &ImprintResultRecord,
165) -> Result<(BrepSolid, HashMap<u64, Vec<u64>>), KernelRefusal> {
166 let requested = imprint
167 .edge_splits
168 .iter()
169 .filter(|split| split.operand == operand)
170 .map(|split| (split.edge_id, split.parameters.as_slice()))
171 .collect::<HashMap<_, _>>();
172 if requested.is_empty() {
173 return Ok((solid.clone(), HashMap::default()));
174 }
175
176 let mut result = solid.clone();
177 let mut next_vertex_id = result
178 .vertices
179 .iter()
180 .map(|vertex| vertex.id)
181 .max()
182 .unwrap_or(0)
183 + 1;
184 let mut next_edge_id = result.edges.iter().map(|edge| edge.id).max().unwrap_or(0) + 1;
185 let mut next_coedge_id = result
186 .shells
187 .iter()
188 .flat_map(|shell| &shell.faces)
189 .flat_map(|face| &face.loops)
190 .flat_map(|loop_record| &loop_record.coedges)
191 .map(|coedge| coedge.id)
192 .max()
193 .unwrap_or(0)
194 + 1;
195 let mut pieces: HashMap<u64, Vec<EdgeRecord>> = HashMap::default();
196 let mut entity = EntityTolerances::with_floor(solid, 0.0);
205
206 for edge in &solid.edges {
207 let Some(parameters) = requested.get(&edge.id) else {
208 continue;
209 };
210 let parameter_tolerance = (edge.t1 - edge.t0).abs() * 1e-10;
211 let mut partition = parameters
212 .iter()
213 .copied()
214 .filter(|parameter| {
215 *parameter > edge.t0 + parameter_tolerance
216 && *parameter < edge.t1 - parameter_tolerance
217 })
218 .collect::<Vec<_>>();
219 partition.sort_by(f64::total_cmp);
220 partition.dedup_by(|a, b| (*a - *b).abs() <= parameter_tolerance);
221 partition.insert(0, edge.t0);
222 partition.push(edge.t1);
223 if partition.len() <= 2 {
224 continue;
225 }
226 let entity_band = entity.edge(edge.id);
227 let mut vertex_ids = vec![edge.start_vertex_id];
228 for parameter in &partition[1..partition.len() - 1] {
229 let point = edge
230 .curve
231 .evaluate(*parameter)
232 .or_refuse(KernelStage::Refine, "evaluate")?;
233 vertex_ids.push(claim_vertex(
234 &mut result.vertices,
235 imprint,
236 edge,
237 point,
238 entity_band,
239 &mut next_vertex_id,
240 ));
241 }
242 vertex_ids.push(edge.end_vertex_id);
243 let mut edge_pieces = Vec::new();
244 for index in 0..partition.len() - 1 {
245 let name = edge.name.as_ref().map(|name| {
248 if index == 0 {
249 name.clone()
250 } else {
251 format!("{name}_{index}")
252 }
253 });
254 edge_pieces.push(EdgeRecord {
255 id: next_edge_id,
256 curve: edge.curve.clone(),
257 t0: partition[index],
258 t1: partition[index + 1],
259 start_vertex_id: vertex_ids[index],
260 end_vertex_id: vertex_ids[index + 1],
261 degenerate: false,
262 name,
263 });
264 next_edge_id += 1;
265 }
266 pieces.insert(edge.id, edge_pieces);
267 }
268
269 result.edges.retain(|edge| !pieces.contains_key(&edge.id));
270 for edge_pieces in pieces.values() {
271 result.edges.extend(edge_pieces.iter().cloned());
272 }
273
274 let source_edges: HashMap<u64, &EdgeRecord> =
278 solid.edges.iter().map(|edge| (edge.id, edge)).collect();
279
280 for face in result
281 .shells
282 .iter_mut()
283 .flat_map(|shell| shell.faces.iter_mut())
284 {
285 for loop_record in &mut face.loops {
286 let mut rebuilt = Vec::new();
287 for coedge in &loop_record.coedges {
288 let Some(edge_pieces) = pieces.get(&coedge.edge_id) else {
289 rebuilt.push(coedge.clone());
290 continue;
291 };
292 let original = *source_edges.get(&coedge.edge_id).ok_or_else(|| {
293 KernelRefusal::internal(
294 KernelStage::Refine,
295 "edge_split",
296 "apply_edge_splits: missing source edge",
297 )
298 })?;
299 let span = original.t1 - original.t0;
300 let indices: Box<dyn Iterator<Item = usize>> = if coedge.forward {
301 Box::new(0..edge_pieces.len())
302 } else {
303 Box::new((0..edge_pieces.len()).rev())
304 };
305 for index in indices {
306 let piece = &edge_pieces[index];
307 let start_fraction = if coedge.forward {
308 (piece.t0 - original.t0) / span
309 } else {
310 (original.t1 - piece.t1) / span
311 };
312 let end_fraction = if coedge.forward {
313 (piece.t1 - original.t0) / span
314 } else {
315 (original.t1 - piece.t0) / span
316 };
317 rebuilt.push(CoedgeRecord {
318 id: next_coedge_id,
319 edge_id: piece.id,
320 forward: coedge.forward,
321 pcurve: subcurve_by_fraction(&coedge.pcurve, start_fraction, end_fraction)?,
322 });
323 next_coedge_id += 1;
324 }
325 }
326 loop_record.coedges = rebuilt;
327 }
328 }
329 let map = pieces
330 .iter()
331 .map(|(source_id, edge_pieces)| {
332 (
333 *source_id,
334 edge_pieces.iter().map(|piece| piece.id).collect(),
335 )
336 })
337 .collect();
338 Ok((result, map))
339}
340