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.6.0 release adds the VMC input adapter and its vmc_record CLI to the
workspace's four plugin bundles, five shared libraries, and three 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 |
vrmAdapterVmc |
Plain static CMake library | VMC Protocol input: OSC-over-UDP datagrams → canonical humanoid motion | v0.6.0 |
vmc_record |
CLI executable | Records and inspects VMC packet captures with a decode report, and exports what the adapter delivered as a capture trace the tools above replay unchanged | v0.6.0 |
vrmAdapterMocopi |
Plain static CMake library | Native live UDP input for one capture product, kept strictly separate from the relay path above | Scaffold, frozen diagnostics and the recorded packet format landed; decoder planned |
motionSource |
Plain static CMake library | Format-neutral source skeleton / animation model, the producer-profile contract, and the converter to canonical humanoid motion | Model landed; profile and converter planned |
motionBvh |
Plain static CMake library | BVH syntax and extraction only — no producer semantics, no default profile | Syntax landed; extraction planned |
motion_bvh_inspect |
CLI executable | Reports what a BVH file contains — hierarchy, channels in declaration order, frames, and per-column value ranges | Reporting landed; profile candidates planned |
motion_bvh_convert |
CLI executable | Converts a BVH file to the avatar-independent semantic clip under an explicitly named profile | Planned |
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.
v0.6.0 supplies the first product-specific input leaf: vrmAdapterVmc decodes
VMC Protocol from OSC-over-UDP through frame assembly and VRM bone mapping into
the existing LiveCaptureSource; vmc_record records the same wire input for
inspection and corpus work, and --export-trace hands what the adapter
delivered to motion_capture as a plain capture trace — the product's tools
consume a live VMC session without linking the adapter, or knowing it exists.
Every corpus behind that is generated — the motion traces from closed-form
maths, the VMC captures from the protocol's shapes — so nothing here has yet met
a real sender or a real device. Recording that evidence is the next release,
along with the other half of the input layer: a capture product sends packets
and writes files, and reading the files is a generic BVH pipeline
(motionBvh + motionSource + a declarative producer profile) rather than that
product's importer. The two halves meet at motionCore and nowhere earlier.
OpenExec evaluation follows, and uses those recordings as its parity input.
Schedule: docs/roadmap/.
| 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 plain libraries + their CLIs | Live input leaves — a VMC Protocol adapter first, then vendor-native and generator adapters. The only place product or protocol names are permitted in code (e.g. VMC, Mocopi, ARDY) |
motionSource · motionBvh |
Plain static CMake libraries | Recorded-file input: BVH syntax, a format-neutral source model, and conversion to canonical humanoid motion under an explicit producer profile |
profiles/motion/ |
Package data | One declarative file per producer and export preset. Product names live here rather than in the libraries that read them |
vrmAdapterVmc |
Plain static CMake library | The first input leaf: VMC Protocol from OSC-over-UDP datagrams through frame assembly and VRM bone mapping to canonical humanoid semantics; includes a recorded-packet corpus and the vmc_record CLI |
.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
vrmAdapterVmc ──────────> motionCore, motionRuntime
vrmAdapterMocopi ───────> motionCore, motionRuntime
(planned)
motionSource ───────────> motionCore
motionBvh ──────────────> motionSource
motion_bvh_convert ─────> motionBvh, motionSource, OpenUSD stage
execMotion ─────────────> motionCore, motionRuntime
execVrm ────────────────> vrmSchema, vrmRetarget
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. - A file reader knows a format and no semantics;
motionSourceknows semantics and no format.motionBvh → motionSourcenever reverses, so a second reader can be added without changing anything above it. - Live input and recorded files meet at
motionCoreand nowhere earlier. An adapter never reaches for a reader, and a reader never reaches for an adapter.
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/<Material> UsdShadeMaterial: identity, binding target, VRM semantics
preview/ UsdShadeNodeGraph holding the UsdPreviewSurface network
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 sixteen cells against digest-pinned cy2026 runtimes on hosted Windows /
macOS arm64 / Linux:
- Twelve bundle cells — each of the four bundles on each OS — building,
testing (
--up-to 5; Windows is capped at 4), and packaging each. - Three workspace cells (
kind: workspace), one per OS, which build the root CMake tree and run its CTest suite. That tree is the only configuration in which the plain libraries and the two CLI tools exist, so these cells are the only coveragemotionCore,motionRuntime,vrmRetarget,vrmContainer,motion_retarget,motion_captureand the whole-workspaceusdvrm_baselinegate get. - One graph cell (
verify: graph), which runsost plugin test --workspace --graph-only— the WORKSPACE.md §2 dependency-direction gate — before anything is built, in milliseconds.
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.
usdVrmFileFormat carries a buildInfo.json stamp (commit / toolchain /
OpenUSD release and PXR_VERSION / OpenExec components / build type / schema
contract version), surfaced by tools/vrm_report.py.
Every bundle is built against OpenUSD 26.08 and nothing else, and against a
26.08 that carries OpenExec. Both are enforced at configure time by
cmake/UsdVrmOpenUsd.cmake, for ost and
plain-CMake builds alike — see
supported configurations.
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.