View Full Version : Space switch matching - setKeyframe or variable problem?

03 March 2009, 01:25 AM

I'm new to mel and rigging. I'm having problems with this script/scriptJob that is meant for space switching. It's for a toy windmill prop and I'm trying to set up the blades of the windmill with two parents, the toy itself and the world.

The setup I've made is like this: the windmill has two controls, a "main_ctrl" and a "blades_ctrl". The blades_ctrl is a child of a group called "blades_spaceSwitch_grp". The blades_spaceSwitch_grp is a child of another group named "blades_ctrl_cnst" which has one parent constraint constraining it to the main_ctrl. For space switch matching I'm using a null parented under the main_ctrl (pivot and position matches the blades_ctrl exactly).

blades_ctrl_cnst (constrained to main_ctrl)
> blades_spaceSwitch_grp (has enum attr named "parent", values "free" and "attached")
-> blades_ctrl (has the same "parent" enum attr but is non-keyable)

The blades_ctrl.parent triggers the script like it was a button. The blades_spaceSwitch_grp.parent is directly connected to the weight of the blades_ctrl_cnst parent constraint.

What I'm trying for besides the seamless space switch is to set a key on the spaceSwitch_grp on the previous frame with the previous parent/position condition and a key on the current frame with the new parent/position.

Here's the code:

global string $ns = "";

proc matchBlades()
// declare variables for script

global string $ns;
// match object info
$bt = `xform -query -worldSpace -rotatePivot ($ns+"blades_ctrl_match")`;
$br = `xform -query -worldSpace -rotation ($ns+"blades_ctrl_match")`;
// spaceSwitch current info
$curPos = `getAttr ($ns+"blades_spaceSwitch_grp.translate")`;
$curRot = `getAttr ($ns+"blades_spaceSwitch_grp.rotate")`;
// current and new parent info
$curState = `getAttr ($ns+"blades_spaceSwitch_grp.parent")`;
$newState = `getAttr ($ns+"blades_ctrl.parent")`;
// current and previous frame info
$curFrm = `currentTime -query`;
$prevFrm = ($curFrm - 1);

// match blades to the match object when the parent switch is changed to "free"
if ($newState == 0)
setKeyframe -time $prevFrm -outTangentType step ($ns+"blades_spaceSwitch_grp");
move -rpr $bt[0] $bt[1] $bt[2] ($ns+"blades_spaceSwitch_grp");
rotate -a -worldSpace $br[0] $br[1] $br[2] ($ns+"blades_spaceSwitch_grp");
setAttr ($ns+"blades_spaceSwitch_grp.parent") 0;
setKeyframe -time $curFrm -outTangentType step ($ns+"blades_spaceSwitch_grp");
else //zero out the blades bringing them back to the windmill
setKeyframe -time $prevFrm -outTangentType step ($ns+"blades_spaceSwitch_grp");
setAttr ($ns+"blades_spaceSwitch_grp.translate") 0 0 0;
setAttr ($ns+"blades_spaceSwitch_grp.rotate") 0 0 0;
setAttr ($ns+"blades_spaceSwitch_grp.parent") 1;
setKeyframe -time $curFrm -outTangentType step ($ns+"blades_spaceSwitch_grp");
//key the blades control
setKeyframe -time $curFrm ($ns+"blades_ctrl");
// execute script when the "Parent" attribute changes
$bMatchJob = `scriptJob -ac ($ns+"blades_ctrl.parent") matchBlades`;

I've seen awkward space switching scriptJobs that are triggered by an attribute that is keyable. I've found that you can't scrub the time line and expect the scriptJobs to trigger, or you get into weird loops when you undo (I undo keying the parent attr and the scriptJob triggers again unintentionally) I've gotten the individual pieces of my script to work, but I can't get the setKeyframe command to work consistently. It always keys the same values on both the previous and current frames. I printed the variables and they all look okay. What am I not getting?

(I know there are easier ways to animate the windmill blades' connection but I'm trying to learn this technique so I can use it in the future.)

Help!! (please)


03 March 2009, 08:41 PM
I personally tend to shy away from scriptJobs, just because it seems like just one more thing maya has to check for everytime its condition is met. I think, in this instance, it seems like a button would work just as effectively.

But now that I have that off my chest, let's take a look at what you're trying to do here:

I set up a quick example scene in maya to try this out.
You don't say exactly where "blades_ctrl_match" is -- I assumed it's the name of the null you placed under your main_ctrl.

I'm noticing you have a few variables that you never use: $curPos, $curRot, $curState...did you intend to implement these in some way?

Interestingly enough, I'm not finding any problems with the keys being set -- the only time the "blades_spaceSwitch_grp.parent" attribute's previous key stays the same is when I set the "blade_ctrl.parent" to the same value it already was at. Is this maybe where you planned to use $curState?

I also am not having any trouble scrubbing the time or undoing the scriptJob's effects-- or were you just saying that's a problem you've found in other scriptJobs?

Where I AM having difficulty is actualy matching the positioning after the space-switch.
I think this may have something to do with the logic of the script -- so in the meantime, I'm going to investigate further, and see if I can make this work...

Glad to see you're experimenting with some setup stuff, Marcos!

03 March 2009, 12:47 AM
Thanks Ryan,

About those unused variables, I forgot to remove them before I posted the code. In my experiments I was trying to use those to setAttr on the spaceSwitch_grp before setting a key (for its current state obviously), but then I thought since those attributes haven't changed then why do I need to set them?

Anyway, I've attached a file that comes close to what I was going for. On the blades_ctrl there is an attr that controls what it's parented to and another that activates the match. The only problem here is that after you press "Go!" you have to go back to the previous frame and set a key on the parent attr, setting it back to the previous state to preserve what was going on before the match. I got greedy and thought, "can't all this happen from the script?" I've since changed the set up to what I described earlier so that there is no "Parent Activate" attr on the blades_ctrl (you'll see when you open my old file). I'll also include in the attachment a version without the scriptJob if it helps (it has the new set up that is trying to work with the script I posted). Let me know if you have any ideas.


03 March 2009, 12:57 AM
Ok, so I've got something that I think will work for this -- I've made a few modifications to the script and hierarchy, as shown below:

Whenever you want to space-switch, run:
spaceSwitch("blades_ctrl", "blades_spaceSwitch_grp");

Here's the hierarchy:

[ Ctrl_Gp ]
|--[ main_ctrlZero ]
| . . |
| . . |--[ main_ctrl ]
|--[ blades_ctrlZero ]
| . . |
| . . |--[ blades_spaceSwitch_grp ]
| . . | . . . |
| . . | . . . |--[ blades_ctrl ]
| . . |
| . . |--[ blades_ctrlZero_parentConstraint1 ]

Here's the script we'll be using:

global proc spaceSwitch(string $switchGrp, string $ctrl)
// declare variables for script
string $sel[];
string $ns = `match ".*:" $sel[0]`;

// get blade's positioning info
$curPos = `xform -q -ws -rp ($ns+$switchGrp)`;
$curRot = `xform -q -ws -ro ($ns+$switchGrp)`;

// get "parent" attr value
$curState = `getAttr ($ns+$ctrl+".parent")`;

// get current and previous frame
$curFrm = `currentTime -query`;
$prevFrm = ($curFrm - 1);

//key parent state on previous frame (before matching)
setKeyframe -t $prevFrm -ott step ($ns+$switchGrp);
setKeyframe -t $prevFrm -ott step ($ns+$ctrl+".parent");

if ($curState == 1)
setAttr ($ns+$ctrl+".parent") 0;
xform -ws -t $curPos[0] $curPos[1] $curPos[2] ($ns+$switchGrp);
xform -ws -ro $curRot[0] $curRot[1] $curRot[2] ($ns+$switchGrp);

setAttr ($ns+$ctrl+".parent") 1;
xform -ws -t $curPos[0] $curPos[1] $curPos[2] ($ns+$switchGrp);
xform -ws -ro $curRot[0] $curRot[1] $curRot[2] ($ns+$switchGrp);


//key parent state on current frame (after matching);
setKeyframe -t $curFrm -ott step ($ns+$switchGrp);
setKeyframe -t $curFrm -ott step ($ns+$ctrl+".parent");

print "Space switch done.";

First off, I don't like using global variables if I can avoid them. Local variables are freed from memory after the block they are declared has run (in this case, a procedure), while global variables just sit in memory until maya is closed. While sometimes you may want that behaviour, I don't think you need it here.

To take care of namespace issues, I've used the "match" mel command. It's super handy, so I recommend reading up on it. Basically, what I've done here is said "look at the selected object, and take any characters (letters), any number of times that precede a colon, and take those letters and that colon and dump it in the $ns variable.

Now, unless I've missed the point of space-switching, the idea is to have the controller in the same place as before switching it's parent.
This means we don't really need the null under the main group -- we just want to check where the switch_Grp is before the parent change, then make sure it ends up in the same place after the parent change.

That said, to make our script a little more flexible (and easier to edit), I've got the two nodes we'll be working with turned into variables.

In the example scene I did, I only added the "parent" attr to the "blades_ctrl" -- thinking it'd be easier for animator to access the space-switch curves.

I'm realizing now that to change the timing of the space switch, the animator will need to access the translations and rotation keys on the switch_grp anyways, so really, you were right to put the "parent" attr on the switch_grp. Then just connect that attr to a locked enum attr on the ctrl, so animators can see in their channelbox what the active parent is.

Hope this helps -- I realize it's a bit wordy.

I still recommend using this script via a button over a script job though ;)

03 March 2009, 12:37 AM
Super elegant script Ryan. I appreciate the pointers about global variables and using the "match" command. Very cool! When I thought of scriptJob for the space switching I was just thinking of a one-click solution that was "built in". I agree that a button would work and would avoid any sort of scriptJob grief. I'm rethinking whether or not having external scripts is an inconvenience or not.

I was wondering how will the $sel variable get the selected object's info? Should it be

string $sel[] = `ls -sl`;?

My logic for using a null as a match object was to establish a technique for use with space switching between FK and IK limbs that are not in the same position and that may have different rotation orders. I could place these match objects under the opposite limb and as long as the rotation orders matched their respective control objects then the xform command would work predictably. That is, if the rotation orders differ from the FK wrist to the IK arm control, then a null with the same rotation order as the FK wrist placed under the IK arm control will give me a target for the xform query that should work correctly when applied later. At least that's what the tutorial video told me :) .

Speaking of xform... I was having issues with the results of "xform -ws -t" when the object (or its parents?) had its transforms frozen. I found that "move -a -rpr" worked. I guess I'm not clear when I should or shouldn't use freeze transformations.

Thanks again for the help. I'm trying it now.


03 March 2009, 12:32 AM
I was wondering how will the $sel variable get the selected object's info?

Oh yeah, my bad! Good eye. Yeah, you'll actually need to assign a value to the $sel array with `ls -sl`, heh heh.

External scripts are great. It makes them quite portable, for one, and you don't have to worry about losing your code if maya crashes...

If maya is giving you grief about finding the scripts, here's a few tips:

In the maya.env file, add:
where [path] is the path to where your scripts are. You can enter multple paths, seperating each with a semicolon.


MAYA_SCRIPT_PATH = C:/MEL/developing;C:/MEL/stringTools;C:/MEL/Comet;

I don't think it makes a difference which direction slash you use, as long as you are consitent. I prefer the forward slash because that's what you use in MEL. :) Also, the back slash is considered an escape character in most of the languages I've encountered...

Note that Maya should automatically be able to find scripts in the maya/[version]/scripts folder; BUT, not in any subdirectories. Maya will not check any subdirectories, you'll have to explicitly state them in the MAYA_SCRIPT_PATH.

If you've created a new script file since you've opened Maya, Maya wont be able to find it unless you first run the "rehash;" command.

Speaking of xform... I was having issues with the results of "xform -ws -t" when the object (or its parents?) had its transforms frozen. I found that "move -a -rpr" worked. I guess I'm not clear when I should or shouldn't use freeze transformations.

I'll have to look into this some more, but here's another reason why I shy away from "freeze transforms"; I basically never use it to "zero"/freeze out transformation values and keep a unique pivot -- rather, I put a group/null directly above each control with the exact same transforms. Since transform values are relative to the parent, this effectively "zeroes" the values of the control, without doing goofy things like offsetting pivots or whatever it is that freezeTransforms does (I forget exactly).

If you look at my earlier post, that's the purpose of the "_Zero" nodes.

I'll try taking a look at your ik/fk arm problem over the weekend.

03 March 2009, 07:33 AM
Again, some more good pointers! I like to keep things organized and my scripts directory is looking pretty crazy right now. I'm going to start making some sub folders for organization's sake.

I always freeze transforms on control objects (curves) but maybe I'll stop doing that if it help things to work more consistently in Maya. The technique that I mentioned previously for FK IK matching came from a video tutorial I watched. I'm not actually working on my own character yet but I know that it's a viable solution, obviously not the only one. Don't go out of your way to work on alternatives if you've got other things going on. I just stated the technique to explain the wackiness of my prop's setup.


EDIT: I forgot to mention that I got the windmill working! I used a scriptJob just prove I could do it, not in defiance of external scripts. ;) Check it out:

CGTalk Moderation
03 March 2009, 07:33 AM
This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.