VTU — VTK XML UnstructuredGrid (.vtu)
Field arrays with zero components are not representable by VTK XML and are skipped with a warning, rather than emitting an unreadable NumberOfComponents="0". Empty scalar arrays (zero tuples, one component) remain supported. This also prevents empty MED component-name metadata from corrupting a MED-to-VTU conversion.
The serial VTK XML UnstructuredGrid format: an XML container whose DataArray payloads can be inline ASCII, inline base64 binary, or appended raw/base64 binary, optionally block-compressed.
| Format name | vtu |
| Extensions | .vtu |
| Read / Write | ✓ / ✓ |
| Extra dependencies | — |
Reading & writing
import meshioplusplus
mesh = meshioplusplus.read("mesh.vtu")
meshioplusplus.vtu.write("out.vtu", mesh,
binary=True,
compression="zlib", # "zlib", "lzma", "lz4", "zstd", or None
header_type=None, # "UInt32" or "UInt64"
appended=False, # True: raw binary in one <AppendedData> section
)binary— base64-encoded binary DataArrays (True) or ASCII.compression— block compression filter for binary data (vtkZLibDataCompressor/vtkLZMADataCompressor, or none).header_type— integer type used for the binary block header/sizes (defaultUInt32).appended(since v16.21.0) — write every array asformat="appended"into one<AppendedData encoding="raw">section at the end of the file: the same header and (compressed) blocks asbinary, as raw bytes rather than base64, so the file is about a quarter smaller and is read without a base64 decode. VTK's own writers default to this layout. It needsbinary=True, and either engine writes it (the C++ core forNone/zlib/lz4/zstd, the Python reference forlzmatoo). The same encoding isWriteEncoding::RawAppendedin C++ (write_vtu_appended),MIO_ENCODING_RAW_APPENDEDin C,"raw_appended"for the pipeline, WASM and the MCPconverttool'smode, and--appendedin both CLIs; every other format refuses it by name.
File structure
<VTKFile type="UnstructuredGrid" byte_order="..." [compressor="..."] [header_type="..."]>
<UnstructuredGrid>
<Piece NumberOfPoints=".." NumberOfCells="..">
<Points><DataArray .../></Points>
<Cells>
<DataArray Name="connectivity" .../>
<DataArray Name="offsets" .../>
<DataArray Name="types" .../>
<!-- polyhedron only: -->
<DataArray Name="faces" .../><DataArray Name="faceoffsets" .../>
</Cells>
<PointData><DataArray Name=".." .../></PointData>
<CellData><DataArray Name=".." .../></CellData>
</Piece>
<FieldData><DataArray Name=".." .../></FieldData>
</UnstructuredGrid>
<AppendedData encoding="base64">_<blob></AppendedData>
</VTKFile>With appended=True the arrays declare format="appended" offset="..." instead of an inline format="binary" body, and the file ends in <AppendedData encoding="raw">_<blob></AppendedData> — one section holding every array's bytes back to back, addressed by offset, rather than a base64 body per array.
Binary encoding scheme (matching VTK's own convention exactly, so files round-trip byte-for-byte with other VTK tools):
- Uncompressed:
base64(header[header_type: total_nbytes] + raw_bytes). - Compressed:
base64(header[nblocks, blocksize=32768, last_block_size, csize_0..csize_{n-1}]), followed by a separate base64 blob ofconcat(compressed_block_0..n-1). Header fields use the file's declaredheader_typedtype throughout. - Raw appended (
appended=True,encoding="raw"): the same header and (compressed) blocks as above, written as raw bytes rather than base64 — no_-prefixed padding trick needed since there is no text encoding to escape from. On a 216,000-hexahedron grid this wrote 2.6× faster and read 6.9× faster than inline binary, and with zlib 1.6× and 1.4× faster respectively; VTK'svtkXMLUnstructuredGridReaderreads the files.
Cell types
The full VTK cell set, including VTK Lagrange high-order cells (VTK_LAGRANGE_*) and, since v16.40.0, VTK's rational Bézier cells (VTK_BEZIER_*, types 75–81). See VTK for the shared numeric type-code table.
Data mapping
<PointData>/<CellData> map generically to point_data/cell_data; named regions round-trip as region:<kind>:<name> <FieldData> arrays (v16.27.0; VTU has no native set concept, so this is a meshio++ convention, documented there). point_sets/cell_sets are views over regions and travel the same way; before v16.27.0 they were converted into data arrays.
<FieldData> ↔ mesh.field_data (v15.0.0): mesh-level arrays travel in a <FieldData> element on the grid, before the <Piece>, exactly where VTK's own writers put it, one <DataArray> per name with the NumberOfTuples VTK requires (and NumberOfComponents for a two-or-more-dimensional array; a higher rank is flattened to (tuples, components), and a rank-0 scalar reads back as a length-1 array). Reading accepts it on the grid and inside a <Piece> (the piece's overriding the grid's); an array of a non-numeric type (type="String") has no meshio++ dtype and is skipped with a warning rather than failing the read. A mesh without field data writes no <FieldData> element at all, so every existing file is byte-identical. A TimeValue array is VTK's "time in field data" convention: ParaView reads it as the dataset's time step (verified against ParaView 6.1.1), and a .pvd entry with no timestep= takes its step time from it. A value that is not a numeric array has no VTK type and is not written (Python warns).
Bézier cells and rational weights
A Bézier cell (since v16.40.0) is read as a ragged VTK_BEZIER_* block, its control points in the node order of the Lagrange cell of the same shape (cell types). VTK finds a Bézier cell's weights and degrees through two section attributes, <PointData RationalWeights="…"> and <CellData HigherOrderDegrees="…">, each naming one of the section's arrays. Both engines read the array an attribute names under the attribute's own name, RationalWeights (point data) or HigherOrderDegrees (cell data), whatever the file calls it, so the weights reach every surface without a side channel; an unmarked array that already has that name gives way, with a warning. Writing marks a RationalWeights point array and a HigherOrderDegrees cell array the same way, which is what VTK and ParaView read (test_vtk_reads_the_weights_we_mark checks it against VTK 9.7.1); a mesh with neither writes the same bytes as before. .pvtu index files and VTKHDF carry the cells but not the markers. The test files in tests/python/bezier_vtu_files.py were written by VTK itself (tools/gen_bezier_vtu_fixtures.py regenerates them).
Quirks & limitations
- Appended data (since v16.6.0 in both engines):
<AppendedData encoding="raw">is cut out of the file as bytes before the XML is parsed (a raw payload may hold any byte,<included), and everyformat="appended"array is read at itsoffsetinto it;encoding="base64"payloads are decoded the same way, whether each array's header was encoded together with its body or apart from it (VTK's appended writer pads them separately). Offsets padded with blanks (offset=" 1660", as FEconv writes them) are accepted. Before v16.6.0 the C++ reader refused any<AppendedData>and the Python fallback failed on padded offsets and on base64 appended data. - Byte order:
byte_order="BigEndian"is honoured for headers and array bodies, inline or appended, in both engines; arrays are returned in native order. - lzma compression is native in a build with
MESHIOPLUSPLUS_WITH_LZMA=ON(liblzma, see codecs); without it the C++ reader/writer decline and Python handles it. - Polyhedron cells are read and written by both engines, including meshes that mix polyhedra with other cell types in one file (the C++ reader and writer carry VTU's
faces/faceoffsetsstreams, with-1marking a non-polyhedral cell; the Python writer has accepted mixing since v9.19.0), as an OpenFOAM-derived mesh always requires. Before v16.6.0 the Python writer (which writes every polyhedral file: the C++ writer is not reached for ragged blocks) put each polyhedron'sfaceoffsetsentry one short when an ordinary cell preceded it, and VTK 9.4+ (ParaView 6) refused such files. - Multi-
<Piece>files are merged by both engines (since v16.6.0; the C++ reader used to refuse them and the Python one kept only the last piece's cells): points are concatenated, each piece's node ids, offsets and polyhedron face streams shifted accordingly, and point and cell data kept when every piece has the array; an array missing from some piece is dropped with a warning. A piece that declares points but has no<Points>is aReadError. - The C++ writer streams binary arrays (unreleased): each array is generated, compressed and base64-encoded 8 MiB at a time, so only its compressed bytes are held, and the points and fixed-size cell arrays are produced a window at a time rather than built whole. Writing a 10.4M-tetrahedron mesh as zlib VTU needs about half the mesh in extra memory (it needed three times as much); the files are byte-identical to before. The VTP, VTR, VTS and VTI writers share the encoder. Measurements: long-run findings.
- A
header_typeother than the default (None, meaningUInt32) always forces the Python path. - 2D points are auto-padded to 3D on write (warning in Python; silent in C++).
- Byte order: the Python writer records the system's native byte order in the
byte_orderattribute; the C++ writer always declaresLittleEndian.
Notes
tests/python/meshes/vtu/00_raw_binary.vtu,01_raw_binary_int64.vtu(uses an Int64 header type),02_raw_compressed.vtu(zlib-compressed appended data) — each a 162-point, 64-celltetramesh, all read through the raw appended path described above.raw_bigendian.vtu,raw_zlib.vtu,base64_appended.vtu,binary_bigendian.vtuandtwo_pieces_polyhedron.vtu(generated bytools/gen_feconv_quirk_fixtures.py) reproduce the framings of FEconv's samples: the same pyramid-on-a-polyhedral-cube in each, read identically by both engines.- The C++ core handles ASCII, uncompressed binary, and zlib binary (when built with
MESHIO_WITH_ZLIB; otherwise the Python stdlib handles zlib too) — see native acceleration.