1use std::collections::HashMap;
2use std::fs::File;
3use std::io::{BufRead, BufReader, BufWriter, Read, Seek, SeekFrom, Write};
4
5use crate::error::{Result, Error};
6
7#[cfg(feature = "rayon")]
8use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
9
10pub enum StlFormat {
15 Ascii,
16 Binary,
17}
18
19pub fn write_stl(
30 nodes: &[[f64; 3]],
31 cells: &[[usize; 3]],
32 filename: Option<&str>,
33 format: Option<StlFormat>,
34) -> Result<()> {
35 match format.unwrap_or(StlFormat::Binary) {
36 StlFormat::Ascii => write_stl_ascii(nodes, cells, filename),
37 StlFormat::Binary => write_stl_binary(nodes, cells, filename),
38 }
39}
40
41pub fn write_stl_ascii(
54 nodes: &[[f64; 3]],
55 cells: &[[usize; 3]],
56 filename: Option<&str>,
57) -> Result<()> {
58 let process_cell = |cell: &[usize; 3]| -> String {
60 let (normal, [p1, p2, p3]) = compute_facet_data(nodes, cell);
61 format!(
62 " facet normal {} {} {}\n outer loop\n vertex {} {} {}\n vertex {} {} {}\n vertex {} {} {}\n endloop\n endfacet",
63 normal[0], normal[1], normal[2],
64 p1[0], p1[1], p1[2],
65 p2[0], p2[1], p2[2],
66 p3[0], p3[1], p3[2]
67 )
68 };
69
70 #[cfg(feature = "rayon")]
72 let facet_strings: Vec<String> = cells.par_iter().map(process_cell).collect();
73
74 #[cfg(not(feature = "rayon"))]
75 let facet_strings: Vec<String> = cells.iter().map(process_cell).collect();
76
77 let file = File::create(filename.unwrap_or("mesh.stl"))?;
79 let mut writer = BufWriter::new(file);
80
81 writeln!(writer, "solid exported_grid")?;
82
83 for facet_str in facet_strings {
84 writeln!(writer, "{}", facet_str)?;
85 }
86
87 writeln!(writer, "endsolid exported_grid")?;
88
89 Ok(())
90}
91
92pub fn write_stl_binary(
106 nodes: &[[f64; 3]],
107 cells: &[[usize; 3]],
108 filename: Option<&str>,
109) -> Result<()> {
110 let process_cell = |cell: &[usize; 3]| -> [u8; 50] {
112 let (normal, [p1, p2, p3]) = compute_facet_data(nodes, cell);
113 let mut buffer = [0u8; 50];
114 let mut cursor = 0;
115
116 for f in normal
118 .iter()
119 .chain(p1.iter())
120 .chain(p2.iter())
121 .chain(p3.iter())
122 {
123 let bytes = (*f as f32).to_le_bytes();
124 buffer[cursor..cursor + 4].copy_from_slice(&bytes);
125 cursor += 4;
126 }
127 buffer
128 };
129
130 #[cfg(feature = "rayon")]
132 let all_triangle_data: Vec<[u8; 50]> = cells.par_iter().map(process_cell).collect();
133
134 #[cfg(not(feature = "rayon"))]
135 let all_triangle_data: Vec<[u8; 50]> = cells.iter().map(process_cell).collect();
136
137 let mut file = File::create(filename.unwrap_or("mesh.stl"))?;
139
140 let mut header = [0u8; 80];
142 header[..30].copy_from_slice(b"Binary STL generated with GMAC");
143 file.write_all(&header)?;
144
145 let num_triangles = cells.len() as u32;
147 file.write_all(&num_triangles.to_le_bytes())?;
148
149 let flat_buffer: Vec<u8> = all_triangle_data.into_iter().flatten().collect();
151 file.write_all(&flat_buffer)?;
152
153 Ok(())
154}
155
156fn compute_facet_data(
171 nodes: &[[f64; 3]],
172 cell: &[usize; 3],
173) -> ([f64; 3], [[f64; 3]; 3]) {
174 let p1 = nodes[cell[0]];
175 let p2 = nodes[cell[1]];
176 let p3 = nodes[cell[2]];
177
178 let u = [p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]];
180 let v = [p3[0] - p1[0], p3[1] - p1[1], p3[2] - p1[2]];
181
182 let mut normal = [
183 (u[1] * v[2]) - (u[2] * v[1]),
184 (u[2] * v[0]) - (u[0] * v[2]),
185 (u[0] * v[1]) - (u[1] * v[0]),
186 ];
187
188 let norm =
190 (normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]).sqrt();
191 if norm > f64::EPSILON {
192 normal = [normal[0] / norm, normal[1] / norm, normal[2] / norm];
193 }
194
195 (normal, [p1, p2, p3])
196}
197
198pub fn read_stl(filename: &str) -> Result<(Vec<[f64; 3]>, Vec<[usize; 3]>)> {
214 let file = File::open(filename)?;
215 let mut reader = BufReader::new(file);
216
217 let mut peek_buf = [0u8; 512];
219 let bytes_read = reader.read(&mut peek_buf)?;
220
221 let header_str = std::str::from_utf8(&peek_buf[..bytes_read]).unwrap_or("");
222
223 let is_ascii = header_str.trim_start().starts_with("solid")
224 && header_str.contains("facet normal");
225
226 reader.seek(SeekFrom::Start(0))?;
228
229 if is_ascii {
230 read_stl_ascii_from_buf(reader)
231 } else {
232 reader.seek(SeekFrom::Start(80))?;
234
235 read_stl_binary_from_buf(reader)
236 }
237}
238
239pub fn read_stl_ascii_from_buf<R: BufRead>(
255 reader: R,
256) -> Result<(Vec<[f64; 3]>, Vec<[usize; 3]>)> {
257 let mut node_map: HashMap<(u64, u64, u64), usize> = HashMap::new();
258 let mut nodes: Vec<[f64; 3]> = Vec::new();
259 let mut cells: Vec<[usize; 3]> = Vec::new();
260
261 let mut current_triangle = [0usize; 3];
262 let mut vertex_index = 0;
263
264 for line in reader.lines() {
265 let line = line?.trim().to_string();
266 if line.starts_with("vertex") {
267 let parts: Vec<f64> = line
268 .split_whitespace()
269 .skip(1)
270 .filter_map(|s| s.parse::<f64>().ok())
271 .collect();
272 if parts.len() == 3 {
273 let point = [parts[0], parts[1], parts[2]];
274 let key = (point[0].to_bits(), point[1].to_bits(), point[2].to_bits());
275 let idx = *node_map.entry(key).or_insert_with(|| {
276 let new_idx = nodes.len();
277 nodes.push(point);
278 new_idx
279 });
280 current_triangle[vertex_index] = idx;
281 vertex_index += 1;
282 if vertex_index == 3 {
283 cells.push(current_triangle);
284 vertex_index = 0;
285 }
286 }
287 }
288 }
289
290 Ok((nodes, cells))
291}
292
293pub fn read_stl_binary_from_buf<R: Read>(
309 mut reader: R,
310) -> Result<(Vec<[f64; 3]>, Vec<[usize; 3]>)> {
311 let mut count_buf = [0u8; 4];
313 reader.read_exact(&mut count_buf)?;
314 let num_triangles = u32::from_le_bytes(count_buf) as usize;
315
316 if num_triangles > 10_000_000 {
318 return Err(Error::FileSystem(std::io::Error::new(
319 std::io::ErrorKind::InvalidData,
320 format!("Too many triangles: {}", num_triangles),
321 )));
322 }
323
324 let mut nodes: Vec<[f64; 3]> = Vec::with_capacity(num_triangles * 3);
325 let mut cells: Vec<[usize; 3]> = Vec::with_capacity(num_triangles);
326 let mut node_map: HashMap<(u64, u64, u64), usize> = HashMap::new();
327
328 let mut tri_buf = [0u8; 50];
329
330 for _ in 0..num_triangles {
331 reader.read_exact(&mut tri_buf)?;
332
333 let mut triangle = [0usize; 3];
334 for (i, chunk) in tri_buf[12..48].chunks_exact(12).enumerate() {
336 let x = f32::from_le_bytes(chunk[0..4].try_into().unwrap()) as f64;
337 let y = f32::from_le_bytes(chunk[4..8].try_into().unwrap()) as f64;
338 let z = f32::from_le_bytes(chunk[8..12].try_into().unwrap()) as f64;
339
340 let key = (x.to_bits(), y.to_bits(), z.to_bits());
341 let index = *node_map.entry(key).or_insert_with(|| {
342 let idx = nodes.len();
343 nodes.push([x, y, z]);
344 idx
345 });
346
347 triangle[i] = index;
348 }
349
350 cells.push(triangle);
351 }
352
353 Ok((nodes, cells))
354}
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359 use std::fs::{remove_file, read_to_string, metadata};
360 use std::path::Path;
361
362 fn sample_mesh() -> (Vec<[f64; 3]>, Vec<[usize; 3]>) {
364 (
365 vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
366 vec![[0, 1, 2]],
367 )
368 }
369
370 #[test]
371 fn test_write_stl_ascii_creates_file() {
372 let (nodes, cells) = sample_mesh();
373 let filename = "test_ascii.stl";
374 let result = write_stl(&nodes, &cells, Some(filename), Some(StlFormat::Ascii));
375 assert!(result.is_ok());
376 assert!(Path::new(filename).exists());
377
378 let content = read_to_string(filename).expect("Failed to read ASCII STL");
379 assert!(content.contains("solid"));
380 assert!(content.contains("facet normal"));
381 remove_file(filename).unwrap();
382 }
383
384 #[test]
385 fn test_write_stl_binary_creates_file() {
386 let (nodes, cells) = sample_mesh();
387 let filename = "test_binary.stl";
388 let result = write_stl(&nodes, &cells, Some(filename), Some(StlFormat::Binary));
389 assert!(result.is_ok());
390 assert!(Path::new(filename).exists());
391
392 let metadata = metadata(filename).expect("Failed to get metadata");
393 assert!(metadata.len() > 80);
395 remove_file(filename).unwrap();
396 }
397
398 #[test]
399 fn test_write_stl_defaults_to_binary() {
400 let (nodes, cells) = sample_mesh();
401 let filename = "test_default.stl";
402 let result = write_stl(&nodes, &cells, Some(filename), None);
403 assert!(result.is_ok());
404 assert!(Path::new(filename).exists());
405
406 let metadata = metadata(filename).unwrap();
408 assert!(metadata.len() > 84);
409 remove_file(filename).unwrap();
410 }
411}