Creating a Usdview Plugin

Creating a Usdview Plugin


VERIFIED ON USD VERSION 18.09

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.

tutorialPlugin/_init_.py
from pxr import Tf
from pxr.Usdviewq.plugin import PluginContainer


def printMessage(usdviewApi):
    print("Hello, World!")


class TutorialPluginContainer(PluginContainer):

    def registerPlugins(self, plugRegistry, usdviewApi):

        self._printMessage = plugRegistry.registerCommandPlugin(
            "TutorialPluginContainer.printMessage",
            "Print Message",
            printMessage)

    def configureView(self, plugRegistry, plugUIBuilder):

        tutMenu = plugUIBuilder.findOrCreateMenu("Tutorial")
        tutMenu.addItem(self._printMessage)

Tf.Type.Define(TutorialPluginContainer)

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.

plugInfo.json
{
    "Plugins": [
        {
            "Type": "python",
            "Name": "tutorialPlugin",
            "Info": {
                "Types": {
                    "tutorialPlugin.TutorialPluginContainer": {
                        "bases": ["pxr.Usdviewq.plugin.PluginContainer"],
                        "displayName": "Usdview Tutorial Plugin"
                    }
                }
            }
        }
    ]
}

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 help(usdviewApi).

  • 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()
  • 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.
  • GrabViewportShot()/GrabWindowShot() - Captures a screenshot of the viewport or the entire main window and returns it as a QImage.

Deferring Imports

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.

tutorialPlugin/printer.py
print("Imported printer!")


def printMessage(usdviewApi):
    print("Hello, World!")

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.

tutorialPlugin/_init_.py - Normal Import
from pxr import Tf
from pxr.Usdviewq.plugin import PluginContainer

import printer


class TutorialPluginContainer(PluginContainer):

    def registerPlugins(self, plugRegistry, usdviewApi):

        self._printMessage = plugRegistry.registerCommandPlugin(
            "TutorialPluginContainer.printMessage",
            "Print Message",
            printer.printMessage)

    def configureView(self, plugRegistry, plugUIBuilder):

        tutMenu = plugUIBuilder.findOrCreateMenu("Tutorial")
        tutMenu.addItem(self._printMessage)

Tf.Type.Define(TutorialPluginContainer)

If we run Usdview now, we'll immediately see the message "Imported printer!" appear in the console. Now, we'll do a deferred import.

tutorialPlugin/_init_.py - Deferred Import
from pxr import Tf
from pxr.Usdviewq.plugin import PluginContainer


class TutorialPluginContainer(PluginContainer):

    def registerPlugins(self, plugRegistry, usdviewApi):

        printer = self.deferredImport(".printer")
        self._printMessage = plugRegistry.registerCommandPlugin(
            "TutorialPluginContainer.printMessage",
            "Print Message",
            printer.printMessage)

    def configureView(self, plugRegistry, plugUIBuilder):

        tutMenu = plugUIBuilder.findOrCreateMenu("Tutorial")
        tutMenu.addItem(self._printMessage)

Tf.Type.Define(TutorialPluginContainer)

All we did was remove printer from our imports and add line 9. 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.

SendMail Example Plugin

An example plugin file is provided with the USD distribution at USD/extras/usd/examples/usdviewPlugins/sendMail.py. We can add this plugin to our PluginContainer and specify sendMail.SendMail as the command callback function.

When SendMail is invoked, a dialog opens which prompts the user to send an email which contains a screenshot of Usdview. The user can modify the recipient, subject, and body of the email and select whether to send a screenshot of the entire main Usdview window or just the render viewport.

If you inspect sendMail.py, you can see it call usdviewApi.GrabWindowShot() and usdviewApi.GrabViewportShot() to capture both types of screenshot. You can also see an example of creating a dialog parented to the Usdview main window using usdviewApi.qMainWindow. The plugin also includes several pieces of data from the API to the email body.


Graphics Home