Value Clips#

What Are Value Clips?#

Value clips let you assemble time-sampled attribute values from a sequence of external layers. They are useful for simulation caches and other large animation datasets, where each file may contain only a portion of the animation.

Clip layers are opened as their values are needed. This avoids adding every cache file to the layer stack and processing those layers during composition. Using sublayers alone also does not join the samples: the strongest layer with time samples for an attribute supplies its animation.

In this lesson, you will configure a clip set, reuse its animation with different timing, and check which attributes receive clip values.

How Does It Work?#

Value clips participate in value resolution. They supply animated values for attributes already defined on the stage, either through authored declarations or a schema. Clip layers do not introduce prim structure, metadata, or default values into the stage.

As described in Value Resolution, OpenUSD checks time samples, then animation splines, then defaults, then clips at a given location in the composition. A default authored at the same location takes precedence over clip values.

Clip Layers, Manifests, and Clip Sets#

A value clip setup uses three related pieces:

  • Clip layers store the animated data. A shared prim path identifies the data to read in each clip. It can differ from the target prim path on the stage, allowing the same clip data to drive multiple prims.

  • The manifest declares which attributes have animation in the clips. OpenUSD uses it to determine whether an attribute can receive clip values without inspecting every clip file.

  • The clip set stores metadata on a prim that identifies the clip layers, their timing, and the manifest.

Clip metadata is stored in a clips dictionary keyed by clip set name. A prim can have multiple clip sets; the Python API uses a set named default unless you specify another name.

Working With Python#

Use Usd.ClipsAPI(prim) to configure a clip set. The following table lists the metadata fields for an explicit clip set, which specifies the clip files and their activation times directly. The API methods add a Clip prefix to these field names, as in SetClipAssetPaths().

Field

Required?

Purpose

assetPaths

Yes

Ordered list of clip layers

primPath

Yes

Prim path to read inside the clips

active

Yes

(stageTime, clipIndex) pairs identifying when each clip is active

times

No

(stageTime, clipTime) pairs for retiming; identity if omitted

manifestAssetPath

No

Path to the manifest; generated in memory if omitted

Declaring Attributes in a Manifest#

If you omit manifestAssetPath, OpenUSD generates a manifest by opening and inspecting the clip layers. For large clip sets, you can avoid this work at runtime by generating and saving a manifest with Usd.ClipsAPI.GenerateClipManifest() or the usdstitchclips command line tool, then setting manifestAssetPath to that file.

An authored manifest determines which attributes can receive values from the clip set. It must declare each attribute you want to read, even if the clip files already contain samples for that attribute. A declaration such as double size is sufficient; the manifest does not need to duplicate the animation samples.

Retiming With times#

The active field selects the clip to use at a given stage time code. The times field maps that stage time to a time within the clip using (stageTime, clipTime) pairs. OpenUSD interpolates linearly between these pairs.

You can use this mapping to reuse animation at different speeds or with a delayed start. If you omit times, the mapping is identity: stage time is passed to the clip unchanged.

times

Effect

[(0, 0), (24, 24)]

Plays at the authored speed

[(0, 0), (48, 24)]

Stretches 24 frames of clip data across 48 stage frames

[(0, 0), (12, 0), (36, 24)]

Holds for 12 frames, then plays

[(0, 24), (24, 0)]

Plays in reverse

Template Clips#

For regularly numbered files, template metadata provides a compact way to specify a clip sequence. OpenUSD derives the asset paths and timing from the pattern, start time, end time, and stride:

1api.SetClipTemplateAssetPath("./cache.###.usda")
2api.SetClipTemplateStartTime(1)
3api.SetClipTemplateEndTime(10)
4api.SetClipTemplateStride(1)
5api.SetClipPrimPath("/Cache")

The # characters specify the minimum padding width, so cache.###.usda selects names such as cache.001.usda. The generated timing uses an identity mapping in the layer’s time coordinates, so the sample times inside each clip should match the numbered sequence.

Use an explicit clip set when you need a custom times mapping, such as a hold or a loop. Template clips derive their mapping from the sequence, but you can still shift and scale their playback with layer offsets on a reference or sublayer. See Layer Offsets in the value clips documentation for how offsets apply to both forms. If you author both template and explicit clip metadata, OpenUSD prefers the explicit form.

Examples#

Tip

You can run these examples locally as Jupyter notebooks. See How to Run Notebooks Locally for setup instructions. Run the examples in order; later examples reuse the clip files created in Example 1.

Example 1: A Working Clip Set#

First, we create two clip layers, each with samples at local times 0 and 1. The make_clip() function authors a size attribute at /Clip and saves its samples to a file. The setup cell defines fresh_layer() to create an empty layer under _assets/clips/, including when you rerun the notebook.

 1from pxr import Usd, Sdf
 2
 3
 4def make_clip(name, samples):
 5    """Write one clip layer holding time samples for /Clip.size."""
 6    layer = fresh_layer(name)
 7    stage = Usd.Stage.Open(layer)
 8    attr = stage.DefinePrim("/Clip").CreateAttribute("size", Sdf.ValueTypeNames.Double)
 9    for time, value in samples.items():
10        attr.Set(value, time)
11    layer.Save()
12    return layer

Next, we declare the attribute on the target prim and configure the clip set. The first clip is active before stage time 2, and the second is active from time 2 onward. The times mapping maps stage times 0–1 to the first clip’s local times 0–1 and stage times 2–3 to the second clip’s local times 0–1.

 1from pxr import Usd, Sdf
 2
 3make_clip("clip_a.usda", {0: 0.0, 1: 1.0})
 4make_clip("clip_b.usda", {0: 10.0, 1: 11.0})
 5
 6root = fresh_layer("root.usda")
 7stage: Usd.Stage = Usd.Stage.Open(root)
 8thing = stage.DefinePrim("/World/Thing", "Xform")
 9
10# Declare the attribute on the stage; clips supply its animated values
11size = thing.CreateAttribute("size", Sdf.ValueTypeNames.Double)
12
13api = Usd.ClipsAPI(thing)
14api.SetClipAssetPaths([Sdf.AssetPath("./clip_a.usda"), Sdf.AssetPath("./clip_b.usda")])
15api.SetClipPrimPath("/Clip")
16api.SetClipActive([(0, 0), (2, 1)])
17# Map stage 0..1 onto clip time 0..1, then stage 2..3 onto clip time 0..1 again
18api.SetClipTimes([(0, 0), (1, 1), (2, 0), (3, 1)])
19
20print("time samples:", size.GetTimeSamples())
21print("values      :", sample(size, [0, 0.5, 1, 2, 2.5, 3]))
22print()
23print(root.ExportToString())
time samples: [0.0, 1.0, 2.0, 3.0]
values      : {0: 0.0, 0.5: 0.5, 1: 1.0, 2: 10.0, 2.5: 10.5, 3: 11.0}

#usda 1.0

def "World"
{
    def Xform "Thing" (
        clips = {
            dictionary default = {
                double2[] active = [(0, 0), (2, 1)]
                asset[] assetPaths = [@./clip_a.usda@, @./clip_b.usda@]
                string primPath = "/Clip"
                double2[] times = [(0, 0), (1, 1), (2, 0), (3, 1)]
            }
        }
    )
    {
        custom double size
    }
}

You can query and interpolate the composed attribute as you would other animated attributes. Its time samples come from the two clip files, which are accessed during value resolution.

Example 2: Retiming a Shared Clip#

This example applies one animation clip to three cubes. Each cube uses a different times mapping so you can compare normal playback, half-speed playback, and a delayed start.

 1from pxr import Gf, Usd, UsdGeom, Sdf
 2
 3# One clip layer: a slide from x=0 to x=6 over the clip's own frames 0..24
 4slide = fresh_layer("slide.usda")
 5slide_stage = Usd.Stage.Open(slide)
 6slide_op = UsdGeom.Xformable(slide_stage.DefinePrim("/Clip", "Xform")).AddTranslateOp()
 7slide_op.Set(Gf.Vec3d(0, 0, 0), 0)
 8slide_op.Set(Gf.Vec3d(6, 0, 0), 24)
 9slide.Save()
10
11offsets = fresh_layer("offsets.usda")
12offset_stage: Usd.Stage = Usd.Stage.Open(offsets)
13offset_stage.SetStartTimeCode(0)
14offset_stage.SetEndTimeCode(48)
15world = UsdGeom.Xform.Define(offset_stage, "/World")
16offset_stage.SetDefaultPrim(world.GetPrim())
17
18
19def clipped_cube(name, row, times):
20    cube = UsdGeom.Cube.Define(offset_stage, f"/World/{name}")
21    cube.GetSizeAttr().Set(1.0)
22    xformable = UsdGeom.Xformable(cube)
23    xformable.AddTranslateOp()                                        # driven by clips
24    xformable.AddTranslateOp(opSuffix="row").Set(Gf.Vec3d(0, row, 0))  # static, separates the rows
25    api = Usd.ClipsAPI(cube.GetPrim())
26    api.SetClipAssetPaths([Sdf.AssetPath("./slide.usda")])
27    api.SetClipPrimPath("/Clip")
28    api.SetClipActive([(0, 0)])
29    api.SetClipTimes(times)
30    return cube.GetPrim().GetAttribute("xformOp:translate")
31
32
33full = clipped_cube("FullSpeed", 0.0, [(0, 0), (24, 24)])
34half = clipped_cube("HalfSpeed", 2.0, [(0, 0), (48, 24)])
35delayed = clipped_cube("Delayed", 4.0, [(0, 0), (12, 0), (36, 24)])
36offsets.Save()
37
38retiming_results = {}
39print("frame   FullSpeed  HalfSpeed  Delayed")
40for frame in (0, 12, 24, 36, 48):
41    row = (full.Get(frame)[0], half.Get(frame)[0], delayed.Get(frame)[0])
42    retiming_results[frame] = row
43    print(f"{frame:5}   {row[0]:8.2f}   {row[1]:8.2f}   {row[2]:7.2f}")
frame   FullSpeed  HalfSpeed  Delayed
    0       0.00       0.00      0.00
   12       3.00       1.50      0.00
   24       6.00       3.00      3.00
   36       6.00       4.50      6.00
   48       6.00       6.00      6.00
Loading your model...
_assets/clips/offsets.usda
        
    

FullSpeed finishes moving by frame 24. HalfSpeed stretches the same motion across 48 frames. Delayed holds clip time at 0 for the first 12 frames, then plays the motion through frame 36. Each cube uses the same clip data; only the timing metadata changes.

Note

The viewer flattens the stage before converting it for display. Flattening converts resolved clip values to ordinary time samples, which the viewer can play back.

Example 3: Selecting Attributes With a Manifest#

We now compare three configurations using the clips from Example 1: an automatically generated manifest, an authored manifest that declares size, and an empty manifest.

 1from pxr import Usd, Sdf
 2
 3# A manifest declaring size, and one declaring nothing at all
 4good = fresh_layer("manifest_good.usda")
 5mstage = Usd.Stage.Open(good)
 6mstage.OverridePrim("/Clip").CreateAttribute("size", Sdf.ValueTypeNames.Double)
 7good.Save()
 8
 9empty = fresh_layer("manifest_empty.usda")
10empty.Save()
11
12
13def build(manifest, tag):
14    layer = fresh_layer(f"root_{tag}.usda")
15    stage = Usd.Stage.Open(layer)
16    prim = stage.DefinePrim("/World/Thing", "Xform")
17    prim.CreateAttribute("size", Sdf.ValueTypeNames.Double)
18    api = Usd.ClipsAPI(prim)
19    api.SetClipAssetPaths([Sdf.AssetPath("./clip_a.usda"), Sdf.AssetPath("./clip_b.usda")])
20    api.SetClipPrimPath("/Clip")
21    api.SetClipActive([(0, 0), (2, 1)])
22    if manifest:
23        api.SetClipManifestAssetPath(Sdf.AssetPath(f"./{manifest}"))
24    # Return the stage, not the attribute: an attribute handle expires when the
25    # stage it came from is garbage collected.
26    return stage
27
28
29manifest_results = {}
30manifest_stages = {}
31for manifest, tag in ((None, "none"), ("manifest_good.usda", "good"), ("manifest_empty.usda", "empty")):
32    manifest_stages[tag] = build(manifest, tag)
33    attr = manifest_stages[tag].GetAttributeAtPath("/World/Thing.size")
34    manifest_results[tag] = (attr.GetTimeSamples(), attr.Get(0))
35    print(f"manifest={tag:6} samples={str(attr.GetTimeSamples()):22} value at 0 = {attr.Get(0)}")
manifest=none   samples=[0.0, 1.0, 2.0]        value at 0 = 0.0
manifest=good   samples=[0.0, 1.0, 2.0]        value at 0 = 0.0
manifest=empty  samples=[]                     value at 0 = None

Both the generated manifest and the authored manifest that declares size allow the clip values to resolve. The empty manifest excludes size from the clip set. In this example, the attribute has no other value source, so it returns no time samples and Get(0) returns None.

Example 4: Checking Required Fields#

This example omits each required field in turn and queries the resulting attribute. Compare each result with the complete clip set to see whether it contributes values.

 1from pxr import Usd, Sdf
 2
 3FIELDS = ["assetPaths", "primPath", "active"]
 4
 5
 6def build_omitting(missing):
 7    layer = fresh_layer(f"root_omit_{missing or 'nothing'}.usda")
 8    stage = Usd.Stage.Open(layer)
 9    prim = stage.DefinePrim("/World/Thing", "Xform")
10    prim.CreateAttribute("size", Sdf.ValueTypeNames.Double)
11    api = Usd.ClipsAPI(prim)
12    if missing != "assetPaths":
13        api.SetClipAssetPaths([Sdf.AssetPath("./clip_a.usda"), Sdf.AssetPath("./clip_b.usda")])
14    if missing != "primPath":
15        api.SetClipPrimPath("/Clip")
16    if missing != "active":
17        api.SetClipActive([(0, 0), (2, 1)])
18    return stage
19
20
21omission_results = {}
22omission_stages = {}
23for missing in [None] + FIELDS:
24    omission_stages[missing] = build_omitting(missing)
25    attr = omission_stages[missing].GetAttributeAtPath("/World/Thing.size")
26    samples, value = attr.GetTimeSamples(), attr.Get(0)
27    omission_results[missing] = (samples, value)
28    label = missing or "(nothing omitted)"
29    print(f"omit {label:18} samples={str(samples):22} value={value}")
omit (nothing omitted)  samples=[0.0, 1.0, 2.0]        value=0.0
omit assetPaths         samples=[]                     value=None
omit primPath           samples=[]                     value=None
omit active             samples=[]                     value=None

Omitting any required field prevents this clip set from contributing values. Because the attribute has no other value source, its sample list is empty and Get(0) returns None.

Other value sources can still provide a result. For example, changing the prim’s type to Cube makes the schema’s fallback value for size available, even though the clip set is still missing active:

1from pxr import UsdGeom
2
3fallback_stage = omission_stages["active"]
4fallback_prim = fallback_stage.GetPrimAtPath("/World/Thing")
5fallback_prim.SetTypeName("Cube")
6fallback_value = UsdGeom.Cube(fallback_prim).GetSizeAttr().Get(0)
7print("size from the Cube schema fallback:", fallback_value)
size from the Cube schema fallback: 2.0

Troubleshooting Clip Values#

If the expected animation is missing, check the clip set’s fields, manifest, and attribute declarations. The table below distinguishes a clip set contributing no values from an attribute having no resolved value at all.

Configuration

Result and next step

An explicit clip set omits assetPaths, primPath, or active

The clip set contributes no values. Check that all three fields are authored.

The manifest omits an attribute

The clip set does not supply that attribute. Include its declaration or regenerate the manifest.

The attribute is not defined on the composed stage

Clips do not create the attribute. Declare it on the stage or use a schema that defines it.

The attribute type differs from the type in the clips

Interpolation may fail. Use matching attribute types in the stage and clip layers.

primPath does not identify the data in the clips

The clip set does not supply the expected values. Check the path in the clip files.

A file listed in assetPaths is missing

OpenUSD reports a warning. Check the file path and asset resolution.

When a clip set contributes no values, other sources, including authored values or schema fallbacks, can still resolve. In Example 4, the custom attribute returns None until the Cube schema provides a fallback.

Note

In tests with usd-core 25.11, omitting a required field or using an empty manifest produced no warning. Check the resolved values as well as diagnostics when troubleshooting.

Note

In usd-core 25.11, the Python getters GetClipTemplateStartTime(), GetClipTemplateEndTime(), GetClipTemplateStride(), and GetClipTemplateActiveOffset() can return an uninitialized numeric value when the field is absent. This is an implementation issue in that version’s Python wrapper, rather than a meaningful default. Check for the field before reading it, for example with "templateStartTime" in api.GetClips().get("default", {}).

Key Takeaways#

Value clips let you assemble animation from external layers while keeping prim and attribute definitions in the composed stage. A clip set identifies the data to read, its active intervals, and the mapping between stage time and clip time. Its manifest declares which attributes can receive clip values.

Use explicit clip metadata for custom timing and template metadata for regularly numbered sequences. Layer offsets can shift and scale either form. When expected clip values are missing, check the clip metadata and attribute declarations, and consider whether another source is providing the resolved value.