Houdini 20.5 Python scripting

Render Gallery background render plugin

On this page

Overview

The render snapshot gallery lets you snapshot the current Solaris view and network, or start a background render to render the view fully in the background.

You can write a Python plugin that implements a type of background render. Houdini ships with two background render implementations, to render in a background thread, and to render on an HQueue farm. You might implement your own background render to integrate with another render farm manager, or a studip’s proprietary render pipeline.

  • Houdini looks for render import plugins in HOUDINIPATH/husdplugins/backgroundrenderers/.

    You can customize where Houdini looks for USD-related plugins by setting the $HOUDINI_HUSDPLUGINS_PATH environment variable.

  • Create a Python source code (.py) file that will be found in that path, for example $HOUDINI_USER_PREF_DIR/husdplugins/backgroundrenderers/my_bg_render.py.

    Copy the template content below and paste it into your plugin file.

  • A plugin file should have at least one background render class (subclassed from husd.backgroundrenderer.BackgroundRenderer), and a function named registerBackgroundRenderers() that takes a single manager argument and uses it to register the background render classes in the file (see the plugin file template).

  • You can define and register background render plugins in a single .py file (this improves startup time). However, you may want to keep each plugin in a separate file to have the flexibility to move them into/out of the path individually.

  • Files earlier in the path override files with the same name later in the path.

Plugin file template

In the Python file, paste in the following skeleton:

from husd.backgroundrenderer import BackgroundRenderer


# Your render class should subclass husd.backgroundrenderer.BackgroundRenderer.
# You probably want to name your class based on the type of render it implements,
# for example CloudServiceRenderer.

class MyCustomRenderer(BackgroundRenderer):
    # The default __init__ implementation sets up some useful instance variables:
    # self._lopnet_path:
    #     (str) The node path of the LOP network, for example "/stage"
    # self._item_id:
    #     (str) A unique ID string for this render. You can use this to construct
    #     names or identifiers, for example a job name for a render farm manager.

    # You can call the following helper functions:
    #
    # self.updateImagePath(image_path)
    #     Update the snapshot to use the image at the given path.
    #
    # self.updateMetadata(metadata)
    #     Update the snapshot's metadata using the given dict. The keys are mostly
    #     arbitrary, but a few are used by Houdini's UI:
    #     "totalClockTime":
    #         (float) current elapsed time in seconds, the render gallery viewer
    #         displays this in the render stats.
    #     "percentDone":
    #         (float) Percent complete (0-100), also displayed in the render stats.
    #     "peakMemory":
    #         (int) Maximum memory usage of the 

    def pollFrequency(self):
        # How often (every this number of (floating poitn) seconds) the system
        # should ask call the methods on this object to get updates.
        return 1.0

    def getConfigOptions(self, old_options):
        # This method is intended to launch a configuration interface, get
        # option values from the user, and then return the option key/values
        # as a dict. The argument is a dict containing what this method
        # returned the last time it was called, so you can fill in the
        # previous values in the options UI for the user.
        # This method is only called if the user chooses the render plugin
        # from the menu. If the user just clicks the Background button,
        # the system will call startBackgroundRender() with the previous
        # values directly instead of calling this method to show the config
        # UI first.
        return {}

    def startBackgroundRender(self, usd_filepath, options):
        # This method should start the background renderer.
        # usd_filepath: the file path of the USD file to render.
        # The options argument is a dict where:
        # - options["plugin"] is the user options dict returned by
        #   getConfigOptions().
        # - options["viewport"]["rendersettings_path"] is the scene graph path
        #   of the RenderSettings prim for the snapshot.
        # - options["viewport"]["camera_path"] is the scene graph path to the
        #   camera prim for the snapshot.
        # - options["viewport"]["res_x"] is the pixel width of the snapshot
        #   render product.
        # - options["viewport"]["res_y"] is the pixel height of the snapshot
        #   render product.
        pass

    def isRenderFinished(self):
        # The system will call this method periodically. It should return
        # True if the render started by this object has finished.
        #
        # This is where you can call self.updateImagePath(image_path)
        # and/or self.updateMetadata(metadata_dict) to send feedback about
        # your render's progress.
        pass

    def mouseClick(self, x, y):
        # Called when the user clicks in a render preview window.
        # x and y are normalized coordinates, so for example x=0.0 means
        # the left edge, x=0.5 means the center, and x=1.0 means the
        # right edge.
        # The usual behavior for mouse clicks is to concentrate rendering
        # in the part of the image where the user clicked.
        pass

    def stopBackgroundRender(self):
        # Called if the user right-clicks the live snapshot and chooses
        # Stop Rendering. This method should not return until the render
        # is stopped.
        pass


# You must include the registration function: this is what Houdini looks for
# when it loads your plugin file

def registerBackgroundRenderers(manager):
    # The first argument is a human-readable label for the render type,
    # the second argument is your background render class
    manager.registerBackgroundRenderer('Cloud Service Render', MyCustomRenderer)

Updating Houdini

At intervals controlled by the number of seconds you return from your pollFrequency() method, Houdini will call your plugin’s isRenderFinished() method. The method should return True if the render has completed, or False otherwise.

Besides returning whether the render is finished, this is where you can update Houdini on the progress of the render, if possible, by calling the self.updateMetadata(metadata_dict) and/or self.updateImagePath(image_file_path) helper methods.

self.updateMetadata(metadata_dict)

Update the metadata associated with the snapshot. The user can see the metadata keys and values if they click the Info button in the render gallery viewer.

If the following keys are in the dictionary, Houdini will display the values as part of the render stats in the top right corner of the image preview:

"totalClockTime"

(float) current elapsed time in seconds. The render gallery viewer will display this

"percentDone"

(float) Percent complete (0-100), also displayed in the render stats.

"peakMemory"

(int) Maximum memory usage of the renderer in bytes.

self.updateImagePath(image_file_path)

Calling this helper method tells Houdini it should re-read the image at the given file path and use it as the snapshot image. If your render plugin supports progressive updates of the snapshot, you should call this whenever the image has changed, even if the image file path is the same each time.

Note

You should always call self.updateImagePath(image_file_path) at least once in the lifecycle of your plugin object: in isRenderFinished() when the render is, in fact, finished.

See the example below.

Launching an external process

If the render type your plugin implements involves running an external program, you can use Python’s subprocess.Popen() API to launch the external process and monitor it.

  • You can get the process ID from the returned Popen object using Popen.pid.

  • Depending on the platform you may be able to use the process ID to get stats about the process, such as its CPU usage and virtual memory usage. (There is a third party Python library called psutil which is deigned to extract info about a process in a cross-platform way, which might be useful.)

  • You can check if the process has exited by checking the result of Popen.poll() (it returns a return code if the process is finished, or None if it is still running).

  • You can implement stopBackgroundRender() by calling Popen.kill().

The folliwng imaginary example shows how you could launch an external render executable called fastrender, check if it’s still running, and terminate it, as needed.

import datetime
import json
import subprocess
import tempfile

from husd.backgroundrenderer import BackgroundRenderer


class FastRender(BackgroundRenderer):
    def pollFrequency(self):
        # The system should check this render for updates every 2 seconds.
        return 2.0

    def startBackgroundRender(self, usd_filepath, options):
        self._popen = subprocess.Popen(
            ["fastrender",
             "--image", self._image_filepath]
        )
        # Remember when the render started
        self._start_datetime = datetime.datetime.utcnow() 

    def isRenderFinished(self):
        # Update the render metadata
        # If the process has some way of progressively outputting render stats
        # (for example, you can interpret its output to stdout, or it periodically
        # writes out a JSON file), you can use that to populate the metadata dict.
        metadata = {}

        # We can keep track of the process's wall-clock ourselves pretty easily
        time_delta = datetime.datetime.utcnow() - self._start_datetime
        metadata["totalClockTime"] = time_delta.total_seconds()

        # In this example, we'll imagine the render process is progressively
        # updating the image file on disk, so we can keep telling the system
        # that the image has updated
        self.updateImagePath(self._imagepath)

        # Popen.poll() returns a return code int if the process has finsihed,
        # otherwise it returns None
        rc = self._popen.poll()
        return rc is not None

    def stopBackgroundRender(self):
        self._popen.kill()


def registerBackgroundRenderers(manager):
    # The first argument is a human-readable label for the render type,
    # the second argument is your background render class
    manager.registerBackgroundRenderer('Fast Render', FastRender)

The example above assumes a render process that continuously rewrites the output image as it renders. If instead you only know the image is finsiehd when the render process is complete, you could implement isRenderFinished() like this:

def isRenderFinished():
    # ... udpate metadata ...

    # Popen.poll() returns a return code int if the process has finsihed,
    # otherwise it returns None
    rc = self._popen.poll()
    if rc is not None:
        # The render is finished, tell Houdini to update the snapshot image
        self.updateImagePath(self._imagepath)
        return True
    else:
        return False

Python scripting

Getting started

Next steps

Reference

  • hou

    Module containing all the sub-modules, classes, and functions to access Houdini.

Guru level

Python viewer states

Python viewer handles

Plugin types