Getting started
Once the library is installed with Python binding, we can test the Python API by following a simple example.
Editing the Stage
Let’s start by creating a USD stage in memory and register a callback to listen to Usd.Notice.ObjectsChanged and print all updated paths:
from pxr import Usd, Tf
stage = Usd.Stage.CreateInMemory()
def _updated(notice, stage):
"""Print updated paths from the stage."""
print("Resynced Paths: {}".format([
(path, notice.GetChangedFields(path))
for path in notice.GetResyncedPaths()
]))
print("ChangedInfoOnly Paths: {}\n".format([
(path, notice.GetChangedFields(path))
for path in notice.GetChangedInfoOnlyPaths()
]))
key = Tf.Notice.Register(Usd.Notice.ObjectsChanged, _updated, stage)
Let’s now edit the stage by adding a cylinder prim and update the attributes:
prim = stage.DefinePrim("/Foo", "Cylinder")
prim.GetAttribute("radius").Set(5)
prim.GetAttribute("height").Set(10)
This should have triggered five Usd.Notice.ObjectsChanged notices to be emitted. The first notice was emitted when the prim was created, the second and the fourth when both attributes where created, the third and fifth when they were given a default value. As a result, the following information will be printed in the shell:
Resynced Paths: [(Sdf.Path('/Foo'), ['specifier', 'typeName'])]
ChangedInfoOnly Paths: []
Resynced Paths: [(Sdf.Path('/Foo.radius'), [])]
ChangedInfoOnly Paths: []
Resynced Paths: []
ChangedInfoOnly Paths: [(Sdf.Path('/Foo.radius'), ['default'])]
Resynced Paths: [(Sdf.Path('/Foo.height'), [])]
ChangedInfoOnly Paths: []
Resynced Paths: []
ChangedInfoOnly Paths: [(Sdf.Path('/Foo.height'), ['default'])]
Editing the Layer
To consolidate the number of notices emitted, we could use the Sdf API to edit the root layer, then use a Sdf.ChangeBlock which would also limit the number of recompositions and greatly improve overall performance:
from pxr import Sdf
layer = stage.GetRootLayer()
with Sdf.ChangeBlock():
prim = Sdf.CreatePrimInLayer(layer, "/Foo")
prim.specifier = Sdf.SpecifierDef
prim.typeName = "Cylinder"
attr = Sdf.AttributeSpec(prim, "radius", Sdf.ValueTypeNames.Double)
attr.default = 5
attr = Sdf.AttributeSpec(prim, "height", Sdf.ValueTypeNames.Double)
attr.default = 10
Warning
It is not safe to edit the stage with the USD API when using Sdf.ChangeBlock.
One single notice will be emitted:
Resynced Paths: [(Sdf.Path('/Foo'), ['specifier', 'typeName'])]
ChangedInfoOnly Paths: []
Using the library
Let’s now create a new stage and modify the notice registration to target the
unf.Notice.ObjectsChanged
notice:
from pxr import Usd, Tf
import unf
stage = Usd.Stage.CreateInMemory()
def _updated(notice, stage):
"""Print updated paths from the stage."""
print("Resynced Paths: {}".format([
(path, notice.GetChangedFields(path))
for path in notice.GetResyncedPaths()
]))
print("ChangedInfoOnly Paths: {}\n".format([
(path, notice.GetChangedFields(path))
for path in notice.GetChangedInfoOnlyPaths()
]))
key = Tf.Notice.Register(unf.Notice.ObjectsChanged, _updated, stage)
To ensure that a unf.Notice.ObjectsChanged
notice is sent whenever a
Usd.Notice.ObjectsChanged is emitted, we need to
create a Broker
associated with the stage:
broker = unf.Broker.Create(stage)
Note
The Broker
is using a dispatcher to
emit a standalone notice for each Usd notice.
Let’s now edit the stage once again with the USD API:
prim = stage.DefinePrim("/Foo", "Cylinder")
prim.GetAttribute("radius").Set(5)
prim.GetAttribute("height").Set(10)
Like in the first section, five notices are emitted with the same information
as with the Usd.Notice.ObjectsChanged notice.
However, the unf.Notice.ObjectsChanged
notice is defined as mergeable.
It is therefore possible to reduce the number of notices emitted by using a
notice transaction:
broker.BeginTransaction()
prim = stage.DefinePrim("/Foo", "Cylinder")
prim.GetAttribute("radius").Set(5)
prim.GetAttribute("height").Set(10)
broker.EndTransaction()
For safety, it is recommended to use the unf.NoticeTransaction
object
instead which can be used as a context manager:
with unf.NoticeTransaction(broker):
prim = stage.DefinePrim("/Foo", "Cylinder")
prim.GetAttribute("radius").Set(5)
prim.GetAttribute("height").Set(10)
As a result, only one notice will be emitted:
Resynced Paths: [(Sdf.Path('/Foo'), ['typeName', 'specifier'])]
ChangedInfoOnly Paths: []
It is sometimes necessary to de-register listeners to a particular set of notices when editing the stage. If many clients are listening to Usd notices, this process can be tedious.
The Unf library provides a way to filter out some or all Unf notices during a transaction using a predicate function. For instance, the following transaction will only emit “Foo” notices:
def _predicate(notice):
"""Indicate whether *notice* should be captured and emitted."""
return isinstance(notice, Foo)
with unf.NoticeTransaction(stage, predicate=_predicate):
# Stage editing ...
For convenience, a unf.CapturePredicate.BlockAll()
predicate has been
provided to block all notices emitted during a transaction:
with unf.NoticeTransaction(
stage, predicate=unf.CapturePredicate.BlockAll()
):
# Stage editing ...
See also