.. _callbacks-ref-label:

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.

.. image:: 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.

.. image:: 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.

.. image:: 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-ref-label:

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:

#. Create a file called **init.py** in your plug-in path directory (if one doesn't already exist).
#. 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)

.. image:: 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.

.. image:: 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.

.. image:: 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.

.. image:: 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 between Windows paths and 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 = filename.replace( 'y:', '/mnt/y' )
            filename = filename.replace( 'x:', '/mnt/x' )
        else:
            filename = filename.replace( '/mnt/y', 'y:' )
            filename = filename.replace( '/mnt/x', 'x:' )
            
        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 :ref:`callbacksautosave-ref-label` 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 :ref:`callbacksautosave-ref-label` 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 :ref:`callbacksautosave-ref-label` for an example of using the autosave filters.


.. _callbacksautosave-ref-label:

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.

.. literalinclude::  examples/rollingAutoSave.py

.. _stereocallback-ref-label:

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 :ref:`stereosetup-ref-label` 

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):

.. image:: images/setUpMultiView_02.png


Default Colorspaces 
^^^^^^^^^^^^^^^^^^^
As of NUKE 10 you can dynamically replace the default colorspaces used on Read and Write Nodes in the DAG.

The default colorspace is the one the file-specific Reader and Writer objects tell NUKE is the best match for the file which is being read or written and it can be selected in one of several ways:

* the file may store the colorspace by name in metadata or the fileheader, Reader may use this to set the colorspace by name
* the file may contain data of a specific type, 8-bit, 16-bit, floating point or log space data and ask NUKE to use the corresponding colorspace for that type from the Project Settings
* the Reader/Writer, having deep knowledge of NUKE, may request a built-in LUT type by ID, for example REDLog, AlexaLog, ProTune or Gamma2.2 (see the DataType enum in DDImage/LUT.h for a full list)

Thanks to the support of OpenColorIO (OCIO) configs in NUKE it is now easier to introduce a colorspace that NUKE's shipped Readers and Writers cannot map to, and therefore easier to get NUKE in an error state due to un-matched colorspaces, especially with custom OCIO configs. These callbacks will help you avoids such error with custom made configurations.

In the same way as all the other callbacks you can add and remove callbacks using the following API::

    nuke.addDefaultColorspaceMapper(function)
    nuke.removeDefaultColorspaceMapper(function)

The function must be of the form::

    def myDefaultColorspaceMapper(colorspaceName, dataTypeHint) :
      # ... map colorspace name to desired colorspace ...
      return colorspaceName

The objects passed in are:
    * **name** - The current colorspace name that has been requested by the Reader/Writer, which may be set to one of NUKE's defaults or any other existing or not colorspace whether it exists or not.
    * **dataTypeHint** - As above, this will be a value which represents one of 3 aspects

      * nuke.INVALID - The colorspace has been set by name and by name only
      * a value linked to project setting, see below - The colorspaceName will be initially set to the corresponding project setting value, note that because these callbacks are chained, you cannot rely on the colorspace matching the corresponding project-setting colorspace as previously executed callbacks may have changed it
      * an ID of a built-in NUKE LUT, see below - The colorspaceName will be the first value in the list of colorspaces

Project setting linked data-type hint values:

    * **nuke.INT8** - colospace name will be set to the colorspace name in the '8-bit files' knob in project settings
    * **nuke.INT16** - colospace name will be set to the colorspace name  in the '16-bit files' knob in project settings
    * **nuke.MONITOR** - colospace name will be set to the colorspace name in the 'monitor' knob in project settings. Used for displays, for examples viewers and postage stamps
    * **nuke.FLOAT** - colospace name will be set to the colorspace name in the 'float files' knob in project settings
    * **nuke.LOG** - colospace name will be set to the colorspace name in the 'log files' knob in project settings

Other data-type values which map to builtin LUTs are those in DDImage/LUT.h, back-compatibility with Reader and Writers:
    * **nuke.VIEWER** - unused
    * **6** - equivalent to DD::Image::LUT::GAMMA1_8
    * **7** - equivalent to DD::Image::LUT::GAMMA2_2
    * **8** - equivalent to DD::Image::LUT::GAMMA2_4
    * **9** - equivalent to DD::Image::LUT::PANALOG
    * **10** - equivalent to DD::Image::LUT::REDLOG
    * **11** - equivalent to DD::Image::LUT::VIPERLOG
    * **12** - equivalent to DD::Image::LUT::ALEXAV3LOGC
    * **13** - equivalent to DD::Image::LUT::PLOGLIN
    * **14** - equivalent to DD::Image::LUT::SLOG
    * **15** - equivalent to DD::Image::LUT::SLOG1
    * **16** - equivalent to DD::Image::LUT::SLOG2
    * **17** - equivalent to DD::Image::LUT::SLOG3
    * **18** - equivalent to DD::Image::LUT::CLOG
    * **19** - equivalent to DD::Image::LUT::PROTUNE
    * **20** - equivalent to DD::Image::LUT::GAMMA2_6
    * **21** - equivalent to DD::Image::LUT::LOG3G10
    * **22** - equivalent to DD::Image::LUT::LOG3G12
    * **23** - equivalent to DD::Image::LUT::BT1886
    * **24** - equivalent to DD::Image::LUT::HDR2084
    * **25** - equivalent to DD::Image::LUT::HYBRIDLOGGAMMA

For example, if you wanted to support the ACES 1.0.3 preview config, you could add the following custom callback to ensure the correction function for R3D files::

    import nuke

    def r3d_aces103_default_colorspace(name, dataTypeHint) :
      """ finds appropriate R3D colorspaces in the aces 101 config """

      usePrefs = (dataTypeHint >= 0) and (dataTypeHint <= nuke.FLOAT) #  5 = LUT::FLOAT
      if usePrefs:
        return name

      assert "/" not in name, "family name unexpectedly found in colorspace name"
      allColorspaces = nuke.colorspaces.getColorspaceList( nuke.thisNode().knob('colorspace') )[1:]
      exactMatch = name in allColorspaces
      if exactMatch:
        # if there's an exact name-match in the config use that
        return name

      nukeRedLog = (dataTypeHint == 10) # LUT::REDLOG
      if nukeRedLog:
        # first use the Nuke-side data-type hint
        aces103RedLog = "Input - RED - Curve - REDlogFilm"
        if aces103RedLog in allColorspaces :
          # we have the aces103 specific RED-log space, return that
          return aces103RedLog

      if name == "rec709" or name == "Gamma2_2":
        acesRec709 = "Output - Rec.709"
        if acesRec709 in allColorspaces:
          return acesRec709
      elif name == "REDSpace":
        acesRedSpace = "Input - RED - REDlogFilm - REDcolor4"
        if acesRedSpace in allColorspaces:
          return acesRedSpace
      elif name == "Log3G10":
        acesRedLog3G10 = "Input - RED - Curve - REDLog3G10"
        if acesRedLog3G10 in allColorspaces:
          return acesRedLog3G10
      elif name == "BT1886":
        aces1886 = "Utility - Curve - Rec.1886"
        if aces1886 in allColorspaces:
          return aces1886


    # add the mapper function to NUKE
    nuke.addDefaultColorspaceMapper( r3d_aces103_default_colorspace )

