Creating a Usdview Plugin
VERIFIED ON USD VERSION 0.8.3
In this tutorial, we'll create a new Python plugin for Usdview and learn how to use the Usdview API.
Setting Up a PluginContainer
First, we'll create a new directory to hold our new plugin. We can put this directory anywhere, but it's a good idea to nest it in a directory that can hold other plugins in case we want to install more in the future.
mkdir -p <some path>/usdviewPlugins/tutorialPlugin/
We now want to create a new PluginContainer class in our plugin module's __init__.py file.
PluginContainers are just objects which knows how to register new command plugins and add them to Usdview's UI. This is done through 2 methods:
When a PluginContainer is loaded, Usdview's plugin system first calls registerPlugins() which gives the container a chance to add its command plugins to the plugin registry. Each command plugin needs an identifier string, display name, and callback function. The identifier must be globally unique, so it is good practice to prepend the name of the plugin container. All command plugin callbacks take a usdviewApi object as their only parameter (we'll learn how to use the API later). In our example, we registered a new plugin which prints "Hello, World!" when invoked.
After a PluginContainer has registered all of its command plugins, it is given the chance to expose them to Usdview's UI in the configureView() method. Currently, plugins can only create simple menus in Usdview's menu bar as well as open new Qt windows. Our example creates a new menu named "Tutorial" and adds our "printMessage" command plugin to the menu.
Since plugins are loaded through Pixar's libplug library, we also need to define our PluginContainer as a new Tf.Type. This just lets libplug find the container later. We also need to create a new plugInfo.json file in our plugin directory, so we'll do that now.
If you're making your own plugin, all you need to do is change the "Name" field to match your plugin's Python module name, change the "tutorialPlugin.TutorialPluginContainer" type to match your PluginContainer type name, and update the "displayName."
Lastly, we need to configure the environment. libplug loads Python plugins by importing the module directly, so we need to make sure our plugins directory (usdviewPlugins/ in our example, NOT tutorialPlugin/) is listed in our PYTHONPATH environment variable. If we want libplug to load our plugin, we also need to add the path to the plugin directory (this time tutorialPlugin/ in our example) to the PXR_PLUGINPATH_NAME environment variable.
At this point, if we open Usdview we should see a new "Tutorial" menu. If we open this menu and select "Print Message," we should see "Hello, World!" printed to the console.
Congratulations! You've just created a new Usdview plugin!
Using the Usdview API
Now that you can create command plugins, you can start interacting with Usdview using the
usdviewApi object. An overview of API features is given below below. For a full listing of all API features, open the Interpreter window in Usdview (Window > Interpreter), and type
usdviewApi.dataModel- a full representation of Usdview's state. The majority of the data and functionality available to plugins is available through the data model.
stage- The current Usd.Stage object.
currentFrame- Usdview's current frame.
viewSettings- A collection of settings which only affect the viewport. Most of these settings are normally controlled using Usdview's 'View' menu. Some examples are listed below.
complexity- The scene's subdivision complexity.
freeCamera- The camera object used when Usdview is not viewing through a camera prim. Plugins can modify this camera to change the view.
renderMode- The mode used for rendering models (smooth-shaded, flat-shaded, wireframe, etc.).
selection- The current state of prim and property selections.
- Common prim selection methods:
getFocusPrim(), getPrims(), setPrim(prim), addPrim(prim), clearPrims()
- Common property selection methods:
getFocusProp(), getProps(), setProp(prop), addProp(prop), clearProps()
- Common prim selection methods:
usdviewApi.qMainWindow- Usdview's Qt MainWindow object. You can use it as a parent for other Qt windows and dialogs, but you are strongly discouraged from using it for anything else.
usdviewApi.PrintStatus(msg)- Prints a status message at the bottom of the Usdview window.
GrabWindowShot()- Captures a screenshot of the viewport or the entire main window and returns it as a QImage.
Usdview is designed to be quick to launch, so to be a good Usdview citizen we should make sure our plugin loads as quickly as possible. Sometimes, importing other Python modules takes a noticeable amount of time, so it is a good idea to lazy-import them when our command plugin is called for the first time.
The easiest way to do this is by putting our plugin logic into a separate Python file and using the deferredImport(moduleName) method available on PluginContainers. Let's fix the above example to use this method.
First, we'll put our printMessage function into a new Python file called printer.py. This function doesn't require any heavy imports, so we'll just print a message when the file is imported so we know it was deferred properly.
Then, we'll import the module the normal way (without deferring yet) in __init__.py and make sure to call the printMessage function off this new module.
If we run Usdview now, we'll immediately see the message "Imported printer!" appear in the console. Now, we'll do a deferred import.
All we did was remove printer from our imports and add line 10. The deferredImport method just returns a fake module object, and pretends to know about any function we access on it. The first time one of its functions is called, it actually imports the target module and calls its function instead.
deferredImport doesn't know anything about the target module until it is imported, so it assumes any object we reference is a function in the module. If you try to use a function which doesn't exist on the target module, we'll get an ImportError.
If we run Usdview again, we won't see the "Imported printer!" message until we invoke printMessage. The module is only imported once, so invoking
printMessage multiple times will only print the message the first time.