Skip to main content

oxiphysics_io/stl/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5#![allow(clippy::manual_strip)]
6#[allow(unused_imports)]
7use super::functions::*;
8#[allow(unused_imports)]
9use super::functions_2::*;
10use std::collections::HashMap;
11
12/// A single triangle in an STL mesh, with a normal vector and three vertices.
13pub struct StlTriangle {
14    /// Surface normal vector (unit vector).
15    pub normal: [f32; 3],
16    /// First vertex.
17    pub v0: [f32; 3],
18    /// Second vertex.
19    pub v1: [f32; 3],
20    /// Third vertex.
21    pub v2: [f32; 3],
22}
23/// An STL mesh consisting of named triangles.
24pub struct StlMesh {
25    /// The triangles that make up this mesh.
26    pub triangles: Vec<StlTriangle>,
27    /// The name of the solid (used in ASCII output).
28    pub name: String,
29}
30impl StlMesh {
31    /// Create a new empty STL mesh with the given name.
32    pub fn new(name: &str) -> Self {
33        StlMesh {
34            triangles: Vec::new(),
35            name: name.to_string(),
36        }
37    }
38    /// Add a triangle defined by three vertices; the normal is computed automatically.
39    pub fn add_triangle(&mut self, v0: [f32; 3], v1: [f32; 3], v2: [f32; 3]) {
40        let normal = compute_normal(v0, v1, v2);
41        self.triangles.push(StlTriangle { normal, v0, v1, v2 });
42    }
43    /// Serialize this mesh to binary STL bytes.
44    ///
45    /// Layout: 80-byte header + 4-byte little-endian triangle count +
46    /// (12 floats + 2-byte attribute) per triangle = 50 bytes each.
47    pub fn to_binary_bytes(&self) -> Vec<u8> {
48        let n = self.triangles.len();
49        let mut buf = Vec::with_capacity(84 + n * 50);
50        let mut header = [0u8; 80];
51        let name_bytes = self.name.as_bytes();
52        let copy_len = name_bytes.len().min(80);
53        header[..copy_len].copy_from_slice(&name_bytes[..copy_len]);
54        buf.extend_from_slice(&header);
55        buf.extend_from_slice(&(n as u32).to_le_bytes());
56        for tri in &self.triangles {
57            for &f in &tri.normal {
58                buf.extend_from_slice(&f.to_le_bytes());
59            }
60            for &f in &tri.v0 {
61                buf.extend_from_slice(&f.to_le_bytes());
62            }
63            for &f in &tri.v1 {
64                buf.extend_from_slice(&f.to_le_bytes());
65            }
66            for &f in &tri.v2 {
67                buf.extend_from_slice(&f.to_le_bytes());
68            }
69            buf.extend_from_slice(&0u16.to_le_bytes());
70        }
71        buf
72    }
73    /// Parse a binary STL byte slice into an `StlMesh`.
74    pub fn from_binary_bytes(data: &[u8]) -> Result<Self, String> {
75        if data.len() < 84 {
76            return Err(format!(
77                "Binary STL too short: {} bytes (need at least 84)",
78                data.len()
79            ));
80        }
81        let header_bytes = &data[..80];
82        let name = String::from_utf8_lossy(header_bytes)
83            .trim_end_matches('\0')
84            .trim()
85            .to_string();
86        let n = u32::from_le_bytes([data[80], data[81], data[82], data[83]]) as usize;
87        let expected_len = 84 + n * 50;
88        if data.len() < expected_len {
89            return Err(format!(
90                "Binary STL too short for {} triangles: got {} bytes, need {}",
91                n,
92                data.len(),
93                expected_len
94            ));
95        }
96        let mut triangles = Vec::with_capacity(n);
97        for i in 0..n {
98            let base = 84 + i * 50;
99            let normal = read_f32x3(data, base)?;
100            let v0 = read_f32x3(data, base + 12)?;
101            let v1 = read_f32x3(data, base + 24)?;
102            let v2 = read_f32x3(data, base + 36)?;
103            triangles.push(StlTriangle { normal, v0, v1, v2 });
104        }
105        Ok(StlMesh { triangles, name })
106    }
107    /// Serialize this mesh to ASCII STL text.
108    pub fn to_ascii(&self) -> String {
109        let mut s = String::new();
110        s.push_str(&format!("solid {}\n", self.name));
111        for tri in &self.triangles {
112            s.push_str(&format!(
113                "  facet normal {:.6e} {:.6e} {:.6e}\n",
114                tri.normal[0], tri.normal[1], tri.normal[2]
115            ));
116            s.push_str("    outer loop\n");
117            s.push_str(&format!(
118                "      vertex {:.6e} {:.6e} {:.6e}\n",
119                tri.v0[0], tri.v0[1], tri.v0[2]
120            ));
121            s.push_str(&format!(
122                "      vertex {:.6e} {:.6e} {:.6e}\n",
123                tri.v1[0], tri.v1[1], tri.v1[2]
124            ));
125            s.push_str(&format!(
126                "      vertex {:.6e} {:.6e} {:.6e}\n",
127                tri.v2[0], tri.v2[1], tri.v2[2]
128            ));
129            s.push_str("    endloop\n");
130            s.push_str("  endfacet\n");
131        }
132        s.push_str(&format!("endsolid {}\n", self.name));
133        s
134    }
135    /// Parse an ASCII STL string into an `StlMesh`.
136    pub fn from_ascii(s: &str) -> Result<Self, String> {
137        let mut lines = s.lines().peekable();
138        let first = lines
139            .next()
140            .ok_or_else(|| "Empty STL string".to_string())?
141            .trim();
142        let name = if first.starts_with("solid") {
143            first[5..].trim().to_string()
144        } else {
145            return Err(format!("Expected 'solid', got: {first}"));
146        };
147        let mut triangles = Vec::new();
148        while let Some(l) = lines.next() {
149            let line = l.trim().to_string();
150            if line.starts_with("endsolid") {
151                break;
152            }
153            if line.is_empty() {
154                continue;
155            }
156            if !line.starts_with("facet normal") {
157                return Err(format!("Expected 'facet normal', got: {line}"));
158            }
159            let normal = parse_vec3_from_line(&line, "facet normal")?;
160            let outer = lines
161                .next()
162                .ok_or_else(|| "Unexpected EOF after facet normal".to_string())?
163                .trim()
164                .to_string();
165            if !outer.starts_with("outer loop") {
166                return Err(format!("Expected 'outer loop', got: {outer}"));
167            }
168            let v0 = parse_vertex_line(
169                lines
170                    .next()
171                    .ok_or_else(|| "Unexpected EOF reading vertex 0".to_string())?
172                    .trim(),
173            )?;
174            let v1 = parse_vertex_line(
175                lines
176                    .next()
177                    .ok_or_else(|| "Unexpected EOF reading vertex 1".to_string())?
178                    .trim(),
179            )?;
180            let v2 = parse_vertex_line(
181                lines
182                    .next()
183                    .ok_or_else(|| "Unexpected EOF reading vertex 2".to_string())?
184                    .trim(),
185            )?;
186            let _endloop = lines
187                .next()
188                .ok_or_else(|| "Unexpected EOF reading endloop".to_string())?;
189            let _endfacet = lines
190                .next()
191                .ok_or_else(|| "Unexpected EOF reading endfacet".to_string())?;
192            triangles.push(StlTriangle { normal, v0, v1, v2 });
193        }
194        Ok(StlMesh { triangles, name })
195    }
196    /// Compute the total surface area of this mesh.
197    pub fn surface_area(&self) -> f32 {
198        self.triangles.iter().map(triangle_area).sum()
199    }
200    /// Compute the axis-aligned bounding box as `(min, max)`.
201    ///
202    /// Returns `([0.0;3], [0.0;3])` for an empty mesh.
203    pub fn bounding_box(&self) -> ([f32; 3], [f32; 3]) {
204        if self.triangles.is_empty() {
205            return ([0.0; 3], [0.0; 3]);
206        }
207        let mut lo = [f32::MAX; 3];
208        let mut hi = [f32::MIN; 3];
209        for tri in &self.triangles {
210            for v in [&tri.v0, &tri.v1, &tri.v2] {
211                for k in 0..3 {
212                    if v[k] < lo[k] {
213                        lo[k] = v[k];
214                    }
215                    if v[k] > hi[k] {
216                        hi[k] = v[k];
217                    }
218                }
219            }
220        }
221        (lo, hi)
222    }
223    /// Return `true` if every edge is shared by exactly two triangles (watertight).
224    pub fn is_watertight(&self) -> bool {
225        let mut edge_counts: HashMap<EdgeKey, usize> = HashMap::new();
226        for tri in &self.triangles {
227            for &(a, b) in &[(tri.v0, tri.v1), (tri.v1, tri.v2), (tri.v2, tri.v0)] {
228                let key = EdgeKey::new(a, b);
229                *edge_counts.entry(key).or_insert(0) += 1;
230            }
231        }
232        edge_counts.values().all(|&c| c == 2)
233    }
234}
235/// Result of an STL mesh validation pass.
236#[derive(Debug, Clone, Default)]
237pub struct StlValidationReport {
238    /// Number of boundary (non-manifold) edges.
239    pub boundary_edge_count: usize,
240    /// Whether the mesh appears watertight (no boundary edges).
241    pub is_watertight: bool,
242    /// Whether the mesh appears manifold (each edge shared by exactly 2 triangles).
243    pub is_manifold: bool,
244    /// Number of degenerate triangles (zero-area).
245    pub degenerate_count: usize,
246}
247/// A simple indexed triangle mesh produced from the STL-to-mesh pipeline.
248#[allow(dead_code)]
249#[derive(Debug, Clone)]
250pub struct TriangleMesh {
251    /// Unique vertex positions.
252    pub positions: Vec<[f32; 3]>,
253    /// Per-vertex normals (averaged from all adjacent triangles).
254    pub normals: Vec<[f32; 3]>,
255    /// Triangle indices (3 indices per triangle).
256    pub indices: Vec<[usize; 3]>,
257}
258/// Color encoded in the 2-byte attribute field of a binary STL triangle
259/// using the VisCAM/SolidView convention:
260/// bit 15 = valid flag, bits 14–10 = R (5-bit), 9–5 = G, 4–0 = B.
261#[derive(Debug, Clone, Copy, Default)]
262pub struct StlColor {
263    /// Red channel (0–31).
264    pub r: u8,
265    /// Green channel (0–31).
266    pub g: u8,
267    /// Blue channel (0–31).
268    pub b: u8,
269}
270impl StlColor {
271    /// Create a color from 5-bit R/G/B channels.
272    pub fn new(r: u8, g: u8, b: u8) -> Self {
273        Self {
274            r: r & 0x1F,
275            g: g & 0x1F,
276            b: b & 0x1F,
277        }
278    }
279    /// Encode into a 2-byte attribute word (VisCAM format, bit 15 set).
280    pub fn encode(&self) -> u16 {
281        0x8000 | ((self.r as u16) << 10) | ((self.g as u16) << 5) | (self.b as u16)
282    }
283    /// Decode from a 2-byte attribute word.  Returns `None` if the valid bit
284    /// (bit 15) is not set.
285    pub fn decode(attr: u16) -> Option<Self> {
286        if attr & 0x8000 == 0 {
287            return None;
288        }
289        Some(Self {
290            r: ((attr >> 10) & 0x1F) as u8,
291            g: ((attr >> 5) & 0x1F) as u8,
292            b: (attr & 0x1F) as u8,
293        })
294    }
295    /// Convert to normalized (0.0–1.0) RGB triple.
296    pub fn to_normalized(&self) -> [f32; 3] {
297        [
298            self.r as f32 / 31.0,
299            self.g as f32 / 31.0,
300            self.b as f32 / 31.0,
301        ]
302    }
303}
304/// Quality metric report for an STL mesh.
305#[allow(dead_code)]
306#[derive(Debug, Clone)]
307pub struct StlQualityMetrics {
308    /// Aspect ratio of each triangle (longest edge / shortest altitude).
309    pub max_aspect_ratio: f32,
310    /// Minimum triangle area.
311    pub min_area: f32,
312    /// Maximum triangle area.
313    pub max_area: f32,
314    /// Fraction of triangles that are degenerate (zero area).
315    pub degenerate_fraction: f32,
316    /// Whether any NaN/Inf coordinates exist.
317    pub has_bad_geometry: bool,
318}
319/// Validation result for an STL mesh.
320#[allow(dead_code)]
321#[derive(Debug, Clone)]
322pub struct StlValidation {
323    /// Number of degenerate (zero-area) triangles.
324    pub degenerate_count: usize,
325    /// Number of non-manifold edges (shared by != 2 triangles).
326    pub non_manifold_edges: usize,
327    /// Whether the mesh is watertight.
328    pub is_watertight: bool,
329    /// Number of triangles with NaN or Inf vertices.
330    pub nan_inf_count: usize,
331    /// Whether all normals are unit-length.
332    pub normals_valid: bool,
333}
334/// A canonicalised (sorted) directed edge key using bit-exact f32 representation.
335#[derive(PartialEq, Eq, Hash)]
336pub(super) struct EdgeKey {
337    pub(super) a: [u32; 3],
338    pub(super) b: [u32; 3],
339}
340impl EdgeKey {
341    pub(super) fn new(p: [f32; 3], q: [f32; 3]) -> Self {
342        let pa = p.map(f32::to_bits);
343        let qa = q.map(f32::to_bits);
344        if pa <= qa {
345            EdgeKey { a: pa, b: qa }
346        } else {
347            EdgeKey { a: qa, b: pa }
348        }
349    }
350}
351/// Statistics for an STL mesh.
352#[allow(dead_code)]
353#[derive(Debug, Clone)]
354pub struct StlStatistics {
355    /// Number of triangles.
356    pub triangle_count: usize,
357    /// Total surface area.
358    pub surface_area: f32,
359    /// Bounding box min corner.
360    pub bb_min: [f32; 3],
361    /// Bounding box max corner.
362    pub bb_max: [f32; 3],
363    /// Bounding box size.
364    pub bb_size: [f32; 3],
365    /// Average triangle area.
366    pub avg_triangle_area: f32,
367    /// Minimum triangle area.
368    pub min_triangle_area: f32,
369    /// Maximum triangle area.
370    pub max_triangle_area: f32,
371    /// Average edge length.
372    pub avg_edge_length: f32,
373    /// Number of unique vertices (approximate, by bit-exact matching).
374    pub approx_unique_vertices: usize,
375}
376/// Vertex welding result: unique vertices and remapped face indices.
377#[allow(dead_code)]
378#[derive(Debug, Clone)]
379pub struct WeldedMesh {
380    /// Unique vertex positions.
381    pub vertices: Vec<[f32; 3]>,
382    /// Triangle indices into `vertices`.
383    pub triangles: Vec<[usize; 3]>,
384}