brep_kernel/construction/
loft_guides.rs1use crate::{coons_bilinear, face_from_surface, face_from_surface_trimmed, make_plane, solid_from_sheets, BrepSolid, KernelRefusal, KernelStage, NurbsCurve, OrRefuse, Vec3};
12
13pub const LOFT_ON_GUIDES_REFUSAL: &str = "loftOnGuides";
15
16fn input(what: &'static str, message: String) -> KernelRefusal {
17 KernelRefusal::input(KernelStage::Collect, what, format!("{LOFT_ON_GUIDES_REFUSAL}: {message}"))
18}
19
20fn ends(curve: &NurbsCurve) -> Result<[Vec3; 2], KernelRefusal> {
21 let [t0, t1] = curve.domain().or_refuse(KernelStage::Collect, "loftOnGuides.domain")?;
22 Ok([curve.evaluate(t0).or_refuse(KernelStage::Collect, "loftOnGuides.end")?, curve.evaluate(t1).or_refuse(KernelStage::Collect, "loftOnGuides.end")?])
23}
24
25fn poles(curves: &[NurbsCurve]) -> Vec<Vec3> {
26 curves.iter().flat_map(|curve| &curve.control_points).map(|pole| Vec3::new(pole.x / pole.w, pole.y / pole.w, pole.z / pole.w)).collect()
27}
28
29fn flat_end(section: &[NurbsCurve], size: f64, which: &str) -> Result<BrepSolid, KernelRefusal> {
31 let points = poles(section);
32 let origin = points[0];
33 let far = |off: Option<Vec3>| {
34 points.iter().map(|point| point.sub(origin)).map(|arm| off.map_or(arm, |axis| arm.sub(axis.scale(arm.dot(axis))))).fold(Vec3::new(0.0, 0.0, 0.0), |best, arm| if arm.length() > best.length() { arm } else { best })
35 };
36 let flat = || input("section_plane", format!("the {which} section does not lie in a plane"));
37 let e1 = far(None).normalized().map_err(|_| flat())?;
38 let across = far(Some(e1));
39 if across.length() <= 1e-9 * size {
40 return Err(flat());
41 }
42 let e2 = across.normalized().map_err(|_| flat())?;
43 let normal = e1.cross(e2);
44 if points.iter().any(|point| point.sub(origin).dot(normal).abs() > 1e-9 * size) {
45 return Err(flat());
46 }
47 let reach = |axis: Vec3| points.iter().map(|point| point.sub(origin).dot(axis)).fold((0.0f64, 0.0f64), |(low, high), at| (low.min(at), high.max(at)));
48 let ((low1, high1), (low2, high2)) = (reach(e1), reach(e2));
49 let corner = origin.add(e1.scale(low1 - 0.5 * size)).add(e2.scale(low2 - 0.5 * size));
50 let plane = make_plane(corner, e1, e2, high1 - low1 + size, high2 - low2 + size).or_refuse(KernelStage::Collect, "loftOnGuides.plane")?;
51 face_from_surface_trimmed(&plane, &[section.to_vec()])
52}
53
54fn pointed_wall(side: &NurbsCurve, rail: &NurbsCurve, next: &NurbsCurve, k: usize, tolerance: f64) -> Result<crate::NurbsSurface, KernelRefusal> {
59 let [from, to] = ends(side)?;
60 let straight = poles(std::slice::from_ref(side)).iter().all(|point| point.sub(from).cross(to.sub(from)).length() <= tolerance * to.sub(from).length());
61 if !straight {
62 return Err(KernelRefusal::unsupported(KernelStage::Fragment, "pointed_wall_side", format!("{LOFT_ON_GUIDES_REFUSAL}: wall {k} closes on a point from a side that is not straight")));
63 }
64 if rail.degree != next.degree || rail.knots != next.knots {
65 return Err(KernelRefusal::unsupported(KernelStage::Fragment, "pointed_wall_guides", format!("{LOFT_ON_GUIDES_REFUSAL}: wall {k} closes on a point between guides of different degree or knots")));
66 }
67 let net = vec![rail.control_points.clone(), next.control_points.clone()];
68 crate::NurbsSurface::new(1, rail.degree, vec![0.0, 0.0, 1.0, 1.0], rail.knots.clone(), net).or_refuse(KernelStage::Fragment, "loftOnGuides.pointed_wall")
69}
70
71pub fn loft_profile_brep_on_guides(first: &[NurbsCurve], last: &[NurbsCurve], guides: &[NurbsCurve]) -> Result<BrepSolid, KernelRefusal> {
84 let count = first.len();
85 if count < 2 || last.len() != count || guides.len() != count {
86 return Err(input("counts", format!("the sections and the guides must be equally many, at least 2 (got {count}, {} and {})", last.len(), guides.len())));
87 }
88 let all = poles(&[first, last, guides].concat());
89 let size = all.iter().map(|point| point.sub(all[0]).length()).fold(0.0, f64::max);
90 let tolerance = 1e-9 * size;
91 let near = |a: Vec3, b: Vec3| a.sub(b).length() <= tolerance;
92 let mut rails = Vec::with_capacity(count);
94 for k in 0..count {
95 let ([foot, _], [head, _]) = (ends(&first[k])?, ends(&last[k])?);
96 let [from, to] = ends(&guides[k])?;
97 let rail = if near(from, foot) && near(to, head) {
98 guides[k].clone()
99 } else if near(to, foot) && near(from, head) {
100 guides[k].reversed().or_refuse(KernelStage::Collect, "loftOnGuides.reverse")?
101 } else {
102 return Err(input("guide_ends", format!("guide {k} does not join the start of curve {k} of the first section to that of the last")));
103 };
104 rails.push(rail);
105 }
106 let mut sheets = Vec::with_capacity(count + 2);
107 for k in 0..count {
108 let next = (k + 1) % count;
109 let far_side = last[k].reversed().or_refuse(KernelStage::Collect, "loftOnGuides.reverse")?;
110 let back = rails[k].reversed().or_refuse(KernelStage::Collect, "loftOnGuides.reverse")?;
111 let [head, head_next] = ends(&last[k])?;
112 let patch = if near(head, head_next) && poles(&last[k..=k]).iter().all(|point| near(*point, head)) {
113 pointed_wall(&first[k], &rails[k], &rails[next], k, tolerance)?
114 } else {
115 coons_bilinear([first[k].clone(), rails[next].clone(), far_side, back])
116 .map_err(|error| KernelRefusal::unsupported(KernelStage::Fragment, "wall", format!("{LOFT_ON_GUIDES_REFUSAL}: wall {k} has no Coons patch: {error:?}")))?
117 };
118 sheets.push(face_from_surface(&patch)?);
119 }
120 sheets.push(flat_end(first, size, "first")?);
121 let apex = ends(&last[0])?[0];
122 let pointed = poles(last).iter().all(|point| near(*point, apex));
123 if !pointed {
124 sheets.push(flat_end(last, size, "last")?);
125 }
126 solid_from_sheets(&sheets, (1e-8 * size).max(1e-7))
128}
129