brep_kernel/construction/loft_topology/
closed.rs1use super::*;
2
3pub fn loft_profile_brep_closed(input_sections: &[Vec<NurbsCurve>]) -> Result<BrepSolid, String> {
12 let tolerance = 1e-6;
13 let section_count = input_sections.len();
14 if section_count < 4 {
15 return Err("loftSolid: a closed loft needs at least 4 sections".into());
16 }
17 let sections = input_sections.to_vec();
18 let curve_count = sections[0].len();
19 if sections.iter().any(|section| section.len() != curve_count) {
20 return Err("loftSolid: sections must have the same curve count".into());
21 }
22 for section in §ions {
23 closed_points(section, tolerance)?;
24 }
25 for curve_index in 0..curve_count {
26 let reference = §ions[0][curve_index];
27 for (section_index, section) in sections.iter().enumerate().skip(1) {
28 let curve = §ion[curve_index];
29 if curve.degree != reference.degree
30 || curve.control_points.len() != reference.control_points.len()
31 || curve.knots.len() != reference.knots.len()
32 || curve
33 .knots
34 .iter()
35 .zip(&reference.knots)
36 .any(|(a, b)| (a - b).abs() > 1e-9)
37 || curve
38 .control_points
39 .iter()
40 .zip(&reference.control_points)
41 .any(|(a, b)| (a.w - b.w).abs() > 1e-9)
42 {
43 return Err(format!(
44 "loftSolid: section {section_index} curve {curve_index} incompatible with section 0"
45 ));
46 }
47 }
48 }
49
50 let mut accumulated = vec![0.0; section_count + 1];
53 let mut columns = 0usize;
54 for curve_index in 0..curve_count {
55 for control_index in 0..sections[0][curve_index].control_points.len() {
56 let mut chords = vec![0.0; section_count + 1];
57 let mut total = 0.0;
58 for station in 1..=section_count {
59 let previous =
60 sections[station - 1][curve_index].control_points[control_index].point()?;
61 let current = sections[station % section_count][curve_index].control_points
62 [control_index]
63 .point()?;
64 total += current.sub(previous).length();
65 chords[station] = total;
66 }
67 if total <= tolerance {
68 continue;
69 }
70 for station in 0..=section_count {
71 accumulated[station] += chords[station] / total;
72 }
73 columns += 1;
74 }
75 }
76 if columns == 0 {
77 return Err("loftSolid: sections coincide".into());
78 }
79 let mut parameters = vec![0.0; section_count + 1];
80 for station in 0..=section_count {
81 parameters[station] = accumulated[station] / columns as f64;
82 }
83 parameters[0] = 0.0;
84 parameters[section_count] = 1.0;
85 if parameters.windows(2).any(|pair| pair[1] <= pair[0] + 1e-9) {
86 return Err("loftSolid: closed sections are not strictly ordered".into());
87 }
88
89 let mut skins = Vec::with_capacity(curve_count);
90 for curve_index in 0..curve_count {
91 let reference = §ions[0][curve_index];
92 let mut grid = Vec::with_capacity(reference.control_points.len());
93 let mut knots_v = Vec::new();
94 for control_index in 0..reference.control_points.len() {
95 let points = sections
96 .iter()
97 .map(|section| section[curve_index].control_points[control_index].point())
98 .collect::<Result<Vec<_>, _>>()?;
99 let interpolated = crate::interpolate_curve_closed(&points, ¶meters)?;
100 knots_v = interpolated.knots.clone();
101 let weight = reference.control_points[control_index].w;
102 grid.push(
103 interpolated
104 .control_points
105 .iter()
106 .map(|point| Vec4::from_point(point.point().unwrap(), weight))
107 .collect(),
108 );
109 }
110 skins.push(NurbsSurface::new(
111 reference.degree,
112 3,
113 reference.knots.clone(),
114 knots_v,
115 grid,
116 )?);
117 }
118
119 let base_points = closed_points(§ions[0], tolerance)?;
123 let mut vertices = Vec::with_capacity(curve_count);
124 for (index, point) in base_points.iter().enumerate() {
125 vertices.push(VertexRecord {
126 id: index as u64 + 1,
127 point: *point,
128 });
129 }
130 let mut edges = Vec::with_capacity(2 * curve_count);
131 let mut ring_edge_ids = Vec::with_capacity(curve_count);
132 let mut seam_edge_ids = Vec::with_capacity(curve_count);
133 for index in 0..curve_count {
134 let [u_start, u_end] = sections[0][index].domain()?;
135 let _ = u_end;
136 let ring = skins[index].iso_curve_u(u_start)?;
137 let [ring_start, ring_end] = ring.domain()?;
138 let ring_id = 100 + index as u64;
139 ring_edge_ids.push(ring_id);
140 edges.push(EdgeRecord {
141 id: ring_id,
142 curve: ring,
143 t0: ring_start,
144 t1: ring_end,
145 start_vertex_id: index as u64 + 1,
146 end_vertex_id: index as u64 + 1,
147 degenerate: false,
148 name: None,
149 });
150 let seam = sections[0][index].clone();
151 let [seam_start, seam_end] = seam.domain()?;
152 let seam_id = 100 + curve_count as u64 + index as u64;
153 seam_edge_ids.push(seam_id);
154 edges.push(EdgeRecord {
155 id: seam_id,
156 curve: seam,
157 t0: seam_start,
158 t1: seam_end,
159 start_vertex_id: index as u64 + 1,
160 end_vertex_id: ((index + 1) % curve_count) as u64 + 1,
161 degenerate: false,
162 name: None,
163 });
164 }
165
166 let mut next_id = 1000u64;
167 let mut faces = Vec::with_capacity(curve_count);
168 for index in 0..curve_count {
169 let [u_start, u_end] = sections[0][index].domain()?;
170 let coedges = vec![
173 CoedgeRecord {
174 id: next_id,
175 edge_id: seam_edge_ids[index],
176 forward: true,
177 pcurve: parameter_line(u_start, 0.0, u_end, 0.0)?,
178 },
179 CoedgeRecord {
180 id: next_id + 1,
181 edge_id: ring_edge_ids[(index + 1) % curve_count],
182 forward: true,
183 pcurve: parameter_line(u_end, 0.0, u_end, 1.0)?,
184 },
185 CoedgeRecord {
186 id: next_id + 2,
187 edge_id: seam_edge_ids[index],
188 forward: false,
189 pcurve: parameter_line(u_end, 1.0, u_start, 1.0)?,
190 },
191 CoedgeRecord {
192 id: next_id + 3,
193 edge_id: ring_edge_ids[index],
194 forward: false,
195 pcurve: parameter_line(u_start, 1.0, u_start, 0.0)?,
196 },
197 ];
198 next_id += 4;
199 let loop_id = next_id;
200 next_id += 1;
201 let face_id = next_id;
202 next_id += 1;
203 faces.push(FaceRecord {
204 id: face_id,
205 surface: skins[index].clone(),
206 same_sense: true,
207 loops: vec![LoopRecord {
208 id: loop_id,
209 coedges,
210 }],
211 name: None,
212 });
213 }
214
215 let mut solid = BrepSolid {
216 id: next_id,
217 vertices,
218 edges,
219 shells: vec![ShellRecord {
220 id: next_id + 1,
221 faces,
222 }],
223 genus: 1,
224 };
225 let issues = solid.validate();
226 if !issues.is_empty() {
227 return Err(format!(
228 "loftSolid: closed loft failed validation: {issues:?}"
229 ));
230 }
231 if crate::solid_signed_volume(&solid)? < 0.0 {
234 crate::offset_shell::flip_all_faces(&mut solid)?;
235 }
236 Ok(solid)
237}