PVD — ParaView collection (.pvd)
A time-indexed list of VTK XML files: the on-disk face of the sequence engine for ParaView, where a .pvd gives the time slider its steps (v15.0.0). An entry names a serial or parallel XML file — .vtu, .vtp, .vtm, .pvtu or .pvtp, never legacy .vtk — so .pvd → .pvtu → .vtu is the ordinary layout of a partitioned transient run. The format is documented on the ParaView wiki rather than in VTK.
| Format name | pvd |
| Extensions | .pvd |
| Read / Write | ✓ / ✓ |
| Time steps | ✓ — time_step=, read_metadata()["time_values"], read_sequence and write_sequence (fan-in) |
| Extra dependencies | — (zlib for compressed pieces, as VTU) |
Reading & writing
import meshioplusplus
# many steps: one .vtu per step next to the index, the time of each step in the index
meshioplusplus.write_sequence("run.pvd", ((t, mesh_at(t)) for t in times))
mesh = meshioplusplus.read("run.pvd") # step 0
last = meshioplusplus.read("run.pvd", time_step=-1) # the final state
meta = meshioplusplus.read_metadata("run.pvd")
meta["time_values"] # every step's time, off the index alone
for t, mesh in meshioplusplus.read_sequence("run.pvd"): # one mesh alive at a time
...
# one step
meshioplusplus.pvd.write("case.pvd", mesh) # time from field_data["meshio:time"], else 0meshioplusplus.read/write dispatch on the extension, so .pvd needs no explicit format name.
File structure
Writing run.pvd creates a sibling directory named after the index's own stem, holding one .vtu per step, numbered with zero padding (width 4):
run.pvd
run/
run_0000.vtu
run_0001.vtu
run_0002.vtu<?xml version="1.0"?>
<VTKFile type="Collection" version="1.0" byte_order="LittleEndian">
<Collection>
<DataSet timestep="0" part="0" file="run/run_0000.vtu"/>
<DataSet timestep="0.5" part="0" file="run/run_0001.vtu"/>
<DataSet timestep="2" part="0" file="run/run_0002.vtu"/>
</Collection>
</VTKFile>file= is always relative, written with forward slashes and XML-escaped. Times are written as the shortest spelling that reads back exactly (0.1, not 0.10000000000000001), independent of the process locale.
Two axes: time and part
A <DataSet> carries timestep, part and (optionally) group and name. The two index axes map onto the two selectors meshio++ already has:
.pvd attribute | meshio++ selector | default |
|---|---|---|
timestep= | time_step= (ReadOptions::mTimeStep) | step 0, the earliest |
part= | piece= (ReadOptions::mPiece) | unset: every part of the chosen step, merged |
group= / name= | region name | — |
- Steps are the distinct
timestepvalues in ascending order, whatever order the entries are listed in and however unevenly they are spaced. A missingtimestepis taken from the entry's own file (below), else0, as ParaView reads it.time_step=-1is the last step; out of range names the step count. - Parts of a step are its entries, ordered by
partand then by document order.read()merges them (no welding) with oneRegionKind::Cellregion per entry, named fromname=, elsegroup/part_<p>, elsepart_<p>.piece=kkeeps one of them alone, with no region. - A step with a single entry is returned as that file reads: a step that is one
.pvtukeeps its ownpiece_<i>regions and is not wrapped in a second all-cells region. - The chosen step's time is attached as
field_data["meshio:time"], so fan-out and fan-in close on time. read_metadatareports every step's time from the index alone — no piece is opened fortime_values— and summarizes step 0's pieces for the rest, which is what a defaultreadreturns.
meshioplusplus.read("run.pvd", time_step=3) # step 3, all its parts merged
meshioplusplus.read("run.pvd", time_step=3, piece=2) # step 3, part 2 aloneComposition: .pvd → .pvtu → .vtu
The normal layout for a partitioned transient run is one .pvtu per step, each naming its per-part .vtu files, under one .pvd:
for k, (t, mesh) in enumerate(steps):
meshioplusplus.pvtu.write_pieces(f"step{k}.pvtu", meshioplusplus.partition(mesh, 4, ghost_layers=1))
# then a .pvd whose entries are timestep="t" file="step{k}.pvtu"Both levels go through the same piece reader, so they nest with no special case; ghosts="drop" (meshioplusplus.read(..., ghosts="drop"), convert --drop-ghosts, ReadOptions::mGhosts) reaches every child. A parallel index does not nest (a .pvtu names .vtu files only), so no cycle is expressible.
Quirks & limitations
- A plain
writeis one step.meshioplusplus.write("x.pvd", mesh)writes a one-entry collection. Many steps go throughwrite_sequence, which streams them: one mesh alive at a time, and the index is rewritten after every step, so a run that is killed leaves a collection ParaView opens covering every finished step. - Step pieces are
.vtu. Whatever the mesh, each step is written as a.vtu(polygonal data included). ThePvdSeriesWriterclass does not take a piece format. - A file's own time is the fallback for a missing
timestep=. VTK's "time in field data" convention puts aTimeValuearray in a file's<FieldData>; an entry withouttimestep=takes its step time from the file it names:TimeValuefirst, elsemeshio:time, else0. An explicittimestep=always wins. The resolution runs for every entry in both the read andread_metadata, so a summary and a real read cannot group the steps differently. An index whose entries all carrytimestep=— what meshio++ and ParaView write — never opens a piece for it; one that relies on the files' own time opens those files (narrowed to the two arrays in C++). ParaView itself does not do this for a.pvd(it treats a missingtimestepas 0), so writetimestep=when the collection is meant for ParaView, whichwrite_sequencealways does. - Non-finite times are refused on write; a
timestepthat is not a number is refused on read. - An entry whose
file=is missing, does not exist (naming the attribute and the path — an absolute path from another machine is not searched for elsewhere) or is not one of the five XML extensions raisesReadError. An empty collection reads as an empty mesh. - Windows-style backslashes in a
file=attribute are not translated. - ParaView opens it. Vanilla VTK has no collection reader (
vtkPVDReaderships with ParaView), sotests/python/test_pvd_paraview.pydrives a realpvpython(6.1.1 when written; skipped where it is absent): a.pvdof.vtureports the written time steps and each step's geometry, and a.pvdof ghosted.pvtureports the same steps with every halo cell recognised as a ghost cell and the dataset's field data (oneTimeValueper step). PyVista's independent pure-PythonPVDReaderis checked the same way where PyVista is installed.
Notes
PvdSeriesWriter (C++, formats/pvd.hpp) is what the native sequence driver (sequence_to_timeseries) writes a .pvd fan-in through, and so what the C API's mio_sequence_to_timeseries and the wrappers built on it reach; that route is covered by the C++ tests, not by tests of each wrapper. The Python meshioplusplus.pvd.SeriesWriter is its twin and the one write_sequence uses. write_sequence to a .pvd accepts Encoding (ASCII or binary pieces) and rejects Codec and FloatFormat, the rule every transient writer applies to an option it cannot honour.