meshx/io/
vtk.rs

1use crate::algo::merge::Merge;
2use crate::attrib::{Attrib, AttribDict, AttribIndex, Attribute, AttributeValue};
3use crate::mesh::topology::*;
4use crate::mesh::{CellType, Mesh, PointCloud, PolyMesh, TetMesh, VertexPositions};
5use flatk::{
6    consts::{U10, U11, U12, U13, U14, U15, U16, U2, U3, U4, U5, U6, U7, U8, U9},
7    U,
8};
9
10use super::MeshExtractor;
11use super::Real;
12use super::{NORMAL_ATTRIB_NAME, UV_ATTRIB_NAME};
13
14pub use vtkio::Error as VtkError;
15pub use vtkio::*;
16
17pub use super::Error;
18
19/// The name of the Field Data in vtk files used to store face vertex attributes.
20/// This is used to add support for things like face vertex attributes in vtk files as recommended
21/// by the Vtk file format documentation.
22const FACE_VERTEX_ATTRIBUTES_FIELD: &str = "face_vertex_attributes";
23
24// TODO: Add support for cell vertex attributes in the same way.
25//const CELL_VERTEX_ATTRIBUTES_FIELD: &'static str = "cell_vertex_attributes";
26
27/// Populate populate this function with special field attribute names to be ignored when
28/// transferring (reading) field attributes onto primary element topologies.
29fn special_field_attributes() -> &'static [&'static str] {
30    &[FACE_VERTEX_ATTRIBUTES_FIELD]
31}
32
33/// An enum indicating how polygon data should be exported in VTK format.
34///
35/// Polygon data can be represented by the designated `PolyData` VTK data set type or the more
36/// general `UnstructuredGrid` type.
37///
38/// Note that both styles are supported in XML as well as Legacy VTK files.
39pub enum VTKPolyExportStyle {
40    /// Use `PolyData` VTK type for exporting polygons.
41    PolyData,
42    /// Use `UnstructuredGrid` VTK type for exporting polygons.
43    UnstructuredGrid,
44}
45
46pub fn convert_mesh_to_vtk_format<T: Real>(mesh: &Mesh<T>) -> Result<model::Vtk, Error> {
47    let points: Vec<T> = mesh
48        .vertex_positions()
49        .iter()
50        .flat_map(|x| x.iter().cloned())
51        .collect();
52    let mut vertices = Vec::new();
53    for cell in mesh.cell_iter() {
54        vertices.push(cell.len() as u32);
55        for &vtx in cell.iter() {
56            vertices.push(vtx as u32);
57        }
58    }
59
60    let cell_types: Vec<_> = mesh
61        .cell_type_iter()
62        .map(|cell_type| match cell_type {
63            CellType::Tetrahedron => model::CellType::Tetra,
64            CellType::Triangle => model::CellType::Triangle,
65        })
66        .collect();
67
68    let point_attribs = mesh
69        .attrib_dict::<VertexIndex>()
70        .iter()
71        .filter_map(|(name, attrib)| mesh_to_vtk_named_attrib(name, attrib))
72        .collect();
73
74    let cell_attribs = mesh
75        .attrib_dict::<CellIndex>()
76        .iter()
77        .filter_map(|(name, attrib)| mesh_to_vtk_named_attrib(name, attrib))
78        .collect();
79
80    Ok(model::Vtk {
81        version: model::Version::new((0, 1)),
82        title: String::from("Unstructured Mesh"),
83        byte_order: model::ByteOrder::BigEndian,
84        file_path: None,
85        data: model::DataSet::inline(model::UnstructuredGridPiece {
86            points: points.into(),
87            cells: model::Cells {
88                cell_verts: model::VertexNumbers::Legacy {
89                    num_cells: mesh.num_cells() as u32,
90                    vertices,
91                },
92                types: cell_types,
93            },
94            data: model::Attributes {
95                point: point_attribs,
96                cell: cell_attribs,
97            },
98        }),
99    })
100}
101
102pub fn convert_polymesh_to_vtk_format<T: Real>(
103    mesh: &PolyMesh<T>,
104    style: VTKPolyExportStyle,
105) -> Result<model::Vtk, Error> {
106    let points: Vec<T> = mesh
107        .vertex_positions()
108        .iter()
109        .flat_map(|x| x.iter().cloned())
110        .collect();
111    let mut vertices = Vec::new();
112    for face in mesh.face_iter() {
113        vertices.push(face.len() as u32);
114        for &vtx in face.iter() {
115            vertices.push(vtx as u32);
116        }
117    }
118
119    let point_attribs = mesh
120        .attrib_dict::<VertexIndex>()
121        .iter()
122        .filter_map(|(name, attrib)| mesh_to_vtk_named_attrib(name, attrib))
123        .collect();
124
125    let face_attribs = mesh
126        .attrib_dict::<FaceIndex>()
127        .iter()
128        .filter_map(|(name, attrib)| mesh_to_vtk_named_attrib(name, attrib))
129        .chain(
130            // In addition to Face attributes, we can load face vertex attributes into a Vtk file
131            // via Vtk FIELD attributes.
132            mesh_to_vtk_named_field_attribs(
133                FACE_VERTEX_ATTRIBUTES_FIELD,
134                mesh.attrib_dict::<FaceVertexIndex>(),
135            ),
136        )
137        .collect();
138
139    Ok(model::Vtk {
140        version: model::Version::new((0, 1)),
141        title: String::from("Polygonal Mesh"),
142        byte_order: model::ByteOrder::BigEndian,
143        file_path: None,
144        data: match style {
145            VTKPolyExportStyle::UnstructuredGrid => {
146                model::DataSet::inline(model::UnstructuredGridPiece {
147                    points: IOBuffer::new(points),
148                    cells: model::Cells {
149                        cell_verts: model::VertexNumbers::Legacy {
150                            num_cells: mesh.num_faces() as u32,
151                            vertices,
152                        },
153                        types: vec![model::CellType::Polygon; mesh.num_faces()],
154                    },
155                    data: model::Attributes {
156                        point: point_attribs,
157                        cell: face_attribs,
158                    },
159                })
160            }
161            VTKPolyExportStyle::PolyData => model::DataSet::inline(model::PolyDataPiece {
162                points: IOBuffer::new(points),
163                polys: Some(model::VertexNumbers::Legacy {
164                    num_cells: mesh.num_faces() as u32,
165                    vertices,
166                }),
167                data: model::Attributes {
168                    point: point_attribs,
169                    cell: face_attribs,
170                },
171                ..Default::default()
172            }),
173        },
174    })
175}
176
177pub fn convert_tetmesh_to_vtk_format<T: Real>(tetmesh: &TetMesh<T>) -> Result<model::Vtk, Error> {
178    let points: Vec<T> = tetmesh
179        .vertex_positions()
180        .iter()
181        .flat_map(|x| x.iter().cloned())
182        .collect();
183    let mut vertices = Vec::new();
184    for cell in tetmesh.cell_iter() {
185        vertices.push(cell.len() as u32);
186        for &vtx in cell.iter() {
187            vertices.push(vtx as u32);
188        }
189    }
190
191    let point_attribs = tetmesh
192        .attrib_dict::<VertexIndex>()
193        .iter()
194        .filter_map(|(name, attrib)| mesh_to_vtk_named_attrib(name, attrib))
195        .collect();
196
197    let cell_attribs = tetmesh
198        .attrib_dict::<CellIndex>()
199        .iter()
200        .filter_map(|(name, attrib)| mesh_to_vtk_named_attrib(name, attrib))
201        .collect();
202
203    Ok(model::Vtk {
204        version: model::Version::new((0, 1)),
205        title: String::from("Tetrahedral Mesh"),
206        byte_order: model::ByteOrder::BigEndian,
207        file_path: None,
208        data: model::DataSet::inline(model::UnstructuredGridPiece {
209            points: points.into(),
210            cells: model::Cells {
211                cell_verts: model::VertexNumbers::Legacy {
212                    num_cells: tetmesh.num_cells() as u32,
213                    vertices,
214                },
215                types: vec![model::CellType::Tetra; tetmesh.num_cells()],
216            },
217            data: model::Attributes {
218                point: point_attribs,
219                cell: cell_attribs,
220            },
221        }),
222    })
223}
224
225pub fn convert_pointcloud_to_vtk_format<T: Real>(
226    ptcloud: &PointCloud<T>,
227    style: VTKPolyExportStyle,
228) -> Result<model::Vtk, Error> {
229    let num_verts = ptcloud.num_vertices() as u32;
230    let points: Vec<T> = ptcloud
231        .vertex_positions()
232        .iter()
233        .flat_map(|x| x.iter().cloned())
234        .collect();
235
236    let point_attribs = ptcloud
237        .attrib_dict::<VertexIndex>()
238        .iter()
239        .filter_map(|(name, attrib)| mesh_to_vtk_named_attrib(name, attrib))
240        .collect();
241
242    Ok(model::Vtk {
243        version: model::Version::new((0, 1)),
244        title: String::from("Point Cloud"),
245        byte_order: model::ByteOrder::BigEndian,
246        file_path: None,
247        data: match style {
248            VTKPolyExportStyle::PolyData => {
249                model::DataSet::inline(model::PolyDataPiece {
250                    points: IOBuffer::new(points),
251                    // A single VERTICES entry containing all points
252                    verts: Some(model::VertexNumbers::Legacy {
253                        num_cells: 1,
254                        vertices: std::iter::once(num_verts)
255                            .chain(0..num_verts)
256                            .collect::<Vec<_>>(),
257                    }),
258                    data: model::Attributes {
259                        point: point_attribs,
260                        cell: Vec::new(),
261                    },
262                    ..Default::default()
263                })
264            }
265            VTKPolyExportStyle::UnstructuredGrid => {
266                model::DataSet::inline(model::UnstructuredGridPiece {
267                    points: IOBuffer::new(points),
268                    cells: model::Cells {
269                        cell_verts: model::VertexNumbers::Legacy {
270                            num_cells: 1,
271                            vertices: std::iter::once(num_verts)
272                                .chain(0..num_verts)
273                                .collect::<Vec<_>>(),
274                        },
275                        types: vec![model::CellType::Vertex; ptcloud.num_vertices()],
276                    },
277                    data: model::Attributes {
278                        point: point_attribs,
279                        cell: Vec::new(),
280                    },
281                })
282            }
283        },
284    })
285}
286
287// TODO: Refactor the functions below to reuse code.
288impl<T: Real> MeshExtractor<T> for model::Vtk {
289    /// Constructs an unstructured Mesh from this VTK model.
290    ///
291    /// This function will clone the given model as necessary.
292    fn extract_mesh(&self) -> Result<Mesh<T>, Error> {
293        let model::Vtk {
294            file_path, data, ..
295        } = &self;
296        match data {
297            model::DataSet::UnstructuredGrid { pieces, .. } => {
298                Ok(Mesh::merge_iter(pieces.iter().filter_map(|piece| {
299                    let model::UnstructuredGridPiece {
300                        points,
301                        cells: model::Cells { cell_verts, types },
302                        data,
303                    } = piece
304                        .load_piece_data(file_path.as_ref().map(AsRef::as_ref))
305                        .ok()?;
306                    // Get points.
307                    let pt_coords: Vec<T> = points.cast_into()?;
308                    let mut pts = Vec::with_capacity(pt_coords.len() / 3);
309                    for coords in pt_coords.chunks_exact(3) {
310                        pts.push([coords[0], coords[1], coords[2]]);
311                    }
312
313                    let num_cells = cell_verts.num_cells();
314                    let (connectivity, offsets) = cell_verts.into_xml();
315
316                    // Mapping to original topology. This is used when the vtk file has elements
317                    // not supported by our Mesh.
318                    let mut orig_cell_idx = Vec::with_capacity(num_cells);
319
320                    // Get contiguous indices (4 vertex indices for each tet or 3 for triangles).
321                    let mut begin = 0usize;
322                    let mut indices = Vec::new();
323                    let mut counts = Vec::new();
324                    let mut cell_types = Vec::new();
325                    for (c, &end) in offsets.iter().enumerate() {
326                        let n = end as usize - begin;
327                        let cell_type = match types[c] {
328                            model::CellType::Triangle if n == 3 => CellType::Triangle,
329                            model::CellType::Tetra if n == 4 => CellType::Tetrahedron,
330                            _ => {
331                                // Not a valid cell type, skip it.
332                                begin = end as usize;
333                                continue;
334                            }
335                        };
336
337                        if cell_types.is_empty() || *cell_types.last().unwrap() != cell_type {
338                            // Start a new block.
339                            cell_types.push(cell_type);
340                            counts.push(1);
341                        } else if let Some(last) = counts.last_mut() {
342                            *last += 1;
343                        } else {
344                            // Bug in the code. Counts must have the same size as cell_types.
345                            return None;
346                        }
347
348                        orig_cell_idx.push(c);
349                        for i in 0..n {
350                            indices.push(connectivity[begin + i] as usize);
351                        }
352                        begin = end as usize;
353                    }
354
355                    let mut mesh =
356                        Mesh::from_cells_counts_and_types(pts, indices, counts, cell_types);
357
358                    // Don't bother transferring attributes if there are no vertices or cells.
359                    // This supresses some needless size mismatch warnings when the dataset has an
360                    // unstructured grid representing something other than a recognizable Mesh.
361
362                    if mesh.num_vertices() > 0 {
363                        // Populate point attributes.
364                        vtk_to_mesh_attrib::<_, VertexIndex>(data.point, &mut mesh, None);
365                    }
366
367                    if mesh.num_cells() > 0 {
368                        // Populate tet attributes
369                        vtk_to_mesh_attrib::<_, CellIndex>(
370                            data.cell,
371                            &mut mesh,
372                            Some(orig_cell_idx.as_slice()),
373                        );
374                    }
375
376                    Some(mesh)
377                })))
378            }
379            _ => Err(Error::UnsupportedDataFormat),
380        }
381    }
382    /// Constructs a PolyMesh from this VTK model.
383    ///
384    /// This function will clone the given model as necessary.
385    fn extract_polymesh(&self) -> Result<PolyMesh<T>, Error> {
386        let model::Vtk {
387            file_path, data, ..
388        } = &self;
389        match data {
390            model::DataSet::UnstructuredGrid { pieces, .. } => {
391                let mesh = PolyMesh::merge_iter(pieces.iter().filter_map(|piece| {
392                    let model::UnstructuredGridPiece {
393                        points,
394                        cells: model::Cells { cell_verts, types },
395                        data,
396                    } = piece
397                        .load_piece_data(file_path.as_ref().map(AsRef::as_ref))
398                        .ok()?;
399                    // Get points.
400                    let pt_coords: Vec<T> = points.cast_into()?; // None is returned in case of overflow.
401                    let mut pts = Vec::with_capacity(pt_coords.len() / 3);
402                    for coords in pt_coords.chunks_exact(3) {
403                        pts.push([coords[0], coords[1], coords[2]]);
404                    }
405
406                    // We use this counter to determine if the vtk file should actually be parsed as a
407                    // different type of mesh.
408                    let mut count_non_polymesh_faces = 0;
409
410                    let num_cells = cell_verts.num_cells();
411                    // TODO: it would be more efficient to have an implementation for xml and legacy formats separately.
412                    let (connectivity, offsets) = cell_verts.into_xml();
413
414                    // Mappings to original topology. This is used when the topology must be modified when converting to PolyMesh.
415                    let mut orig_cell_idx = Vec::with_capacity(num_cells);
416                    let mut orig_cell_vtx_idx = Vec::with_capacity(connectivity.len());
417
418                    let mut begin = 0usize;
419                    let mut faces = Vec::new();
420                    for c in 0..num_cells {
421                        let end = offsets[c] as usize;
422                        let n = end - begin;
423
424                        // Skip geometry we can't represent as a polygon mesh.
425                        let skip = match types[c] {
426                            model::CellType::Line => n != 1,
427                            model::CellType::Triangle => n != 3,
428                            model::CellType::Quad => n != 4,
429                            model::CellType::Polygon | model::CellType::PolyLine => false,
430                            _ => true,
431                        };
432                        if skip {
433                            count_non_polymesh_faces += 1;
434                            begin = end;
435                            continue;
436                        }
437
438                        if types[c] == model::CellType::PolyLine {
439                            // Each polyline is broken into multiple 2-vertex line segments.
440                            for i in begin..end - 1 {
441                                orig_cell_idx.push(c);
442                                orig_cell_vtx_idx.push(i - begin);
443                                orig_cell_vtx_idx.push(i + 1 - begin);
444                                faces.push(2);
445                                faces.push(connectivity[i] as usize);
446                                faces.push(connectivity[i + 1] as usize);
447                            }
448                        } else {
449                            orig_cell_idx.push(c);
450                            faces.push(n);
451                            orig_cell_vtx_idx.extend(0..end - begin);
452                            faces.extend(connectivity[begin..end].iter().map(|&i| i as usize));
453                        }
454
455                        begin = end;
456                    }
457
458                    if faces.is_empty() && count_non_polymesh_faces > 0 {
459                        // This should be parsed as another type of mesh.
460                        // We opt to not interpret it as a point cloud mesh, which would be the case if
461                        // there were no other types of faces.
462                        return None;
463                    } // Otherwise we output what we have found, whether or not some other faces were ignored.
464
465                    let mut polymesh = PolyMesh::new(pts, &faces);
466
467                    // Populate point attributes.
468                    vtk_to_mesh_attrib::<_, VertexIndex>(data.point, &mut polymesh, None);
469
470                    // Populate face attributes.
471                    let remainder = vtk_to_mesh_attrib::<_, FaceIndex>(
472                        data.cell,
473                        &mut polymesh,
474                        Some(orig_cell_idx.as_slice()),
475                    );
476
477                    // Populate face vertex attributes.
478                    vtk_field_to_mesh_attrib(
479                        remainder,
480                        &mut polymesh,
481                        Some(orig_cell_vtx_idx.as_slice()),
482                    );
483
484                    Some(polymesh)
485                }));
486
487                // If we try to build a polymesh out of an unstructured grid with no polygon data,
488                // interpret this as an error, since it is most likely unintentional.
489                // Clients could also very well be relying on this behaviour when trying to interpret a
490                // vtk file.
491                if mesh.num_faces() == 0 {
492                    Err(Error::MeshTypeMismatch)
493                } else {
494                    Ok(mesh)
495                }
496            }
497            model::DataSet::PolyData { pieces, .. } => {
498                Ok(PolyMesh::merge_iter(pieces.iter().filter_map(|piece| {
499                    let model::PolyDataPiece {
500                        points,
501                        lines,
502                        polys,
503                        strips,
504                        // PolyData with vertices is best represented by a PointCloud
505                        data,
506                        ..
507                    } = piece
508                        .load_piece_data(file_path.as_ref().map(AsRef::as_ref))
509                        .ok()?;
510                    // Get points.
511                    let pt_coords: Vec<T> = points.cast_into()?; // None is returned in case of overflow.
512                    let mut pts = Vec::with_capacity(pt_coords.len() / 3);
513                    for coords in pt_coords.chunks_exact(3) {
514                        pts.push([coords[0], coords[1], coords[2]]);
515                    }
516
517                    // Mappings to original topology. This is used when the topology must be modified when converting to PolyMesh.
518                    let mut cell_idx_map = Vec::new();
519                    let mut cell_vtx_idx_map = Vec::new();
520
521                    let mut num_faces = 0;
522
523                    let mut faces = Vec::new();
524
525                    let mut append_topo = |topo: model::VertexNumbers| {
526                        cell_idx_map.extend(num_faces..num_faces + topo.num_cells());
527                        num_faces += topo.num_cells();
528                        let (connectivity, offsets) = topo.into_xml();
529                        let mut begin = 0;
530                        for &offset in offsets.iter() {
531                            let end = offset as usize;
532                            cell_vtx_idx_map.extend(begin..end);
533                            faces.push(end - begin);
534                            faces.extend(connectivity[begin..end].iter().map(|&i| i as usize));
535                            begin = end;
536                        }
537                    };
538
539                    polys.map(&mut append_topo);
540                    strips.map(&mut append_topo);
541
542                    if let Some(topo) = lines {
543                        let (connectivity, offsets) = topo.into_xml();
544                        let mut begin = 0;
545                        for (orig_face_idx, &offset) in offsets.iter().enumerate() {
546                            // Each polyline is broken into multiple 2-vertex line segments.
547                            for i in begin..offset as usize - 1 {
548                                cell_idx_map.push(num_faces + orig_face_idx);
549                                cell_vtx_idx_map.push(i - begin);
550                                cell_vtx_idx_map.push(i + 1 - begin);
551                                faces.push(2);
552                                faces.push(connectivity[i] as usize);
553                                faces.push(connectivity[i + 1] as usize);
554                            }
555                            begin = offset as usize;
556                        }
557                    }
558
559                    let mut polymesh = PolyMesh::new(pts, &faces);
560
561                    // Populate point attributes.
562                    vtk_to_mesh_attrib::<_, VertexIndex>(data.point, &mut polymesh, None);
563
564                    // Populate face attributes
565                    let remainder = vtk_to_mesh_attrib::<_, FaceIndex>(
566                        data.cell,
567                        &mut polymesh,
568                        Some(cell_idx_map.as_slice()),
569                    );
570
571                    // Populate face vertex attributes.
572                    vtk_field_to_mesh_attrib(
573                        remainder,
574                        &mut polymesh,
575                        Some(cell_vtx_idx_map.as_slice()),
576                    );
577
578                    Some(polymesh)
579                })))
580            }
581            _ => Err(Error::UnsupportedDataFormat),
582        }
583    }
584
585    /// Constructs a TetMesh from this VTK model.
586    ///
587    /// This function will clone the given model as necessary.
588    fn extract_tetmesh(&self) -> Result<TetMesh<T>, Error> {
589        let model::Vtk {
590            file_path, data, ..
591        } = &self;
592        match data {
593            model::DataSet::UnstructuredGrid { pieces, .. } => {
594                Ok(TetMesh::merge_iter(pieces.iter().filter_map(|piece| {
595                    let model::UnstructuredGridPiece {
596                        points,
597                        cells: model::Cells { cell_verts, types },
598                        data,
599                    } = piece
600                        .load_piece_data(file_path.as_ref().map(AsRef::as_ref))
601                        .ok()?;
602                    // Get points.
603                    let pt_coords: Vec<T> = points.cast_into()?;
604                    let mut pts = Vec::with_capacity(pt_coords.len() / 3);
605                    for coords in pt_coords.chunks_exact(3) {
606                        pts.push([coords[0], coords[1], coords[2]]);
607                    }
608
609                    let num_cells = cell_verts.num_cells();
610                    let (connectivity, offsets) = cell_verts.into_xml();
611
612                    // Mapping to original topology. This is used when the vtk file has elements other than tets.
613                    let mut orig_cell_idx = Vec::with_capacity(num_cells);
614
615                    // Get contiguous indices (4 vertex indices for each tet).
616                    let mut begin = 0usize;
617                    let mut indices = Vec::new();
618                    for (c, &end) in offsets.iter().enumerate() {
619                        let n = end as usize - begin;
620
621                        if n != 4 || types[c] != model::CellType::Tetra {
622                            // Not a tetrahedron, skip it.
623                            begin = end as usize;
624                            continue;
625                        }
626
627                        orig_cell_idx.push(c);
628                        indices.push([
629                            connectivity[begin] as usize,
630                            connectivity[begin + 1] as usize,
631                            connectivity[begin + 2] as usize,
632                            connectivity[begin + 3] as usize,
633                        ]);
634                        begin = end as usize;
635                    }
636
637                    let mut tetmesh = TetMesh::new(pts, indices);
638
639                    // Don't bother transferring attributes if there are no vertices or cells.
640                    // This supresses some needless size mismatch warnings when the dataset has an
641                    // unstructuredgrid representing something other than a tetmesh.
642
643                    if tetmesh.num_vertices() > 0 {
644                        // Populate point attributes.
645                        vtk_to_mesh_attrib::<_, VertexIndex>(data.point, &mut tetmesh, None);
646                    }
647
648                    if tetmesh.num_cells() > 0 {
649                        // Populate tet attributes
650                        vtk_to_mesh_attrib::<_, CellIndex>(
651                            data.cell,
652                            &mut tetmesh,
653                            Some(orig_cell_idx.as_slice()),
654                        );
655                    }
656
657                    Some(tetmesh)
658                })))
659            }
660            _ => Err(Error::UnsupportedDataFormat),
661        }
662    }
663
664    /// Constructs a PointCloud from this VTK model.
665    ///
666    /// This function will clone the given model as necessary.
667    fn extract_pointcloud(&self) -> Result<PointCloud<T>, Error> {
668        let model::Vtk {
669            file_path, data, ..
670        } = &self;
671        let mut pts = Vec::new();
672        let mut vertices = Vec::new();
673        match data {
674            model::DataSet::UnstructuredGrid { pieces, .. } => {
675                let ptcloud = PointCloud::merge_iter(pieces.iter().filter_map(|piece| {
676                    let model::UnstructuredGridPiece {
677                        points,
678                        cells: model::Cells { cell_verts, types },
679                        data,
680                    } = piece
681                        .load_piece_data(file_path.as_ref().map(AsRef::as_ref))
682                        .ok()?;
683
684                    pts.clear();
685                    vertices.clear();
686
687                    // Get points.
688                    let pt_coords: Vec<T> = points.cast_into()?;
689                    pts.reserve(pt_coords.len() / 3);
690                    for coords in pt_coords.chunks_exact(3) {
691                        pts.push([coords[0], coords[1], coords[2]]);
692                    }
693
694                    // We use this counter to determine if the vtk file should actually be parsed as a
695                    // different type of mesh.
696                    let mut count_non_vertex_cells = 0;
697
698                    let (num_cells, cell_vertices) = cell_verts.into_legacy();
699
700                    let mut i = 0usize;
701                    for c in 0..num_cells {
702                        if i >= cell_vertices.len() {
703                            break;
704                        }
705
706                        let n = cell_vertices[i] as usize;
707                        // Skip geometry we can't represent as a point cloud.
708                        if types[c as usize] == model::CellType::Vertex {
709                            if n != 1 {
710                                i += n + 1;
711                                count_non_vertex_cells += 1;
712                                continue;
713                            }
714                        } else if types[c as usize] != model::CellType::PolyVertex {
715                            i += n + 1;
716                            count_non_vertex_cells += 1;
717                            continue;
718                        }
719
720                        i += 1; // Skipping the size of the cell
721
722                        for _ in 0..=n {
723                            vertices.push(cell_vertices[i] as usize);
724                            i += 1;
725                        }
726                    }
727
728                    if vertices.is_empty() && count_non_vertex_cells > 0 {
729                        // This should be parsed as another type of mesh.
730                        // We opt to not interpret it as a point cloud mesh, which would be the case if
731                        // there were no other types of faces.
732                        return None;
733                    } // Otherwise we output what we have found, whether or not some other faces were ignored.
734                    let referenced_points: Vec<_> = vertices.iter().map(|&vtx| pts[vtx]).collect();
735                    let mut pointcloud = PointCloud::new(referenced_points);
736
737                    // Populate point attributes.
738                    vtk_to_mesh_attrib::<_, VertexIndex>(data.point, &mut pointcloud, None);
739
740                    Some(pointcloud)
741                }));
742
743                // If we try to build a point cloud out of an unstructured grid with no vertex objects,
744                // interpret this as an error, since it is most likely unintentional.
745                // Clients could also very well be relying on this behaviour when trying to interpret a
746                // vtk file.
747                if ptcloud.num_vertices() == 0 {
748                    Err(Error::MeshTypeMismatch)
749                } else {
750                    Ok(ptcloud)
751                }
752            }
753            model::DataSet::PolyData { pieces, .. } => {
754                Ok(PointCloud::merge_iter(pieces.iter().filter_map(|piece| {
755                    let model::PolyDataPiece {
756                        points,
757                        verts,
758                        data,
759                        ..
760                    } = piece.load_piece_data(None).ok()?;
761                    pts.clear();
762                    vertices.clear();
763
764                    // Get points.
765                    let pt_coords: Vec<T> = points.cast_into()?;
766                    pts.reserve(pt_coords.len() / 3);
767                    for coords in pt_coords.chunks_exact(3) {
768                        pts.push([coords[0], coords[1], coords[2]]);
769                    }
770
771                    // If vertex topology is given use it, otherwise load all vertices.
772                    let mut ptcloud = if let Some(topo) = verts {
773                        let (_, cell_vertices) = topo.into_legacy();
774                        vertices.extend(cell_vertices.into_iter().skip(1).map(|x| x as usize));
775                        let referenced_points: Vec<_> =
776                            vertices.iter().map(|&vtx| pts[vtx]).collect();
777                        PointCloud::new(referenced_points)
778                    } else {
779                        PointCloud::new(pts.clone())
780                    };
781
782                    // Populate point attributes.
783                    vtk_to_mesh_attrib::<_, VertexIndex>(data.point, &mut ptcloud, None);
784
785                    Some(ptcloud)
786                })))
787            }
788            _ => Err(Error::UnsupportedDataFormat),
789        }
790    }
791}
792
793fn flatten2<T: Clone>(vec: Vec<[T; 2]>) -> Vec<T> {
794    vec.iter().flat_map(|x| x.iter().cloned()).collect()
795}
796fn flatten3<T: Clone>(vec: Vec<[T; 3]>) -> Vec<T> {
797    vec.iter().flat_map(|x| x.iter().cloned()).collect()
798}
799fn flatten4<T: Clone>(vec: Vec<[T; 4]>) -> Vec<T> {
800    vec.iter().flat_map(|x| x.iter().cloned()).collect()
801}
802fn flatten33<T: Clone>(vec: Vec<[[T; 3]; 3]>) -> Vec<T> {
803    vec.iter()
804        .flat_map(|x| x.iter().flat_map(|y| y.iter().cloned()))
805        .collect()
806}
807
808/// Transfer a `uv` attribute from this attribute to the `vtk` model.
809fn into_vtk_attrib_uv<I>(name: &str, attrib: &Attribute<I>) -> Option<model::Attribute> {
810    // Try 2d texture coordinates
811    let mut maybe_iobuf = attrib
812        .direct_clone_into_vec::<[f32; 2]>()
813        .map(|y| IOBuffer::from(flatten2(y)));
814    if maybe_iobuf.is_err() {
815        // try with f64
816        maybe_iobuf = attrib
817            .direct_clone_into_vec::<[f64; 2]>()
818            .map(|y| IOBuffer::from(flatten2(y)));
819    }
820
821    if let Ok(data) = maybe_iobuf {
822        return Some(model::Attribute::tcoords(name, 2).with_data(data));
823    }
824
825    // Try 3d texture coordinates
826    maybe_iobuf = attrib
827        .direct_clone_into_vec::<[f32; 3]>()
828        .map(|y| flatten3(y).into());
829    if maybe_iobuf.is_err() {
830        // try with f64
831        maybe_iobuf = attrib
832            .direct_clone_into_vec::<[f64; 3]>()
833            .map(|y| flatten3(y).into());
834    }
835
836    if let Ok(data) = maybe_iobuf {
837        return Some(model::Attribute::tcoords(name, 3).with_data(data));
838    }
839
840    None
841}
842
843macro_rules! try_interpret_attrib {
844    (@direct $attrib:ident, $t:ident, $f:expr) => {
845            $attrib.direct_clone_into_vec::<$t>().map($f)
846    };
847    (@build $attrib:ident, ($t:ident $($ts:ident)*), $f:expr) => {
848        $attrib.direct_clone_into_vec::<$t>().map($f)
849        $(
850            .or_else(|_| try_interpret_attrib!(@direct $attrib, $ts, $f))
851        )*
852    };
853    (@direct $attrib:ident, $n:expr, $t:ident, $f:expr) => {
854            $attrib.direct_clone_into_vec::<[$t; $n]>().map($f)
855    };
856    (@build $attrib:ident, $n:expr, ($t:ident $($ts:ident)*), $f:expr) => {
857        $attrib.direct_clone_into_vec::<[$t; $n]>().map($f)
858        $(
859            .or_else(|_| try_interpret_attrib!(@direct $attrib, $n, $ts, $f))
860        )*
861    };
862    (@direct $attrib:ident, $n:expr, $m:expr, $t:ident, $f:expr) => {
863            $attrib.direct_clone_into_vec::<[[$t; $n]; $m]>().map($f)
864    };
865    (@build $attrib:ident, $n:expr, $m:expr, ($t:ident $($ts:ident)*), $f:expr) => {
866        $attrib.direct_clone_into_vec::<[[$t; $n]; $m]>().map($f)
867        $(
868            .or_else(|_| try_interpret_attrib!(@direct $attrib, $n, $m, $ts, $f))
869        )*
870    };
871    ($attrib:ident, $f:expr) => {
872        {
873            try_interpret_attrib!(@build $attrib, (u8 i8 u16 i16 u32 i32 u64 i64 f32 f64), $f)
874        }
875    };
876    ($attrib:ident, $n:expr, $f:expr) => {
877        {
878            try_interpret_attrib!(@build $attrib, $n, (u8 i8 u16 i16 u32 i32 u64 i64 f32 f64), $f)
879        }
880    };
881    ($attrib:ident, $n:expr, $m:expr, $f:expr) => {
882        {
883            try_interpret_attrib!(@build $attrib, $n, $m, (u8 i8 u16 i16 u32 i32 u64 i64 f32 f64), $f)
884        }
885    }
886}
887
888macro_rules! try_interpret_generic_attrib {
889    ($attrib:ident, $name:ident $(,$n:expr)*) => {
890        {
891            $(
892                if let Ok(data) = try_interpret_attrib!($attrib, $n, |x| IOBuffer::from(
893                        x.iter().flat_map(|x| x.iter().cloned()).collect::<Vec<_>>()
894                )) {
895                    return Some(model::Attribute::generic($name, $n).with_data(data));
896                }
897            )*
898        }
899    }
900}
901
902fn mesh_to_vtk_attrib_impl<I>(name: &str, attrib: &Attribute<I>) -> Option<model::Attribute> {
903    // Try to match a scalar field.
904    if let Ok(data) = try_interpret_attrib!(attrib, IOBuffer::from) {
905        return Some(model::Attribute::scalars(name, 1).with_data(data));
906    }
907
908    // Try to match a vector field.
909    if let Ok(data) = try_interpret_attrib!(attrib, 2, |x| IOBuffer::from(flatten2(x))) {
910        return Some(model::Attribute::scalars(name, 2).with_data(data));
911    }
912
913    // Try to match a vector field.
914    if let Ok(data) = try_interpret_attrib!(attrib, 3, |x| IOBuffer::from(flatten3(x))) {
915        return Some(model::Attribute::vectors(name).with_data(data));
916    }
917
918    // Try to match a vector field.
919    if let Ok(data) = try_interpret_attrib!(attrib, 4, |x| IOBuffer::from(flatten4(x))) {
920        return Some(model::Attribute::scalars(name, 4).with_data(data));
921    }
922
923    // Try to match a tensor field.
924    if let Ok(data) = try_interpret_attrib!(attrib, 3, 3, |x| IOBuffer::from(flatten33(x))) {
925        return Some(model::Attribute::tensors(name).with_data(data));
926    }
927
928    // Try to match a generic field for any size up to 16.
929    try_interpret_generic_attrib!(attrib, name, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
930    None
931}
932
933fn mesh_to_vtk_named_attrib<I>(name: &str, attrib: &Attribute<I>) -> Option<model::Attribute> {
934    // Try to match special attributes
935    if name == UV_ATTRIB_NAME {
936        let attrib = into_vtk_attrib_uv(name, attrib);
937        if attrib.is_some() {
938            return attrib;
939        }
940    } else if name == NORMAL_ATTRIB_NAME {
941        let mut maybe_iobuf: Result<IOBuffer, _> = attrib
942            .direct_clone_into_vec::<[f32; 3]>()
943            .map(|y| flatten3(y).into());
944        if maybe_iobuf.is_err() {
945            // try with f64
946            maybe_iobuf = attrib
947                .direct_clone_into_vec::<[f64; 3]>()
948                .map(|y| flatten3(y).into());
949        }
950
951        if let Ok(data) = maybe_iobuf {
952            return Some(model::Attribute::normals(name).with_data(data));
953        }
954    }
955
956    // Match with other vtk attributes.
957    mesh_to_vtk_attrib_impl(name, attrib)
958}
959
960/// Transfer attribute data from `attrib_dict` to a vtk FIELD attribute. This is useful for storing
961/// attributes for topologies that Vtk doesn't directly support like `FaceVertex` or `CellVertex`
962/// attributes which are important for passing through texture coordinates with seams.
963fn mesh_to_vtk_named_field_attribs<I>(
964    field_data_name: &str,
965    attrib_dict: &AttribDict<I>,
966) -> Option<model::Attribute> {
967    let data_array: Vec<_> = attrib_dict
968        .iter()
969        .filter_map(|(name, attrib)| {
970            // Try to match a scalar field.
971            if let Ok(data) = try_interpret_attrib!(attrib, IOBuffer::from) {
972                return Some(model::FieldArray::new(name, 1).with_data(data));
973            }
974
975            // Try to match a 2D vector field.
976            if let Ok(data) = try_interpret_attrib!(attrib, 2, |x| IOBuffer::from(flatten2(x))) {
977                return Some(model::FieldArray::new(name, 2).with_data(data));
978            }
979
980            // Try to match a 3D vector field.
981            if let Ok(data) = try_interpret_attrib!(attrib, 3, |x| IOBuffer::from(flatten3(x))) {
982                return Some(model::FieldArray::new(name, 3).with_data(data));
983            }
984
985            None
986        })
987        .collect();
988
989    if !data_array.is_empty() {
990        Some(model::Attribute::field(field_data_name).with_field_data(data_array))
991    } else {
992        None
993    }
994}
995
996fn insert_2d_array_attrib<'a, T, M, I>(
997    buf: &[T],
998    name: &'a str,
999    mesh: &mut M,
1000    remap: Option<&[usize]>,
1001) -> Result<(), Error>
1002where
1003    T: AttributeValue + Copy + Default,
1004    I: AttribIndex<M>,
1005    M: Attrib,
1006{
1007    let n = 9;
1008    let mut vecs = Vec::with_capacity(buf.len() / n);
1009    let mut count_comp = 0;
1010    let mut cur = [[T::default(); 3]; 3];
1011    let mut push_val = |val| {
1012        cur[count_comp / 3][count_comp % 3] = val; // row-major -> col-major
1013        count_comp += 1;
1014        if count_comp == n {
1015            vecs.push(cur);
1016            count_comp = 0;
1017        }
1018    };
1019    if let Some(remap) = remap {
1020        remap.iter().for_each(|&i| push_val(buf[i]));
1021    } else {
1022        buf.iter().cloned().for_each(push_val);
1023    }
1024    mesh.insert_attrib_data::<_, I>(name, vecs)?;
1025    Ok(())
1026}
1027
1028fn insert_array_attrib<'a, T, M, I>(
1029    buf: &[T],
1030    name: &'a str,
1031    mesh: &mut M,
1032    remap: Option<&[usize]>,
1033) -> Result<(), Error>
1034where
1035    T: AttributeValue + Default,
1036    I: AttribIndex<M>,
1037    M: Attrib,
1038{
1039    let remapped_buf = if let Some(remap) = remap {
1040        remap.iter().map(|&i| buf[i].clone()).collect()
1041    } else {
1042        buf.to_vec()
1043    };
1044    mesh.insert_attrib_data::<_, I>(name, remapped_buf)?;
1045    Ok(())
1046}
1047
1048fn insert_array_attrib_n<'a, T, M, I: AttribIndex<M>, N>(
1049    buf: &[T],
1050    name: &'a str,
1051    mesh: &mut M,
1052    remap: Option<&[usize]>,
1053) -> Result<(), Error>
1054where
1055    T: bytemuck::Pod + AttributeValue + Default,
1056    M: Attrib,
1057    N: flatk::Unsigned + Default + flatk::Array<T>,
1058    <N as flatk::Array<T>>::Array: Default + PartialEq + std::fmt::Debug + Send + Sync,
1059{
1060    let remapped_buf = if let Some(remap) = remap {
1061        remap
1062            .iter()
1063            .flat_map(|&i| (0..N::to_usize()).map(move |j| buf[N::to_usize() * i + j]))
1064            .collect()
1065    } else {
1066        buf.to_vec()
1067    };
1068    let chunked = flatk::UniChunked::<_, U<N>>::from_flat(remapped_buf);
1069    mesh.insert_attrib_data::<_, I>(name, chunked.into_arrays())?;
1070    Ok(())
1071}
1072
1073/// Adds VTK attributes to the given mesh, and returns any unprocessed attributes that can be
1074/// processed further.
1075///
1076/// If the reason an attribute is not processed is because it
1077/// has an unsupported type, we leave it out of the remainder.
1078#[allow(clippy::cognitive_complexity)]
1079fn vtk_to_mesh_attrib<M, I>(
1080    attribs: Vec<model::Attribute>,
1081    mesh: &mut M,
1082    orig_map: Option<&[usize]>,
1083) -> Vec<model::Attribute>
1084where
1085    M: Attrib,
1086    I: AttribIndex<M>,
1087{
1088    // We populate another vector instead of using filter_map to allow for errors to propagate.
1089    let mut remainder = Vec::with_capacity(attribs.len());
1090
1091    for attrib in attribs {
1092        match attrib {
1093            model::Attribute::DataArray(model::DataArray { name, elem, data }) => {
1094                let name = name.as_str();
1095                match elem {
1096                    model::ElementType::Scalars { num_comp: dim, .. } | model::ElementType::TCoords(dim) => {
1097                        match dim {
1098                            // Note that only the first found attribute with the same name and location
1099                            // will be inserted.
1100                            1 => match_buf!( &data, v => insert_array_attrib::<_,M,I>(v, name, mesh, orig_map) ),
1101                            2 => match_buf!( &data, v => insert_array_attrib_n::<_,M,I,U2>(v, name, mesh, orig_map) ),
1102                            3 => match_buf!( &data, v => insert_array_attrib_n::<_,M,I,U3>(v, name, mesh, orig_map) ),
1103                            4 => match_buf!( &data, v => insert_array_attrib_n::<_,M,I,U4>(v, name, mesh, orig_map) ),
1104                            // Other values for dim are not supported by the vtk standard
1105                            // at the time of this writing.
1106                             _ => continue,
1107                        }
1108                    }
1109                    model::ElementType::Vectors | model::ElementType::Normals => {
1110                        match_buf!( &data, v => insert_array_attrib_n::<_,M,I,U3>(v, name, mesh, orig_map) )
1111                    }
1112                    model::ElementType::Tensors => {
1113                        match_buf!( &data, v => insert_2d_array_attrib::<_,M,I>(v, name, mesh, orig_map) )
1114                    }
1115                    model::ElementType::Generic(dim) => {
1116                        match dim {
1117                            1 => match_buf!( &data, v => insert_array_attrib::<_,M,I>(v, name, mesh, orig_map) ),
1118                            2 => match_buf!( &data, v => insert_array_attrib_n::<_,M,I,U2>(v, name, mesh, orig_map) ),
1119                            3 => match_buf!( &data, v => insert_array_attrib_n::<_,M,I,U3>(v, name, mesh, orig_map) ),
1120                            4 => match_buf!( &data, v => insert_array_attrib_n::<_,M,I,U4>(v, name, mesh, orig_map) ),
1121                            5 => match_buf!( &data, v => insert_array_attrib_n::<_,M,I,U5>(v, name, mesh, orig_map) ),
1122                            6 => match_buf!( &data, v => insert_array_attrib_n::<_,M,I,U6>(v, name, mesh, orig_map) ),
1123                            7 => match_buf!( &data, v => insert_array_attrib_n::<_,M,I,U7>(v, name, mesh, orig_map) ),
1124                            8 => match_buf!( &data, v => insert_array_attrib_n::<_,M,I,U8>(v, name, mesh, orig_map) ),
1125                            9 => match_buf!( &data, v => insert_array_attrib_n::<_,M,I,U9>(v, name, mesh, orig_map) ),
1126                            10 => match_buf!( &data, v => insert_array_attrib_n::<_,M,I,U10>(v, name, mesh, orig_map) ),
1127                            11 => match_buf!( &data, v => insert_array_attrib_n::<_,M,I,U11>(v, name, mesh, orig_map) ),
1128                            12 => match_buf!( &data, v => insert_array_attrib_n::<_,M,I,U12>(v, name, mesh, orig_map) ),
1129                            13 => match_buf!( &data, v => insert_array_attrib_n::<_,M,I,U13>(v, name, mesh, orig_map) ),
1130                            14 => match_buf!( &data, v => insert_array_attrib_n::<_,M,I,U14>(v, name, mesh, orig_map) ),
1131                            15 => match_buf!( &data, v => insert_array_attrib_n::<_,M,I,U15>(v, name, mesh, orig_map) ),
1132                            16 => match_buf!( &data, v => insert_array_attrib_n::<_,M,I,U16>(v, name, mesh, orig_map) ),
1133                            _ => continue,
1134                        }
1135                    }
1136                    _ => continue, // LookupTable and ColorScalars attributes ignored
1137                }
1138            }
1139            model::Attribute::Field { data_array, name } => {
1140                if special_field_attributes().contains(&name.as_str()) {
1141                    remainder.push(model::Attribute::Field { name, data_array });
1142                    continue;
1143                }
1144                for model::FieldArray {
1145                    name,
1146                    elem,
1147                    data,
1148                } in data_array
1149                {
1150                    let name = name.as_str();
1151                    // Field attributes do not necessarily have the right size. We check it here.
1152                    match elem {
1153                        // Note that only the first found attribute with the same name and location
1154                        // will be inserted.
1155                        1 => match_buf!( &data, v => insert_array_attrib::<_,M,I>(v, name, mesh, orig_map) ),
1156                        2 => match_buf!( &data, v => insert_array_attrib_n::<_,M,I,U2>(v, name, mesh, orig_map) ),
1157                        3 => match_buf!( &data, v => insert_array_attrib_n::<_,M,I,U3>(v, name, mesh, orig_map) ),
1158                        4 => match_buf!( &data, v => insert_array_attrib_n::<_,M,I,U4>(v, name, mesh, orig_map) ),
1159                        _ => continue,
1160                    }
1161                    .unwrap_or_else(|err| eprintln!("WARNING: Field attribute transfer error: {}", err));
1162                }
1163                continue;
1164            }
1165        }
1166        // Attribute transfer might fail, but we shouldn't stop trying the rest of the attributes.
1167        // Simply issue a warning and continue;
1168        .unwrap_or_else(|err| {
1169            #[cfg(feature = "unstable")]
1170            {
1171                eprintln!("WARNING: Attribute transfer error at {}: {}", std::intrinsics::type_name::<I>(), err)
1172            }
1173            #[cfg(not(feature = "unstable"))]
1174            {
1175                eprintln!("WARNING: Attribute transfer error: {}", err)
1176            }
1177        })
1178    }
1179    remainder
1180}
1181
1182/// Populate face vertex attributes from field attributes.
1183#[allow(clippy::cognitive_complexity)]
1184fn vtk_field_to_mesh_attrib<M>(
1185    attribs: Vec<model::Attribute>,
1186    mesh: &mut M,
1187    orig_map: Option<&[usize]>,
1188) where
1189    M: Attrib + FaceVertex,
1190    FaceVertexIndex: AttribIndex<M>,
1191{
1192    for attrib in attribs {
1193        if !special_field_attributes().contains(&attrib.name()) {
1194            continue;
1195        }
1196        if let model::Attribute::Field { data_array, .. } = attrib {
1197            for model::FieldArray { name, elem, data } in data_array {
1198                let name = name.as_str();
1199                match elem {
1200                    // Note that only the first found attribute with the same name and location
1201                    // will be inserted.
1202                    1 => match_buf!( &data, v => insert_array_attrib::<_, _, FaceVertexIndex>(v, name, mesh, orig_map) ),
1203                    2 => match_buf!( &data, v => insert_array_attrib_n::<_, _, FaceVertexIndex,U2>(v, name, mesh, orig_map) ),
1204                    3 => match_buf!( &data, v => insert_array_attrib_n::<_, _, FaceVertexIndex,U3>(v, name, mesh, orig_map) ),
1205                    4 => match_buf!( &data, v => insert_array_attrib_n::<_, _, FaceVertexIndex,U4>(v, name, mesh, orig_map) ),
1206                    _ => continue,
1207                }
1208                .unwrap_or_else(|err| eprintln!("WARNING: Face Vertex Attribute transfer error for \"{}\": {}", name, err))
1209            }
1210        } // Ignore all other attributes
1211    }
1212}
1213
1214#[cfg(test)]
1215mod tests {
1216    use super::*;
1217
1218    #[test]
1219    fn basic_test() {
1220        let vtk = model::Vtk {
1221            version: model::Version::new((0, 1)),
1222            title: String::from("Tetrahedral Mesh"),
1223            byte_order: model::ByteOrder::BigEndian,
1224            file_path: None,
1225            data: model::DataSet::inline(model::UnstructuredGridPiece {
1226                points: vec![
1227                    2., 1., 0., 0., 0., 1., 0., 1., 1., 1., 1., 1., 2., 1., 1., 0., 1., 2., 2., 1.,
1228                    2., 1., 1., 4., 2., 1., 4., 1., 1., 5., 2., 1., 5.,
1229                ]
1230                .into(),
1231                cells: model::Cells {
1232                    cell_verts: model::VertexNumbers::Legacy {
1233                        num_cells: 3,
1234                        vertices: vec![4, 1, 3, 2, 5, 4, 0, 4, 3, 6, 4, 9, 10, 8, 7],
1235                    },
1236                    types: vec![model::CellType::Tetra; 3],
1237                },
1238                data: model::Attributes {
1239                    point: vec![
1240                        model::Attribute::scalars("scalars", 1).with_data(vec![
1241                            0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0f32,
1242                        ]),
1243                        model::Attribute::vectors("vectors").with_data(vec![
1244                            1., 0., 0., 1., 1., 0., 0., 2., 0., 1., 0., 0., 1., 1., 0., 0., 2., 0.,
1245                            1., 0., 0., 1., 1., 0., 0., 2., 0., 0., 0., 1., 0., 0., 1.,
1246                        ]),
1247                        model::Attribute::tensors("tensors").with_data(vec![
1248                            1., 0., 0., 1., 1., 0., 0., 2., 0., 1., 0., 0., 1., 1., 0., 0., 2., 0.,
1249                            1., 0., 0., 1., 1., 0., 0., 2., 0., 0., 0., 1., 0., 0., 1., 1., 0., 0.,
1250                            1., 1., 0., 0., 2., 0., 1., 0., 0., 1., 1., 0., 0., 2., 0., 1., 0., 0.,
1251                            1., 1., 0., 0., 2., 0., 0., 0., 1., 0., 0., 1., 1., 0., 0., 1., 1., 0.,
1252                            0., 2., 0., 1., 0., 0., 1., 1., 0., 0., 2., 0., 1., 0., 0., 1., 1., 0.,
1253                            0., 2., 0., 0., 0., 1., 0., 0., 1.,
1254                        ]),
1255                    ],
1256                    cell: vec![],
1257                },
1258            }),
1259        };
1260
1261        let vtktetmesh = vtk.extract_tetmesh().unwrap();
1262
1263        let pts = vec![
1264            [2., 1., 0.],
1265            [0., 0., 1.],
1266            [0., 1., 1.],
1267            [1., 1., 1.],
1268            [2., 1., 1.],
1269            [0., 1., 2.],
1270            [2., 1., 2.],
1271            [1., 1., 4.],
1272            [2., 1., 4.],
1273            [1., 1., 5.],
1274            [2., 1., 5.],
1275        ];
1276        let indices = vec![[1, 3, 2, 5], [0, 4, 3, 6], [9, 10, 8, 7]];
1277        let mut tetmesh = TetMesh::new(pts, indices);
1278
1279        let scalars = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0f32];
1280        let vectors = vec![
1281            [1., 0., 0.],
1282            [1., 1., 0.],
1283            [0., 2., 0.],
1284            [1., 0., 0.],
1285            [1., 1., 0.],
1286            [0., 2., 0.],
1287            [1., 0., 0.],
1288            [1., 1., 0.],
1289            [0., 2., 0.],
1290            [0., 0., 1.],
1291            [0., 0., 1.],
1292        ];
1293        let tensors = vec![
1294            [[1., 0., 0.], [1., 1., 0.], [0., 2., 0.]],
1295            [[1., 0., 0.], [1., 1., 0.], [0., 2., 0.]],
1296            [[1., 0., 0.], [1., 1., 0.], [0., 2., 0.]],
1297            [[0., 0., 1.], [0., 0., 1.], [1., 0., 0.]],
1298            [[1., 1., 0.], [0., 2., 0.], [1., 0., 0.]],
1299            [[1., 1., 0.], [0., 2., 0.], [1., 0., 0.]],
1300            [[1., 1., 0.], [0., 2., 0.], [0., 0., 1.]],
1301            [[0., 0., 1.], [1., 0., 0.], [1., 1., 0.]],
1302            [[0., 2., 0.], [1., 0., 0.], [1., 1., 0.]],
1303            [[0., 2., 0.], [1., 0., 0.], [1., 1., 0.]],
1304            [[0., 2., 0.], [0., 0., 1.], [0., 0., 1.]],
1305        ];
1306
1307        tetmesh
1308            .insert_attrib_data::<_, VertexIndex>("scalars", scalars)
1309            .ok()
1310            .unwrap();
1311
1312        tetmesh
1313            .insert_attrib_data::<_, VertexIndex>("tensors", tensors)
1314            .ok()
1315            .unwrap();
1316        tetmesh
1317            .insert_attrib_data::<_, VertexIndex>("vectors", vectors)
1318            .ok()
1319            .unwrap();
1320
1321        // vtk -> tetmesh test
1322        assert_eq!(vtktetmesh, tetmesh);
1323
1324        // tetmesh -> vtk test
1325        let tetmeshvtk = convert_tetmesh_to_vtk_format(&tetmesh).unwrap();
1326        let vtktetmesh = tetmeshvtk.extract_tetmesh().unwrap();
1327        assert_eq!(vtktetmesh, tetmesh);
1328    }
1329
1330    fn vtk_polymesh_example_data() -> (IOBuffer, model::VertexNumbers, model::Attributes) {
1331        let (buf, mut attrib) = vtk_pointcloud_example_data();
1332        let cell_verts = model::VertexNumbers::Legacy {
1333            num_cells: 3,
1334            vertices: vec![4, 1, 3, 2, 5, 4, 0, 4, 3, 6, 4, 9, 10, 8, 7],
1335        };
1336        attrib.cell = vec![model::Attribute::scalars("scalars", 1).with_data(vec![0.0, 1.0, 2.0])];
1337
1338        (buf, cell_verts, attrib)
1339    }
1340    fn vtk_polymesh_example_mixed_polydata() -> (
1341        IOBuffer,
1342        model::VertexNumbers,
1343        model::VertexNumbers,
1344        model::Attributes,
1345    ) {
1346        let (buf, mut attrib) = vtk_pointcloud_example_data();
1347        let polys = model::VertexNumbers::Legacy {
1348            num_cells: 2,
1349            vertices: vec![4, 1, 3, 2, 5, 4, 0, 4, 3, 6],
1350        };
1351        let lines = model::VertexNumbers::Legacy {
1352            num_cells: 1,
1353            vertices: vec![4, 9, 10, 8, 7],
1354        };
1355        attrib.cell =
1356            vec![model::Attribute::scalars("scalars", 1).with_data(vec![0.0, 1.0, 2.0, 2.0, 2.0])];
1357
1358        (buf, polys, lines, attrib)
1359    }
1360
1361    /// Produce an example polymesh for testing that corresponds to the vtk model returned by
1362    /// `vtk_polymesh_example_data`.
1363    fn polymesh_example() -> PolyMesh<f64> {
1364        let pts = vec![
1365            [2., 1., 0.],
1366            [0., 0., 1.],
1367            [0., 1., 1.],
1368            [1., 1., 1.],
1369            [2., 1., 1.],
1370            [0., 1., 2.],
1371            [2., 1., 2.],
1372            [1., 1., 4.],
1373            [2., 1., 4.],
1374            [1., 1., 5.],
1375            [2., 1., 5.],
1376        ];
1377        let faces: Vec<usize> = vec![4, 1, 3, 2, 5, 4, 0, 4, 3, 6, 4, 9, 10, 8, 7];
1378        let mut polymesh = PolyMesh::new(pts, &faces);
1379
1380        let face_scalars = vec![0.0, 1.0, 2.0];
1381        let scalars = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0f32];
1382        let vectors = vec![
1383            [1., 0., 0.],
1384            [1., 1., 0.],
1385            [0., 2., 0.],
1386            [1., 0., 0.],
1387            [1., 1., 0.],
1388            [0., 2., 0.],
1389            [1., 0., 0.],
1390            [1., 1., 0.],
1391            [0., 2., 0.],
1392            [0., 0., 1.],
1393            [0., 0., 1.],
1394        ];
1395        let tensors = vec![
1396            [[1., 0., 0.], [1., 1., 0.], [0., 2., 0.]],
1397            [[1., 0., 0.], [1., 1., 0.], [0., 2., 0.]],
1398            [[1., 0., 0.], [1., 1., 0.], [0., 2., 0.]],
1399            [[0., 0., 1.], [0., 0., 1.], [1., 0., 0.]],
1400            [[1., 1., 0.], [0., 2., 0.], [1., 0., 0.]],
1401            [[1., 1., 0.], [0., 2., 0.], [1., 0., 0.]],
1402            [[1., 1., 0.], [0., 2., 0.], [0., 0., 1.]],
1403            [[0., 0., 1.], [1., 0., 0.], [1., 1., 0.]],
1404            [[0., 2., 0.], [1., 0., 0.], [1., 1., 0.]],
1405            [[0., 2., 0.], [1., 0., 0.], [1., 1., 0.]],
1406            [[0., 2., 0.], [0., 0., 1.], [0., 0., 1.]],
1407        ];
1408
1409        polymesh
1410            .insert_attrib_data::<_, FaceIndex>("scalars", face_scalars)
1411            .unwrap();
1412
1413        polymesh
1414            .insert_attrib_data::<_, VertexIndex>("scalars", scalars)
1415            .unwrap();
1416
1417        polymesh
1418            .insert_attrib_data::<_, VertexIndex>("tensors", tensors)
1419            .unwrap();
1420
1421        polymesh
1422            .insert_attrib_data::<_, VertexIndex>("vectors", vectors)
1423            .unwrap();
1424
1425        polymesh
1426    }
1427
1428    #[test]
1429    fn unstructured_data_polymesh_test() {
1430        let (points, cell_verts, data) = vtk_polymesh_example_data();
1431        let vtk = model::Vtk {
1432            version: model::Version::new((0, 1)),
1433            title: String::from("Polygonal Mesh"),
1434            byte_order: model::ByteOrder::BigEndian,
1435            file_path: None,
1436            data: model::DataSet::inline(model::UnstructuredGridPiece {
1437                points,
1438                cells: model::Cells {
1439                    cell_verts: cell_verts.clone(),
1440                    types: vec![model::CellType::Polygon; cell_verts.num_cells() as usize],
1441                },
1442                data,
1443            }),
1444        };
1445
1446        let vtkpolymesh = vtk.extract_polymesh().unwrap();
1447        let polymesh = polymesh_example();
1448
1449        // vtk -> polymesh test
1450        assert_eq!(vtkpolymesh, polymesh);
1451
1452        // polymesh -> vtk test
1453        let polymeshvtk =
1454            convert_polymesh_to_vtk_format(&polymesh, VTKPolyExportStyle::UnstructuredGrid)
1455                .unwrap();
1456        let vtkpolymesh = polymeshvtk.extract_polymesh().unwrap();
1457        assert_eq!(vtkpolymesh, polymesh);
1458    }
1459
1460    /// Produce an example polymesh for testing that corresponds to the vtk model returned by
1461    /// `vtk_polymesh_example_data`. This version interprets the last poly in `vtk_polymesh_example_data` as a polyline.
1462    fn polymesh_example_with_polyline() -> PolyMesh<f64> {
1463        let pts = vec![
1464            [2., 1., 0.],
1465            [0., 0., 1.],
1466            [0., 1., 1.],
1467            [1., 1., 1.],
1468            [2., 1., 1.],
1469            [0., 1., 2.],
1470            [2., 1., 2.],
1471            [1., 1., 4.],
1472            [2., 1., 4.],
1473            [1., 1., 5.],
1474            [2., 1., 5.],
1475        ];
1476        let faces: Vec<usize> = vec![4, 1, 3, 2, 5, 4, 0, 4, 3, 6, 2, 9, 10, 2, 10, 8, 2, 8, 7];
1477        let mut polymesh = PolyMesh::new(pts, &faces);
1478
1479        let face_scalars = vec![0.0, 1.0, 2.0, 2.0, 2.0];
1480        let scalars = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0f32];
1481        let vectors = vec![
1482            [1., 0., 0.],
1483            [1., 1., 0.],
1484            [0., 2., 0.],
1485            [1., 0., 0.],
1486            [1., 1., 0.],
1487            [0., 2., 0.],
1488            [1., 0., 0.],
1489            [1., 1., 0.],
1490            [0., 2., 0.],
1491            [0., 0., 1.],
1492            [0., 0., 1.],
1493        ];
1494        let tensors = vec![
1495            [[1., 0., 0.], [1., 1., 0.], [0., 2., 0.]],
1496            [[1., 0., 0.], [1., 1., 0.], [0., 2., 0.]],
1497            [[1., 0., 0.], [1., 1., 0.], [0., 2., 0.]],
1498            [[0., 0., 1.], [0., 0., 1.], [1., 0., 0.]],
1499            [[1., 1., 0.], [0., 2., 0.], [1., 0., 0.]],
1500            [[1., 1., 0.], [0., 2., 0.], [1., 0., 0.]],
1501            [[1., 1., 0.], [0., 2., 0.], [0., 0., 1.]],
1502            [[0., 0., 1.], [1., 0., 0.], [1., 1., 0.]],
1503            [[0., 2., 0.], [1., 0., 0.], [1., 1., 0.]],
1504            [[0., 2., 0.], [1., 0., 0.], [1., 1., 0.]],
1505            [[0., 2., 0.], [0., 0., 1.], [0., 0., 1.]],
1506        ];
1507
1508        polymesh
1509            .insert_attrib_data::<_, FaceIndex>("scalars", face_scalars)
1510            .unwrap();
1511
1512        polymesh
1513            .insert_attrib_data::<_, VertexIndex>("scalars", scalars)
1514            .unwrap();
1515
1516        polymesh
1517            .insert_attrib_data::<_, VertexIndex>("tensors", tensors)
1518            .unwrap();
1519
1520        polymesh
1521            .insert_attrib_data::<_, VertexIndex>("vectors", vectors)
1522            .unwrap();
1523
1524        polymesh
1525    }
1526
1527    #[test]
1528    fn mixed_unstructured_data_polymesh_test() {
1529        use model::CellType;
1530        let (points, cell_verts, data) = vtk_polymesh_example_data();
1531        let vtk = model::Vtk {
1532            version: model::Version::new((0, 1)),
1533            title: String::from("Polygonal Mesh"),
1534            byte_order: model::ByteOrder::BigEndian,
1535            file_path: None,
1536            data: model::DataSet::inline(model::UnstructuredGridPiece {
1537                points,
1538                cells: model::Cells {
1539                    cell_verts: cell_verts.clone(),
1540                    types: vec![CellType::Polygon, CellType::Polygon, CellType::PolyLine],
1541                },
1542                data: model::Attributes {
1543                    point: data.point,
1544                    // Override the cell data since the polyline is split into separate line segments.
1545                    cell: vec![model::Attribute::scalars("scalars", 1)
1546                        .with_data(vec![0.0, 1.0, 2.0, 2.0, 2.0])],
1547                },
1548            }),
1549        };
1550
1551        let vtkpolymesh = vtk.extract_polymesh().unwrap();
1552        let polymesh = polymesh_example_with_polyline();
1553
1554        // vtk -> polymesh test
1555        assert_eq!(vtkpolymesh, polymesh);
1556
1557        // polymesh -> vtk test
1558        let polymeshvtk =
1559            convert_polymesh_to_vtk_format(&polymesh, VTKPolyExportStyle::UnstructuredGrid)
1560                .unwrap();
1561        let vtkpolymesh = polymeshvtk.extract_polymesh().unwrap();
1562        assert_eq!(vtkpolymesh, polymesh);
1563    }
1564
1565    #[test]
1566    fn poly_data_polymesh_test() {
1567        let (points, polys, data) = vtk_polymesh_example_data();
1568        let vtk = model::Vtk {
1569            version: model::Version::new((0, 1)),
1570            title: String::from("Polygonal Mesh"),
1571            byte_order: model::ByteOrder::BigEndian,
1572            file_path: None,
1573            data: model::DataSet::inline(model::PolyDataPiece {
1574                points,
1575                polys: Some(polys),
1576                data,
1577                ..Default::default()
1578            }),
1579        };
1580
1581        let vtkpolymesh = vtk.extract_polymesh().unwrap();
1582        let polymesh = polymesh_example();
1583
1584        // vtk -> polymesh test
1585        assert_eq!(vtkpolymesh, polymesh);
1586
1587        // polymesh -> vtk test
1588        let polymeshvtk =
1589            convert_polymesh_to_vtk_format(&polymesh, VTKPolyExportStyle::PolyData).unwrap();
1590        let vtkpolymesh = polymeshvtk.extract_polymesh().unwrap();
1591        assert_eq!(vtkpolymesh, polymesh);
1592    }
1593
1594    #[test]
1595    fn mixed_poly_data_polymesh_test() {
1596        let (points, polys, lines, data) = vtk_polymesh_example_mixed_polydata();
1597        let vtk = model::Vtk {
1598            version: model::Version::new((0, 1)),
1599            title: String::from("Polygonal Mesh"),
1600            byte_order: model::ByteOrder::BigEndian,
1601            file_path: None,
1602            data: model::DataSet::inline(model::PolyDataPiece {
1603                points,
1604                polys: Some(polys),
1605                lines: Some(lines),
1606                data,
1607                ..Default::default()
1608            }),
1609        };
1610
1611        let vtkpolymesh = vtk.extract_polymesh().unwrap();
1612        let polymesh = polymesh_example_with_polyline();
1613
1614        // vtk -> polymesh test
1615        assert_eq!(vtkpolymesh, polymesh);
1616
1617        // polymesh -> vtk test
1618        let polymeshvtk =
1619            convert_polymesh_to_vtk_format(&polymesh, VTKPolyExportStyle::PolyData).unwrap();
1620        let vtkpolymesh = polymeshvtk.extract_polymesh().unwrap();
1621        assert_eq!(vtkpolymesh, polymesh);
1622    }
1623
1624    fn vtk_pointcloud_example_data() -> (IOBuffer, model::Attributes) {
1625        (
1626            vec![
1627                2., 1., 0., 0., 0., 1., 0., 1., 1., 1., 1., 1., 2., 1., 1., 0., 1., 2., 2., 1., 2.,
1628                1., 1., 4., 2., 1., 4., 1., 1., 5., 2., 1., 5.,
1629            ]
1630            .into(),
1631            model::Attributes {
1632                point: vec![
1633                    model::Attribute::scalars("scalars", 1).with_data(vec![
1634                        0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0f32,
1635                    ]),
1636                    model::Attribute::vectors("vectors").with_data(vec![
1637                        1., 0., 0., 1., 1., 0., 0., 2., 0., 1., 0., 0., 1., 1., 0., 0., 2., 0., 1.,
1638                        0., 0., 1., 1., 0., 0., 2., 0., 0., 0., 1., 0., 0., 1.,
1639                    ]),
1640                    model::Attribute::tensors("tensors").with_data(vec![
1641                        1., 0., 0., 1., 1., 0., 0., 2., 0., 1., 0., 0., 1., 1., 0., 0., 2., 0., 1.,
1642                        0., 0., 1., 1., 0., 0., 2., 0., 0., 0., 1., 0., 0., 1., 1., 0., 0., 1., 1.,
1643                        0., 0., 2., 0., 1., 0., 0., 1., 1., 0., 0., 2., 0., 1., 0., 0., 1., 1., 0.,
1644                        0., 2., 0., 0., 0., 1., 0., 0., 1., 1., 0., 0., 1., 1., 0., 0., 2., 0., 1.,
1645                        0., 0., 1., 1., 0., 0., 2., 0., 1., 0., 0., 1., 1., 0., 0., 2., 0., 0., 0.,
1646                        1., 0., 0., 1.,
1647                    ]),
1648                ],
1649                cell: vec![],
1650            },
1651        )
1652    }
1653
1654    /// Produce an example pointcloud for testing that corresponds to the vtk model returned by
1655    /// `vtk_pointcloud_example_data`.
1656    fn pointcloud_example() -> PointCloud<f64> {
1657        let pts = vec![
1658            [2., 1., 0.],
1659            [0., 0., 1.],
1660            [0., 1., 1.],
1661            [1., 1., 1.],
1662            [2., 1., 1.],
1663            [0., 1., 2.],
1664            [2., 1., 2.],
1665            [1., 1., 4.],
1666            [2., 1., 4.],
1667            [1., 1., 5.],
1668            [2., 1., 5.],
1669        ];
1670        let mut pointcloud = PointCloud::new(pts);
1671
1672        let scalars = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0f32];
1673        let vectors = vec![
1674            [1., 0., 0.],
1675            [1., 1., 0.],
1676            [0., 2., 0.],
1677            [1., 0., 0.],
1678            [1., 1., 0.],
1679            [0., 2., 0.],
1680            [1., 0., 0.],
1681            [1., 1., 0.],
1682            [0., 2., 0.],
1683            [0., 0., 1.],
1684            [0., 0., 1.],
1685        ];
1686        let tensors = vec![
1687            [[1., 0., 0.], [1., 1., 0.], [0., 2., 0.]],
1688            [[1., 0., 0.], [1., 1., 0.], [0., 2., 0.]],
1689            [[1., 0., 0.], [1., 1., 0.], [0., 2., 0.]],
1690            [[0., 0., 1.], [0., 0., 1.], [1., 0., 0.]],
1691            [[1., 1., 0.], [0., 2., 0.], [1., 0., 0.]],
1692            [[1., 1., 0.], [0., 2., 0.], [1., 0., 0.]],
1693            [[1., 1., 0.], [0., 2., 0.], [0., 0., 1.]],
1694            [[0., 0., 1.], [1., 0., 0.], [1., 1., 0.]],
1695            [[0., 2., 0.], [1., 0., 0.], [1., 1., 0.]],
1696            [[0., 2., 0.], [1., 0., 0.], [1., 1., 0.]],
1697            [[0., 2., 0.], [0., 0., 1.], [0., 0., 1.]],
1698        ];
1699
1700        pointcloud
1701            .insert_attrib_data::<_, VertexIndex>("scalars", scalars)
1702            .unwrap();
1703
1704        pointcloud
1705            .insert_attrib_data::<_, VertexIndex>("tensors", tensors)
1706            .unwrap();
1707        pointcloud
1708            .insert_attrib_data::<_, VertexIndex>("vectors", vectors)
1709            .unwrap();
1710
1711        pointcloud
1712    }
1713
1714    #[test]
1715    fn poly_data_pointcloud_test() {
1716        let (points, data) = vtk_pointcloud_example_data();
1717        let num_vertices = (points.len() / 3) as u32;
1718        let verts = Some(model::VertexNumbers::Legacy {
1719            num_cells: 1,
1720            vertices: std::iter::once(num_vertices)
1721                .chain(0..num_vertices)
1722                .collect(),
1723        });
1724
1725        let vtk = model::Vtk {
1726            version: model::Version::new((0, 1)),
1727            title: String::from("Point Cloud"),
1728            byte_order: model::ByteOrder::BigEndian,
1729            file_path: None,
1730            data: model::DataSet::inline(model::PolyDataPiece {
1731                points,
1732                verts,
1733                data,
1734                ..Default::default()
1735            }),
1736        };
1737
1738        let vtkpointcloud = vtk.extract_pointcloud().unwrap();
1739        let pointcloud = pointcloud_example();
1740
1741        // vtk -> pointcloud test
1742        assert_eq!(vtkpointcloud, pointcloud);
1743
1744        // pointcloud -> vtk test
1745        let pointcloudvtk =
1746            convert_pointcloud_to_vtk_format(&pointcloud, VTKPolyExportStyle::PolyData).unwrap();
1747        let vtkpointcloud = pointcloudvtk.extract_pointcloud().unwrap();
1748        assert_eq!(vtkpointcloud, pointcloud);
1749    }
1750}