Callbacks

Using the nuke.add...() functions described below, you can call Python functions automatically when various events (such as creating a node or loading a script) happen in NUKE.

You can use all the nuke.add...() functions in your init.py or menu.py files. This way, the code is considered part of your NUKE environment and does not vary from script to script.

All the nuke.add...() functions take the same arguments, for example:

nuke.addOnCreate (callable, args=( ), kwargs={}, nodeClass='*')

Where:

  • callable is a Python callable, such as the name of a defined function.

  • args is the list of arguments. These should be in parenthesis. For example:

    nuke.addOnCreate(nuke.tprint, ('what to print')).
  • kwargs is a list of arguments given as keyword+value pairs. For example:

    nuke.addOnCreate( nuke.tprint, ( 'a', 'b' ), { 'sep': ',' } )
  • nodeClass the code is only called if nuke.thisNode().nodeClass() equals this string. For example:

    nuke.addOnCreate(nuke.tprint, ('Creating Write'), nodeClass='Write')

The default value of ‘*’ means that this is called for all nodes.

During the callback, there is a context node (the affected node), which is examined using nuke.thisNode(). For knobChanged, there is also a context knob which you can get using nuke.thisKnob().

For many of the nuke.add...() functions, such as onCreate, there are also knobs with the same name. These knobs fall into two categories:

  • Visible knobs - These are the knobs that the artists are expected to edit. They can be seen on the Python tab of some Properties panels. The visible knobs include onScriptLoad, onScriptSave, onScriptClose, beforeRender, beforeFrameRender, afterRender, and afterFrameRender.
  • Hidden knobs - These are not visible in any Properties panels, but can be set using Python. These are intended to allow you to define the behavior of gizmos. Artists should not set these knobs, as the user settings override any saved gizmo settings. Hidden knobs include onCreate, onDestroy, knobChanged, updateUI, and autolabel.

If there are many callbacks registered, code attached to knobs is always called first before the nuke.add...() functions. (For example, code put in the onCreate knob is called before the nuke.addOnCreate() function.) In most cases, it is then followed by all the nuke.add...() functions with a matching nodeClass in the order they were added, and finally the ‘*’ ones, also in the order they were added. Note that the addAutolabel() and addFilenameFilter() functions are exceptions to this rule, however, as they are called in reverse order.

All the add callbacks calls have a corresponding remove callback call.

OnUserCreate

nuke.addOnUserCreate(function)
nuke.removeOnUserCreate(function)

This is executed whenever a node is created by the user using the NUKE graphical user interface (GUI). It is also called at start-up on the Root and Viewer nodes. It is not called when loading existing scripts, pasting nodes, or undoing a delete.

You can use this code to change the default values of knobs, to add user knobs, and to perform other actions to preset nodes as users see them.

nuke.addOnUserCreate is run immediately after the knobs are set to their default values (including knobDefault values). It is run before onCreate. If nuke.addOnUserCreate() is not used, nuke.tcl(“OnCreate”) is called for backward compatibility.

onCreate

node.knob('onCreate')
nuke.addOnCreate(function)
nuke.removeOnCreate(function)

This is executed when any node is created, for example, when loading a script, pasting a node, selecting a menu item, or undoing a delete. Note that if an onCreate function is defined before a script is loaded, it is run for each node. This is because the nodes are created while the script is loaded. When a group (including the root) is created with a number of inner nodes, onCreate is called first on the inner nodes, then on the group itself. For root, this is run when any script is loaded (see onScriptLoad below) or when File > New is selected. For the preferences and Python knob panels, this is run when they are created.

For example, the following code makes the creation of every node print OnCreate called for [node name] in the terminal:

nuke.addOnCreate(lambda: nuke.tprint("OnCreate called for "+nuke.thisNode().name()))
onScriptLoad
root.knob('onScriptLoad'), nuke.addOnScriptLoad(function), nuke.removeOnScriptLoad(function)

onScriptLoad

root.knob('onScriptLoad')
nuke.addOnScriptLoad(function)
nuke.removeOnScriptLoad(function)

This is executed when any script is loaded, immediately after the onCreate for the root. Note that it is NOT run for new scripts.

The onScriptLoad knob is visible in the Project Settings > Python tab.

images/pythonCallbacks.png

onScriptSave

root.knob('onScriptSave')
nuke.addOnScriptSave(function)
nuke.removeOnScriptSave(function)

These are run when the user tries to save a script. If this changes the script name, that is the name the script is saved under. The onScriptSave knob is visible in the Project Settings > Python tab.

images/pythonCallbacks.png

onScriptClose

root.knob('onScriptClose')
nuke.addOnScriptClose(function)
nuke.removeOnScriptClose(function)

These are run when the user exits NUKE or closes a script, and by the scriptClear() function. They are run immediately before the onDestroy for the root.

The onScriptClose knob is visible in the Project Settings > Python tab.

images/pythonCallbacks.png

onDestroy

node.knob('onDestroy')
nuke.addOnDestroy(function)
nuke.removeOnDestroy(function)

These are executed when any node is deleted, including when the creation of a node is undone. They are called on the root when a script is closed, or when the user exits NUKE. They are NOT run for the Preferences or Python knob panels, or if NUKE crashes.

For groups and the root, this is run before all the children.

knobChanged

node.knob('knobChanged')
nuke.addKnobChanged(function)
nuke.removeKnobChanged(function)

These are executed when the user changes the value of any knob when the control panel is open. They are not called recursively. You can find the knob that was changed by using nuke.thisKnob().

knobChanged allows you to make gizmos with knobs that have effects like enabling, disabling, or presetting other knobs. To access the knob that was changed, use nuke.thisKnob().

Note

The purpose of knobChanged is to update the control panel appearance. You cannot use it ‘track’ knobs (for example, to keep a database up-to-date with the values of all the knobs), as it is not called when the Properties panel is closed. To track knobs, use updateUI as described below.

When the user opens or closes the properties panel, or when they change the input connections while the panel is open, you can call knobChanged with the dummy knobs named showPanel and inputChange. If you are disabling, or otherwise altering values of knobs on changes, you probably need to respond to showPanel to get the panel into the correct state for the current settings when it is opened. For example, you could do:

# if slider is zero, disable the checkmark:
n = nuke.thisNode()
k = nuke.thisKnob()
if k.name()=="slider" or k.name()=="showPanel":
  n['checkmark'].setEnable(n['slider'].getValue()!=0)

You can use nuke.addKnobChanged to gang the gain and gamma sliders of the selected ColorCorrect node so that, if the user adjusts one slider, the other is automatically set to the same value. To gang the sliders, use the following code:

def gangGammaGainSliders():
 n = nuke.thisNode()
 k = nuke.thisKnob()
 if k.name() == "gamma":
   n['gain'].setValue(k.value())
 elif k.name() == "gain":
   n['gamma'].setValue(k.value())
nuke.addKnobChanged(gangGammaGainSliders, nodeClass="ColorCorrect")

updateUI

node.knob('updateUI')
nuke.addUpdateUI(function)
nuke.removeUpdateUI(function)

These are run on every node after any changes to the script. This is done as a low-priority process during idle time and thus NUKE may have already started calculating the image for the Viewer before updateUI is called. Therefore, updateUI should not make the kind of changes to the script that change the image (otherwise, the Viewer would have to restart).

updateUI runs just before autolabel and after knobChanged. To avoid running this on every node, NUKE examines what updateUI looks at (such as the frame or view number) to decide whether or not to run it. If the function does not refer to the frame or view number, NUKE only calls it when the knob values on the node are changed, and not if the frame or view changes.

autolabel

node.knob('autolabel')
nuke.addAutolabel(function)
nuke.removeAutolabel(function)

These are run immediately after updateUI and return the string to draw the node in the Node Graph. If the autolabel knob is not blank and returns anything other than None, the return value is displayed on the node. Otherwise, the nuke.addAutolabel() functions are called in reverse order and the first one to return anything other than None is used. If all of them return None, then nuke.thisNode().name() is used.

For backward compatibility, NUKE does addAutolabel(__main__.autolabel) on start-up.

beforeRender

write.knob('beforeRender')
nuke.addBeforeRender(function)
nuke.removeBeforeRender(function)

These are run prior to starting rendering in execute(). If they throw an exception, the render aborts. They can be used to create the destination directory or communicate with your render farm, for example. The beforeRender knob (before render on the GUI) is visible on the Write node’s Render tab.

Tip

By default, NUKE doesn’t create directories when rendering files. If you have a Write node whose file control points to a directory that doesn’t exist, the render fails. However, you can change this behavior by using the function nuke.addBeforeRender() to register a function that creates the necessary directory prior to rendering the first frame. Here’s an example of how to do so:

  1. Create a file called init.py in your plug-in path directory (if one doesn’t already exist).

  2. Open the init.py file in a text editor and add an entry in the following format:

    def createWriteDir():
      import nuke, os, errno
      file = nuke.filename(nuke.thisNode())
      dir = os.path.dirname( file )
      osdir = nuke.callbacks.filenameFilter( dir )
      # cope with the directory existing already by ignoring that exception
      try:
        os.makedirs( osdir )
      except OSError, e:
        if e.errno != errno.EEXIST:
          raise
    nuke.addBeforeRender(createWriteDir)
images/pythonCallbacks1.png

beforeFrameRender

write.knob('beforeFrameRender')
nuke.addBeforeFrameRender(function)
nuke.removeBeforeFrameRender(function)

These are run prior to starting rendering of each individual frame. If they throw an exception, the render aborts. They can be used to create the destination directory or communicate with your render farm, for example. The beforeFrameRender knob (before each frame on the GUI) is visible on the Write node’s Render tab.

images/pythonCallbacks1.png

afterFrameRender

write.knob('afterFrameRender')
nuke.addAfterFrameRender(function)
nuke.removeAfterFrameRender(function)

These are run after each frame is finished rendering. They are not called if the render aborts. If they throw an exception, the render aborts. They can be used to copy each frame to a digital video recorder (DVR), for example. The afterFrameRender knob (after each frame on the GUI) is visible on the Write node’s Render tab.

images/pythonCallbacks1.png

afterRender

write.knob('afterRender')
nuke.addAfterRender(function)
nuke.removeAfterRender(function)

These are run after rendering of all frames is finished. If they throw an error, the render aborts. They are useful for finishing up a copy to a digital video recorder (DVR) or for communicating with a render farm, for example. The afterRender knob (after render on the GUI) is visible on the Write node’s Render tab.

images/pythonCallbacks1.png

afterBackgroundRender

nuke.addAfterBackgroundRender(function)
nuke.removeAfterBackgroundRender(function)

Add code to execute after any background renders. The call must be in the form of:

def foo(context):
  pass
The context object that is passed in is a dictionary containing the following elements:
  • id - The identifier for the task that’s ended.

Please be aware that the current NUKE context does not make sense in the callback, for example nuke.thisNode returns a random node.

afterBackgroundFrameRender

nuke.addBackgroundFrameRender(function)
nuke.removeAfterBackgroundFrameRender(function)

Add code to execute after each frame of a background render. The call must be in the form of:

def foo(context):
  pass
The context object that is passed in is a dictionary containing the following elements:
  • id - The identifier for the task that’s making progress.
  • frame - The current frame number being rendered.
  • numFrames - The total number of frames to be rendered
  • frameProgress - The number of frames rendered so far.

Please be aware that the current NUKE context does not make sense in the callback, for example nuke.thisNode returns a random node.

filenameFilter

nuke.addFilenameFilter(function)
nuke.removeFilenameFilter(function)

This adds a filter function that processes any file names that are in the NUKE script before passing the script to the system. These filters can be used to remove differences between operating systems or to insert required portions of path names. This callback must be a single string argument, and can either return a new string or None (which is the same as returning the string unchanged). For filenames that are not for a specific node, such as plug-in names, this function is called with nuke.thisNode() set to the root node. All the functions passed to nuke.addFilenameFilter are called in reverse order (the one added last is called first).

For backward compatibility, if no functions have been added, NUKE runs main.filenameFix(s). The default version of this function in turn calls nuke.tcl(‘filename_fix’,s).

The example below uses the filename filter to map Windows paths to Linux paths on a shared Windows and Linux mixed environment. For instance, the workstations are on Windows and access a shared drive mounted as ‘y:’. On Linux this corresponds to the mount point ‘/mnt/y/’:

import nuke

def myFilenameFilter(filename)
    if nuke.env['LINUX']:
        filename.replace( 'y:', '/mnt/y' )
    return filename

nuke.addFilenameFilter(myFilenameFilter)

validateFilename

nuke.addValidateFilename(function)
nuke.removeValidateFilename(function)

Add a function to validate a filename in Write nodes. The first argument is the filename and should return a Boolean as to whether the filename is valid or not. If a callback is provided, it controls whether the Render button of Write nodes, and the Execute button of WriteGeo nodes, is enabled or not.

autoSaveFilter

nuke.addAutoSaveFilter(function)
nuke.removeAutoSaveFilter(function)

Add a function to modify the autosave filename before NUKE saves the current script on an autosave timeout.

The filter function should be in the form:

def myAutoSaveFilter(filename):
  return filename

The first argument to the filter is the current autosave filename. The filter returns the filename to save the autosave to, or None if no autosave is required.

See Using Autosave Callbacks to Implement a Rolling Autosave for an example of using the autosave filters.

autoSaveRestoreFilter

nuke.addAutoSaveRestoreFilter(function)
nuke.removeAutoSaveRestoreFilter(function)

Add a function to modify the autosave restore filename before NUKE attempts to restores the autosave file.

The filter function should be in the form:

def myAutoSaveRestoreFilter(filename):
  return filename

The first argument to the filter is the current autosave filename. This function returns the filename to load autosave from or None if the autosave file is not required. If the autosave restore filter returns None this also suppresses the NUKE dialog that asks the user if they want to restore a found autosave.

See Using Autosave Callbacks to Implement a Rolling Autosave for an example of using the autosave filters.

autoSaveDeleteFilter

Add a function to modify the autosave filename before NUKE attempts delete the autosave file.

The filter function should be in the form:

def myAutoSaveDeleteFilter(filename):
  return filename

The first argument to the filter is the current autosave filename. This function returns the filename to delete or returns None if no file requires deletion.

See Using Autosave Callbacks to Implement a Rolling Autosave for an example of using the autosave filters.

Using Autosave Callbacks to Implement a Rolling Autosave

The example below uses the three autosave callbacks to implement a ‘rolling autosave’. Every time the autosave is invoked, a new autosave is created numbered from 1-9 (wrapping around to 0). For example: autosave, autosave1, autosave2 ... autosave9.

import nuke
import glob
import time
import os

### Example that implements a rolling autosave using the autoSaveFilter callbacks
###
## autosaves roll from 0-9 eg myfile.autosave, myfile.autosave1, myfile.autosave2...
#
## To use just add 'import nukescripts.autosave' in your init.py


def onAutoSave(filename):

  ## ignore untiled autosave
  if nuke.root().name() == 'Root':
    return filename

  fileNo = 0
  files = getAutoSaveFiles(filename)

  if len(files) > 0 :
    lastFile = files[-1]
    # get the last file number

    if len(lastFile) > 0:
      try:
        fileNo = int(lastFile[-1:])
      except:
        pass

      fileNo = fileNo + 1

  if ( fileNo > 9 ):
    fileNo = 0

  if ( fileNo != 0 ):
    filename = filename + str(fileNo)

  return filename


def onAutoSaveRestore(filename):

  files = getAutoSaveFiles(filename)

  if len(files) > 0:
    filename = files[-1]

  return filename

def onAutoSaveDelete(filename):

  ## only delete untiled autosave
  if nuke.root().name() == 'Root':
    return filename

  # return None here to not delete auto save file
  return None

  
def getAutoSaveFiles(filename):
  date_file_list = []
  files = glob.glob(filename + '[1-9]')
  files.extend( glob.glob(filename) )

  for file in files:
      # retrieves the stats for the current file as a tuple
      # (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime)
      # the tuple element mtime at index 8 is the last-modified-date
      stats = os.stat(file)
      # create tuple (year yyyy, month(1-12), day(1-31), hour(0-23), minute(0-59), second(0-59),
      # weekday(0-6, 0 is monday), Julian day(1-366), daylight flag(-1,0 or 1)) from seconds since epoch
      # note:  this tuple can be sorted properly by date and time
      lastmod_date = time.localtime(stats[8])
      #print image_file, lastmod_date   # test
      # create list of tuples ready for sorting by date
      date_file_tuple = lastmod_date, file
      date_file_list.append(date_file_tuple)
   
  date_file_list.sort()
  return [ filename for _, filename in date_file_list ]


nuke.addAutoSaveFilter( onAutoSave )
nuke.addAutoSaveRestoreFilter( onAutoSaveRestore )
nuke.addAutoSaveDeleteFilter( onAutoSaveDelete )

### As an example to remove the callbacks use this code
#nuke.removeAutoSaveFilter( onAutoSave )
#nuke.removeAutoSaveRestoreFilter( onAutoSaveRestore )
#nuke.removeAutoSaveDeleteFilter( onAutoSaveDelete )

Using Callbacks on Root to Add Stereo Setup

To add a script that sets up new projects as a stereo/multi view project you can do something like this:

# if necessary import the module that holds the script you want to run on startup:
import examples
# prepping the argument for this particular script
views = [('L', (0,.5,0)), ('R',(.5,0,0)), ('M',(.5,.5,0))]
# add script to the callback
nuke.addOnUserCreate( examples.setUpMultiView, views, nodeClass='Root' )

For details on the setupMultiView script, check Setting Up Stereo

The onUserCreate callback is triggered every time a node is created by the user the first time (whereas the onCreate callback is created every time a node is created, even when it’s copied or loaded from disk). Since the root is a node, we can use the nodeClass filter to make sure this script is only run when the root node is created.

With the above entry in your menu.py or init.py, new nuke scripts now have three views called L (green), R (red), and M (yellow):

_images/setUpMultiView_02.png