Houdini TD - Create Shelves On The Fly


One thing you want in your pipeline is a way to distribute your tools to the artists through the shelf, you can do this several ways:


The first twos are the easiest to implement and maintain, but they have one major drawback: the users can’t customize their shelves. Soon enough you are going to have to fit that need, limiting basic user customisation of the software they use daily is usually not a good idea. That lets us with the third option but it can take a lot of time and is more error prone… Well here it is!

This script will automatically build a shelf from a dictionnary, call it in the 123.py file. It can also update shelves already created by putting only the needed changes in the json, if you only want to change an icon or the order of tools, you can.

TOOL_PARAMS = ['label', 'icon', 'script', 'language', 'help', 'helpURL']

def update_shelf(data):
    """Update a shelf based on a dict() constructed from build_json_from_shelf() useful for iterating shelves while letting the user customise their shelves
    All tools will be forced updated with the dict() data even if the user modified it. Other tools or attributes of a tool won't be updated if it's not in the dict()
    Do not change the "name" of a shelf/tool or you will have several references for the same tool. It can bypass PermissionErrors when files are write restricted.
    """
    try:
        hou.shelves.beginChangeBlock()
        shelf = get_shelf(name=data['name']) or hou.shelves.newShelf(file_path='', name=data['name'], label=data['label'])
        for name, parameters in data['tools'].items():
            t = hou.shelves.tool(name)

            # Give the ability to add post/pre scripts to the original script
            prescript = parameters.pop('prescript'), '')
            postscript = parameters.pop('postscript', '')
            if parameters.get('script') and (prescript or postscript):
                parameters['script'] = '\n'.join([prescript, parameters['script'], postscript])
            
            if t:  # Update already existing tools with parameters in dict but keep already existing parameters
                t.setLabel(parameters.pop('label', t.label()))  # There are no label argument in hou.Tool.setData()
                parameters.setdefault('help_url', parameters.pop('helpURL', t.helpURL()))  # The attribute helpURL and argument for tool.setData(help_url) has not the same nomenclature
                for parm in [i for i in TOOL_PARAMS if i not in ['label', 'helpURL']]:
                    parameters.setdefault(parm, getattr(t, parm)())
                t.setData(**parameters)
            else:  # Create new tool
                parameters['help_url'] = parameters.pop('helpURL', '')
                t = hou.shelves.newTool(name=name, **parameters)
            if t not in shelf.tools():  # Add it to the shelf
                shelf.setTools(list(shelf.tools()) + [t])

            tools = [y for x in data['order'] for y in shelf.tools() if x == y.name()]  # For Python 2.7 (dict unordered)
            diff = [i for i in shelf.tools() if i.name() not in data['order']]  # Add tools who were in the shelf, but are not in the initial dict order
            shelf.setTools(tools + diff)
        hou.shelves.endChangeBlock()  # This will write the changes to disk, remove this line if you don't want to
    except hou.PermissionError:
        # If the shelf files are not write accessible, houdini will raise an exception.
        # Because of hou.shelves.endChangeBlock() the UI will still be updated even though it won't be saved on disk
        pass
    finally:
        return shelf


def get_shelf(label='', name=''):
    """Return shelf by name or label"""
    for shelf in hou.shelves.shelves().values():
        if shelf.label() == label or shelf.name() == name:
            return shelf
    return None

“Yes but you have to build the json and it’s very cumbersome”. Well, why not create your shelf in the Houdini UI and let a script do the rest? This exporter will build the necessary dictionnary with all the data.

def build_json_from_shelf(shelf):
    """Build a json with all the data of a corresponding shelf. This way we can rebuild it procedurally with update_shelf()"""
    if isinstance(shelf, str):
        shelf = get_shelf(name=shelf) or get_shelf(label=shelf)
    result = {'label': shelf.label(), 'name': shelf.name(), 'tools': {}, 'order': []}
    for tool in shelf.tools():
        t = {}
        for parm in TOOL_PARAMS:
            attrib = getattr(tool, parm)()
            if attrib:
                t[parm] = attrib
        if t:
            result['order'].append(tool.name())  # For Python 2.7 (dict unordered)
            result['tools'][tool.name()] = t
    return result

Funny thing? It even let’s you customise the shelf even when the files are read only, without impeding their functionnalities… don’t tell your favourite TD!

Here a JSON example of what can be done.

{
    "label":"Shelf To Add",
    "name":"Shelftoadd",
    "tools":{
        "name":"tool_name",
        "label":"Tool Name",
        "icon":"MISC_present",
        "prescript":"if kwargs['ctrlclick'] and not all([kwargs['shiftclick'], kwargs['altclick'], kwargs['cmdclick']]):\n    import houtools\n    houtools.open_flipbook()\n    quit()",
        "script":"create_flipbook()",
        "help":"\n\n\nTooltip when hovering the Tool with the mouse\"\"\"",
        "":"",
    }
}