1use crate::{KernelRefusal, KernelStage, OrRefuse};
2use super::*;
3
4pub(super) struct RimPiece {
6 pub(super) face_id: u64,
7 pub(super) loop_index: usize,
8 pub(super) window: [f64; 2],
10 pub(super) edge_id: u64,
11}
12
13pub fn blend_smooth_chain(
19 solid: &BrepSolid,
20 seed_edge_id: u64,
21 radius: f64,
22 chamfer: bool,
23 name: Option<&str>,
24) -> Result<BrepSolid, KernelRefusal> {
25 if !(radius > 0.0) || !radius.is_finite() {
26 return Err(KernelRefusal::input(KernelStage::Collect, "radius", "blend: radius must be positive"));
27 }
28 let chain = collect_smooth_chain(solid, seed_edge_id)?;
29 if chain.segments.len() < 2 {
30 return Err(KernelRefusal::internal(KernelStage::Refine, "chain_single_segment", "blend: chain collapsed to a single segment"));
31 }
32 if chain.closed {
33 blend_closed_smooth_chain(solid, &chain.segments, radius, chamfer, name)
34 } else {
35 blend_open_smooth_chain(solid, &chain, radius, chamfer, name)
36 }
37}
38
39pub fn blend_smooth_chain_if_closed(
46 solid: &BrepSolid,
47 seed_edge_id: u64,
48 radius: f64,
49 chamfer: bool,
50 name: Option<&str>,
51) -> Result<BrepSolid, KernelRefusal> {
52 if !(radius > 0.0) || !radius.is_finite() {
53 return Err(KernelRefusal::input(KernelStage::Collect, "radius", "blend: radius must be positive"));
54 }
55 let chain = collect_smooth_chain(solid, seed_edge_id)?;
56 if chain.segments.len() < 2 {
57 return Err(KernelRefusal::internal(KernelStage::Refine, "chain_single_segment", "blend: chain collapsed to a single segment"));
58 }
59 if !chain.closed {
60 return Err(
61 KernelRefusal::unsupported(KernelStage::Classify, "open_tangent_chain", "blend: the edge is one arc of an OPEN tangent chain; the unselected \
62 continuation is capped, not blended"),
63 );
64 }
65 blend_closed_smooth_chain(solid, &chain.segments, radius, chamfer, name)
66}
67
68fn blend_closed_smooth_chain(
79 solid: &BrepSolid,
80 segments: &[ChainSegment<'_>],
81 radius: f64,
82 chamfer: bool,
83 name: Option<&str>,
84) -> Result<BrepSolid, KernelRefusal> {
85 let bar = crate::KernelTolerances::for_solid(solid, 1e-7).intersection_fit;
86 let mut per_segment = CHAIN_PER_SEGMENT;
89 let first = loop {
90 let rung = build_closed_smooth_chain(
91 solid,
92 segments,
93 radius,
94 chamfer,
95 name,
96 per_segment,
97 FoldPolicy::Refuse,
98 bar,
99 );
100 match rung {
101 Ok(Rung::TooCoarse { deviation }) => {
102 crate::blend::carve::carve_trace(format_args!(
103 "blend chain: {per_segment} stations per segment leaves the rails \
104 {deviation:.6e} from the rolling ball, against {bar:.3e}"
105 ));
106 if per_segment * 2 > CHAIN_CARVE_MAX_PER_SEGMENT {
107 return Err(KernelRefusal::non_convergence(KernelStage::Refine, "chain_ladder_top", format!(
108 "blend: at {per_segment} stations per segment — the top of the chain's \
109 ladder — this collar's fitted rails are still {deviation:.6e} from the \
110 rolling ball's own contacts against a bar of {bar:.3e}, so there is no \
111 wall accurate enough to build"
112 )));
113 }
114 per_segment *= 2;
115 }
116 other => break other,
117 }
118 };
119 if let Err(error) = &first {
120 crate::blend::carve::carve_trace(format_args!(
121 "blend chain: the march at {per_segment} stations per segment refused: {error}"
122 ));
123 }
124 match first {
125 Ok(Rung::Built(built)) => Ok(built),
126 Ok(Rung::TooCoarse { .. }) => {
127 Err(KernelRefusal::internal(KernelStage::Refine, "chain_ladder_rung", "blend: the chain ladder left an accuracy rung unhandled"))
128 }
129 Err(error) if crate::blend::fold::is_rail_fold(&error) => Err(error),
135 Err(error) if crate::blend::is_wall_fold(&error) && !chamfer => {
138 carve_ladder(solid, segments, radius, name, bar)
139 .map_err(|carve_error| {
147 if crate::blend::is_wall_fold(&carve_error) {
148 carve_error
149 } else {
150 crate::blend::fold::wall_fold_refusal(
151 Vec::new(),
152 format!(
153 "{} {} fits this edge: the wall folds, and the fold band could not \
154 be carved — {carve_error}",
155 crate::blend::WALL_FOLDS,
156 radius.abs()
157 ),
158 )
159 }
160 })
161 }
162 Err(error) => Err(error),
163 }
164}
165
166fn carve_ladder(
176 solid: &BrepSolid,
177 segments: &[ChainSegment<'_>],
178 radius: f64,
179 name: Option<&str>,
180 bar: f64,
181) -> Result<BrepSolid, KernelRefusal> {
182 let mut per_segment = CHAIN_PER_SEGMENT;
183 loop {
184 match build_closed_smooth_chain(
185 solid,
186 segments,
187 radius,
188 false,
189 name,
190 per_segment,
191 FoldPolicy::Carve,
192 bar,
193 )? {
194 Rung::Built(built) => {
195 crate::blend::carve::carve_trace(format_args!(
196 "blend carve: {per_segment} stations per segment is the first rung inside \
197 {bar:.3e}"
198 ));
199 return Ok(built);
200 }
201 Rung::TooCoarse { deviation } => {
202 crate::blend::carve::carve_trace(format_args!(
203 "blend carve: {per_segment} stations per segment leaves the rails \
204 {deviation:.6e} from the rolling ball, against {bar:.3e}"
205 ));
206 if per_segment * 2 > CHAIN_CARVE_MAX_PER_SEGMENT {
207 return Err(crate::blend::fold::wall_fold_refusal(
208 Vec::new(),
209 format!(
210 "{} {} fits this edge: the wall folds, and at {per_segment} stations \
211 per segment — the top of the carve's ladder — its fitted rails are \
212 still {deviation:.6e} from the rolling ball's own contacts against a \
213 bar of {bar:.3e}, so there is no wall accurate enough to carve",
214 crate::blend::WALL_FOLDS,
215 radius.abs()
216 ),
217 ));
218 }
219 per_segment *= 2;
220 }
221 }
222 }
223}
224
225enum Rung {
228 Built(BrepSolid),
229 TooCoarse { deviation: f64 },
230}
231
232fn rail_deviation(
253 samples: &[ChainSample],
254 cr: &NurbsCurve,
255 cs: &NurbsCurve,
256 fit_low: f64,
257 fit_range: f64,
258) -> Result<f64, KernelRefusal> {
259 let parameter_of = |segment: usize, position: isize| -> Option<f64> {
260 samples
261 .iter()
262 .find(|sample| sample.segment == segment && sample.position == position)
263 .map(|sample| sample.parameter)
264 };
265 let closest = |curve: &NurbsCurve, target: Vec3, seed: f64, reach: f64| -> Result<f64, KernelRefusal> {
266 let [low, high] = curve.domain().or_refuse(KernelStage::Refine, "domain")?;
267 let distance = |u: f64| -> Result<f64, KernelRefusal> {
268 Ok(curve.derivatives_extended(u, 0).or_refuse(KernelStage::Refine, "derivatives_extended")?[0].sub(target).length())
269 };
270 let mut u = seed.clamp(low, high);
271 let mut best = distance(u)?;
272 for _ in 0..40 {
273 let derivatives = curve.derivatives_extended(u, 2).or_refuse(KernelStage::Refine, "derivatives_extended")?;
274 let offset = derivatives[0].sub(target);
275 let slope = offset.dot(derivatives[1]);
276 let curvature = derivatives[1].dot(derivatives[1]) + offset.dot(derivatives[2]);
277 let mut step = if curvature > 0.0 {
280 (slope / curvature).clamp(-reach, reach)
281 } else {
282 reach.copysign(slope)
283 };
284 let mut trial = (u - step).clamp(low, high);
285 let mut trial_distance = distance(trial)?;
286 for _ in 0..30 {
287 if trial_distance < best {
288 break;
289 }
290 step *= 0.5;
291 trial = (u - step).clamp(low, high);
292 trial_distance = distance(trial)?;
293 }
294 if !(trial_distance < best) {
295 break;
296 }
297 let moved = (trial - u).abs();
298 u = trial;
299 best = trial_distance;
300 if moved < 1e-15 {
301 break;
302 }
303 }
304 Ok(best)
305 };
306 let mut worst: f64 = 0.0;
307 let mut per_segment: Vec<(f64, isize, f64, f64, Vec3, Vec3)> = Vec::new();
310 for sample in samples {
311 let Some([p1, p2]) = sample.midpoint else {
312 continue;
313 };
314 let Some(next) = parameter_of(sample.segment, sample.position + 1) else {
315 continue;
316 };
317 let global = (0.5 * (sample.parameter + next)).rem_euclid(1.0);
318 let seed = (global - fit_low) / fit_range;
319 let reach = (next - sample.parameter).abs() / fit_range;
320 let (miss1, miss2) = (closest(cr, p1, seed, reach)?, closest(cs, p2, seed, reach)?);
321 worst = worst.max(miss1).max(miss2);
322 if per_segment.len() <= sample.segment {
323 per_segment.resize(
324 sample.segment + 1,
325 (0.0, 0, 0.0, 0.0, Vec3::default(), Vec3::default()),
326 );
327 }
328 if miss1.max(miss2) >= per_segment[sample.segment].0 {
329 per_segment[sample.segment] = (miss1.max(miss2), sample.position, miss1, miss2, p1, p2);
330 }
331 }
332 for (segment, (miss, position, miss1, miss2, p1, p2)) in per_segment.iter().enumerate() {
333 crate::blend::carve::carve_trace(format_args!(
334 " rail miss segment {segment}: worst {miss:.6e} after station {position} \
335 (cr {miss1:.6e} at ({:.6}, {:.6}, {:.6}), cs {miss2:.6e} at ({:.6}, {:.6}, {:.6}))",
336 p1.x, p1.y, p1.z, p2.x, p2.y, p2.z
337 ));
338 }
339 Ok(worst)
340}
341
342fn build_closed_smooth_chain(
343 solid: &BrepSolid,
344 segments: &[ChainSegment<'_>],
345 radius: f64,
346 chamfer: bool,
347 name: Option<&str>,
348 per_segment: usize,
349 fold: FoldPolicy,
350 bar: f64,
351) -> Result<Rung, KernelRefusal> {
352 let samples = march_chain(segments, radius, false, per_segment, fold)?;
353
354 let mut global: Vec<(f64, &ChainSample)> = samples
356 .iter()
357 .filter(|sample| (0..per_segment as isize).contains(&sample.position))
358 .map(|sample| (sample.parameter.rem_euclid(1.0), sample))
359 .collect();
360 global.sort_by(|a, b| a.0.total_cmp(&b.0));
361 let count = global.len();
362 let degree = FIT_DEGREE;
363 let mut extended_params = Vec::new();
364 let mut samples_cr = Vec::new();
365 let mut samples_mid = Vec::new();
366 let mut samples_cs = Vec::new();
367 let mut push = |station: &Station, parameter: f64| {
368 extended_params.push(parameter);
369 samples_cr.push(Vec4::from_point(station.p1, 1.0));
370 samples_cs.push(Vec4::from_point(station.p2, 1.0));
371 samples_mid.push(Vec4 {
372 x: station.apex.x * station.weight,
373 y: station.apex.y * station.weight,
374 z: station.apex.z * station.weight,
375 w: station.weight,
376 });
377 };
378 for offset in (1..=degree).rev() {
379 let (parameter, sample) = &global[count - offset];
380 push(&sample.station, parameter - 1.0);
381 }
382 for (parameter, sample) in &global {
383 push(&sample.station, *parameter);
384 }
385 push(&global[0].1.station, global[0].0 + 1.0);
388 for offset in 1..=degree {
389 let (parameter, sample) = &global[offset];
390 push(&sample.station, parameter + 1.0);
391 }
392 let low = extended_params[0];
393 let high = *extended_params.last().unwrap();
394 let range = high - low;
395 let normalized: Vec<f64> = extended_params
396 .iter()
397 .map(|parameter| (parameter - low) / range)
398 .collect();
399 let seam_low = (0.0 - low) / range;
400 let seam_high = (1.0 - low) / range;
401 let fit_row = |row: &[Vec4]| -> Result<NurbsCurve, KernelRefusal> {
402 let curve = fit::interpolate_homogeneous(row, degree, &normalized).or_refuse(KernelStage::Refine, "interpolate_homogeneous")?;
403 let (_, tail) = curve.split(seam_low).or_refuse(KernelStage::Refine, "split")?;
404 let (middle, _) = tail.split(seam_high).or_refuse(KernelStage::Refine, "split")?;
405 Ok(middle)
406 };
407 let cr = fit_row(&samples_cr)?;
408 let cs = fit_row(&samples_cs)?;
409 let mid = if chamfer {
410 None
411 } else {
412 Some(fit_row(&samples_mid)?)
413 };
414 let u_domain = cr.domain().or_refuse(KernelStage::Refine, "domain")?;
415 let surface = crate::blend::rows::surface_from_rows(degree, &cr, &cs, mid.as_ref(), true)?;
416
417 let deviation = rail_deviation(&samples, &cr, &cs, low, range)?;
430 if deviation > bar {
431 return Ok(Rung::TooCoarse { deviation });
432 }
433 crate::blend::carve::carve_trace(format_args!(
434 "blend chain: {per_segment} stations per segment leaves the rails {deviation:.6e} \
435 from the rolling ball, inside {bar:.3e}"
436 ));
437 let crease = if fold == FoldPolicy::Carve {
438 let bar = crate::KernelTolerances::for_solid(solid, 1e-7).pcurve_consistency;
448 let Some(carved) = crate::blend::carve::trace_wall_crease(&surface, radius.abs())? else {
449 return Err(KernelRefusal::internal(KernelStage::Refine, "carve_no_fold", format!(
450 "blend carve: the wall re-marched at {per_segment} stations per segment carries no fold to carve, but the march at {CHAIN_PER_SEGMENT} refused one"
451 )));
452 };
453 Some(crate::blend::carve::fit_crease(&surface, &carved, bar)?)
454 } else {
455 None
456 };
457
458 let single_face = |side: usize| -> bool {
462 let first_id = segments[0].mate(side).face.id;
463 segments
464 .iter()
465 .all(|segment| segment.mate(side).face.id == first_id)
466 };
467 let mut whole_pcurves: [Option<NurbsCurve>; 2] = [None, None];
468 for side in 0..2 {
469 if !single_face(side) {
470 continue;
471 }
472 let mut row = Vec::new();
473 let select = |station: &Station| -> [f64; 2] {
474 if side == 0 {
475 station.uv1
476 } else {
477 station.uv2
478 }
479 };
480 let mut push_uv = |station: &Station| {
481 let uv = select(station);
482 row.push(Vec4::from_point(Vec3::new(uv[0], uv[1], 0.0), 1.0));
483 };
484 for offset in (1..=degree).rev() {
485 push_uv(&global[count - offset].1.station);
486 }
487 for (_, sample) in &global {
488 push_uv(&sample.station);
489 }
490 push_uv(&global[0].1.station);
491 for offset in 1..=degree {
492 push_uv(&global[offset].1.station);
493 }
494 whole_pcurves[side] = Some(fit_row(&row)?);
495 }
496
497 let mut pcurves1 = Vec::with_capacity(segments.len());
500 let mut pcurves2 = Vec::with_capacity(segments.len());
501 for segment in 0..segments.len() {
502 let mut window: Vec<&ChainSample> = samples
503 .iter()
504 .filter(|sample| sample.segment == segment)
505 .collect();
506 window.sort_by(|a, b| a.parameter.total_cmp(&b.parameter));
507 let params: Vec<f64> = window.iter().map(|sample| sample.parameter).collect();
508 let low = params[0];
509 let high = *params.last().unwrap();
510 let normalized: Vec<f64> = params
511 .iter()
512 .map(|parameter| (parameter - low) / (high - low))
513 .collect();
514 let fit_uv = |select: &dyn Fn(&Station) -> [f64; 2]| -> Result<NurbsCurve, KernelRefusal> {
515 let points: Vec<Vec4> = window
516 .iter()
517 .map(|sample| {
518 let uv = select(&sample.station);
519 Vec4::from_point(Vec3::new(uv[0], uv[1], 0.0), 1.0)
520 })
521 .collect();
522 let curve = fit::interpolate_homogeneous(&points, degree, &normalized).or_refuse(KernelStage::Refine, "interpolate_homogeneous")?;
523 Ok(curve)
527 };
528 pcurves1.push((low, high, fit_uv(&|station| station.uv1)?));
529 pcurves2.push((low, high, fit_uv(&|station| station.uv2)?));
530 }
531
532 Ok(Rung::Built(chain_surgery(
533 solid,
534 segments,
535 ChainRows {
536 surface,
537 cr,
538 cs,
539 u_domain,
540 fit_low: low,
541 fit_range: range,
542 pcurves1,
543 pcurves2,
544 whole_pcurves,
545 crease,
546 },
547 name,
548 )?))
549}
550
551pub(super) struct ChainRows {
552 pub(super) surface: NurbsSurface,
553 pub(super) cr: NurbsCurve,
554 pub(super) cs: NurbsCurve,
555 pub(super) u_domain: [f64; 2],
556 pub(super) fit_low: f64,
560 pub(super) fit_range: f64,
561 pub(super) pcurves1: Vec<(f64, f64, NurbsCurve)>,
564 pub(super) pcurves2: Vec<(f64, f64, NurbsCurve)>,
565 pub(super) whole_pcurves: [Option<NurbsCurve>; 2],
567 pub(super) crease: Option<crate::blend::carve::CarvedCrease>,
570}
571
572impl ChainRows {
573 pub(super) fn to_global(&self, fit_parameter: f64) -> f64 {
574 self.fit_low + fit_parameter * self.fit_range
575 }
576}
577
578pub(super) fn pcurve_portion(fitted: &(f64, f64, NurbsCurve), a: f64, b: f64) -> Result<NurbsCurve, KernelRefusal> {
582 let (low, high, curve) = fitted;
583 let to_local = |value: f64| {
586 let mut best = value;
587 for candidate in [value, value + 1.0, value - 1.0] {
588 if candidate >= low - 1e-9 && candidate <= high + 1e-9 {
589 best = candidate;
590 break;
591 }
592 }
593 ((best - low) / (high - low)).clamp(0.0, 1.0)
594 };
595 let mut local_a = to_local(a);
596 let mut local_b = to_local(b);
597 if local_a > local_b {
598 std::mem::swap(&mut local_a, &mut local_b);
599 }
600 let epsilon = 1e-9;
601 let (_, tail) = if local_a > epsilon {
602 curve.split(local_a).or_refuse(KernelStage::Refine, "split")?
603 } else {
604 (curve.clone(), curve.clone())
605 };
606 let tail = if local_a > epsilon {
607 tail
608 } else {
609 curve.clone()
610 };
611 let portion = if local_b < 1.0 - epsilon {
612 tail.split(local_b).or_refuse(KernelStage::Refine, "split")?.0
613 } else {
614 tail
615 };
616 Ok(portion)
617}
618
619pub(super) fn cross_edge_at(
625 solid: &BrepSolid,
626 face_before: &FaceRecord,
627 loop_before: usize,
628 face_after: &FaceRecord,
629 loop_after: usize,
630 vertex: u64,
631 before_edge: u64,
632 after_edge: u64,
633) -> Result<Option<u64>, KernelRefusal> {
634 let mut found: Option<u64> = None;
635 let mut consider = |face: &FaceRecord, loop_index: usize| -> Result<(), KernelRefusal> {
636 for coedge in &face.loops[loop_index].coedges {
637 if coedge.edge_id == before_edge || coedge.edge_id == after_edge {
638 continue;
639 }
640 let edge = solid
641 .edges
642 .iter()
643 .find(|edge| edge.id == coedge.edge_id)
644 .ok_or(KernelRefusal::internal(KernelStage::Refine, "missing_edge", "blend: loop references missing edge"))?;
645 if edge.start_vertex_id == vertex || edge.end_vertex_id == vertex {
646 if found.is_some() && found != Some(edge.id) {
647 return Err(KernelRefusal::unsupported(KernelStage::Classify, "chain_cross_edges", "blend: multiple cross edges at a chain vertex"));
648 }
649 found = Some(edge.id);
650 }
651 }
652 Ok(())
653 };
654 consider(face_before, loop_before)?;
655 if !(face_after.id == face_before.id && loop_after == loop_before) {
656 consider(face_after, loop_after)?;
657 }
658 Ok(found)
659}
660
661fn nearest_seam_image(value: f64, low: f64, high: f64) -> f64 {
664 let period = high - low;
665 low + ((value - low) / period).round() * period
666}
667
668pub(super) fn project_piece_pcurve(
686 piece: &NurbsCurve,
687 surface: &NurbsSurface,
688 start_is_crossing: bool,
689 end_is_crossing: bool,
690) -> Result<NurbsCurve, KernelRefusal> {
691 let [u0, u1] = surface.domain_u().or_refuse(KernelStage::Refine, "domain_u")?;
692 let [v0, v1] = surface.domain_v().or_refuse(KernelStage::Refine, "domain_v")?;
693 let (_, closed_v) = surface.closed_directions().or_refuse(KernelStage::Refine, "closed_directions")?;
694 let period = u1 - u0;
695 let period_v = v1 - v0;
696 let [d0, d1] = piece.domain().or_refuse(KernelStage::Refine, "domain")?;
697 let dense = (8 * piece.control_points.len()).clamp(96, 4096);
705 let mut proj_u = Vec::with_capacity(dense + 1);
706 let mut proj_v = Vec::with_capacity(dense + 1);
707 let mut point3 = Vec::with_capacity(dense + 1);
708 let mut params = Vec::with_capacity(dense + 1);
709 let mut previous_u: Option<f64> = None;
710 let mut previous_foot: Option<[f64; 2]> = None;
715 let mut feet: Vec<[f64; 2]> = Vec::with_capacity(dense + 1);
716 for section in 0..=dense {
717 let t = d0 + (d1 - d0) * section as f64 / dense as f64;
718 let point = piece.evaluate(t).or_refuse(KernelStage::Refine, "evaluate")?;
719 let projection = match previous_foot {
720 None => crate::project_point_to_surface(surface, point).or_refuse(KernelStage::Refine, "project_point_to_surface")?,
721 Some(seed) => crate::projection::project_point_to_surface_from_seed(surface, point, seed).or_refuse(KernelStage::Refine, "project_point_to_surface_from_seed")?,
722 };
723 previous_foot = Some([projection.u, projection.v]);
724 feet.push([projection.u, projection.v]);
725 let mut u = projection.u;
726 if let Some(previous) = previous_u {
727 while u - previous > period * 0.5 {
728 u -= period;
729 }
730 while previous - u > period * 0.5 {
731 u += period;
732 }
733 }
734 previous_u = Some(u);
735 proj_u.push(u);
736 proj_v.push(projection.v);
737 point3.push(point);
738 params.push(section as f64 / dense as f64);
739 }
740 if closed_v {
747 for index in 2..=dense {
748 while proj_v[index] - proj_v[index - 1] > period_v * 0.5 {
749 proj_v[index] -= period_v;
750 }
751 while proj_v[index - 1] - proj_v[index] > period_v * 0.5 {
752 proj_v[index] += period_v;
753 }
754 }
755 while proj_v[0] - proj_v[1] > period_v * 0.5 {
756 proj_v[0] -= period_v;
757 }
758 while proj_v[1] - proj_v[0] > period_v * 0.5 {
759 proj_v[0] += period_v;
760 }
761 }
762 let mean_u: f64 = proj_u[1..dense].iter().sum::<f64>() / (dense - 1) as f64;
765 let seam_u = if mean_u - u0 > u1 - mean_u { u1 } else { u0 };
766 let pinned = |index: usize,
775 neighbour: usize,
776 proj_u: &[f64],
777 proj_v: &[f64]|
778 -> Result<(f64, f64), KernelRefusal> {
779 let point = point3[index];
780 let u_error = surface
781 .evaluate(seam_u, proj_v[index]).or_refuse(KernelStage::Refine, "evaluate")?
782 .sub(point)
783 .length();
784 if closed_v {
785 let seam_v = nearest_seam_image(proj_v[neighbour], v0, v1);
786 let v_error = surface
787 .evaluate(proj_u[index], seam_v).or_refuse(KernelStage::Refine, "evaluate")?
788 .sub(point)
789 .length();
790 if v_error < u_error {
791 return Ok((proj_u[index], seam_v));
792 }
793 }
794 Ok((seam_u, proj_v[index]))
795 };
796 if start_is_crossing {
797 let (u, v) = pinned(0, 1, &proj_u, &proj_v)?;
798 proj_u[0] = u;
799 proj_v[0] = v;
800 }
801 if end_is_crossing {
802 let (u, v) = pinned(dense, dense - 1, &proj_u, &proj_v)?;
803 proj_u[dense] = u;
804 proj_v[dense] = v;
805 }
806 let mut shift = 0.0;
807 while mean_u + shift > u1 + 1e-9 {
808 shift -= period;
809 }
810 while mean_u + shift < u0 - 1e-9 {
811 shift += period;
812 }
813 for u in proj_u.iter_mut() {
814 *u += shift;
815 }
816 if closed_v {
817 let mean_v: f64 = proj_v[1..dense].iter().sum::<f64>() / (dense - 1) as f64;
818 let mut shift_v = 0.0;
819 while mean_v + shift_v > v1 + 1e-9 {
820 shift_v -= period_v;
821 }
822 while mean_v + shift_v < v0 - 1e-9 {
823 shift_v += period_v;
824 }
825 for v in proj_v.iter_mut() {
826 *v += shift_v;
827 }
828 }
829 let tolerance = crate::pcurve::PCURVE_REFINEMENT_TOLERANCE;
844 let at = |fraction: f64| piece.evaluate(d0 + (d1 - d0) * fraction);
851 let mut track = crate::blend::track_fit::Track {
852 fractions: params,
853 uv: proj_u.iter().zip(&proj_v).map(|(u, v)| [*u, *v]).collect(),
854 feet,
855 breaks: Vec::new(),
856 };
857 let breaks = crate::blend::track_fit::curve_breaks(piece, d0, d1, true);
858 crate::blend::track_fit::polish_track(surface, &at, &mut track)?;
859 crate::blend::track_fit::insert_knot_crossings(surface, &at, &mut track, &breaks)?;
860 let fit = crate::blend::track_fit::fit_track(surface, &track, &at, tolerance)?;
861 if std::env::var("BREP_DEBUG_TRACK_FIT").is_ok() {
862 eprintln!(
863 "TRACK_FIT closed-chain piece: miss {:.3e} at {} samples of {} ({} breaks), on floor {}",
864 fit.miss,
865 fit.samples,
866 track.fractions.len(),
867 track.breaks.len(),
868 fit.on_floor
869 );
870 }
871 if !fit.on_floor {
878 return Err(KernelRefusal::non_convergence(KernelStage::Refine, "chain_pcurve_floor", format!(
879 "{} a closed chain's support piece misses its pcurve by {:.3e} at {} samples, \
880 against a floor of {tolerance:.1e}",
881 crate::blend::PCURVE_OFF_FLOOR,
882 fit.miss,
883 fit.samples
884 )));
885 }
886 Ok(fit.curve)
887}