rust_3d/io/
stl.rs

1/*
2Copyright 2017 Martin Buck
3
4Permission is hereby granted, free of charge, to any person obtaining a copy
5of this software and associated documentation files (the "Software"),
6to deal in the Software without restriction, including without limitation the
7rights to use, copy, modify, merge, publish, distribute, sublicense,
8and/or sell copies of the Software, and to permit persons to whom the Software
9is furnished to do so, subject to the following conditions:
10
11The above copyright notice and this permission notice shall
12be included all copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
20OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21*/
22
23//! Module for IO operations of the stl file format
24
25use crate::*;
26
27use std::{
28    fmt,
29    io::{BufRead, Error as ioError, Read, Write},
30};
31
32use fnv::FnvHashMap;
33
34use super::{byte_reader::*, from_bytes::*, types::*, utils::*};
35
36//------------------------------------------------------------------------------
37
38//@todo can be resolved in a better way once https://github.com/rust-lang/rust/issues/48043 is on stable
39//@todo work around in case the binary data is invalid
40const MAX_TRIANGLES_BINARY: u32 = 1_000_000_000;
41
42//------------------------------------------------------------------------------
43
44/// Saves an IsMesh3D in the ASCII .stl file format
45pub fn save_stl_ascii<M, P, W>(write: &mut W, mesh: &M) -> StlResult<()>
46where
47    M: IsMesh3D<P>,
48    P: IsBuildable3D,
49    W: Write,
50{
51    write.write_all(b"solid STL generated by rust-3d\n")?;
52
53    for i in 0..mesh.num_faces() {
54        let [v1, v2, v3] = mesh.face_vertices(FId { val: i }).unwrap(); // safe since iterating num_faces
55        let n = mesh.face_normal(FId { val: i }).unwrap(); // safe since iterating num_faces
56        let buffer = "facet normal ".to_string()
57            + &str_exp(&n)
58            + "\n"
59            + "    outer loop\n"
60            + "        vertex "
61            + &str_exp(&v1)
62            + "\n"
63            + "        vertex "
64            + &str_exp(&v2)
65            + "\n"
66            + "        vertex "
67            + &str_exp(&v3)
68            + "\n"
69            + "    endloop\n"
70            + "endfacet\n";
71        write.write_all(buffer.as_bytes())?;
72    }
73    write.write_all(b"endsolid STL generated by rust-3d\n")?;
74    Ok(())
75}
76
77//------------------------------------------------------------------------------
78
79/// Loads a Mesh from .stl file with duplicate vertices
80pub fn load_stl_mesh_duped<EM, P, R, IPN>(
81    read: &mut R,
82    format: StlFormat,
83    mesh: &mut EM,
84    face_normals: &mut IPN,
85) -> StlIOResult<()>
86where
87    EM: IsFaceEditableMesh<P, Face3> + IsVertexEditableMesh<P, Face3>,
88    P: IsBuildable3D + Clone,
89    R: BufRead,
90    IPN: IsPushable<P>,
91{
92    if is_ascii(read, format).simple()? {
93        load_stl_mesh_duped_ascii(read, mesh, face_normals)
94    } else {
95        load_stl_mesh_duped_binary(read, mesh, face_normals).simple()
96    }
97}
98
99//------------------------------------------------------------------------------
100
101/// Loads a Mesh from .stl file with unique vertices, dropping invalid triangles
102pub fn load_stl_mesh_unique<EM, P, R, IPN>(
103    read: &mut R,
104    format: StlFormat,
105    mesh: &mut EM,
106    face_normals: &mut IPN,
107) -> StlIOResult<()>
108where
109    EM: IsFaceEditableMesh<P, Face3> + IsVertexEditableMesh<P, Face3>,
110    P: IsBuildable3D + Clone,
111    R: BufRead,
112    IPN: IsPushable<P>,
113{
114    if is_ascii(read, format).simple()? {
115        load_stl_mesh_unique_ascii(read, mesh, face_normals)
116    } else {
117        load_stl_mesh_unique_binary(read, mesh, face_normals).simple()
118    }
119}
120
121//------------------------------------------------------------------------------
122
123/// Loads points from .stl file as triplets into IsPushable<IsBuildable3D>
124pub fn load_stl_triplets<IP, P, R, IPN>(
125    read: &mut R,
126    format: StlFormat,
127    ip: &mut IP,
128    face_normals: &mut IPN,
129) -> StlIOResult<()>
130where
131    IP: IsPushable<P>,
132    P: IsBuildable3D,
133    R: BufRead,
134    IPN: IsPushable<P>,
135{
136    if is_ascii(read, format).simple()? {
137        load_stl_triplets_ascii(read, ip, face_normals)
138    } else {
139        load_stl_triplets_binary(read, ip, face_normals).simple()
140    }
141}
142
143//------------------------------------------------------------------------------
144//------------------------------------------------------------------------------
145//------------------------------------------------------------------------------
146
147fn is_ascii<R>(read: &mut R, format: StlFormat) -> StlResult<bool>
148where
149    R: BufRead,
150{
151    let solid = "solid".as_bytes();
152    let mut buffer = [0u8; 5];
153
154    let mut result = true;
155    read.read_exact(&mut buffer)?;
156
157    for i in 0..5 {
158        if buffer[i] != solid[i] {
159            result = false
160        }
161    }
162
163    // It is important to always consume the bytes above, even if format defines the result
164    Ok(match format {
165        StlFormat::Ascii => true,
166        StlFormat::Binary => false,
167        StlFormat::Auto => result,
168    })
169}
170
171//------------------------------------------------------------------------------
172
173fn load_stl_mesh_duped_ascii<EM, P, R, IPN>(
174    read: &mut R,
175    mesh: &mut EM,
176    face_normals: &mut IPN,
177) -> StlIOResult<()>
178where
179    EM: IsFaceEditableMesh<P, Face3> + IsVertexEditableMesh<P, Face3>,
180    P: IsBuildable3D + Clone,
181    R: BufRead,
182    IPN: IsPushable<P>,
183{
184    let mut i_line = 0;
185    let mut line_buffer = Vec::new();
186
187    // skip first line
188    read.read_until(b'\n', &mut line_buffer).index(i_line)?;
189    i_line += 1;
190
191    loop {
192        match read_stl_facet(read, &mut line_buffer, &mut i_line) {
193            Ok([a, b, c, n]) => {
194                mesh.add_face(a, b, c);
195                face_normals.push(n);
196            }
197            Err(WithLineInfo::None(StlError::LoadFileEndReached))
198            | Err(WithLineInfo::Index(_, StlError::LoadFileEndReached))
199            | Err(WithLineInfo::Line(_, _, StlError::LoadFileEndReached)) => break,
200            Err(x) => return Err(x),
201        }
202    }
203
204    Ok(())
205}
206
207struct StlTriangle {
208    pub n: [f32; 3],
209    pub x: [f32; 3],
210    pub y: [f32; 3],
211    pub z: [f32; 3],
212}
213
214#[inline(always)]
215fn read_stl_triangle<R>(read: &mut R) -> StlResult<StlTriangle>
216where
217    R: Read,
218{
219    // size for StlTriangle + u16 garbage
220    let mut buffer = [0u8; 50];
221    read.read_exact(&mut buffer)?;
222
223    Ok(StlTriangle {
224        n: array_from_bytes_le!(f32, 3, &buffer[0..12])?,
225        x: array_from_bytes_le!(f32, 3, &buffer[12..24])?,
226        y: array_from_bytes_le!(f32, 3, &buffer[24..36])?,
227        z: array_from_bytes_le!(f32, 3, &buffer[36..48])?,
228    })
229}
230
231//------------------------------------------------------------------------------
232
233fn load_stl_mesh_duped_binary<EM, P, R, IPN>(
234    read: &mut R,
235    mesh: &mut EM,
236    face_normals: &mut IPN,
237) -> StlResult<()>
238where
239    EM: IsFaceEditableMesh<P, Face3> + IsVertexEditableMesh<P, Face3>,
240    P: IsBuildable3D + Clone,
241    R: Read,
242    IPN: IsPushable<P>,
243{
244    // Drop header ('solid' is already dropped)
245    {
246        let mut buffer = [0u8; 75];
247        read.read_exact(&mut buffer)?;
248    }
249
250    let n_triangles = LittleReader::read_u32(read)?;
251    if n_triangles > MAX_TRIANGLES_BINARY {
252        return Err(StlError::InvalidFaceCount);
253    }
254
255    mesh.reserve_vertices(3 * n_triangles as usize);
256    mesh.reserve_faces(n_triangles as usize);
257
258    face_normals.reserve(n_triangles as usize);
259
260    for _ in 0..n_triangles {
261        let t = read_stl_triangle(read)?;
262
263        let n = P::new(t.n[0] as f64, t.n[1] as f64, t.n[2] as f64);
264        let x = P::new(t.x[0] as f64, t.x[1] as f64, t.x[2] as f64);
265        let y = P::new(t.y[0] as f64, t.y[1] as f64, t.y[2] as f64);
266        let z = P::new(t.z[0] as f64, t.z[1] as f64, t.z[2] as f64);
267
268        mesh.add_face(x, y, z);
269        face_normals.push(n)
270    }
271
272    Ok(())
273}
274
275//------------------------------------------------------------------------------
276
277fn load_stl_mesh_unique_ascii<EM, P, R, IPN>(
278    read: &mut R,
279    mesh: &mut EM,
280    face_normals: &mut IPN,
281) -> StlIOResult<()>
282where
283    EM: IsFaceEditableMesh<P, Face3> + IsVertexEditableMesh<P, Face3>,
284    P: IsBuildable3D + Clone,
285    R: BufRead,
286    IPN: IsPushable<P>,
287{
288    let mut i_line = 0;
289    let mut line_buffer = Vec::new();
290
291    // skip first line
292    read.read_until(b'\n', &mut line_buffer).index(i_line)?;
293    i_line += 1;
294
295    let mut map = FnvHashMap::default();
296
297    loop {
298        match read_stl_facet::<P, _>(read, &mut line_buffer, &mut i_line) {
299            Ok([a, b, c, n]) => {
300                let id_a = *map.entry(a.clone()).or_insert_with(|| {
301                    let value = mesh.num_vertices();
302                    mesh.add_vertex(a);
303                    value
304                });
305
306                let id_b = *map.entry(b.clone()).or_insert_with(|| {
307                    let value = mesh.num_vertices();
308                    mesh.add_vertex(b);
309                    value
310                });
311
312                let id_c = *map.entry(c.clone()).or_insert_with(|| {
313                    let value = mesh.num_vertices();
314                    mesh.add_vertex(c);
315                    value
316                });
317
318                // Ignore this issues since this only fails if a triangle uses a vertex multiple times
319                // Simply do not add this triangle and normal
320                match mesh.try_add_connection(
321                    VId { val: id_a },
322                    VId { val: id_b },
323                    VId { val: id_c },
324                ) {
325                    Ok(_) => {
326                        face_normals.push(n);
327                    }
328                    Err(_) => (),
329                }
330            }
331            Err(WithLineInfo::None(StlError::LoadFileEndReached))
332            | Err(WithLineInfo::Index(_, StlError::LoadFileEndReached))
333            | Err(WithLineInfo::Line(_, _, StlError::LoadFileEndReached)) => break,
334            Err(x) => return Err(x),
335        }
336    }
337
338    Ok(())
339}
340
341//------------------------------------------------------------------------------
342
343fn load_stl_mesh_unique_binary<EM, P, R, IPN>(
344    read: &mut R,
345    mesh: &mut EM,
346    face_normals: &mut IPN,
347) -> StlResult<()>
348where
349    EM: IsFaceEditableMesh<P, Face3> + IsVertexEditableMesh<P, Face3>,
350    P: IsBuildable3D + Clone,
351    R: Read,
352    IPN: IsPushable<P>,
353{
354    // Drop header ('solid' is already dropped)
355    {
356        let mut buffer = [0u8; 75];
357        read.read_exact(&mut buffer)?;
358    }
359
360    let n_triangles = LittleReader::read_u32(read)?;
361    if n_triangles > MAX_TRIANGLES_BINARY {
362        return Err(StlError::InvalidFaceCount);
363    }
364
365    mesh.reserve_vertices((0.5 * n_triangles as f64) as usize);
366    mesh.reserve_faces(n_triangles as usize);
367
368    face_normals.reserve(n_triangles as usize);
369
370    let mut map = FnvHashMap::default();
371
372    for _ in 0..n_triangles {
373        let t = read_stl_triangle(read)?;
374
375        let n = P::new(t.n[0] as f64, t.n[1] as f64, t.n[2] as f64);
376        let x = P::new(t.x[0] as f64, t.x[1] as f64, t.x[2] as f64);
377        let y = P::new(t.y[0] as f64, t.y[1] as f64, t.y[2] as f64);
378        let z = P::new(t.z[0] as f64, t.z[1] as f64, t.z[2] as f64);
379
380        let id_x = *map.entry(x.clone()).or_insert_with(|| {
381            let value = mesh.num_vertices();
382            mesh.add_vertex(x);
383            value
384        });
385
386        let id_y = *map.entry(y.clone()).or_insert_with(|| {
387            let value = mesh.num_vertices();
388            mesh.add_vertex(y);
389            value
390        });
391
392        let id_z = *map.entry(z.clone()).or_insert_with(|| {
393            let value = mesh.num_vertices();
394            mesh.add_vertex(z);
395            value
396        });
397
398        // Ignore this issues since this only fails if a triangle uses a vertex multiple times
399        // Simply do not add this triangle
400        match mesh.try_add_connection(VId { val: id_x }, VId { val: id_y }, VId { val: id_z }) {
401            Ok(_) => {
402                face_normals.push(n);
403            }
404            Err(_) => (),
405        }
406    }
407
408    Ok(())
409}
410
411//------------------------------------------------------------------------------
412
413fn load_stl_triplets_ascii<IP, P, R, IPN>(
414    read: &mut R,
415    ip: &mut IP,
416    face_normals: &mut IPN,
417) -> StlIOResult<()>
418where
419    IP: IsPushable<P>,
420    P: IsBuildable3D,
421    R: BufRead,
422    IPN: IsPushable<P>,
423{
424    let mut i_line = 0;
425    let mut line_buffer = Vec::new();
426
427    // skip first line
428    read.read_until(b'\n', &mut line_buffer).index(i_line)?;
429    i_line += 1;
430
431    loop {
432        match read_stl_facet(read, &mut line_buffer, &mut i_line) {
433            Ok([a, b, c, n]) => {
434                ip.push(a);
435                ip.push(b);
436                ip.push(c);
437                face_normals.push(n);
438            }
439            Err(WithLineInfo::None(StlError::LoadFileEndReached))
440            | Err(WithLineInfo::Index(_, StlError::LoadFileEndReached))
441            | Err(WithLineInfo::Line(_, _, StlError::LoadFileEndReached)) => break,
442            Err(x) => return Err(x),
443        }
444    }
445
446    Ok(())
447}
448
449//------------------------------------------------------------------------------
450
451fn load_stl_triplets_binary<IP, P, R, IPN>(
452    read: &mut R,
453    ip: &mut IP,
454    face_normals: &mut IPN,
455) -> StlResult<()>
456where
457    IP: IsPushable<P>,
458    P: IsBuildable3D,
459    R: Read,
460    IPN: IsPushable<P>,
461{
462    // Drop header ('solid' is already dropped)
463    {
464        let mut buffer = [0u8; 75];
465        read.read_exact(&mut buffer)?;
466    }
467
468    let n_triangles = LittleReader::read_u32(read)?;
469    if n_triangles > MAX_TRIANGLES_BINARY {
470        return Err(StlError::InvalidFaceCount);
471    }
472
473    ip.reserve(3 * n_triangles as usize);
474    face_normals.reserve(n_triangles as usize);
475
476    for _ in 0..n_triangles {
477        let t = read_stl_triangle(read)?;
478
479        let n = P::new(t.n[0] as f64, t.n[1] as f64, t.n[2] as f64);
480        let x = P::new(t.x[0] as f64, t.x[1] as f64, t.x[2] as f64);
481        let y = P::new(t.y[0] as f64, t.y[1] as f64, t.y[2] as f64);
482        let z = P::new(t.z[0] as f64, t.z[1] as f64, t.z[2] as f64);
483
484        ip.push(x);
485        ip.push(y);
486        ip.push(z);
487
488        face_normals.push(n)
489    }
490
491    Ok(())
492}
493
494//------------------------------------------------------------------------------
495
496fn read_stl_facet<P, R>(
497    read: &mut R,
498    line_buffer: &mut Vec<u8>,
499    i_line: &mut usize,
500) -> StlIOResult<[P; 4]>
501where
502    P: IsBuildable3D,
503    R: BufRead,
504{
505    let mut line: &[u8];
506
507    line = trim_start(fetch_line(read, line_buffer).index(*i_line)?);
508    *i_line += 1;
509
510    if line.starts_with(b"endsolid") {
511        return Err(StlError::LoadFileEndReached).line(*i_line, line);
512    }
513
514    if !line.starts_with(b"facet") {
515        return Err(StlError::Facet).line(*i_line, line);
516    }
517
518    let n = read_stl_normal(&line).unwrap_or(P::new(0.0, 0.0, 1.0));
519
520    line = trim_start(fetch_line(read, line_buffer).index(*i_line)?);
521    *i_line += 1;
522
523    if !line.starts_with(b"outer loop") {
524        return Err(StlError::Loop).line(*i_line, line);
525    }
526
527    line = fetch_line(read, line_buffer).index(*i_line)?;
528    *i_line += 1;
529
530    let a = read_stl_vertex(&line)
531        .ok_or(StlError::Vertex)
532        .line(*i_line, line)?;
533
534    line = fetch_line(read, line_buffer).index(*i_line)?;
535    *i_line += 1;
536
537    let b = read_stl_vertex(&line)
538        .ok_or(StlError::Vertex)
539        .line(*i_line, line)?;
540
541    line = fetch_line(read, line_buffer).index(*i_line)?;
542    *i_line += 1;
543
544    let c = read_stl_vertex(&line)
545        .ok_or(StlError::Vertex)
546        .line(*i_line, line)?;
547
548    line = trim_start(fetch_line(read, line_buffer).index(*i_line)?);
549    *i_line += 1;
550
551    if !line.starts_with(b"endloop") {
552        return Err(StlError::EndLoop).line(*i_line, line);
553    }
554
555    line = trim_start(fetch_line(read, line_buffer).index(*i_line)?);
556    *i_line += 1;
557
558    if !line.starts_with(b"endfacet") {
559        return Err(StlError::EndFacet).line(*i_line, line);
560    }
561
562    Ok([a, b, c, n])
563}
564
565//------------------------------------------------------------------------------
566
567fn read_stl_vertex<P>(line: &[u8]) -> Option<P>
568where
569    P: IsBuildable3D,
570{
571    let mut words = to_words_skip_empty(line);
572
573    // skip "vertex"
574    words.next()?;
575
576    let x = from_ascii(words.next()?)?;
577    let y = from_ascii(words.next()?)?;
578    let z = from_ascii(words.next()?)?;
579
580    Some(P::new(x, y, z))
581}
582
583//------------------------------------------------------------------------------
584
585fn read_stl_normal<P>(line: &[u8]) -> Option<P>
586where
587    P: IsBuildable3D,
588{
589    let mut words = to_words_skip_empty(line);
590
591    // skip "facet"
592    words.next()?;
593
594    // skip "normal"
595    words.next()?;
596
597    let i = from_ascii(words.next()?)?;
598    let j = from_ascii(words.next()?)?;
599    let k = from_ascii(words.next()?)?;
600
601    Some(P::new(i, j, k))
602}
603
604//------------------------------------------------------------------------------
605
606fn str_exp<P>(p: &P) -> String
607where
608    P: Is3D,
609{
610    format!("{:e} {:e} {:e}", p.x(), p.y(), p.z()).to_string()
611}
612
613//------------------------------------------------------------------------------
614
615/// Whether format shall be considered to be binary/ASCII or auto determined
616#[derive(Copy, Clone)]
617pub enum StlFormat {
618    Ascii,
619    Binary,
620    Auto,
621}
622
623impl Default for StlFormat {
624    fn default() -> Self {
625        Self::Auto
626    }
627}
628
629//------------------------------------------------------------------------------
630
631/// Error type for .stl file operations
632pub enum StlError {
633    LoadFileEndReached,
634    AccessFile,
635    BinaryData,
636    InvalidFaceCount,
637    Facet,
638    EndFacet,
639    Vertex,
640    Loop,
641    EndLoop,
642}
643
644/// Result type for .stl file operations
645pub type StlResult<T> = std::result::Result<T, StlError>;
646
647/// Result type for .stl file operations
648pub type StlIOResult<T> = IOResult<T, StlError>;
649
650impl fmt::Debug for StlError {
651    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
652        match self {
653            Self::LoadFileEndReached => write!(f, "Unexpected reach of .stl file end"),
654            Self::AccessFile => write!(f, "Unable to access file"),
655            Self::BinaryData => write!(f, "Binary data seems to be invalid"),
656            Self::InvalidFaceCount => write!(f, "Containing an invalid face count"),
657            Self::Facet => write!(f, "Unable to parse facet"),
658            Self::EndFacet => write!(f, "Unable to parse endfacet"),
659            Self::Vertex => write!(f, "Unable to parse vertex"),
660            Self::Loop => write!(f, "Unable to parse loop"),
661            Self::EndLoop => write!(f, "Unable to parse endloop"),
662        }
663    }
664}
665
666impl fmt::Display for StlError {
667    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
668        write!(f, "{:?}", self)
669    }
670}
671
672impl From<ioError> for StlError {
673    fn from(_error: ioError) -> Self {
674        StlError::AccessFile
675    }
676}
677
678impl From<WithLineInfo<ioError>> for WithLineInfo<StlError> {
679    fn from(other: WithLineInfo<ioError>) -> Self {
680        match other {
681            WithLineInfo::<ioError>::None(x) => WithLineInfo::None(StlError::from(x)),
682            WithLineInfo::<ioError>::Index(i, x) => WithLineInfo::Index(i, StlError::from(x)),
683            WithLineInfo::<ioError>::Line(i, l, x) => WithLineInfo::Line(i, l, StlError::from(x)),
684        }
685    }
686}
687
688impl From<WithLineInfo<FetchLineError>> for WithLineInfo<StlError> {
689    fn from(other: WithLineInfo<FetchLineError>) -> Self {
690        match other {
691            WithLineInfo::<FetchLineError>::None(x) => WithLineInfo::None(StlError::from(x)),
692            WithLineInfo::<FetchLineError>::Index(i, x) => {
693                WithLineInfo::Index(i, StlError::from(x))
694            }
695            WithLineInfo::<FetchLineError>::Line(i, l, x) => {
696                WithLineInfo::Line(i, l, StlError::from(x))
697            }
698        }
699    }
700}
701
702impl From<std::array::TryFromSliceError> for StlError {
703    fn from(_error: std::array::TryFromSliceError) -> Self {
704        StlError::BinaryData
705    }
706}
707impl From<FromBytesError> for StlError {
708    fn from(_error: FromBytesError) -> Self {
709        StlError::BinaryData
710    }
711}
712
713impl From<FetchLineError> for StlError {
714    fn from(_error: FetchLineError) -> Self {
715        StlError::LoadFileEndReached
716    }
717}