Tuesday 7 December 2010

Custom pickwalking

Pickwalking, being able to navigate up and down a hierarchy using the arrow keys, can be a real time saver for animators. The trouble is most of the time the things you want to pickwalk up, down, left or right to aren't directly connected in a hierarchy.

So here is how to set up a custom pickwalking solution where you can define which node to navigate up, down, left and right to. More importantly, I'll also cover how to seamlessly integrate this with the default Maya arrow key bindings and behaviour rather than using a second set of custom hotkeys.



This is in two parts. The first is a simple UI where you can assign left/right/up/down pickWalk targets for a node. Each target is stored on a string attribute on that node.
The second is modifying the default arrow-key pickwalk behaviour to redirect it to our defined target node if it exists (if not, it will revert to the default Maya pickWalk behaviour).

Part 1. Tool for assigning custom pickwalk targets

So, usual thing... here is the tool script. To run, source and run NT_customPickwalk.

global proc NT_customPickwalk_load(string $mode)
{
    if ($mode == "target") {
        string $sel[] = `ls -sl`;
        button -e -w 200 -l $sel[0] loadTarget_BTN;
        string $pickWalkUp = "up";
        string $pickWalkDown = "down";
        string $pickWalkLeft = "left";
        string $pickWalkRight = "right";
        if (`attributeExists "NT_pickWalkUp" $sel[0]`) {
            $pickWalkUp = `getAttr (($sel[0])+".NT_pickWalkUp")`;
        }
        if (`attributeExists "NT_pickWalkDown" $sel[0]`) {
            $pickWalkDown = `getAttr (($sel[0])+".NT_pickWalkDown")`;
        }
        if (`attributeExists "NT_pickWalkLeft" $sel[0]`) {
            $pickWalkLeft = `getAttr (($sel[0])+".NT_pickWalkLeft")`;
        }
        if (`attributeExists "NT_pickWalkRight" $sel[0]`) {
            $pickWalkRight = `getAttr (($sel[0])+".NT_pickWalkRight")`;
        }
               
        button -e -w 200 -l $pickWalkUp loadUp_BTN;
        button -e -w 200 -l $pickWalkDown loadDown_BTN;
        button -e -w 200 -l $pickWalkLeft loadLeft_BTN;
        button -e -w 200 -l $pickWalkRight loadRight_BTN;
    }
    if ($mode == "up") {
        string $sel[] = `ls -sl`;
        button -e -w 200 -l $sel[0] loadUp_BTN;
        string $targetNode = `button -q -l loadTarget_BTN`;
        if (`objExists $targetNode`) {
            if (!`attributeExists "NT_pickWalkUp" $targetNode`) {
                addAttr -ln "NT_pickWalkUp"  -dt "string" $targetNode;
            }
            setAttr -e-keyable true (($targetNode)+".NT_pickWalkUp");
            setAttr -type "string" (($targetNode)+".NT_pickWalkUp") $sel[0];
        }
       
    }
    if ($mode == "down") {
        string $sel[] = `ls -sl`;
        button -e -w 200 -l $sel[0] loadDown_BTN;
        string $targetNode = `button -q -l loadTarget_BTN`;
        if (`objExists $targetNode`) {
            if (!`attributeExists "NT_pickWalkDown" $targetNode`) {
                addAttr -ln "NT_pickWalkDown"  -dt "string" $targetNode;
            }
            setAttr -e-keyable true (($targetNode)+".NT_pickWalkDown");
            setAttr -type "string" (($targetNode)+".NT_pickWalkDown") $sel[0];
        }
       
    }
    if ($mode == "left") {
        string $sel[] = `ls -sl`;
        button -e -w 200 -l $sel[0] loadLeft_BTN;
        string $targetNode = `button -q -l loadTarget_BTN`;
        if (`objExists $targetNode`) {
            if (!`attributeExists "NT_pickWalkLeft" $targetNode`) {
                addAttr -ln "NT_pickWalkLeft"  -dt "string" $targetNode;
            }
            setAttr -e-keyable true (($targetNode)+".NT_pickWalkLeft");
            setAttr -type "string" (($targetNode)+".NT_pickWalkLeft") $sel[0];
        }
    }
    if ($mode == "right") {
        string $sel[] = `ls -sl`;
        button -e -w 200 -l $sel[0] loadRight_BTN;
        string $targetNode = `button -q -l loadTarget_BTN`;
        if (`objExists $targetNode`) {
            if (!`attributeExists "NT_pickWalkRight" $targetNode`) {
                addAttr -ln "NT_pickWalkRight"  -dt "string" $targetNode;
            }
            setAttr -e-keyable true (($targetNode)+".NT_pickWalkRight");
            setAttr -type "string" (($targetNode)+".NT_pickWalkRight") $sel[0];
        }
       
    }
}

global proc NT_customPickwalk_UI ()
{
    if (`window -q -ex NT_customPickwalk_win`)
        deleteUI NT_customPickwalk_win;
    window -t "CG Toolkit - Custom Pick Walker v1.0" -w 610 -h 150 NT_customPickwalk_win;           
    columnLayout;
        rowColumnLayout -nc 3 -cw 1 200 -cw 2 200 -cw 3 200 ;
            columnLayout  -w 200 ;
                button -w 200 -l "" -en false;
                button -w 200 -l "left" -c "NT_customPickwalk_load(\"left\");" loadLeft_BTN;
                button -w 200 -l "" -en false;
            setParent ..;
            columnLayout  -w 200 ;               
                button -w 200 -l "up" -c "NT_customPickwalk_load(\"up\");" loadUp_BTN;
                button -w 200 -l "load target" -c "NT_customPickwalk_load(\"target\");" loadTarget_BTN;
                button -w 200 -l "down" -c "NT_customPickwalk_load(\"down\");" loadDown_BTN;
            setParent ..;
            columnLayout  -w 200 ;
                button -w 200 -l "" -en false;
                button -w 200 -l "right" -c "NT_customPickwalk_load(\"right\");" loadRight_BTN;
                button -w 200 -l "" -en false;
            setParent ..;
        setParent..;
    setParent ..;
               

    //Show Main Window Command
    showWindow NT_customPickwalk_win;
    window -e -t "NT custom pickwalk" -w 616 -h 100 NT_customPickwalk_win;
}

global proc NT_customPickwalk()
{
    NT_customPickwalk_UI;
}


There are two main procedures in this script.
The first, NT_customPickwalk_UI  builds a simple UI with five buttons. The center button loads the node that you wish to assign custom pickwalk targets to by calling the procedure NT_customPickwalk_load with the argument "target". The remaining buttons load a selected node to a target direction by calling NT_customPickwalk_load with the argument "left", "right", "up" or "down".


The second, NT_customPickwalk_load(string $mode) does two things depending on the argument passed.
For argument "target" it loads the currently selected node to the center button label. If custom pickwalk targets have been assigned it will also load the names of those targets to the up/down/left/right buttons.
For arguments "left", "right", "up" or "down" it will create a string attribute NT_pickWalkUp, NT_pickWalkDown, NT_pickWalkLeft or NT_pickWalkRight if one doesn't already exist and assign the name of the currently selected node to that string variable.

Here I have a grid of locators all in the scene root. You can see that for locator7 I have assigned custom pickwalk targets and those are shown in the UI.



Here are the four string attributes on locator7 that store the custom pickWalk targets for this node.


Part 2. Modifying Maya's default pickwalk behaviour

There are four scripts you need to make modified version of (one for each pickWalk direction). These are pickWalkUp.mel /Down/Left/Right and can be found in C:\Program Files\Autodesk\Maya2008\scripts\others. To see how I structure and source modified Maya scripts see here.

Here is the default procedure pickWalkDown:
global proc pickWalkDown(){
   
    string $cmd = "pickWalk -d down";
   
    string $edgeSel[] = `filterExpand -sm 32`;
   
    if (size($edgeSel) != 0){
        $cmd += " -type edgering";
    }
   
    evalEcho($cmd);
}


...and here is the modified version:
global proc pickWalkDown(){
   
    string $sel[] = `ls -sl`;
    string $tokenized[];
    string $namespace = "";
    tokenize $sel[0] ":" $tokenized;
    if (`size $tokenized` > 1)
        $namespace = (($tokenized[0])+":");
   
    string $cmd;
    string $edgeSel[];
    string $customTarget;
   
    if (`attributeExists "NT_pickWalkDown" $sel[0]`) {
        $
customTarget= `getAttr (($sel[0])+".NT_pickWalkDown")`;
    }
    if (`objExists (($namespace)+($
customTarget))`) {
        select -r (($namespace)+($
customTarget));
    }
    else {
        $cmd = "pickWalk -d down";
       
        string $edgeSel[] = `filterExpand -sm 32`;
       
        if (size($edgeSel) != 0){
            $cmd += " -type edgering";
        }
    }
   
    evalEcho($cmd);
}


Added sections are highlighted. The first section (in red) strips the namespace from the selected node so we can add it onto the pickWalk down target string. This is to allow it to work still on a referenced rig which will place every node in a namespace of the animators choosing.
The second section (in cyan) checks for the custom pickwalk attribute NT_pickWalkDown. If it exists (adding the namespace string) it stores the down target in a string and checks that the node exists before selecting it. If the attribute NT_pickWalkDown does not exist then the default Maya pickwalk behaviour will be used.

Here's a capture of it working.

2 comments:

  1. Nice blog, congrats!

    We did exactly the same for our animators here at Kandor (Softimage based), we also store on custom attributes some code to be triggered on selection change and things like that.

    A really powerfull concept :)

    ReplyDelete
  2. thanks, I was wondering how to do this to make a rig a bit more bomb proof

    ReplyDelete