OpenUSD plugins for VRM avatars.
This repository is an OpenUSD plugin workspace: it separates schema definitions, file-format import, package resolution, and shared GLB container parsing into independently buildable, independently testable components. The v0.5.0 release ships four plugin bundles, four shared libraries, and two CLIs.
The importer reads VRM 0.x and 1.0, normalizes the differences away, and authors a static USD stage. It never evaluates or simulates — that boundary is the project's central design decision, and it is described below.
Built with OpenStrata.
usd-vrm-pluginsis OpenStrata's first external adopter, and theostCLI is how this workspace is built, tested, packaged, and released. The record of adopting it — every version from pre-0.3 to 0.19.0, including what broke — is published in docs/reports/ost/. The repo is dual-mode: everything also builds with plain CMake against any OpenUSD install, with noostinvolved.
| Component | Type | Role | Status |
|---|---|---|---|
vrmSchema |
USD schema bundle (usd-schema) |
VRM typed API schemas + the schema contract | Shipped |
usdVrmFileFormat |
SdfFileFormat bundle (usd-fileformat) |
.vrm parsing, canonicalization, USD authoring |
Shipped |
usdVrmPackageResolver |
ArPackageResolver bundle (usd-package-resolver) |
Embedded resource resolution from .vrm |
Shipped |
vrmContainer |
Plain CMake library | GLB parsing + byte-range validation | Shipped |
usdVrmaFileFormat |
SdfFileFormat bundle (usd-fileformat) |
.vrma motion clips → canonical UsdSkelAnimation |
v0.3.0 |
motionCore |
Plain static CMake library | Vendor-neutral humanoid pose / animation / root-motion / constraint types | v0.3.0 |
motionRuntime |
Plain static CMake library | Timestamped pose buffer, interpolation, resample, filter, blend; live-capture intake, recorded traces, replay | v0.4.0 · v0.5.0 |
vrmRetarget |
Plain static CMake library | Humanoid mapping, rest-pose correction, root-motion policy, pose retargeter | v0.4.0 |
motion_retarget |
CLI executable | Bakes a semantic clip onto a target rig as UsdSkelAnimation |
v0.4.0 |
motion_capture |
CLI executable | Replays a recorded capture session into a semantic clip the above consumes unchanged | v0.5.0 |
usdVrm |
Aggregate product name | Composed distribution of the workspace | Shipped via ost plugin package --workspace --product |
usdVrm is not a bundle id — it names the product as a whole. It was the
file-format bundle's name until the workspace split; documentation and artifacts
that predate that rename use it in the old sense.
motionCore and usdVrmaFileFormat were the v0.3.0 foundation; v0.4.0 added
motionRuntime, vrmRetarget, and the motion_retarget CLI, which together
make a .vrma clip play back on a real avatar. v0.5.0 adds the observation
side — a vendor-neutral LiveCaptureSource, a recorded-trace format, and the
motion_capture CLI — which produces the same semantic clip, so a live
session is baked by the retarget tool unchanged. The fixed contract is
docs/design/MOTION_CONTRACT.md. The exec*
identities remain reserved; runtime evaluation is not part of this release.
| Component | Type | Role |
|---|---|---|
usdVrmaFileFormat |
SdfFileFormat bundle |
.vrma motion clips → UsdSkelAnimation on a canonical semantic humanoid skeleton |
motionCore |
Plain static CMake library | Vendor-neutral pose / animation / root-motion / constraint types |
motionRuntime |
Plain static CMake library | Timestamped pose buffer, interpolation, resample, filter, blend |
vrmRetarget |
Plain static CMake library | Humanoid mapping, rest-pose correction, root-motion policy, pose retargeter |
motion_retarget |
CLI executable | The stage half: reads the rig and the clip, bakes the retargeted UsdSkelAnimation, binds skel:animationSource |
execMotion |
OpenExec bundle | Vendor-neutral motion nodes |
execVrm |
OpenExec bundle | VRM semantics: retarget, root motion, expression, look-at, avatar apply |
adapters/ |
Optional bundles | The only place product names are permitted (e.g. Mocopi, ARDY) |
.vrm and .vrma are deliberately separate file-format plugins with
symmetric structure, and they compose by reference, not subLayer — a
subLayer stack cannot express which skeleton a clip applies to. A third
binding/assembly layer relates them.
usdVrmFileFormat ───────> vrmSchema
│
└───────────────> vrmContainer
usdVrmPackageResolver ──> vrmContainer
usdVrmaFileFormat ──────> vrmContainer, motionCore
motionRuntime ──────────> motionCore
vrmRetarget ────────────> motionCore, motionRuntime
motion_retarget (CLI) ──> vrmRetarget + OpenUSD stage APIs
(planned)
execMotion ─────────────> motionCore, motionRuntime
execVrm ────────────────> vrmSchema, vrmRetarget
adapters/* ─────────────> motionCore, motionRuntime
Five rules keep those edges honest:
vrmSchemadepends on no other bundle or library.usdVrmPackageResolvernever links the file-format bundle; the importer's dependency on the resolver is runtime-only, never link-time.execVrmreads the schema contract from the stage — never the importer's private API or canonical model.vrmRetargetdoes not depend on OpenExec. The retarget core is finished and testable before any OpenExec node exists; the nodes are thin wrappers.- Adapters depend on the core. The core never depends on an adapter, and
motionCorenever sees a vendor SDK, a network protocol, or a product name.
The bundle graph is validated by ost plugin test --workspace, and each
consumer adds a binary link check proving what it does and does not import. Full
contract: docs/architecture/WORKSPACE.md.
.vrm is read as a GLB container (via vendored
cgltf v1.15) and normalized — VRM 0.x and 1.0 differences are
absorbed into a canonical model before any USD is authored — into:
/Asset SkelRoot (or Xform when there is no skeleton), kind=component
customData.vrm.* sourceFormat / sourceVersion / specVersion / meta / rawExtension
geo/ Scope of UsdGeomMesh (one per glTF primitive)
<Mesh> points/normals/st, material binding; skel binding when
skinned, else the glTF node transform as xformOp
mtl/ Scope of UsdShadeMaterial (UsdPreviewSurface)
skel/Skeleton single UsdSkelSkeleton unified across all glTF skins
(bind transforms from the inverse bind matrices)
rig/Humanoid vrm:humanBones:<bone> joint tokens, typed VrmHumanoidAPI
Every /Asset/rig/* control prim carries typed schema data. The schema types
themselves are provided by the vrmSchema bundle; usdVrmFileFormat depends
on schema contract version 1 and authors against it. Raw VRM blocks stay in
customData as the lossless fallback.
Import: VRM bytes ──> canonical model ──> USD stage
Runtime: USD stage + vrmSchema ──> OpenExec / DCC / renderer runtime
The importer authors data only:
- Import is deterministic. The same bytes produce the same stage.
- LookAt, node constraints, and spring bones are written as typed schema data, never executed.
- Evaluation and simulation belong to
execVrm(planned) or an external runtime. - No physics runs at import time.
This keeps import pure, so a runtime can be swapped without touching the importer.
VRM 0.x / 1.0 detection and canonicalization, geometry, UsdPreviewSurface
materials with the full texture set, MToon source preservation
(vrm:mtoon:raw; renderer-specific realization is not implemented), unified
skeleton + skinning from inverse bind matrices, skeletal animation, humanoid
mapping, front-direction normalization, and a coded diagnostic taxonomy.
Per-feature status is in docs/reference/CAPABILITY_MATRIX.md. Supported platforms, OpenUSD versions, and build requirements are in docs/reference/SUPPORTED_CONFIGURATIONS.md. The schema contract is in plugins/vrmSchema/docs/SCHEMA_CONTRACT.md.
See docs/guides/INSTALL.md for release-artifact, OpenStrata, and from-source installation, verification, and troubleshooting.
Install the components you use from the release artifacts. Each release publishes four member bundles and one aggregate product archive. The three VRM bundles are installed together;
usdVrmaFileFormatis independently installable because it has no plugin-bundle dependency. The member bundles are separately addressable, while the aggregate archive keeps the exact workspace closure together. See the install guide for extraction and verification.
Requires ost 0.19+, so requires.bundles and requires.libraries are
composed automatically.
# One-time: adopt an OpenUSD install as the cy2026 runtime.
ost runtime pull cy2026 --profile usd --from-usd /path/to/openusd-install
# Validate the bundle graph, then test every bundle in dependency order.
ost plugin test --workspaceost plugin build plugins/usdVrmFileFormat
ost plugin test plugins/usdVrmFileFormat # L0-L5 verification pyramid
ost plugin build plugins/usdVrmaFileFormat
ost plugin test plugins/usdVrmaFileFormat # L0-L5 + VRMA golden
# Inspect a real avatar. build/test/run/package compose the manifest's
# requires.bundles closure automatically:
ost plugin run plugins/usdVrmFileFormat \
-- python plugins/usdVrmFileFormat/tools/inspect_vrm.py avatar.vrm
# `view` / `test-view` are the exception: they load only what --with names, so
# the runtime siblings must be spelled out or the schema apply fails.
ost plugin view plugins/usdVrmFileFormat avatar.vrm \
--with plugins/vrmSchema --with plugins/usdVrmPackageResolverThe workspace root composes every bundle:
cmake -S . -B build -DCMAKE_PREFIX_PATH=/path/to/openusd-install
cmake --build build --config Release
ctest --test-dir build -C ReleaseEach bundle also builds standalone against installed sibling packages
(find_package(vrmSchema CONFIG REQUIRED)), which is what CI proves; a bundle
never reaches sideways into a sibling's source tree.
The built libUsdVrmFileFormat.{dll,so,dylib} lands in
plugins/usdVrmFileFormat/lib/; add
plugins/usdVrmFileFormat/plugin/resources/usdVrmFileFormat to
PXR_PLUGINPATH_NAME and the lib/ dir to your dynamic-loader path to use it.
Verifies the packaged bundles have no build-tree dependency:
python scripts/clean_install_smoke.py # build + package + extract + smoke
python scripts/clean_install_smoke.py --skip-build # reuse the current buildIt packages the three VRM bundles with ost, extracts them into a fresh directory
outside the repo, and runs the assertions in
plugins/usdVrmFileFormat/tests/clean_install_smoke.py against that extracted
tree: .vrm discovery served from the package, a textured fixture and a corpus
avatar open and validate, and an embedded texture resolves straight from the
.vrm container. Needs ost + a validated cy2026 runtime.
CI is generated from the support matrix in openstrata.ci.yaml
(ost ci generate github). The PR lane (.github/workflows/ost-source-ci.yml)
runs twelve cells — each of the four bundles on hosted Windows / macOS arm64 /
Linux against digest-pinned cy2026 runtimes — building, testing
(--up-to 5; Windows is capped at 4), and packaging each. A weekly scheduled
lane (ost-support-matrix.yml) re-validates pinned runtime × plugin artifact
cells on a self-hosted real runtime.
Pushing a tag vX.Y.Z (matching VERSION, with that version's
CHANGELOG.md section finalized) runs .github/workflows/release.yml: it builds
on all three OS cells, proves the packaged artifact (packaged-artifact
verification, clean-install smoke, digest-reproducible packaging), and assembles
a draft GitHub release — per-target lean + debug bundles, a source archive,
SHA256SUMS, and notes rendered from CHANGELOG.md via
docs/contributing/RELEASE_NOTES_TEMPLATE.md.
Publishing the draft is a human decision. Run the workflow manually
(workflow_dispatch) for a dry run that creates no release.
Each bundle carries a buildInfo.json stamp (commit / toolchain / OpenUSD /
build type / schema contract version), surfaced by tools/vrm_report.py.
docs/ is organized by responsibility — the same layout open-strata
and hydra-merlin use:
| docs/architecture/ | The binding workspace contract: identities, dependency directions, artifact naming |
| docs/guides/ | How to install |
| docs/reference/ | What is supported, on what |
| docs/roadmap/ | What is planned next (incomplete work only) |
| docs/releases/ | Per-version release records |
| docs/design/ | Why the significant decisions were made |
| docs/reports/ | Evidence from real runs: the ost dogfooding series + the delivery log |
Release history is in the CHANGELOG; the release version lives in the single-source VERSION file.
Original source and documentation: Apache-2.0 (see LICENSE).
Third-party components keep their own licenses; see
THIRD_PARTY_NOTICES.md. cgltf v1.15 is vendored under
third_party/cgltf with its MIT license.
Local test VRM avatars used during development are not part of this repository and are not redistributed here; mind their individual licenses.