1use 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
22pub 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
49pub 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
85pub 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
132pub 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 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 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#[derive(Debug, Error)]
185pub enum Error {
186 #[error("no extension specified")]
188 NoExtension,
189
190 #[error("unrecognized extension found `{0:?}`")]
192 InvalidExtension(String),
193
194 #[error("I/O error whilst exporting to file")]
196 Io(#[from] std::io::Error),
197
198 #[error("maximum triangle count exceeded")]
200 InvalidTriangleCount,
201
202 #[error("threemf error whilst exporting to 3MF file")]
204 ThreeMF(#[from] threemf::Error),
205
206 #[error("obj error whilst exporting to OBJ file")]
208 OBJ,
209}