Script Description:
This script I wrote for generating control rigs for skinning skeletons. It allows the user to make control chains that are either FK, IK or an FK/IK switch. Once the user has made some controls the second half the UI is for assembling the pieces into a cohesive rig. The user can make a world control and cog control. They can also parent controls together and parent all top level controls to the world control.UI:
The UI for this script is divided into two parts. The top of the UI is for making controls and the bottom is for assembling those controls into a complete rig.
Functions:
In order to build the controls I had to be able to duplicate
joints to be control joints. One of the challenges I ran into with this was
that originally I had been duplicating the parent joint with its children. The
problem was that it meant that lots of extra joints were being created. For
example, if I was trying to make the arm controls the hand would get duplicated
as well. As a solution I ended up duplicating only the parent joint without the
children and then making a list of the duplicated joints. Once I had that list
I parented them together to match the skinning skeleton hierarchy. Giving me
the joints I needed without anything extra.
#duplicates joints based off given joint list def duplicateJoints(self, jointList, prefix): dupJointList = [] #duplicates all joints in joint list without their children and adds them to a list for joint in jointList: dupJoint = pm.duplicate(joint, name = prefix + str(joint), po = True) pm.parent(dupJoint[0], world = True) dupJointList.append(dupJoint) #parents the joints together for i in range(len(dupJointList) -1): prnt = dupJointList[i +1] child = dupJointList[i] self.parentObject(prnt[0], child[0]) #hides the created joints to clean up the viewport pm.hide(dupJointList[len(dupJointList) - 1]) #returns the duplicated joints return dupJointList
One of the more complicated things to write for this script
was the function for making the IK chains. One of the reasons was that IK chains have to be made in a chain of
joints that is at least two joints in length so right off the bat I had to make a
check for if the start joint was equal to the end joint and then give the user
a warning if they were.
The next challenge was figuring out how to place the pole vectors. Ideally they are out in space from the center of the chain. So I gave the user the ability to specify the middle joint in the chain as well as a set of radio buttons to specify if the pole vector should be in front of, behind, to the right of, or to the left of the model. For placing the pole vector I assumed z positive was forward. Then using the world space location of the middle joint along with where the user specified the pole vector should go in relation to the model, I placed the pole vector in the world. Once it's placed I constrain it to the handle with a pole vector constraint.
The other thing I had to solve was the rotation at the end of the IK chain. To do this I looked for the first child joint of the end joint in the chain. Duplicate the child joint to make an IK joint and then make a chain from the end of the IK chain to the child joint. Once the handle is made I parent it to the IK handle for the entire chain thus enabling the IK for the original chain to rotate. Since I can't be sure that there is a child joint if I can't find one I give the user a warning saying that the end of the chain won't be able to rotate. I leave the IK chain in the scene since the translation is still functional and depending on the users needs it might work just fine.
The next challenge was figuring out how to place the pole vectors. Ideally they are out in space from the center of the chain. So I gave the user the ability to specify the middle joint in the chain as well as a set of radio buttons to specify if the pole vector should be in front of, behind, to the right of, or to the left of the model. For placing the pole vector I assumed z positive was forward. Then using the world space location of the middle joint along with where the user specified the pole vector should go in relation to the model, I placed the pole vector in the world. Once it's placed I constrain it to the handle with a pole vector constraint.
The other thing I had to solve was the rotation at the end of the IK chain. To do this I looked for the first child joint of the end joint in the chain. Duplicate the child joint to make an IK joint and then make a chain from the end of the IK chain to the child joint. Once the handle is made I parent it to the IK handle for the entire chain thus enabling the IK for the original chain to rotate. Since I can't be sure that there is a child joint if I can't find one I give the user a warning saying that the end of the chain won't be able to rotate. I leave the IK chain in the scene since the translation is still functional and depending on the users needs it might work just fine.
#makes Ik handle for given start and end joints def makeIk(self, ctrlType): if self.startJoint == self.endJoint: ikChainWarning = QtWidgets.QMessageBox() ikChainWarning.setWindowTitle('Warning') ikChainWarning.setText('Can\'t make an Ik chain one joint in length. Please select two different joints to continue.') ikChainWarning.exec_() #deletes the created control joints that were made by the make controls function ctrlJoint = self.findCtrlJoint(self.startJoint) pm.delete(ctrlJoint) else: #makes joints to be driven by ik controls directly ikJoints = self.duplicateJoints(self.jointList, 'IK_') #for the joints in the list of ikJoints for j in range(len(ikJoints)): #if not being used for an IK/FK switch constrain control joints if self.switch == False: ctrlJoint = self.findCtrlJoint(self.jointList[j]) self.parentConstrainObject(ikJoints[j], ctrlJoint) #makes the Ik handle handle = pm.ikHandle(sj = ikJoints[len(ikJoints) - 1][0], ee = ikJoints[0][0]) #hides the handle to clean up the veiwport pm.hide(handle[0]) #makes a control ctrl = self.getSetPostion((10.0 * self.scaleFactor), 'IK_' + str(self.endJoint), self.endJoint, ctrlType) #hides attributes on the ik control that the user doesn't need attrList = ['scaleX', 'scaleY', 'scaleZ', 'visibility'] for attr in attrList: pm.setAttr(str(ctrl[1][0]) + '.' + attr, k = False, cb = False) #parents the IK handle under the ctrl self.parentObject(ctrl[1][0], handle[0]) #makes a control for the pole vector poleCtrl = self.getSetPostion((5.0 * self.scaleFactor), 'IK' + str(self.middleJoint) + '_Pole_Vector', self.middleJoint, ctrlType) #hides attributes the pole vector control doesn't need for attr in attrList: pm.setAttr(str(poleCtrl[1][0]) + '.' + attr, k = False, cb = False) #finds offset group for the pole vector control children = pm.listRelatives(poleCtrl[0], ad = True) offset = children[len(children)-1] #gets the world space postion of the joint to use for placing the pole vector jointTrans = pm.xform(self.middleJoint, query = True, ws = True, t = True) #determines the x, y and z values to move the pole vector control based off user selection #if the user selected front if self.poleVectorPostion == -2: x = jointTrans[0] y = jointTrans[1] z = (100 * self.scaleFactor) #if the user selected back elif self.poleVectorPostion == -3: x = jointTrans[0] y = jointTrans[1] z = (-100 * self.scaleFactor) #if the user selcted right elif self.poleVectorPostion == -4: x = (-100 * self.scaleFactor) y = jointTrans[1] z = jointTrans[2] #if the user selcted left elif self.poleVectorPostion == -5: x = (100 * self.scaleFactor) y = jointTrans[1] z = jointTrans[2] #if the user didn't make a selection gives a warning else: poleVectorPostionWarning = QtWidgets.QMessageBox() poleVectorPostionWarning.setWindowTitle('Warning') poleVectorPostionWarning.setText('No pole vector postion selected. Please select a pole vector postion to continue.') poleVectorPostionWarning.exec_() #removes non functioning partial rig pm.delete(ikJoints) pm.delete(poleCtrl) pm.delete(ctrl) ctrlJoint = self.findCtrlJoint(self.startJoint) pm.delete(ctrlJoint) return #moves the pole vector pm.xform(offset, ws = True, t = (x,y,z)) #constrains the ik handle to the pole vector pm.poleVectorConstraint(poleCtrl[1][0], handle[0]) #makes a control for the top of the ik chain and constrains the ik chain to it topCtrl = self.getSetPostion((10.0 * self.scaleFactor), 'Ik_' + str(self.startJoint), self.startJoint, ctrlType) self.parentConstrainObject(topCtrl[1][0], ikJoints[len(ikJoints) - 1]) #locks and hides unnecessary attributes from the control for attr in attrList: pm.setAttr(str(topCtrl[1][0]) + '.' + attr, k = False, cb = False) #tries to find a child joint to make a secondary ik handle to enable rotation of the end of the ik chain try: #finds the children of the end joint childJoint = pm.listRelatives(self.endJoint, c = True, type = 'joint')[0] childJointList = [childJoint] #makes an ik joint and parents it to the rest of the ik chain ikChildJoint = self.duplicateJoints(childJointList, 'IK_') self.parentObject(ikJoints[0][0], ikChildJoint[0]) #makes an ik handle and parents it the other handle to enable rotation at the end of the ik chain ikChildHandle = pm.ikHandle(sj = ikJoints[0][0], ee = ikChildJoint[0][0]) self.parentObject(handle[0], ikChildHandle[0]) #hides the ik handles pm.hide(ikChildHandle[0]) #if there are no child joints lets the user know that rotation won't work but leaves the control except: noChildWarning = QtWidgets.QMessageBox() noChildWarning.setWindowTitle('Warning') noChildWarning.setText('No child joints after the end of the IK chain. The control on the end of the chain will not be able to rotate.') noChildWarning.exec_() #returns the list of ik joints and all the controls for use in ik/fk switching function return ikJoints, ctrl, poleCtrl, topCtrl
To help keep making FK chains simple I divided it up into
two functions. One for making the FK controls and one for assembling the chain.
The make control function handles placing the controls, and constraining the FK
joints to them.
The FK chain function calls the make FK controls function the right number of times and parents the controls together to make the chain.
#this function sets up an fk control given a skinning joint def makeFKCtrl(self, skJoint, fkJoint, name, ctrlType): joint = self.findCtrlJoint(skJoint) #if this chain is not for an IK/FK switch parent constrain control joint to the fk joint if self.switch == False: #constrains the control joint to the fk joint self.parentConstrainObject(fkJoint, joint) #makes a control and places it on the fk joint ctrl = self.getSetPostion((10.0 * self.scaleFactor), name, fkJoint, ctrlType) #constrains the fk joint to the fk control self.parentConstrainObject(ctrl[1], fkJoint) #hides unnecessary attributes on the control attrList = ['translateX', 'translateY', 'translateZ', 'scaleX', 'scaleY', 'scaleZ', 'visibility'] for attr in attrList: pm.setAttr(str(ctrl[1][0]) + '.' + attr, k = False, cb = False) return ctrl
The FK chain function calls the make FK controls function the right number of times and parents the controls together to make the chain.
#makes a chain of fk controls for all the specified joint and it's children def makeFKChain(self, ctrlType): #makes joints to be driven by fk controls directly fkJoints = self.duplicateJoints(self.jointList, 'FK_') #ctrlList will contain ctrl and prnt groups #starting with bottom of the chain self.ctrlList = [] #makes controls and adds there prnt groups and ctrls to a list for joint in range(len(fkJoints)): childName = str(fkJoints[joint][0]) childCtrl = self.makeFKCtrl(self.jointList[joint], fkJoints[joint], childName, ctrlType) self.ctrlList.append(childCtrl) #parents the fk controls together for i in range(len(self.ctrlList)-1): prnt = self.ctrlList[i +1] child = self.ctrlList[i] self.parentObject(prnt[1][0], child[0]) return fkJoints
Like the FK function I divided making the IK/FK switch into
two different functions one for making a control to hold the IK/FK switch
attribute and one for connecting attributes and placing the control in the
world. With the function that makes the control I use the setAttr function to lock and hide all the attributes on the control since it's just made for holding the attribute to switch the mode of the joint chain from FK to IK. I then use the addAttr function to add a float attribute to be the IK/FK switch attribute.
I made use of a reverse node that I made with the shadingNode pymel function to make the FK input opposite the IK input since it reverses the value of the input. For example, if the switch attribute is set to 1 the reverse node gives off the value of 0. Once I made one I connected both the visibility of the controls and the weight of the IK and FK joints using the connectAttr function. I connected the IK directly to the switch attribute and the FK to the value from the reverse node. I also place the IK/FK switch on the opposite side of the body from the pole vector so that it would be placed with a predictable pattern.
#makes a control to hold an IK, FK switch def makeIkFkSwitchCtrl(self, name, ctrlType): ctrl = self.makeCtrl((5.0 * self.scaleFactor), name, ctrlType) #gets all the keyable attributes(ones found in channel box) channelAttr = pm.listAttr(ctrl[1][0], k = True) #this locks and hides the attributes for attr in channelAttr: pm.setAttr(ctrl[1][0] + '.' + attr, l = True, k = False, cb = False) #adds attribute for IK/FK switch pm.addAttr(ctrl[1][0], ln = 'IK_FK_Switch', at = 'float', max = 1.0, min = 0.0, k = True) return ctrl
I made use of a reverse node that I made with the shadingNode pymel function to make the FK input opposite the IK input since it reverses the value of the input. For example, if the switch attribute is set to 1 the reverse node gives off the value of 0. Once I made one I connected both the visibility of the controls and the weight of the IK and FK joints using the connectAttr function. I connected the IK directly to the switch attribute and the FK to the value from the reverse node. I also place the IK/FK switch on the opposite side of the body from the pole vector so that it would be placed with a predictable pattern.
#makes an Ik/Fk switch def ikFkSwitch(self, fkJoint, IkJoint, skJoint): #makes a control for the ik/fk switch attribute ctrl = self.makeIkFkSwitchCtrl('Switch', 'Extra') #gets the Ik/Fk switch attribute off the Ik/Fk control switchAttr = pm.listAttr(ctrl[1][0], c = True, k = True)[0] #constrains the rest of the joints and keeps track of them in a list constraintList = [] for j in range(len(fkJoint)): ctrlJoint = self.findCtrlJoint(self.jointList[j]) childC = pm.parentConstraint(fkJoint[j], IkJoint[0][j], ctrlJoint) constraintList.append(childC) #make a reverse node reverseNode = pm.shadingNode('reverse', asUtility = True) #connect the switch attribute to the input of the reverse node pm.connectAttr(ctrl[1][0] + '.' + switchAttr, reverseNode + '.input.inputX') #connect the constraint weights to the fk/ik switch #also connects control visibility to the switch for constraint in range(len(constraintList)): attributes = pm.listAttr(constraintList[constraint], c = True, k = True) pm.connectAttr(ctrl[1][0] + '.' + switchAttr, constraintList[constraint] + '.' + attributes[len(attributes) -1]) pm.connectAttr(reverseNode + '.output.outputX', constraintList[constraint] + '.' + attributes[len(attributes) -2]) pm.connectAttr(reverseNode + '.output.outputX', self.ctrlList[constraint][1][0] + '.visibility') pm.connectAttr(ctrl[1][0] + '.' + switchAttr, IkJoint[constraint + 1][0] + '.visibility') #positions the Ik/Fk switch opposite to the position of the pole vector ctrlJoint = self.findCtrlJoint(skJoint) self.parentConstrainObject(ctrlJoint, ctrl[0]) child = pm.listRelatives(ctrl[0], c = True, type = 'transform') offset = child[0] ctrlJointTrans = pm.xform(ctrlJoint, query = True, ws = True, t = True) #if the user picked front if self.poleVectorPostion == -2: x = ctrlJointTrans[0] y = ctrlJointTrans[1] z = (-20 * self.scaleFactor) #if the user picked back elif self.poleVectorPostion == -3: x = ctrlJointTrans[0] y = ctrlJointTrans[1] z = (20 * self.scaleFactor) #if the user picked right elif self.poleVectorPostion == -4: x = (20 * self.scaleFactor) y = ctrlJointTrans[1] z = ctrlJointTrans[2] #if the user picked left else: x = (-20 * self.scaleFactor) y = ctrlJointTrans[1] z = ctrlJointTrans[2] #places the switch in the world pm.xform(offset, ws = True, t = (x, y, z))
No comments:
Post a Comment