On this page

Overview

See USD basics and About LOP node for background information on USD and how LOP nodes generate USD layers.

A LOP network either generates a USD stage, either from scratch or starting with a top-level USD file, possibly adding in USD layers from files. At the end of the network, you can write out the finished USD, which will often involve writing out multiple files: a top-level file and separate files for each layer.

Layers being read from disk are always left unmodified by the save process, though an anonymous layer generated by LOP nodes may overwrite an existing layer file on disk.

How to

  • The main node that writes out the USD is the USD render node. This node is available as a LOP (which you can put at the end of the LOP network) or a ROP (which you can put in a render network and point at the LOP network you want to output).

    (See other ways to write out USD below for more specialized options.)

  • You can also use the USD Zip render node which outputs a self-contained USDZ file. This file is an archive containing all the top-level USD, layer files, external files (such as textures) and so on.

    USDZ retains all the edit-ability of the "loose" files with the convenience of a single file. It also has certain features that make reading USD directly out of the archive efficient. This is very useful for publishing USD on the Internet.

Where the files go

  • In the USD render node, you specify a file path (in the Output file parameter) for the "top-level" USD file, containing data from the root layer.

    In addition to this file, the node writes any layers that have their file path metadata set to their own USD files.

  • Layers that were imported from disk remember their file paths and are written out to those paths, relative to the top-level file.

  • You can use the Configure Layer LOP to assign/change a file path for any layer in the network. When you render to USD the layer will be written out to disk.

  • The SOP Import LOP and SOP Create LOP also let you specify a file path where the geometry will be written when you write out USD.

  • File paths in Houdini are usually specified relative to the HIP file ($HIP/props/lamp.usd), while paths in USD are recommeded to be relative to the top-level file (./props/lamp.usd). The USD render node has a default output processor that translates paths to be relative to the top-level file as the node writes the USD.

  • The Separate file per frame parameter on the render node controls whether it writes out individual files for each frame (for example, lamp_0001.usd, lamp_0002.usd, and so on) or files containing time sample data from all frames.

Output processors

Output processors are Python plugins that can alter the file locations and file path strings used for external files.

The USD render node starts with one default output processor the makes file path references in layer files relative.

Writing a custom output processor

  1. Create a new directory $HOUDINI_USER_PREFS_DIR/python2.7libs/husdoutputprocessors.

    (We’ll create the plugin in the user prefs directory in this example. Of course, if you could put the python2.7libs/husdoutputprocessors directory under any directory on the Houdini path.)

  2. Inside $HOUDINI_USER_PREFS_DIR/python2.7libs/husdoutputprocessors, create a file called __init__.py and paste in the following Python code:

    __path__ = __import__('pkgutil').extend_path(__path__, __name__)
    

    // NOTE: When we switch to Python 3, this will no longer be necessary,

    // we should use native Python namespaces instead of pkgutil

  3. Inside $HOUDINI_USER_PREFS_DIR/python2.7libs/husdoutputprocessors. create a Python file for your output processor plug-in. For example:

    $HOUDINI_USER_PREFS_DIR/python2.7libs/husdoutputprocessors/outputreview.py
    
  4. In the Python plug=in file, sublcass the husdoutputprocessors.base.OutputProcessorBase class.

    In the subclass, implement the displayName() method to return a string describing what the processor does (for example, Save Paths Relative to Output Path).

    import hou
    from husdoutputprocessors.base import OutputProcessorBase
    
    
    class ReviewOutputProcessor(OutputProcessorBase):
        def displayName(self):
            return "Manually Review Every Output Path"
    
  5. At the bottom of the file, define a function called usdOutputProcessor() that takes no arguments and returns a single instance of your class whenever it’s called.

    The existence of this function is what marks this module as implementing an output processor (Houdini looks for modules containing this function), and how Houdini gets your output processor object (it does not instantiate your class directly, it always uses this function).

    import hou
    from husdoutputprocessors.base import OutputProcessorBase
    
    
    class ReviewOutputProcessor(OutputProcessorBase):
        def displayName(self):
            return "Display the list of output files"
    
    
    # Must have: module-level function to return a processor instance
    outputprocessor = ReviewOutputProcessor()
    def usdOutputProcessor():
        return outputprocessor
    

    Note

    You have to maintain and return a single instance because currently the render node currently calls the factory method over and over, every time it needs to call a method on the object, so currently you can only keep information between calls if you always return the same instance. This will probably change in a future version of Houdini.

  6. Override API methods to implement your output processor. See the output processor API below.

    import hou
    from husdoutputprocessors.base import OutputProcessorBase
    
    
    class ReviewOutputProcessor(OutputProcessorBase):
        def displayName(self):
            return "Display the list of output files"
    
        def processSavePath(self, asset_path, asset_is_layer):
            # Make the asset path absolute
            asset_path = hou.text.abspath(asset_path)
    
            # This processor asks the user to manually rewrite every file path.
            # This is just an example, don't do this! It would be annoying!
            return hou.ui.readInput(
                message="Rewrite this output file path if you want",
                initial_contents=asset_path,
                buttons=("OK",),
            )
    
        def processReferencePath(self, asset_path, source_path, asset_is_layer):
            # Make file path pointers relative to the source file's location
            return hou.text.relpath(asset_path, source_path)
    
    
    # Must have: module-level function to return a processor instance
    outputprocessor = ReviewOutputProcessor()
    def usdOutputProcessor():
        return outputprocessor
    

Output Processor method API

hidden(self)bool

If this method returns True, this processor is not included in the list of output processors shown to the user. The default implementation always returns False.

displayName(self)str (REQUIRED)

Returns a label to describe the processor, in the list of output processors shown to the user.

The label should describe the function/of the processor, for example: Save Paths Relative to Output Path.

Note

You must override this method in your subclass, otherwise it will raise a NotImplementedError exception when Houdini tries to instantiate your class.

parameters(self)str

Returns a string containing Houdini "dialog script" describing the parameters this processor shows to the user for configuration.

The default implementation returns the script for an empty parameter group, so if your processor doesn’t need any parameters, you don’t need to override this method.

You can generate the dialog script by building up a hou.ParmTemplateGroup with hou.ParmTemplate objects inside, and then returning the value from hou.ParmTemplateGroup.asDialogScript().

group = hou.ParmTemplateGroup()
group.append(hou.StringParmTemplate(
    "texturedir",
    "Texture Directory",
    string_type=hou.stringParmType.FileReference
))
return group.asDialogScript()

The internal names of any parameters you create here must be unique among all other parameters on the render node, so you probably want to use a naming scheme like modulename_parmname (where modulename is the name of the Python module under husdoutputprocessors).

beginSave(self, config_node, t)

Called when a render node using this processor starts to write out files. This gives you the chance to read parameter values (either the configuration parameters added by parameters(), or the render node’s own parameters, depending on what information you need).

config_node

A hou.Node object representing the render node.

t

A floating point value representing the time (in seconds) along the timeline which the node is rendering. If you read parameter values from the node, you should use Parm.evalAtTime(t) in case the parameter is animated.

processSavePath(self, asset_path, is_layer)str

Called when the render node needs to determine where on disk to save an asset. The asset_path is the file path as Houdini knows it (for example, from USD metadata or a Houdini parameter).

This should return an absolute path to where the asset should be saved. (If you return a relative path from this method, it will be relative to the current directory (os.getcwd()) which is probably not what you want.)

asset_path

The path to the asset, as specified in Houdini. This string comes with expressions and environment variables (such as $HIP) expanded already, so if you want to compare to another path, you should also expand that path (for example, with os.path.expandvars() or hou.text.expandString()).

is_layer

A boolean value indicating whether this asset is a USD layer file. If this is False, the asset is something else (for example, a texture or volume file).

Note

Only the first processor in line sees the asset_path as it was originally in Houdini. For all other processors in line, they receive the absolute path the first call returned.

processReferencePath(self, asset_path, asset_saved_path, source_path, is_layer)str

Called when the render node needs to write a file path pointing to an asset (to sublayer or reference in the file).

asset_path

The path to the asset, as specified in Houdini. This is the same string as would have been passed to processSavePath() when the asset was saved. The return value from that call is passed to this method as asset_saved_path.

asset_saved_path

The processed, absolute save location of the asset. This string comes with expressions and environment variables (such as $HIP) expanded already, so if you want to compare to another path, you should also expand that path (for example, with os.path.expandvars() or hou.text.expandString()).

source_path

The processed, absolute save location of the "source" file containing the pointer being written. You can use this to make the path pointer relative. (For example, hou.text.relpath(asset_saved_path, source_path)).

is_layer

A boolean value indicating whether this asset is a USD layer file. If this is False, the asset is something else (for example, a texture or volume file).

endSave(self)

Called when the render node using this processor is finished writing out files.

Adding an output processor in a script

  • Houdini will use an output processor on a USD render node if the node instance has a spare checkbox parameter named enableoutputprocessor_modulename that is turned on (where modulename is the name of the Python module under husdoutputprocessors).

    For example, if you implemented your class in $HOUDINI_USER_PREFS_DIR/python2.7libs/husdoutputprocessors/myprocessor.py, then you would need to have a spare checkbox parameter named enableoutputprocessor_myprocessor on a node to activate the processor for that node.

  • This means you can enable a processor by creating the correct parameter and turning it on, such as in a script, even if the processor is hidden from the processor list in the user interface (see the hidden() method above).

    You can disable a processor by turning off the spare checkbox, or deleting the spare parameter.

  • If the output processor has additional parameters for configuring it (see the parameters() method above), you can create and fill in those parameters in a script as well.

Saving animation

The Separate file per frame parameter on the USD render node controls whether it writes out individual files containing the data for each frame, or files containing time sample data across all frames.

When the USD render node writes out a frame range with Separate file per frame off:

  1. For each frame the ROP will generate a set of layers ready to be saved to disk, but which still exist in-memory.

  2. It uses USD Stitch to combine the generated frames with previously cooked frames in-memory.

If the LOP Network is generating a lot of data, this can quickly use a lot of memory (even though the stitch operation does not duplicate data which is the same from frame to frame).

If you find writing out animated USD runs out of memory in Houdini, you can instead save out separate layer files per-frame, and use USD Stitch or USD Stitch Clips to batch-combine them from disk, either into layers containing time samples, or into a set of value clips.

Other ways to write out USD

  • You can use Python (interactively in a Python shell or procedurally in a Python Script LOP) to write out individual layers.

  • You can right-click a node, open the LOP Actions sub-menu, and choose Inspect flattened stage or Inspect active layer. These menu items open a viewer window showing the stage/layer as usda code. You can save the usda code to a file from this window.

Solaris

USD

Tutorials

Karma