diff --git a/.gitignore b/.gitignore index 81b392c..70b8186 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ /target +/target_remote **/*.rs.bk Cargo.lock .vscode .DS_Store +.idea +/assets/proto diff --git a/assets/box.vtu b/assets/box.vtu index 4d2c46c..9a25ee0 100644 --- a/assets/box.vtu +++ b/assets/box.vtu @@ -1 +1 @@ -AAAAAAAAAAS/AAAAvwAAAD8AAAA/AAAAvwAAAL8AAAA/AAAAPwAAAA==AAAAAAAAAAQ+TMzNAAAAAD+AAAA+TMzNAAAAAD+AAAAAAAAAP4AAAD3MzM0AAAAAP4AAAD3MzM0+TMzNAAAAAD+AAAA+TMzNAAAAAD+AAAAAAAAAP4AAAD3MzM0AAAAAP4AAAD3MzM0=AAAAAAAAAAQAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQ==AAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=AAAAAAAAAAg/4KqqoAAAAL/gAAAAAAAAP+AAAAAAAAC/4KqqoAAAAL/gAAAAAAAAP+AAAAAAAAA/4KqqoAAAAD/gAAAAAAAAP+AAAAAAAAC/4KqqoAAAAD/gAAAAAAAAP+AAAAAAAAC/4KqqoAAAAL/gAAAAAAAAv+AAAAAAAAA/4KqqoAAAAL/gAAAAAAAAv+AAAAAAAAC/4KqqoAAAAD/gAAAAAAAAv+AAAAAAAAA/4KqqoAAAAD/gAAAAAAAAv+AAAAAAAAA=AAAAAAAAAAgAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAMAAAAAAAAAAgAAAAAAAAAEAAAAAAAAAAUAAAAAAAAABwAAAAAAAAAGAAAAAAAAAAYAAAAAAAAABwAAAAAAAAACAAAAAAAAAAMAAAAAAAAABQAAAAAAAAAEAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAHAAAAAAAAAAEAAAAAAAAABAAAAAAAAAAGAAAAAAAAAAM=AAAAAAAAAAgAAAAAAAAABAAAAAAAAAAIAAAAAAAAAAwAAAAAAAAAEAAAAAAAAAAUAAAAAAAAABg=AAAAAAAAAAEHBwcHBwc= \ No newline at end of file +AAAAAAAAACC/AAAAvwAAAD8AAAA/AAAAvwAAAL8AAAA/AAAAPwAAAA==AAAAAAAAAGA+TMzNAAAAAD+AAAA+TMzNAAAAAD+AAAAAAAAAP4AAAD3MzM0AAAAAP4AAAD3MzM0+TMzNAAAAAD+AAAA+TMzNAAAAAD+AAAAAAAAAP4AAAD3MzM0AAAAAP4AAAD3MzM0=AAAAAAAAACAAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQ==AAAAAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=AAAAAAAAAMA/4KqqoAAAAL/gAAAAAAAAP+AAAAAAAAC/4KqqoAAAAL/gAAAAAAAAP+AAAAAAAAA/4KqqoAAAAD/gAAAAAAAAP+AAAAAAAAC/4KqqoAAAAD/gAAAAAAAAP+AAAAAAAAC/4KqqoAAAAL/gAAAAAAAAv+AAAAAAAAA/4KqqoAAAAL/gAAAAAAAAv+AAAAAAAAC/4KqqoAAAAD/gAAAAAAAAv+AAAAAAAAA/4KqqoAAAAD/gAAAAAAAAv+AAAAAAAAA=AAAAAAAAAMAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAMAAAAAAAAAAgAAAAAAAAAEAAAAAAAAAAUAAAAAAAAABwAAAAAAAAAGAAAAAAAAAAYAAAAAAAAABwAAAAAAAAACAAAAAAAAAAMAAAAAAAAABQAAAAAAAAAEAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAHAAAAAAAAAAEAAAAAAAAABAAAAAAAAAAGAAAAAAAAAAM=AAAAAAAAADAAAAAAAAAABAAAAAAAAAAIAAAAAAAAAAwAAAAAAAAAEAAAAAAAAAAUAAAAAAAAABg=AAAAAAAAAAYHBwcHBwc= \ No newline at end of file diff --git a/assets/pygmsh/ascii.vtk b/assets/pygmsh/ascii.vtk new file mode 100644 index 0000000..c864984 --- /dev/null +++ b/assets/pygmsh/ascii.vtk @@ -0,0 +1,181 @@ +# vtk DataFile Version 5.1 +written by meshio v5.3.0 +ASCII +DATASET UNSTRUCTURED_GRID +POINTS 18 double +0.0 0.0 0.0 1.0 -0.2 0.0 1.1 1.2 0.0 0.1 0.7 0.0 0.3333333333325021 -0.06666666666650042 0.0 0.6666666666657866 -0.1333333333331573 0.0 1.0249999999999424 0.14999999999919245 0.0 1.0499999999998704 0.4999999999981836 0.0 1.074999999999934 0.8499999999990746 0.0 0.766666666667985 1.0333333333339925 0.0 0.433333333334733 0.8666666666673664 0.0 0.050000000000122564 0.3500000000008579 0.0 0.7444729167676052 0.3524793413776178 0.0 0.3781088913238718 0.4816987298113132 0.0 0.7412636346823331 0.6806963451979247 0.0 0.5070791452210437 0.16277273408010906 0.0 0.253704273975508 0.18556095944515594 0.0 0.7797139636550688 0.08823831456107314 0.0 +CELLS 39 94 +OFFSETS vtktypeint64 +0 +2 +4 +6 +8 +10 +12 +14 +16 +18 +20 +22 +24 +27 +30 +33 +36 +39 +42 +45 +48 +51 +54 +57 +60 +63 +66 +69 +72 +75 +78 +81 +84 +87 +90 +91 +92 +93 +94 +CONNECTIVITY vtktypeint64 +0 +4 +4 +5 +5 +1 +1 +6 +6 +7 +7 +8 +8 +2 +2 +9 +9 +10 +10 +3 +3 +11 +11 +0 +10 +13 +14 +13 +12 +14 +10 +3 +13 +8 +2 +9 +4 +5 +15 +9 +10 +14 +3 +11 +13 +6 +7 +12 +8 +9 +14 +7 +8 +14 +12 +7 +14 +15 +5 +17 +5 +1 +17 +1 +6 +17 +12 +13 +15 +13 +11 +16 +15 +13 +16 +11 +0 +16 +0 +4 +16 +6 +12 +17 +12 +15 +17 +4 +15 +16 +0 +1 +2 +3 +CELL_TYPES 38 +3 +3 +3 +3 +3 +3 +3 +3 +3 +3 +3 +3 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +1 +1 +1 +1 diff --git a/assets/pygmsh/ascii.vtu b/assets/pygmsh/ascii.vtu new file mode 100644 index 0000000..0d879a2 --- /dev/null +++ b/assets/pygmsh/ascii.vtu @@ -0,0 +1,248 @@ + + + + + + + +0.00000000000e+00 +0.00000000000e+00 +0.00000000000e+00 +1.00000000000e+00 +-2.00000000000e-01 +0.00000000000e+00 +1.10000000000e+00 +1.20000000000e+00 +0.00000000000e+00 +1.00000000000e-01 +7.00000000000e-01 +0.00000000000e+00 +3.33333333333e-01 +-6.66666666665e-02 +0.00000000000e+00 +6.66666666666e-01 +-1.33333333333e-01 +0.00000000000e+00 +1.02500000000e+00 +1.49999999999e-01 +0.00000000000e+00 +1.05000000000e+00 +4.99999999998e-01 +0.00000000000e+00 +1.07500000000e+00 +8.49999999999e-01 +0.00000000000e+00 +7.66666666668e-01 +1.03333333333e+00 +0.00000000000e+00 +4.33333333335e-01 +8.66666666667e-01 +0.00000000000e+00 +5.00000000001e-02 +3.50000000001e-01 +0.00000000000e+00 +7.44472916768e-01 +3.52479341378e-01 +0.00000000000e+00 +3.78108891324e-01 +4.81698729811e-01 +0.00000000000e+00 +7.41263634682e-01 +6.80696345198e-01 +0.00000000000e+00 +5.07079145221e-01 +1.62772734080e-01 +0.00000000000e+00 +2.53704273976e-01 +1.85560959445e-01 +0.00000000000e+00 +7.79713963655e-01 +8.82383145611e-02 +0.00000000000e+00 + + + + + +0 +4 +4 +5 +5 +1 +1 +6 +6 +7 +7 +8 +8 +2 +2 +9 +9 +10 +10 +3 +3 +11 +11 +0 +10 +13 +14 +13 +12 +14 +10 +3 +13 +8 +2 +9 +4 +5 +15 +9 +10 +14 +3 +11 +13 +6 +7 +12 +8 +9 +14 +7 +8 +14 +12 +7 +14 +15 +5 +17 +5 +1 +17 +1 +6 +17 +12 +13 +15 +13 +11 +16 +15 +13 +16 +11 +0 +16 +0 +4 +16 +6 +12 +17 +12 +15 +17 +4 +15 +16 +0 +1 +2 +3 + + + +2 +4 +6 +8 +10 +12 +14 +16 +18 +20 +22 +24 +27 +30 +33 +36 +39 +42 +45 +48 +51 +54 +57 +60 +63 +66 +69 +72 +75 +78 +81 +84 +87 +90 +91 +92 +93 +94 + + + +3 +3 +3 +3 +3 +3 +3 +3 +3 +3 +3 +3 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +1 +1 +1 +1 + + + + + + diff --git a/assets/pygmsh/binary.vtk b/assets/pygmsh/binary.vtk new file mode 100644 index 0000000..a4151b9 Binary files /dev/null and b/assets/pygmsh/binary.vtk differ diff --git a/assets/pygmsh/gen.py b/assets/pygmsh/gen.py new file mode 100644 index 0000000..757ea34 --- /dev/null +++ b/assets/pygmsh/gen.py @@ -0,0 +1,25 @@ +import pygmsh + +with pygmsh.geo.Geometry() as geom: + p = geom.add_polygon( + [ + [0.0, 0.0], + [1.0, -0.2], + [1.1, 1.2], + [0.1, 0.7], + ], + mesh_size=0.4, + ) + geom.add_physical(p.lines[0], label="bottom") + geom.add_physical(p.lines[1], label="right") + geom.add_physical(p.lines[2], label="top") + geom.add_physical(p.lines[3], label="left") + + mesh = geom.generate_mesh() + +mesh.write("no-compression.vtu", compression=None) +mesh.write("lzma.vtu", compression="lzma") +mesh.write("zlib.vtu", compression="zlib") +mesh.write("ascii.vtu", binary=False) +mesh.write("binary.vtk") +mesh.write("ascii.vtk", binary=False) diff --git a/assets/pygmsh/lzma.vtu b/assets/pygmsh/lzma.vtu new file mode 100644 index 0000000..c7327e8 --- /dev/null +++ b/assets/pygmsh/lzma.vtu @@ -0,0 +1,24 @@ + + + + + + + +AQAAAACAAACwAQAALAEAAA==/Td6WFoAAATm1rRGAgAhARYAAAB0L+Wj4AGvAOldAABuFTfD5hQGEWOiYOj3Slyvfg1L/Z3iyRycR0v7+mOn0ba3Qa6+uxHTkBa13rJ9UW0iFhj//utywp06/cuuBp7r/6RF/0kQoQF1wJmUOfjtXDwO7oyM72B1k1bOtSrX7Q0EP0RdrG6mYpB8fY3uRXcPLRuV7F4NlhoBsuNcKUphVnWEjbr0xN4z91QfOD8gJGvTgxj+c6oSBQZKgdtigQALByl5tf4xMtymx21LFrFg6zcv9wiCzWeolkrsvIzWHJ/1DV9+aqDd8NNsMoPJ8LbmXclBx+HI8VRh4JKcNhXg+zXNCDzMd6RhAAAAAPixvspGiUmFAAGFArADAAAV8tzoscRn+wIAAAAABFla + + + + +AQAAAACAAADwAgAAsAAAAA==/Td6WFoAAATm1rRGAgAhARYAAAB0L+Wj4ALvAG5dAABqf4CGWT3j9Uqwil2OUtsCHzHFFrl+aqabptUzL9SOe39SAvCK7iPawz8fvDCpSHkBzp1bECAXygDmyLZ4X/nMqID8Eqbc4azCjU2G0AEMSPklcaC2HvsDEIGliAwrSJ1nUyGCUcs8KhCW/QesAAAAi+NSyjXe5WYAAYoB8AUAAJ8es8GxxGf7AgAAAAAEWVo= + + +AQAAAACAAAAwAQAAgAAAAA==/Td6WFoAAATm1rRGAgAhARYAAAB0L+Wj4AEvAD9dAAEAOcG2vwm8gB0fh9y6FkceUbXO4HhwUvGjyt0gZdVKBz8EkkpMcf8iDB+6wFXI0nHkq4NBKD80x7XEniAEygAAlCvKhF26m8oAAVuwAgAAAHbs0h+xxGf7AgAAAAAEWVo= + + +AQAAAACAAAAwAQAAUAAAAA==/Td6WFoAAATm1rRGAgAhARYAAAB0L+Wj4AEvAA9dAAGAOfWXBWQaoCCI8wHgAAAA85sVNsk6+VAAASuwAgAAALfh8BSxxGf7AgAAAAAEWVo= + + + + + diff --git a/assets/pygmsh/no-compression.vtu b/assets/pygmsh/no-compression.vtu new file mode 100644 index 0000000..a168c8c --- /dev/null +++ b/assets/pygmsh/no-compression.vtu @@ -0,0 +1,24 @@ + + + + + + + +sAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPA/mpmZmZmZyb8AAAAAAAAAAJqZmZmZmfE/MzMzMzMz8z8AAAAAAAAAAJqZmZmZmbk/ZmZmZmZm5j8AAAAAAAAAANcaVVVVVdU/RuIQERERsb8AAAAAAAAAAF42VVVVVeU/S/gQERERwb8AAAAAAAAAAGNlZmZmZvA/jMEyMzMzwz8AAAAAAAAAAIXKzMzMzPA/L4D/////3z8AAAAAAAAAAAoyMzMzM/E/pBIzMzMz6z8AAAAAAAAAAOu2iIiIiOg/IZSIiIiI8D8AAAAAAAAAADoevLu7u9s/WtS7u7u76z8AAAAAAAAAAJnemZmZmak/xaJmZmZm1j8AAAAAAAAAAHrJyN240uc/FuHuggWP1j8AAAAAAAAAALIFpKLvMtg//R3E6CbU3j8AAAAAAAAAAD6XlYNuuOc/0Deks0PI5T8AAAAAAAAAABSoJgv+OeA/QPzGqLzVxD8AAAAAAAAAAIAi5dmwPNA/IJwdJnbAxz8AAAAAAAAAAPlBxLJq8+g/mkFLQ8mWtj8AAAAAAAAAAA== + + + + +8AIAAAAAAAAAAAAABAAAAAAAAAAEAAAAAAAAAAUAAAAAAAAABQAAAAAAAAABAAAAAAAAAAEAAAAAAAAABgAAAAAAAAAGAAAAAAAAAAcAAAAAAAAABwAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAAgAAAAAAAAACAAAAAAAAAAkAAAAAAAAACQAAAAAAAAAKAAAAAAAAAAoAAAAAAAAAAwAAAAAAAAADAAAAAAAAAAsAAAAAAAAACwAAAAAAAAAAAAAAAAAAAAoAAAAAAAAADQAAAAAAAAAOAAAAAAAAAA0AAAAAAAAADAAAAAAAAAAOAAAAAAAAAAoAAAAAAAAAAwAAAAAAAAANAAAAAAAAAAgAAAAAAAAAAgAAAAAAAAAJAAAAAAAAAAQAAAAAAAAABQAAAAAAAAAPAAAAAAAAAAkAAAAAAAAACgAAAAAAAAAOAAAAAAAAAAMAAAAAAAAACwAAAAAAAAANAAAAAAAAAAYAAAAAAAAABwAAAAAAAAAMAAAAAAAAAAgAAAAAAAAACQAAAAAAAAAOAAAAAAAAAAcAAAAAAAAACAAAAAAAAAAOAAAAAAAAAAwAAAAAAAAABwAAAAAAAAAOAAAAAAAAAA8AAAAAAAAABQAAAAAAAAARAAAAAAAAAAUAAAAAAAAAAQAAAAAAAAARAAAAAAAAAAEAAAAAAAAABgAAAAAAAAARAAAAAAAAAAwAAAAAAAAADQAAAAAAAAAPAAAAAAAAAA0AAAAAAAAACwAAAAAAAAAQAAAAAAAAAA8AAAAAAAAADQAAAAAAAAAQAAAAAAAAAAsAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAQAAAAAAAAAAYAAAAAAAAADAAAAAAAAAARAAAAAAAAAAwAAAAAAAAADwAAAAAAAAARAAAAAAAAAAQAAAAAAAAADwAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAACAAAAAAAAAAMAAAAAAAAA + + +MAEAAAIAAAAAAAAABAAAAAAAAAAGAAAAAAAAAAgAAAAAAAAACgAAAAAAAAAMAAAAAAAAAA4AAAAAAAAAEAAAAAAAAAASAAAAAAAAABQAAAAAAAAAFgAAAAAAAAAYAAAAAAAAABsAAAAAAAAAHgAAAAAAAAAhAAAAAAAAACQAAAAAAAAAJwAAAAAAAAAqAAAAAAAAAC0AAAAAAAAAMAAAAAAAAAAzAAAAAAAAADYAAAAAAAAAOQAAAAAAAAA8AAAAAAAAAD8AAAAAAAAAQgAAAAAAAABFAAAAAAAAAEgAAAAAAAAASwAAAAAAAABOAAAAAAAAAFEAAAAAAAAAVAAAAAAAAABXAAAAAAAAAFoAAAAAAAAAWwAAAAAAAABcAAAAAAAAAF0AAAAAAAAAXgAAAAAAAAA= + + +MAEAAAMAAAAAAAAAAwAAAAAAAAADAAAAAAAAAAMAAAAAAAAAAwAAAAAAAAADAAAAAAAAAAMAAAAAAAAAAwAAAAAAAAADAAAAAAAAAAMAAAAAAAAAAwAAAAAAAAADAAAAAAAAAAUAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAUAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAUAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAUAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAUAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAUAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAUAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAUAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAA= + + + + + diff --git a/assets/pygmsh/zlib.vtu b/assets/pygmsh/zlib.vtu new file mode 100644 index 0000000..d1f6794 --- /dev/null +++ b/assets/pygmsh/zlib.vtu @@ -0,0 +1,24 @@ + + + + + + + +AQAAAACAAACwAQAABQEAAA==eJxjYMAHPtjPmgkCJ/fDRCD8j/bGYPDZHlV8p30aGDyDi1+XCgWCq/ZujwQEBQU3ws2JMwOJP7X3/gESPwgXT04F6f9g33PQCGj+Ybg5rafOAMEHe/2G/0BwHy7OBVJm/NF+iRCIfg0Xf72tAwhe2CtOAdEf4OJWcnt279592z7qCpDajVA/8x7I/Svtjy4C2X8NLl518sTdHZee24s9fNfE2o8Q38S6ZNF7oxv2f2WPvFC7cg8ubjd9anPejuf2F8yXbHY+8RQuLrJCjfuf5QN7hz/HVuy5egQu3qD09OYGmwv2CnNk1coOHIeL/3Q8sinr8wv7WY7ezienbYOLAwDOhYjK + + + + +AQAAAACAAADwAgAAfAAAAA==eJx1kUEOgCAMBEFRUBHw/5/1oHOZhF4mXZploSF8lcRNjOIuZrGIi3iIp7iKl0gxf/9s6qt0+zPnnOTyf/RJbvydF3//E7m4F7+muSK96hydXOR81Efp3iM6/uTu6nnXmJwPzQXpVJJODu53ni49Sbc/72Of7OUFXXQDPw== + + +AQAAAACAAAAwAQAAWAAAAA==eJwtxdEGgwAAAMDJJMlkMhEjRsQYI8YYMSJGRKza/3/GHrp7uWC32Tt05NiJD059dOaTcxc+u/TFlWtfffPdjR9++uXWb3fu/fHg0ZO/nr149c9/z+4HFg== + + +AQAAAACAAAAwAQAAFQAAAA==eJxjZoAAZhrRrEOMZiRAAwBbWACX + + + + + diff --git a/src/lib.rs b/src/lib.rs index 4a24d83..08152ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,17 +47,17 @@ //! println!("{}", output); //! } //! ``` -//! +//! //! To quickly extract some data from a file, you can cast it to an `f64` type as follows -//! +//! //! ```no_run //! use vtkio::model::*; // import model definition of a VTK file -//! +//! //! // Load up vtk file. //! let file_path = "../assets/para_tet.vtk"; //! let mut vtk = Vtk::import(&file_path) //! .expect(&format!("Failed to load file: {:?}", file_path)); -//! +//! //! // Get all the pieces knowing that type they are. //! let pieces = if let DataSet::UnstructuredGrid { pieces, .. } = vtk.data { //! pieces @@ -68,7 +68,7 @@ //! // Often files have only a single piece, so we load it up here. //! // To avoid cloning you can also use `into_loaded_piece_data` here. //! let piece = pieces[0].load_piece_data(None).unwrap(); -//! +//! //! // Get the first cell attribute. //! let attribute = &piece.data.cell[0]; //! @@ -86,9 +86,9 @@ //! } else { //! panic!("First attribute is not a field"); //! }; -//! +//! //! assert_eq!(data.as_slice(), &[0.0]); -//! ``` +//! ``` #[macro_use] extern crate nom; @@ -351,7 +351,7 @@ impl Vtk { /// use vtkio::model::*; // import the model definition of a VTK file /// /// let input: &[u8] = b"\ - /// \ + /// \ /// \ /// \ /// \ diff --git a/src/model.rs b/src/model.rs index 825fe49..373e805 100644 --- a/src/model.rs +++ b/src/model.rs @@ -1346,14 +1346,14 @@ impl Attributes { /// /// This struct compiles a list of point indices that make up each cell. /// -/// # Legacy +/// # Contiguous /// /// In legacy format, cell vertex numbers are listed with a preceeding number of points per cell. /// In other words, each cell's point list is given by a number of points in the cell followed by /// the individual point numbers. /// This struct could represent one of VERTICES, LINES, POLYGONS, TRIANGLE_STRIPS or CELLS. /// -/// # XML +/// # Offsets /// /// In XML format, the cell vertex numbers listed as a contiguous array, so to distinguish between /// different cells, a secondary array of offsets is given to indicate the ends of each cell as an @@ -1361,16 +1361,24 @@ impl Attributes { /// `Verts`, `Lines`, `Strips` or `Polys`. #[derive(Clone, PartialEq, Debug)] pub enum VertexNumbers { + /// Specifies the vertex numbers for cells using a contiguous array of cell sizes and vertex + /// indices. Legacy { /// Total number of cells contained in the `vertices` vector. num_cells: u32, /// Each cell in `vertices` is of the form: `n i_1 ... i_n`. vertices: Vec, }, + /// Specifies the vertex numbers for cells using an array of offsets into a connectivity array + /// giving the actual vertex indices. XML { /// A contiguous array of all of the cells' point lists concatenated together. connectivity: Vec, /// The offsets into the connectivity array indicating the end of each cell. + /// + /// This array may or may not have a leading zero. This depends on the input VTK file. + /// Some file (in particular Legacy type VTK files version 5 and higher) include a leading zero, + /// however, XML files typically do not. offsets: Vec, }, } diff --git a/src/parser.rs b/src/parser.rs index de98770..4356ee1 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -112,6 +112,66 @@ impl VtkParser { ) } + /// Parse cell topology indices in the legacy way. + /// + /// Cells are stored as a single contiguous array with the format `n v0 v1 ... vn` for each cell. + fn legacy_cell_topo<'a>( + input: &'a [u8], + n: u32, + size: u32, + ft: FileType, + ) -> IResult<&'a [u8], VertexNumbers> { + do_parse!( + input, + data: call!(parse_data_vec::, size as usize, ft) + >> ({ + VertexNumbers::Legacy { + num_cells: n, + vertices: data, + } + }) + ) + } + + /// Parse cell topology indices in modern way using offsets and connectivity arras. + /// + /// Cells are stored as two arrays: OFFSETS and CONNECTIVITY, which are specified separately. + fn modern_cell_topo<'a>( + input: &'a [u8], + n: u32, + size: u32, + ft: FileType, + ) -> IResult<&'a [u8], VertexNumbers> { + complete!( + input, + do_parse!( + offsets: call!(Self::topo, "OFFSETS", n, ft) + >> connectivity: call!(Self::topo, "CONNECTIVITY", size, ft) + >> (VertexNumbers::XML { + offsets, + connectivity, + }) + ) + ) + } + + /// Parse either a CONNECTIVITY or OFFSETS array. + fn topo<'a>( + input: &'a [u8], + tag: &'static str, + n: u32, + ft: FileType, + ) -> IResult<&'a [u8], Vec> { + do_parse!( + input, + ws!(tag_no_case!(tag)) + >> take_until!("\n") // Skip data type, parse everything as u64 + >> tag!("\n") + >> data: call!(parse_data_vec::, n as usize, ft) + >> (data) + ) + } + /// Parse a collection of cells. The given tag should be one of /// * "CELLS" /// * "VERTICES" @@ -128,13 +188,12 @@ impl VtkParser { n: ws!(do_parse!(tag_no_case!(tag) >> n: u32_b >> (n))) >> size: sp!(u32_b) >> tag!("\n") - >> data: call!(parse_data_vec::, size as usize, ft) - >> ({ - VertexNumbers::Legacy { - num_cells: n, - vertices: data, - } - }) + >> vertex_numbers: + alt!( + call!(Self::modern_cell_topo, n, size, ft) + | call!(Self::legacy_cell_topo, n, size, ft) + ) + >> (vertex_numbers) ) } diff --git a/src/writer.rs b/src/writer.rs index 773a058..60cf0ff 100644 --- a/src/writer.rs +++ b/src/writer.rs @@ -1,6 +1,7 @@ use std::fmt::Arguments; use byteorder::{BigEndian, ByteOrder, LittleEndian}; +use num_traits::ToPrimitive; use crate::model::ByteOrder as ByteOrderTag; use crate::model::*; @@ -12,6 +13,8 @@ pub struct AsciiWriter(pub W); pub struct BinaryWriter(pub W); mod write_vtk_impl { + use std::fmt::Display; + use super::*; use byteorder::WriteBytesExt; @@ -239,7 +242,10 @@ mod write_vtk_impl { fn write_fmt(&mut self, args: Arguments) -> Result; fn write_file_type(&mut self) -> Result; fn write_cell_types(&mut self, data: Vec) -> Result; - fn write_u32_vec(&mut self, data: Vec) -> Result; + fn write_vec( + &mut self, + data: Vec, + ) -> Result; fn write_buf(&mut self, data: IOBuffer) -> Result; fn write_attributes( @@ -522,7 +528,7 @@ mod write_vtk_impl { let (_, vertices) = cell_verts.into_legacy(); - self.write_u32_vec::(vertices).map_err(|e| { + self.write_vec::(vertices).map_err(|e| { Error::DataSet(DataSetError::PolyData(DataSetPart::Cells( EntryPart::Data(e.into()), ))) @@ -576,23 +582,50 @@ mod write_vtk_impl { })?; let num_cells = cells.cell_verts.num_cells(); - let num_verts = cells.cell_verts.num_verts(); - writeln!(self, "\nCELLS {} {}", num_cells, num_cells + num_verts).map_err( - |_| { + // Write CELLS structure. + if vtk.version.major >= 5 { + // From version 5 and on the cells are written as an offsets and connectivity pair. + let (connectivity, offsets) = cells.cell_verts.into_xml(); + + writeln!(self, "\nCELLS {} {}", offsets.len(), connectivity.len()) + .map_err(|_| { + Error::DataSet(DataSetError::UnstructuredGrid( + DataSetPart::Cells(EntryPart::Header), + )) + })?; + + writeln!(self, "\nOFFSETS vtktypeint64")?; + self.write_vec::<_, BO>(offsets).map_err(|e| { Error::DataSet(DataSetError::UnstructuredGrid(DataSetPart::Cells( - EntryPart::Header, + EntryPart::Data(e.into()), ))) - }, - )?; + })?; - let (_, vertices) = cells.cell_verts.into_legacy(); + writeln!(self, "\nCONNECTIVITY vtktypeint64")?; + self.write_vec::<_, BO>(connectivity).map_err(|e| { + Error::DataSet(DataSetError::UnstructuredGrid(DataSetPart::Cells( + EntryPart::Data(e.into()), + ))) + })?; + } else { + let num_verts = cells.cell_verts.num_verts(); - self.write_u32_vec::(vertices).map_err(|e| { - Error::DataSet(DataSetError::UnstructuredGrid(DataSetPart::Cells( - EntryPart::Data(e.into()), - ))) - })?; + writeln!(self, "\nCELLS {} {}", num_cells, num_cells + num_verts) + .map_err(|_| { + Error::DataSet(DataSetError::UnstructuredGrid( + DataSetPart::Cells(EntryPart::Header), + )) + })?; + + let (_, vertices) = cells.cell_verts.into_legacy(); + + self.write_vec::(vertices).map_err(|e| { + Error::DataSet(DataSetError::UnstructuredGrid(DataSetPart::Cells( + EntryPart::Data(e.into()), + ))) + })?; + } writeln!(self, "\nCELL_TYPES {}", cells.types.len()).map_err(|_| { Error::DataSet(DataSetError::UnstructuredGrid(DataSetPart::CellTypes( @@ -809,7 +842,10 @@ mod write_vtk_impl { } writeln!(&mut self.0).map_err(|_| Error::NewLine) } - fn write_u32_vec(&mut self, data: Vec) -> Result { + fn write_vec( + &mut self, + data: Vec, + ) -> Result { let buf = IOBuffer::from(data); self.write_buf::(buf) } @@ -870,8 +906,11 @@ mod write_vtk_impl { fn write_cell_types(&mut self, data: Vec) -> Result { BinaryWriter(self).write_cell_types::(data) } - fn write_u32_vec(&mut self, data: Vec) -> Result { - BinaryWriter(self).write_u32_vec::(data) + fn write_vec( + &mut self, + data: Vec, + ) -> Result { + BinaryWriter(self).write_vec::(data) } fn write_buf(&mut self, buf: IOBuffer) -> Result { BinaryWriter(self).write_buf::(buf) @@ -897,7 +936,10 @@ mod write_vtk_impl { writeln!(&mut self.0).map_err(|_| err)?; Ok(()) } - fn write_u32_vec(&mut self, data: Vec) -> Result { + fn write_vec( + &mut self, + data: Vec, + ) -> Result { for i in 0..data.len() { write!(&mut self.0, "{}", data[i])?; if i < data.len() - 1 { @@ -925,8 +967,11 @@ mod write_vtk_impl { fn write_cell_types(&mut self, data: Vec) -> Result { AsciiWriter(self).write_cell_types::(data) } - fn write_u32_vec(&mut self, data: Vec) -> Result { - AsciiWriter(self).write_u32_vec::(data) + fn write_vec( + &mut self, + data: Vec, + ) -> Result { + AsciiWriter(self).write_vec::(data) } fn write_buf(&mut self, buf: IOBuffer) -> Result { AsciiWriter(self).write_buf::(buf) diff --git a/src/xml.rs b/src/xml.rs index dd8b762..09c8726 100644 --- a/src/xml.rs +++ b/src/xml.rs @@ -1529,6 +1529,23 @@ impl Cells { } } + /// Decodes a data array for types. + /// + /// These can be specified as u8 or some other integer type. + /// This logic is encapsulated in this function. + fn get_type_codes( + buf: model::IOBuffer, + ) -> std::result::Result, ValidationError> { + use num_traits::FromPrimitive; + let type_codes = buf + .cast_into::() + .ok_or_else(|| ValidationError::InvalidDataFormat)?; + type_codes + .into_iter() + .map(|x| model::CellType::from_u8(x).ok_or_else(|| ValidationError::InvalidCellType(x))) + .collect() + } + /// Given the expected number of elements and an optional appended data, /// converts this `Topo` struct into a `mode::VertexNumbers` type. pub fn into_model_cells( @@ -1537,15 +1554,7 @@ impl Cells { appended: Option<&AppendedData>, ei: EncodingInfo, ) -> std::result::Result { - use num_traits::FromPrimitive; - - let type_codes: Option> = self.types.into_io_buffer(l, appended, ei)?.into(); - let type_codes = type_codes.ok_or_else(|| ValidationError::InvalidDataFormat)?; - let types: std::result::Result, ValidationError> = type_codes - .into_iter() - .map(|x| model::CellType::from_u8(x).ok_or_else(|| ValidationError::InvalidCellType(x))) - .collect(); - let types = types?; + let types = Self::get_type_codes(self.types.into_io_buffer(l, appended, ei)?)?; let offsets: Option> = self.offsets.into_io_buffer(l, appended, ei)?.cast_into(); let offsets = offsets.ok_or_else(|| ValidationError::InvalidDataFormat)?; @@ -1912,6 +1921,49 @@ impl DataArray { DataArray { num_comp, ..self } } + /// Helper to extract possibly compressed binary data from a `String` in `IOBuffer` format. + fn extract_data( + ei: EncodingInfo, + scalar_type: ScalarType, + data: Data, + ) -> std::result::Result { + use model::IOBuffer; + + let header_bytes = ei.header_type.size(); + // Binary data in a data array (i.e. not in appended data) is always base64 encoded. + // It can also be compressed. + if matches!(ei.compressor, Compressor::None) { + // First byte gives the bytes + let bytes = base64::decode(data.into_string())?; + // eprintln!("{:?}", &bytes[..header_bytes]); + return Ok(IOBuffer::from_bytes( + &bytes[header_bytes..], + scalar_type.into(), + ei.byte_order, + )?); + } + + // Temporary buffer used for decoding compressed types. + let mut buf = Vec::new(); + + let data_string = data.into_string(); + let encoded_data = data_string.as_bytes(); + let bytes = decode_and_decompress( + &mut buf, + base64_decode_buf, + to_b64, + encoded_data, + header_bytes, + ei, + )?; + + Ok(IOBuffer::from_bytes( + bytes.as_slice(), + scalar_type.into(), + ei.byte_order, + )?) + } + /// Convert this data array into a `model::FieldArray` type. /// /// The given arguments are the number of elements (not bytes) in the expected output @@ -1937,7 +1989,6 @@ impl DataArray { //eprintln!("name = {:?}", &name); let num_elements = usize::try_from(num_comp).unwrap() * l; - let header_bytes = ei.header_type.size(); let data = match format { DataArrayFormat::Appended => { @@ -1957,14 +2008,7 @@ impl DataArray { } } DataArrayFormat::Binary => { - // First byte gives the bytes - let bytes = base64::decode(data[0].clone().into_string())?; - //eprintln!("{:?}", &bytes[..header_bytes]); - let buf = IOBuffer::from_bytes( - &bytes[header_bytes..], - scalar_type.into(), - ei.byte_order, - )?; + let buf = Self::extract_data(ei, scalar_type, data.into_iter().next().unwrap())?; if buf.len() != num_elements { return Err(ValidationError::DataArraySizeMismatch { name, @@ -2200,6 +2244,151 @@ pub enum Encoding { Raw, } +/// Customized base64::decode function that accepts a buffer. +fn base64_decode_buf<'a>( + input: &[u8], + buf: &'a mut Vec, +) -> std::result::Result<&'a [u8], ValidationError> { + base64::decode_config_buf( + input, + base64::STANDARD.decode_allow_trailing_bits(true), + buf, + )?; + Ok(buf.as_slice()) +} + +/// Converts the number of target bytes to number of chars in base64 encoding. +fn to_b64(bytes: usize) -> usize { + 4 * (bytes as f64 / 3.0).ceil() as usize + //(bytes * 4 + 1) / 3 + match bytes % 3 { + // 1 => 2, 2 => 1, _ => 0 + //} +} + +// Helper function to read a single header number, which depends on the encoding parameters. +fn read_header_num>( + header_buf: &mut std::io::Cursor, + ei: EncodingInfo, +) -> std::result::Result { + use byteorder::ReadBytesExt; + use byteorder::{BE, LE}; + Ok(match ei.byte_order { + model::ByteOrder::LittleEndian => { + if ei.header_type == ScalarType::UInt64 { + header_buf.read_u64::()? as usize + } else { + header_buf.read_u32::()? as usize + } + } + model::ByteOrder::BigEndian => { + if ei.header_type == ScalarType::UInt64 { + header_buf.read_u64::()? as usize + } else { + header_buf.read_u32::()? as usize + } + } + }) +} + +/// Returns an allocated decompressed Vec of bytes. +// Allow this warning which are fired when compression is disabled. +#[allow(unused_variables)] +fn decode_and_decompress<'a, D, B>( + buf: &'a mut Vec, + mut decode: D, + mut to_b64: B, + data: &'a [u8], + header_bytes: usize, + ei: EncodingInfo, +) -> std::result::Result, ValidationError> +where + D: for<'b> FnMut(&'b [u8], &'b mut Vec) -> std::result::Result<&'b [u8], ValidationError>, + B: FnMut(usize) -> usize, +{ + use std::io::Cursor; + + // Compressed data has a more complex header. + // The data is organized as [nb][nu][np][nc_1]...[nc_nb][Data] + // Where + // [nb] = Number of blocks in the data array + // [nu] = Block size before compression + // [np] = Size of the last partial block before compression (zero if it is not needed) + // [nc_i] = Size in bytes of block i after compression + // See https://vtk.org/Wiki/VTK_XML_Formats for details. + // In this case we don't know how many bytes are in the data array so we must first read + // this information from a header. + + // First we need to determine the number of blocks stored. + let num_blocks = { + let encoded_header = &data[0..to_b64(header_bytes)]; + let decoded_header = decode(encoded_header, buf)?; + read_header_num(&mut Cursor::new(decoded_header), ei)? + }; + + let full_header_bytes = header_bytes * (3 + num_blocks); // nb + nu + np + sum_i nc_i + buf.clear(); + + let encoded_header = &data[0..to_b64(full_header_bytes)]; + let decoded_header = decode(encoded_header, buf)?; + let mut header_cursor = Cursor::new(decoded_header); + let _nb = read_header_num(&mut header_cursor, ei); // We already know the number of blocks + let _nu = read_header_num(&mut header_cursor, ei); + let _np = read_header_num(&mut header_cursor, ei); + let nc_total = (0..num_blocks).fold(0, |acc, _| { + acc + read_header_num(&mut header_cursor, ei).unwrap_or(0) + }); + let num_data_bytes = to_b64(nc_total); + let start = to_b64(full_header_bytes); + buf.clear(); + let encoded_data = &data[start..start + num_data_bytes]; + let decoded_data = decode(encoded_data, buf)?; + + // Now that the data is decoded, what is left is to decompress it. + match ei.compressor { + Compressor::ZLib => { + #[cfg(not(feature = "flate2"))] + { + return Err(ValidationError::MissingCompressionLibrary(ei.compressor)); + } + #[cfg(feature = "flate2")] + { + use std::io::Read; + let mut out = Vec::new(); + let mut decoder = flate2::read::ZlibDecoder::new(decoded_data); + decoder.read_to_end(&mut out)?; + Ok(out) + } + } + Compressor::LZ4 => { + #[cfg(not(feature = "lz4"))] + { + return Err(ValidationError::MissingCompressionLibrary(ei.compressor)); + } + #[cfg(feature = "lz4")] + { + Ok(lz4::decompress(decoded_data, num_data_bytes)?) + } + } + Compressor::LZMA => { + #[cfg(not(feature = "xz2"))] + { + return Err(ValidationError::MissingCompressionLibrary(ei.compressor)); + } + #[cfg(feature = "xz2")] + { + use std::io::Read; + let mut out = Vec::new(); + let mut decoder = xz2::read::XzDecoder::new(decoded_data); + decoder.read_to_end(&mut out)?; + Ok(out) + } + } + _ => { + unreachable!() + } + } +} + impl AppendedData { /// Extract the decompressed and unencoded raw bytes from appended data. /// @@ -2214,14 +2403,6 @@ impl AppendedData { scalar_type: ScalarType, ei: EncodingInfo, ) -> std::result::Result { - // Convert number of target bytes to number of chars in base64 encoding. - fn to_b64(bytes: usize) -> usize { - 4 * (bytes as f64 / 3.0).ceil() as usize - //(bytes * 4 + 1) / 3 + match bytes % 3 { - // 1 => 2, 2 => 1, _ => 0 - //} - } - let header_bytes = ei.header_type.size(); let expected_num_bytes = num_elements * scalar_type.size(); let mut start = offset; @@ -2266,136 +2447,10 @@ impl AppendedData { }; } - // Compressed data has a more complex header. - // The data is organized as [nb][nu][np][nc_1]...[nc_nb][Data] - // Where - // [nb] = Number of blocks in the data array - // [nu] = Block size before compression - // [np] = Size of the last partial block before compression (zero if it is not needed) - // [nc_i] = Size in bytes of block i after compression - // See https://vtk.org/Wiki/VTK_XML_Formats for details. - // In this case we dont know how many bytes are in the data array so we must first read - // this information from a header. - - // Helper function to read a single header number, which depends on the encoding parameters. - fn read_header_num>( - header_buf: &mut std::io::Cursor, - ei: EncodingInfo, - ) -> std::result::Result { - use byteorder::ReadBytesExt; - use byteorder::{BE, LE}; - Ok(match ei.byte_order { - model::ByteOrder::LittleEndian => { - if ei.header_type == ScalarType::UInt64 { - header_buf.read_u64::()? as usize - } else { - header_buf.read_u32::()? as usize - } - } - model::ByteOrder::BigEndian => { - if ei.header_type == ScalarType::UInt64 { - header_buf.read_u64::()? as usize - } else { - header_buf.read_u32::()? as usize - } - } - }) - } - - // Allow this warning which are fired when compression is disabled. - #[allow(unused_variables)] - fn get_data_slice<'a, D, B>( - buf: &'a mut Vec, - mut decode: D, - mut to_b64: B, - data: &'a [u8], - header_bytes: usize, - ei: EncodingInfo, - ) -> std::result::Result, ValidationError> - where - D: for<'b> FnMut( - &'b [u8], - &'b mut Vec, - ) -> std::result::Result<&'b [u8], ValidationError>, - B: FnMut(usize) -> usize, - { - use std::io::Cursor; - - // First we need to determine the number of blocks stored. - let num_blocks = { - let encoded_header = &data[0..to_b64(header_bytes)]; - let decoded_header = decode(encoded_header, buf)?; - read_header_num(&mut Cursor::new(decoded_header), ei)? - }; - - let full_header_bytes = header_bytes * (3 + num_blocks); // nb + nu + np + sum_i nc_i - buf.clear(); - - let encoded_header = &data[0..to_b64(full_header_bytes)]; - let decoded_header = decode(encoded_header, buf)?; - let mut header_cursor = Cursor::new(decoded_header); - let _nb = read_header_num(&mut header_cursor, ei); // We already know the number of blocks - let _nu = read_header_num(&mut header_cursor, ei); - let _np = read_header_num(&mut header_cursor, ei); - let nc_total = (0..num_blocks).fold(0, |acc, _| { - acc + read_header_num(&mut header_cursor, ei).unwrap_or(0) - }); - let num_data_bytes = to_b64(nc_total); - let start = to_b64(full_header_bytes); - buf.clear(); - let encoded_data = &data[start..start + num_data_bytes]; - let decoded_data = decode(encoded_data, buf)?; - - // Now that the data is decoded, what is left is to decompress it. - match ei.compressor { - Compressor::ZLib => { - #[cfg(not(feature = "flate2"))] - { - return Err(ValidationError::MissingCompressionLibrary(ei.compressor)); - } - #[cfg(feature = "flate2")] - { - use std::io::Read; - let mut out = Vec::new(); - let mut decoder = flate2::read::ZlibDecoder::new(decoded_data); - decoder.read_to_end(&mut out)?; - Ok(out) - } - } - Compressor::LZ4 => { - #[cfg(not(feature = "lz4"))] - { - return Err(ValidationError::MissingCompressionLibrary(ei.compressor)); - } - #[cfg(feature = "lz4")] - { - Ok(lz4::decompress(decoded_data, num_data_bytes)?) - } - } - Compressor::LZMA => { - #[cfg(not(feature = "xz2"))] - { - return Err(ValidationError::MissingCompressionLibrary(ei.compressor)); - } - #[cfg(feature = "xz2")] - { - use std::io::Read; - let mut out = Vec::new(); - let mut decoder = xz2::read::XzDecoder::new(decoded_data); - decoder.read_to_end(&mut out)?; - Ok(out) - } - } - _ => { - unreachable!() - } - } - } - let out = match self.encoding { Encoding::Raw => { let mut buf = Vec::new(); - get_data_slice( + decode_and_decompress( &mut buf, |header, _| Ok(header), |x| x, @@ -2406,16 +2461,9 @@ impl AppendedData { } Encoding::Base64 => { let mut buf = Vec::new(); - get_data_slice( + decode_and_decompress( &mut buf, - |header, buf| { - base64::decode_config_buf( - header, - base64::STANDARD.decode_allow_trailing_bits(true), - buf, - )?; - Ok(buf.as_slice()) - }, + base64_decode_buf, to_b64, &self.data.0[offset..], header_bytes, @@ -2759,7 +2807,7 @@ impl TryFrom for model::Vtk { let encoding_info = EncodingInfo { byte_order, - header_type: header_type.unwrap_or(ScalarType::UInt64), + header_type: header_type.unwrap_or(ScalarType::UInt32), compressor, compression_level: 0, // This is meaningless when decoding }; diff --git a/tests/pygmsh.rs b/tests/pygmsh.rs new file mode 100644 index 0000000..2b810d5 --- /dev/null +++ b/tests/pygmsh.rs @@ -0,0 +1,278 @@ +//! This test module tests against simple files generated by the pygmsh package. + +use nom::IResult; +use vtkio::model::*; +use vtkio::parser::*; +use vtkio::writer::*; +use vtkio::Error; + +macro_rules! test_b { + ($fn:ident ($in:expr, $($args:expr),*) => $out:expr) => { + assert_eq!($fn($in, $($args),*), IResult::Done("".as_bytes(), $out.clone())); + }; + ($fn:ident ($in:expr) => $out:expr) => { + assert_eq!($fn($in), IResult::Done("".as_bytes(), $out.clone())); + }; +} + +macro_rules! test_ignore_rem { + ($fn:ident ($in:expr, $($args:expr),*) => $out:expr) => { + { + let result = $fn($in, $($args),*); + assert!(result.is_done()); + assert_eq!(result.unwrap().1, $out.clone()); + } + }; + ($fn:ident ($in:expr) => $out:expr) => { + { + let result = $fn($in); + assert!(result.is_done()); + assert_eq!(result.unwrap().1, $out.clone()); + } + }; +} + +type Result = std::result::Result<(), Error>; + +// Helper functions to convert between endianness. + +fn ne(vtk: &Vtk) -> Vtk { + Vtk { + byte_order: ByteOrder::native(), + ..vtk.clone() + } +} + +fn le(vtk: &Vtk) -> Vtk { + Vtk { + byte_order: ByteOrder::LittleEndian, + ..vtk.clone() + } +} + +fn make_test_file(leading_zero_offset: bool) -> Vtk { + Vtk { + version: Version::new((5, 1)), + byte_order: ByteOrder::BigEndian, + title: String::from("written by meshio v5.3.0"), + file_path: None, + data: DataSet::inline(UnstructuredGridPiece { + points: vec![ + 0.0f64, + 0.0, + 0.0, + 1.0, + -0.2, + 0.0, + 1.1, + 1.2, + 0.0, + 0.1, + 0.7, + 0.0, + 0.3333333333325021, + -0.06666666666650042, + 0.0, + 0.6666666666657866, + -0.1333333333331573, + 0.0, + 1.0249999999999424, + 0.14999999999919245, + 0.0, + 1.0499999999998704, + 0.4999999999981836, + 0.0, + 1.074999999999934, + 0.8499999999990746, + 0.0, + 0.766666666667985, + 1.0333333333339925, + 0.0, + 0.433333333334733, + 0.8666666666673664, + 0.0, + 0.050000000000122564, + 0.3500000000008579, + 0.0, + 0.7444729167676052, + 0.3524793413776178, + 0.0, + 0.3781088913238718, + 0.4816987298113132, + 0.0, + 0.7412636346823331, + 0.6806963451979247, + 0.0, + 0.5070791452210437, + 0.16277273408010906, + 0.0, + 0.253704273975508, + 0.18556095944515594, + 0.0, + 0.7797139636550688, + 0.08823831456107314, + 0.0, + ] + .into(), + cells: Cells { + cell_verts: VertexNumbers::XML { + offsets: { + let mut offsets = vec![ + 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 27, 30, 33, 36, 39, 42, 45, + 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87, 90, 91, 92, 93, + 94, + ]; + if leading_zero_offset { + offsets.insert(0, 0); + } + offsets + }, + connectivity: vec![ + 0, 4, 4, 5, 5, 1, 1, 6, 6, 7, 7, 8, 8, 2, 2, 9, 9, 10, 10, 3, 3, 11, 11, 0, + 10, 13, 14, 13, 12, 14, 10, 3, 13, 8, 2, 9, 4, 5, 15, 9, 10, 14, 3, 11, 13, + 6, 7, 12, 8, 9, 14, 7, 8, 14, 12, 7, 14, 15, 5, 17, 5, 1, 17, 1, 6, 17, 12, + 13, 15, 13, 11, 16, 15, 13, 16, 11, 0, 16, 0, 4, 16, 6, 12, 17, 12, 15, 17, + 4, 15, 16, 0, 1, 2, 3, + ], + }, + types: vec![ + vec![CellType::Line; 12], + vec![CellType::Triangle; 22], + vec![CellType::Vertex; 4], + ] + .into_iter() + .flatten() + .collect::>(), + }, + data: Attributes { + point: vec![], + cell: vec![], + }, + }), + } +} + +#[test] +fn legacy_ascii() -> Result { + let input = include_str!("../assets/pygmsh/ascii.vtk").as_bytes(); + let out1 = make_test_file(true); + assert!(parse_be(input).is_done()); + test_ignore_rem!(parse_be(input) => out1); + let mut outtest = String::new(); + outtest.write_vtk_ne(out1.clone())?; + // println!("{}", outtest); + test_b!(parse_ne(String::new().write_vtk_ne(out1.clone())?.as_bytes()) => ne(&out1)); + test_b!(parse_ne(Vec::::new().write_vtk_ne(out1.clone())?) => ne(&out1)); + test_b!(parse_le(Vec::::new().write_vtk_le(out1.clone())?) => le(&out1)); + test_b!(parse_be(Vec::::new().write_vtk_be(out1.clone())?) => out1); + Ok(()) +} + +#[test] +fn legacy_binary() -> Result { + let input = include_bytes!("../assets/pygmsh/binary.vtk"); + let out1 = make_test_file(true); + assert!(parse_be(input).is_done()); + test_ignore_rem!(parse_be(input) => out1); + let mut outtest = String::new(); + outtest.write_vtk_ne(out1.clone())?; + // println!("{}", outtest); + test_b!(parse_ne(String::new().write_vtk_ne(out1.clone())?.as_bytes()) => ne(&out1)); + test_b!(parse_ne(Vec::::new().write_vtk_ne(out1.clone())?) => ne(&out1)); + test_b!(parse_le(Vec::::new().write_vtk_le(out1.clone())?) => le(&out1)); + test_b!(parse_be(Vec::::new().write_vtk_be(out1.clone())?) => out1); + Ok(()) +} + +/// Ensures that points from the two given vtk files are equivalent up to floating point error, and then overwrites +/// the first input to match exactly to the points in the second input, so the can be compared using `PartialEq` later. +fn compare_points_in_float_and_overwrite(vtu: &mut Vtk, expected: &Vtk) { + let expected_points = if let DataSet::UnstructuredGrid { ref pieces, .. } = expected.data { + pieces[0] + .load_piece_data(None) + .unwrap() + .points + .cast_into::() + .unwrap() + } else { + panic!("Wring vtk data type"); + }; + + // Compare positions via floating point comparisons. + if let DataSet::UnstructuredGrid { pieces, .. } = &mut vtu.data { + let piece = &mut pieces[0]; + if let Piece::Inline(piece_data) = piece { + let mut points = piece_data + .points + .cast_into::() + .expect("Point have the wrong type."); + for (i, (point, &expected_point)) in + points.iter_mut().zip(expected_points.iter()).enumerate() + { + if (*point - expected_point).abs() > 1e-6 * expected_point.abs() { + eprintln!("{}: actual {} vs. expected {}", i, *point, expected_point); + } + assert!((*point - expected_point).abs() <= 1e-6 * expected_point.abs()); + *point = expected_point; // match test data for full comparison later. + } + piece_data.points = points.into(); + } else { + panic!("Loaded vtk file has no inline unstructured grid piece"); + } + } else { + panic!("Loaded vtk file is not an unstructured grid"); + } +} + +/// Ensures the given xml based vtk file has the right values and overwrites them to match +/// the asset returned by make_test_file. +fn assert_and_fix_xml_vtu(vtu: &mut Vtk) { + vtu.file_path = None; // Reset file path to satisfy comparison + assert_eq!(vtu.version, Version::new((0, 1))); // XML file version is ignored. + vtu.version = (5, 1).into(); // Explicitly set version to satisfy comparison. + assert_eq!(vtu.title, String::new()); // Default empty title + vtu.title = "written by meshio v5.3.0".into(); // Match test file + vtu.byte_order = ByteOrder::BigEndian; // Match test file +} + +#[test] +fn xml_ascii() -> Result { + let mut vtu = Vtk::import("./assets/pygmsh/ascii.vtu")?; + assert_and_fix_xml_vtu(&mut vtu); + let expected = make_test_file(false); + compare_points_in_float_and_overwrite(&mut vtu, &expected); + assert_eq!(vtu, expected); + Ok(()) +} + +#[test] +#[cfg(feature = "xz2")] +fn xml_lzma() -> Result { + let mut vtu = Vtk::import("./assets/pygmsh/lzma.vtu")?; + assert_and_fix_xml_vtu(&mut vtu); + let expected = make_test_file(false); + compare_points_in_float_and_overwrite(&mut vtu, &expected); + assert_eq!(vtu, expected); + Ok(()) +} + +#[test] +fn xml_no_compression() -> Result { + let mut vtu = Vtk::import("./assets/pygmsh/no-compression.vtu")?; + assert_and_fix_xml_vtu(&mut vtu); + let expected = make_test_file(false); + compare_points_in_float_and_overwrite(&mut vtu, &expected); + assert_eq!(vtu, expected); + Ok(()) +} + +#[test] +#[cfg(feature = "flate2")] +fn xml_zlib() -> Result { + let mut vtu = Vtk::import("./assets/pygmsh/zlib.vtu")?; + assert_and_fix_xml_vtu(&mut vtu); + let expected = make_test_file(false); + compare_points_in_float_and_overwrite(&mut vtu, &expected); + assert_eq!(vtu, expected); + Ok(()) +}