Last active
November 24, 2021 11:49
-
-
Save jedypod/6969051 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import nuke, nukescripts, nuke.rotopaint as rp, _curvelib as cl | |
''' | |
## LINK TOOLS | |
Utility functions for creating linked nodes. This script can create roto linked to trackers, | |
transform linked to trackers, or cameras linked to each other, or held on a projection frame. | |
Installation: | |
Put link_tools.py somewhere in your nuke path. | |
You can add the script as commands to your Nodes panel. This code creates a custom menu entry called "Scripts". | |
I have them set to the shortcuts Alt-O for roto and Alt-L for generalized link (works with tracker, transform, and camera nodes). | |
import link_tools | |
nuke.toolbar('Nodes').addMenu('Scripts').addCommand('Link Roto', 'link_tools.link("roto")', 'alt+o') | |
nuke.toolbar('Nodes').addMenu('Scripts').addCommand('Link Tool', 'link_tools.link()', 'alt+l') | |
''' | |
def link_transform(target_node): | |
""" | |
Utility function for link_transform: creates linked transform node | |
""" | |
target_name = target_node.name() | |
nclass = target_node.Class() | |
if "Tracker" not in nclass and "Transform" not in nclass: | |
print "Must select Tracker or Transform node!" | |
return | |
grid_x = int(nuke.toNode('preferences').knob('GridWidth').value()) | |
grid_y = int(nuke.toNode('preferences').knob('GridHeight').value()) | |
target_node.setSelected(False) | |
# Create linked Transform Node | |
trans = nuke.nodes.Transform() | |
trans.setName('TransformLink') | |
trans.setSelected(True) | |
trans.knob('help').setValue("<b>TransformLink</b>\n\nA Transform node with options for linking to a Tracker or a Transform node. \n\nAllows you to set a seperate identity transform frame from the linked Tracker. Select the link target and click 'Set Target', or set the link target by name. You can also bake the expression link into keyframes if you want it independant from the target node. \n\nThe transform Matchmoves or Stabilizes depending on what the parent tracker node is set to, but you can invert this by enabling the 'invert' checkbox.") | |
trans.knob('label').setValue('Matchmove: [value link_target]') | |
trans.setXYpos(target_node.xpos()-grid_x*0, target_node.ypos()+grid_y*2) | |
trans.addKnob(nuke.Tab_Knob('TrackLink')) | |
trans.addKnob(nuke.Int_Knob('identity_frame', 'Identity Frame')) | |
if 'Tracker' in nclass: | |
trans.knob('identity_frame').setValue( int(target_node.knob('reference_frame').value()) ) | |
else: | |
trans.knob('identity_frame').setValue(nuke.frame()) | |
trans.addKnob(nuke.PyScript_Knob('identity_to_curframe', 'Set to Current Frame')) | |
trans.knob('identity_to_curframe').setTooltip("Set identity frame to current frame.") | |
trans.knob('identity_to_curframe').setCommand("nuke.thisNode().knob('identity_frame').setValue(nuke.frame())") | |
trans.addKnob(nuke.PyScript_Knob('del_relative', 'Del Relative')) | |
trans.knob('del_relative').setTooltip('Delete relative transformation.\n\nUse original Transform values.') | |
trans.knob('del_relative').setCommand("# Delete Rel - remove identity frame transformations on transform link\ndef del_relative():\n n = nuke.thisNode()\n # Get target node name\n target_name = n['translate'].animation(0).expression().split(' - ')[0].split('parent.')[1].split('.translate')[0]\n n.knob('translate').clearAnimated()\n n.knob('translate').setExpression('parent.{0}.translate'.format(target_name))\n n.knob('rotate').clearAnimated()\n n.knob('rotate').setExpression('parent.{0}.rotate'.format(target_name))\n n.knob('scale').clearAnimated()\n n.knob('scale').setExpression('parent.{0}.scale'.format(target_name))\n n.knob('skewX').clearAnimated()\n n.knob('skewX').setExpression('parent.{0}.skewX'.format(target_name))\n n.knob('skewY').clearAnimated()\n n.knob('skewY').setExpression('parent.{0}.skewY'.format(target_name))\n n.knob('center').clearAnimated()\n n.knob('center').setExpression('parent.{0}.center'.format(target_name))\nif __name__ == '__main__':\n del_relative()") | |
trans.addKnob(nuke.String_Knob('link_target', 'Link Target', target_name)) | |
trans.knob('link_target').setTooltip('Type in a node name to link to.') | |
trans.addKnob(nuke.PyScript_Knob('set_target', 'Set Target')) | |
trans.knob('set_target').clearFlag(nuke.STARTLINE) | |
trans.knob('set_target').setTooltip('Sets the link target to the selected node.\n\nIf no node is selected, set target node to the value of Link Target.') | |
trans.knob('set_target').setCommand("# Set Target Button\ndef link_to_target():\n trans = nuke.thisNode()\n selected_nodes = nuke.selectedNodes()\n if len(selected_nodes) is 0:\n target_name = nuke.thisNode().knob('link_target').getValue()\n target_node = nuke.toNode(target_name)\n if target_node is None:\n nuke.message('Target node does not exist!')\n return\n if len(selected_nodes) is 1:\n target_node = selected_nodes[0]\n target_name = target_node.name()\n trans.knob('link_target').setValue(target_name)\n if len(selected_nodes) > 1:\n print 'Error: Exactly 1 target node must be specified.'\n return\n target_class = target_node.Class()\n if target_class in ['Tracker3', 'Tracker4', 'Transform', 'TransformMasked']:\n trans.knob('translate').clearAnimated()\n trans.knob('translate').setExpression('parent.{0}.translate - parent.{0}.translate(identity_frame)'.format(target_name))\n trans.knob('rotate').clearAnimated()\n trans.knob('rotate').setExpression('parent.{0}.rotate - parent.{0}.rotate(identity_frame)'.format(target_name))\n trans.knob('scale').clearAnimated()\n trans.knob('scale').setExpression('parent.{0}.scale / parent.{0}.scale(identity_frame)'.format(target_name))\n trans.knob('skewX').clearAnimated()\n trans.knob('skewX').setExpression('parent.{0}.skewX - parent.{0}.skewX(identity_frame)'.format(target_name))\n trans.knob('skewY').clearAnimated()\n trans.knob('skewY').setExpression('parent.{0}.skewY - parent.{0}.skewY(identity_frame)'.format(target_name))\n trans.knob('center').clearAnimated()\n trans.knob('center').setExpression('parent.{0}.center+parent.{0}.translate(identity_frame)'.format(target_name))\n if target_class in ['Tracker3', 'Tracker4']:\n trans.knob('identity_frame').setValue( int(nuke.toNode(target_name).knob('reference_frame').getValue()) )\n else:\n print 'Error: Must select a Tracker or Transform class node.'\nif __name__ == '__main__':\n link_to_target()") | |
trans.addKnob(nuke.PyScript_Knob('bake_link', 'Bake Expression Links')) | |
trans.knob('bake_link').setFlag(nuke.STARTLINE) | |
trans.knob('bake_link').setTooltip('Bake expression links to keyframes') | |
trans.knob('bake_link').setCommand("# Bake Expression Links Button\ndef bake_expression_links():\n trans = nuke.thisNode()\n link_target_name = trans.knob('link_target').getValue()\n target_node = nuke.toNode(link_target_name)\n if target_node.knob('translate').isAnimated():\n # Get animation framerange from translate knob of target tracker node\n target_anims = target_node.knob('translate').animations()\n first_key = target_anims[0].keys()[0].x\n last_key = target_anims[0].keys()[-1].x\n else:\n nuke.message('No Keys on Source!')\n return\n # Bake knob values\n def bake_knob( node, knob, first_key, last_key):\n first_key = str(first_key)\n last_key = str(last_key)\n def horizontal_ends(knob, i):\n # Set start and end keyframe to horizontal interpolation so it matches expression before and after animation\n anim = knob.animation(i)\n anim.changeInterpolation( [anim.keys()[0], anim.keys()[-1]], nuke.HORIZONTAL)\n return\n if knob.width() == 1:\n nuke.animation('{0}.{1}'.format(node.name(), knob.name()), 'generate', (first_key, last_key, '1', 'y', '{0}'.format(knob.name())))\n horizontal_ends(knob, 0)\n if knob.width() == 2: \n if knob.name() == 'scale':\n if knob.singleValue() == True:\n nuke.animation('{0}.scale'.format(node.name()), 'generate', (first_key, last_key, '1', 'y', 'scale'))\n horizontal_ends(knob, 0)\n else:\n for i, dim in enumerate(['w', 'h']):\n nuke.animation('{0}.{1}.{2}'.format(node.name(), knob.name(), dim), 'generate', (first_key, last_key, '1', 'y', '{0}.{1}'.format(knob.name(), dim)))\n horizontal_ends(knob, i)\n else:\n for i, dim in enumerate(['x', 'y']):\n nuke.animation('{0}.{1}.{2}'.format(node.name(), knob.name(), dim), 'generate', (first_key, last_key, '1', 'y', '{0}.{1}'.format(knob.name(), dim)))\n horizontal_ends(knob, i)\n for knob_name, knob in trans.knobs().iteritems():\n if knob_name in ['translate', 'rotate', 'scale', 'skewX', 'skewY', 'center']:\n print 'Baking {0}'.format(knob_name)\n bake_knob(trans, knob, first_key, last_key)\nif __name__ == '__main__':\n bake_expression_links()") | |
trans.addKnob(nuke.Text_Knob("")) | |
trans.addKnob(nuke.Double_Knob('motionblur_gui', 'gui mblur')) | |
trans.knob('motionblur_gui').setValue(0) | |
trans.knob('motionblur_gui').setTooltip('Set the $gui motion blur value') | |
# Link knobs | |
trans.knob('translate').setExpression('parent.{0}.translate - parent.{0}.translate(identity_frame)'.format(target_name)) | |
trans.knob('rotate').setExpression('parent.{0}.rotate - parent.{0}.rotate(identity_frame)'.format(target_name)) | |
trans.knob('scale').setExpression('parent.{0}.scale / parent.{0}.scale(identity_frame)'.format(target_name)) | |
trans.knob('skewX').setExpression('parent.{0}.skewX - parent.{0}.skewX(identity_frame)'.format(target_name)) | |
trans.knob('skewY').setExpression('parent.{0}.skewY - parent.{0}.skewY(identity_frame)'.format(target_name)) | |
trans.knob('center').setExpression('parent.{0}.center+parent.{0}.translate(identity_frame)'.format(target_name)) | |
trans.knob('motionblur').setExpression('$gui ? motionblur_gui : 4') | |
trans.knob('shutteroffset').setValue('centred') | |
def link_roto(tracker_node, roto_node=False): | |
''' | |
Utility function: Creates a layer in roto_node linked to tracker_node | |
if roto_node is False, creates a roto node next to tracker node to link to | |
''' | |
grid_x = int(nuke.toNode('preferences').knob('GridWidth').value()) | |
grid_y = int(nuke.toNode('preferences').knob('GridHeight').value()) | |
tracker_name = tracker_node.name() | |
tracker_node.setSelected(False) | |
# If Roto node not selected, create one. | |
if not roto_node: | |
roto_node = nuke.nodes.Roto() | |
roto_node.setXYpos(tracker_node.xpos()-grid_x*0, tracker_node.ypos()+grid_y*2) | |
roto_node.setSelected(True) | |
# Create linked layer in Roto Node | |
curves_knob = roto_node["curves"] | |
stab_layer = rp.Layer(curves_knob) | |
stab_layer.name = "stab_"+tracker_name | |
trans_curve_x = cl.AnimCurve() | |
trans_curve_y = cl.AnimCurve() | |
trans_curve_x.expressionString = "parent.{0}.translate.x".format(tracker_name) | |
trans_curve_y.expressionString = "parent.{0}.translate.y".format(tracker_name) | |
trans_curve_x.useExpression = True | |
trans_curve_y.useExpression = True | |
rot_curve = cl.AnimCurve() | |
rot_curve.expressionString = "parent.{0}.rotate".format(tracker_name) | |
rot_curve.useExpression = True | |
scale_curve = cl.AnimCurve() | |
scale_curve.expressionString = "parent.{0}.scale".format(tracker_name) | |
scale_curve.useExpression = True | |
center_curve_x = cl.AnimCurve() | |
center_curve_y = cl.AnimCurve() | |
center_curve_x.expressionString = "parent.{0}.center.x".format(tracker_name) | |
center_curve_y.expressionString = "parent.{0}.center.y".format(tracker_name) | |
center_curve_x.useExpression = True | |
center_curve_y.useExpression = True | |
# Define variable for accessing the getTransform() | |
transform_attr = stab_layer.getTransform() | |
# Set the Animation Curve for the Translation attribute to the value of the previously defined curve, for both x and y | |
transform_attr.setTranslationAnimCurve(0, trans_curve_x) | |
transform_attr.setTranslationAnimCurve(1, trans_curve_y) | |
# Index value of setRotationAnimCurve is 2 even though there is only 1 parameter... | |
# http://www.mail-archive.com/[email protected]/msg02295.html | |
transform_attr.setRotationAnimCurve(2, rot_curve) | |
transform_attr.setScaleAnimCurve(0, scale_curve) | |
transform_attr.setScaleAnimCurve(1, scale_curve) | |
transform_attr.setPivotPointAnimCurve(0, center_curve_x) | |
transform_attr.setPivotPointAnimCurve(1, center_curve_y) | |
curves_knob.rootLayer.append(stab_layer) | |
def link_camera(src_cam, proj_frame, expr_link, clone, index): | |
""" | |
Create a linked camera to src_cam -- this camera can be frozen on a projection frame | |
""" | |
# Default grid size is 110x24. This enables moving by grid increments. | |
# http://forums.thefoundry.co.uk/phpBB2/viewtopic.php?t=3739&sid=c40e65b1f575ba9166583faf807184ee | |
# Offset by 1 grid in x for each additional iteration | |
grid_x = int(nuke.toNode('preferences').knob('GridWidth').value()) | |
grid_y = int(nuke.toNode('preferences').knob('GridHeight').value())*index | |
src_cam.setSelected(False) | |
# Create Projection Camera | |
proj_cam = nuke.nodes.Camera2() | |
# Create Projection Frame knob | |
frame_tab = nuke.Tab_Knob('Frame') | |
proj_cam.addKnob(frame_tab) | |
proj_frame_knob = nuke.Double_Knob('proj_frame') | |
if clone: | |
proj_frame_knob.setExpression('t') | |
else: | |
proj_frame_knob.setValue(proj_frame) | |
proj_cam.addKnob(proj_frame_knob) | |
## Copy the knob values of the Source Camera | |
for knob_name, knob in src_cam.knobs().iteritems(): | |
# For Animated knobs, copy or link the values depending on if Expression Links are enabled. | |
if knob.isAnimated(): | |
#print "setting animated knob", knob_name | |
if expr_link == True: | |
#print "setting expression for animated knobs" | |
for index in range(src_cam.knob(knob_name).arraySize()): | |
if src_cam.knob(knob_name).isAnimated(index): | |
proj_cam[knob_name].copyAnimation(index, src_cam[knob_name].animation(index)) | |
proj_cam[knob_name].setExpression('parent.' + src_cam.name() + "." + knob_name + "(proj_frame)") | |
# http://www.nukepedia.com/python/knob-animation-and-python-a-primer/ | |
else: | |
for index in range(src_cam.knob(knob_name).arraySize()): | |
if src_cam.knob(knob_name).isAnimated(index): | |
proj_cam[knob_name].copyAnimation(index, src_cam[knob_name].animation(index)) | |
proj_cam[knob_name].setExpression('curve(proj_frame)') | |
# For all non-animated knobs that are not set to default values, match value from src_cam | |
elif hasattr(knob, "notDefault") and knob.notDefault(): | |
try: | |
#print "changing ", knob_name | |
proj_cam[knob_name].setValue(knob.value()) | |
except TypeError: | |
pass | |
# Set label, color, name, and position | |
proj_cam.setXYpos(src_cam.xpos()-grid_x, src_cam.ypos()-grid_y*4) | |
if clone: | |
proj_cam.setName("{0}_CLONE_".format(src_cam.name())) | |
proj_cam["gl_color"].setValue(0xff5f00ff) | |
proj_cam["tile_color"].setValue(0xff5f00ff) | |
else: | |
proj_cam.setName("{0}_PROJ_".format(src_cam.name())) | |
proj_cam["gl_color"].setValue(0xffff) | |
proj_cam["tile_color"].setValue(0xffff) | |
proj_cam["label"].setValue("FRAME [value proj_frame]") | |
def link(link_type=None): | |
nodes = nuke.selectedNodes() | |
track_nodes = [n for n in nodes if 'Tracker' in n.Class()] | |
cam_nodes = [n for n in nodes if n.Class() in ['Camera', 'Camera2']] | |
roto_nodes = [n for n in nodes if n.Class() in ['Roto', 'RotoPaint', 'SplineWarp3']] | |
xform_nodes = [n for n in nodes if n.Class() == 'Transform'] | |
if link_type == 'roto': | |
if len(track_nodes) == 0: | |
print "Error: At least one Tracker node must be selected." | |
return | |
if len(roto_nodes) > 1 and len(track_nodes) > 1: | |
print "Error: if multiple roto nodes are selected, exactly 1 Tracker node must be selected." | |
return | |
for track_node in track_nodes: | |
if len(roto_nodes) is 0: | |
link_roto(track_node) | |
if len(roto_nodes) >= 1: | |
for roto_node in roto_nodes: | |
link_roto(track_node, roto_node) | |
if link_type == None: | |
for track_node in track_nodes: | |
link_transform(track_node) | |
for xform_node in xform_nodes: | |
link_transform(xform_node) | |
for cam_node in cam_nodes: | |
## Set up camera linker panel | |
p = nuke.Panel('Camera Linker: {0}'.format(cam_node.name())) | |
p.setWidth(450) | |
p.addSingleLineInput('Frame', str(nuke.frame())) | |
p.addBooleanCheckBox("Clone", False) | |
p.addBooleanCheckBox('Link', True) | |
if p.show(): | |
clone = p.value('Clone') | |
framestring = p.value('Frame') | |
expr_link = p.value('Link') | |
else: | |
# Cancelled | |
return | |
## Parse frame string | |
if "," in framestring: | |
framelist = map(int, framestring.split(',')) | |
for i, frame in enumerate(framelist): | |
link_camera(cam_node, frame, expr_link, clone, i) | |
else: | |
try: | |
framestring = int(framestring) | |
except: | |
print "Error converting frame to integer!" | |
return | |
link_camera(cam_node, framestring, expr_link, clone, 0) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
About:
This script is a set of tools to expression link Transform, Tracker, Roto, and Camera nodes. This is advantageous so that you don't have tons of copies of knobs with animation curves, which can increase your script size and slow things down. This tool lets you easily create expression-linked Transform and Roto nodes that are linked to a master Tracker node, or copies of a Camera that are expression-linked to a master Camera in your script.
It is often the case that you need to create a Roto or RotoPaint node that has transforms linked to a tracker. The Create Linked Roto button creates a new Roto node with a layer that is linked to the tracker's transforms. Or, if a Roto or RotoPaint node is selected, a linked layer will be added to that node.
The Create Linked Transform button creates a transform node that is linked to the tracker. The created transform node lets you specify the identity transform frame separate from the Tracker node, and lets you bake the expression to keyframes, or re-link to a different tracker node. The transform can be switched from Matchmove to Stabilize by checking the invert checkbox.
This might seem redundant to the built-in functionality of Nuke7's Tracker node that lets you create a Matchmove or Stabilize transform. Unfortunately the transforms that are created using this method are burdened by python code that markedly degrades Nuke's UI performance. Turn on "Echo python commands to output window" in the Preferences under Script Editor in a script with a few of these nodes, and see what I mean.
Installation:
Put link_tools.py somewhere in your nuke path.
You can add the script as commands to your Nodes panel. This code creates a custom menu entry called "Scripts".
I have them set to the shortcuts Alt-O for roto and Alt-L for generalized link (works with tracker, transform, and camera nodes).
import link_tools
nuke.toolbar('Nodes').addMenu('Scripts').addCommand('Link Roto', 'link_tools.link("roto")', 'alt+o')
nuke.toolbar('Nodes').addMenu('Scripts').addCommand('Link Tool', 'link_tools.link()', 'alt+l')