Hi! This is my first post here. I’m not sure I used the correct tag—sorry about that (and also for my English ).
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.