fj_export/
lib.rs

1//! # Fornjot Exporter
2//!
3//! [Fornjot] is an early-stage b-rep CAD kernel written in Rust. The kernel is
4//! split into multiple libraries that can be used semi-independently, and this
5//! is one of those.
6//!
7//! This library exports Fornjot models to external file formats.
8//!
9//! [Fornjot]: https://www.fornjot.app/
10
11use std::{
12    fs::File,
13    io::{Seek, Write},
14    path::Path,
15};
16
17use thiserror::Error;
18
19use fj_interop::Mesh;
20use fj_math::{Point, Triangle};
21
22/// Export the provided mesh to the file at the given path.
23///
24/// This function will create a file if it does not exist, and will truncate it if it does.
25///
26/// Currently 3MF & STL file types are supported. The case insensitive file extension of
27/// the provided path is used to switch between supported types.
28pub fn export(mesh: &Mesh<Point<3>>, path: &Path) -> Result<(), Error> {
29    match path.extension() {
30        Some(extension) if extension.to_ascii_uppercase() == "3MF" => {
31            let mut file = File::create(path)?;
32            export_3mf(mesh, &mut file)
33        }
34        Some(extension) if extension.to_ascii_uppercase() == "STL" => {
35            let mut file = File::create(path)?;
36            export_stl(mesh, &mut file)
37        }
38        Some(extension) if extension.to_ascii_uppercase() == "OBJ" => {
39            let mut file = File::create(path)?;
40            export_obj(mesh, &mut file)
41        }
42        Some(extension) => Err(Error::InvalidExtension(
43            extension.to_string_lossy().into_owned(),
44        )),
45        None => Err(Error::NoExtension),
46    }
47}
48
49/// Export the provided mesh to the provided writer in the 3MF format.
50pub fn export_3mf(
51    mesh: &Mesh<Point<3>>,
52    write: impl Write + Seek,
53) -> Result<(), Error> {
54    let vertices = mesh
55        .vertices()
56        .map(|point| threemf::model::Vertex {
57            x: point.x.into_f64(),
58            y: point.y.into_f64(),
59            z: point.z.into_f64(),
60        })
61        .collect();
62
63    let indices: Vec<_> = mesh.indices().collect();
64    let triangles = indices
65        .chunks(3)
66        .map(|triangle| threemf::model::Triangle {
67            v1: triangle[0] as usize,
68            v2: triangle[1] as usize,
69            v3: triangle[2] as usize,
70        })
71        .collect();
72
73    let mesh = threemf::Mesh {
74        vertices: threemf::model::Vertices { vertex: vertices },
75        triangles: threemf::model::Triangles {
76            triangle: triangles,
77        },
78    };
79
80    threemf::write(write, mesh)?;
81
82    Ok(())
83}
84
85/// Export the provided mesh to the provided writer in the STL format.
86pub fn export_stl(
87    mesh: &Mesh<Point<3>>,
88    mut write: impl Write,
89) -> Result<(), Error> {
90    let points = mesh
91        .triangles()
92        .map(|triangle| triangle.inner.points())
93        .collect::<Vec<_>>();
94
95    let vertices = points.iter().map(|points| {
96        points.map(|point| point.coords.components.map(|s| s.into_f32()))
97    });
98
99    let normals = points
100        .iter()
101        .map(|&points| points.into())
102        .map(|triangle: Triangle<3>| triangle.normal())
103        .map(|vector| vector.components.map(|s| s.into_f32()));
104
105    let triangles = vertices
106        .zip(normals)
107        .map(|([v1, v2, v3], normal)| stl::Triangle {
108            normal,
109            v1,
110            v2,
111            v3,
112            attr_byte_count: 0,
113        })
114        .collect::<Vec<_>>();
115
116    let binary_stl_file = stl::BinaryStlFile {
117        header: stl::BinaryStlHeader {
118            header: [0u8; 80],
119            num_triangles: triangles
120                .len()
121                .try_into()
122                .map_err(|_| Error::InvalidTriangleCount)?,
123        },
124        triangles,
125    };
126
127    stl::write_stl(&mut write, &binary_stl_file)?;
128
129    Ok(())
130}
131
132/// Export the provided mesh to the provided writer in the OBJ format.
133pub fn export_obj(
134    mesh: &Mesh<Point<3>>,
135    mut write: impl Write,
136) -> Result<(), Error> {
137    for (cnt, t) in mesh.triangles().enumerate() {
138        // write each point of the triangle
139        for v in t.inner.points() {
140            wavefront_rs::obj::writer::Writer { auto_newline: true }
141                .write(
142                    &mut write,
143                    &wavefront_rs::obj::entity::Entity::Vertex {
144                        x: v.x.into_f64(),
145                        y: v.y.into_f64(),
146                        z: v.z.into_f64(),
147                        w: None,
148                    },
149                )
150                .or(Err(Error::OBJ))?;
151        }
152
153        // write the triangle
154        wavefront_rs::obj::writer::Writer { auto_newline: true }
155            .write(
156                &mut write,
157                &wavefront_rs::obj::entity::Entity::Face {
158                    vertices: vec![
159                        wavefront_rs::obj::entity::FaceVertex {
160                            vertex: (cnt * 3 + 1) as i64,
161                            texture: None,
162                            normal: None,
163                        },
164                        wavefront_rs::obj::entity::FaceVertex {
165                            vertex: (cnt * 3 + 2) as i64,
166                            texture: None,
167                            normal: None,
168                        },
169                        wavefront_rs::obj::entity::FaceVertex {
170                            vertex: (cnt * 3 + 3) as i64,
171                            texture: None,
172                            normal: None,
173                        },
174                    ],
175                },
176            )
177            .or(Err(Error::OBJ))?;
178    }
179
180    Ok(())
181}
182
183/// An error that can occur while exporting
184#[derive(Debug, Error)]
185pub enum Error {
186    /// No extension specified
187    #[error("no extension specified")]
188    NoExtension,
189
190    /// Unrecognized extension found
191    #[error("unrecognized extension found `{0:?}`")]
192    InvalidExtension(String),
193
194    /// I/O error whilst exporting to file
195    #[error("I/O error whilst exporting to file")]
196    Io(#[from] std::io::Error),
197
198    /// Maximum triangle count exceeded
199    #[error("maximum triangle count exceeded")]
200    InvalidTriangleCount,
201
202    /// Threemf error whilst exporting to 3MF file
203    #[error("threemf error whilst exporting to 3MF file")]
204    ThreeMF(#[from] threemf::Error),
205
206    /// OBJ exporter error whilst exporting to OBJ file
207    #[error("obj error whilst exporting to OBJ file")]
208    OBJ,
209}