Remapping Viewer Hotkeys for Roto efficiency

Remapping Viewer Hotkeys for Roto efficiency

This started out as nitpicking but turned into an experiment in changing hotkey on the go while using Nuke.

I don’t like where the shortcut key for next/previous frames (arrow keys) in Nuke. It’s far away from the left side of the keyboard, and since I’m right handed, my hands are in an awkward position when I’m rotoing. Right hand using tablet on the right side on the table, while left hand sits awkwardly close to the right. What makes it worse is when I want to jump to next keyframe or mid way, it’s CTRL or ALT + arrow key. Then both hands need to move to trigger the key, and I like to minimize my movement to maximize my work efficiency.

It’s a first world problem.

First, I wouldn’t want it to be a hotkey that triggers anywhere on screen, it’ll have to be viewer only. Second, what’s the new hotkeys? I decided to use “Q” and “W” because I don’t use them much often when I’m actively rotoing. But I don’t want to replace them permanently, because obviously I still want to be able to toggle overlay or wipe with ease when I stopped rotoing. So here’s the thing I’m sure of.

  1. It’ll be in nuke.menu(‘Viewer’) so it’ll only activate when mouse is in viewer area
  2. It’ll use “Q”, “W” as hotkeys, and modifier key enable extra functions.
  3. I’ll need to be able to return these hotkeys to original when I stopped using them.

The last one was the most difficult for me to solve as I’ve never swap hotkeys on the go before.

Reading from Nuke Python Reference, I learned the command to navigate frame is as below. So I can replicate the function of arrow keys and jumping between keyframes in python.

frameControl(self, i)
i is an integer indicating viewer frame control 'button' to execute:    

	-6 go to start
	-5 play reverse
	-4 go to previous keyframe
	-3 step back by increment
	-2 go back previous keyframe or increment, whichever is closer
	-1 step back one frame
	0 stop
	+1 step forward one frame
	+2 go to next keyframe or increment, whichever is closer
	+3 step forward by increment
	+4 go to next keyframe
	+5 play forward
	+6 go to end 

Returns: True

First I thought maybe I can enable those hotkeys when a roto properties is shown, and disabled when no roto properties are present. Perhaps knobChanged? Here’s the piece of code I tried out.

def rotoShortcut():
	viewerMenu = nuke.menu('Viewer')
	viewerMenu.addCommand('Next Frame', "nuke.activeViewer().frameControl(+1)", 'w')
	viewerMenu.addCommand('Previous Frame', "nuke.activeViewer().frameControl(-1)", 'q')
	viewerMenu.addCommand('Next Keyframe', "nuke.activeViewer().frameControl(2)", 'alt+w')
	viewerMenu.addCommand('Previous Keyframe', "nuke.activeViewer().frameControl(-2)", 'alt+q')

	if nuke.thisNode().shown():
	    viewerMenu.findItem('Overlay').setEnabled(False)
	    viewerMenu.findItem('Enable Wipe').setEnabled(False)
	    viewerMenu.findItem('Set New ROI').setEnabled(False)
	    viewerMenu.findItem('Next Frame').setEnabled(True)
	    viewerMenu.findItem('Previous Frame').setEnabled(True)
	    viewerMenu.findItem('Next Keyframe').setEnabled(True)
	    viewerMenu.findItem('Previous Keyframe').setEnabled(True)
	else:
	    viewerMenu.findItem('Next Keyframe').setEnabled(False)
	    viewerMenu.findItem('Previous Keyframe').setEnabled(False)
	    viewerMenu.findItem('Next Frame').setEnabled(False)
	    viewerMenu.findItem('Previous Frame').setEnabled(False)
	    viewerMenu.findItem('Overlay').setEnabled(True)
	    viewerMenu.findItem('Enable Wipe').setEnabled(True)
	    viewerMenu.findItem('Set New ROI').setEnabled(True)

nuke.addKnobChanged(rotoShortcut, nodeClass='Roto')

It did not work out. Since node.shown() isn’t a knob, so knobChanged doesn’t really work here. And it crashes Nuke everytime I select a roto node. The first bit also replaced the hotkeys permanently even though I tried to use .setEnabled to enable and disable them, it doesn’t bring back the original hotkeys for overlay and wipe. Turned out when you use .setShortcut(), it will clear out existing conflicting shortcut keys.

So a different approach.

After more research I realized Nuke actually allows assigning hotkey to menu item on the fly. According to more nuke python reference here. So instead of trying to add command and enable/disable them, I can simply assign hotkeys to them. However I didn’t figure out how to trigger this automatically as a roto properties is shown. The methods I tried proven to be unstable and crash Nuke occasionally. This is the piece of code I wrote that requires manual trigger to enable disable the hotkeys change.

import nuke
viewerMenu = nuke.menu('Viewer')

#setting list of items/commands/hotkeys to add and/or remove
labels = ['Next Frame', 'Previous Frame', 'Next Keyframe or Increment', 'Previous Keyframe or Increment']
commands = ['nuke.activeViewer().frameControl(+1)', 'nuke.activeViewer().frameControl(-1)', 'nuke.activeViewer().frameControl(+2)', 'nuke.activeViewer().frameControl(-2)']
hotkeys = ['w', 'q', 'alt+w', 'alt+q']
conflictingLabels = ['Enable Wipe', 'Overlay', 'Set New ROI'] #original items with conflicting hotkeys

#disable up/down arrow hotkeys in viewer
viewerMenu.findItem('Previous Input (A Side)').setEnabled(False)
viewerMenu.findItem('Next Input (A Side)').setEnabled(False)

#Add commands and functions to viewer menu
index = 0
for new in labels:
    viewerMenu.addCommand(new, commands[index])
    index += 1

def enableRotoHotkeys():
    for old in conflictingLabels:
        viewerMenu.findItem(old).setShortcut('')
        print 'disabled ' + old + ' hotkey...'
    index = 0
    for new in labels:
        viewerMenu.findItem(new).setShortcut(hotkeys[index])
        print 'enabled ' + new + ' hotkey...'
        index += 1

def disableRotoHotkeys():
    for roto in labels:
        viewerMenu.findItem(roto).setShortcut('')
        print 'cleared ' + roto + ' hotkey...'
    index = 0
    for original in conflictingLabels:
        viewerMenu.findItem(original).setShortcut(hotkeys[index])
        print 'enabled ' + original + ' hotkey...'
        index += 1



#Add functions to viewer menu on nuke menu
viewerSubMenu = nuke.menu('Viewer').addMenu('Roto Hotkeys')
viewerSubMenu.addCommand('Enable', "viewerRotoHotkeys.enableRotoHotkeys()", '', icon='Roto.png')
viewerSubMenu.addCommand('Disable', "viewerRotoHotkeys.disableRotoHotkeys()", '')

So instead of switching automatically, it’ll be a manual right click in viewer window, navigating to a custom menu called “Roto Hotkeys” to enable or disable them. While disabled, “Q” and “W” will be return to their normal functions, overlay and wipe. I found this to be the most stable method to changing hotkeys in Nuke. I tried to add a few lines to actually remove the extra items I added in viewer menu, but for reason unknown to me, if I try to remove a menuItem in GUI, Nuke simply crashes without a warning. Perhaps I will report this as a bug.

I also created list and use for loop to change the keys as the previously attempted method seems really messy. And this way I can add more or alter them with ease

You may also notice as highlighted above I also disabled the up/down arrow keys. I often accidentally hit them, and my next viewer item might be a really heavy node. I then have to wait a few seconds frustratingly while Nuke thinks and just switch back to whatever I was doing.

After a few months of using it, I figured I need to be able to trigger it with a hotkey instead of right click to enable/disable every single time. The end result is a bit of combination of everything above.

First I added a line of code that check whether the menuItem has shortcut or not. If it does, it will clear out the hotkey and assign it to the corresponding item I have in mind. However, the line of code below is not working as I expected.

nuke.menu('Viewer').findItem('Overlay').shortcut()

It is suppose to return “Q” when hotkey exists, and “” when it’s not. To my surprise, it only returns “” whether menuItem “Overlay” has its hotkey or not.

Thanks to nuke python forum, I learned that I can use menuItem().action().enabled() to check whether a menuItem is enabled or not. So using by utilizing .setEnabled(), I can disable the original menuItem and clear its hotkey, and enable the new one and reassign hotkeys to them. Below is the updated final code.

import nuke
viewerMenu = nuke.menu('Viewer')

#setting list of items/commands/hotkeys to add and/or remove
labels = ['Next Frame', 'Previous Frame', 'Next Keyframe or Increment', 'Previous Keyframe or Increment']
commands = ['nuke.activeViewer().frameControl(+1)', 'nuke.activeViewer().frameControl(-1)', 'nuke.activeViewer().frameControl(+2)', 'nuke.activeViewer().frameControl(-2)']
hotkeys = ['w', 'q', 'alt+w', 'alt+q']
conflictingLabels = ['Enable Wipe', 'Overlay', 'Set New ROI'] #original items with conflicting hotkeys

#disable up/down arrow hotkeys in viewer
viewerMenu.findItem('Previous Input (A Side)').setEnabled(False)
viewerMenu.findItem('Next Input (A Side)').setEnabled(False)

#Add commands and functions to viewer menu
index = 0
for new in labels:
    viewerMenu.addCommand(new, commands[index])
    viewerMenu.findItem(new).setEnabled(False)
    index += 1

def toggleRotoHotkeys():
    for items in [conflictingLabels, labels]:
        index = 0
        for item in items:
            menuItem = viewerMenu.findItem(item)
            if menuItem.action().isEnabled():
                menuItem.setShortcut('')
                menuItem.setEnabled(False)
            else:
                menuItem.setEnabled(True)
                menuItem.setShortcut(hotkeys[index])
            index += 1



#Add functions to viewer menu on nuke menu
viewerMenu.addCommand('Toggle Roto Hotkeys', "viewerRotoHotkeys.toggleRotoHotkeys()", 'Alt+Shift+R', icon='Roto.png')

Leave a Reply

Your email address will not be published. Required fields are marked *