Seamless IK FK Switch in Maya with Python

EDIT: I would advise against using this exact same script as the parentConstraints have been known to cause issues and are not a graceful solution at all. Until I update the article, either try replacing them with xform calls or have a look at Alessandra’s comment.

I remember the first time I tried to set up a seamless IK FK switch with Python vividly. There was this mechanical EVA suit that I was rigging for a masterclass assignment at uni given by Frontier. The IK to FK switching was trivial and there were not many issues with that, but I had a very hard time figuring out the FK to IK one, as I had no idea what the pole vector really is and also, my IK control was not oriented the same way as my FK one.

Im sure that throughout the web there are many solutions to the problem, but most of the ones I found were in MEL and some of them were a bit unstable, because they were relying too much on the xform command or the rotate one with the ws flag, which I am assuming causes issues sometimes when mapping from world space to relative, where a joint will have the exact same world rotation, so it looks perfectly, but if you blend between IK and FK you can see it shifting and then coming back in place. That’s why I decided to use constraints to achieve my rotations, which seems to be a simple enough and stable solution.

EDIT: It seems like even with constraints it is possible to get that issue in the case where the IK control is oriented differently. What fixes is though is switching back and forth once more.

Here is what we are trying to achieve

Seamless IK FK swich demo

Basically, there is just one command for the seamless IK FK Switch, which detects the current kinematics and switches to the other one maintaining the pose. I have added the button to a custom marking menu for easier access.

So, in order to give you a bit of a better context I have uploaded the example scene that I am using, so you can have a look at the exact structure, but feel free to use your own scene with IK/FK blending setup. The full code (which is very short anyway) is in this gist and there are three scene files in here for each version of our setup. The files contain just a simple IK/FK blending system, on which we can test our matching setup, but with different control orientations.

It is important to understand the limitations of a seamless IK FK switch before we dive in. Mainly, I am talking about the limited rotation of the second joint in the chain, as IK setups allow for rotations only in one axis. What this means is that if we have rotations in multiple axis on our FK control for that middle joint (elbow, knee, etc.) the IK/FK matching will not work properly. All this is due to the nature of inverse kinematics.

Also, for easier explaining I assume we are working on an arm and hand setup, but obviously the same approach would work for any IK/FK chain.

We will consider three cases:
All controls and joints are oriented the same
IK Control oriented in world space
IK Control and IK hand joint both oriented in world

Again, you do not have to use the same file as I do as it is just an example, but it is important to be clear on the existing setup. We assume that we have an arm joint chain – L_arm01_JNT > L_arm02_JNT > L_arm03_JNT and a hand joint chain – L_hand01_JNT > L_hand02_JNT with their correspondent IK and FK chains – L_armIk01_JNT > …, L_armFk01_JNT > …, etc. These two chains are blended via a few blendColors nodes for translate, rotate and scale into the final chain. The blending is controlled by L_armIkFk_CTL.fkIk. Then we have a simple non-stretchy IK setup, but obviously a stretchy one would work in the same way. Lastly, the L_hand01_JNT is point constrained to L_arm03_JNT and we only blend the rotate and scale attributes on it, as otherwise the wrist becomes dislocated during blending, because we are interpolating linearly translation values.

Now that we know what we have to work with, let us get on with it.

Seamless IK FK Switch when everything shares orientation

So, in this case, all of our controls and joints have the exact same orientation in both IK and FK. What this means is that essentially all we need to do to match the kinematics is to just plug the rotations from one setup to the other. Let’s have a look. The scene file for this one is called ikFkSwitch_sameOrient.ma

IK to FK

This one is always the easier setup, as FK controls generally just need to get the same rotation values as the IK joints and that’s it. Now, initially I tried copying the rotation via rotate and xform commands, but whenever a control was rotated a bit too extreme these would cause flipping when blending between IK and FK, which I am assuming is because these commands have a hard time converting the world space rotation to a relative one, causing differences of 360 degrees. So, even though in full FK and full IK everything looks perfect, in-between the joint rotates 360 degrees. Luckily, maya has provided us with constraints which have all the math complexity built in. Assuming you have named your joints the same way as me we use the following code.

mc.delete(mc.orientConstraint("L_armIk01_JNT", "L_armFk01_CTL"))
mc.delete(mc.orientConstraint("L_armIk02_JNT", "L_armFk02_CTL"))
mc.delete(mc.orientConstraint("L_handIk01_JNT", "L_handFk01_CTL"))

mc.setAttr("L_armIkFk_CTL.fkIk", 0)

As I said, this one is fairly trivial. We just orient each of our FK controls to match the rotations of the IK joints. Then in the end we change our blending control to FK to finalize the switch.

FK to IK

Now, this one was a pain the first time I was trying to do it, because I had no idea how pole vectors worked at all. As soon as I understood that all we need to know about them is that they just need to lie on the same plane as the three joints in the chain, it became easy. So essentially, we need to place the IK control on the FK joint to solve the end position. And then to get the elbow (or whatever your mid joint is representing) to match the FK, we just place the pole vector control at the exact location of the corresponding joint in the FK chain. So, we get something like this.

mc.delete(mc.parentConstraint("L_handFk01_JNT", "L_armIk_CTL"))
mc.xform("L_armPv_CTL", t=mc.xform("L_armFk02_JNT", t=1, q=1, ws=1), ws=1)

mc.setAttr("L_armIkFk_CTL.fkIk", 1)

Now even though this does the matching job perfectly it is not great for the animators to have the control snap at the mid joint location as it might go inside the geometry, which is just an unnecessary pain. What we can do is, get the two vectors from arm01 to arm02 and from arm03 to arm02 and use them to offset our pole vector a bit. Here’s the way we do that.

arm01Vec = [mc.xform("L_armFk02_JNT", t=1, ws=1, q=1)[i] - mc.xform("L_armFk01_JNT", t=1, ws=1, q=1)[i] for i in range(3)]
arm02Vec = [mc.xform("L_armFk02_JNT", t=1, ws=1, q=1)[i] - mc.xform("L_armFk03_JNT", t=1, ws=1, q=1)[i] for i in range(3)]

mc.xform("L_armPv_CTL", t=[mc.xform("L_armFk02_JNT", t=1, q=1, ws=1)[i] + arm01Vec[i] * .75 + arm02Vec[i] * .75 for i in range(3)], ws=1)

So, since xform returns lists in order to subtract them to get the vectors we just loop through them and subtract the individual elements. If you are new to the shorter loop form in Python have a look at this. Then once we have the two vectors we add 75% of them to the position of the arm02 FK joint and we arrive at a position slightly offset from the elbow, but still on the same plane, thus the matching is still precise. Then our whole FK to IK code would look like this

mc.delete(mc.parentConstraint("L_handFk01_JNT", "L_armIk_CTL"))

arm01Vec = [mc.xform("L_armFk02_JNT", t=1, ws=1, q=1)[i] - mc.xform("L_armFk01_JNT", t=1, ws=1, q=1)[i] for i in range(3)]
arm02Vec = [mc.xform("L_armFk02_JNT", t=1, ws=1, q=1)[i] - mc.xform("L_armFk03_JNT", t=1, ws=1, q=1)[i] for i in range(3)]

mc.xform("L_armPv_CTL", t=[mc.xform("L_armFk02_JNT", t=1, q=1, ws=1)[i] + arm01Vec[i] * .75 + arm02Vec[i] * .75 for i in range(3)], ws=1)

mc.setAttr("L_armIkFk_CTL.fkIk", 1)

Seamless IK FK switch when the IK control is oriented in world space

Now, in this case, the orientation of the IK control is not the same as the hand01 joint. I think in most cases people go for that kind of setup as it is much nicer for animators to have the world axis to work with in IK. The scene file for this one is called ikFkSwitch_ikWorld.ma.

The IK to FK switch is exactly the same as the previous one, so we will skip it.

FK to IK

So, in order to get this to work, we need to do the same as what we did in the previous case, but introduce an offset for our IK control. How do we get this offset then? Well, since we can apply transformations only on the controls, we need to calculate what rotation we need to apply to that control in order to get the desired rotation. Even though, we can calculate the offsets using maths and then apply them using maths, we might run into the same issue with flipping that I discussed in the previous case. So, instead, a much easier solution, but somewhat dirtier is to create a locator which will act as our dummy object to orient to.

Then, in our case where only the IK control is oriented differently from the joints, what we need to do is create a locator and have it assume the transformation of the IK control. The easiest way would be to just parent it underneath the control and zero out the transformations. Then parent the locator to the L_handFk01_JNT, as that’s the one that we want to match to. Now wherever that handFk01 joint goes, we have the locator parented underneath which shares the same orientation as our IK control. Therefore, just using parentConstraint will give us our matching pose. Assuming the locator is called L_hand01IkOfs_LOC all we do is this.

mc.delete(mc.parentConstraint("L_hand01IkOfs_LOC", "L_armIk_CTL"))

This will get our wrist match the pose perfectly. Then we apply the same code as before to get the pole vector to match as well and set the IK/FK blend attribute to IK.

arm01Vec = [mc.xform("L_armFk02_JNT", t=1, ws=1, q=1)[i] - mc.xform("L_armFk01_JNT", t=1, ws=1, q=1)[i] for i in range(3)]
arm02Vec = [mc.xform("L_armFk02_JNT", t=1, ws=1, q=1)[i] - mc.xform("L_armFk03_JNT", t=1, ws=1, q=1)[i] for i in range(3)]

mc.xform("L_armPv_CTL", t=[mc.xform("L_armFk02_JNT", t=1, q=1, ws=1)[i] + arm01Vec[i] * .75 + arm02Vec[i] * .75 for i in range(3)], ws=1)

mc.setAttr("L_armIkFk_CTL.fkIk", 1)

Seamless IK FK switch when the IK control and joint are both oriented in world space

Now, in this last scenario, we have the handIk01 joint oriented in world space, as well as the control. The reason you would want to do this again is to give the animators the easiest way to interact with the hand. In the previous case, the axis of the IK control do not properly align with the joint which is a bit awkward. So a solution would be to have the handIk01 joint oriented in the same space as our control, so the rotation is 1 to 1 and it is a bit more intuitive. The scene for this one is ikFkSwitch_ikJointWorld.ma and it looks like this.

It is important to note that the IK joint is just rotated to match the position of the control, but the jointOrient attributes are still the same as the FK one and the blend one.

Seamless IK FK Switch with IK control and joint oriented in world space
IK FK Switch with IK control and joint oriented in world space

So again, going from IK to FK is the same as before, we are skipping it. Let us have a look at the FK to IK.

FK to IK

This one is very similar to the previous one, where we have an offset transform object to snap to. The difference is that now instead of having that offset be calculated just from the difference between the IK control and the FK joint, we also need to adjust for the existing rotation of the IK joint as well. So, we start with our locator the same way as before – parent it to the IK control, zero out transformations and parent to the handFk01 joint. And then, the extra step here is to apply the negative rotation of the IK joint to the locator in order to get the needed offset. So, this calculation would look like this.

ikRot = [-1 * mc.xform("L_handIk01_JNT", ro=1 ,q=1)[i] for i in range(3)]
mc.xform("L_hand01IkOfs_LOC", ro=ikRot, r=1)

We just take the rotation of the IK joint and multiply it by -1, which we then apply as a relative rotation to the locator.

And then again, as previously we just apply the pole vector calculation and we’re done.

Conclusion

So, as you can see, scripting a seamless IK FK switch is not really that complicated at all, but if you are trying to figure it out for the first time, without being very familiar with rigging and 3D maths it might be a bit of a pain. Again, if you want to see the full code it is in this gist.

16 Comments on "Seamless IK FK Switch in Maya with Python"


  1. The scripting you discuss… is this incorporated into a Control in the example scenes ? (just wondering where I’d apply the script

    Reply

      1. Hi!

        No scripts are incorporated into the scenes. Everything from the post is supposed to be copied to your script editor, where you can test the code. Ideally, though, when you write something that you would actually use in production, you would have to put it somewhere the animators can easily access it – shelf, marking menu, custom tool or even as a callback to an attribute like Raffaele shows over at http://www.cultofrig.com/.

        As for the menu from the gif, it is just a custom marking menu. Here is my approach to create such menus http://bindpose.com/custom-marking-menu-maya-python/

        Reply

    1. Regarding copying all code the script editor in Maya.. that’s what I did but running the code as a button I created on the custom shelf did nothing… hhmmm ??

      Reply

      1. I’ve downloaded and opened your sample Maya files (the arm setup) & I’ve also copied the Python from gist. setup a custom button in my shelf… I click the button nothing.

        So what am I not getting here ?

        Reply

  2. FALSE ALARM…. I understand now 🙂

    I was running all the code at once…
    I’ve since separate and executed & it works great …
    Thx one again

    Reply

    1. I thought running the whole thing at once should still work. I will have another look. Glad you got it working, though!

      Reply

    2. hello Michael… am so experienced in Maya rigging, and same as you I have the script and the sample files. but sadly I can’t figure out the way to make the script work. i don’t know step by step way to create the rig work. i don’t know if i have to select the fk controls, ik control and lastly the pole vector before running the script.
      any enlightenment will be greatly appreciated. just a few steps i have to do to create the rig. waiting for any help, thanks in advance

      Reply

  3. Hey man, I am trying to build this into a scriptJob and create a script node from it.

    I need to have the IK/FK switch in built in the rig with an attribute ( similar to the IK/FK snapping in animation mentor rigs)

    I can appreciate some help or guidelines

    Mehdi

    Reply

  4. Thanks for the post, this was very clarifying! I am just having a problem now, when I add animation to the controls, the script doesn’t work anymore, I think the parent constraint doesnt work well with keyframing. The only thing that move the the spot when I have keyframes is the elbow polevector, since this is the only one that uses the Xform, and not the constraint method. I tried to force mantain offest off, and that still didn’t work… 🙁

    Does it work for you when you add keyframes?

    Reply

    1. Hi! I used this script for my university short and there weren’t any issues with keyframes, so I am not entirely sure what could be happening here.

      What happens when you run it? Do the controls snap to wrong positions and rotations or do they not move at all? Also, is there any chance there are locked transform values on those controls?

      Reply

      1. I thought it could have been something on my rig, but I just tried on your ikFkSwitch_sameOrient.ma file, and I have the same issue. If you add animation to the controls and use the script, the controls just don’t move at all.

        My workaround to make it work was to not delete the constraint on the same line of code that you create the constraint and add a keyframe on the controls in between the constraint and delete commands.
        Example:
        constraint = cmds.parentConstraint(yadda yadda yadda)
        cmds.setKeyframe(yadda yadda yadda)
        cmds.delete(constraint)

        Setting a keyframe was the crucial part to save the positions of the control. If you delete that constraint, which was created on top of an existing animation, you just lose the position information and it just goes back to where the animation says it should be.

        I also did a test case with just two locators to test:
        1_ Animate loc1 and loc2
        2_ Parent loc1 to loc2
        3_ Look how the control channel change color to green on loc2
        4_ Remove the constraint on loc2
        5_ Watch loc2 pop back to its original position on that frame

        If you add a keyframe to loc2 between steps 3 and 4, when you delete the constraint, it will save the new position.

        I also need to mention that because were are using constraints on this snap method, depending on how your rig is setup, Maya keeps complaining about cycle checks. In my case, I had to turn off the cycle check at the beginning of the code and turn it back on at the end.

        Reply

        1. Sorry for the late reply.

          This is very odd, but I totally see what you mean. I just gave it a quick test and I do get the same result as you, even with auto key turned on, which I thought might be the culprit. That being said, we did make it through a full production with this method, so I am really curious as to why this didn’t become a problem.

          It’s nice to see you have already came up with a solution!

          This script, though, is really not graceful at all when it comes to solving the IK/FK switch as it is a really bad idea to rely on `parentConstraint`s to snap. Ideally, everything should be replaced with either `xform` commands or doing it through the API.

          I hope I can give this article a much needed update soon, but until then I am going to add an edit and point people to your comment.

          Thanks!

          Reply

    2. I had the same problem and realized that when keyframes are already set, the parent constraint does work but once it is removed the values return to the keyframed values, making it seem like nothing happened.

      I solved that by setting a keyframe for the parent constrained item before deleting the parent constraint in the script.

      I don’t know if that is the best way but it works for now. If anyone has better solutions, do share.

      Reply

Leave a Reply

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