def addMirrorInfo(ctrlName): '''given a ctrlName add mirror info attributes to the ctrlName, so poses can be mirrored. This method needs to do a dot product on each axis and set the mirror info based on if the axes point towards or away. Then it sets the mirror info attr to be 1 or -1 based on that test (per axis). This is used later to mirror poses. Channel box attributes are copied over, and depending on the -1 or 1 they are reversed or straight copied. Mirror Info is added by the base rig class at the very end of building. ''' other = findMirrorCtrl(ctrlName) if not other: return mirrorInfo = [1, 1, 1] oXform = rigmath.Transform(other) cXform = rigmath.Transform(ctrlName) cXform.reflect() cx = cXform.xAxis() cy = cXform.yAxis() cz = cXform.zAxis() ox = oXform.xAxis() oy = oXform.yAxis() oz = oXform.zAxis() if cx.dot(ox) <= 0: mirrorInfo[0] = -1 if cy.dot(oy) <= 0: mirrorInfo[1] = -1 if cz.dot(oz) <= 0: mirrorInfo[2] = -1 if not cmds.objExists(ctrlName + '.mirrorInfo'): cmds.addAttr(ctrlName, ln='mirrorInfo', at='double3', k=False) cmds.addAttr(ctrlName, ln='mirrorInfoX', at='double', parent='mirrorInfo', k=False) cmds.addAttr(ctrlName, ln='mirrorInfoY', at='double', parent='mirrorInfo', k=False) cmds.addAttr(ctrlName, ln='mirrorInfoZ', at='double', parent='mirrorInfo', k=False) cmds.setAttr(ctrlName + '.mirrorInfo', mirrorInfo[0], mirrorInfo[1], mirrorInfo[2])
def getDownAxis(joint): '''given a joint return the axis ('x','y', or 'z') that points towards child. On oriented joints this will be the same as the long axis, but if orientation is not know this function is slower but still produces an accurate result. Joints at the end of the chain will use their parent instead to determine downAxis. Negative axes aren't specified, function just returns x y or z.''' axes = 'xyz' target = cmds.listRelatives(joint, type='joint') if not target: target = cmds.listRelatives(joint, p=True, type='joint') if not target: raise RuntimeError( "no child or parent joints on %s, cannot determine downAxis" % joint) thisVector = rigmath.Vector(joint) thisXform = rigmath.Transform(joint) targVector = rigmath.Vector(target[0]) diff = thisVector - targVector diff.normalize() #compare alignment of local axes of transform to diff vector dots = [] for axis in (thisXform.xAxis(), thisXform.yAxis(), thisXform.zAxis()): dots.append(abs(diff.dot(axis))) return axes[dots.index(max(dots))]
def build(self): #make pyramids for world offsets ctrlXform = mpMath.Transform() ctrlXform.scale(2) zero, c1 = self.addCtrl('01', type='FK', shape='pyramid', parent=self.limbNode, shapeXform=ctrlXform) ctrlXform.scale(0.7) zero, c2 = self.addCtrl('02', type='FK', shape='pyramid', parent=c1, shapeXform=ctrlXform) ctrlXform.scale(0.7) zero, c3 = self.addCtrl('03', type='FK', shape='pyramid', parent=c2, shapeXform=ctrlXform) mpRig.addPickParent(c3, c2) mpRig.addPickParent(c2, c1)
def changeCtrlShape(ctrl, newShape, shapeXform=None, size=1.0, segments=13): '''given a ctrl and a new shape, swap the shape. If shapeXform is given it will override the ctrl's shapeMatrix. ''' newCrv = getCurve(shape=newShape, size=size, segments=segments) if not shapeXform: if cmds.objExists(ctrl + '.shapeMatrix'): shapeXform = rigmath.Transform(cmds.getAttr(ctrl + '.shapeMatrix')) if shapeXform: shapeXform.scale(size) cluster = cmds.cluster(newCrv) xfMatrix = rigmath.Transform(shapeXform) cmds.xform(cluster, ws=True, m=xfMatrix.get()) cmds.delete(newCrv, ch=True) cmds.setAttr(ctrl + '.shapeMatrix', *shapeXform.get(), type='matrix') newShape = cmds.listRelatives(newCrv, s=True)[0] oldShape = cmds.listRelatives(ctrl, s=True)[0] cmds.connectAttr(newShape + ".local", oldShape + ".create", force=True) cmds.delete(cmds.cluster(oldShape)) #force update of crv shape cmds.delete(newCrv)
def snapIKFK(ikctrl): '''Given an ik ctrl, snap the ik to the fk for that limb. Uses messages on the ikctrl to find fk ctrls. pvPosMult: increase distance of poleVector control. Doesn't affect IK solution, just distance. ''' # get all IK ctrls ikCtrlSet = getCtrlSetIK(ikctrl) or [ikctrl] allIKCtrls = cmds.sets(ikCtrlSet, q=True) #loop through twice, first doing end ctrls, then doing aims results = dict() for ctrlName in allIKCtrls: if not cmds.objExists(ctrlName + '.snapParents'): results[ctrlName] = None continue snapParents = cmds.listConnections(ctrlName + '.snapParents', s=1, d=0) or [] #if there is one snap parent this is an end effector ctrl #do a simple snap if len(snapParents) == 1: results[ctrlName] = cmds.xform(snapParents[0], q=True, ws=True, m=True) for ctrlName in allIKCtrls: if not cmds.objExists(ctrlName + '.snapParents'): results[ctrlName] = None continue snapParents = cmds.listConnections(ctrlName + '.snapParents', s=1, d=0) or [] #Use three parents to compute aim position if len(snapParents) == 3: aimV = getAimVector(snapParents[0], snapParents[1], snapParents[2]) aimXform = rigmath.Transform(aimV) results[ctrlName] = aimXform.get() #bug fix for some IK limbs that need multiple snaps to reach the goal #This is because IK hierarchies can be arbitrary, and it's difficult to determine #which controls move others, so this takes an iterative approach for i in range(4): for ctrlName, value in results.iteritems(): if not value: resetCtrl(ctrlName) else: cmds.xform(ctrlName, ws=True, m=value) # Turn on IK limbNode = getLimbNodeShape(allIKCtrls[0]) cmds.setAttr(limbNode + '.' + name.FKIKBLENDATTR, 1)
def makeCtrl(self, startJoint, parent): '''recursive function to build ctrls''' ctrlXform = mpMath.Transform() ctrlXform.scale(0.3, 0.3, 0.3) zero, c1 = self.addCtrl('%02d' % len(self.ctrls), type='FK', shape='box', parent=parent, xform=startJoint, shapeXform=ctrlXform) cmds.parentConstraint(c1, startJoint, mo=True) children = cmds.listRelatives(startJoint, type='joint') or [] for child in children: childZero, childCtrl = self.makeCtrl(child, c1) mpRig.addPickParent(childCtrl, c1) return (zero, c1)
def addCtrl(ctrlname, shape='sphere', size=1.0, segments=13, parent=None, color=None, shapeXform=None, xform=None): '''make a ctrl with a given shape out of a curve, parented under a zero null shape: sphere,cube,circle,cross,pyramid,line,spoon size: defaults to 1.0 segments: used on round shapes, defaults to 13 parent: object to parent under (if any) color: maya color name or color index shapeXform: a matrix or object (uses worldMatrix) to transform ctrl shape with. This is only cosmetic. xform: an object, vector, or matrix to xform the transform and rotationOrder of the ctrl ''' #get shape with name crv = getCurve(shape, size=size, segments=segments) #rename and make zero null crv = cmds.rename(crv, ctrlname) zero = cmds.createNode('transform', n=crv + "_Zero") attr.hideAnimChannels(zero) #lock scale #In the rare cases that a ctrl must be scaled this can simply be unlocked. #Otherwise this saves a lot of headache later IMO. attr.lockAndHide(crv, 's') #parent under zero null so it's local xforms are zero cmds.parent(crv, zero) #set color based on argument, or position (based on name) if not specified if not color: color = getPositionColor(ctrlname) setColor(ctrlname, color) #do transforming (if given) if shapeXform: cluster = cmds.cluster(crv) xfMatrix = rigmath.Transform(shapeXform) cmds.xform(cluster, ws=True, m=xfMatrix.get()) cmds.delete(crv, ch=True) else: shapeXform = rigmath.Transform() if parent: cmds.parent(zero, parent) #store shape transform for later, if needed cmds.addAttr(crv, dt='matrix', ln='shapeMatrix') shapeXform.scale(size) cmds.setAttr(crv + '.shapeMatrix', *shapeXform.get(), type='matrix') #flag setAsCtrl(crv) if xform: #if name of node then math matrix and rotate order if isinstance(xform, str) and cmds.objExists(xform): matchMatrix = cmds.xform(xform, q=True, ws=True, m=True) attr.matchAttr(xform, zero, "rotateOrder") attr.matchAttr(xform, crv, "rotateOrder") #otherwise just match matrix. #wrapping a Transform here for convenience else: try: matchMatrix = rigmath.Transform(xform).get() except RuntimeError: raise RuntimeError( "Couldn't find object or transform %s to match" % xform) cmds.xform(zero, ws=True, m=matchMatrix) return (zero, crv)
def addFKIKChain(self, startJoint, endJoint, localParent, worldParent): '''Create a chain of FK ctrls with a blended IKRP solver. Requires three or more joints in chain. Also creates a "stub" joint with an SC solver at the end of the chain, so that the last joint's rotation is blended between the IK end ctrl and the last FK ctrl properly. - localParent = drives translation of FK chain always, rotation when local space is on. IK ignores. - worldParent = drives rotation when 'world' space is blended on. Drives IK translate and rotate. Returns list of [FkCtrl1,FKCtrl2,...,IKAim,IKEnd] ''' jointList = mpJoint.getJointList(startJoint, endJoint) if len(jointList) < 3: raise RuntimeError('FKIKChain needs at least three joints') fkCtrls = self.addFKChain(startJoint, endJoint, localParent) aimCtrl, endCtrl = self.addIKChain(startJoint, endJoint, worldParent) #lock mid ctrls axes so the FK system can only rotate on one plane. #This is needed so the IK system, which is always on a plane, can snap to the FK system #First find which axis will be locked by checking its local axes against the #normal vector of the chain. The highest dot product is the most parallel. #Note: I may need to lock joint DoF as well, sometimes Maya injects tiny rotation values there #when in IK mode. midJointIdx = int(len(jointList) / 2) midJoint = jointList[midJointIdx] startV = mpMath.Vector(startJoint) midV = mpMath.Vector(midJoint) endV = mpMath.Vector(endJoint) chainMid = midV - startV chainEnd = endV - startV chainMid.normalize() chainEnd.normalize() chainNormal = chainMid.cross(chainEnd) chainNormal.normalize() axes = ['x', 'y', 'z'] for ctrl in fkCtrls[1:-1]: ctrlXform = mpMath.Transform(ctrl) dots = list() dots.append(abs(ctrlXform.xAxis().dot(chainNormal))) dots.append(abs(ctrlXform.yAxis().dot(chainNormal))) dots.append(abs(ctrlXform.zAxis().dot(chainNormal))) del axes[dots.index(max(dots))] for axis in axes: mpAttr.lockAndHide(ctrl, ['r%s' % axis]) #constrain fk ctrls to local/world firstFKZero = cmds.listRelatives(fkCtrls[0], p=True)[0] cmds.parentConstraint(localParent, firstFKZero, mo=True) #make a stub joint and an SCIKsolver #This is using Maya's built in 'ikBlend' blends rotate on the last joint self.name.desc = 'ikStub' cmds.select(cl=True) stubJoint = cmds.joint(n=self.name.get()) cmds.parent(stubJoint, endJoint) stubPos = endV + ((endV - midV) * 0.5) cmds.xform(stubJoint, t=stubPos.get(), ws=True) self.name.desc = 'iKStubHandle' stubHandle, stubEffector = cmds.ikHandle(n=self.name.get(), solver='ikSCsolver', sj=endJoint, ee=stubJoint) self.name.desc = 'stubEffector' stubEffector = cmds.rename(stubEffector, self.name.get()) cmds.parent(stubHandle, self.noXform) cmds.parentConstraint(endCtrl, stubHandle, mo=True) mpCache.flag(stubJoint, False) #don't want stub joints saved in jointSRTs #Construct the blend FKIKblender = self.addAttrLimb(ln=mpName.FKIKBLENDATTR, at='float', min=0, max=1, dv=0, k=True) effector, handle = mpJoint.getIKNodes(endJoint) cmds.connectAttr(FKIKblender, handle + '.ikBlend') cmds.connectAttr(FKIKblender, stubHandle + '.ikBlend') #switch ctrl vis for ctrl in (aimCtrl, endCtrl): shape = cmds.listRelatives(ctrl, s=True)[0] mpAttr.connectWithAdd(FKIKblender, shape + '.v', 0.4999999) for ctrl in fkCtrls: shape = cmds.listRelatives(ctrl, s=True)[0] adder = mpAttr.connectWithAdd(FKIKblender, shape + '.v', -0.4999999) mpAttr.connectWithReverse(adder + '.output', shape + '.v', force=True) #setup IK->FK snapping messages #Since the IK end ctrl and the last FK ctrl can have totally different oris, #make a null matching the IK's ori under the FK ctrl to act as a snap target self.name.desc = 'ikEndSnap' endSnapNull = cmds.group(em=True, n=self.name.get(), p=fkCtrls[-1]) cmds.xform(endSnapNull, ws=True, m=cmds.xform(endCtrl, q=True, ws=True, m=True)) mpRig.addSnapParent(endCtrl, endSnapNull) mpRig.addSnapParent(aimCtrl, fkCtrls[0]) mpRig.addSnapParent(aimCtrl, fkCtrls[1]) mpRig.addSnapParent(aimCtrl, fkCtrls[2]) #cleanup for hideObject in (handle, stubHandle, stubJoint): cmds.setAttr(hideObject + '.v', 0) mpAttr.lockAndHide(hideObject, 'v') fkCtrls.extend([aimCtrl, endCtrl]) return fkCtrls
def build(self): self.addPinParent() self.addAttrLimb(ln='noStretch', at='float', min=0, max=1, dv=0, k=True, s=1) self.addAttrLimb(ln='slideAlong', at='float', min=-1, max=1, dv=0, k=True, s=1) jointList = mpJoint.getJointList(self.startJoint, self.endJoint) if len(jointList) < 2: raise RuntimeError( 'NurbsStrip requires at least 2 joints in chain. Got %s' % len(jointList)) #Create NURBS strip by making curves along joints, and a cross section crv, then extruding crv = mpNurbs.curveFromNodes(jointList) crossCurve = cmds.curve(d=1, p=[(0, 0, -0.5 * self.stripWidth), (0, 0, 0.5 * self.stripWidth)], k=(0, 1)) cmds.select([crossCurve, crv], r=1) surf = cmds.extrude(ch=False, po=0, et=2, ucp=1, fpt=1, upn=1, rotation=0, scale=1, rsp=1)[0] cmds.delete([crv, crossCurve]) self.name.desc = 'driverSurf' surf = cmds.rename(surf, self.name.get()) cmds.parent(surf, self.noXform) #Rebuild strip to proper number of spans cmds.rebuildSurface(surf, ch=0, rpo=1, rt=0, end=1, kr=0, kcp=0, kc=1, sv=self.numSpans, su=0, du=1, tol=0.01, fr=0, dir=2) #make live curve on surface down the middle #this is used later for noStretch curvMaker = cmds.createNode('curveFromSurfaceIso', n=surf + "CurveIso") cmds.setAttr(curvMaker + ".isoparmValue", 0.5) cmds.setAttr(curvMaker + ".isoparmDirection", 1) cmds.connectAttr(surf + ".worldSpace[0]", curvMaker + ".inputSurface") offsetCrvShp = cmds.createNode("nurbsCurve", n=crv + "_driverSurfCrvShape") offsetCrv = cmds.listRelatives(p=1)[0] offsetCrv = cmds.rename(offsetCrv, crv + "_driverSurfCrv") cmds.connectAttr(curvMaker + ".outputCurve", offsetCrvShp + ".create") cmds.parent(offsetCrv, self.noXform) #Measure curve length and divide by start length #to get a normalized value that is useful later to control stretch crvInfo = cmds.createNode('curveInfo', n=offsetCrv + "Info") cmds.connectAttr(offsetCrv + ".worldSpace[0]", crvInfo + ".ic") arcLength = cmds.getAttr(crvInfo + ".al") stretchAmountNode = cmds.createNode('multiplyDivide', n=offsetCrv + "Stretch") cmds.setAttr(stretchAmountNode + ".op", 2) #divide cmds.setAttr(stretchAmountNode + ".input1X", arcLength) cmds.connectAttr(crvInfo + ".al", stretchAmountNode + ".input2X") #Stretch Blender blends start length with current length #and pipes it back into stretchAmoundNode's startLength, so user can turn #stretch behavior on or off stretchBlender = cmds.createNode('blendColors', n=offsetCrv + "StretchBlender") cmds.setAttr(stretchBlender + ".c1r", arcLength) cmds.connectAttr(crvInfo + ".al", stretchBlender + ".c2r") cmds.connectAttr(stretchBlender + ".opr", stretchAmountNode + ".input1X") cmds.connectAttr(self.limbNode + ".noStretch", stretchBlender + ".blender") #attach joints to surface closestPoint = cmds.createNode('closestPointOnSurface', n='tempClose') cmds.connectAttr(surf + ".worldSpace[0]", closestPoint + ".inputSurface") for idx, jnt in enumerate(jointList): self.name.desc = 'jnt%02dOffset' % idx locator = cmds.spaceLocator(n=self.name.get())[0] cmds.setAttr(locator + '.localScale', self.stripWidth, self.stripWidth, self.stripWidth) cmds.parent(locator, self.noXform) #Use closest point to to find jnt's percent along the curve cmds.setAttr(closestPoint + '.ip', *cmds.xform(jnt, q=True, t=True, ws=True)) percentage = cmds.getAttr(closestPoint + '.r.v') #attach to surface posNode, aimCnss, moPath, slider = self.attachObjToSurf( locator, surf, offsetCrv, stretchAmountNode, percentage) cmds.connectAttr(self.limbNode + ".slideAlong", slider + ".i2") cmds.parentConstraint(locator, jnt, mo=True) cmds.delete(closestPoint) #add controls.These drive new joints which skinCluster the NURBS strips stripJoints = [] stripJointParent = cmds.createNode('transform', n=crv + "_stripJoints", p=self.noXform) ctrlParent = cmds.createNode('transform', n=crv + "_Ctrls", p=self.pinParent) cmds.xform(ctrlParent, ws=True, m=[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]) prevCtrl = None for i in range(self.numCtrls): ctrlXform = mpMath.Transform(jointList[0]) zero, ctrl = self.addCtrl('Ctrl%02d' % i, type='FK', shape='box', parent=ctrlParent, xform=ctrlXform) ctrlXform.scale(0.8, 0.8, 0.8) tZero, tCtrl = self.addCtrl('TweakCtrl%02d' % i, type='FK', shape='cross', parent=ctrl, xform=ctrlXform) #Make the new joint for the control to drive cmds.select(clear=True) self.name.desc = 'StripJnt%02d' % i jnt = cmds.joint(p=(0, 0, 0), n=self.name.get()) cmds.parentConstraint(tCtrl, jnt, mo=False) #briefly attach ctrls to strip to align them percentage = float(i) / (self.numCtrls - 1.0) if i > 0 and i < self.numCtrls - 1: percentage = self.uMin + (percentage * (self.uMax - self.uMin)) cmds.delete( self.attachObjToSurf(zero, surf, offsetCrv, stretchAmountNode, percentage)) cmds.parent(jnt, stripJointParent) stripJoints.append(jnt) if prevCtrl: cmds.parent(zero, prevCtrl) mpRig.addPickParent(ctrl, prevCtrl) prevCtrl = ctrl #skin strip to controls #Can get some different behavior by chaning the strip's weights #or perhaps using dual quat. mode on the skinCluster skinObjs = stripJoints + [surf] cmds.skinCluster( skinObjs, bindMethod=0, #closest Point sm=0, #standard bind method ih=True, #ignore hierarchy )
def orientToRot(jnt): '''copy orient to rotation''' origTransform = rigmath.Transform(jnt) cmds.setAttr(jnt + '.jointOrient', 0, 0, 0) cmds.xform(jnt, ws=True, m=origTransform.get())
def build(self): jointList = mpJoint.getJointList(self.startJoint, self.endJoint) if len(jointList) < 5: raise RuntimeError( 'LegFKIK requires at least 5 joints in chain: hip/knee/ankle/ball/toetip. Got %s' % len(jointList)) #start with standard blend setup and FKIKChain self.addPinBlend() legCtrls = self.addFKIKChain(jointList[0], jointList[-3], self.pinBlend, self.pinWorld) #add FK offsets to ball ballFKZero, ballFKCtrl = self.addCtrl('%02d' % len(jointList[:-2]), type='FK', shape='sphere', parent=legCtrls[-3], xform=jointList[-2]) mpAttr.lockAndHide(ballFKCtrl, 't') cmds.orientConstraint(ballFKCtrl, jointList[-2], mo=True) #addFKIKChain returns list with [...,lastFK,IKAim,IKEnd], so grabbing last FK for pick parent: mpRig.addPickParent(ballFKCtrl, legCtrls[-3]) #create classic 'reverse foot' setup if self.heel: heelPos = mpMath.Transform(self.heel) else: RIGLOG.warning( 'No ".heel" joint specified on LegFKIK, guessing pivot') #take the ball->toe length, double it, and go back from the ball that much heelOffset = mpMath.Vector(jointList[-2]) #ball heelOffset -= mpMath.Vector(jointList[-1]) #minus toe heelOffset *= 2 heelOffset + mpMath.Vector(jointList[-2]) #add back to ball heelPos = mpMath.Transform( jointList[-2]) #heel equal ball plus our new vector heelPos += heelOffset #Make ik single chains for ball and toe self.name.desc = 'ikHandleBall' ballHandle, ballEffector = cmds.ikHandle(n=self.name.get(), solver='ikSCsolver', sj=jointList[-3], ee=jointList[-2]) self.name.desc = 'ikHandleToe' toeHandle, toeEffector = cmds.ikHandle(n=self.name.get(), solver='ikSCsolver', sj=jointList[-2], ee=jointList[-1]) self.name.desc = 'ballEffector' ballEffector = cmds.rename(ballEffector, self.name.get()) cmds.parent(ballHandle, self.noXform) self.name.desc = 'toeEffector' toeEffector = cmds.rename(toeEffector, self.name.get()) cmds.parent(toeHandle, self.noXform) #Make foot controls #These transforms are for cosmetic SRT of the foot controls anklePos = mpMath.Vector(jointList[-3]) heelVec = mpMath.Vector(heelPos) footCtrlXform = mpMath.Transform() footCtrlXform.scale(2, 0.4, 4) footCtrlXform.translate(0, heelVec.y - anklePos.y, 0) toeCtrlXform = mpMath.Transform() toeCtrlXform.setFromEuler(0, 90, 0) footZero, footCtrl = self.addCtrl('Foot', shape='cube', type='IK', parent=self.pinWorld, xform=mpMath.Vector(jointList[-3]), shapeXform=footCtrlXform) heelZero, heelCtrl = self.addCtrl('Heel', shape='circle', type='IK', parent=footCtrl, xform=heelPos, shapeXform=toeCtrlXform) toeTipZero, toeTipCtrl = self.addCtrl('ToeTip', shape='circle', type='IK', parent=heelCtrl, xform=jointList[-1], shapeXform=toeCtrlXform) ballZero, ballCtrl = self.addCtrl('Ball', shape='circle', type='IK', parent=toeTipCtrl, xform=jointList[-2], shapeXform=toeCtrlXform) cmds.parentConstraint(ballCtrl, ballHandle, mo=True) cmds.parentConstraint(toeTipCtrl, toeHandle, mo=True) #Blend already exists, but this will grab the right attr FKIKblender = self.addAttrLimb(ln=mpName.FKIKBLENDATTR, at='float', min=0, max=1, dv=0, k=True) cmds.connectAttr(FKIKblender, ballHandle + '.ikBlend') cmds.connectAttr(FKIKblender, toeHandle + '.ikBlend') #constrain legIK endctrl to new foot ctrl ankleIKCtrl = legCtrls[-1] mpAttr.unlockAndShow(ankleIKCtrl, 'r') cmds.parentConstraint(ballCtrl, ankleIKCtrl, mo=True) #swap out foot for old IK handle ctrl #First retrieve effector, handle, aim, and endNull from IK system effector, handle = mpJoint.getIKNodes(jointList[-3]) handleCns = cmds.listConnections(handle + '.tx', s=1, d=0)[0] endNull = cmds.listConnections(handleCns + '.target[0].targetTranslate', s=1, d=0)[0] endNullCns = cmds.listConnections(endNull + '.tx', s=1, d=0)[0] aimCtrl = legCtrls[-2] aimCtrlZero = cmds.listRelatives(aimCtrl, p=True)[0] #delete old aimCtrl blend cns cmds.delete(cmds.listConnections(aimCtrlZero + '.tx', s=1, d=0)[0]) cmds.pointConstraint(self.pinWorld, footCtrl, aimCtrlZero, mo=True) #delete old IK ctrl and wire IK to new foot ctrl cmds.delete(endNullCns) cmds.parentConstraint(ballCtrl, endNull, mo=True) self.deleteCtrl(legCtrls[-1]) #add new pickwalk/snap info mpRig.addPickParent(footCtrl, legCtrls[-2]) mpRig.addPickParent(ballCtrl, footCtrl) mpRig.addPickParent(toeTipCtrl, ballCtrl) mpRig.addPickParent(heelCtrl, toeTipCtrl) #Make some nulls to act as snap targets. This is because IK and FK controls might have different axis order or initial positions. self.name.desc = 'ikAnkleSnapTarget' ikAnkleSnapTarget = cmds.group(em=True, n=self.name.get(), p=legCtrls[-3]) cmds.xform(ikAnkleSnapTarget, ws=True, m=cmds.xform(footCtrl, ws=True, q=True, m=True)) mpRig.addSnapParent(footCtrl, ikAnkleSnapTarget) self.name.desc = 'ikBallSnapTarget' ikBallSnapTarget = cmds.group(em=True, n=self.name.get(), p=ballFKCtrl) cmds.xform(ikBallSnapTarget, ws=True, m=cmds.xform(ballCtrl, ws=True, q=True, m=True)) mpRig.addSnapParent(ballCtrl, ikBallSnapTarget) self.name.desc = 'fkBallSnapTarget' fkBallSnapTarget = cmds.group(em=True, n=self.name.get(), p=ballCtrl) cmds.xform(fkBallSnapTarget, ws=True, m=cmds.xform(ballFKCtrl, ws=True, q=True, m=True)) mpRig.addSnapParent(ballFKCtrl, fkBallSnapTarget) self.name.desc = 'ikToeSnapTarget' ikToeSnapTarget = cmds.group(em=True, n=self.name.get(), p=ballFKCtrl) cmds.xform(ikToeSnapTarget, ws=True, m=cmds.xform(toeTipCtrl, ws=True, q=True, m=True)) mpRig.addSnapParent(toeTipCtrl, ikToeSnapTarget) #add new ctrls to vis switch for ctrl in (footCtrl, heelCtrl, toeTipCtrl, ballCtrl): shape = cmds.listRelatives(ctrl, s=True)[0] mpAttr.connectWithAdd(FKIKblender, shape + '.v', 0.4999999) for ctrl in [ballFKCtrl]: shape = cmds.listRelatives(ctrl, s=True)[0] adder = mpAttr.connectWithAdd(FKIKblender, shape + '.v', -0.4999999) revNode = mpAttr.connectWithReverse(adder + '.output', shape + '.v', force=True) #cleanup for item in [ballHandle, toeHandle, handle]: mpAttr.visOveride(item, 0)
def build(self): jointList = mpJoint.getJointList(self.startJoint, self.endJoint) if len(jointList) < 5: raise RuntimeError( 'LegFKIK requires at least 5 joints in chain: hip/knee/ankle/ball/toetip. Got %s' % len(jointList)) #Internally the dog leg is driven by a 3 joint ik system made here. #This three joint IK has the same total length as hip->ball joint chain. length = mpJoint.getChainLength(jointList[0], jointList[-2]) halfLength = length / 2.0 hip = mpMath.Vector(jointList[0]) knee = mpMath.Vector(jointList[1]) ball = mpMath.Vector(jointList[-2]) legBaseV = ball - hip legBaseMidV = legBaseV * 0.5 binormalLength = -1 * math.sqrt( 0.5 * legBaseV.length()**2 / halfLength**2) #Find the isoceles triange that is coplanar with the leg chain, #and whose tall legs sum to the same length as the dogleg. #The top vertex of this triangle is where we want the mid joint on our #three joint system kneeV = knee - hip kneeV.normalize() ballV = ball - hip ballV.normalize() chainNormal = kneeV.cross(ballV) chainNormal.normalize() chainBinorm = chainNormal.cross(kneeV) chainBinorm *= binormalLength midJointPos = (legBaseMidV + chainBinorm) + hip #make driver joints cmds.select(cl=True) self.name.desc = 'driverJoint01' driverJnt1 = cmds.joint(n=self.name.get(), p=hip.get()) self.name.desc = 'driverJoint02' driverJnt2 = cmds.joint(n=self.name.get(), p=midJointPos.get()) self.name.desc = 'driverJoint03' driverJnt3 = cmds.joint(n=self.name.get(), p=ball.get()) #start with standard blend setup and FKIKChain on hip->ankle self.addPinBlend() legCtrls = self.addFKIKChain(jointList[0], jointList[-3], self.pinBlend, self.pinWorld) ikEnd = legCtrls[-1] effector, handle = mpJoint.getIKNodes(jointList[-3]) #add FK offsets to ball ballFKZero, ballFKCtrl = self.addCtrl('%02d' % len(jointList[:-2]), type='FK', shape='sphere', parent=legCtrls[-3], xform=jointList[-2]) mpAttr.lockAndHide(ballFKCtrl, 't') cmds.orientConstraint(ballFKCtrl, jointList[-2], mo=True) mpRig.addPickParent(ballFKCtrl, legCtrls[-3]) #Make IK Chain on driver joints oldpart = self.name.part self.name.part += "Driver" driverAimCtrl, driverEndCtrl = self.addIKChain(driverJnt1, driverJnt3, self.pinWorld) self.name.part = oldpart #set ctrl flags false on driver ctrls. #This way animators will never touch them and they won't snap/reset/etc. driverEndNull = cmds.listRelatives(driverEndCtrl, p=True)[0] mpCtrl.setAsCtrl(driverEndCtrl, False) mpCtrl.setAsCtrl(driverAimCtrl, False) #and delete shapes so they aren't visible for ctrl in (driverEndCtrl, driverAimCtrl): self.ctrls.remove(ctrl) cmds.delete(cmds.listRelatives(ctrl, s=True)[0]) #make single chain on ankle->ball and ball->tip for ankle roll and toe tap self.name.desc = 'ikHandleBall' ballHandle, ballEffector = cmds.ikHandle(n=self.name.get(), solver='ikSCsolver', sj=jointList[-3], ee=jointList[-2]) self.name.desc = 'ikHandleToe' toeHandle, toeEffector = cmds.ikHandle(n=self.name.get(), solver='ikSCsolver', sj=jointList[-2], ee=jointList[-1]) self.name.desc = 'ballEffector' ballEffector = cmds.rename(ballEffector, self.name.get()) cmds.parent(ballHandle, self.noXform) self.name.desc = 'toeEffector' toeEffector = cmds.rename(toeEffector, self.name.get()) cmds.parent(toeHandle, self.noXform) #Make a ankle roll ctrl and toe tap ctrl #These transforms are for cosmetic SRT of the foot controls footCtrlXform = mpMath.Transform() footCtrlXform.scale(2, 0.4, 4) toeCtrlXform = mpMath.Transform() toeCtrlXform.setFromEuler(0, 90, 0) footZero, footCtrl = self.addCtrl('Foot', shape='cube', type='IK', parent=self.pinWorld, xform=mpMath.Vector(jointList[-2]), shapeXform=footCtrlXform) toeTipZero, toeTipCtrl = self.addCtrl('ToeTip', shape='circle', type='IK', parent=footCtrl, xform=jointList[-1], shapeXform=toeCtrlXform) ballZero, ballCtrl = self.addCtrl('Ball', shape='circle', type='IK', parent=toeTipCtrl, xform=jointList[-2], shapeXform=toeCtrlXform) #parent constrain single chains under new ctrls cmds.parentConstraint(ballCtrl, ballHandle, mo=True) cmds.parentConstraint(toeTipCtrl, toeHandle, mo=True) cmds.parentConstraint(footCtrl, driverEndNull, mo=True) #parent constrain ankle IK to new foot ctrls driverEffector, driverHandle = mpJoint.getIKNodes(driverJnt3) mpAttr.unlockAndShow(ikEnd, 'r') ikEndNull = cmds.listRelatives(ikEnd, p=True)[0] cmds.parentConstraint(ballCtrl, ikEndNull, mo=True) cmds.parentConstraint(toeTipCtrl, driverHandle, mo=True) #parent constrain ball ctrl to driver knee joint cmds.parentConstraint(driverJnt2, ballZero, mo=True) #Blend already exists, but this will grab the right attr FKIKblender = self.addAttrLimb(ln=mpName.FKIKBLENDATTR, at='float', min=0, max=1, dv=0, k=True) cmds.connectAttr(FKIKblender, ballHandle + '.ikBlend') cmds.connectAttr(FKIKblender, toeHandle + '.ikBlend') #add new pickwalk/snap info mpRig.addPickParent(footCtrl, legCtrls[-2]) mpRig.addPickParent(ballCtrl, footCtrl) mpRig.addPickParent(toeTipCtrl, ballCtrl) #Make some nulls to act as snap targets. This is because IK and FK controls might have different axis order or initial positions. self.name.desc = 'ikAnkleSnapTarget' ikAnkleSnapTarget = cmds.group(em=True, n=self.name.get(), p=jointList[-2]) cmds.xform(ikAnkleSnapTarget, ws=True, m=cmds.xform(footCtrl, ws=True, q=True, m=True)) mpRig.addSnapParent(footCtrl, ikAnkleSnapTarget) self.name.desc = 'ikBallSnapTarget' ikBallSnapTarget = cmds.group(em=True, n=self.name.get(), p=ballFKCtrl) cmds.xform(ikBallSnapTarget, ws=True, m=cmds.xform(ballCtrl, ws=True, q=True, m=True)) mpRig.addSnapParent(ballCtrl, ikBallSnapTarget) self.name.desc = 'fkBallSnapTarget' fkBallSnapTarget = cmds.group(em=True, n=self.name.get(), p=jointList[-2]) cmds.xform(fkBallSnapTarget, ws=True, m=cmds.xform(ballFKCtrl, ws=True, q=True, m=True)) mpRig.addSnapParent(ballFKCtrl, fkBallSnapTarget) self.name.desc = 'ikToeSnapTarget' ikToeSnapTarget = cmds.group(em=True, n=self.name.get(), p=ballFKCtrl) cmds.xform(ikToeSnapTarget, ws=True, m=cmds.xform(toeTipCtrl, ws=True, q=True, m=True)) mpRig.addSnapParent(toeTipCtrl, ikToeSnapTarget) #add new ctrls to vis switch for ctrl in (footCtrl, toeTipCtrl, ballCtrl): shape = cmds.listRelatives(ctrl, s=True)[0] mpAttr.connectWithAdd(FKIKblender, shape + '.v', 0.4999999) for ctrl in [ballFKCtrl]: shape = cmds.listRelatives(ctrl, s=True)[0] adder = mpAttr.connectWithAdd(FKIKblender, shape + '.v', -0.4999999) mpAttr.connectWithReverse(adder + '.output', shape + '.v', force=True) #cleanup for item in [ballHandle, toeHandle, handle]: mpAttr.visOveride(item, 0) mpAttr.visOveride(driverJnt1, 0)