Home Website Youtube GitHub

Fixing IK/FK hand orient in post

Hi! This is my first post here. I’m not sure I used the correct tag—sorry about that (and also for my English :sweat_smile:).

I’ve been searching and reading threads about this issue but couldn’t find a complete fix. While the rotation matches at the neutral pose, when you reset controls or switch spaces the IK still flips or rotates incorrectly. So I used @Andy’s script as a starting point to figure out where the problem begins:

The thing is, changing controls to match rotations isn’t best practice—you end up with extra rotation values, mismatched spaces, and so on. That’s why I went for a root‑level fix. It appears the actual root group isn’t aligned with the guide’s rotation (not sure if it’s a node or transform issue). I adjusted the script to work with that group: it aligns the FK control’s rotation and updates the parentConstraint so spaces work correctly.

Here’s the script:

import maya.cmds as cmds
import pymel.core as pm
import mgear.shifter.custom_step as cstp

"""
Based in @Andy script from mGear forums
http://forum.mgear-framework.com/t/unable-to-mirror-ik-hand-rotation-on-epic-arm-component/3514/3 
"""

class CustomShifterStep(cstp.customShifterMainStep):
    """Custom Step description
    """

    def setup(self):
        self.name = "HandIKAlign"

    def run(self):
        for side in 'LR':
            self.fix_ik_hand_orientation(side)

    def zero_OPM(self, obj):
        joints = obj
        aux = pm.group(em=True)
        for jnt in joints:
            aux.setMatrix(jnt.getMatrix(ws=True), ws=True)
            pm.parent(aux, jnt.getParent())

            aux.matrix >> jnt.offsetParentMatrix

            pm.xform(jnt, t=[0, 0, 0], ro=[0, 0, 0], s=[1, 1, 1])
            if pm.objectType(jnt, isType='joint'):
                jnt.jointOrient.set([0, 0, 0])
            aux.matrix // jnt.offsetParentMatrix
        pm.delete(aux)

    def fix_ik_hand_orientation(self, side):

        handRef = pm.PyNode('arm_{}0_fk2_ctl'.format(side))  # locate FK hand control
        ikCns = pm.PyNode('arm_{}0_ik_cns'.format(side))  # locate ik_cns group root
        ikCtrl = pm.PyNode('arm_{}0_ik_ctl'.format(side))  # locate IK hand control
        guideRef = pm.PyNode('clavicle_{}0_tip|arm_{}0_root'.format(side, side))  # locate arm guide root
        handChildren = ikCtrl.getChildren(type='transform')
        cnsNode = pm.PyNode('arm_{}0_ik_cns_parentConstraint1'.format(side))  # get parentConstraint node
        cnsTargets = cmds.parentConstraint(str(cnsNode), q=True, tl=True)  # get parentConstraint targets
        cmds.setAttr(str(ikCns) + '.rotateX', lock=0)  # unlock ik_cns group rotation attributes
        cmds.setAttr(str(ikCns) + '.rotateY', lock=0)
        cmds.setAttr(str(ikCns) + '.rotateZ', lock=0)
        cmds.matchTransform(str(ikCns), str(handRef), rot=True)  # match ik_cns group rot to FK_hand_ctl

        # adding rot offset to ik_cns group

        xRot = cmds.getAttr(str(ikCns) + '.rotateX')

        if guideRef + ".mirrorIK":
            if side == "L":
                cmds.setAttr(str(ikCns) + '.rotateX', xRot + 90)
            else:
                cmds.setAttr(str(ikCns) + '.rotateX', xRot + -90)
        else:
            cmds.setAttr(str(ikCns) + '.rotateX', xRot + 90)

        for eachTarget in cnsTargets:
            cmds.parentConstraint(eachTarget, str(cnsNode), edit=True, maintainOffset=True)

        obj = [ikCns, ikCtrl]

        # reset ik_hand_ctl rot
        cmds.setAttr(str(ikCtrl) + '.rotateX', 0)
        cmds.setAttr(str(ikCtrl) + '.rotateY', 0)
        cmds.setAttr(str(ikCtrl) + '.rotateZ', 0)

        # locking ik_cns group rot attributes
        cmds.setAttr(str(ikCns) + '.rotateX', lock=1)
        cmds.setAttr(str(ikCns) + '.rotateY', lock=1)
        cmds.setAttr(str(ikCns) + '.rotateZ', lock=1)

        return

I’d prefer to implement this in the build process itself, but at least it works as a post‑build solution. I’ve tested it with EPIC templates and it works fine there, though I haven’t confirmed it works with every rig.

Cheers!

EDIT: Forgot to mention that I’m not used to PyMel, so the code is a bit mixed with cmds.