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 ...