From d231ef23bf11a2286cf43c40f001f71832014dfc Mon Sep 17 00:00:00 2001 From: Luca Scheller Date: Wed, 27 Sep 2023 23:25:04 +0200 Subject: [PATCH] Improve fps example code --- code/core/elements.py | 47 ++++++++++++++++++++++++++++- docs/src/core/elements/animation.md | 27 ++++++++++++----- docs/src/production/faq.md | 2 +- 3 files changed, 66 insertions(+), 10 deletions(-) diff --git a/code/core/elements.py b/code/core/elements.py index 88b667a..b8c5a43 100644 --- a/code/core/elements.py +++ b/code/core/elements.py @@ -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 @@ -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") diff --git a/docs/src/core/elements/animation.md b/docs/src/core/elements/animation.md index b936031..0acc221 100644 --- a/docs/src/core/elements/animation.md +++ b/docs/src/core/elements/animation.md @@ -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) 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 diff --git a/docs/src/production/faq.md b/docs/src/production/faq.md index 9d0434e..39eca5e 100644 --- a/docs/src/production/faq.md +++ b/docs/src/production/faq.md @@ -37,7 +37,7 @@ Our time samples that are written in the time unit-less `{: }` 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? 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.