FK IK matching (snapping) tool help

Hey!

So this is my first post here! I recently started as technical animator/artist and its quite difficult to get certain information just from the internet. I wanted to inquire from those more experienced how I would specifically, account for a mirror when matching from FK to IK (in this context, matching FK controls to IK joints).

To give some context, this is not a rig I made and is a Maya-based rig. This isn’t a work project, but something I’m kind of keen to know about as I can’t for the life of me solve it.

The rig setup:

The rig is something I haven’t seen before ( I did not create it). The Left and right limb joints are not mirrors of one another, instead they have the same orientations. Instead, to get that mirrored behavior (say, +45 on the Z axis rotates the controls in opposite directions), the controls themselves are mirrored, and the constraints from the FK controls to those un-mirrored FK joints have a constraint offset.

I’ve tried so many different ways to get the matching script to work, I just am unable to. I wanted to ask if there is anything specific I should be accounting for. I’ve supplied below a script that is one of the variations of a solution I tried to make work. (so you can see my approach).

CODE**********************************
hand_node = pm.PyNode(end_ctrl.partialPathName())

##############################################
# In IK Mode.
##############################################

hand_node.ikSwitch.set(1)

ik_joint_parent_offset_m_matrix = Ops.getParentOffsetMatrix(ik_joint)
constraint_matrix = Ops.get_worldMatrix(ik_joint) * ik_joint_parent_offset_m_matrix.inverse()
pre_constraint_matrix = (constraint_matrix.inverse() * ik_joint.inclusiveMatrix())

matrices = {
    "cached_ik_joint": Ops.get_localMatrix(ik_joint),
    "ik_joint_world_matrix": Ops.get_worldMatrix(ik_joint),
    "ik_parent_offset_matrix": ik_joint_parent_offset_m_matrix,
    "constraint_matrix": constraint_matrix,
    "pre_constraint_matrix": pre_constraint_matrix
}

for name, matrix in matrices.items():
    matrices[name] = Ops.get_ik_data(name, matrix)

# get our null
null = pm.PyNode(pm.group(em=True))
pm.matchTransform(null, fk_ctrl.partialPathName())

##############################################
# In FK Mode.
##############################################

hand_node.ikSwitch.set(0)

fk_ctrl_local_matrix = fk_ctrl.inclusiveMatrix() * fk_ctrl.exclusiveMatrixInverse()

# Get the inverse parent offset matrix. If I get this and then the IK local matrix, if its not an
# identity then we're good.

# MQuaternion of fk_ctrl.
fk_ctrl_quaternion = Ops.matrix_to_quaternion(fk_ctrl_local_matrix)
printEulerAnglesFromQuaternion(fk_ctrl_quaternion, name="fk_ctrl quat: ")

# MQuaternion of IK_joint.
ik_joint_quaternion = matrices["cached_ik_joint"]["MQuaternion"]

# r_side
if mirrored:

    fk_ctrl_Euler = fk_ctrl_quaternion.asEulerRotation()
    fk_ctrl_Euler.y = fk_ctrl_Euler.y + 180
    newEuler = om.MEulerRotation(fk_ctrl_Euler.x, fk_ctrl_Euler.y, fk_ctrl_Euler.z)

    fk_ctrl_quaternion.setValue(newEuler)

    # Calculate the offset rotation
    adjusted_rotation = (matrices["ik_joint_world_matrix"]["MQuaternion"]
                         * matrices["ik_parent_offset_matrix"]["MQuaternion"].inverse())

    # Combine the adjusted rotation with the modified FK quaternion
    final_rotation = adjusted_rotation.inverse() * fk_ctrl_quaternion.conjugate()

    printEulerAnglesFromQuaternion(final_rotation, name="final_rotation_quat: ")

    euler_offset = final_rotation.asEulerRotation()
    degrees = [math.degrees(angle) for angle in (euler_offset.x, euler_offset.y, euler_offset.z)]

    cmds.xform(fk_ctrl, objectSpace=True, ro=degrees, relative=True)

# l_side
else:
    quat_offset = ik_joint_quaternion.inverse() * fk_ctrl_quaternion.conjugate()

    printEulerAnglesFromQuaternion(quat_offset, name="quat offset: ")
    euler_offset = quat_offset.asEulerRotation()
    print("euler offset", euler_offset)
    degrees = [math.degrees(angle) for angle in (euler_offset.x, euler_offset.y, euler_offset.z)]

    print(degrees)
    cmds.xform(fk_ctrl, objectSpace=True, ro=degrees, relative=True)

CODE END********

I’m not specifically looking for any code fixes as this code doesn’t really work, but basically suggestions for how to account for the type of mirror I stated earlier.

Thank you and appreciate the time!

I’m not sure I completely understand, but if you need to match against something with an offset orientation I would probably not use euler angles to flip by 180 degrees, since that can introduce gimbaling and end up with the wrong final rotation.

I’d look into either

  • Building a matrix representing the offset between source and destination local orientations, then multiplying that by your world-orientation quaternion to get the final offset orientation.
  • Take your final world orientation as a matrix, decompose that into basis vectors, then manipulate those / flip them / swizzle them however you want and recompose them into a matrix to get your offset world orientation.