Friday, July 13, 2012

Question about tranforms on a frozen object. . .

A fella named Jason emailed me at Vimeo with a question about the IK 2 FK snapping video I did (here):

"I'm a student over at rigging dojo and my mentor turned me towrds your ik fk snapping tool that you created. The script works great. But I would ask how do you handle ctrl curves when they have been say zeroed out at (5,5,5) but when I query there position i get back (0,0,0) and when i apply the proper cordinates to the ctrl curve it goes the right amount but because it was zeroed out it isn't right... Any help would be appreciated"

Thought it was a good question (as I would expect from Rigging Dojo!) and thought it was worth talking about for two reasons: 1) the problem/solution are kind of interesting (if you're into this kinda thing) and 2) it reinforces some stuff I've said in the past, which bears repeating. Namely, you're kinda doing it wrong.

{slight digression (would make it footnote if knew how). . . When I say "wrong" here I'm using the term loosely. That is, you may have valid reasons for doing whatever you're doing and in your case it may make sense, BUT in most ordinary circumstances, you're probably creating more trouble than you're saving. end of digression}

So second things first . . . What is "wrong" and why?:

It has to do with freezing your transforms. And THAT has to do with the Matrix ("whooa . . ."). Each object has a transformation matrix that basically keeps track of where the object is in space, how it's rotated, scaled, etc. I won't make a fool out of you and me by trying to explain it in detail (Hamish Mckenzie has some cool posts on his blog about it), cuz I don't know it that well. But what happens, more or less, when you freeze transforms is that you "reset" the objects matrix (or perhaps more accurately, offset the matrix) and tell it that where it is NOW is at 0, 0, 0.  In the ordinary course of events, that may work, but when you come in with your cheeky little scripts and try to tell it to go somewhere specific, as you've discovered, there's a problem and Maya doesn't know what you mean (or again, to be more precise, Maya knows exactly what's going on, YOU don't know what you mean, or least I don't).
So that's what I mean when I say you're doing it wrong. Freezing transforms on controls is usually NOT the best way to zero them out. A much better way is to "group orient" them (some people call it "group freezing"). I even did a post and a video on the process. In short, you create your control at 0,0,0 and immediately group it. Then take the group and move THAT to the correct location + orientation, either with constraints that you then delete or better yet with "xform" and math and scripts and such. But you get two nice benefits: 1) the control stays at 0,0,0 because it's parented under the group that's doing all the moving and 2) because the control's matrix is then inheriting it's information from the group (which DOES have all the correct info with no offsets), both you and Maya are talking about the same world space, etc. and things like "xform" and "move" and stuff should work fine.

Sooooo. . . what if (as I imagine is the case) you've already done the freezing on your control and don't want to go back. Can you still use the IK2FK python code? The answer is "yes" (though I would recommend fixing the control using group orienting and being done with it. . .).
There are actually a few ways I would imagine. I read somewhere once that Maya stores the freeze transform offset matrix somewhere. I also bet that there's some way to get all smartypants and mathy and solve the problem by busting out some matrix voodoo. But since I can't do that (yet?), I can only offer a solution that will solve the translate problem the same way we did the rest of the script, with a lil elbow grease and vector math. It's not that hard:
Basically, the trick is to find the world space position of the IK control. If you just xform the transform node (the object), it obviously returns a weird value cuz the matrix has been offset. So instead, you need to do something like:
cmds.xform(ctrlName, q=True, rp=True) which will instead use something that's not part of the matrix business (rp is for the rotate pivot point). This will give you the world space of the object. When you compare THAT to the actual value you get from "xform" of the control, it gives you how far "off" you are from Maya's interpretation of the location. You now just need to know where to move it to, and since you've got the offset value, you just need to subtract THAT from the location of the FK ctrl to get the number you'd need to move the transform of the IK ctl. So putting those together and assuming everything else in the script is the same, the section where you move the IK wrist would look like this:

   #snap IK ctrl to wrist position
    trueIKraw = cmds.xform(ikWrist, ws=True, q=True, rp=True)  #get IK pos in true ws
    trueIK = om.MVector(trueIKraw[0], trueIKraw[1],trueIKraw[2]) #make it a vector
    frozenIKraw = cmds.xform(ikWrist, q=True, t=True) #get IK pos via matrix
    frozenIK = om.MVector(frozenIKraw[0], frozenIKraw[1],frozenIKraw[2]) #make vector
    offset = trueIK - frozenIK    #get the offset value for the Ik ctrl
    correctedFw = fw - offset   #this modifies the fk pos to account for the offset
    cmds.move(correctedFw.x, correctedFw.y, correctedFw.z, ikWrist, ws=True)
So all we're doing is figuring out the difference between the world space of the ik ctrl (trueIK) and the values the maya is giving us for the frozen transform (frozenIK) and applying that difference to the fk wrist position to get the final destination of the ik ctrl. This should also work when you HAVEN'T frozen the transforms (since the offset would then be 0).
Whew. Hope that makes sense ...


  1. Thanks for clarifying this.

    I'm still stuck on one thing though. How would you perform the same function for rotations on controls with frozen transformations?

    I'm having a hell of a time getting the rotations of the FK wrist and the IK wrist to match up. The main problem is that the FK wrist control is inheriting it's transforms from the elbow and the shoulder, whereas the IK wrist control is obviously not.

    If I use an orient constraint, I can get them to match up. So, in that sense, I've kind of solved the problem as in the script I can just create an orient constraint to grab the correct rotations and then delete it.

    However, what I'd really love to know is how to get those rotations without using a constraint. I'm fairly certain it involves some crazy matrix math that I've yet to figure out.

    What's confusing me the most is that if I decompose the matrices for the IK and FK wrist controls after using an orient constraint, I see that their world orientations are the same (which makes sense). The part that gets me is that I can't figure out what's creating the offset between their world and local rotations.

    I don't know if I even managed to explain myself well enough to expect an answer, but if you have any thoughts I'd be very grateful.

  2. Benjamin (my middle name, btw)-

    Yeah frozen rotations are harder, fer sure. Again that's why I'd go back and group freeze them properly, if given the opportunity.
    I'd have to look it back up, but as I said, there may be somewhere where the offsets are stored when frozen, not sure.
    Without knowing much about it, I can't say whether decomposing the matrix would even work in that instance. The freezing itself is what's messing up the matching values (one, the ik, is rotated, but thinks it's at 0,0,0). But if the orient constraint works, go with it (I'm not sure why it should work. Seems like it shouldn't unless you just got lucky with your control orientation/postion . . .)
    And if your joints were positioned on top of each other to begin with, then the shoulder, elbow rots should affect both wrists the same way (as they'll both be the same once you get to the wrist).
    Not a clean solution, but you're already cleaning up something that was funky to begin with :)
    Sorry I can't be more help . . .


  3. Thanks for your response. Just thought I'd follow up on my comment.

    I looked and looked for a way apply the rotations using matrix math (on the frozen controls), and couldn't find anything. Ultimately, I just rebuilt the controls using zero groups and the matrix math worked perfectly.

    So, I suppose all that there is to say is that I don't intend to freeze controls anymore.

    Regardless, thank you for the great tutorial, and for taking the time to look at my problem.