Houdini 12 Python Scripting with the Houdini Object Model

You can use HOM to store and retrieve arbitrary data on individual nodes.

Overview

Sometimes you need to store extra information on individual nodes. For example, you may want to...

  • Associate scene-level nodes with production database entries or asset tags.

  • Attach additional versioning information beyond what is already stored by Houdini.

  • Tag nodes as having been operated on by some tool to avoid unnecessary work.

The hou.Node.setUserData, hou.Node.userData, hou.Node.userDataDict, and hou.Node.destroyUserData methods let you set and get a name/string pair on a given node instance. The name/string pairs are saved with the node in the .hip file and so persist between Houdini sessions.

Similarly, the hou.Node.setCachedUserData, hou.Node.cachedUserData, hou.Node.cachedUserDataDict, and hou.Node.destroyCachedUserData methods let you set a name/object pair on a given node instance. This cached data is not saved with the hip file, and the objects may be any Python object (not just strings). Cached data is useful for nodes implemented using Python, since they can save temporary values between cooks in order to avoid recomputing them on subsequent cooks.

Persistent Data Usage

>>> import time
>>> # Get a node instance
>>> n = hou.node("/obj/sphere1")
>>> # Put a name/value on the node
>>> n.setUserData("last_indexed", str(time.time()))
>>> # Get the named value back from the node
>>> n.userData("last_indexed")
1260572254.21
>>> # Get a dictionary of all name/value pairs on the node
>>> n.userDataDict()
{'last_indexed': '1260572254.21'}
>>> # Accessing a non-existent name returns None
>>> print n.userData("foo")
None
>>> # Remove a value from the node
>>> n.destroyUserData("last_indexed")

The contents of the string can be structured data such as XML, JSON, compressed binary data, or pickled Python objects, however you are responsible for encoding and decoding structured data to/from simple strings.

For example, here are two functions you could use to store compressed JSON representations of Python values:

import zlib
import hutil.json

def setCompressedJSON(node, data):
    node.setUserData(
        "acme_studio", zlib.compress(hutil.json.utf8Dumps(data)))

def compressedJSON(node):
    str_data = node.userData("acme_studio")
    if str_data is None:
        return None
    return hutil.json.utf8Loads(zlib.decompress(str_data))
>>> n = hou.node("/obj/geo1")
>>> setNodeData(n, {"version": 1, "database_id": 1723})
>>> print nodeData(n)["database_id"]
1723

Cached Data Usage

The following example code would exist in the Cook tab of a node type (e.g. a SOP) implemented using Python. It avoids unnecessarily reparsing a configuration file between subsequent cooks.

def cook(node):
    config_file_name = node.evalParm("configfile")
    if node.cachedUserData("config_file_name") != config_file_name:
        config = ConfigParser.RawConfigParser()
        config.read(config_file_name)
        node.setCachedUserData("config", config)
        node.setCachedUserData("config_file_name", config_file_name)
    else:
        config = node.cachedUserData("config")

    # Use the configuration values and perform the cook.
    #value = config.getfloat("foo")
    # ...

cook(hou.pwd())

Tips

  • Trying to access a non-existent value returns None. It does not raise a KeyError or other exception. Since Node.userData returns a string if the key is valid and None otherwise, you can simply check against None to see if a key is valid. However, Node.cachedUserData may contain None for a valid key, so use key in node.cachedUserDataDict() to check if a key is valid.

  • To remove a name/value from a node, use Node.destroyUserData or Node.destroyCachedUserData. Deleting the value from the dictionary returned by Node.userDataDict or Node.cachedUserDataDict will not remove the value from the node. If you try to remove a non-existent key, the methods will raise hou.OperationFailed.

  • To avoid namespace conflicts for persistent node data, you should consider storing all your data in a single name/value pair (e.g. using JSON), rather than storing multiple name/value pairs per node. Namespace conflicts should be less common for cached node data, since cached data is intended for use by the code implementing a Python node.

  • When storing structured data in persistent node data, you should add a version key to identify the format in which the data is stored, as shown in the example above. This practice will come in handy when you need to change or extend the format and want your tools to deal gracefully with old data.