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