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 |
|---|---|---|
|
Yes |
Ordered list of clip layers |
|
Yes |
Prim path to read inside the clips |
|
Yes |
|
|
No |
|
|
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.
|
Effect |
|---|---|
|
Plays at the authored speed |
|
Stretches 24 frames of clip data across 48 stage frames |
|
Holds for 12 frames, then plays |
|
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 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 |
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. |
|
The clip set does not supply the expected values. Check the path in the clip files. |
A file listed in |
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.