美文网首页CgTd
Nuke Python roto

Nuke Python roto

作者: N景波 | 来源:发表于2016-11-22 11:44 被阅读0次

    怎么创建roto 图形和 画笔
    当获取或者设置roto,rotopaint节点时,需要读取节点的curves knob

    rpNode = nuke.toNode('RotoPaint1')
    cKnob = rpNode['curves']
    
    _images/rotoPaintPrimer_01.png_images/rotoPaintPrimer_01.png

    读取root层:

    root = cKnob.rootLayer
    

    每层中curves 的knob是一个迭代对象,其产生每层的成员:

    for shape in root:
        print shape.name
    

    使用曲线的knob的toElement方法,通过名字读取层。

    for shape in cKnob.toElement('Layer1'):
        print shape.name
    
    # Result:
    Layer1
    Brush2
    Bezier1
    

    用同样的方法来读取图形中的控制点和画笔:

    for p in cKnob.toElement('Layer1/Brush1'):
        print p
    
    # Result:
    Brush1
    Bezier2
    

    curve knob有三种类型的对象:

    • shapes 描述Beziers 和B样条
    • strokes 描述画笔
    • layers 描述不同的层
      想创建新的画笔和层,需要导入RotoPaint的api,使用别名来简化使用
    import nuke.rotopaint as rp
    

    类 Shape,Stroke,Layer,ShapeControlPoint,AnimControlPoints,还有更多需要使用python来创建RotoPaint元素的。

    例子:
    paintTrajectory

    这段代码 沿着Array_knob的动画轨迹来绘制画笔,将其可视化。


    _images/paintTrajectory_01.png_images/paintTrajectory_01.png

    准备阶段,Transform节点的translate knob设置一些关键帧,它就可以沿着屏幕运动。在脚本编辑器中,给knob赋值,并赋值一个帧范
    围(比如1-100):

    knob = nuke.toNode('Transform1')['translate']
    frameRange = nuke.FrameRange('1-100')
    

    使用的knob至少有两个域,可以提供x,y。因此快速检测下:

    if knob.arraySize() != 2:
        raise TypeError, 'knob must have array size of 2'
    

    如果knob有效,就抓取其父节点,创建RotoPaint节点,引用其curves knob:

    parentNode = knob.node()
    paintNode = nuke.createNode('RotoPaint')
    curvesKnob = paintNode['curves']
    

    需要使用到nuke.rotopaint模块:

    import nuke.rotopaint as rp
    

    使用Stroke类创建一个画笔:

    stroke = rp.Stroke(curvesKnob)
    

    下一步,遍历所有帧,抓取knob的值:

    for f in frameRange:
        pos = knob.valueAt(f)
    

    如果knob的父节点有center knob,或许要偏移下这个值,保证stroke落在轨迹上,获取这个偏移量吧:

    try:
        offset = parentNode['center'].valueAt(f)
    except NameError:
        offset = (0, 0)
    

    给新控制点计算stroke的x,y

    finalPos = [sum(p) for p in zip(pos, offset) ]
    

    用RotoPaint模块的AnimControlPoint创建新的控制点,并计算x,y位置。然后新控制点添加给stroke:

    stroke.append( rp.AnimControlPoint(*finalPos))
    

    给stroke个新名字,可以在curves的knob里面显示,最后,将其添加到root层:

    stroke.name = 'trajectory for %s.%s' %(parentNode.name(), knob.name() )
    curvesKnob.rootLayer.append(stroke)
    

    目前所有的代码:

    import nuke.rotopaint as rp
    
    knob = nuke.toNode('Transform1')['translate']
    frameRange = nuke.FrameRange('1-100')
    
    if knob.arraySize() != 2:
        raise TypeError, 'knob must have array size of 2'
    
    parentNode = knob.node()
    paintNode = nuke.createNode('RotoPaint')
    curvesKnob = paintNode['curves']
    
    stroke = rp.Stroke(curvesKnob)
    
    for f in frameRange:
        pos = knob.valueAt(f)
        try :
            # IF PARENT NODE HAS "CENTER" KNOB ADD THE OFFSET TO LINE UP STROKE PROPERLY
            offset = parentNode['center'].valueAt(f)
        except NameError:
            # OTHERWISE NO OFFSET IS APPLIED
            offset =(0, 0)
        finalPos = [ sum(p) for p in zip(pos, offset) ]
        stroke.append(rp.AnimControlPoint(*finalPos))
    
    
    stroke.name = 'trajectory for %s.%s' %(parentNode.name(), knob.name())
    curvesKnob.rootLayer.append(stroke)
    

    调用这段代码的好地方应该是animation menu,这样就可以从对应knob的动画菜单直接调用。将代码包装成函数,并接收knob和
    frame range作为参数:

    def paintTrajectory(knob, frameRange):
        if knob.arraySize() != 2:
            raise TypeError, 'knob must have array size of 2'
    
        parentNode = knob.node()
        paintNode = nuke.createNode('RotoPaint')
        curvesKnob = paintNode['curves']
    
        stroke = rp.Stroke(curvesKnob)
        ctrlPoints = []
        for f in frameRange:
            pos = knob.valueAt(f)
            try :
                # IF PARENT NODE HAS "CENTER" KNOB ADD THE OFFSET TO LINE UP STROKE PROPERLY
                offset = parentNode['center'].valueAt(f)
            except NameError:
                # OTHERWISE NO OFFSET IS APPLIED
                offset =(0, 0)
            finalPos = [ sum(p) for p in zip(pos, offset) ]
            stroke.append(rp.AnimControlPoint(*finalPos))
    
        stroke.name = 'trajectory for %s.%s' %(parentNode.name(), knob.name())
        curvesKnob.rootLayer.append(stroke)
    

    现在创建一个辅助函数,从knob获取帧范围,这样,用户就不用自己输入了。我们这样做的,遍历knob的动画曲线,获取其帧范围。
    首先,初始化FrameRanges对象,保存所有knob的帧范围:

    def getKnobRange(knob):
            allRanges = nuke.FrameRanges()
    

    下一步,遍历curves,创建帧范围。如果没有找到关键帧,那么曲线可能是表达式定义的,那么就利用脚本的范围。一旦有了第一帧和
    最后一帧,创建帧对象,并添加到帧范围里面:

    for anim in knob.animations():
        if not anim.keys():
            first = nuke.root().firstFrame()
            last = nuke.root().lastFrame()
            allRanges.add(nuke.FrameRange(first, last))
        allKeys  = anim.keys()
        allRanges.add(nuke.FrameRange( allKeys[0].x, allKeys[-1].x, 1))
    

    所有范围收集完成后,使用FrameRanges.minFrame(), FrameRanges.maxFrame()获取最小,最大帧。

    return nuke.FrameRange( allRanges.minFrame(), allRanges.maxFrame(), 1)
    

    所有代码如下:

    import nuke
    import nuke.rotopaint as rp
    
    def getKnobRange( knob ):
        '''
        Return a frame range object of the knob's animation range.
        If the knob has no keyframes the script range is returned
        args:
           knob - animated knob
        '''
        allRanges = nuke.FrameRanges()
        for anim in knob.animations():
            if not anim.keys():
                #KNOB ONLY HAS EXPRESSION WITHOUT KEYS SO USE SCRIPT RANGE
                first = nuke.root().firstFrame()
                last = nuke.root().lastFrame()
                allRanges.add( nuke.FrameRange( first, last ) )
            else:
                # GET FIRST FRAME
                allKeys = anim.keys()
                allRanges.add( nuke.FrameRange(  allKeys[0].x, allKeys[-1].x, 1 ) )
    
        return nuke.FrameRange( allRanges.minFrame(), allRanges.maxFrame(), 1 )
    
    
    def paintTrajectory( knob, frameRange ):
        '''
        Create a paint stroke that visualises a knob's animation path
        args:
            knob - Array knob with 2 fields. Presumably this is a XY_Knob but can be any
            frameRange - Range for which to draw the trajectory.
                         This is an iterable object containing the requested frames.
                         Default is current script range
        '''
        if knob.arraySize() != 2:
            raise TypeError, 'knob must have array size of 2'
    
        parentNode = knob.node()
        paintNode = nuke.createNode('RotoPaint')
        curvesKnob = paintNode['curves']
    
        stroke = rp.Stroke( curvesKnob )
        ctrlPoints = []
        for f in frameRange:
            pos = knob.valueAt( f )
            try :
                # IF PARENT NODE HAS "CENTER" KNOB ADD THE OFFSET TO LINE UP STROKE PROPERLY
                offset = parentNode['center'].valueAt( f )
            except NameError:
                # OTHERWISE NO OFFSET IS APPLIED
                offset = ( 0, 0 )
            finalPos = [ sum(p) for p in zip( pos, offset ) ]
            stroke.append( rp.AnimControlPoint( *finalPos ) )
    
        stroke.name = 'trajectory for %s.%s' % ( parentNode.name(), knob.name() )
        curvesKnob.rootLayer.append( stroke )
    

    两个函数准备好了,现在就可运行来绘制动画路径了:

    knob = nuke.toNode('Transform1')['translate']
    paintTrajectory(knob,getKnobRange(knob))
    

    就像上面提到的,使用这段代码的最好地方是在动画菜单里面 nuke.thisKnob()

    import examples
    nuke.menu('Animation').addCommand('Paint Trajectory', lambda: examples.paintTrajectory(nuke.thisKnob(),
    examples.getKnobRange(nuke.thisKnob())))
    
    _images/paintTrajectory_02.png_images/paintTrajectory_02.png
    trackShape_01trackShape_01
    路径控制

    这个基本上是在线版的trackShape,其使用python代码将Transform节点的translate knob链接到给定的图元。那么用户的knob就可以
    沿着路径给transform定位了。

    animPath_01animPath_01
    animPath_02animPath_02

    path控制着Transform沿着RotoPaint节点中的Brush1移动的比例。需要将python代码放入translate的knob来实现:

    animPath_03animPath_03

    x 表达式中代码如下:

    try:
       shape = nuke.toNode('RotoPaint1')['curves'].toElement('Brush1').evaluate(nuke.frame())
    except:
       pass
    ret = shape.getPoint(nuke.thisNode()['path'].value()).x
    

    y中代码如下:

    try:
       shape = nuke.toNode('RotoPaint1')['curves'].toElement('Brush1').evaluate(nuke.frame())
    except:
       pass
    ret = shape.getPoint(nuke.thisNode()['path'].value()).y
    

    曲线是三次曲线,图形请看上图。
    nuke脚本nuke script

    trackCV

    下面代码给图形的控制点创建了个Tracker节点。首先创建Roto节点,在其中画一条贝塞尔曲线,并K动画。

    trackCV_01trackCV_01

    确保viewer中的label points勾选了,这就能看到CV点的标号了,这能帮助识别你想创建Tracker的那个。

    trackCV_02trackCV_02

    确认选中了节点图中的Roto节点,通过抓取选中节点启动脚本。

    node = nuke.selectedNode()
    

    选定你要跟踪的帧范围,图元的名字,点编号。下面是硬编码,后续可以做一个界面:

    fRange = nuke.FrameRange('1-100')
    shapeName = 'Bezier1'
    cv = 0
    

    脚本运行时,想看到tracker的创建,那么就需要在另一个线程里面做这个工作了。这个函数是cvTracker其参数如下:

    • node Roto节点
    • shapeName 包含控制点的图元
    • cvID 要跟踪的控制点序号
    • fRange 跟踪的帧范围

    下面代码启动另一个线程调用此函数:

    threading.Thread(None, _cvTracker, args=(node, shapeName, cv, fRange)).start()
    

    到现在为止,代码如下:

    import nuke.rotopaint as rp
    
    node = nuke.selectedNode()
    fRange = nuke.FrameRange('1-100')
    shapeName = 'Bezier1'
    cv = 0
    
    threading.Thread(None, _cvTracker, args=(node, shapeName, cv, fRange)).start()
    

    显示实现这个函数:

    def _cvTracker(node, shapeName, cvID, fRange):
        shape = node['curves'].toElement(shapeName)
    

    使用toElement方法,能通过名字获取图元并能定位我们索引的点,例子中,点编号为0:

    shapePoint = shape[cvID]
    

    添加点错误处理,防止索引的点不存在:

    try:
        shapePoint = shape[cvID]
    except IndexError:
        nuke.message('Index %s not found in %s.%s' %())
        return
    

    变量shapePoint中保存的ShapeControlPoint保存了所有的属性,main和feature曲线的邻接关系,中心点。我们仅想跟踪
    main 曲线的中心点,获取代码如下:

    animPoint = shapePoint.center
    

    animPoint提供了x,y坐标,创建一个Tracker节点来保存动画:

    tracker = nuke.createNode('Tracker3')
    

    给一个提示标签,让track1的knob接收动画:

    tracker['label'].setValue('tracking cv#%s in %s.%s' %(cvID, node.name(), shape.name))
    trackerKnob = tracker['track1']
    trackerKnob.setAnimated()
    

    在做跟踪前,设置一个进度条,允许用户取消进度:

    task = nuke.ProgressTask('CV Tracker')
    task.setMessage('tracking CV'
    

    现在遍历请求的帧。循环中我们会检测用户是否点击了进度条上的Cancle

    for f in fRange:
        if task.isCancelled():
            nuke.executeInMainThread(nuke.message, args=("CV Track Cancelled"))
            break
    

    下一步设置处理的进度:

    task.setProgress(int(float(f)/fRange.last() * 100))
    

    现在可以做具体的跟踪工作了。获取循环中对应帧的AnimationControlPoint

    pos = animPoint.getPosition(f)
    

    最后,给trackerknob设置新位置。我们在主线程中做这个,脚本运行时能看到关键帧的生成:

    nuke.executeInMainThreadWithResult(trackerKnob.setValueAt, args=(pos.x, f, 0)) # SET X VALUE
    nuke.executeInMainThreadWithResult(trackerKnob.setValueAt, args=(pos.y, f, 1)) # SET Y VALUE
    

    整个函数如下:

    def _cvTracker(node, shapeName, cvID, fRange):
        shape = node['curves'].toElement(shapeName)
    
        # SHAPE CONTROL POINT
        try:
            shapePoint = shape[cvID]
        except IndexError:
            nuke.message('Index %s not found in %s.%s' %())
            return
    
        # ANIM CONTROL POINT
        animPoint = shapePoint.center
    
        # CREATE A TRACKER NODE TO HOLD THE DATA
        tracker = nuke.createNode('Tracker3')
        tracker['label'].setValue('tracking cv#%s in %s.%s' %(cvID, node.name(), shape.name))
        trackerKnob = tracker['track1']
        trackerKnob.setAnimated()
    
        # SET UP PROGRESS BAR
        task = nuke.ProgressTask('CV Tracker')
        task.setMessage('tracking CV')
    
            # DO THE WORK
        for f in fRange:
            if task.isCancelled():
                nuke.executeInMainThread(nuke.message, args=("CV Track Cancelled"))
                break
            task.setProgress(int(float(f)/fRange.last() * 100))
    
                    # GET POSITION
            pos = animPoint.getPosition(f)
            nuke.executeInMainThreadWithResult(trackerKnob.setValueAt, args=(pos.x, f, 0))
            nuke.executeInMainThreadWithResult(trackerKnob.setValueAt, args=(pos.y, f, 1))
    

    注意: 不要在主线程中运行此函数,因为** nuke.executeInMainThreadWithResult**会让Nuke卡死。

    为了让程序交互性更好,做一个python的小面板,提供图元名字,点编号,帧范围。


    shapeAndCVPanel_01shapeAndCVPanel_01

    怎么做小面板请看ShapeAndCVPanel
    记得导入小面板代码,修改其参数,就不用上面硬编码了:

    import examples
    
    node = nuke.selectedNode()
    p = examples.ShapeAndCVPanel(node)
    if p.showModalDialog():
        fRange = nuke.FrameRange(p.fRange.value())
        shapeName = p.shape.value()
        cv = p.cv.value()
        threading.Thread(None, _cvTracker, args=(node, shapeName, cv, fRange)).start()
    

    将代码封装一下,供**Properties **属性菜单上的右键使用。同样会添加错误处理代码保证选取的节点时Roto或者RotoPaint:

    def trackCV():
        node = nuke.selectedNode()
    
        # BAIL OUT IF THE NODE IS NOT WHAT WE NEED
        if node.Class() not in ('Roto', 'RotoPaint'):
            nuke.message('Unsupported node type. Node must be of class Roto or RotoPaint')
            return
    
        p = examples.ShapeAndCVPanel(node)
        if p.showModalDialog():
            fRange = nuke.FrameRange(p.fRange.value())
            shapeName = p.shape.value()
            cv = p.cv.value()
            threading.Thread(None, _cvTracker, args=(node, shapeName, cv, fRange)).start()
    

    最终代码:

    import examples
    import nuke
    import nukescripts
    import threading
    
    def _cvTracker( node, shapeName, cvID, fRange ):
        shape = node['curves'].toElement( shapeName )
    
        # SHAPE CONTROL POINT
        try:
            shapePoint = shape[cvID]
        except IndexError:
            nuke.message( 'Index %s not found in %s.%s' % (  ) )
            return
    
        # ANIM CONTROL POINT
        animPoint = shapePoint.center
    
        # CREATE A TRACKER NODE TO HOLD THE DATA
        tracker = nuke.createNode( 'Tracker3' )
        tracker['label'].setValue( 'tracking cv#%s in %s.%s' % ( cvID, node.name(), shape.name ) )
        trackerKnob = tracker['track1']
        trackerKnob.setAnimated()
    
        # SET UP PROGRESS BAR
        task = nuke.ProgressTask( 'CV Tracker' )
        task.setMessage( 'tracking CV' )
    
        # DO THE WORK
        for f in fRange:
            if task.isCancelled():
                nuke.executeInMainThread( nuke.message, args=( "CV Track Cancelled" ) )
                break
            task.setProgress( int( float(f)/fRange.last() * 100 ) )
    
            # GET POSITION
            pos = animPoint.getPosition( f )
            nuke.executeInMainThreadWithResult( trackerKnob.setValueAt, args=( pos.x, f, 0 ) ) # SET X VALUE
            nuke.executeInMainThreadWithResult( trackerKnob.setValueAt, args=( pos.y, f, 1 ) ) # SET Y VALUE
    
    def trackCV():
        # GET THE SELECTED NODE. SINCE WE PLAN ON CALLING THIS FROM THE PROPERTIES MENU
        # WE CAN BE SURE THAT THE SELECTED NODE IS ALWAYS THE ONE THE USER CLICKED IN
        node = nuke.selectedNode()
    
        # BAIL OUT IF THE NODE IS NOT WHAT WE NEED
        if node.Class() not in ('Roto', 'RotoPaint'):
            nuke.message( 'Unsupported node type. Node must be of class Roto or RotoPaint' )
            return
    
        p = examples.ShapeAndCVPanel( node )
        if p.showModalDialog():
            fRange = nuke.FrameRange( p.fRange.value() )
            shapeName = p.shape.value()
            cv = p.cv.value()
            threading.Thread( None, _cvTracker, args=(node, shapeName, cv, fRange) ).start()
    

    添加到Properties右键菜单的代码:

    nuke.menu('Properties').addCommand('Track CV', examples.trackCV)
    
    trackCV_03trackCV_03
    trackCV_04trackCV_04

    相关文章

      网友评论

        本文标题:Nuke Python roto

        本文链接:https://www.haomeiwen.com/subject/grmppttx.html