Skip to content

Repository files navigation

USD VRM Plugins

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-plugins is OpenStrata's first external adopter, and the ost CLI 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 no ost involved.

Workspace components

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.

The motion layer

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.

Dependencies

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:

  • vrmSchema depends on no other bundle or library.
  • usdVrmPackageResolver never links the file-format bundle; the importer's dependency on the resolver is runtime-only, never link-time.
  • execVrm reads the schema contract from the stage — never the importer's private API or canonical model.
  • vrmRetarget does 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 motionCore never sees a vendor SDK, a network protocol, or a product name.
  • A file reader knows a format and no semantics; motionSource knows semantics and no format. motionBvh → motionSource never reverses, so a second reader can be added without changing anything above it.
  • Live input and recorded files meet at motionCore and 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.

What the importer produces

.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.

Runtime boundary

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.

Feature support

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.

Install

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; usdVrmaFileFormat is 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.

Build and test

Whole workspace, with OpenStrata (ost)

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 --workspace

A single bundle

ost 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/usdVrmPackageResolver

With plain CMake (no OpenStrata)

The 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 Release

Each 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.

Clean-install smoke

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 build

It 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

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 coverage motionCore, motionRuntime, vrmRetarget, vrmContainer, motion_retarget, motion_capture and the whole-workspace usdvrm_baseline gate get.
  • One graph cell (verify: graph), which runs ost 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.

Release artifacts

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.

Documentation

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.

License

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.

Releases

Packages

Contributors

Languages