vtkio/
lib.rs

1//! Import and export library for Visualization Toolkit (VTK)
2//! [files](https://lorensen.github.io/VTKExamples/site/VTKFileFormats/).
3//!
4//! Legacy `.vtk` files as well as modern XML formats are supported.
5//! Both "serial" and "parallel" XML files are supported with facilities for lazily loading.
6//!
7//! The [`Vtk`] struct exposes the primary IO API.
8//!
9//! # Examples
10//!
11//! Many sample files can be found in the `assets` directory.
12//!
13//! For the following example, we will load a VTK file named `tet.vtk`, modify it and write it back
14//! in Legacy ASCII format.
15//!
16//! ```no_run
17//! use vtkio::model::*; // import model definition of a VTK file
18//! fn main() {
19//!     use std::path::PathBuf;
20//!     let file_path = PathBuf::from("../assets/tet.vtk");
21//!
22//!     let mut vtk_file = Vtk::import(&file_path)
23//!         .expect(&format!("Failed to load file: {:?}", file_path));
24//!
25//!     vtk_file.version = Version::new((4,2)); // arbitrary change
26//!
27//!     vtk_file.export_ascii(&file_path)
28//!         .expect(&format!("Failed to save file: {:?}", file_path));
29//! }
30//! ```
31//!
32//! Files are sometimes provided as strings or byte slices, so it is also useful to be able to
33//! parse VTK files and write them back to a string or byte slice.
34//!
35//! ```no_run
36//! use vtkio::model::*; // import model definition of a VTK file
37//! fn main() {
38//!     let data: &[u8] = include_str!("../assets/tet.vtk").as_bytes(); // Or just include_bytes!
39//!
40//!     let mut vtk_file = Vtk::parse_legacy_be(data).expect(&format!("Failed to parse file"));
41//!
42//!     vtk_file.version = Version::new((4,2)); // arbitrary change
43//!
44//!     let mut output = String::new();
45//!     Vtk::write_legacy_ascii(vtk_file, &mut output).expect(&format!("Failed to write file"));
46//!
47//!     println!("{}", output);
48//! }
49//! ```
50#[macro_use]
51extern crate nom;
52
53#[macro_use]
54pub mod basic;
55
56#[macro_use]
57pub mod model;
58pub mod parser;
59pub mod writer;
60#[cfg(feature = "xml")]
61pub mod xml;
62
63#[cfg(feature = "xml")]
64use std::convert::{TryFrom, TryInto};
65use std::fs::File;
66#[cfg(feature = "xml")]
67use std::io::BufRead;
68use std::io::{self, BufWriter, Read, Write};
69use std::path::Path;
70
71use crate::writer::{AsciiWriter, BinaryWriter, WriteVtk};
72
73pub use model::IOBuffer;
74
75/// The primary `vtkio` API is provided through the `Vtk` struct.
76pub use model::Vtk;
77
78/// Error type for Import/Export operations.
79#[derive(Debug)]
80pub enum Error {
81    IO(io::Error),
82    Write(writer::Error),
83    Parse(nom::ErrorKind<u32>),
84    #[cfg(feature = "xml")]
85    XML(xml::Error),
86    UnknownFileExtension(Option<String>),
87    Load(model::Error),
88    Unknown,
89}
90
91impl std::fmt::Display for Error {
92    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
93        match self {
94            Error::IO(source) => write!(f, "IO error: {}", source),
95            Error::Write(source) => write!(f, "Write error: {}", source),
96            Error::Parse(source) => write!(f, "Parse error: {:?}", source),
97            #[cfg(feature = "xml")]
98            Error::XML(source) => write!(f, "XML error: {}", source),
99            Error::UnknownFileExtension(Some(ext)) => {
100                write!(f, "Unknown file extension: {:?}", ext)
101            }
102            Error::UnknownFileExtension(None) => write!(f, "Missing file extension"),
103            Error::Load(source) => write!(f, "Load error: {}", source),
104            Error::Unknown => write!(f, "Unknown error"),
105        }
106    }
107}
108
109impl std::error::Error for Error {
110    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
111        match self {
112            Error::IO(source) => Some(source),
113            Error::Write(source) => Some(source),
114            Error::Parse(_) => None,
115            #[cfg(feature = "xml")]
116            Error::XML(source) => Some(source),
117            Error::UnknownFileExtension(_) => None,
118            Error::Load(source) => Some(source),
119            Error::Unknown => None,
120        }
121    }
122}
123
124/// Convert `std::io` error into `vtkio` error.
125impl From<io::Error> for Error {
126    fn from(e: io::Error) -> Error {
127        Error::IO(e)
128    }
129}
130
131/// Convert [`xml::Error`] error into the top level `vtkio` error.
132///
133/// [`xml::Error`]: xml.enum.Error.html
134#[cfg(feature = "xml")]
135impl From<xml::Error> for Error {
136    fn from(e: xml::Error) -> Error {
137        Error::XML(e)
138    }
139}
140
141/// Convert `vtkio` error into `std::io` error.
142impl From<Error> for io::Error {
143    fn from(err: Error) -> io::Error {
144        match err {
145            Error::IO(e) => e,
146            _ => io::Error::new(io::ErrorKind::Other, format!("{:?}", err)),
147        }
148    }
149}
150
151impl From<writer::Error> for Error {
152    fn from(e: writer::Error) -> Error {
153        Error::Write(e)
154    }
155}
156
157impl Vtk {
158    /// Helper for parsing legacy VTK files.
159    fn parse_vtk<F>(mut reader: impl Read, parse: F, buf: &mut Vec<u8>) -> Result<Vtk, Error>
160    where
161        F: Fn(&[u8]) -> nom::IResult<&[u8], Vtk>,
162    {
163        use nom::IResult;
164        reader.read_to_end(buf)?;
165        match parse(buf) {
166            IResult::Done(_, vtk) => Ok(vtk),
167            IResult::Error(e) => Err(Error::Parse(e.into_error_kind())),
168            IResult::Incomplete(_) => Err(Error::Unknown),
169        }
170    }
171
172    /// Helper for importing legacy VTK files from the given path.
173    fn import_vtk<F>(file_path: &Path, parse: F) -> Result<Vtk, Error>
174    where
175        F: Fn(&[u8]) -> nom::IResult<&[u8], Vtk>,
176    {
177        let file = File::open(file_path)?;
178        Vtk::parse_vtk(file, parse, &mut Vec::new())
179    }
180
181    /// Parse a legacy VTK file from the given reader.
182    ///
183    /// If the file is in binary format, numeric types will be interpreted in big endian format,
184    /// which is the most common among VTK files.
185    /// Note that this function and [`parse_legacy_le`](Vtk::parse_legacy_le) also work equally well for
186    /// parsing VTK files in ASCII format.
187    ///
188    /// # Examples
189    ///
190    /// Parsing an ASCII file:
191    ///
192    /// ```
193    /// use vtkio::model::*; // import the model definition of a VTK file
194    /// let vtk_ascii: &[u8] = b"
195    /// ## vtk DataFile Version 2.0
196    /// Triangle example
197    /// ASCII
198    /// DATASET POLYDATA
199    /// POINTS 3 float
200    /// 0.0 0.0 0.0
201    /// 1.0 0.0 0.0
202    /// 0.0 0.0 -1.0
203    ///
204    /// POLYGONS 1 4
205    /// 3 0 1 2
206    /// ";
207    ///
208    /// let vtk = Vtk::parse_legacy_be(vtk_ascii).expect("Failed to parse vtk file");
209    ///
210    /// assert_eq!(vtk, Vtk {
211    ///     version: Version::new((2,0)),
212    ///     byte_order: ByteOrder::BigEndian,
213    ///     title: String::from("Triangle example"),
214    ///     file_path: None,
215    ///     data: DataSet::inline(PolyDataPiece {
216    ///         points: vec![0.0f32, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, -1.0].into(),
217    ///         polys: Some(VertexNumbers::Legacy {
218    ///             num_cells: 1,
219    ///             vertices: vec![3, 0, 1, 2]
220    ///         }),
221    ///         data: Attributes::new(),
222    ///         ..Default::default()
223    ///     })
224    /// });
225    /// ```
226    pub fn parse_legacy_be(reader: impl Read) -> Result<Vtk, Error> {
227        Vtk::parse_vtk(reader, parser::parse_be, &mut Vec::new())
228    }
229
230    /// Parse a legacy VTK file from the given reader.
231    ///
232    /// If the file is in binary format, numeric types will be interpreted in little endian format.
233    /// Note that this function and [`parse_legacy_be`](Vtk::parse_legacy_be) also work equally well for
234    /// parsing VTK files in ASCII format.
235    ///
236    /// # Examples
237    ///
238    /// Parsing an ASCII file:
239    ///
240    /// ```
241    /// use vtkio::model::*; // import the model definition of a VTK file
242    /// let vtk_ascii: &[u8] = b"
243    /// ## vtk DataFile Version 2.0
244    /// Triangle example
245    /// ASCII
246    /// DATASET POLYDATA
247    /// POINTS 3 float
248    /// 0.0 0.0 0.0
249    /// 1.0 0.0 0.0
250    /// 0.0 0.0 -1.0
251    ///
252    /// POLYGONS 1 4
253    /// 3 0 1 2
254    /// ";
255    ///
256    /// let vtk = Vtk::parse_legacy_le(vtk_ascii).expect("Failed to parse vtk file");
257    ///
258    /// assert_eq!(vtk, Vtk {
259    ///     version: Version::new((2,0)),
260    ///     byte_order: ByteOrder::LittleEndian,
261    ///     title: String::from("Triangle example"),
262    ///     file_path: None,
263    ///     data: DataSet::inline(PolyDataPiece {
264    ///         points: vec![0.0f32, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, -1.0].into(),
265    ///         polys: Some(VertexNumbers::Legacy {
266    ///             num_cells: 1,
267    ///             vertices: vec![3, 0, 1, 2]
268    ///         }),
269    ///         data: Attributes::new(),
270    ///         ..Default::default()
271    ///     })
272    /// });
273    /// ```
274    pub fn parse_legacy_le(reader: impl Read) -> Result<Vtk, Error> {
275        Vtk::parse_vtk(reader, parser::parse_le, &mut Vec::new())
276    }
277
278    /// Parse a legacy VTK file in big endian format from the given reader and a buffer.
279    ///
280    /// This is the buffered version of [`Vtk::parse_legacy_be`](Vtk::parse_legacy_be), which allows one to reuse the same
281    /// heap allocated space when reading many files.
282    pub fn parse_legacy_buf_be(reader: impl Read, buf: &mut Vec<u8>) -> Result<Vtk, Error> {
283        Vtk::parse_vtk(reader, parser::parse_be, buf)
284    }
285
286    /// Parse a legacy VTK file in little endian format from the given reader and a buffer.
287    ///
288    /// This is the buffered version of [`parse_legacy_le`](Vtk::parse_legacy_le), which allows one to reuse the same
289    /// heap allocated space when reading many files.
290    pub fn parse_legacy_buf_le(reader: impl Read, buf: &mut Vec<u8>) -> Result<Vtk, Error> {
291        Vtk::parse_vtk(reader, parser::parse_le, buf)
292    }
293
294    /// Parse a modern XML style VTK file from a given reader.
295    ///
296    /// # Examples
297    ///
298    /// Parsing a binary file in big endian format representing a polygon mesh consisting of a single
299    /// triangle:
300    ///
301    /// ```
302    /// use vtkio::model::*; // import the model definition of a VTK file
303    ///
304    /// let input: &[u8] = b"\
305    /// <VTKFile type=\"PolyData\" version=\"2.0\" byte_order=\"BigEndian\">\
306    ///   <PolyData>\
307    ///     <Piece NumberOfPoints=\"3\" NumberOfLines=\"0\" NumberOfStrips=\"0\" NumberOfPolys=\"1\" NumberOfVerts=\"0\">\
308    ///       <PointData/>\
309    ///       <CellData/>\
310    ///       <Points>\
311    ///         <DataArray type=\"Float32\" format=\"binary\" NumberOfComponents=\"3\">\
312    ///           AAAAAAAAAAQAAAAAAAAAAAAAAAA/gAAAAAAAAAAAAAAAAAAAAAAAAL+AAAA=\
313    ///         </DataArray>\
314    ///       </Points>\
315    ///       <Polys>\
316    ///         <DataArray type=\"UInt64\" Name=\"connectivity\" format=\"binary\" NumberOfComponents=\"1\">\
317    ///           AAAAAAAAAAgAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAI=\
318    ///         </DataArray>\
319    ///         <DataArray type=\"UInt64\" Name=\"offsets\" format=\"binary\" NumberOfComponents=\"1\">\
320    ///           AAAAAAAAAAgAAAAAAAAAAw==\
321    ///         </DataArray>\
322    ///       </Polys>\
323    ///     </Piece>\
324    ///   </PolyData>\
325    /// </VTKFile>";
326    ///
327    /// let vtk = Vtk::parse_xml(input).expect("Failed to parse XML VTK file");
328    ///
329    /// assert_eq!(vtk, Vtk {
330    ///     version: Version::new((2,0)),
331    ///     byte_order: ByteOrder::BigEndian, // This is default
332    ///     title: String::new(),
333    ///     file_path: None,
334    ///     data: DataSet::inline(PolyDataPiece {
335    ///         points: vec![0.0f32, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, -1.0].into(),
336    ///         polys: Some(VertexNumbers::XML {
337    ///             connectivity: vec![0, 1, 2],
338    ///             offsets: vec![3]
339    ///         }),
340    ///         data: Attributes::new(),
341    ///         ..Default::default()
342    ///     })
343    /// });
344    /// ```
345    #[cfg(feature = "xml")]
346    pub fn parse_xml(reader: impl BufRead) -> Result<Vtk, Error> {
347        // There is no extension to check with the data is provided directly.
348        // Luckily the xml file contains all the data necessary to determine which data is
349        // being parsed.
350        let vtk_file = xml::parse(reader)?;
351        Ok(vtk_file.try_into()?)
352    }
353
354    #[cfg(feature = "async_blocked")]
355    async fn import_vtk_async<F>(file_path: &Path, parse: F) -> Result<Vtk, Error>
356    where
357        F: Fn(&[u8]) -> nom::IResult<&[u8], Vtk>,
358    {
359        use nom::IResult;
360        use tokio::fs::File;
361        use tokio::io::AsyncReadExt;
362
363        let mut file = File::open(file_path).await?;
364        let mut buf = Vec::new();
365        file.read_to_end(&mut buf).await?;
366        match parse(&buf) {
367            IResult::Done(_, vtk) => Ok(vtk),
368            IResult::Error(e) => Err(Error::Parse(e.into_error_kind())),
369            IResult::Incomplete(_) => Err(Error::Unknown),
370        }
371    }
372
373    /// Import a VTK file at the specified path.
374    ///
375    /// This function determines the vtk file type from the extension as prescribed by the [VTK
376    /// file formats documentation](https://lorensen.github.io/VTKExamples/site/VTKFileFormats/):
377    ///
378    ///  - Legacy (`.vtk`) -- Simple legacy file format (Non-XML)
379    ///  - Image data (`.vti`) -- Serial vtkImageData (structured)
380    ///  - PolyData (`.vtp`) -- Serial vtkPolyData (unstructured)
381    ///  - RectilinearGrid (`.vtr`) -- Serial vtkRectilinearGrid (structured)
382    ///  - StructuredGrid (`.vts`) -- Serial vtkStructuredGrid (structured)
383    ///  - UnstructuredGrid (`.vtu`) -- Serial vtkUnstructuredGrid (unstructured)
384    ///  - PImageData (`.pvti`) -- Parallel vtkImageData (structured)
385    ///  - PPolyData (`.pvtp`) -- Parallel vtkPolyData (unstructured)
386    ///  - PRectilinearGrid (`.pvtr`) -- Parallel vtkRectilinearGrid (structured)
387    ///  - PStructuredGrid (`.pvts`) -- Parallel vtkStructuredGrid (structured)
388    ///  - PUnstructuredGrid (`.pvtu`) -- Parallel vtkUnstructuredGrid (unstructured)
389    ///
390    /// # Examples
391    ///
392    /// The following example imports a legacy `.vtk` file called `tet.vtk`, and panics with an
393    /// appropriate error message if the file fails to load.
394    ///
395    /// ```should_panic
396    /// use vtkio::Vtk;
397    /// use std::path::PathBuf;
398    ///
399    /// let file_path = PathBuf::from("tet.vtk");
400    ///
401    /// let mut vtk_file = Vtk::import(&file_path)
402    ///     .expect(&format!("Failed to load file: {:?}", file_path));
403    /// ```
404    pub fn import(file_path: impl AsRef<Path>) -> Result<Vtk, Error> {
405        Vtk::import_impl(file_path.as_ref())
406    }
407
408    /// A non-generic helper for the `import` function.
409    fn import_impl(path: &Path) -> Result<Vtk, Error> {
410        let ext = path
411            .extension()
412            .and_then(|s| s.to_str())
413            .ok_or(Error::UnknownFileExtension(None))?;
414        match ext {
415            "vtk" => Vtk::import_vtk(path, parser::parse_be),
416            #[cfg(feature = "xml")]
417            ext => {
418                let ft = xml::FileType::try_from_ext(ext)
419                    .ok_or(Error::UnknownFileExtension(Some(ext.to_string())))?;
420                let vtk_file = xml::import(path)?;
421                let exp_ft = xml::FileType::from(vtk_file.data_set_type);
422                if ft != exp_ft {
423                    Err(Error::XML(xml::Error::TypeExtensionMismatch))
424                } else {
425                    let mut vtk: Vtk = vtk_file.try_into()?;
426                    vtk.file_path = Some(path.into());
427                    Ok(vtk)
428                }
429            }
430            #[cfg(not(feature = "xml"))]
431            _ => Err(Error::UnknownFileExtension(None)),
432        }
433    }
434
435    /// Import a VTK file at the specified path.
436    ///
437    /// This is the async version of [`import`](Vtk::import).
438    ///
439    /// This function determines the vtk file type from the extension as prescribed by the [VTK
440    /// file formats documentation](https://lorensen.github.io/VTKExamples/site/VTKFileFormats/):
441    ///
442    ///  - Legacy (`.vtk`) -- Simple legacy file format (Non-XML)
443    ///  - Image data (`.vti`) -- Serial vtkImageData (structured)
444    ///  - PolyData (`.vtp`) -- Serial vtkPolyData (unstructured)
445    ///  - RectilinearGrid (`.vtr`) -- Serial vtkRectilinearGrid (structured)
446    ///  - StructuredGrid (`.vts`) -- Serial vtkStructuredGrid (structured)
447    ///  - UnstructuredGrid (`.vtu`) -- Serial vtkUnstructuredGrid (unstructured)
448    ///  - PImageData (`.pvti`) -- Parallel vtkImageData (structured)
449    ///  - PPolyData (`.pvtp`) -- Parallel vtkPolyData (unstructured)
450    ///  - PRectilinearGrid (`.pvtr`) -- Parallel vtkRectilinearGrid (structured)
451    ///  - PStructuredGrid (`.pvts`) -- Parallel vtkStructuredGrid (structured)
452    ///  - PUnstructuredGrid (`.pvtu`) -- Parallel vtkUnstructuredGrid (unstructured)
453    ///
454    /// # Examples
455    ///
456    /// The following example imports a legacy `.vtk` file called `tet.vtk`, and panics with an
457    /// appropriate error message if the file fails to load.
458    ///
459    /// ```should_panic
460    /// use vtkio::Vtk;
461    /// use std::path::PathBuf;
462    ///
463    /// let file_path = PathBuf::from("tet.vtk");
464    ///
465    /// let mut vtk_file = Vtk::import_async(&file_path).await
466    ///     .expect(&format!("Failed to load file: {:?}", file_path));
467    /// ```
468    #[cfg(feature = "async_blocked")]
469    pub async fn import_async(file_path: impl AsRef<Path>) -> Result<Vtk, Error> {
470        let path = file_path.as_ref();
471        let ext = path.extension().and_then(|s| s.to_str()).ok_or()?;
472        match ext {
473            "vtk" => import_vtk_async(path, parser::parse_be).await,
474            #[cfg(feature = "xml")]
475            ext => {
476                let ft = xml::FileType::try_from_ext(ext)
477                    .ok_or(Error::UnknownFileExtension(Some(ext.to_string())))?;
478                let vtk_file = xml::import_async(path).await?;
479                let exp_ft = xml::FileType::from(vtk_file.data_set_type);
480                if ft != exp_ft {
481                    Err(Error::XML(xml::Error::TypeExtensionMismatch))
482                } else {
483                    Ok(vtk_file.try_into()?)
484                }
485            }
486            #[cfg(not(feature = "xml"))]
487            _ => Err(Error::UnknownFileExtension(None)),
488        }
489    }
490
491    /// Import an XML VTK file in raw form.
492    ///
493    /// This importer performs a direct translation from the XML string to a Rust representation
494    /// without any decoding or conversion. For a more complete import use [`import`].
495    ///
496    /// [`VTKFile`] is used internally as an intermediate step for constructing the [`Vtk`] model,
497    /// which has built-in facilities for loading pieces referenced in "parallel" XML formats as well
498    /// as representing Legacy VTK formats, which are more compact when serialized.
499    ///
500    /// [`Vtk`]: model/struct.Vtk.html
501    /// [`VTKFile`]: xml/struct.VTKFile.html
502    /// [`import`]: fn.import.html
503    #[cfg(feature = "unstable")]
504    pub fn import_xml(file_path: impl AsRef<Path>) -> Result<xml::VTKFile, Error> {
505        let path = file_path.as_ref();
506        let ext = path
507            .extension()
508            .and_then(|s| s.to_str())
509            .ok_or(Error::UnknownFileExtension(None))?;
510
511        // Check that the file extension is one of the known ones.
512        let _ = xml::FileType::try_from_ext(ext)
513            .ok_or(Error::UnknownFileExtension(Some(ext.to_string())))?;
514
515        Ok(xml::import(path)?)
516    }
517
518    /// Import a legacy VTK file at the specified path.
519    ///
520    /// If the file is in binary format, numeric types will be interpreted in little endian format.
521    /// For the default byte order used by most `.vtk` files use [`import`] or [`import_legacy_be`].
522    ///
523    /// [`import`]: fn.import.html
524    /// [`import_legacy_be`]: fn.import_legacy_be.html
525    pub fn import_legacy_le(file_path: impl AsRef<Path>) -> Result<Vtk, Error> {
526        Vtk::import_vtk(file_path.as_ref(), parser::parse_le)
527    }
528
529    #[deprecated(since = "0.6.2", note = "Please use Vtk::import_legacy_le instead")]
530    pub fn import_le(file_path: impl AsRef<Path>) -> Result<Vtk, Error> {
531        Vtk::import_legacy_le(file_path.as_ref())
532    }
533
534    /// Import a legacy VTK file at the specified path.
535    ///
536    /// If the file is in binary format, numeric types will be interpreted in big endian format.
537    /// This function behaves the same as [`import`], but expects the given file to be strictly in
538    /// legacy `.vtk` format.
539    ///
540    /// [`import`]: fn.import.html
541    pub fn import_legacy_be(file_path: impl AsRef<Path>) -> Result<Vtk, Error> {
542        Vtk::import_vtk(file_path.as_ref(), parser::parse_be)
543    }
544
545    #[deprecated(since = "0.6.2", note = "Please use Vtk::import_legacy_be instead")]
546    pub fn import_be(file_path: impl AsRef<Path>) -> Result<Vtk, Error> {
547        Vtk::import_legacy_be(file_path.as_ref())
548    }
549
550    /// Export given [`Vtk`] file to the specified file.
551    ///
552    /// The type of file exported is determined by the extension in `file_path`.
553    ///
554    /// Files ending in `.vtk` are exported in binary format. For exporting in ASCII, use
555    /// [`export_ascii`].
556    ///
557    /// Endianness is determined by the `byte_order` field of the [`Vtk`] type.
558    ///
559    /// # Examples
560    ///
561    /// ```no_run
562    /// use vtkio::model::*;
563    /// use std::path::PathBuf;
564    /// let vtk = Vtk {
565    ///     version: Version::new((4,1)),
566    ///     byte_order: ByteOrder::BigEndian,
567    ///     title: String::from("Tetrahedron"),
568    ///     file_path: Some(PathBuf::from("./test.vtk")),
569    ///     data: DataSet::inline(UnstructuredGridPiece {
570    ///         points: vec![0.0f32, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 1.0, 0.0].into(),
571    ///         cells: Cells {
572    ///             cell_verts: VertexNumbers::Legacy {
573    ///                 num_cells: 1,
574    ///                 vertices: vec![4, 0, 1, 2, 3]
575    ///             },
576    ///             types: vec![CellType::Tetra],
577    ///         },
578    ///         data: Attributes::new(),
579    ///     })
580    /// };
581    /// vtk.export("test.vtk");
582    /// ```
583    ///
584    /// [`Vtk`]: struct.Vtk.html
585    /// [`export_ascii`]: fn.export_ascii.html
586    pub fn export(self, file_path: impl AsRef<Path>) -> Result<(), Error> {
587        self.export_impl(file_path.as_ref())
588    }
589
590    /// A non-generic helper for the export function.
591    fn export_impl(self, path: &Path) -> Result<(), Error> {
592        let ext = path
593            .extension()
594            .and_then(|s| s.to_str())
595            .ok_or(Error::UnknownFileExtension(None))?;
596        match ext {
597            "vtk" => {
598                let file = File::create(path)?;
599                BinaryWriter(BufWriter::new(file)).write_vtk(self)?;
600                Ok(())
601            }
602            #[cfg(feature = "xml")]
603            ext => {
604                let ft = xml::FileType::try_from_ext(ext)
605                    .ok_or(Error::UnknownFileExtension(Some(ext.to_string())))?;
606                let vtk_file = xml::VTKFile::try_from(self)?;
607                let exp_ft = xml::FileType::from(vtk_file.data_set_type);
608                if ft != exp_ft {
609                    Err(Error::XML(xml::Error::TypeExtensionMismatch))
610                } else {
611                    xml::export(&vtk_file, path)?;
612                    Ok(())
613                }
614            }
615            #[cfg(not(feature = "xml"))]
616            _ => Err(Error::UnknownFileExtension(None)),
617        }
618    }
619
620    /// Write the given VTK file in binary legacy format to the specified [`Write`](std::io::Write)r.
621    ///
622    /// # Examples
623    ///
624    /// Writing a binary file in big endian format representing a polygon mesh consisting of a single
625    /// triangle:
626    ///
627    /// ```
628    /// use vtkio::model::*; // import model definition of a VTK file
629    ///
630    /// let mut vtk_bytes = Vec::<u8>::new();
631    ///
632    /// Vtk {
633    ///     version: Version::new((2,0)),
634    ///     byte_order: ByteOrder::BigEndian,
635    ///     title: String::from("Triangle example"),
636    ///     file_path: None,
637    ///     data: DataSet::inline(PolyDataPiece {
638    ///         points: vec![0.0f32, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, -1.0].into(),
639    ///         polys: Some(VertexNumbers::Legacy {
640    ///             num_cells: 1,
641    ///             vertices: vec![3, 0, 1, 2]
642    ///         }),
643    ///         data: Attributes::new(),
644    ///         ..Default::default()
645    ///     })
646    /// }.write_legacy(&mut vtk_bytes);
647    ///
648    /// println!("{}", String::from_utf8_lossy(&vtk_bytes));
649    /// ```
650    pub fn write_legacy(self, writer: impl std::io::Write) -> Result<(), Error> {
651        BinaryWriter(writer).write_vtk(self)?;
652        Ok(())
653    }
654
655    /// Write the given VTK file in binary legacy format to the specified [`Write`](std::fmt::Write)r.
656    ///
657    /// # Examples
658    ///
659    /// Writing an ASCII file representing a polygon mesh consisting of a single triangle:
660    ///
661    /// ```
662    /// use vtkio::model::*; // import model definition of a VTK file
663    ///
664    /// let mut vtk_string = String::new();
665    ///
666    /// Vtk {
667    ///     version: Version::new((2,0)),
668    ///     byte_order: ByteOrder::BigEndian, // Ignored
669    ///     title: String::from("Triangle example"),
670    ///     file_path: None,
671    ///     data: DataSet::inline(PolyDataPiece {
672    ///         points: vec![0.0f32, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, -1.0].into(),
673    ///         polys: Some(VertexNumbers::Legacy {
674    ///             num_cells: 1,
675    ///             vertices: vec![3, 0, 1, 2]
676    ///         }),
677    ///         data: Attributes::new(),
678    ///         ..Default::default()
679    ///     })
680    /// }.write_legacy_ascii(&mut vtk_string);
681    ///
682    /// assert_eq!(vtk_string.as_str(), "\
683    /// ## vtk DataFile Version 2.0
684    /// Triangle example
685    /// ASCII
686    ///
687    /// DATASET POLYDATA
688    /// POINTS 3 float
689    /// 0 0 0 1 0 0 0 0 -1
690    ///
691    /// POLYGONS 1 4
692    /// 3 0 1 2
693    ///
694    /// POINT_DATA 3
695    ///
696    /// CELL_DATA 1
697    ///
698    /// ");
699    /// ```
700    pub fn write_legacy_ascii(self, writer: impl std::fmt::Write) -> Result<(), Error> {
701        AsciiWriter(writer).write_vtk(self)?;
702        Ok(())
703    }
704
705    /// Write the given VTK file in modern XML format to the specified [`Write`](std::io::Write)r.
706    ///
707    /// # Examples
708    ///
709    /// Writing a binary file in big endian format representing a polygon mesh consisting of a single
710    /// triangle:
711    ///
712    /// ```
713    /// use vtkio::model::*; // import model definition of a VTK file
714    ///
715    /// let mut vtk_bytes = Vec::<u8>::new();
716    ///
717    /// Vtk {
718    ///     version: Version::new((2,0)),
719    ///     byte_order: ByteOrder::BigEndian,
720    ///     title: String::from("Triangle example"),
721    ///     file_path: None,
722    ///     data: DataSet::inline(PolyDataPiece {
723    ///         points: vec![0.0f32, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, -1.0].into(),
724    ///         polys: Some(VertexNumbers::Legacy {
725    ///             num_cells: 1,
726    ///             vertices: vec![3, 0, 1, 2]
727    ///         }),
728    ///         data: Attributes::new(),
729    ///         ..Default::default()
730    ///     })
731    /// }.write_xml(&mut vtk_bytes);
732    ///
733    /// assert_eq!(String::from_utf8_lossy(&vtk_bytes), "\
734    /// <VTKFile type=\"PolyData\" version=\"2.0\" byte_order=\"BigEndian\" header_type=\"UInt64\">\
735    ///   <PolyData>\
736    ///     <Piece NumberOfPoints=\"3\" NumberOfLines=\"0\" NumberOfStrips=\"0\" NumberOfPolys=\"1\" NumberOfVerts=\"0\">\
737    ///       <PointData/>\
738    ///       <CellData/>\
739    ///       <Points>\
740    ///         <DataArray type=\"Float32\" format=\"binary\" NumberOfComponents=\"3\">\
741    ///           AAAAAAAAACQAAAAAAAAAAAAAAAA/gAAAAAAAAAAAAAAAAAAAAAAAAL+AAAA=\
742    ///         </DataArray>\
743    ///       </Points>\
744    ///       <Polys>\
745    ///         <DataArray type=\"UInt64\" Name=\"connectivity\" format=\"binary\" NumberOfComponents=\"1\">\
746    ///           AAAAAAAAABgAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAI=\
747    ///         </DataArray>\
748    ///         <DataArray type=\"UInt64\" Name=\"offsets\" format=\"binary\" NumberOfComponents=\"1\">\
749    ///           AAAAAAAAAAgAAAAAAAAAAw==\
750    ///         </DataArray>\
751    ///       </Polys>\
752    ///     </Piece>\
753    ///   </PolyData>\
754    /// </VTKFile>");
755    /// ```
756    #[cfg(feature = "xml")]
757    pub fn write_xml(self, writer: impl Write) -> Result<(), Error> {
758        let vtk_file = xml::VTKFile::try_from(self)?;
759        xml::write(&vtk_file, writer)?;
760        Ok(())
761    }
762
763    /// Export the VTK data to the specified path in little endian binary format.
764    ///
765    /// This function is used as [`export`] but overrides endiannes.
766    ///
767    /// [`export`]: fn.export.html
768    pub fn export_le(self, file_path: impl AsRef<Path>) -> Result<(), Error> {
769        let file = File::create(file_path.as_ref())?;
770        BinaryWriter(BufWriter::new(file)).write_vtk_le(self)?;
771        Ok(())
772    }
773
774    /// Export the VTK data to the specified path in big endian binary format.
775    ///
776    /// This function is used as [`export`] but overrides endiannes.
777    ///
778    /// [`export`]: fn.export.html
779    pub fn export_be(self, file_path: impl AsRef<Path>) -> Result<(), Error> {
780        let file = File::create(file_path.as_ref())?;
781        BinaryWriter(BufWriter::new(file)).write_vtk_be(self)?;
782        Ok(())
783    }
784
785    /// Export VTK data to the specified file in ASCII format.
786    ///
787    /// # Examples
788    ///
789    /// ```no_run
790    /// use vtkio::model::*;
791    /// use std::path::PathBuf;
792    /// let vtk = Vtk {
793    ///     version: Version::new((4,1)),
794    ///     title: String::from("Tetrahedron"),
795    ///     byte_order: ByteOrder::BigEndian,
796    ///     file_path: Some(PathBuf::from("./test.vtk")),
797    ///     data: DataSet::inline(UnstructuredGridPiece {
798    ///         points: vec![0.0f32, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 1.0, 0.0].into(),
799    ///         cells: Cells {
800    ///             cell_verts: VertexNumbers::Legacy {
801    ///                 num_cells: 1,
802    ///                 vertices: vec![4, 0, 1, 2, 3]
803    ///             },
804    ///             types: vec![CellType::Tetra],
805    ///         },
806    ///         data: Attributes::new(),
807    ///     })
808    /// };
809    /// vtk.export_ascii("test.vtk");
810    /// ```
811    pub fn export_ascii(self, file_path: impl AsRef<Path>) -> Result<(), Error> {
812        // Ascii formats are typically used for small files, so it makes sense to make the write
813        // in-memory first.
814        let mut out_str = AsciiWriter(String::new());
815        out_str.write_vtk(self)?;
816        let mut file = File::create(file_path.as_ref())?;
817        file.write_all(out_str.0.as_bytes())?;
818        Ok(())
819    }
820}