Wrong bounding boxes for meshes with skeleton animation


I’ve a very strange problem with the calculation of a mesh that has a skeleton animation.

Because I’m using USD in Nvidia Omniverse, I’ve opened a thread in the Omniverse Developer forum, but after handcrafting a sample to reproduce the problem, I think it is an USD problem and not a Omniverse-problem.

In the last post I added a handcrafted usda that works fine and one that produces wrong results, but I cannot see the difference.

Can anyone here have a look at this ( Possible Bug: BoundingBox for prims with geometries with skeleton animation are to large - Omniverse / Developer - NVIDIA Developer Forums )?

Thanks you very much… I’m totally list :slight_smile:


Hey @c.Bickmeier. I’m starting to look at this. There is another thread here that looks related: Getting different results with ComputeWorldBound for '/' when root is xform vs SkelRoot

Hi Mati,

thank you very much, I dont’t think that this is the same problem, because it makes no difference if I have a xfrom as root or not… but it would be great if we could figure out, whats going on.


Actually, that thread went on to talk about computing the bounds of skinned and non-skinned meshes. My test and your screenshot on the OV forum from the arm example seems like the mesh is not taken into account and only the bound of the skeleton is calculated.

Here’s the arm file again: arm.usda (2.1 KB)

I’ve brought it into usdview to take OV out of the mix and I’m running this in the interpreter window:

from pxr import UsdGeom

stage = usdviewApi.stage

prim = stage.GetPrimAtPath("/Model")
boundable = UsdGeom.Boundable(prim)

extent = boundable.CreateExtentAttr()
for x in range(0, 101, 10):
    value = UsdGeom.Boundable.ComputeExtentFromPlugins(boundable, x)
    extent.Set(value, x)
    print(f"Extent at {x} is {value}")

The result shows no width in the X dimension and it’s tightly bound to just the skeleton.

Extent at 0 is [(0, 0, 0), (0, 0, 4)]
Extent at 10 is [(0, -0.28462765, 0), (0, 0, 3.9796433)]
Extent at 20 is [(0, -0.59383637, 0), (0, 0, 3.9098058)]


Thats very interesting… in Omniverse the values for my geometry looks like the bounds of the skeleton… that’s exactly what I thought.

So in USDView this is true for the arm-sample too? But why does omniverse calculate the bounds for the arms correctly?

I get the same flat extent in OV too and your console output for the script looks like the same flat extent.

I get closer if I author an extent on the mesh first:

from pxr import Usd, UsdGeom

stage = usdviewApi.stage

prim = stage.GetPrimAtPath("/Model/Arm")
boundable = UsdGeom.Boundable(prim)
extent = boundable.CreateExtentAttr()
value = UsdGeom.Boundable.ComputeExtentFromPlugins(boundable, Usd.TimeCode.Default())

prim = stage.GetPrimAtPath("/Model")
boundable = UsdGeom.Boundable(prim)

extent = boundable.CreateExtentAttr()
for x in range(0, 101, 10):
    value = UsdGeom.Boundable.ComputeExtentFromPlugins(boundable, x)
    extent.Set(value, x)
    print(f"Extent at {x} is {value}")

Maybe that’s enough to get you by for now, but hopefully someone else can chime in. Feels like a bug.

There are two console output screenshots… one from the arm and one from my geometry… omniverse computes the correct outputs for the arm… the “flat one” is the result for my geometry.

when I author an extent on the mesh, I get the result from the first post in the omniverse forum thread… the bounds are way to large and never changes regardless of the time code.

can you open the arm-sample (from the zip in the omniverse forum), open that in usd compose and run the script from my post? can you confirm that it produces correct results?

Yes, I ran your code on the arm you provided and I got the same output as your first screenshot:

It is changing per sample as you observed, but it is ultimately wrong because the X values are 0. The extent is fitted to the skeleton, not the skeleton + deformed mesh.

The same thing is happening in the example for your geometry, but because your example is a skeleton with only 2 joints and the only thing that is animated is the rotation on the terminating joint, the extent does not change per sample. The bbox is always the same.

So, I think we need to figure out why the deformed mesh is not being included correctly in the calculation. That should fix both cases.

Oh man… you are right… I’m looking at this problem for so long now… I didn’t see that :slight_smile:

But at the end this is better as if it would have worked in this case :slight_smile:

This is really a big problem for us, because we have to position to products (root nodes) based on the position in our product configurator, and to do so, we need to find the center, so correct bounding boxes are very important for us, and this is the last problem that prevents us from using our Omniverse integration in production scenarios… thank you very much for your support

But at the end there is a difference, because my with my geometry the values doesn’t change at all…

TL;DR: You can’t currently compute accurate bounds directly on a SkelRoot or any of its posed Mesh children. What you can do, which it sounds may be sufficient for your needs, @c.Bickmeier , is use UsdSkelBakeSkinning (apologies it does not seem to be documented) to bake out the posed points of all affected meshes (set your UsdEditTarget to the session layer), and then run normal bounds computations on the resulting hierarchy of Meshes.

Computing accurate bounds of any or all Meshes in a UsdSkel requires basically the same, large amount of computation and data access as drawing the thing. UsdSkel was designed primarily to serve the needs of large crowds in VFX, and in VFX, the most important use for bounds is to get a quick, rough segmentation of the scene prior to doing any serious rendering processing. Given that we don’t yet (this may change with OpenExec) have a consistent and manageable way of caching computations in USD, it would be too taxing to do all those computations twice for each render. This the compromise we struck is twofold:

  1. We only provide the posed bbox of the SkelRoot as a whole (which is why it is Boundable), and do not provide any entrypoint for computing bboxes of individual meshes inside the Skel
  2. Even the supplied bbox computation for the SkelRoot is a fairly substantial approximation of the true, tight bbox

We are finding we may want to reconsider several aspects of the UsdSkel design as it get more use in both VFX production, and in many other non-VFX use-cases, like yours! It will take some time, though. Hopefully the workaround can help you out until then.

Hi Sebastian,

thank you very much for your response. I only need the correct bounding box for the default time code.

Could you provide me a smal hint how to do that with UsdSkelBakeSkinning?

Comsider that I have a scene graph with some child node that have gemoetries with skeleton animations… all I need is the the ability to calculate the correct boundingbox of the root node of my scene graph…

That would helo alm lot…

Hi @spiff,

I’ve tested UsdSkelBakeSkinning, and I can confirm that it does work as expected. The bounds are correct now (I still have one geometry where the bounds are not correct, but I think that is due to a misplacement of the bounds).

But as you already mentioned, the result is quite large. A geometry that has 445kb is round about 20mb after baking. This is not a problem, but I think when we use our high resolution geometries it will be a problem (the mentioned geometry for example has 9mb in its high resolution version). Our software has an export method too, that allows the user to export the configured product as fbx etc. when those exports are many hundred mb, this will not work for us).

So I tried this approach:

  1. open stage
  2. remove all existing extents (just to be sure)
  3. export the stage to a tempfile
  4. open a second stage from tempfile
  5. bake the skeleton animation in the temp stage
  6. copy the newly calculated extents over to the original stage
  7. save the original stage

I hoped, to get correct bounding boxes this wax, but without blowing up the size. But to my surprise the result is this:

Here is the corresponding file… from my point of view the extents are correct, but why is the bounding box so large?

Optimized.usda (1.8 MB)

Can you explain how the interval paramter works? I need only the correct bounding box for the default time code… would it be possible to only bake the skeleton animation for timecode 0?

Again… thank you very much for your help


Hi Carl,
If you want to bake points only at timeCode zero (the API won’t allow you to bake the pose at the Default timeCode), then you can specify the interval parameter as Gf.Interval(0.0) .

I am not super-familiar with the skinning or baking logic, and I’m unsure why your bbox is so large. To eliminate the possibility of differing logic in the program used to produce the image vs “native USD”, it might be helpful to bring up your temp stage in usdview, turning on display of both oriented and axis-aligned bboxes, to see if we produced a tight oriented bbox that becomes sloppy after hierarchical rotations are applied.

Finally, just wanted to point out that you should be able to skip the temp-stage workflow. After you have cleared out extents in your root layer of your stage:

with Usd.EditContex(stage, stage.GetSessionLayer()):

Now you can copy extents to the root layer, though you should be careful to only copy extents from the root/SkelRoot prim, because the extents on all the Mesh prims will be the posed extents, which will cause issues if you should need to use the SkelRoot’s builtin extent computation.

1 Like

Hi Sebastian,

your hint about the extents on SkelRoot was the key… thank you so much!!!

Baking the skinning results in extents on the mesh and not on the SkelRoot… after moving the calculated extents from the mesh to the SkelRoot, it works… without the need to blow up the size by duplicating the mesh for each timecode.


The best of all… when playing the animation the bounding box is updated perfectly correct…

That is more than I ever wanted… simply perfect :slight_smile:


Glad you weee able to make a working pipeline, Carl - bounds have been a friction point for quite a few folks trying to use UsdSkel. Definitely room for improvement!