Skip to content

Commit

Permalink
Improve fps example code
Browse files Browse the repository at this point in the history
  • Loading branch information
LucaScheller committed Sep 27, 2023
1 parent 020127b commit d231ef2
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 10 deletions.
47 changes: 46 additions & 1 deletion code/core/elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -1764,6 +1764,51 @@ def Xform "explosion" (
layer.framesPerSecond = 25
layer.startTimeCode = time_samples[0]
layer.endTimeCode = time_samples[-1]

###### Stage vs Layer TimeSample Scaling ######
from pxr import Sdf, Usd
import os

layer_fps = 25
layer_identifier = "ref_layer.usd"
stage_fps = 24
stage_identifier = "root_layer.usd"

# Create layer
reference_layer = Sdf.Layer.CreateAnonymous()
prim_path = Sdf.Path("/bicycle")
prim_spec = Sdf.CreatePrimInLayer(reference_layer, prim_path)
prim_spec.specifier = Sdf.SpecifierDef
prim_spec.typeName = "Cube"
attr_spec = Sdf.AttributeSpec(prim_spec, "size", Sdf.ValueTypeNames.Double)
for frame in range(1001, 1005):
value = float(frame - 1000)
reference_layer.SetTimeSample(attr_spec.path, frame, value)
# FPS Metadata
time_samples = Sdf.Layer.ListAllTimeSamples(reference_layer)
reference_layer.timeCodesPerSecond = layer_fps
reference_layer.framesPerSecond = layer_fps
reference_layer.startTimeCode = time_samples[0]
reference_layer.endTimeCode = time_samples[-1]
# reference_layer.Export(layer_identifier)

# Create stage
stage = Usd.Stage.CreateInMemory()
# With scale
# reference_layer_offset = Sdf.LayerOffset(0, layer_fps/stage_fps)
# Without scale
reference_layer_offset = Sdf.LayerOffset(0, 1)
reference = Sdf.Reference(reference_layer.identifier, "/bicycle", reference_layer_offset)
bicycle_prim_path = Sdf.Path("/bicycle")
bicycle_prim = stage.DefinePrim(bicycle_prim_path)
references_api = bicycle_prim.GetReferences()
references_api.AddReference(reference, position=Usd.ListPositionFrontOfAppendList)
# FPS Metadata (In Houdini we can't set this via python, use a 'configure layer' node instead.)
stage.SetTimeCodesPerSecond(stage_fps)
stage.SetFramesPerSecond(stage_fps)
stage.SetStartTimeCode(1001)
stage.SetEndTimeCode(1005)
# stage.Export(stage_identifier)
#// ANCHOR_END: animationFPS


Expand Down Expand Up @@ -3176,7 +3221,7 @@ def "prim" (
print("Value Type Name Role", value_type_name.role) # Returns: 'TextureCoordinate'
## Array vs Scalar (Single Value)
print("Value Type Name IsArray", value_type_name.isArray) # Returns: True
print("Value Type Name IsArray", value_type_name.isScalar) # Returns: False
print("Value Type Name IsScalar", value_type_name.isScalar) # Returns: False
## Convert type between Scalar <-> Array
print("Value Type Name -> Get Array Type", value_type_name.arrayType) # Returns: Sdf.ValueTypeName("texCoord2f[]") (Same as type_name in this case)
print("Value Type Name -> Get Scalar Type", value_type_name.scalarType) # Returns: Sdf.ValueTypeName("texCoord2f")
Expand Down
27 changes: 19 additions & 8 deletions docs/src/core/elements/animation.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,18 +201,29 @@ You can also tell a time sample to block a value. Blocking means that the attrib
### Time Metrics (Frames Per Second & Frame Range) <a name="animationMetadata"></a>
With what FPS the samples are interpreted is defined by the `timeCodesPerSecond`/`framesPerSecond` metadata.

The [loading order of FPS](https://openusd.org/dev/api/class_usd_stage.html#a85092d7455ae894d50224e761dc6e840) is:
When working with stages, we have the following [loading order of FPS](https://openusd.org/dev/api/class_usd_stage.html#a85092d7455ae894d50224e761dc6e840):
1. timeCodesPerSecond from session layer
2. timeCodesPerSecond from root layer
3. framesPerSecond from session layer
4. framesPerSecond from root layer
5. fallback value of 24
1. timeCodesPerSecond from root layer
1. framesPerSecond from session layer
1. framesPerSecond from root layer
1. fallback value of 24

When writing layers, we should always write these layer metrics, so that we know what
the original intended FPS were.
These should match the FPS settings of your DCC. The 'framesPerSecond' is intended to be a hint for playback engines (e.g. your DCC/Usdview etc.) to set the FPS to when reading your file. The 'timeCodesPerSecond' describes the actual time sample intent. With the fallback behaviour we can also only specify the 'framesPerSecond' to keep both metadata entries in sync.

When working with layers, we have the following [loading order of FPS](https://openusd.org/dev/api/class_sdf_layer.html#a8c7a1ac2e85efa2aa4831123de576b7c):
1. timeCodesPerSecond of layer
1. framesPerSecond of layer
1. fallback value of 24

~~~admonish info
When loading samples from a sublayered/referenced or payloaded file, USD automatically uses the above mentioned metadata in the layer as a frame of reference of how to bring in the time samples. If the FPS settings mismatch it will automatically scale the time samples to match our stage FPS settings as mentioned above.
Therefore when writing layers, we should always write these layer metrics, so that we know what
the original intended FPS were and our caches work FPS independently.
~~~

~~~admonish warning
If we want to load a let's say 24 FPS cache in a 25 FPS setup, we will have to apply a `Sdf.LayerOffset` when loading in the layer. This way we can move back the sample to the "correct" frame based times by scaling with a factor of 24/25.
In the below code example, this doesn't seem to work (at least in Houdini/UsdView), we'll have to verify again with Pixar if this ia a bug, we are doing something wrong here or intended.
~~~

```python
Expand Down
2 changes: 1 addition & 1 deletion docs/src/production/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Our time samples that are written in the time unit-less `{<frame>: <value> }` fo
If we want to load a let's say 24 FPS cache in a 25 FPS setup, we will have to apply a `Sdf.LayerOffset` when loading in the layer. This way we can move back the sample to the "correct" frame based times by scaling with a factor of 24/25.
~~~

You can find more details about the specific metadata priority and how to set the metadata in our [animation section](../core/elements/animation.md#frames-per-second).
You can find more details about the specific metadata priority and how to set the metadata in our [animation section](../core/elements/animation.html#animationMetadata).

## How is the scene scale unit and up axis handled in USD? <a name="faqSceneScale"></a>
We can supply an up axis and scene scale hint in the layer metadata, but this does not seem to be used by most DCCs or in fact Hydra itself when rendering the geo. So if you have a mixed values, you'll have to counter correct via transforms yourself.
Expand Down

0 comments on commit d231ef2

Please sign in to comment.