【NUKE教程】Nuke Python 回调函数

14 九月, 2016
16
0

nukepythoncall
使用下文描述的nuke.add…()函数,当有变量事件(比如,创建节点,加载脚本)时就自动调用python函数。

在init.py,menu.py里面可以随便用nuke.add…(). 这代码会被当作nuke环境的一部分,不跟着脚本变。

所有nuke.add…()函数的参数都一样, 例如:

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

其中:

  • callable 是python调用,比如函数名之类
  • args 是参数列表,有圆括号包括着。比如:nuke.addOnCreate( nuke.tprint, ( ‘what to print’ ))
  • kwargs是用k-v形式给的参数,比如:nuke.addOnCreate( nuke.tprint, (‘a’, ‘b’), {‘sep’:’,’ } )
  • nodeClass 仅在nuke.thisNode().nodeClass() 等于这个字符串时才调用,比如:nuke.addOnCreate( nuke.tprint, (‘Creating Write’), nodeClass=’Write’)

默认的*意味着可以被任何节点调用。

在callback中,有个context节点,使用nuke.thisNode()时需要检验。knobChanged,有一个可以利用nuke.thisKonb()获取的内容knob。

对于众多的nuke.add…() 函数,比如 onCreate,也有同名的knob,其分成两类:

  • 可见knob– 就是制作人员想编辑的knob。可以在属性panel的python面板看到。可见konb包括: onScriptLoad, onScriptSave, onScriptClose, beforeRender, beforeFrameRender, afterRender, afterFrameRender.
  • 隐藏knob 在属性面板看不到他们,但可以用python来设置。允许定义gizmo的行为。制作人员不应该设置这些knob,因为用户设置会覆盖gizmo设置。隐藏knob包括: onCreate, onDestroy, knobChanged, updateUI, autolabel.

如果很多注册的回调,附加到knob上的代码总是优先于nuke.add…()( 例如,放入到onCreate knob的代码,要比nuke.addOnCreate()先调用)大多数情况下,nuke.add…()函数都会按顺序跟着一个对应的nodeClass。最后,带* 的,也是添加的顺序。注意addAutolabel()和addFilenameFilter()函数是例外,他们按照添加时候的逆序调用。

所有的回调函数都有对应的移除函数

OnUserCreate

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

当用户在GUI模式下创建节点时就执行。启动时在root和viewer节点上也会调用。但是加载现有脚本,粘贴节点,undo delete时,不会执行。

可以用这个代码改变knob的默认值,添加user knob,执行其他行为。
nuke.addOnUserCreate 在默认值改变后立即运行。要在onCreate前面。如果没有使用nuke.addOnUserCreate(),那就会调用nuke.tcl(“OnCreate” )后向兼容。

onCreate

node.knob("onCreate" )
nuke.addOnCreate(function)
nuke.removeCreate(function)

当node创建时就执行,例如,当加载脚本,粘贴节点,选择菜单选项,撤销删除等。注意,如果onCreate在脚本加载前就定义了,会为每个节点执行。这是因为节点
创建出来后,脚本也已经加载了。 创建group时(包括root节点,包含很多内部节点),内部节点先调用onCreate,group节点后调用onCreate。root节点任何脚本
加载时都会执行(查看onScriptLoad) 或 菜单中File》New被选择时也会执行。 预设和python knob panel创建时也会执行。

例如,下面的代码让每个节点创建时在终端输出OnCreate。

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 )

当脚本加载时就执行,在root的onCreate创建后立即执行。注意,其不为新脚本执行。
在Project Setting》Python 标签可以看到 onScriptLoad的knob

Nuke Python 回调函数

onScriptSave

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

当用户保存脚本时运行。
Nuke Python 回调函数

onScriptClose

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

当用户退出nuke或者关闭脚本,scriptClear()函数
Nuke Python 回调函数

onDestroy

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

节点删除时会执行此函数,包括撤销节点的创建。root节点关闭后也会调用,用户退出nuke,nuke崩溃后不会为Preferences。
对于group和root 此函数比所有孩子都要先运行。

knobChanged

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

panel打开时,任何对knob数值的修改都能执行回调,不会递归调用。使用nuke.thisKnob()来查看哪个knob被改变了。
knobChanged可以制作带有knob的gizmos,knob有enabling,disabling,presettin等。nuke.thisKnob()能获取变动的那个knob。

注意:knobChanged是用来刷新panel控制面板。不能用来跟踪knobs(例如,用数据库的数据来更新所有的knobs)。因为当Properties面板关闭时,就不调用了。想跟踪knobs,用下面的updateUI

用户打开、关闭properties面板时,panel打开状态下,变动了input的连接,就可以使用knobChanged中的showPanelinputChange 这两个knobs。否则on change回调更改了knob的值,就要响应showPanel来设置正确的状态了。例如:

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

nuke.addKnobChanged 可以一次获取ColorCorrect节点的gain和gamma 滑动条,用户更改了其中一个滑动条,另一个会自动变为同样的值。获取滑动条的代码:

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 )

脚本变动后,每个节点都运行此回调。这个是作为空闲时间执行的低优先级进程来实现的。很可能Nuke都开始给view渲染计算了,updateUI还没执行呢。因此updateUI不能执行更改图像的脚本(否则,viewer就要重启了)。

updateUIautolabel之前knobChanged之后运行。想不让它在每个节点运行,Nuke检查updateUI看上去像啥(比如,帧,view数量)来决定是否运行它。 如果函数推测不出来帧和view数量,Nuke仅在这个node上knob变动时调用一次,frame或view变动不会调用。

autolabel

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

updateUI后立马运行上面脚本,并返回绘制节点的绘制字符串。如果autolabel不是空的,或返回任何非空,返回值就显示在节点上。否则,nuke.addAutolabel()会被逆序调用,使用第一个返回非None的。如果都返回None,那就用nuke.thisNode().name().

为了后向兼容,Nuke调用启动时调用addAutolabel(main.autolabel)

beforeRender

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

这个在execute()开始渲染之前就运行了。抛出异常,就不进行后续渲染了。可以用这个特性来创建目标输出文件夹或者和农场通信,例如,beforeRender knob(GUI上是before render)就在write节点的Render页面上。

提示:默认情况下,NUKE渲染时不创建文件夹。如果Write节点的输出路径所在文件夹不存在,渲染会失败。但是,能通过nuke.addBeforeRender()注册回调函数渲染之前创建文件夹来改变nuke的默认行为。 下面是一个例子:

  1. 插件目录中创建 init.py 如果不存在
  2. 文件中添加如下代码:
def createWriteDir():
    import nuke, os, errno
    file = nuke.filename( nuke.thisNode() )
    dir = os.path.dirname( file )
    osdir = nuke.callbacks.filenameFilter( dir )
    try:
    os.makedirs( osdir )
    except OSError, e:
    if e.errno != errno.EEXIST:
    raise
    nuke.addBeforeRender( createWriteDir )

Nuke Python 回调函数

beforeFrameRender

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

每帧开始渲染前执行。如果抛出了错误,渲染就终止。 这个对渲染完拷贝到数字视频录像机(DVR)或跟渲染农场通信很有用。例如,beforeFrameRender knob (GUI上的 after render)就在输出节点的Render页面下。
Nuke Python 回调函数

afterFrameRender

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

每帧渲染完成后执行。如果抛出了错误,渲染就终止。 这个对渲染完拷贝到数字视频录像机(DVR)或跟渲染农场通信很有用。例如,afterFrameRender knob (GUI上的 after render)就在输出节点的Render页面下。
Nuke Python 回调函数

afterRender

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

所有帧渲染完成后运行。如果抛出了错误,渲染就终止。 这个对渲染完拷贝到数字视频录像机(DVR)或跟渲染农场通信很有用。例如,afterRender knob (GUI上的 after render)就在输出节点的Render页面下。

Nuke Python 回调函数

afterBackgroundRender

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

后台渲染后执行,调用形式必须这样:

def foo( context ):
    pass

context是一个包含下面元素的字典:

  • id – 完成task的标识

请注意当前NUKE上下文对回调无效,例如,nuke.thisNode会随机返回一个节点。

afterBackgroundFrameRender

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

调用形式如下:

def foo( context ):
    pass

context是一个包含下面元素的字典:

  • id – 完成task的标识
  • frame – 当前渲染的帧号
  • numFrames – 要渲染的总帧数
  • framgeProgress – 已经渲染了多少帧

请注意当前NUKE上下文对回调无效,例如,nuke.thisNode会随机返回一个节点。

filenameFilter

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

在脚本传输给系统前,给过程添加一个过滤函数来处理文件名。
为了后向兼容,如果没有添加函数,nuke运行mian.filenameFix(s). 默认版本调用nuke.tcl(‘filename_fix’,s)
使用这个来进行路径转换,从windows到linux的转换:

添加了一个filter在Nuke脚本传输到操作系统前处理所有的文件名。这些filter能消除操作系统间的差异,也能插入需要的路径。回调函数的参数必须是一个字符串,返回值可以是字符串也可以是None(这个返回没改动的字符串是一样的)。文件名并不是一个特殊的节点,比如插件名,此函数调用nuke.thisNode()设置root节点。所有传给nuke.addFilenameFilter的函数逆序调用。

为了后向兼容,如果没有添加函数,nuke运行main.filenameFix(s) 。默认版本调用nuke.tcl(‘filename_fix’,s)

下面的例子使用filename filter来处理win和linux混合环境下的路径问题。例如,工作站是win系统,访问共享的驱动器‘Y:’,在linux对应的挂载点是‘/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 )

验证write node中的文件名。第一个参数是文件名,返回布尔值,说明文件名是否有效。如果提供了回调函数,Write node中的Render按钮,WriteGeo节点中的Excute按钮可用与否就受回调函数结果影响。

autoSaveFilter

nuke.addAutoSaveFilter( function )
nuke.removeAutosaveFilter( function )

在nuke自动保存文件文件前,对autosave进行修改。

def myAutoSaveFilter( filename ):
return filename

第一个参数为当前autosave文件名,返回的是要保存的aotusave文件名,木有就返回None。

更多请看 使用AutoSave回调实现AutoSave回滚

autoSaveRestoreFilter

nuke.addAutoSaveRestoreFilter( function )
nuke.removeAutoSaveRestore( function )

Nuke恢复aotosave文件时修改autosave获取的文件名

函数格式如下:

def myAutoSaveRestoreFilter( filename ):
    return filename

第一个参数为当前autosave 文件名,函数返回要加载的autosave文件名,没有返回None。如果aotosave恢复filter返回None这也压制了Nuke对话框询问用户是否加载一个找到的autosave。

更多请看 使用AutoSave回调实现AutoSave回滚

autoSaveDeleteFilter

在nuke删除自动保存文件时修改autosave 的文件名

filter函数形式如下:

def myAutoSaveDeleteFilter( filename ):
    return filename

第一个参数就是autosave的文件名。返回要删除的文件名,没有可删除的就返回None。

更多请看 使用AutoSave回调实现AutoSave回滚

使用autosave的回调完成autosave回滚操作

使用三个autosave回调来完成回滚。每次调用autosave,就创建一个新的autosave,数组从1-9.例如: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 )

在root上使用回调来添加双目设置

添加一段脚本,将新项目设置成stereo/multi view项目,操作如下:

# 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' )

更多setupMultiView脚本,请看Setting Up Stereo

用户第一次创建某类节点时会触发onUserCreateOnCreate是每个节点创建时都会调用,甚至复制,从磁盘加载也会)。既然root也是个节点,就能用nodeClass过滤下保证仅root节点创建时执行。

上面的脚本加入menu.pyinit.py后,新的nuke脚本有三个view了,L(绿色),R(红色), M(黄色)。

Nuke Python 回调函数

默认色彩空间

Nuke10中可以动态替换Read和Write节点使用的色彩空间。

默认色彩空间是特定文件的Reader和Writer对象在读取和写入文件时告诉Nuke最可能的那个色彩空间。有以下几种途径来获取:

  • 文件metadata或文件头中存了colorspace的名字,Reader没准会用这个名字来设置默认色彩空间。
  • 文件包含特定数据类型,8bit,16bit,浮点数或者log空间,Nuke从项目设置中读取对应格式的色彩空间。
  • Reader、Writer 会通过ID请求内置的LUT ,例如REDlog, AlexaLog, ProTune,Gamma2.2( 查看DDImage/LUT.h 中的DataType)

多亏OpenColorIO(OCIO)在Nuke中的配置,现在很容易支持Nuke搞不定的色彩空间。由于色彩空间匹配不上,就很容易获取Nuke的错误状态,尤其使用自定义OCIO配置时。下面的回调能处理这些自定义配置导致的错误。

和其他回调一样,用下面的方法可以添加和删除回调:

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

函数形式如下:

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

传递的对象如下:

  • name – Reader、Writer当前请求的色彩空间,可能是Nuke默认的,或其他存在,不存在的,没准不是色彩空间。
  • dataTypeHint – 如上,为下面三种情况之一
    • nuke.INVALID – 仅设置了色彩空间的名字
    • 链接到项目设置里面的一个值, 如下– 色彩空间会根据项目设置来定。注意,回调是链式调用,和项目设置中的色彩空间匹配这个不可靠,因为前面执行的回调可能改了它。
    • 内置LUT的ID – 色彩空间名字为下表中的第一个。

项目设置中data type hint的值:

  • nuke.INT8 色彩空间会设置成项目设置中‘8-bit files’这个值
  • nuke.INT16 色彩空间会设置成项目设置中‘16-bit files’这个值
  • nuke.MONITOR 色彩空间会设置成项目设置中‘monitor’这个值, 用于显示,比如,viewers和 邮票
  • nuke.FLOAT 色彩空间会设置成项目设置中‘float files’这个值
  • nuke.LOG 色彩空间会设置成项目设置中‘log files’这个值

DDImage/LUT.h中的其他data-type,后向兼容Reader和Writer:

  • 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

例如: 想支持ACES 1.0.1 预览设置,添加如下回调函数来保证R3D文件的修正函数。

import nuke
def r3d_aces101_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
     aces101RedLog = "Input - RED - Curve - REDlogFilm"
     if aces101RedLog in allColorspaces :
       # we have the aces101 specific RED-log space, return that
       return aces101RedLog
   if name == "rec709":
     acesRec709 = "Output - Rec.709"
     if acesRec709 in allColorspaces:
       return acesRec709
   elif name == "REDSpace":
     acesRedSpace = "Input - RED - REDlogFilm - REDcolor4"
     if acesRedSpace in allColorspaces:
       return acesRedSpace
 # add the mapper function to NUKE
 nuke.addDefaultColorspaceMapper( r3d_aces101_default_colorspace )