Home Website Youtube GitHub

Need help with custom spine guide ik/fk switching

hello,

I’ve been working on an RFK spine guide and I want the top of the chain (the chest) to be able to switch between local and world space using an orient constraint. Right now I’m just using an orient constraint from applyops but looking at the arm component I can see there is an actual switching system. I’m having trouble figuring out how to use it. Im puttintg my init file below. Thanks.

"""Component cGear Spine module"""
import mgear.pymaya as pm
from mgear.pymaya import datatypes
from mgear.shifter import component
from mgear.core import transform, primitive, vector
from mgear.core import applyop

#this init file is copied from chain_loc_ori_01

##########################################################
# COMPONENT
##########################################################

class Component(component.Main):
    """Shifter component Class"""

    # =====================================================
    # OBJECTS
    # =====================================================
    def addObjects(self):

        self.WIP = self.options["mode"]

        if self.negate and self.settings["overrideNegate"]:
            self.negate = False
            self.n_factor = 1

        if self.settings["overrideNegate"]:
            self.mirror_conf = [0, 0, 1,
                                1, 1, 0,
                                0, 0, 0]
        else:
            self.mirror_conf = [0, 0, 0,
                                0, 0, 0,
                                0, 0, 0]

        self.fk_npo  = []
        self.fk_ctl  = []
        self.rfk_npo = []
        self.ik_npo  = []
        self.ik_ctl  = []

        parent = self.root
        tOld = False
        self.previusTag = self.parentCtlTag

        for i, t in enumerate(self.guide.atra[:-1]):
            t = transform.setMatrixScale(t)
            if self.settings["neutralpose"] or not tOld:
                tnpo = t
            else:
                tnpo = transform.setMatrixPosition(
                    tOld, transform.getPositionFromMatrix(t))

            end_ctl = (i == len(self.guide.atra) - 2)

            #FK controls
            if end_ctl:
                self.end_ik_cns = primitive.addTransform(
                    parent, self.getName("fk%s_ik_cns" % i), tnpo)
                fk_npo = primitive.addTransform(
                    self.end_ik_cns, self.getName("fk%s_npo" % i), tnpo)

            else:
                fk_npo = primitive.addTransform(
                    parent, self.getName("fk%s_npo" % i), tnpo)

            fk_ctl = self.addCtl(
                fk_npo,
                "fk%s_ctl" % i,
                t,
                self.color_fk,
                "circle",
                w=self.size * .4,
                ro=datatypes.Vector([0, 0, 1.5708]),
                tp=self.previusTag,
                mirrorConf=self.mirror_conf)

            #RFK nodes
            if i != 0 and i != len(self.guide.atra) - 2:
                rfk_npo = primitive.addTransform(
                    fk_ctl, self.getName("rfk%s_npo" % i), t)
                ik_npo = primitive.addTransform(
                    rfk_npo, self.getName("ik%s_npo" % i), t)
                self.rfk_npo.append(rfk_npo)
            else:
                ik_npo = primitive.addTransform(
                    fk_ctl, self.getName("ik%s_npo" % i), t)

            ik_ctl = self.addCtl(
                ik_npo,
                "ik%s_ctl" % i,
                t,
                self.color_ik,
                "square",
                w=self.size * .25,
                ro=datatypes.Vector([0, 0, 1.5708]),
                tp=fk_ctl,
                mirrorConf=self.mirror_conf)

            self.fk_npo.append(fk_npo)
            self.fk_ctl.append(fk_ctl)
            self.ik_npo.append(ik_npo)
            self.ik_ctl.append(ik_ctl)

            tOld = t
            self.previusTag = fk_ctl
            parent = fk_ctl

            if self.settings["addJoints"]:
                self.jnt_pos.append([ik_ctl, i, None, False])
                
    # =====================================================
    # ATTRIBUTES
    # =====================================================
    def addAttributes(self):
        self.scale_factor_attrs = []

        #get world positions of base and top
        base_pos = self.fk_ctl[0].getTranslation(space = "world")
        top_pos  = self.fk_ctl[-1].getTranslation(space = "world")
        total = (top_pos - base_pos).length()

        for i, npo in enumerate(self.rfk_npo):
            #get position on chain to calculate scale factor
            npo_pos = npo.getTranslation(space = "world")
            dist_from_base = (npo_pos - base_pos).length()
            scale_factor = dist_from_base/total

            attr = self.addAnimParam(
                "scale_factor_%i" % i,
                "scale_factor_%i" % i,
                "double",
                scale_factor,
                0,
                1
            )
            self.scale_factor_attrs.append(attr)
      
        self.addAnimEnumParam(
            "top_orient_ref",
            "Top Orient",
            0,
            ["local", "world"]
        )


    # =====================================================
    # OPERATORS
    # =====================================================
    def addOperators(self):
        print([m for m in dir(self) if "host" in m.lower() or "ui" in m.lower()])
        #create the rfk system by getting twist between base and top of spine
        #and then distributing through mid segments
        
        #get foward axes (set in guide oiptions)
        axes = ["X", "Y", "Z"]
        spine_axis    = axes[self.settings["spineAxis"]]
        world_forward = axes[self.settings["worldForward"]]
               
        #ID rfk drivers
        base_ctl = self.fk_ctl[0]
        top_ctl = self.fk_ctl[-1]
        
        #get matrices from drivers
        base_dcm = pm.createNode("decomposeMatrix", n = self.getName("base_dcm"))
        top_dcm = pm.createNode("decomposeMatrix", n = self.getName("top_dcm"))   
        pm.connectAttr(base_ctl.worldMatrix[0], base_dcm.inputMatrix)
        pm.connectAttr(top_ctl.worldMatrix[0],  top_dcm.inputMatrix)
            
        #ID mid segments
        mid_segments = len(self.rfk_npo)  
        
        #connect mid segments to rfk solver
        for i, npo in enumerate(self.rfk_npo):
            scale_factor = (i + 1)/(mid_segments + 1)
            base_weight = 1 - scale_factor
            top_weight = scale_factor
            
            #base multiply divide
            base_md = pm.createNode("multiplyDivide", n = self.getName("base_md%s" % i))
            pm.connectAttr(base_dcm.attr("outputRotate%s" % world_forward), base_md.input1X)
            pm.connectAttr(self.scale_factor_attrs[i], base_md.input2X)

            #top multiply divide
            top_md = pm.createNode("multiplyDivide", n = self.getName("top_md%s" % i))
            pm.connectAttr(top_dcm.attr("outputRotate%s" % world_forward), top_md.input1X)
            pm.connectAttr(self.scale_factor_attrs[i], top_md.input2X)
            
            #blender
            blend = pm.createNode("plusMinusAverage", n = self.getName("twist_blend%s" % i))
            blend.operation.set(2)
            pm.connectAttr(base_md.outputX, blend.input1D[1])
            pm.connectAttr(top_md.outputX,  blend.input1D[0])    
            #connect
            pm.connectAttr(blend.output1D, npo.attr("rotate%s" % spine_axis))

        #get world ctl
        global_ctl = pm.ls("global_C0_ctl")[0]

        #orient constrain to global
        ori_cns = applyop.oriCns(global_ctl, self.end_ik_cns, maintainOffset=True)
        pm.setAttr(f"{ori_cns}.{global_ctl.name()}W0", 0)

        #connect attr to weight
        space_attr = self.uihost.attr("spine_top_orient_ref")
        pm.connectAttr(space_attr, f"{ori_cns}.{global_ctl.name()}W0")

                       
    # =====================================================
    # CONNECTOR
    # =====================================================
    def setRelation(self):
        self.relatives["root"] = self.fk_ctl[0]
        self.controlRelatives["root"] = self.fk_ctl[0]
        self.jointRelatives["root"] = 0

        for i in range(0, len(self.fk_ctl) - 1):
            self.relatives["%s_loc" % i] = self.fk_ctl[i + 1]
            self.controlRelatives["%s_loc" % i] = self.fk_ctl[i + 1]
            self.jointRelatives["%s_loc" % i] = i + 1
            self.aliasRelatives["%s_ctl" % i] = i + 1

        self.relatives["%s_loc" % (len(self.fk_ctl) - 1)] = self.fk_ctl[-1]
        self.controlRelatives["%s_loc" % (len(self.fk_ctl) - 1)] = self.fk_ctl[-1]
        self.jointRelatives["%s_loc" % (len(self.fk_ctl) - 1)] = len(self.fk_ctl) - 1
        self.aliasRelatives["%s_loc" % (len(self.fk_ctl) - 1)] = len(self.fk_ctl) - 1

Hope you can figure it out.

An easy workaround hack is to make a Control_01 and add the space switches to it. Then directly constrain your chest to that. You can optionally hide the control_01 as a hidden hook that the animator doesn’t really need to see directly. Put the switch on the chest.

Just one route.

I even cloned control_01 as hook_01 for hidden utility controls that have no icon, no _ctl name, and no set membership by default.