I Spent Way Too Long Getting Blender Animations into Vision Pro

So here’s the thing about Apple Vision Pro development that nobody warns you about: getting animated 3D characters into your app is way harder than it should be.

I’d been working on a project with some cute low-poly animals–rabbits, foxes, that kind of thing. Each one had maybe 40 animations. Walk cycles, idles, attacks, death animations. Standard game asset stuff. They worked perfectly in Blender. Played great in Unity. Even looked fine in the USD preview on macOS.

But the moment I dropped them into Reality Composer Pro? Nothing. Programmatically loading the assets? Still borked… The model would load, but it just… stood there. T-posing into the void or running one animation then freezing like it was asserting dominance.

The Rabbit Hole

I tried everything. Different export settings. Converting to USDZ through Apple’s tools. Manually editing the USD files. I read so many forum posts and GitHub issues that I started dreaming about skeleton bindings.

The frustrating part was that sometimes it worked. I had this fox character that exported fine, but the rabbit–basically the same rig, same animation setup–refused to cooperate. What was different?

After way too many hours of comparing working and broken files, I figured it out. Actually, a few things:

Problem 1: Blender’s USD exporter doesn’t handle multiple actions well.

If you have 40 actions on an armature (which is normal!), Blender doesn’t automatically concatenate them into a single timeline for export. It kind of just… picks one? Or exports them in a weird overlapping way? I’m still not entirely sure what it’s doing, honestly.

Problem 2: The NLA editor is both the solution and the problem.

You can use Blender’s NLA (Non-Linear Animation) editor to lay out all your actions on a single timeline. Great! But if you export with NLA active, Blender sometimes forgets to set the skel:animationSource property that tells the mesh “hey, use this animation.” The animation data is in the file, but nothing points to it.

Problem 3: The “active action” overrides everything.

This one drove me crazy. Even if you set up your NLA track perfectly, if there’s still an “active action” assigned to the armature, Blender ignores the NLA entirely and just exports that one action. On a 2000-frame timeline. So you get a rabbit that plays “Attack_1” for 83 seconds straight.

The Fix That Actually Works

The solution ended up being a very specific sequence of operations:

  1. Build an NLA track with all your animations laid out sequentially
  2. Clear the active action (set it to None)
  3. Solo the NLA track
  4. Create a new empty action and assign it
  5. Bake the entire NLA into that action
  6. Mute all NLA tracks
  7. Then export

When you do it in this exact order, Blender’s USD exporter sees a clean setup: one action with keyframes, no NLA weirdness. And it works.

I wrote a script to automate this for one character. Then I needed to do it for another character. Then another. At some point I realized I should just make this a proper addon with a UI so I’d stop copy-pasting code and changing variable names.

What the Addon Does

AVP Animation Exporter Pro (I’m bad at names) gives you a panel in Blender’s sidebar where you can:

  • See all your animations in a list
  • Check/uncheck which ones to export
  • Preview any animation with one click
  • Export to USDZ with all the baking handled automatically
  • Get a JSON file with all your frame ranges so your Swift code knows where each animation starts and ends

That last part is actually really useful. Instead of hardcoding frame numbers in your visionOS app, you can load the JSON and look up animations by name:

let idleStart = animMap.views["Idle"]!.start
let startTime = Double(idleStart) / animMap.fps

Way better than having magic numbers scattered through your codebase.

The Stuff I Learned

A few things that might save someone else some time:

Baking is your friend. Yeah, it makes bigger files because you have a keyframe on every single frame. But it’s way more compatible than trying to preserve the action/NLA structure through export. USDZ isn’t really designed for Blender’s animation system anyway.

Reality Composer Pro is picky. Some things that preview fine in macOS’s Quick Look won’t work in Reality Composer Pro. Always test in your actual target environment.

The USD Python library is useful for debugging. If an animation isn’t working, you can pip install usd-core and write a script to inspect exactly what’s in your USDZ file. That’s how I figured out the missing skel:animationSource issue.

Frame padding matters. If you don’t leave a gap between animations, they can blend into each other in weird ways. I default to 10 frames of padding, which is less than half a second at 24fps. Enough to have clean boundaries without wasting too much timeline space.

Is This Just a Blender Problem?

Honestly, kind of? Maya and Cinema 4D seem to have smoother USD export pipelines for skeletal animation. But Blender is free, it’s what a lot of indie developers and hobbyists use, and the Vision Pro community skews toward smaller teams who aren’t paying for Maya licenses.

Blender’s USD support is also relatively new and actively being developed. I’m sure it’ll get better. But if you’re trying to ship something now, you need workarounds.

The Addon is Free

I put it on GitHub. MIT license. Use it, modify it, whatever. If it saves you the hours of debugging I went through, that’s worth it.

Get AVP Animation Exporter Pro on GitHub

There’s also a standalone script version if you don’t want to install an addon. It’s less pretty but does the same thing.

If you run into issues, open a GitHub issue. I can’t promise I’ll fix everything, but I’ll try to help. We’re all just figuring this stuff out together.

The actual technical explanation of what the addon does is in the README on GitHub. This post was more about the “why”–specifically, the mass frustration that led to writing an addon instead of just finishing my actual project. Classic developer move.

Questions or feedback?

2 Comments