1#![allow(unexpected_cfgs)]
2
3use std::path::Path;
14
15pub use vtkio::Vtk;
16
17use crate::attrib;
18use crate::mesh::{Mesh, PointCloud, PolyMesh, TetMesh, TriMesh};
19
20#[cfg(feature = "mshio")]
21pub mod msh;
22pub mod obj;
23pub mod vtk;
24
25#[cfg(feature = "vtkio")]
26pub trait Real: vtkio::model::Scalar + std::str::FromStr + crate::Real {}
27#[cfg(feature = "vtkio")]
28impl<T> Real for T where T: vtkio::model::Scalar + std::str::FromStr + crate::Real {}
29
30#[cfg(not(feature = "vtkio"))]
31pub trait Real: crate::Real + std::str::FromStr {}
32#[cfg(not(feature = "vtkio"))]
33impl<T> Real for T where T: crate::Real + std::str::FromStr {}
34
35const UV_ATTRIB_NAME: &str = "uv";
38const NORMAL_ATTRIB_NAME: &str = "N";
39const MTL_ATTRIB_NAME: &str = "mtl";
40const OBJECT_ATTRIB_NAME: &str = "object";
41const GROUP_ATTRIB_NAME: &str = "group";
42
43pub trait MeshExtractor<T: crate::Real> {
48    fn extract_mesh(&self) -> Result<Mesh<T>, Error> {
52        Err(Error::UnsupportedDataFormat)
53    }
54    fn extract_polymesh(&self) -> Result<PolyMesh<T>, Error> {
58        Err(Error::UnsupportedDataFormat)
59    }
60    fn extract_tetmesh(&self) -> Result<TetMesh<T>, Error> {
64        Err(Error::UnsupportedDataFormat)
65    }
66    fn extract_pointcloud(&self) -> Result<PointCloud<T>, Error> {
70        Err(Error::UnsupportedDataFormat)
71    }
72}
73
74pub fn load_mesh<T: Real, P: AsRef<Path>>(file: P) -> Result<Mesh<T>, Error> {
80    load_mesh_impl(file.as_ref())
81}
82
83fn load_mesh_impl<T: Real>(file: &Path) -> Result<Mesh<T>, Error> {
84    match file.extension().and_then(|ext| ext.to_str()) {
85        Some("vtk") | Some("vtu") | Some("pvtu") => {
86            let vtk = Vtk::import(file)?;
87            vtk.extract_mesh()
88        }
89        #[cfg(feature = "mshio")]
90        Some("msh") => {
91            let msh_bytes = std::fs::read(file)?;
92            let msh = mshio::parse_msh_bytes(msh_bytes.as_slice()).map_err(msh::MshError::from)?;
93            msh.extract_mesh()
94        }
95        _ => Err(Error::UnsupportedFileFormat),
97    }
98}
99
100pub fn save_mesh<T: Real, P: AsRef<Path>>(mesh: &Mesh<T>, file: P) -> Result<(), Error> {
102    save_mesh_impl(mesh, file.as_ref())
103}
104
105fn save_mesh_impl<T: Real>(mesh: &Mesh<T>, file: &Path) -> Result<(), Error> {
106    match file.extension().and_then(|ext| ext.to_str()) {
107        Some("vtk") | Some("vtu") | Some("pvtu") => {
108            let vtk = vtk::convert_mesh_to_vtk_format(mesh)?;
109            vtk.export_be(file)?;
110            Ok(())
111        }
112        _ => Err(Error::UnsupportedFileFormat),
114    }
115}
116
117pub fn save_mesh_ascii<T: Real>(mesh: &Mesh<T>, file: impl AsRef<Path>) -> Result<(), Error> {
119    save_mesh_ascii_impl(mesh, file.as_ref())
120}
121
122fn save_mesh_ascii_impl<T: Real>(mesh: &Mesh<T>, file: &Path) -> Result<(), Error> {
123    match file.extension() {
124        Some(ext) if ext.to_str() == Some("vtk") => {
125            let vtk = vtk::convert_mesh_to_vtk_format(mesh)?;
127            vtk.export_ascii(file)?;
128            Ok(())
129        }
130        _ => Err(Error::UnsupportedFileFormat),
132    }
133}
134
135pub fn load_tetmesh<T: Real, P: AsRef<Path>>(file: P) -> Result<TetMesh<T>, Error> {
141    load_tetmesh_impl(file.as_ref())
142}
143
144fn load_tetmesh_impl<T: Real>(file: &Path) -> Result<TetMesh<T>, Error> {
145    match file.extension().and_then(|ext| ext.to_str()) {
146        Some("vtk") | Some("vtu") | Some("pvtu") => {
147            let vtk = Vtk::import(file)?;
148            vtk.extract_tetmesh()
149        }
150        _ => Err(Error::UnsupportedFileFormat),
152    }
153}
154
155pub fn save_tetmesh<T: Real, P: AsRef<Path>>(tetmesh: &TetMesh<T>, file: P) -> Result<(), Error> {
157    save_tetmesh_impl(tetmesh, file.as_ref())
158}
159
160fn save_tetmesh_impl<T: Real>(tetmesh: &TetMesh<T>, file: &Path) -> Result<(), Error> {
161    match file.extension().and_then(|ext| ext.to_str()) {
162        Some("vtk") | Some("vtu") | Some("pvtu") => {
163            let vtk = vtk::convert_tetmesh_to_vtk_format(tetmesh)?;
164            vtk.export_be(file)?;
165            Ok(())
166        }
167        _ => Err(Error::UnsupportedFileFormat),
169    }
170}
171
172pub fn save_tetmesh_ascii<T: Real>(
174    tetmesh: &TetMesh<T>,
175    file: impl AsRef<Path>,
176) -> Result<(), Error> {
177    save_tetmesh_ascii_impl(tetmesh, file.as_ref())
178}
179
180fn save_tetmesh_ascii_impl<T: Real>(tetmesh: &TetMesh<T>, file: &Path) -> Result<(), Error> {
181    match file.extension() {
182        Some(ext) if ext.to_str() == Some("vtk") => {
183            let vtk = vtk::convert_tetmesh_to_vtk_format(tetmesh)?;
185            vtk.export_ascii(file)?;
186            Ok(())
187        }
188        _ => Err(Error::UnsupportedFileFormat),
190    }
191}
192
193pub fn load_polymesh<T: Real, P: AsRef<Path>>(file: P) -> Result<PolyMesh<T>, Error> {
199    load_polymesh_impl(file.as_ref())
200}
201
202fn load_polymesh_impl<T: Real>(file: &Path) -> Result<PolyMesh<T>, Error> {
203    match file.extension().and_then(|ext| ext.to_str()) {
204        Some("vtk") | Some("vtu") | Some("vtp") | Some("pvtu") | Some("pvtp") => {
205            let vtk = Vtk::import(file)?;
206            vtk.extract_polymesh()
207        }
208        Some("obj") => {
209            let mut obj = obj::Obj::load_with_config(file, obj::LoadConfig { strict: false })?;
210            obj.load_mtls().map_err(|e| Error::MeshIO {
211                source: MeshIOError::Mtl { source: e },
212            })?;
213            obj.data.extract_polymesh()
214        }
215        _ => Err(Error::UnsupportedFileFormat),
216    }
217}
218
219pub fn save_polymesh<T: Real, P: AsRef<Path>>(
221    polymesh: &PolyMesh<T>,
222    file: P,
223) -> Result<(), Error> {
224    save_polymesh_impl(polymesh, file.as_ref())
225}
226
227fn save_polymesh_impl<T: Real>(polymesh: &PolyMesh<T>, file: &Path) -> Result<(), Error> {
228    match file.extension().and_then(|ext| ext.to_str()) {
229        Some("vtk") | Some("vtu") | Some("vtp") | Some("pvtu") | Some("pvtp") => {
230            let vtk =
231                vtk::convert_polymesh_to_vtk_format(polymesh, vtk::VTKPolyExportStyle::PolyData)?;
232            vtk.export_be(file)?;
233            Ok(())
234        }
235        Some("obj") => {
236            let obj = obj::convert_polymesh_to_obj_format(polymesh)?;
237            obj.save(file)?;
238            Ok(())
239        }
240        _ => Err(Error::UnsupportedFileFormat),
241    }
242}
243
244pub fn save_polymesh_ascii<T: Real, P: AsRef<Path>>(
246    polymesh: &PolyMesh<T>,
247    file: P,
248) -> Result<(), Error> {
249    save_polymesh_ascii_impl(polymesh, file.as_ref())
250}
251
252fn save_polymesh_ascii_impl<T: Real>(polymesh: &PolyMesh<T>, file: &Path) -> Result<(), Error> {
253    match file.extension().and_then(|ext| ext.to_str()) {
254        Some("vtk") => {
255            let vtk =
257                vtk::convert_polymesh_to_vtk_format(polymesh, vtk::VTKPolyExportStyle::PolyData)?;
258            vtk.export_ascii(file)?;
259            Ok(())
260        }
261        Some("obj") => {
262            let obj = obj::convert_polymesh_to_obj_format(polymesh)?;
263            obj.save(file)?;
264            Ok(())
265        }
266        _ => Err(Error::UnsupportedFileFormat),
267    }
268}
269
270pub fn load_trimesh<T: Real, P: AsRef<Path>>(file: P) -> Result<TriMesh<T>, Error> {
280    load_polymesh_impl(file.as_ref()).map(TriMesh::from)
281}
282
283pub fn save_trimesh<T: Real, P: AsRef<Path>>(trimesh: &TriMesh<T>, file: P) -> Result<(), Error> {
285    save_polymesh_impl(&PolyMesh::from(trimesh.clone()), file.as_ref())
286}
287
288pub fn save_trimesh_ascii<T: Real, P: AsRef<Path>>(
290    trimesh: &TriMesh<T>,
291    file: P,
292) -> Result<(), Error> {
293    save_polymesh_ascii_impl(&PolyMesh::from(trimesh.clone()), file.as_ref())
294}
295
296pub fn load_pointcloud<T: Real, P: AsRef<Path>>(file: P) -> Result<PointCloud<T>, Error> {
302    load_pointcloud_impl(file.as_ref())
303}
304
305fn load_pointcloud_impl<T: Real>(file: &Path) -> Result<PointCloud<T>, Error> {
306    match file.extension().and_then(|ext| ext.to_str()) {
307        Some("vtk") | Some("vtu") | Some("vtp") | Some("pvtu") | Some("pvtp") => {
308            let vtk = Vtk::import(file)?;
309            vtk.extract_pointcloud()
310        }
311        Some("obj") => {
312            let obj = obj::Obj::load_with_config(file, obj::LoadConfig { strict: false })?;
313            obj.data.extract_pointcloud()
314        }
315        _ => Err(Error::UnsupportedFileFormat),
316    }
317}
318
319pub fn save_pointcloud<T: Real, P: AsRef<Path>>(
321    ptcloud: &PointCloud<T>,
322    file: P,
323) -> Result<(), Error> {
324    save_pointcloud_impl(ptcloud, file.as_ref())
325}
326
327pub fn save_pointcloud_impl<T: Real>(ptcloud: &PointCloud<T>, file: &Path) -> Result<(), Error> {
328    match file.extension().and_then(|ext| ext.to_str()) {
329        Some("vtk") | Some("vtu") | Some("vtp") | Some("pvtu") | Some("pvtp") => {
330            let vtk =
331                vtk::convert_pointcloud_to_vtk_format(ptcloud, vtk::VTKPolyExportStyle::PolyData)?;
332            vtk.export_be(file)?;
333            Ok(())
334        }
335        Some("obj") => {
336            let obj = obj::convert_pointcloud_to_obj_format(ptcloud)?;
337            obj.save(file)?;
338            Ok(())
339        }
340        _ => Err(Error::UnsupportedFileFormat),
341    }
342}
343
344pub fn save_pointcloud_ascii<T: Real, P: AsRef<Path>>(
346    ptcloud: &PointCloud<T>,
347    file: P,
348) -> Result<(), Error> {
349    save_pointcloud_ascii_impl(ptcloud, file.as_ref())
350}
351
352fn save_pointcloud_ascii_impl<T: Real>(ptcloud: &PointCloud<T>, file: &Path) -> Result<(), Error> {
353    match file.extension().and_then(|ext| ext.to_str()) {
354        Some("vtk") => {
355            let vtk =
356                vtk::convert_pointcloud_to_vtk_format(ptcloud, vtk::VTKPolyExportStyle::PolyData)?;
357            vtk.export_ascii(file)?;
358            Ok(())
359        }
360        Some("obj") => {
361            let obj = obj::convert_pointcloud_to_obj_format(ptcloud)?;
362            obj.save(file)?;
363            Ok(())
364        }
365        _ => Err(Error::UnsupportedFileFormat),
366    }
367}
368
369#[derive(Debug)]
374pub enum MeshIOError {
375    #[cfg(feature = "mshio")]
376    Msh {
377        source: msh::MshError,
378    },
379    Vtk {
380        source: vtk::VtkError,
381    },
382    Obj {
383        source: obj::ObjError,
384    },
385    Mtl {
386        source: obj::MtlLibsLoadError,
387    },
388}
389
390impl std::error::Error for MeshIOError {
391    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
392        match self {
393            MeshIOError::Msh { source } => Some(source),
394            MeshIOError::Vtk { source } => Some(source),
395            MeshIOError::Obj { source } => Some(source),
396            MeshIOError::Mtl { source } => Some(source),
397        }
398    }
399}
400
401impl std::fmt::Display for MeshIOError {
402    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
403        match self {
404            MeshIOError::Msh { source } => write!(f, "A Msh IO error occurred: {}", source),
405            MeshIOError::Vtk { source } => write!(f, "A Vtk IO error occurred: {}", source),
406            MeshIOError::Obj { source } => write!(f, "An Obj IO error occurred: {}", source),
407            MeshIOError::Mtl { source } => write!(f, "An Mtl IO error occurred: {}", source),
408        }
409    }
410}
411
412#[derive(Debug)]
413pub enum Error {
414    IO { source: std::io::Error },
415    MeshIO { source: MeshIOError },
416    Attrib { source: attrib::Error },
417    UnsupportedFileFormat,
418    UnsupportedDataFormat,
419    MeshTypeMismatch,
420    MissingMeshData,
421}
422
423impl std::error::Error for Error {
424    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
425        match self {
426            Error::IO { source } => Some(source),
427            Error::MeshIO { source } => Some(source),
428            Error::Attrib { .. } => {
429                None }
431            _ => None,
432        }
433    }
434}
435
436impl std::fmt::Display for Error {
437    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
438        match self {
439            Error::IO { source } => write!(f, "IO Error: {}", source),
440            Error::MeshIO { source } => write!(f, "An error in mesh IO occurred: {}", source),
441            Error::Attrib { source } => write!(f, "An attribute error occurred: {}", source),
442            Error::UnsupportedFileFormat => write!(f, "Unsupported file format specified"),
443            Error::UnsupportedDataFormat => write!(f, "Unsupported data format specified"),
444            Error::MeshTypeMismatch => write!(f, "Mesh type doesn't match expected type"),
445            Error::MissingMeshData => write!(f, "Missing mesh data"),
446        }
447    }
448}
449
450impl From<std::io::Error> for Error {
451    fn from(err: std::io::Error) -> Error {
452        Error::IO { source: err }
453    }
454}
455
456#[cfg(feature = "mshio")]
457impl From<msh::MshError> for Error {
458    fn from(err: msh::MshError) -> Error {
459        Error::MeshIO {
460            source: MeshIOError::Msh { source: err },
461        }
462    }
463}
464
465impl From<obj::ObjError> for Error {
466    fn from(err: obj::ObjError) -> Error {
467        Error::MeshIO {
468            source: MeshIOError::Obj { source: err },
469        }
470    }
471}
472
473impl From<vtk::VtkError> for Error {
474    fn from(err: vtk::VtkError) -> Error {
475        Error::MeshIO {
476            source: MeshIOError::Vtk { source: err },
477        }
478    }
479}
480
481impl From<attrib::Error> for Error {
482    fn from(err: attrib::Error) -> Error {
483        Error::Attrib { source: err }
484    }
485}
486
487#[cfg(test)]
488mod tests {
489    use super::*;
490
491    #[test]
496    fn polycube() -> Result<(), Error> {
497        let polymesh: PolyMesh<f32> = load_polymesh("assets/cube.obj")?;
498        save_polymesh(&polymesh, "tests/artifacts/cube.obj")?;
499        let reloaded_polymesh = load_polymesh("tests/artifacts/cube.obj")?;
500        assert_eq!(polymesh, reloaded_polymesh);
501        Ok(())
502    }
503
504    #[test]
508    fn polycube_convert() -> Result<(), Error> {
509        use crate::mesh::TriMesh;
510        let _: TriMesh<f32> = load_polymesh("assets/cube.obj")?.into();
511        Ok(())
512    }
513
514    #[cfg(feature = "binary_vtk")]
516    #[test]
517    fn tet_vtu() -> Result<(), Error> {
518        use crate::mesh::TetMesh;
519        let mesh: TetMesh<f32> = load_tetmesh("assets/tet.vtu")?;
520        dbg!(&mesh);
521        Ok(())
522    }
523
524    #[test]
525    fn cloth_argus() -> Result<(), Error> {
526        let polymesh: PolyMesh<f32> = load_polymesh("assets/cloth_argus.obj")?;
527        dbg!(&polymesh);
528        Ok(())
529    }
530
531    #[test]
535    fn tet_vtk_as_polymesh_error() {
536        assert!(load_polymesh::<f64, _>("assets/tet.vtk").is_err());
537    }
538
539    #[test]
541    fn unstructured_data_polymesh_real_test() {
542        assert!(load_polymesh::<f64, _>("./assets/tube.vtk").is_ok());
543    }
544
545    #[test]
547    fn sphere_msh() -> Result<(), Error> {
548        use crate::mesh::topology::*;
549        let mesh: Mesh<f64> = load_mesh("assets/sphere_coarse.msh")?;
550        assert_eq!(mesh.num_vertices(), 183);
551        assert_eq!(mesh.num_cells(), 593);
552        Ok(())
553    }
554}