UniRig does a great job auto-generating skeletons for GLB models, which makes preparing assets for RealityKit much easier. The surprise came when I pulled that GLB into Reality Composer Pro, exported it as USDZ, and loaded it in a RealityKit app: calling ModelEntity for the skeleton just returned nil.
let skeletonIterator = entity.components[ModelComponent.self]!.mesh.contents.skeletons.makeIterator()
The same code works flawlessly with Apple’s sample robot.usdz, so the issue clearly lives in the asset converted from UniRig. Here is what I discovered and how I worked around it.
What I Observed
- Convert the UniRig GLB to USDZ via Reality Composer Pro.
- Load the USDZ in RealityKit and the
ModelEntityreports anilskeleton. - Swap in Apple’s
robot.usdzand everything behaves, confirming the runtime side is fine.
Digging Into The Asset
- Converted the problematic USDZ from binary (
usdc) to text (usda) withusdcatso I could diff it. - Replaced its skeleton with the one from
robot.usdz; the scene loaded, which pointed to naming or structure in the skeleton data. - Checked the
uniform token[] joints = []definition and noticed some joint names in the UniRig output contained slashes (XXXX/YYYY). RealityKit appears to reject skeletons whose joint names include/.
Fixing The Skeleton Names
- Unpack the USDZ you exported from Reality Composer Pro.
- Run
usdcatto convert theusdcfile tousda. - In the
uniform token[] joints = []array, batch-replace joint names that contain/with names that don’t, e.g. turnXXXX/YYYYintoXXXXYYYY. - Repack the edited
usdaback into USDZ.
After stripping the slashes from those joint names and repacking, RealityKit once again exposes the skeleton without any code changes.
Takeaways
RealityKit seems intolerant of joint names that include /. When you pipeline UniRig GLBs through Reality Composer Pro, double-check the skeleton joint names and sanitize them before publishing the USDZ if needed. That one tweak keeps RealityKit happy.*** End Patch
