Skip to content

Skeleton Path Mismatch

Severity: ⚠️ Warning
Fixable: ✅ Automatic
Prevalence: 🔥 High

The SkelAnimation prim’s joint paths don’t match the Skeleton prim’s joint paths. This causes RealityKit to silently ignore the animation, leaving the model stuck in its bind pose (T-pose). This is one of the most common causes of “my animation works in Blender but not in RealityKit” problems.


  • Pixar USD does not enforce path matching between Skeleton and SkelAnimation - this is a consumer (RealityKit) requirement
  • Apple does not publicly document this requirement in detail

This issue appears frequently in visionOS animation threads, typically manifesting as:

  • “Animation works in QuickLook but not RealityKit”
  • “entity.skeleton returns nil”
  • “availableAnimations is empty”

DCC ToolExport PathTriggers Issue?Notes
BlenderFile → Export → USD (.usda/.usdc)✅ AlwaysAdds /Armature/ or root bone prefix
BlenderVia FBX → Reality Converter🔶 SometimesDepends on armature naming
MixamoDirect download✅ AlwaysUses mixamorig: namespace
MayaUSD Export❌ RarelyUsually produces matching paths
HoudiniUSD ROP🔶 SometimesDepends on skeleton setup
  1. Open Blender with a rigged, animated character
  2. Name the Armature something other than the root bone (e.g., “Armature” with root bone “Hips”)
  3. Export as USD with default settings
  4. Load in Preflight → “Animation Path Mismatch” warning appears
  5. Load in RealityKit → Animation doesn’t play

Minimal test file: /tmp/problematic_skeleton.usda

#usda 1.0
( defaultPrim = "World" upAxis = "Y" )
def SkelRoot "World" {
def Skeleton "Skeleton" {
uniform token[] joints = ["root", "root/spine", "root/spine/head"]
}
def SkelAnimation "Animation" {
# PROBLEM: These paths have an extra "/root/" prefix
uniform token[] joints = ["/root/root", "/root/root/spine", "/root/root/spine/head"]
}
}

The USD UsdSkel schema allows SkelAnimation to use either:

  • Relative paths: "root/spine/head" (relative to skeleton root)
  • Absolute paths: "/World/Character/Skeleton/root/spine/head" (scene-absolute)

Blender’s USD exporter often produces paths with an extra hierarchy level that doesn’t exist in the Skeleton’s joints array.

Blender (most common trigger):

  • Creates an Armature object containing bones
  • USD export includes the Armature name in animation paths but not in Skeleton paths
  • Result: Animation has /Armature/Bone but Skeleton has Bone

Mixamo:

  • Uses mixamorig: namespace prefix on all bones
  • When retargeted to a character without this namespace, paths don’t match

RealityKit attempts to bind animation channels to skeleton joints by exact path match. When paths don’t match:

  • No error is thrown
  • Animation resource appears valid
  • playAnimation() succeeds but nothing moves
  • Model stays in bind pose

Users experience:

  1. Model stuck in T-pose/bind pose - Animation plays but no visible movement
  2. entity.availableAnimations shows animation - The animation IS loaded
  3. entity.model?.mesh.skeleton returns nil - Sometimes the skeleton binding fails entirely
  4. No console errors - Silent failure with no diagnostic output
  5. Works in QuickLook - macOS QuickLook may be more forgiving with path matching

File: PreflightCore/USDClient.swift
Function: checkSkeletonPaths() (lines 259-354)

  1. Collect all joint paths from Skeleton prims into a normalized set
  2. For each SkelAnimation, get the first joint path
  3. Normalize and check if it exists in the Skeleton’s set
  4. If not found, try dropping path components as prefixes:
    • /root/root/spine → try root/spine → match found!
    • The dropped prefix (/root/) is the fix target
// Key detection logic
for i in 1..<components.count {
let suffixArr = components.dropFirst(i)
let normSuffix = normalize(suffixArr.joined(separator: "/"))
if normalizedAll.contains(normSuffix) {
// Found the extraneous prefix
let cleanPrefix = (firstJoint.hasPrefix("/") ? "/" : "") + prefix
results.append(.warning(
"Animation Path Mismatch",
fix: .remapSkeleton(source: cleanPrefix, target: "")
))
}
}

File: PreflightCore/USDSurgeryClient.swift
Function: performPathRemapping()

What the “Remap Skeleton” button does:

  1. Opens the USD stage for editing
  2. Finds all SkelAnimation prims
  3. For each animation’s joints attribute:
    • Strips the detected prefix from each path
    • Ensures paths are relative (no leading /)
  4. Saves the modified stage to a new file
// Fix logic (simplified)
if joint.hasPrefix(source) {
newPath = String(joint.dropFirst(source.count))
// Remove leading slash for relative paths
while newPath.hasPrefix("/") {
newPath = String(newPath.dropFirst())
}
}

Prevent the issue at export time:

  1. Rename Armature to match root bone: If your root bone is “Hips”, name the Armature “Hips” too
  2. Use GLTF/FBX as intermediate: Export to FBX, then convert with Reality Converter
  3. Apply armature before export: In Object mode, select mesh, Ctrl+A → Apply All Transforms

Open the .usda file and find-replace the animation joints:

def SkelAnimation "Animation" {
uniform token[] joints = ["/root/root", "/root/root/spine"]
uniform token[] joints = ["root", "root/spine"]
}

SourceTargetResultNotes
Blender 4.0visionOS 1.1✅ Fix verified/Armature/ prefix stripped
Blender 4.2visionOS 2.0✅ Fix verifiedSame behavior
Mixamo FBXvisionOS 1.1🔶 PartialNamespace requires different handling


DateChange
2024-12-18Initial documentation with automatic fix
2024-12-18Added string-based prefix matching (SdfPath was unreliable)