OpenUSD
A reader and writer for OpenUSD stages — the interchange format Omniverse, usdview and every USD-aware DCC tool read. A solve, or a surrogate's prediction, becomes a scrubbable 3-D asset: points are time-sampled per step and topology is re-authored only on the steps where it actually changes.
| Format name | usd |
| Extensions | .usd, .usda, .usdc |
| Read / Write | ✓ / ✓ |
| Extra dependencies | usd-core (the [usd] extra) |
usd-core is Pixar's self-contained PyPI build of USD — no Omniverse install — and is imported lazily, so a build without it is unaffected until the format is actually used, when it raises a named install error.
Reading & writing
import meshioplusplus
meshioplusplus.write("part.usda", mesh) # text
meshioplusplus.write("part.usdc", mesh) # binary crate; the extension picks it
mesh = meshioplusplus.read("part.usda")meshioplusplus.usd.write(
"part.usda", mesh, prim_path="/scene/part", up_axis="Z", meters_per_unit=0.001
)A time-sampled stage from a sequence:
meshioplusplus.write_sequence("solve.usda", ((t, mesh) for t, mesh in steps))
mesh = meshioplusplus.read("solve.usda", time_step=-1) # the last stepread takes time_step (0 is the first, negatives count from the end) and attaches the full list of authored times to the mesh, so read_metadata(...)["time_values"] and the sequence engine's step count both work.
File structure
One UsdGeomMesh per prim (or a UsdGeomPoints for a cell-less mesh) under prim_path, with points, extent, faceVertexCounts/faceVertexIndices and one primvars:<name> per data array. Stage metadata carries the up axis, metres per unit and the time-code range; the root layer's documentation field carries the provenance block.
Topology is written only when its hash changes, recorded in the prim's custom data under physicsNemo:lastTopologyHash (upstream's own spelling, so a stage written by either tool reads the same). A fixed-topology series therefore authors faceVertexIndices once; a remeshed one authors it per change, and stays valid at every step.
Cell types
n-gons are kept, not triangulated: USD stores a face-vertex count per face, so a quad-dominant hex skin arrives as quads. A mesh with 3-D cells is written as its boundary surface; higher-order surface cells are linearized; a mesh with no 2-D or 3-D cells becomes a point cloud.
On the way back, faces are grouped by vertex count into triangle, quad and one ragged polygon block, in that fixed order.
Data mapping
point_data becomes vertex-interpolated primvars and cell_data uniform ones — getting the interpolation token wrong renders plausible, wrong colours, so it is a contract rather than a detail. A width-3 array is a Float3Array, an integer array an IntArray, and anything else a flat array with SetElementSize. faceVarying and constant primvars have no meshio++ counterpart and are skipped with a warning.
A volume mesh's cell_data is gathered onto its skin through the parent-cell map, so a solid arrives coloured by its per-cell material.
Quirks & limitations
- Block identity is not preserved. A USD prim holds one face list, so a mesh with three blocks (triangle, quad, triangle) comes back as two, grouped by vertex count.
- Several prims on one stage are concatenated, with a
usd:primcell array naming which is which andusd:prim_pathsinfield_data. - A single write authors a default value rather than a time sample, so a viewer with no timeline sees plain geometry.
- Provenance lives in the root layer's
documentation. A text.usdatherefore also surrenders it to the ordinary byte scanner; a binary crate layer needsusd-coreinstalled forread_metadatato recover it. - The step count comes from a full read (there is no cheap metadata path), which is the same arrangement Exodus has.