Create a python snippet container on Null or any node

In this tutorial i will assume you already know your way around Houdini and parameters Interface.

  • First let's create a Null node.

  • Go to the parameter's interface.

  • Add a string and a button parameter.

  • Name the button executeSnippet and the label Execute

  • Name the string pythonSnippet and the label Python Snippet.

  • On the string parm, tick the multi line option and change the language to python

  • Hit the apply button.

If everything is correct you should have something like this:

Null's parms interface

And This:

Null's parms

Now let's connect the button and the python snippet content. Select the button parm and in the callback script section put that following line:

exec(hou.parm("pythonSnippet").eval())

we eval the content of the string parm and pass it to the python exec command. Which just execute any string as python command. It should be like this:

Exec parm

Exec parm

Hit the accept button on the Parameters Option UI.

Let's test this! Open a python shell tab andpPut that command inside the string parm.

print "Hello World! Bitches"

Result

Nice! Pretty cool way to embed some python code hey?


Now let's see what we can do to make this a bit better. Say We would to be able to save and reuse code created in the snippet to a pre-defined location!

Alright so first we going to modify a bit the UI.

  • First add a second button and name it "saveCurrentSnippet" and label "Save Current Snippet"

  • Then Select the Sting parm, go to the Menu tab on the right. Tick on Use Menu and select the second to last option replace (Field + Single Menu Selection)

Now go back to the button and in the callback script section put that line.

import save_python_snippet as ps;ps.saveFile(hou.node("."))

Don't worry about the module's name, we will create it shortly after this.


Alright, so let's do the first part. Saving the content to a desired location. We will create a module with a few different functions in it. The easiest place to put it, is in folder where Houdini already looks for modules. You could definitely change that location but for that tutorial we'll stay simple.

The path would be C:\Users\YOURUSERNAME\Documents\houdini16.5\python2.7libs

If the last folder does not exists just create it manually. In that folder, python2.7libs, create a new python file and name it save_python_snippet.py ( Make sure you have one python file named __init__.py, otherwise it won't work ).

Restart Houdini, open a python shell tab and type:

import save_python_snippet

If everything works properly you should get something like this:

python-snippet_import-module.png

Cool! now let's create the function to save the snippet, but first some imports and some Globals. Globals are not usually good to use. But for this example we will discard this!

Put the following lines in our newly created module

import os
import hou

mainPath = '/YOUR/WORKING/PATH/saved-snippets'
suffix = '-snippet.py'

Make sure to change the path to something working. You could put it in the same directory as above.

C:\Users\YOURUSERNAME\Documents\houdini16.5\python2.7libs\saved-snippets

Ok now let's create our first function. Which is to list the content of that directory and find all the snippets python files already saved.

def getFileList():

    # we check if the path is an actual directory and we can write to it
    if not os.path.isdir(mainPath) and not os.access(mainPath, os.W_OK):
        raise Exception("Please specify a proper working directory")

    # we list all files in that directory
    files = os.listdir(mainPath)

    # we clean out the files we don't need
    files = [ f for f in files if f.endswith(suffix)]
    
    return files

Ok, so now we know the list we can check if the user would like to override a file if the given name already exists.

Let's try it! In the python shell, reload the module first to update the file.

reload(save_python_snippet)

then call the new module's function getFileList(). If everything works fine you should get an empty list in return. Since we haven't saved anything yet.

Now we will do the actual saving function.

def saveFile(node):

    # We ask the user what would it like to name the new snippet
    r, fileName = hou.ui.readInput("Please provide a name for the snippet")

    if fileName != '':

        # we make sure the fileName is proper
        if not fileName.endswith(suffix):
            fileName = fileName.replace(".py", "") + suffix

        existingFiles = getFileList()

        # if the fileName matches a current existing one
        # we ask for override
        if fileName in existingFiles:
            text = "Snippet with this name already Exists, Overwrite the file?"
            if not hou.ui.displayConfirmation(text,
                                title="Found Matching File",
                                suppress=hou.confirmType.OverwriteFile):
                return

        # We get the current content of the string parm
        snippet = node.parm("pythonSnippet").eval()

        # We write it to the file
        f = mainPath +'/'+ fileName
        with open(f, 'w') as ff:
            ff.write(snippet)

Ok now if you save the file and reload the module. You can test the button.

You should get a small dialog and ask you the name of the snippet before saving it.

xaBLtaEbJy[1].gif

Yes good stuff. We are pretty much done with the add-ons. We just need to finish the menu.

Open the Null's Parameter UI Editor and select the string parm. In the String parm's Menu Script section put this code:

import save_python_snippet as ps

l = ps.getFileList()

keys = l.keys()
keys.sort()

newL = []
for i in keys:
    newL.extend([ l[i], i] )

return newL

As you can see the output of getFileList() has changed from list to dict. This is because the way menu works. You have to return a long list of pairs in order to work properly. For example if you have something like:

l = ['a', 'A']

You will only have one value display in the menu. That value is 'A', the value 'a' is what is going to be returned and will replace your string parm's value. 

So basically what we want is to return the python file's content when the name is selected. But first we need to read each files and store it with it's current file name.

Here is the new function with the modifications:

def getFileList():

    # we check if the path is an actual directory and we can write to it
    if not os.path.isdir(mainPath) and not os.access(mainPath, os.W_OK):
        raise Exception("Please specify a proper working directory")

    # we list all files in that directory
    files = os.listdir(mainPath)

    # we clean out the files we don't need
    files = [ f for f in files if f.endswith(suffix)]
    contents = {}
    for f in files:
        with open(mainPath +"/"+ f, 'r') as rf:
            # reading the file's content
            c = rf.read()
            # creating the according dict
            # we remove the suffix for a better UI display
            contents[f.replace(suffix, '')] = c

    return contents

Sub sequentially we need to adjust the save function:

def saveFile(node):

    # We ask the user what would it like to name the new snippet
    r, name = hou.ui.readInput("Please provide a name for the snippet")

    if name != '':

        fileName = name
        # we make sure the fileName is proper
        if not fileName.endswith(suffix):
            fileName = fileName.replace(".py", "") + suffix

        existingFiles = getFileList()

        # if the fileName matches a current existing one
        # we ask for override
        if name in existingFiles.keys():
            text = "Snippet with this name already Exists, Overwrite the file?"
            if not hou.ui.displayConfirmation(text,
                                title="Found Matching File",
                                suppress=hou.confirmType.OverwriteFile):
                return

        # We get the current content of the string parm
        snippet = node.parm("pythonSnippet").eval()

        # We write it to the file
        f = mainPath +'/'+ fileName
        with open(f, 'w') as ff:
            ff.write(snippet)

The main changes are to make sure we don't override a pre existing file by mistake.

Full script here 


Alright here is the final phase. It's good to be able to save and reuse python snippet from a null. But if you loose it or delete by mistake then you will to recreate those parms. I'll just show you how you can simply add those parms to any selected nodes.

In the same file save_python_snippet.py append the following code:

def addParmsToNode(node):


    # This is the code we put in the menu script to generate the menu
    menuScript="""import save_python_snippet as ps

l = ps.getFileList()

keys = l.keys()
keys.sort()

newL = []
for i in keys:
    newL.extend([ l[i], i] )

return newL
"""

    # let's create the new Folder
    snippetFolder = hou.FolderParmTemplate("folder", "Snippet", folder_type=hou.folderType.Simple)

    # we add the button Execute
    snippetFolder.addParmTemplate(hou.ButtonParmTemplate(
                                            "execute",
                                            "Execute",
                                            script_callback = 'exec(hou.parm("pythonSnippet").eval())',
                                            script_callback_language = hou.scriptLanguage.Python ))

    # then the button Save Current Snippet
    snippetFolder.addParmTemplate(hou.ButtonParmTemplate(
                                            "saveCurrentSnippet",
                                            "Save Current Snippet",
                                            script_callback = 'import save_python_snippet as ps;ps.saveFile(hou.node("."))',
                                            script_callback_language = hou.scriptLanguage.Python ))

    # we add a separator between the buttons and the string parms
    snippetFolder.addParmTemplate(hou.SeparatorParmTemplate('sep'))

    # we add the string parm
    snippetFolder.addParmTemplate(hou.StringParmTemplate(
                                            "pythonSnippet",
                                            "Python Snippet",
                                            1,
                                            tags = {"editor": "1",          # the tags are here to specify
                                                    "editorlang": "python"}, # the multi-line and python language
                                            menu_type = hou.menuType.StringReplace,
                                            item_generator_script = menuScript)) # here we specify the menuscript
    # we grab the current parm Template group
    gr = node.parmTemplateGroup()

    # we append the new folder to the current parm layout
    gr.append(snippetFolder)

    # we set it back on the node
    node.setParmTemplateGroup(gr)

Now we just need to put it in a shelf tool and call it from the tab menu.

Here is the code:

import save_python_snippet as ps

nodes = hou.selectedNodes()
for n in nodes:
    ps.addParmsToNode(n)

Woohoo! done!

KSaQxNNzTO[1].gif

Now we can add those parms on any nodes!