# Using BBoxCache to make an auto Guide Cube

**URL:** <https://forum.aousd.org/t/using-bboxcache-to-make-an-auto-guide-cube/684>\
**Category:** USD\
**Tags:** python\
**Created:** [September 20, 2023, 1:21pm UTC](https://forum.aousd.org/t/using-bboxcache-to-make-an-auto-guide-cube/684 "2023-09-20T13:21:37Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![Suekieza](https://sea2.discourse-cdn.com/flex016/user_avatar/forum.aousd.org/suekieza/32/248_2.png) [@Suekieza](https://forum.aousd.org/u/Suekieza)\
**Post date:** [September 20, 2023, 1:21pm UTC](https://forum.aousd.org/t/using-bboxcache-to-make-an-auto-guide-cube/684/1 "2023-09-20T13:21:37Z")

</div>

Hey all,

I’m in the process of trying to create an Auto Guide button for our asset team.  
I have the geo(character,prop whatever) in the model.usd. I compute the bbox for that geometry and would then like to “apply” that to a cube as its new extents.  
Is this possible or am I barking up the wrong tree here?

Thanks Sue

---

<div class="post-metadata">

**Author:** ![BigRoyNL](https://sea2.discourse-cdn.com/flex016/user_avatar/forum.aousd.org/bigroynl/32/413_2.png) [@BigRoyNL](https://forum.aousd.org/u/BigRoyNL)\
**Post date:** [September 20, 2023, 2:05pm UTC](https://forum.aousd.org/t/using-bboxcache-to-make-an-auto-guide-cube/684/2 "2023-09-20T14:05:16Z")

</div>

I’d say [this note in the documentation about Extents/Bounds is related](https://openusd.org/dev/api/class_usd_geom_boundable.html#UsdGeom_Boundable_Extent).

And if you were to compute the extents manually afterwards make sure to do so for every timecode that the object changes in a way that influences its size [as described here](https://openusd.org/dev/api/class_usd_geom_boundable.html#abecc87b5433fec139295a78b439b0531)

The way I understand what you’re trying to do is:

```python
from pxr import UsdGeom

prims = stage.Traverse()
for prim in prims:
    
    if not prim.IsA(UsdGeom.Boundable):
        continue
    
    boundable = UsdGeom.Boundable(prim)
    extent_attr = boundable.GetExtentAttr()
    has_value = False
    if extent_attr.IsValid() and extent_attr.IsAuthored():
        # TODO: Technically this should figure out whether all time samples had their extents computed
        # A value was already set
        print(f"{prim} has extent already set")
        continue
    
    if not extent_attr.IsValid():
        extent_attr = boundable.CreateExtentAttr()
    
    # TODO: This should actually compute for all timecodes
    time = stage.GetStartTimeCode()
    extent = boundable.ComputeExtent(time)
    print(f"Setting {prim} extent {extent} at time {time}")
    extent_attr.Set(extent)

```

Note the todos in there as I’ve marked a lot of it psuedocode. A reference that shows [how to potentially use the BBoxCache is in this example](https://docs.omniverse.nvidia.com/dev-guide/latest/programmer_ref/usd/transforms/compute-prim-bounding-box.html)

Is this roughly your goal?

This does not use the BBoxCache at all since as far as I know it’s mostly intended to be used for bbox queries against xforms (parents of leaf shapes) to compute their global size at a point in time which as the [`UsdGeomBoundable` documentation](https://openusd.org/dev/api/class_usd_geom_boundable.html#UsdGeom_Boundable_Extent) mentions is not intended to be written into the usd files:

> Therefore, we limit to precomputing (generally) leaf-prim extent, which avoids the need to read in large point arrays to compute bounds, and provides [UsdGeomBBoxCache](https://openusd.org/dev/api/class_usd_geom_b_box_cache.html) the means to efficiently compute and (session-only) cache intermediate bounds. You are free to compute and author intermediate bounds into your scenes, of course, which may work well if you have sufficient locks on your pipeline to guarantee that once authored, the geometry and transforms upon which they are based will remain unchanged, or if accuracy of the bounds is not an ironclad requisite.

---

<div class="post-metadata">

**Author:** ![Suekieza](https://sea2.discourse-cdn.com/flex016/user_avatar/forum.aousd.org/suekieza/32/248_2.png) [@Suekieza](https://forum.aousd.org/u/Suekieza)\
**Post date:** [September 20, 2023, 3:44pm UTC](https://forum.aousd.org/t/using-bboxcache-to-make-an-auto-guide-cube/684/3 "2023-09-20T15:44:55Z")

</div>

This is the code I have so far:

```auto
from pxr import Gf, Sdf, Usd, UsdGeom, Vt

stage = Usd.Stage.Open(guide_path)
# Setup scene
root_prim_path = Sdf.Path("/root")
root_prim = stage.DefinePrim(root_prim_path, "Xform")
cone_prim_path = Sdf.Path("/root/cone")
cone_prim = stage.DefinePrim(cone_prim_path, "Cone")
guide = stage.DefinePrim("/guide", "Cube")

root_xformable = UsdGeom.Xformable(root_prim)
root_translate_op = root_xformable.AddTranslateOp()
root_translate_op.Set(Gf.Vec3h([50, 30, 10]))
root_rotate_op = root_xformable.AddRotateZOp()
root_rotate_op.Set(45)
cone_xformable = UsdGeom.Xformable(cone_prim)
cone_translate_op = cone_xformable.AddTranslateOp()
cone_rotate_op = cone_xformable.AddRotateXYZOp()

time_code = Usd.TimeCode.Default() # Determine frame to lookup
bbox_cache = UsdGeom.BBoxCache(time_code, [UsdGeom.Tokens.default_, UsdGeom.Tokens.render],
                               useExtentsHint=False, ignoreVisibility=False)

# Useful for intersection testing:
bbox = bbox_cache.ComputeWorldBound(cone_prim)
print(bbox)
root_geom_model_API = UsdGeom.ModelAPI.Apply(guide)
aligned_range = bbox.ComputeAlignedRange()
print(aligned_range)
extentsHint = Vt.Vec3hArray([Gf.Vec3h(list(aligned_range.GetMin())), Gf.Vec3h(list(aligned_range.GetMax()))])
root_geom_model_API.SetExtentsHint(extentsHint, time_code)

```

So I get extents onto the cube but it doesnt move to where my cone is…  
So this is why im asking if im missunderstanding how to re-apply the bbox info back onto the cube

---

<div class="post-metadata">

**Author:** ![BigRoyNL](https://sea2.discourse-cdn.com/flex016/user_avatar/forum.aousd.org/bigroynl/32/413_2.png) [@BigRoyNL](https://forum.aousd.org/u/BigRoyNL)\
**Post date:** [September 20, 2023, 3:51pm UTC](https://forum.aousd.org/t/using-bboxcache-to-make-an-auto-guide-cube/684/4 "2023-09-20T15:51:12Z")

</div>

Could it be that your cube has extents explicitly defined that override the extents hint and that what you’re seeing as BBox info is the `extents` which you don’t appear to be setting?

If I’m not mistaken these store into different attributes [`extent`](https://openusd.org/dev/api/class_usd_geom_boundable.html#a99f4a8f288a211fc286c3f12f0e87fda) and [`extentsHint`](https://openusd.org/dev/api/class_usd_geom_model_a_p_i.html#a4aa8b1f29a3097fe08da868bd2b8b259).

---

<div class="post-metadata">

**Author:** ![Suekieza](https://sea2.discourse-cdn.com/flex016/user_avatar/forum.aousd.org/suekieza/32/248_2.png) [@Suekieza](https://forum.aousd.org/u/Suekieza)\
**Post date:** [September 20, 2023, 4:45pm UTC](https://forum.aousd.org/t/using-bboxcache-to-make-an-auto-guide-cube/684/5 "2023-09-20T16:45:11Z")

</div>

![image](https://us1.discourse-cdn.com/flex016/uploads/aousd/original/1X/f6d8c0a3fe5590e894cb941764af4699af0b9d17.png)

What the usd looks like inside. I will change extentsHints to extents see what happens

---

<div class="post-metadata">

**Author:** ![Suekieza](https://sea2.discourse-cdn.com/flex016/user_avatar/forum.aousd.org/suekieza/32/248_2.png) [@Suekieza](https://forum.aousd.org/u/Suekieza)\
**Post date:** [September 20, 2023, 4:48pm UTC](https://forum.aousd.org/t/using-bboxcache-to-make-an-auto-guide-cube/684/6 "2023-09-20T16:48:52Z")

</div>

what I’m seeing in Houdini:

 ![image](https://us1.discourse-cdn.com/flex016/uploads/aousd/original/1X/0a55849c115dff5af382c61104b0798cec90ab03.jpeg)

I would have expected the cube to be at the same place as the cone? I’m i understanding this correctly

---

<div class="post-metadata">

**Author:** ![spiff](https://sea2.discourse-cdn.com/flex016/user_avatar/forum.aousd.org/spiff/32/22_2.png) [@spiff](https://forum.aousd.org/u/spiff)\
**Post date:** [September 20, 2023, 6:10pm UTC](https://forum.aousd.org/t/using-bboxcache-to-make-an-auto-guide-cube/684/7 "2023-09-20T18:10:57Z")

</div>

A few things, @Suekieza …

1. Setting extent or extentsHint is **never** going to change an objects rendered or evaluated position - that should always be computed from the Xformable hierarchy directly, plus the geometry. THe only possible exception would be if you are using GeomModelAPI Draw Mode BBox.
2. extentsHint is a Vec3fArray, not a Vec3hArray
3. extentsHint _is_ always optional, and intended to purely be a speed optimization that clients can opt into, so setting it to something other than would be computed from first-principles based on extent attrs and xforms is likely to lead to inconsistent behavior.

So, you _could_ author the `extent` of individual Boundable prims to whatever you want, but it may (or may not!) affect rendering, physics, etc in surprising ways.

---

<div class="post-metadata">

**Author:** ![Suekieza](https://sea2.discourse-cdn.com/flex016/user_avatar/forum.aousd.org/suekieza/32/248_2.png) [@Suekieza](https://forum.aousd.org/u/Suekieza)\
**Post date:** [September 21, 2023, 5:54pm UTC](https://forum.aousd.org/t/using-bboxcache-to-make-an-auto-guide-cube/684/8 "2023-09-21T17:54:06Z")

</div>

Hey hey, Thanks Spiff and Roy for all the help 😃 I got something working will share tomorrow when its not just a jumbled mess of things

---

<div class="post-metadata">

**Author:** ![Suekieza](https://sea2.discourse-cdn.com/flex016/user_avatar/forum.aousd.org/suekieza/32/248_2.png) [@Suekieza](https://forum.aousd.org/u/Suekieza)\
**Post date:** [September 22, 2023, 6:56pm UTC](https://forum.aousd.org/t/using-bboxcache-to-make-an-auto-guide-cube/684/9 "2023-09-22T18:56:46Z")

</div>

Hey hey, so here is the code(simplified) that I ended up with to make my guide cube ;D

```auto
from pxr import Gf, Sdf, Usd, UsdGeom

# Path to the USD file that will contain the guide mesh
guide_path = "./guide.usda"

def get_extent(stage, prim):
    """
    Calculates & sets the extentsHint of a prim based on its World Bounds

    Args:
        stage (Usd.Stage): The USD stage containing the primitive.
        prim (str or Usd.Prim): The address to the primitive such as '/root/cube_model'
                                or the prim object itself.

    Returns:
        extents_hint (Vec3fArray): A two-by-three array containing the endpoints of the
                                   axis-aligned, object-space extent.
    """
    # Convert prim_path string to prim
    if isinstance(prim, str):
        prim_path = prim
        prim = stage.GetPrimAtPath(prim_path)

    # Create a BBoxCache to compute the bounding box
    bbox_cache = UsdGeom.BBoxCache(Usd.TimeCode.Default(),
                                   includedPurposes=[UsdGeom.Tokens.default_,
                                                     UsdGeom.Tokens.render],
                                   useExtentsHint=False,
                                   ignoreVisibility=False)

    # Compute the world bounds of the primitive
    bbox_bounds = bbox_cache.ComputeWorldBound(prim)
    bound_align = bbox_bounds.ComputeAlignedBox()
    bbox_min = bound_align.GetMin()
    bbox_max = bound_align.GetMax()

    def convert_to_float(number):
        # Corrects floating-point formatting issues
        if "e" in str(number):
            string_min = str(number)
            correction = string_min.split("e")
            return float(correction[0])
        return number

    # Correct the bounding box values
    corrected_min = [convert_to_float(number) for number in bbox_min]
    corrected_max = [convert_to_float(number) for number in bbox_max]

    # Compute extents
    extents = [
        corrected_min,
        corrected_max
    ]
    print(extents)

    # Define vertex points for the cube mesh
    vertex_points = [
        Gf.Vec3d(corrected_max[0], corrected_min[1], corrected_max[2]),
        Gf.Vec3d(corrected_min[0], corrected_min[1], corrected_max[2]),
        Gf.Vec3d(corrected_max[0], corrected_max[1], corrected_max[2]),
        Gf.Vec3d(corrected_min[0], corrected_max[1], corrected_max[2]),
        Gf.Vec3d(corrected_min[0], corrected_min[1], corrected_min[2]),
        Gf.Vec3d(corrected_max[0], corrected_min[1], corrected_min[2]),
        Gf.Vec3d(corrected_min[0], corrected_max[1], corrected_min[2]),
        Gf.Vec3d(corrected_max[0], corrected_max[1], corrected_min[2]),
    ]
    return extents, vertex_points

def create_cube_mesh(stage, bbox_extent, vertex_points, default_prim):
    """
    Creates a cube mesh in the USD stage.

    Args:
        stage (Usd.Stage): The USD stage where the cube mesh will be created.
        bbox_extent (Vec3fArray): Extents of the bounding box.
        vertex_points (list of Gf.Vec3d): List of vertex points for the cube.
        default_prim (Usd.Prim): The default primitive where the cube will be added.

    Returns:
        Usd.Prim: The created cube mesh primitive.
    """
    # Create hierarchy for the cube mesh
    asset_prim = stage.DefinePrim(f"{default_prim.GetPath()}", "Xform")
    model_prim = stage.DefinePrim(f"{default_prim.GetPath()}/model", "Xform")
    guide_prim = stage.DefinePrim(f"{default_prim.GetPath()}/model/guide", "Xform")
    cube_mesh = stage.DefinePrim(f"{default_prim.GetPath()}/model/guide/box", "Mesh")

    # Define attributes for the cube mesh
    face_vertex_counts_attribute = cube_mesh.CreateAttribute("faceVertexCounts", Sdf.ValueTypeNames.Int)
    face_vertex_counts_attribute.Set([4, 4, 4, 4, 4, 4])

    points_attribute = cube_mesh.CreateAttribute("points", Sdf.ValueTypeNames.Point3f)
    points_attribute.Set(vertex_points)

    extents_cube = cube_mesh.CreateAttribute("extent", Sdf.ValueTypeNames.Vector3fArray)
    extents_cube.Set(bbox_extent)

    face_vertex_indices_attribute = cube_mesh.CreateAttribute("faceVertexIndices", Sdf.ValueTypeNames.Int)
    face_vertex_indices_attribute.Set([0, 1, 3, 2, 4, 5, 7, 6, 6, 7, 2, 3, 5, 4, 1, 0, 5, 0, 2, 7, 1, 4, 6, 3])

    return cube_mesh

# Path to the USD file containing the model
model_path = "./geo.usda"

# Open the USD stage for the model
model_stage = Usd.Stage.Open(model_path)

# Get the default prim of the model
asset_prim = model_stage.GetDefaultPrim()
print(f"Auto Guide will be calculated for: {asset_prim.GetPath()}")

# Calculate bounding box extent and vertex points
bbox_extent, vertex_points = get_extent(model_stage, asset_prim)

# Create a new USD stage for the guide and create the cube mesh
stage = Usd.Stage.CreateNew(guide_path)
cube_mesh = create_cube_mesh(stage, bbox_extent, vertex_points, asset_prim)

# Save the new USD stage with the cube mesh
stage.Save()

```
