docs/user_guides/time_and_animated_values.rst
.. include:: ../rolesAndUtils.rst
.. _time_and_animated_values:
######################## Time and Animated Values ########################
OpenUSD supports authoring animated values that do not resolve to a single static value but vary in value over time. This functionality is typically used for describing animations in a USD scene, but can be used for any scenario where time-varying values are needed, such as simulations.
To support animated values, OpenUSD provides features to author animated values, query and interpolate values over time, and work with "clips" of animated data.
.. contents:: Table of Contents :local: :depth: 3
.. _time_understanding_timecodes:
Understanding TimeCodes
TimeCodes are the basic time coordinate for USD. By themselves, TimeCodes are
unitless, and do not necessarily conceptually map to "frames per second".
TimeCodes also do not represent other industry standard time codes, such as
SMPTE. Keeping TimeCodes unitless gives clients flexibility to encode their
time-varying data in ways that make the most sense for their application, while
at the same time OpenUSD still provides mechanisms for mapping TimeCodes to real
time units for decoding and playback, as described in
:ref:time_scaling_timecodes_to_real_time below.
TimeCodes are encoded in USD layers as numeric (double precision) values. The
following simple example shows animated attribute values (using
:ref:TimeSamples <usdglossary-timesample>) represented as pairs of TimeCodes
and values (in this case, float values specifying a translation along the
X-axis).
.. code-block:: usda
double xformOp:translateX.timeSamples = { 1: 0, 25: 10, 99: 5, }
TimeCodes are most commonly used as the time coordinate for animated values, as shown above. However, you can also specify that an attribute or metadata field's value type is a TimeCode.
.. code-block:: usda
def "PrimA"
(
# custom metadata with timecode value type
customData = {
timecode timeCodeMetadata = 24
}
)
{
# attribute with timecode value type
timecode timeCodeAttr = 24
}
When used this way, the attribute or metadata field's value will be subject to
any TimeCode scaling or offsets via composition, as described in
:ref:time_working_with_timecode_offsets.
.. admonition:: Avoid Using Infinite TimeCodes
When working with TimeCodes, you may encounter situations where you
want to use "infinite" time coordinates. For example, you might have an
animated attribute where you want to set an arbitrary "earliest" value at
time "negative infinity", so you can adjust the TimeCode later.
While OpenUSD allows authoring and querying using infinite (-inf/inf)
TimeCodes, we strongly discourage working with infinite TimeCodes.
TimeCodeRanges created with infinite TimeCodes are not considered valid,
DCC tools may not be able to represent infinite TimeCodes, and the use
of infinite TimeCodes may create future incompatibilities.
.. _time_working_with_timecodes_programmatically:
OpenUSD provides ways for working with TimeCodes programmatically, for cases where you want to query an animated attribute at a particular TimeCode, or over a TimeCode range. The following example Python code demonstrates using TimeCode APIs to query and author animated values. Note that while you can use double-precision values when querying an animated attribute, we recommend using TimeCodes to ensure future compatibility.
.. code-block:: python
# Given an attribute with animated values, get the value
# at a specific timeCode (which could be interpolated)
workTimeCode = Usd.TimeCode(14)
workValue = translate_attr.Get(workTimeCode)
# Set a new value at a specific timeCode (for this example we assume
# the attribute is using time samples for animated values)
newTimeCode = Usd.TimeCode(24)
translate_attr.Set(2, newTimeCode)
When working with TimeCodes programmatically, there are some situations where the exact numeric value for a TimeCode is not known, or can't be represented. OpenUSD provides the following features for these situations.
For cases where you are looking for the "earliest" time in a set of TimeCodes,
OpenUSD provides
:usdcpp:UsdTimeCode.EarliestTime() <UsdTimeCode::EarliestTime()>.
For example, if you had the following animated values for the
:usda:xformOp:translateX attribute:
.. code-block:: usda
double xformOp:translateX.timeSamples = { 3: 5, 25: 10, 99: 5, }
The following Python query would get the attribute value at the earliest
TimeCode for :usda:xformOp:translateX (for this example, the value of
5 at the earliest TimeCode, which is 3).
.. code-block:: python
earliestValue = translate_attr.Get(Usd.TimeCode.EarliestTime())
For cases where you want the value immediately before a given TimeCode,
OpenUSD provides
:usdcpp:UsdTimeCode.PreTime(time) <UsdTimeCode::PreTime(double)>.
This is useful in situations where an attribute's value can change
discontinuously at a specific TimeCode, for example, when evaluating
attributes with "held" interpolation values, or splines with dual-valued
knots at the given TimeCode.
:usdcpp:UsdTimeCode.PreTime(time) <UsdTimeCode::PreTime(double)> will
evaluate the limit of an attribute's animated value as time
approaches the given time from the "left". For example, using the same set
of animated values for the :usda:xformOp:translateX attribute that we used
previously:
.. code-block:: usda
double xformOp:translateX.timeSamples = { 3: 5, 25: 10, 99: 5, }
The following Python query would get the attribute value for the
:usda:xformOp:translateX attribute just prior to TimeCode 25 (which in
this example would be evaluated to 10.0, as the interpolated value is
continuous at TimeCode 25).
.. code-block:: python
preValue = translate_attr.Get(Usd.TimeCode.PreTime(25))
OpenUSD has a special "Default" sentinel TimeCode coordinate. The
:usdcpp:Default <UsdTimeCode::Default()> TimeCode is used when working with
attributes that have :ref:default values <usdglossary-defaultvalue>
--- "static", non-time-varying values separate from animated values (if any)
for an attribute. In inequality comparisons, the Default TimeCode is
considered less than any numeric TimeCode, include :mono:EarliestTime().
The following Python example queries the :usda:xformOp:translateX attribute
for its default value, if any.
.. code-block:: python
defaultValue = translate_attr.Get(Usd.TimeCode.Default())
.. _time_scaling_timecodes_to_real_time:
TimeCode coordinates for animated values are mapped to real-time seconds by
scaling by the layer's :usda:timeCodesPerSecond metadata. The following
example sets :usda:timeCodesPerSecond to 24.
.. code-block:: usda
#usda 1.0
(
timeCodesPerSecond = 24
)
Assuming the scene used a TimeCode range that started at 0, this would mean a TimeCode of 240 would correspond to 10 seconds of real time.
OpenUSD also provides the legacy :usda:framesPerSecond layer metadata, used
as an indication of the desired playback rate when the animation is viewed in a
playback device (DCC tool, :program:usdview, etc.).
.. admonition:: Legacy Behavior of :mono:framesPerSecond
:usda:`framesPerSecond` normally does not affect TimeCode scaling and does
not combine or interfere with any :usda:`timeCodesPerSecond` value. However,
OpenUSD does provide a special legacy behavior where the
:usda:`framesPerSecond` value, if set, is used as a fallback value for
:usda:`timeCodesPerSecond` if :usda:`timeCodesPerSecond` is *not* set. The
order of precedence OpenUSD uses for determining the
:usda:`timeCodesPerSecond` to use is:
1. :usda:`timeCodesPerSecond` from session layer
2. :usda:`timeCodesPerSecond` from root layer
3. :usda:`framesPerSecond` from session layer
4. :usda:`framesPerSecond` from root layer
5. fallback value of 24
The general best practice is to use :usda:`timeCodesPerSecond` to specify
how TimeCodes are scaled to real time, and :usda:`framesPerSecond` if you
need to encode a specific playback rate on playback devices, regardless of
how many samples per second are recorded in the USD scene. We provide the
information about :usda:`framesPerSecond` as fallback for
:usda:`timeCodesPerSecond` primarily as a debugging aid, should you observe
unexpected time-scaling. The fallback behavior derives only from USD's
relationship to Pixar's Presto animation system.
.. _time_layer_start_end_times:
You can specify start and end time codes in a layer using the
:usda:startTimeCode and :usda:endTimeCode layer metadata.
.. code-block:: usda
#usda 1.0
(
startTimeCode = 1
endTimeCode = 240
)
This TimeCode range is primarily used by clients when determining the initial
playback range to expose to users in a tool. For example, :program:usdview
uses this range to set the initial range in the playback bar.
.. image:: time_usdview_startend.png :width: 75% :alt: usdview showing playback bar with start/end range
:usda:startTimeCode and :usda:endTimeCode are not used by OpenUSD to set an
actual value resolution evaluation range. For example, if you had authored
animated values outside the startTimeCode/endTimeCode range, you can still query
for those values, or for interpolated values outside the
startTimeCode/endTimeCode range.
.. _time_using_timecode_ranges:
If you need to programmatically create a closed range of TimeCodes, use
:usdcpp:UsdUtilsTimeCodeRange. Note that you cannot use TimeCode's
:usdcpp:EarliestTime <UsdTimeCode::EarliestTime()> or
:usdcpp:Default <UsdTimeCode::Default()> as the start
or end TimeCode for the range. You can specify a non-zero stride used for
iterating over TimeCodes in the range. The following Python snippet creates a
TimeCodeRange using the stage's start and end TimeCode, with a stride of 2
TimeCodes, to iterate and get an attribute value over that range.
.. code-block:: python
timeCodeRange = UsdUtils.TimeCodeRange(stage.GetStartTimeCode(),
stage.GetEndTimeCode(), 2)
for timeCode in timeCodeRange:
print(f"At TimeCode {timeCode}, attribute value is: "
f"{example_attribute.Get(timeCode)}")
Negative stride values are allowed only if the range's start TimeCode is greater than or equal to the range's end TimeCode.
.. _time_working_with_timecode_offsets:
When a layer uses composition via
:ref:sublayering <usdglossary-sublayers>,
:ref:references <usdglossary-references>, or
:ref:payloads <usdglossary-payload>, OpenUSD can apply a scale and/or offset
to the TimeCode coordinates for animated values and the values of any
TimeCode-valued attributes and metadata from the target layer. This provides the
flexibility to use animated data in your workflow in different scenes with
different time frames of reference.
.. _time_automatic_scaling:
If a layer specifies :usda:timeCodesPerSecond and is targeted by a sublayer,
reference, or payload composition arc, the TimeCode values and
animated value coordinates in the targeted layer are automatically scaled to map
into the time frame defined by the source layer's :usda:timeCodesPerSecond.
The following example :filename:animationA.usda layer specifies a
:usda:timeCodesPerSecond of 12.
.. code-block:: usda :caption: animationA.usda
#usda 1.0
(
timeCodesPerSecond = 12
)
This layer is sublayered into another layer :filename:animationRoot.usda that
specifies a :usda:timeCodesPerSecond of 24.
.. code-block:: usda :caption: animationRoot.usda
#usda 1.0
(
timeCodesPerSecond = 24
subLayers = [
@animationA.usda@
]
)
With :filename:animationRoot.usda as the root layer in a composed stage, any
TimeCodes for animated attribute values on prims from
:filename:animationA.usda would be automatically scaled to
:filename:animationRoot.usda's time frame. In this case, the
TimeCodes would be scaled by a factor of 2, so that an animated value at
TimeCode 12 in :filename:animationA.usda (which would map to 1 second in
:filename:animationA.usda's :usda:timeCodesPerSecond) would have its
TimeCode scaled to 24 to match the desired 1 second time mapping in
:filename:animationRoot.usda's time scaling. The following diagram shows a
couple of TimeCodes in :filename:animationA.usda scaled to map to
the :filename:animationRoot.usda :usda:timeCodesPerSecond mapping.
.. image:: time_automaticscale.drawio.svg :width: 50% :alt: Automatic scaling of TimeCodes
This automatic scaling is applied across the entire
:ref:LayerStack <usdglossary-layerstack> targeted by the
sublayer/reference/payload composition arc. For example,
:filename:animationA.usda could also sublayer another layer,
:filename:animationB.usda (not shown) that has its own
:usda:timeCodesPerSecond of 6.
.. code-block:: usda :caption: animationA.usda with sublayer
#usda 1.0
(
timeCodesPerSecond = 12
subLayers = [
@animationB.usda@ # animationB has timeCodesPerSecond = 6
]
)
In the composed stage, the TimeCodes for animated values in
:filename:animationB.usda would be automatically scaled by 2 to fit within
:filename:animationA.usda's time frame, and then automatically scaled
again by 2 to fit within :filename:animationRoot.usda's time frame.
The automatic scaling is also applied to authored values of attributes or
metadata fields of the TimeCode value type. For example, suppose
:filename:animationA.usda had a prim with an attribute and metadata field of
TimeCode value type.
.. code-block:: usda :caption: animationA.usda with TimeCode value type attribute and metadata
#usda 1.0
(
timeCodesPerSecond = 12
)
def "PrimA"
(
# metadata with timecode value type
customData = {
timecode timeCodeMetadata = 10
}
)
{
# attribute with timecode value type
timecode timeCodeAttr = 15
}
If this layer were sublayered into :filename:animationRoot.usda with a
:usda:timeCodesPerSecond of 24, the metadata field and attribute values would
be automatically scaled in the flattened results.
.. code-block:: usda :caption: animationRoot.usda flattened with scaled timecode values
#usda 1.0
(
timeCodesPerSecond = 24
)
def "PrimA" (
customData = {
timecode timeCodeMetadata = 20
}
)
{
timecode timeCodeAttr = 30
}
Note there is no automatic application of a layer's :usda:startTimeCode or
:usda:endTimeCode as offsets for TimeCode coordinates in a composed stage.
There is a way to manually specify an offset, discussed in the next section.
.. _time_configuring_using_layer_offsets:
If you need to scale or shift the TimeCodes in a
:ref:sublayer <usdglossary-sublayers> or
:ref:referenced <usdglossary-references> or
:ref:payloaded <usdglossary-payload> layer, you can do so by adding a
"LayerOffset" to the composition arc. For example, you might have character
animation that you want to re-use for a different background character, but it
needs to be re-timed for the current scene.
The following :filename:layerOffsetsRoot.usda layer specifies a LayerOffset
with an offset of 10 and a scale of 2 for a sublayer composition.
.. code-block:: usda :caption: layerOffsetsRoot.usda
#usda 1.0
(
timeCodesPerSecond = 24
subLayers = [
@layerOffsetsA.usda@ (offset = 10; scale = 2)
]
)
In the composed stage, TimeCodes for animated values in
:filename:layerOffsetsA.usda will be scaled and offset accordingly. Note that
when mapping from a layer's time frame to that of the layer that references or
sublayers it, OpenUSD will apply scaling first, and then the offset, if both
are specified.
.. math::
\text{Adjusted TimeCode} = (\text{Layer TimeCode} * \text{LayerOffset scale}) + \text{LayerOffset offset}
In the above example, an animated value at TimeCode 10 in
:filename:layerOffsetsA.usda will have its TimeCode scaled to 20, and then
offset by 10 to 30, in the composed stage. The following diagram shows TimeCodes
from :filename:layerOffsetsA.usda being scaled by 2 and then offset by 10 in a
composed stage.
.. image:: time_layeroffset.drawio.svg :width: 50% :alt: Layer Offset with scale and offset
LayerOffsets can be specified for references and payloads in a similar way. The following example applies a scale and offset to the TimeCodes for any animated attributes of a referenced Prim.
.. code-block:: usda
def Xform "Xform2"
(
prepend references = @animationA.usda@</Xform2> (offset = 10; scale = 2)
)
{
}
As mentioned in :ref:time_understanding_timecodes, TimeCodes can be used as
an attribute or metadata field's value type. LayerOffsets will scale and offset
authored TimeCode values just like TimeCode coordinates. For example, assume you
had the following :filename:timeCodeValues.usda layer.
.. code-block:: usda :caption: timeCodeValues.usda
def "PrimA"
(
# custom metadata with timecode value type
customData = {
timecode timeCodeMetadata = 10
}
)
{
# attribute with timecode value type
timecode timeCodeAttr = 15
}
This layer is then sublayered into :filename:timeCodeValuesRoot.usda
with a LayerOffset scale and offset.
.. code-block:: usda :caption: timeCodeValuesRoot.usda
#usda 1.0
(
startTimeCode = 1
endTimeCode = 240
timeCodesPerSecond = 24
subLayers = [
@timeCodeValues.usda@ (offset = 10; scale = 2)
]
)
If :filename:timeCodeValuesRoot.usda is loaded into a stage and flattened,
the result will have the LayerOffset applied to the TimeCode attribute and
metadata field values from PrimA.
.. code-block:: usda :caption: flattened timeCodeValuesRoot.usda
def "PrimA" (
customData = {
timecode timeCodeMetadata = 30
}
)
{
timecode timeCodeAttr = 40
}
Keep in mind the following requirements for LayerOffsets.
Value Clips <usdglossary-valueclips> feature.Value Clips <usdglossary-valueclips> feature can provide this.In :program:usdview, you can see the LayerOffset for a given layer stack via
the "Layer Stack" tab.
.. image:: time_usdview_layeroffset.png :width: 75% :alt: usdview with layer offset
.. _time_combining_automatic_scaling_and_offsets:
If you apply a LayerOffset to a target layer, and the target layer has a
:usda:timeCodesPerSecond that differs from that of the referencing layer,
OpenUSD applies both the automatic source/target layer scaling and the
LayerOffset scale (in that order) to TimeCodes in the composed stage. For
example, assume you have the following :filename:animationLayer.usda layer.
.. code-block:: usda :caption: animationLayer.usda
#usda 1.0
(
timeCodesPerSecond = 8
)
If you then sublayered this layer in the root layer of your stage, using a Layer Offset with a scale of 2:
.. code-block:: usda :caption: animationRootLayer.usda
#usda 1.0
(
timeCodesPerSecond = 24
subLayers = [
@animationLayer.usda@ (offset = 10; scale = 2)
]
)
TimeCodes for animated values in :filename:animationLayer.usda would be
adjusted as follows. First, the automatic scaling would adjust TimeCodes by a
factor of 3 (from the animationRootLayer's :usda:timeCodesPerSecond value of
24 divided by animationLayer's :usda:timeCodesPerSecond value of 8). Then, the
adjusted TimeCodes would be adjusted further by a factor of 2 (from the subLayer
LayerOffset scale) and offset by 10. For example, a TimeCode coordinate of 10 in
:filename:animationLayer.usda would be multiplied by 3, then multiplied by 2,
and finally offset by 10, resulting in a TimeCode coordinate of 70 in the
composed stage.