def calcOutVector(start, middle, end): ''' Given the lead joint of 3, determine the vector pointing directly away along the xz plane. .. todo:: Gracefully handle if the ik is on the xz plane already. ''' s = dt.Vector(xform(start, q=1, ws=1, t=1)) m = dt.Vector(xform(middle, q=1, ws=1, t=1)) e = dt.Vector(xform(end, q=1, ws=1, t=1)) up = s - e if upAxis(q=True, ax=True) == 'y': kneeScale = (m.y - e.y) / up.y if up.y else 0.0 else: kneeScale = (m.z - e.z) / up.z if up.z else 0.0 modifiedUp = kneeScale * up newPos = modifiedUp + e outFromKnee = m - newPos angleBetween = (m - s).angle(e - m) log.TooStraight.check(angleBetween) outFromKnee.normalize() return outFromKnee
def distanceBetween(a, b): ''' Returns the world space distance between two objects ''' dist = dt.Vector(xform(a, q=True, ws=True, t=True)) - dt.Vector( xform(b, q=True, ws=True, t=True)) return dist.length()
def calcOutVector(start, middle, end): ''' Given the lead joint of 3, determine the vector pointing directly away along the xz plane. .. todo:: Gracefully handle if the ik is on the xz plane already. ''' s = dt.Vector(xform(start, q=1, ws=1, t=1)) m = dt.Vector(xform(middle, q=1, ws=1, t=1)) e = dt.Vector(xform(end, q=1, ws=1, t=1)) up = s - e if upAxis(q=True, ax=True) == 'y': kneeScale = (m.y - e.y) / up.y if up.y else 0.0 else: kneeScale = (m.z - e.z) / up.z if up.z else 0.0 modifiedUp = kneeScale * up newPos = modifiedUp + e outFromKnee = m - newPos outFromKnee.normalize() # If we are halfway to the x/z plane, lerp between the old formula and a new one testUp = dt.Vector(up) if testUp.y < 0: testUp.y *= -1.0 angleToVertical = dt.Vector(0, 1, 0).angle(testUp) if angleToVertical > _45_DEGREES: # Calculate a point perpendicular to the line created by the start and end # going through the middle theta = up.angle(m - e) distToMidpoint = math.cos(theta) * (m - e).length() midPoint = distToMidpoint * up.normal() + e altOutFromKnee = m - midPoint altOutFromKnee.normalize() # lerp between the vectors percent = ( angleToVertical - _45_DEGREES ) / _45_DEGREES # 45 to up axis will favor old, on y axis favors new outFromKnee = slerp(outFromKnee, altOutFromKnee, percent) angleBetween = (m - s).angle(e - m) log.TooStraight.check(angleBetween) outFromKnee.normalize() return outFromKnee
def distanceBetween(a, b): ''' Returns the world space distance between two objects .. todo:: Should this be combined with `measure`? ''' dist = dt.Vector(xform(a, q=True, ws=True, t=True)) - dt.Vector( xform(b, q=True, ws=True, t=True)) return dist.length()
def apply(cls, objects, values, ctrl): pos, rot = cls.split(values) out = util.calcOutVector(pos['hip'], pos['knee'], pos['ankle']) out *= values['length'] pvPos = values['knee'][0] + out util.applyWorldInfo(ctrl, values['matcher']) xform(ctrl.subControl['pv'], ws=True, t=pvPos) # Aim Y at ball matrix = values['ankleMatrix'] bendNormal = dt.Vector(matrix[4:7]) * -1.0 ybasis = (pos['ankle'] - pos['ball']).normal() xbasis = ybasis.cross(bendNormal) zbasis = xbasis.cross(ybasis) if objects['ball'].tx.get() < 0: ybasis *= -1 xbasis *= -1 r = pdil.math.eulerFromMatrix([xbasis, ybasis, zbasis], degrees=True) xform(ctrl.subControl['bend'], ws=True, ro=r)
def split(cls, values): ''' Turns all the `worldInfo` into separate dictionaries. ''' pos, rot = {}, {} for key in cls.WORLD_INFO: pos[key] = dt.Vector(values[key][0]) rot[key] = values[key][1] return pos, rot
def angleBetween(a, mid, c): # Give 3 points, return the angle and axis between the vectors aPos = dt.Vector(xform(a, q=True, ws=True, t=True)) midPos = dt.Vector(xform(mid, q=True, ws=True, t=True)) cPos = dt.Vector(xform(c, q=True, ws=True, t=True)) aLine = midPos - aPos bLine = midPos - cPos aLine.normalize() bLine.normalize() axis = aLine.cross(bLine) if axis.length() > 0.01: return math.degrees(math.acos(aLine.dot(bLine))), axis else: return 0, axis
def findClosest(obj, targets): ''' Given an object or position, finds which of the given targets it is closest to. ''' if isinstance(obj, (PyNode, basestring)): pos = xform(obj, q=True, ws=True, t=True) else: pos = obj dists = [((dt.Vector(xform(t, q=1, ws=1, t=1)) - pos).length(), t) for t in targets] dists.sort() return dists[0][1]
def apply(data, values, ikControl): #_matchIkToChain( ikControl, ikEndJoint, ikControl.subControl['pv'], ikControl.subControl['socket'], endJnt) #_matchIkToChain(ikCtrl, ikJnt, pv, socket, chainEndTarget) # Draw a line from the start to end using the lengths to calc the elbow's projected midpoint startPos = dt.Vector(values['base'][0]) midPos = dt.Vector(values['mid'][0]) endPos = dt.Vector(values['end'][0]) toEndDir = endPos - startPos a = (midPos - startPos).length() b = (endPos - midPos).length() midPoint = startPos + (toEndDir * (a / (a + b))) # The pv direction is from the above projected midpoint to the elbow pvDir = midPos - midPoint pvDir.normalize() armLength = values['armLength'] newPvPos = midPos + pvDir * armLength xform(ikControl.subControl['socket'], ws=True, t=startPos) xform(ikControl, ws=True, t=endPos) #xform( ikControl.subControl['pv'], ws=True, t=newPvPos ) # Not sure how to do the math but this works to properly align the ik tempAligner = group(em=True) tempAligner.t.set(endPos) tempAligner.r.set(xform(ikControl, q=True, ws=True, ro=True)) tempAligner.setParent(data['ikEndJoint']) tempAligner.r.lock() tempAligner.setParent(data['end']) xform(ikControl, ws=True, ro=pdil.dagObj.getRot(tempAligner)) delete(tempAligner) # In case the PV is spaced to the controller, put it back xform(ikControl.subControl['pv'], ws=True, t=newPvPos)
def trueWorldFloorAngle(obj): ''' Only true for Y up, returns the smallest Y axis worldspace angle needed to rotate to be axis aligned. To rotate the object run `rotate([0, a, 0], r=1, ws=1, fo=True)` ''' m = xform(obj, q=True, ws=True, m=True) # The valid axes to check rows = (0, 1, 2) cols = (2, ) hirow = rows[0] hicol = cols[0] highest = m[hirow * 4 + hicol] for col in cols: for row in rows: if abs(m[row * 4 + col]) > abs(highest): highest = m[row * 4 + col] hirow = row hicol = col #print 'col: {} row: {} h: {}'.format(hicol, hirow, highest) # The `col` determines the world axis if hicol == 0: worldAxis = dt.Vector([1.0, 0, 0]) elif hicol == 1: worldAxis = dt.Vector([0, 1.0, 0]) elif hicol == 2: worldAxis = dt.Vector([0, 0, 1.0]) # If the highest is negative, flip it; i.e a local axis closely matched -z if highest < 0: worldAxis *= -1.0 # The `row` determins the local axis if hirow == 0: localAxis = dt.Vector(m[0], 0, m[2]).normal() elif hirow == 1: localAxis = dt.Vector(m[4], 0, m[6]).normal() elif hirow == 2: localAxis = dt.Vector(m[8], 0, m[10]).normal() a = math.degrees(localAxis.angle(worldAxis)) # If the cross in negative, flip the angle if localAxis.cross(worldAxis).y < 0: a *= -1 return a
def spineAlign(spineCard, rotation, threshold=6): ''' Rotates the joints of the card cumulatively to `rotation`, which is spread out proportionally. Joints less than `threshold` from the up axis are not considered. ''' up = dt.Vector(0, 1, 0) spineEnd = spineCard.joints[ -1] # &&& This should validate the end joint is real and not a helper childrenOfSpineCards = [ getRJoint(bpj).getParent() for bpj in spineEnd.proxyChildren ] preserve = { card: pdil.dagObj.getRot(card) for card in childrenOfSpineCards } # Get the positions of the spine joints AND the next joint, since that determines the rotation of the final spine reposeJoints = [getRJoint(j) for j in spineCard.joints] pos = [pdil.dagObj.getPos(rj) for rj in reposeJoints] nextJoint = spineEnd.getOrientState() if nextJoint.joint: pos.append(pdil.dagObj.getPos(getRJoint(nextJoint.joint))) angles = [ math.degrees((child - cur).angle(up)) for cur, child in zip(pos, pos[1:]) ] currentRotations = [pdil.dagObj.getRot(rj) for rj in reposeJoints] adjust = [(i, angle) for i, angle in enumerate(angles) if angle > threshold] total = sum((angle for _, angle in adjust)) for i, angle in adjust: currentRotations[i].x -= rotation * (angle / total) xform(reposeJoints[i], ws=True, ro=currentRotations[i]) for bpj, rot in preserve.items(): xform(bpj, ws=True, ro=rot)
def __load(targetMesh, targetSkeletonRoot): data = _loadTempWeight() if not data: return skiningData = data.values()[0] # Map all the joints in the file to the joints in the targetSkeletonRoot targetJoints = {j: [] for j in listRelatives(targetSkeletonRoot, ad=True, type='joint')} targetJoints[PyNode(targetSkeletonRoot)] = [] #print(skiningData['joints'].values()[0]) sourceTrans = {j: dt.Vector( info[1:4] ) for j, info in skiningData['joints'].items()} targetTrans = {target: target.getTranslation(space='world') for target in targetJoints} #for target in targetJoints: # trans = target.getTranslation(space='world') for srcJ, srcPos in sourceTrans.items(): dists = [((srcPos - tgtPos).length(), tgtJ) for tgtJ, tgtPos in targetTrans.items()] dists.sort() print(srcJ, list((dists))[-3:])
def midAimer(start, end, midCtrl, name='aimer', upVector=None): ''' Creates an object point contrained to two others, aiming at the second. Up vector defaults to the control's Y. ''' aimer = group(em=True, name=name) #aimer.setParent(container) #aimer = polyCone(axis=[1, 0, 0])[0] core.dagObj.moveTo(aimer, midCtrl) pointConstraint(end, start, aimer, mo=True) aimV = dt.Vector(xform(end, q=True, ws=True, t=True)) - dt.Vector( xform(aimer, q=1, ws=1, t=1)) aimV.normalize() if upVector: midCtrlYUp = upVector else: temp = xform(midCtrl, q=True, ws=True, m=True) midCtrlYUp = dt.Vector(temp[4:7]) """ # Generally the X axis is a good default up since things are normally on that plane if abs(aimV[0]) < 0.0001 or min([abs(v) for v in aimV]) == abs(aimV[0]): upV = dt.Vector([-1, 0, 0]) forwardV = aimV.cross(upV) recalcUp = forwardV.cross(aimV) # Reference #xrow = aimV #yrow = recalcUp #zrow = forwardV midCtrlYUp = recalcUp print( 'midCtrlYUp', midCtrlYUp ) else: # Choose Y up as the up (hopefully this works) if abs(aimV[1]) < abs(aimV[0]) and abs(aimV[1]) < abs(aimV[2]): upV = dt.Vector([0, 1, 0]) forwardV = aimV.cross(upV) recalcUp = forwardV.cross(aimV) # Reference #xrow = aimV #yrow = recalcUp #zrow = forwardV midCtrlYUp = recalcUp pass # """ # Determine which axis of the end is closest to the midControl's Y axis. endMatrix = xform(end, q=True, ws=True, m=True) #midMatrix = xform(aimer, q=True, ws=True, m=True) #midCtrlYUp = dt.Vector(midMatrix[4:7]) choices = [ (endMatrix[:3], [1, 0, 0]), ([-x for x in endMatrix[:3]], [-1, 0, 0]), (endMatrix[4:7], [0, 1, 0]), ([-x for x in endMatrix[4:7]], [0, -1, 0]), (endMatrix[8:11], [0, 0, -1]), ([-x for x in endMatrix[8:11]], [0, 0, 1]), ] # Seed with the first choice as the best... low = midCtrlYUp.angle(dt.Vector(choices[0][0])) axis = choices[0][1] # ... and see if any others are better for vector, destAxis in choices[1:]: vector = dt.Vector( vector) # Just passing 3 numbers sometimes gets a math error. if midCtrlYUp.angle(vector) < low: low = midCtrlYUp.angle(vector) axis = destAxis aimConstraint(end, aimer, wut='objectrotation', aim=[1, 0, 0], wuo=end, upVector=[0, 1, 0], wu=axis, mo=False) return aimer
@classmethod def check(cls, jnt): if cls.zero < abs(cmds.xform(str(jnt), q=True, ws=True, t=True)[0]) < cls.tolerance: cls.offcenter.append(jnt) @classmethod def results(cls): if not cls.offcenter: return '' return ('These joints are really close to the center, are they supposed to be offcenter?\n ' + '\n '.join( [str(j) for j in cls.offcenter] ) ) ZERO_VECTOR = dt.Vector(0, 0, 0) class Rotation(Reporter): ''' Controls should only be made on joints that are not rotated, so make sure the are all not rotated. ''' rotatedJoints = [] @classmethod def clear(cls): cls.rotatedJoints = [] @classmethod
def buildIkChain(start, end, pvLen=None, stretchDefault=1, endOrientType=util.EndOrient.TRUE_ZERO, twists={}, makeBendable=False, name='', groupName='', controlSpec={}): ''' :param int pvLen: How far from the center joint to be, defaults to half the length of the chain. .. todo:: * Have fk build as rotate only if not stretchy :param dict twists: Indicates how many twists each section has, ex {1: 2} means joint[1] has 2 twists, which means a 3 joint arm chain becomes shoulder, elbow, twist1, twist2, wrist ''' chain = util.getChain(start, end) # Simplify the names controlChain = util.dupChain(start, end) for j, orig in zip(controlChain, chain): j.rename(util.trimName(orig) + '_proxy') mainJointCount = len(chain) - sum(twists.values()) # Take the linear chain and figure out what are the "main ik", and which # are the twist joints. Also parent the mainArmature as a solo chain for ik application. mainArmature = [] subTwists = {} cur = 0 for i in range(mainJointCount): mainArmature.append(controlChain[cur]) if len( mainArmature ) > 1: # Need to reparent so the 'pivot' joints are independent of the twists if mainArmature[-1].getParent() != mainArmature[ -2]: # ... unless this section has no twists and is already parented. mainArmature[-1].setParent(mainArmature[-2]) cur += 1 if i in twists: subTwists[mainArmature[-1]] = [] for ti in range(twists[i]): subTwists[mainArmature[-1]].append(controlChain[cur]) controlChain[cur].setParent( w=True ) # This ends up being temporary so the ik is applied properly cur += 1 # actual ik node mainIk = ikHandle(sol='ikRPsolver', sj=mainArmature[0], ee=mainArmature[-1])[0] # NOT using Spring because it acts odd. If the pelvis turns, the poleVectors follow it. # Make as RP first so the ik doesn't flip around #PyNode('ikSpringSolver').message >> mainIk.ikSolver # Build the main ik control hide(mainIk) hide(controlChain) if not name: name = util.trimName(start) ctrl = controllerShape.build(name + '_Ik', controlSpec['main'], type=controllerShape.ControlType.IK) container = group(n=name + '_grp') container.setParent(node.mainGroup()) pdil.dagObj.moveTo(ctrl, end) pdil.dagObj.zero(ctrl).setParent(container) # Orient the main ik control if endOrientType == util.EndOrient.TRUE_ZERO: util.trueZeroSetup(end, ctrl) elif endOrientType == util.EndOrient.TRUE_ZERO_FOOT: util.trueZeroFloorPlane(end, ctrl) elif endOrientType == util.EndOrient.JOINT: pdil.dagObj.matchTo(ctrl, end) ctrl.rx.set(util.shortestAxis(ctrl.rx.get())) ctrl.ry.set(util.shortestAxis(ctrl.ry.get())) ctrl.rz.set(util.shortestAxis(ctrl.rz.get())) pdil.dagObj.zero(ctrl) elif endOrientType == util.EndOrient.WORLD: # Do nothing, it's built world oriented pass pdil.dagObj.lock(ctrl, 's') mainIk.setParent(ctrl) # I think orientTarget is for matching fk to ik orientTarget = duplicate(end, po=True)[0] orientTarget.setParent(ctrl) pdil.dagObj.lock(orientTarget) orientConstraint(orientTarget, mainArmature[-1]) hide(orientTarget) pdil.dagObj.lock(mainIk) attr, jointLenMultiplier, nodes = util.makeStretchyNonSpline( ctrl, mainIk, stretchDefault) # &&& Need to do the math for all the # Make the offset joints and setup all the parenting of twists subArmature = [] rotationOffsetCtrls = [] bendCtrls = [] for i, j in enumerate( mainArmature[:-1] ): # [:-1] Since last joint can't logically have twists if makeBendable: j.drawStyle.set( 2 ) # Probably should make groups but not drawing bones works for now. offset = duplicate(j, po=True)[0] offset.setParent(j) offset.rename(pdil.simpleName(j, '{}_Twist')) #subArmature.append(offset) ### OLD if True: ### NEW if not makeBendable: subArmature.append(offset) else: if i == 0: subArmature.append(offset) else: offsetCtrl = controllerShape.build( 'Bend%i' % (len(bendCtrls) + 1), { 'shape': 'band', 'size': 10, 'color': 'green 0.22', 'align': 'x' }) pdil.dagObj.matchTo(offsetCtrl, offset) offsetCtrl.setParent(offset) showHidden(offsetCtrl, a=True) subArmature.append(offsetCtrl) bendCtrls.append(offsetCtrl) rotationOffsetCtrls.append(offset) # &&& Deprectated? attrName = pdil.simpleName(j, '{}_Twist') ctrl.addAttr(attrName, at='double', k=True) ctrl.attr(attrName) >> offset.rx if i in twists: for subTwist in subTwists[j]: subTwist.setParent(j) #subArmature.append(subTwist) ### NEW comment out attrName = pdil.simpleName(subTwist) ctrl.addAttr(attrName, at='double', k=True) ctrl.attr(attrName) >> subTwist.rx if not makeBendable: subArmature.append(subTwist) else: if True: ### NEW offsetCtrl = controllerShape.build( 'Bend%i' % (len(bendCtrls) + 1), { 'shape': 'band', 'size': 10, 'color': 'green 0.22', 'align': 'x' }) pdil.dagObj.matchTo(offsetCtrl, subTwist) offsetCtrl.setParent(subTwist) subTwist.drawStyle.set( 2 ) # Probably should make groups but not drawing bones works fine for now. showHidden(offsetCtrl, a=True) subArmature.append(offsetCtrl) bendCtrls.append(offsetCtrl) #offset.rename( simpleName(j, '{0}_0ffset') ) #for mainJoint, (startSegment, endSegment) in zip( mainArmature, zip( rotationOffsetCtrls, rotationOffsetCtrls[1:] + [mainArmature[-1]] )): # if mainJoint in subTwists: # twistSetup(subTwists[mainJoint], startSegment, endSegment) # Since we don't want twists affecting eachother, base them off the mainArmature if False: ### SKipping this new stuff and resurrecting the old twists for startSegment, endSegment in zip(mainArmature, mainArmature[1:]): #print( 'HAS SUB TWISTS', startSegment in subTwists ) if startSegment in subTwists: twistSetup(ctrl, subTwists[startSegment], startSegment, endSegment, jointLenMultiplier) ''' # Build the groups to hold the twist controls groups = [] for i, (j, nextJ) in enumerate(zip(mainArmature[:-1], mainArmature[1:])): g = group(em=True) parentConstraint(j, g) g.rename( pdil.dagObj.simpleName(g, '{0}_grp') ) groups.append(g) g.setParent(container) if j in subTwists: #totalDist = pdil.dagObj.distanceBetween(j, nextJ) for subTwist in subTwists[j]: dist = pdil.dagObj.distanceBetween(j, subTwist) #disc = 'disc'() disc = controllerShape.build('Twist', {'shape': 'disc', 'align': 'x', 'size': 3}) disc.setParent(g) disc.t.set( 0, 0, 0 ) disc.r.set( 0, 0, 0 ) pdil.dagObj.lock(disc) disc.rx.unlock() disc.tx.unlock() # Manage the lengths of the twist joints and their controls mult = pdil.math.multiply( dist, jointLenMultiplier) mult >> disc.tx mult >> subTwist.tx disc.rx >> subTwist.rx ''' constraints = util.constrainAtoB(chain, subArmature + [mainArmature[-1]]) # PoleVector if not pvLen or pvLen < 0: pvLen = util.chainLength(mainArmature) * 0.5 out = util.calcOutVector(mainArmature[0], mainArmature[1], mainArmature[-1]) pvPos = out * pvLen + dt.Vector( xform(mainArmature[1], q=True, ws=True, t=True)) pvCtrl = controllerShape.build(name + '_pv', controlSpec['pv'], type=controllerShape.ControlType.POLEVECTOR) pdil.dagObj.lock(pvCtrl, 'r s') xform(pvCtrl, ws=True, t=pvPos) controllerShape.connectingLine(pvCtrl, mainArmature[1]) poleVectorConstraint(pvCtrl, mainIk) pdil.dagObj.zero(pvCtrl).setParent(container) # Socket offset control socketOffset = controllerShape.build( name + '_socket', controlSpec['socket'], type=controllerShape.ControlType.TRANSLATE) socketContainer = util.parentGroup(start) socketContainer.setParent(container) pdil.dagObj.moveTo(socketOffset, start) pdil.dagObj.zero(socketOffset).setParent(socketContainer) pdil.dagObj.lock(socketOffset, 'r s') pointConstraint(socketOffset, mainArmature[0]) # Reuse the socketOffset container for the controlling chain mainArmature[0].setParent(socketContainer) # hide( mainArmature[0] ) ''' Currently unable to get this to update, maybe order of operations needs to be enforced? # Add switch to reverse the direction of the bend reverseAngle = controlChain[1].jointOrient.get()[1] * -1.1 ctrl.addAttr( 'reverse', at='short', min=0, max=1, dv=0, k=True ) preferredAngle = pdil.math.condition( ctrl.reverse, '=', 0, 0, reverseAngle ) twist = pdil.math.condition( ctrl.reverse, '=', 0, 0, -180) preferredAngle >> controlChain[1].preferredAngleY twist >> mainIk.twist pdil.math.condition( mainIk.twist, '!=', 0, 0, 1 ) >> mainIk.twistType # Force updating?? ''' if True: # &&& LOCKABLE endToMidDist, g1 = pdil.dagObj.measure(ctrl, pvCtrl, 'end_to_mid') startToMidDist, g2 = pdil.dagObj.measure(socketOffset, pvCtrl, 'start_to_mid') parent(endToMidDist, g1, startToMidDist, g2, container) #ctrl.addAttr( 'lockPV', at='double', min=0.0, dv=0.0, max=1.0, k=True ) #switcher.input[0].set(1) #print('--'* 20) #print(mainArmature) for jnt, dist in zip(mainArmature[1:], [startToMidDist, endToMidDist]): axis = util.identifyAxis(jnt) lockSwitch = jnt.attr('t' + axis).listConnections(s=True, d=False)[0] if jnt.attr('t' + axis).get() < 0: pdil.math.multiply(dist.distance, -1) >> lockSwitch.input[1] else: dist.distance >> lockSwitch.input[1] util.drive(ctrl, 'lockPV', lockSwitch.attributesBlender, 0, 1) """ axis = identifyAxis(mainArmature[-1]) lockSwitchA = mainArmature[-1].attr('t' + axis).listConnections(s=True, d=False)[0] if mainArmature[-1].attr('t' + axis).get() < 0: pdil.math.multiply( endToMidDist.distance, -1) >> lockSwitchA.input[1] else: endToMidDist.distance, -1 >> lockSwitchA.input[1] lockSwitchB = mainArmature[-2].attr('t' + axis).listConnections(s=True, d=False)[0] startToMidDist.distance >> lockSwitchB.input[1] #print(lockSwitchA, lockSwitchB, '-'* 20) drive(ctrl, 'lockPV', lockSwitchA.attributesBlender, 0, 1) drive(ctrl, 'lockPV', lockSwitchB.attributesBlender, 0, 1) """ # Register all the parts of the control for easy identification at other times. ctrl = pdil.nodeApi.RigController.convert(ctrl) ctrl.container = container ctrl.subControl['socket'] = socketOffset for i, bend in enumerate(bendCtrls): ctrl.subControl['bend%i' % i] = bend ctrl.subControl['pv'] = pvCtrl # Add default spaces space.addMain(pvCtrl) #space.add( pvCtrl, ctrl, spaceName=shortName(ctrl, '{0}_posOnly') ) #space.add( pvCtrl, ctrl, spaceName=shortName(ctrl, '{0}_posOnly'), mode=space.TRANSLATE) space.add(pvCtrl, ctrl) space.add(pvCtrl, ctrl, mode=space.Mode.TRANSLATE) return ctrl, constraints
def toObjByCenter(card, otherJoint): pos = xform(card, q=True, ws=True, t=True) dest = xform(otherJoint, q=True, ws=True, t=True) xform(card, ws=True, r=True, t=(dt.Vector(dest) - dt.Vector(pos)))
def to(card, otherJoint): pos = xform(card.start(), q=True, ws=True, t=True) dest = xform(otherJoint, q=True, ws=True, t=True) xform(card, ws=True, r=True, t=(dt.Vector(dest) - dt.Vector(pos)))
def splitCard(tempJoint): ''' Everything after and including the given joint will become a new card. ''' oldCard = tempJoint.cardCon.node() if oldCard.start() == tempJoint: warning('Cannot split at the first joint') return card = makeCard(jointCount=0, size=(1, 1)) newCvs = list(card.cv) newCvs = [newCvs[0], newCvs[2], newCvs[1], newCvs[3]] if oldCard.listRelatives(type='mesh'): points = [ dt.Vector(xform(v, q=True, ws=True, t=True)) for v in oldCard.vtx ] vtx = list(oldCard.vtx) else: points = [ dt.Vector(xform(v, q=True, ws=True, t=True)) for v in oldCard.cv ] points = [points[0], points[2], points[1], points[3] ] # vtx and points must be rearranged in the same way vtx = list(oldCard.cv) vtx = [vtx[0], vtx[2], vtx[1], vtx[3]] midA = (points[0] - points[2]) / 2.0 + points[2] midB = (points[1] - points[3]) / 2.0 + points[3] xform(vtx[0], ws=True, t=midA) xform(vtx[1], ws=True, t=midB) card.setParent(oldCard.getParent()) card.t.set(oldCard.t.get()) card.r.set(oldCard.r.get()) card.s.set(oldCard.s.get()) xform(newCvs[0], ws=True, t=points[0]) xform(newCvs[1], ws=True, t=points[1]) xform(newCvs[2], ws=True, t=midA) xform(newCvs[3], ws=True, t=midB) start, repeat, end = util.parse(oldCard.nameInfo.get()) index = oldCard.joints.index(tempJoint) if index == len(start): # New card is repeat + end oldCard.nameInfo.set(' '.join(start)) card.nameInfo.set(repeat + '* ' + ' '.join(end)) elif index == len(oldCard.joints) - len(end): oldCard.nameInfo.set(' '.join(start) + ' ' + repeat + '*') card.nameInfo.set(' '.join(end)) else: # Terrible split! oldCard.nameInfo.set(' '.join(start) + ' ' + repeat + '*') card.nameInfo.set(repeat + 'X* ' + ' '.join(end)) confirmDialog( m="You are splitting in the repeating Zone, you'll want to fix up names\nAn 'X' has been added to the new cards repeating section" ) card.rename(card.nameInfo.get()) oldCard.rename(oldCard.nameInfo.get()) for j in oldCard.joints[index:]: prevConnection = j.message.listConnections(type=card.__class__, p=1) j.message.disconnect(prevConnection[0]) # Not sure why position is lost but I'm not sure it really matters pos = xform(j, q=True, ws=True, t=True) card.addJoint(j) xform(j, ws=True, t=pos)
def buildDogleg(hipJoint, end, pvLen=None, name='Dogleg', endOrientType=util.EndOrient.TRUE_ZERO_FOOT, groupName='', controlSpec={}): ''' .. todo:: * Specify toe joint instead to remove ambiguity in case of twist joints. * For some reason, sometimes, twist must be introduced because some flippin occurs. For some reason the poleVector doesn't come in straight on. * Need to determine if a 180 twist is needed as the minotaur did. * Need to figure out the best way to constrain the last joint to the controller ''' boundChain = util.getChain(hipJoint, end) container = group(n=name + '_dogHindleg', em=True, p=lib.getNodes.mainGroup()) # &&& I think I want to turn this into the container for all extra stuff related to a given control chainGrp = group(p=container, n=name + "_ikChain", em=True) parentConstraint(hipJoint.getParent(), chainGrp, mo=True) # Make the control to translate/offset the limb's socket. socketOffset = controllerShape.build( name + '_socket', controlSpec['socket'], type=controllerShape.ControlType.TRANSLATE) core.dagObj.lockScale(socketOffset) core.dagObj.lockRot(socketOffset) core.dagObj.moveTo(socketOffset, hipJoint) socketZero = core.dagObj.zero(socketOffset) socketZero.setParent(chainGrp) footCtrl = controllerShape.build(name, controlSpec['main'], type=controllerShape.ControlType.IK) core.dagObj.lockScale(footCtrl) footCtrl.addAttr('bend', at='double', k=True) core.dagObj.moveTo(footCtrl, end) if endOrientType == util.EndOrient.TRUE_ZERO: util.trueZeroSetup(end, footCtrl) elif endOrientType == util.EndOrient.TRUE_ZERO_FOOT: util.trueZeroFloorPlane(end, footCtrl) elif endOrientType == util.EndOrient.JOINT: core.dagObj.matchTo(footCtrl, end) footCtrl.rx.set(util.shortestAxis(footCtrl.rx.get())) footCtrl.ry.set(util.shortestAxis(footCtrl.ry.get())) footCtrl.rz.set(util.shortestAxis(footCtrl.rz.get())) core.dagObj.zero(footCtrl) elif endOrientType == util.EndOrient.WORLD: # Do nothing, it's built world oriented pass util.createMatcher(footCtrl, end).setParent(container) # Make the main ik chain which gives overall compression masterChain = util.dupChain(hipJoint, end) masterChain[0].rename(simpleName(hipJoint, '{0}_OverallCompression')) mainIk = ikHandle(sol='ikRPsolver', sj=masterChain[0], ee=masterChain[-1])[0] PyNode('ikSpringSolver').message >> mainIk.ikSolver mainIk.rename('mainIk') hide(mainIk) springFixup = group(em=True, n='SprinkIkFix') springFixup.inheritsTransform.set(False) springFixup.inheritsTransform.lock() springFixup.setParent(socketOffset) pointConstraint(socketOffset, springFixup) masterChain[0].setParent(springFixup) #pointConstraint( socketOffset, hipJoint ) # Create the polevector. This needs to happen first so things don't flip out later out = util.calcOutVector(masterChain[0], masterChain[1], masterChain[-1]) if not pvLen or pvLen < 0: pvLen = util.chainLength(masterChain[1:]) * 0.5 pvPos = out * pvLen + dt.Vector( xform(boundChain[1], q=True, ws=True, t=True)) pvCtrl = controllerShape.build(name + '_pv', controlSpec['pv'], type=controllerShape.ControlType.POLEVECTOR) core.dagObj.lockScale(pvCtrl) core.dagObj.lockRot(pvCtrl) xform(pvCtrl, ws=True, t=pvPos) poleVectorConstraint(pvCtrl, mainIk) # Verify the knees are in the same place delta = boundChain[1].getTranslation( 'world') - masterChain[1].getTranslation('world') if delta.length() > 0.1: mainIk.twist.set(180) # Make sub IKs so the chain can be offset offsetChain = util.dupChain(hipJoint, end) hide(offsetChain[0]) offsetChain[0].rename('OffsetChain') offsetChain[0].setParent(container) controllerShape.connectingLine(pvCtrl, offsetChain[1]) constraints = util.constrainAtoB(util.getChain(hipJoint, end), offsetChain, mo=False) pointConstraint(masterChain[0], offsetChain[0]) ankleIk = ikHandle(sol='ikRPsolver', sj=offsetChain[0], ee=offsetChain[-2])[0] offsetIk = ikHandle(sol='ikRPsolver', sj=offsetChain[-2], ee=offsetChain[-1])[0] offsetIk.rename('metatarsusIk') offsetControl = group(em=True, n='OffsetBend') offsetContainer = group(offsetControl, n='OffsetSpace') offsetContainer.setParent(footCtrl) # Setup the offsetContainer so it is properly aligned to bend on z offsetContainer.setParent(masterChain[-1]) offsetContainer.t.set(0, 0, 0) #temp = aimConstraint( pvCtrl, offsetContainer, aim=[1, 0, 0], wut='object', wuo=hipJoint, u=[0, 1, 0]) #delete( temp ) ''' NEED TO CHANGE THE ORIENTATION Must perfectly align with ankle segment so the offset ikhandle can translate according to how much things are scaled ''' lib.anim.orientJoint(offsetContainer, boundChain[-2], upTarget=boundChain[-3], aim='y', up='x') #mimic old way lib.anim.orientJoint(offsetContainer, pvCtrl, upTarget=hipJoint, aim='x', up='y') #lib.anim.orientJoint(offsetContainer, pvCtrl, upTarget=hipJoint, aim='x', up='y') offsetControl.t.set(0, 0, 0) offsetControl.t.lock() offsetControl.r.set(0, 0, 0) footCtrl.bend >> offsetControl.rz ''' This is really dumb. Sometimes maya will rotate everything by 180 but I'm not sure how to calculate the proper offset, which normally results in one axis being off by 360, so account for that too. ''' temp = orientConstraint(footCtrl, offsetChain[-1], mo=True) if not core.math.isClose(offsetChain[-1].r.get(), [0, 0, 0]): badVals = offsetChain[-1].r.get() delete(temp) offsetChain[-1].r.set(-badVals) temp = orientConstraint(footCtrl, offsetChain[-1], mo=True) for a in 'xyz': val = offsetChain[-1].attr('r' + a).get() if abs(val - 360) < 0.00001: attr = temp.attr('offset' + a.upper()) attr.set(attr.get() - 360) elif abs(val + 360) < 0.00001: attr = temp.attr('offset' + a.upper()) attr.set(attr.get() + 360) # Hopefully the end of dumbness ankleIk.setParent(offsetControl) # Adjust the offset ikHandle according to how long the final bone is. if masterChain[-1].tx.get() > 0: masterChain[-1].tx >> ankleIk.ty else: core.math.multiply(masterChain[-1].tx, -1.0) >> ankleIk.ty ankleIk.tx.lock() ankleIk.tz.lock() #ankleIk.t.lock() mainIk.setParent(footCtrl) offsetIk.setParent(footCtrl) core.dagObj.zero(footCtrl).setParent(container) hide(masterChain[0], ankleIk, offsetIk) poleVectorConstraint(pvCtrl, ankleIk) poleVectorConstraint(pvCtrl, offsetIk) # Adding the pv constraint might require a counter rotation of the offsetIk counterTwist = offsetChain[-2].rx.get() * ( 1.0 if offsetChain[-2].tx.get() < 0 else -1.0) offsetIk.twist.set(counterTwist) core.dagObj.zero(pvCtrl).setParent(container) # Make stretchy ik, but the secondary chain needs the stretch hooked up too. rig.makeStretchyNonSpline(footCtrl, mainIk) #for src, dest in zip( util.getChain(masterChain, masterEnd)[1:], util.getChain( hipJoint, getDepth(hipJoint, 4) )[1:] ): # src.tx >> dest.tx for src, dest in zip(masterChain[1:], offsetChain[1:]): src.tx >> dest.tx footCtrl = nodeApi.RigController.convert(footCtrl) footCtrl.container = container footCtrl.subControl['pv'] = pvCtrl footCtrl.subControl['socket'] = socketOffset # Add default spaces space.addWorld(pvCtrl) space.add(pvCtrl, footCtrl) space.add(pvCtrl, footCtrl, mode=space.Mode.TRANSLATE) if hipJoint.getParent(): space.add(pvCtrl, hipJoint.getParent()) space.addWorld(footCtrl) space.add(footCtrl, hipJoint.getParent()) return footCtrl, constraints
def buildSquashAndStretch(joints, squashCenter, orientAsParent=True, rangeMin=-5, rangeMax=5, scaleMin=0.5, scaleMax=2, controlSpec={}): ''' :param joints: List of joints that will scale :param squashCenter: The worldspace center point to place the master squash control. :param orientAsParent: Whether the control should be oriented ?? Does this make sense?... Probably not ''' squashCenter = dt.Vector(squashCenter) container = util.parentGroup(joints[0]) container.setParent( node.mainGroup() ) mainCtrl = controllerShape.build( util.trimName(joints[0].getParent()) + "SquashMain_ctrl", controlSpec['main'], type=controllerShape.ControlType.TRANSLATE ) mainCtrl = pdil.nodeApi.RigController.convert(mainCtrl) mainCtrl.setParent(container) mainCtrl.addAttr( 'size', at='double', min=rangeMin, max=rangeMax, dv=0.0, k=True ) pdil.dagObj.lock(mainCtrl, 's') if orientAsParent: pdil.dagObj.matchTo( mainCtrl, joints[0].getParent() ) xform(mainCtrl, ws=True, t=squashCenter) pdil.dagObj.zero(mainCtrl) subControls = [] for i, j in enumerate(joints): subCtrl = controllerShape.build(util.trimName(j) + "_ctrl", controlSpec['manual'], type=controllerShape.ControlType.TRANSLATE ) subControls.append(subCtrl) pdil.dagObj.matchTo(subCtrl, j) subCtrl.setParent(container) pdil.dagObj.zero(subCtrl) pdil.dagObj.lock(subCtrl, 'r s') scalingLoc = spaceLocator() scalingLoc.rename( util.trimName(j) + '_squasher' ) pdil.dagObj.matchTo(scalingLoc, j) hide(scalingLoc) scalingLoc.setParent(mainCtrl) space.add(subCtrl, scalingLoc, 'standard') ctrlPos = dt.Vector(xform(subCtrl, q=True, ws=True, t=True)) setDrivenKeyframe( scalingLoc, at=['tx', 'ty', 'tz'], cd=mainCtrl.size ) mainCtrl.size.set(rangeMin) lower = (ctrlPos - squashCenter) * scaleMin + squashCenter xform(scalingLoc, ws=True, t=lower) setDrivenKeyframe( scalingLoc, at=['tx', 'ty', 'tz'], cd=mainCtrl.size ) mainCtrl.size.set(rangeMax) upper = (ctrlPos - squashCenter) * scaleMax + squashCenter xform(scalingLoc, ws=True, t=upper) setDrivenKeyframe( scalingLoc, at=['tx', 'ty', 'tz'], cd=mainCtrl.size ) mainCtrl.size.set(0.0) xform(scalingLoc, ws=True, t=(ctrlPos)) mainCtrl.subControl[str(i)] = subCtrl constraints = util.constrainAtoB(joints, subControls) mainCtrl.container = container return mainCtrl, constraints
def getPos(obj): ''' Get worldspace position ''' return dt.Vector(xform(obj, q=True, ws=True, t=True))
def buildRibbon(start, end, normal, numControls=3, name='Ribbon', groupName='', controlSpec={}): chain = util.getChain(start, end) controlChain = util.dupChain(start, end) if not name: name = 'Ribbon' container = util.parentGroup(chain[0]) container.setParent(lib.getNodes.mainGroup()) world = group(em=True) hide(world) world.setParent(container) #controlChain[0].setParent( container ) crv = curve(d=1, p=[(0, 0, 0)] * len(chain)) for i, j in enumerate(chain): xform(crv.cv[i], t=xform(j, q=True, ws=True, t=True), ws=True) dup = duplicate(crv)[0] # &&& Obviously need to calc the offset somehow, maybe I can use the mirror state? Maybe not, due to asymmetry #offset = dt.Vector(5, 0, 0) offset = normal crv.t.set(offset) dup.t.set(offset * -1) surfaceTrans = loft(crv, dup, uniform=True, polygon=0, sectionSpans=1, degree=3, autoReverse=True)[0] surfaceTrans.setParent(world) delete(crv, dup) rebuildSurface(surfaceTrans, rebuildType=0, replaceOriginal=True, spansU=1, spansV=(len(chain) - 1) * 2, dir=2, degreeV=3, degreeU=1) hide(surfaceTrans) surfaceShape = surfaceTrans.getShape() closest = createNode('closestPointOnSurface') surfaceShape.worldSpace >> closest.inputSurface vScalar = surfaceShape.minMaxRangeV.get()[1] #constraints = [] for jnt, hairJoint in zip(chain, controlChain): #jnt.setParent(world) follicle = createNode('follicle') hide(follicle) trans = follicle.getParent() #hairJoints.append(trans) trans.setParent(world) pos = jnt.getTranslation(space='world') closest.inPosition.set(pos) surfaceShape.local >> follicle.inputSurface u = closest.parameterU.get() # closestPointOnSurface returns val in relation to the maxV but the follicle needs it normalized. v = closest.parameterV.get() / vScalar follicle.parameterU.set(u) follicle.parameterV.set(v) follicle.outTranslate >> trans.translate follicle.outRotate >> trans.rotate trans.translate.lock() trans.rotate.lock() hairJoint.setParent(trans) constraints = util.constrainAtoB(chain, controlChain) temp = core.capi.asMObject(surfaceShape) nurbsObj = OpenMaya.MFnNurbsSurface(temp.object()) controls = [] controlJoints = [] for i in range(numControls): percent = i / float(numControls - 1) * vScalar p = pointOnSurface(surfaceShape, u=.5, v=percent, p=True) ctrl = controllerShape.build( name + '%i' % (i + 1), controlSpec['main'], type=controllerShape.ControlType.TRANSLATE) ctrl.t.set(p) j = joint(ctrl) hide(j) controlJoints.append(j) ctrl.setParent(container) # Aim the control at the next joint with it's up following the surface if i < numControls - 1: target = chain[i + 1] normal = nurbsObj.normal(0.5, percent) lib.anim.orientJoint(ctrl, target, upVector=dt.Vector(normal.x, normal.y, normal.z)) core.dagObj.zero(ctrl) controls.append(ctrl) # Orient the final control to the final joint core.dagObj.matchTo(controls[-1], chain[-1]) core.dagObj.zero(controls[-1]) skinCluster(surfaceShape, controlJoints, tsb=True) mainCtrl = nodeApi.RigController.convert(controls[0]) mainCtrl.container = container for i, ctrl in enumerate(controls[1:], 1): mainCtrl.subControl[str(i)] = ctrl return mainCtrl, constraints
def orientJoint(jnt, target, upTarget=None, aim='x', up='y', upVector=None): ''' Orient an object (doesn't have to be a joint) to the target. Basically a code only aiming. :param PyNode jnt: The joint to orient :param PyNode target: The object to orient to. :param PyNode/upTarget pos: A PyNode or position [x,y,z] for the up vector :param upVector: If specified, upTarget is not needed :param chr aim: This works for aim=x, up=y and negative versions Things to investigate: * Sometimes the rotations are different but that turns out to be -0.0 in the matrix. AFAIK, everything still ends up fine. # Adept ends up with the same JOs! # Minotaur is the same * It looks like rotate order doesn't matter It's (almost) an all code version of: if isinstance(pos, PyNode): upObj = pos else: upObj = spaceLocator() upObj.t.set( pos ) aim = axisConvert(aim) up = axisConvert(up) # Temporarily unparent children and clear orientation. with lib.core.dagObj.Solo( jnt ): jnt.r.set(0, 0, 0) jnt.jo.set(0, 0, 0) const = aimConstraint( target, jnt, aim=aim, u=up, wut='object', wuo=upObj ) jnt.jo.set( jnt.r.get() ) delete( const ) jnt.r.set(0, 0, 0) def axisConvert( axisChar ): """ Turn a character representing an axis into 3 numbers, ex x = [1,0,0], -y = [0,-1,0] :param char axisChar: Either "x", "y" or "z", possibly negated, eg: "-x" """ axis = [0, 0, 0] c = axisChar[-1] axis[ ord(c) - ord('x') ] = -1 if axisChar.startswith('-') else 1 return axis ''' #print jnt, target, pos, aim, up jPos = dt.Vector(cmds.xform(str(jnt), q=True, ws=True, t=True)) if not isinstance(target, dt.Vector): tPos = dt.Vector(cmds.xform(str(target), q=True, ws=True, t=True)) else: tPos = target if not upVector: if isinstance(upTarget, PyNode): uPos = dt.Vector(cmds.xform(str(upTarget), q=True, ws=True, t=True)) else: uPos = dt.Vector(upTarget) upV = uPos - jPos if up[0] == '-': upV *= -1.0 upV.normalize() else: upV = dt.Vector(upVector) upV.normalize() aimV = tPos - jPos if aim[0] == '-': aimV *= -1.0 aimV.normalize() # The aim/up order determines if it's aim.cross(up) or up.cross(aim) for the final axis if aim[-1] == 'x' and up[-1] == 'y': mainCross = _forwardCross finalCross = _forwardCross elif aim[-1] == 'x' and up[-1] == 'z': mainCross = _reverseCross finalCross = _reverseCross elif aim[-1] == 'y' and up[-1] == 'z': mainCross = _forwardCross finalCross = _forwardCross elif aim[-1] == 'y' and up[-1] == 'x': mainCross = _reverseCross finalCross = _reverseCross elif aim[-1] == 'z' and up[-1] == 'x': mainCross = _forwardCross finalCross = _forwardCross elif aim[-1] == 'z' and up[-1] == 'y': mainCross = _reverseCross finalCross = _reverseCross finalAxis = mainCross(aimV, upV) finalAxis.normalize() # aimV and upV are probably not perpendicular, but finalAxis was built # perpendicular to both so rebuild upV from aimV and finalAxis #newUp = finalAxis.cross(aimV) newUp = finalCross(finalAxis, aimV) newUp.normalize() axes = [None, None, None] if aim[-1] == 'x': axes[0] = list(aimV) + [0.0] elif aim[-1] == 'y': axes[1] = list(aimV) + [0.0] else: axes[2] = list(aimV) + [0.0] if up[-1] == 'x': axes[0] = list(newUp) + [0.0] elif up[-1] == 'y': axes[1] = list(newUp) + [0.0] else: axes[2] = list(newUp) + [0.0] for i, v in enumerate(axes): if not v: axes[i] = list(finalAxis) + [0.0] axes.append(list(jnt.t.get()) + [1.0]) r = core.math.eulerFromMatrix(axes, degrees=True) # Temporarily unparent children and clear orientation. with core.dagObj.TempWorld(jnt): with core.dagObj.Solo(jnt): if jnt.type() == 'joint': jnt.r.set(0, 0, 0) jnt.jo.set(r) else: jnt.r.set(r)
def getRot(obj): ''' Get worldspace rotation ''' return dt.Vector(xform(obj, q=True, ws=True, ro=True))
def determineClosestWorldOrient(obj): ''' Given an object, returns the shortest rotation that aligns the object with the world. This is used to allow IK elements to have world alignment but easily return to the bind pose. ''' ''' # This is essentially a math version of the following: x = spaceLocator() y = spaceLocator() core.dagObj.moveTo( x, obj ) core.dagObj.moveTo( y, obj ) x.tx.set( 1 + x.tx.get() ) y.ty.set( 1 + y.ty.get() ) x.setParent(obj) y.setParent(obj) def zeroSmaller(loc): vals = [abs(v) for v in loc.t.get() ] largetVal = max(vals) index = vals.index(largetVal) for i, attr in enumerate('xyz'): if i == index: continue loc.attr( 't' + attr ).set(0) zeroSmaller( x ) zeroSmaller( y ) ref = spaceLocator() core.dagObj.moveTo( ref, obj ) aimConstraint( x, ref, wut='object', wuo=y ) rot = ref.r.get() delete( x, y, ref ) return rot ''' # Make 2 world spaced points one unit along x and y x = dt.Matrix([(1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (1, 0, 0, 0)]) y = dt.Matrix([(1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 1, 0, 0)]) #z = dt.Matrix( [ (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 1, 0,) ] ) world = obj.worldMatrix.get() inv = world.inverse() # Find the local matrices respective of the obj localX = x * inv localY = y * inv # For X, zero out the two smaller axes for each, ex t=.2, .3, .8 -> t=0, 0, .8 def useX(matrix): return dt.Matrix([ matrix[0], matrix[1], matrix[2], [matrix[3][0], 0, 0, matrix[3][3]] ]) def useY(matrix): return dt.Matrix([ matrix[0], matrix[1], matrix[2], [0, matrix[3][1], 0, matrix[3][3]] ]) def useZ(matrix): return dt.Matrix([ matrix[0], matrix[1], matrix[2], [0, 0, matrix[3][2], matrix[3][3]] ]) xUsed, yUsed, zUsed = [False] * 3 if abs(localX[3][0]) > abs(localX[3][1]) and abs(localX[3][0]) > abs( localX[3][2]): localX = useX(localX) xUsed = True elif abs(localX[3][1]) > abs(localX[3][0]) and abs(localX[3][1]) > abs( localX[3][2]): localX = useY(localX) yUsed = True else: localX = useZ(localX) zUsed = True # Do the same for Y if xUsed: if abs(localY[3][1]) > abs(localY[3][2]): localY = useY(localY) yUsed = True else: localY = useZ(localY) zUsed = True elif yUsed: if abs(localY[3][0]) > abs(localY[3][2]): localY = useX(localY) xUsed = True else: localY = useZ(localY) zUsed = True elif zUsed: if abs(localY[3][0]) > abs(localY[3][1]): localY = useX(localX) xUsed = True else: localY = useY(localY) yUsed = True # Find the 'world' (but treating the obj's pos as the origin) positions. worldX = localX * world worldY = localY * world # Convert this into a rotation matrix by mimicing an aim constraint x = dt.Vector(worldX[-1][:-1]) y = dt.Vector(worldY[-1][:-1]) x.normalize() y.normalize() z = x.cross(y) y = z.cross(x) msutil = maya.OpenMaya.MScriptUtil() mat = maya.OpenMaya.MMatrix() msutil.createMatrixFromList([ x[0], x[1], x[2], 0.0, y[0], y[1], y[2], 0.0, z[0], z[1], z[2], 0.0, 0.0, 0.0, 0.0, 1.0 ], mat) # noqa e123 rot = maya.OpenMaya.MEulerRotation.decompose( mat, maya.OpenMaya.MEulerRotation.kXYZ) return dt.Vector(math.degrees(rot.x), math.degrees(rot.y), math.degrees(rot.z))
def getUpVectors(j): # HACK!!!! Needs to work in surfaceFollow return dt.Vector(0, 0, 1), dt.Vector(0, 0, 1)
def buildBones(cls): # Might need to wrap as single undo() sel = set(util.selectedCards()) if not sel: confirmDialog( m='No cards selected' ) return issues = cls.validateBoneNames(sel) if issues: confirmDialog(m='\n'.join(issues), t='Fix these') return if tpose.reposerExists(): realJoints = [] bindPoseJoints = [] with tpose.matchReposer(cardlister.cardJointBuildOrder()): for card in cardlister.cardJointBuildOrder(): if card in sel: realJoints += card.buildJoints_core(nodeApi.fossilNodes.JointMode.tpose) for card in cardlister.cardJointBuildOrder(): if card in sel: bindPoseJoints += card.buildJoints_core(nodeApi.fossilNodes.JointMode.bind) constraints = [] for bind, real in zip(bindPoseJoints, realJoints): constraints.append( orientConstraint( bind, real ) ) real.addAttr( 'bindZero', at='double3' ) real.addAttr( 'bindZeroX', at='double', p='bindZero' ) real.addAttr( 'bindZeroY', at='double', p='bindZero' ) real.addAttr( 'bindZeroZ', at='double', p='bindZero' ) real.bindZero.set( real.r.get() ) for constraint, real in zip(constraints, realJoints): delete(constraint) real.r.set(0, 0, 0) root = core.findNode.getRoot() topJoints = root.listRelatives(type='joint') for jnt in topJoints: try: index = realJoints.index(jnt) real = jnt bind = bindPoseJoints[index] real.addAttr( 'bindZeroTr', at='double3' ) real.addAttr( 'bindZeroTrX', at='double', p='bindZeroTr' ) real.addAttr( 'bindZeroTrY', at='double', p='bindZeroTr' ) real.addAttr( 'bindZeroTrZ', at='double', p='bindZeroTr' ) real.bindZeroTr.set( dt.Vector(xform(bind, q=True, ws=True, t=True)) - dt.Vector(xform(real, q=True, ws=True, t=True)) ) except ValueError: pass else: # Only build the selected cards, but always do it in the right order. for card in cardlister.cardJointBuildOrder(): if card in sel: card.buildJoints_core(nodeApi.fossilNodes.JointMode.default) select(sel)
def buildSurfaceFollow(joints, groupOrigin, surface=None, controlSpec={}): groupOrigin = dt.Vector(groupOrigin) container = util.parentGroup(joints[0]) container.setParent(lib.getNodes.mainGroup()) mainCtrl = controllerShape.build( util.trimName(joints[0].getParent()) + 'Surface_ctrl', controlSpec['main'], type=controllerShape.ControlType.TRANSLATE) mainCtrl = nodeApi.RigController.convert(mainCtrl) mainCtrl.setParent(container) xform(mainCtrl, ws=True, t=groupOrigin) core.dagObj.lockScale(mainCtrl) core.dagObj.zero(mainCtrl) subControls = [] locs = [] offsets = [] for i, j in enumerate(joints): loc = spaceLocator() locs.append(loc) core.dagObj.matchTo(loc, j) geometryConstraint(surface, loc) objUp, worldObjUp = getUpVectors(j) normalConstraint(surface, loc, wuo=mainCtrl, wut='objectrotation', upVector=objUp, worldUpVector=worldObjUp) offsetCtrl = controllerShape.build( util.trimName(j) + 'Offset_ctrl', controlSpec['offset'], type=controllerShape.ControlType.TRANSLATE) core.dagObj.matchTo(offsetCtrl, loc) offsets.append(offsetCtrl) offsetCtrl.setParent(loc) core.dagObj.zero(offsetCtrl) subCtrl = controllerShape.build( util.trimName(j) + '_ctrl', controlSpec['manual'], type=controllerShape.ControlType.TRANSLATE) subControls.append(subCtrl) core.dagObj.matchTo(subCtrl, loc) subCtrl.setParent(mainCtrl) core.dagObj.zero(subCtrl) pointConstraint(subCtrl, loc) core.dagObj.lockRot(subCtrl) core.dagObj.lockScale(subCtrl) core.dagObj.lockScale(offsetCtrl) loc.setParent(subCtrl) space.add(offsetCtrl, loc, spaceName='surface') mainCtrl.subControl[str(i)] = subCtrl mainCtrl.subControl[str(i) + '_offset'] = offsetCtrl constraints = util.constrainAtoB(joints, offsets) mainCtrl.container = container return mainCtrl, constraints
def buildSplineChest(start, end, name='Chest', indexOfRibCage=-1, useTrueZero=True, groupName='', controlSpec={}): ''' Makes a spline from the start to the `indexOfRibCage` joint, and TODO - the remaining joints get fk controllers (so you can make the spine and neck all one card, I guess) ''' srcChain = util.getChain(start, end) chain = util.dupChain(start, end, '{0}_spline') chestBase = chain[indexOfRibCage] chestIndex = chain.index(chestBase) if chestIndex % 2 == 0: # Due to `division`, have to cast to int midPos = xform(chain[int(chestIndex / 2)], q=True, ws=True, t=True) midRot = xform(chain[int(chestIndex / 2)], q=True, ws=True, ro=True) else: tempIndex = int(math.floor(chestIndex / 2)) low = chain[tempIndex] high = chain[tempIndex + 1] midPos = dt.Vector(xform(low, q=True, ws=True, t=True)) midPos += dt.Vector(xform(high, q=True, ws=True, t=True)) midPos = dt.Vector(midPos) * .5 '''&&& To be safe, find the closest axis on the second obj Get average z basis, forward then average y basis, up calc x, side recalc y, up This is the world matrix of the average rotation''' midRot = xform(low, q=True, ws=True, ro=True) #raise Exception('Need to implement even number of stomach joints') container = group(em=True, p=node.mainGroup(), n=name + "_controls") container.inheritsTransform.set(False) container.inheritsTransform.lock() chain[0].setParent(container) mainIk, _effector, crv = ikHandle(sol='ikSplineSolver', sj=chain[0], ee=chestBase, ns=3, simplifyCurve=False) crvShape = crv.getShape() crvShape.overrideEnabled.set(True) crvShape.overrideDisplayType.set(2) parent(mainIk, crv, container) # -- Base -- # I don't think there is any benefit to controlling this, but it might just be my weighting. base = joint(None, n='Base') pdil.dagObj.moveTo(base, chain[0]) base.setParent(container) parentConstraint(start.getParent(), base, mo=True) hide(base) # -- Chest control -- chestCtrl = controllerShape.build(name + '_main', controlSpec['main'], controllerShape.ControlType.SPLINE) chestCtrl.setParent(container) util.makeStretchySpline(chestCtrl, mainIk) chestCtrl.stretch.set(1) chestCtrl.stretch.lock() chestCtrl.stretch.setKeyable(False) pdil.dagObj.lock(chestCtrl, 's') # Put pivot point at the bottom chestCtrl.ty.set(chestCtrl.boundingBox()[1][1]) pdil.sharedShape.remove(chestCtrl, visNode.VIS_NODE_TYPE) chestCtrl.setPivots([0, 0, 0], worldSpace=True) makeIdentity(chestCtrl, a=True, t=True) pdil.sharedShape.use(chestCtrl, visNode.get()) move(chestCtrl, xform(chestBase, q=True, ws=True, t=True), rpr=True) pdil.dagObj.zero(chestCtrl) if useTrueZero: rot = util.determineClosestWorldOrient(chestBase) util.storeTrueZero(chestCtrl, rot) pdil.dagObj.rezero( chestCtrl ) # Not sure why this is needed but otherwise the translate isn't zeroed chestCtrl.r.set(rot) chest = joint(None, n='Chest') chest.setParent(chestCtrl) pdil.dagObj.moveTo(chest, chestBase) pdil.dagObj.lock(chest) hide(chest) chestMatcher = util.createMatcher(chestCtrl, srcChain[chestIndex]) chestMatcher.setParent(container) # Chest spaces need to happen after it's done being manipulated into place space.add(chestCtrl, start.getParent(), 'local') space.add(chestCtrl, start.getParent(), 'local_posOnly', mode=space.Mode.TRANSLATE) space.addMain(chestCtrl) # Not sure this space is useful... space.addTrueWorld(chestCtrl) space.add(chestCtrl, start.getParent(), 'worldRotate', mode=space.Mode.ALT_ROTATE, rotateTarget=find.mainGroup()) # -- Chest Offset -- &&& Currently hard coded to make a single offset joint chestOffsetCtrl = None if chestIndex < (len(chain) - 1): chestOffsetCtrl = controllerShape.build( name + '_bend', controlSpec['offset'], controllerShape.ControlType.SPLINE) chestOffsetCtrl.setParent(chestCtrl) pdil.dagObj.matchTo(chestOffsetCtrl, chain[-1]) #move(chestOffsetCtrl, [0, 0.7, 3], r=True) pdil.dagObj.zero(chestOffsetCtrl) pdil.dagObj.lock(chestOffsetCtrl, 's') parentConstraint(chestOffsetCtrl, chain[-1], mo=True) # -- Mid -- midCtrl = controllerShape.build(name + '_mid', controlSpec['middle'], controllerShape.ControlType.SPLINE) #pdil.dagObj.matchTo( midCtrl, midPoint ) xform(midCtrl, ws=True, t=midPos) pdil.dagObj.lock(midCtrl, 's') midCtrl.setParent(container) mid = joint(None, n='Mid') #pdil.dagObj.moveTo( mid, midPoint ) xform(mid, ws=True, t=midPos) mid.setParent(midCtrl) pdil.dagObj.lock(mid) hide(mid) # Mid control's rotation aims at the chest pdil.dagObj.zero(midCtrl) aimer = util.midAimer(base, chestCtrl, midCtrl) aimer.setParent(container) hide(aimer) space.add(midCtrl, aimer, spaceName='default') userDriven = space.addUserDriven( midCtrl, 'extreme') # Best name I got, extreme poses! parentConstraint(base, chestCtrl, userDriven, mo=True, skipRotate=('x', 'y', 'z')) orientConstraint(base, chestCtrl, userDriven, mo=True) """ # -- Shoulders -- if numChestJoints > 2: # The shoulder control is skipped if there aren't enough joints shoulderCtrl = controllerShape.build( name + '_shoulders', controlSpec['end'], controllerShape.ControlType.SPLINE ) pdil.dagObj.matchTo( shoulderCtrl, srcChain[-2]) # We want to use the penultimate joint orientation pdil.dagObj.moveTo( shoulderCtrl, end) controllerShape.scaleAllCVs( shoulderCtrl, x=0.15 ) shoulderZero = pdil.dagObj.zero(shoulderCtrl) shoulderZero.setParent(chestCtrl) pdil.dagObj.lock(shoulderCtrl, 't s') neck = joint(None, n='Neck') neck.setParent( shoulderCtrl ) pdil.dagObj.moveTo( neck, end ) pdil.dagObj.lock(neck) hide(neck) # -- Neck -- neckCtrl = controllerShape.build( name + '_neck', controlSpec['neck'], controllerShape.ControlType.ROTATE ) pdil.dagObj.matchTo( neckCtrl, end) if numChestJoints > 2: # The shoulder control is skipped if there aren't enough joints pdil.dagObj.zero(neckCtrl).setParent( shoulderCtrl ) pdil.dagObj.lock(neckCtrl, 's t') space.add( neckCtrl, srcChain[-2], 'chest' ) else: pdil.dagObj.zero(neckCtrl).setParent( chestCtrl ) pdil.dagObj.lock(neckCtrl, 't s') space.add( neckCtrl, chestCtrl, 'chest' ) space.addMain(neckCtrl) """ # Constrain to spline proxy, up to the chest... constraints = [] for src, dest in list(zip(chain, srcChain))[:chestIndex]: constraints.append(pdil.constraints.pointConst(src, dest)) constraints.append(pdil.constraints.orientConst(src, dest)) # ... including the chest src = chain[chestIndex] dest = srcChain[chestIndex] # &&& Gotta remove/figure out what is going on here, why can't I just constrain entirely the srcChain to it's dup'd chain? if False: # numChestJoints > 2: # The shoulder control is skipped if there aren't enough joints constraints.append(pdil.constraints.pointConst(src, dest)) constraints.append(pdil.constraints.orientConst(src, dest)) # ... not including the chest else: chestProxy = duplicate(src, po=True)[0] chestProxy.setParent(chestCtrl) constraints.append(pdil.constraints.pointConst(chestProxy, dest)) constraints.append(pdil.constraints.orientConst(chestProxy, dest)) hide(chestProxy) if chestOffsetCtrl: constraints.append(pdil.constraints.pointConst(chain[-1], srcChain[-1])) constraints.append( pdil.constraints.orientConst(chain[-1], srcChain[-1])) #constraints.append( pdil.constraints.pointConst( neckCtrl, srcChain[-1] ) ) #constraints.append( pdil.constraints.orientConst( neckCtrl, srcChain[-1] ) ) """ if numChestJoints > 2: # The shoulder control is skipped if there aren't enough joints # Make a proxy since we can't constrain with maintainOffset=True if we're making fk too. proxy = duplicate(srcChain[-2], po=True)[0] proxy.setParent(neck) pdil.dagObj.lock(proxy) constraints.append( pdil.constraints.pointConst( proxy, srcChain[-2] ) ) constraints.append( pdil.constraints.orientConst( proxy, srcChain[-2] ) ) """ hide(chain, mainIk) # Bind joints to the curve if False: # numChestJoints > 2: # The shoulder control is skipped if there aren't enough joints skinCluster(crv, base, mid, chest, neck, tsb=True) else: skinCluster(crv, base, mid, chest, tsb=True) chestCtrl = pdil.nodeApi.RigController.convert(chestCtrl) chestCtrl.container = container chestCtrl.subControl['mid'] = midCtrl if chestOffsetCtrl: chestCtrl.subControl['offset'] = chestOffsetCtrl #if numChestJoints > 2: # The shoulder control is skipped if there aren't enough joints # chestCtrl.subControl['offset'] = shoulderCtrl #chestCtrl.subControl['neck'] = neckCtrl # Setup advanced twist startAxis = duplicate(start, po=True)[0] startAxis.rename('startAxis') startAxis.setParent(base) pdil.dagObj.lock(startAxis) endAxis = duplicate(start, po=True)[0] endAxis.rename('endAxis') endAxis.setParent(chestCtrl) endAxis.t.set(0, 0, 0) pdil.dagObj.lock(endAxis) hide(startAxis, endAxis) mainIk.dTwistControlEnable.set(1) mainIk.dWorldUpType.set(4) startAxis.worldMatrix[0] >> mainIk.dWorldUpMatrix endAxis.worldMatrix[0] >> mainIk.dWorldUpMatrixEnd hide(startAxis, endAxis) return chestCtrl, constraints '''
def buildSplineTwist(start, end, controlCountOrCrv=4, twistInfDist=0, simplifyCurve=True, tipBend=True, sourceBend=True, matchOrient=True, allowOffset=True, # noqa e128 useLeadOrient=False, # This is an backwards compatible option, mutually exclusive with matchOrient twistStyle=TwistStyle.ADVANCED, duplicateCurve=True, controlOrient=OrientMode.CLOSEST_JOINT, name='', groupName='', controlSpec={}): ''' Make a spline controller from `start` to `end`. :param int twistInfDist: Default twist controls to falloff before hitting eachother. Otherwise it is the number of joints on either side it will influence. :param bool simplifyCurve: Only used if # of cvs is specified. Turning it on will likely result it the curve not matching the existing joint position but will be more evenly spaced per control. :param bool tipBend: If True, an extra cv will be added at the second to last joint, controlled by the last controller to ease out. ##:param bool applyDirectly: If True, rig the given joints, do not make a duplicate chain :param bool useLeadOrient: If True, the controllers will be aligned the same as the first joint. **NOTE** I think this option only exists to preserve previous builds, this is pretty dumb :param bool matchOrient: Does trueZero on the start and end. I'm not sure this makes sense. .. todo:: * Add the same spline chain +X towards child that the neck has and test out advancedTwist() * See if I can identify the closest joint to a control and orient to that * The first joint has parent AND local, which are the same thing, keep this for convenience of selecting all the controls and editing attrs? * Test specifying your own curve * There is a float division error that can happen if there are too many control cvs. * Verify twists work right with unsimplified curves (hint, I don't think they do). ''' matchOrient = False useLeadOrient = False if isinstance( controlCountOrCrv, int ): assert controlCountOrCrv > 3, "controlCount must be at least 4" # The axis to twist and stretch on. jointAxis = util.identifyAxis( start.listRelatives(type='joint')[0] ) # Make a duplicate chain for the IK that will also stretch. stretchingChain = util.dupChain( start, end, '{0}_stretch' ) # &&& NOTE! This might affect advanced twist in some way. # If the chain is mirrored, we need to reorient to point down x so the # spline doesn't mess up when the main control rotates if stretchingChain[1].tx.get() < 0: # Despite aggresive zeroing of the source, the dup can still end up slightly # off zero so force it. for jnt in stretchingChain: jnt.r.set(0, 0, 0) joint( stretchingChain[0], e=True, oj='xyz', secondaryAxisOrient='yup', zso=True, ch=True) joint( stretchingChain[-1], e=True, oj='none') if isinstance( controlCountOrCrv, int ): mainIk, _effector, crv = ikHandle( sol='ikSplineSolver', sj=stretchingChain[0], ee=stretchingChain[-1], ns=controlCountOrCrv - 3, simplifyCurve=simplifyCurve) else: if duplicateCurve: crv = duplicate(controlCountOrCrv)[0] else: crv = controlCountOrCrv mainIk, _effector = ikHandle( sol='ikSplineSolver', sj=stretchingChain[0], ee=stretchingChain[-1], ccv=False, pcv=False) crv.getShape().worldSpace[0] >> mainIk.inCurve hide(mainIk) mainIk.rename( pdil.simpleName(start, "{0}_ikHandle") ) crv.rename( pdil.simpleName(start, "{0}_curve") ) if not name: name = util.trimName(start) if name.count(' '): name, endName = name.split() else: endName = '' # Only add a tipBend cv if number of cvs was specified. if tipBend and isinstance( controlCountOrCrv, int ): currentTrans = [ xform(cv, q=True, ws=True, t=True) for cv in crv.cv ] insertKnotCurve( crv.u[1], nk=1, add=False, ib=False, rpo=True, cos=True, ch=True) for pos, cv in zip(currentTrans, crv.cv[:-2]): xform( cv, ws=True, t=pos ) xform( crv.cv[-2], ws=True, t=xform(end.getParent(), q=True, ws=True, t=True) ) xform( crv.cv[-1], ws=True, t=currentTrans[-1] ) # Only add a sourceBend cv if number of cvs was specified. if sourceBend and isinstance( controlCountOrCrv, int ): currentTrans = [ xform(cv, q=True, ws=True, t=True) for cv in crv.cv ] insertKnotCurve( crv.u[1.2], nk=1, add=False, ib=False, rpo=True, cos=True, ch=True) # I honestly don't know why, but 1.2 must be different than 1.0 for pos, cv in zip(currentTrans[1:], crv.cv[2:]): xform( cv, ws=True, t=pos ) xform( crv.cv[0], ws=True, t=currentTrans[0] ) xform( crv.cv[1], ws=True, t=xform(stretchingChain[1], q=True, ws=True, t=True) ) grp = group(em=True, p=node.mainGroup(), n=start.name() + "_splineTwist") controls = util.addControlsToCurve(name + 'Ctrl', crv, controlSpec['main']) for ctrl in controls: pdil.dagObj.zero(ctrl).setParent( grp ) if controlOrient == OrientMode.CLOSEST_JOINT: # Use the real chain to match orientations since the stretching chain might reorient to compensate for mirroring. jointPos = {j: dt.Vector(xform(j, q=True, ws=True, t=True)) for j in util.getChain(start, end)} aveSpacing = util.chainLength(stretchingChain) / (len(stretchingChain) - 1) for ctrl in controls: cpos = dt.Vector(xform(ctrl, q=True, ws=True, t=True)) distances = [ ( (jpos - cpos).length() / aveSpacing, j) for j, jpos in jointPos.items() ] distances.sort() ''' Just use the closest joint if within 10% of the average spacing Possible future improvement, look at two joints, and determine if the control is between them and inbetween the orientation. ''' if True: # distances[0][0] < 100: r = xform(distances[0][1], q=True, ro=True, ws=True) with pdil.dagObj.Solo(ctrl): xform(ctrl, ro=r, ws=True) pdil.dagObj.zero(ctrl) if endName: controls[-1].rename(endName + 'Ctrl') if matchOrient: util.trueZeroSetup(start, controls[0]) util.trueZeroSetup(end, controls[-1]) if tipBend: if useLeadOrient and not matchOrient: controls[-1].setRotation( end.getRotation(space='world'), space='world' ) parent( controls[-2].getChildren(), controls[-1] ) name = controls[-2].name() delete( pdil.dagObj.zero(controls[-2]) ) if not endName: controls[-1].rename(name) controls[-2] = controls[-1] controls.pop() #core.dagObj.zero(controls[-2]).setParent(controls[-1]) #channels = [t + a for t in 'trs' for a in 'xyz'] #for channel in channels: # controls[-2].attr( channel ).setKeyable(False) # controls[-2].attr( channel ).lock() if sourceBend: names = [] for ctrl in controls[1:-1]: names.append( ctrl.name() ) ctrl.rename( '__temp' ) endNum = -1 if endName else None for name, cur in zip(names, controls[2:endNum] ): cur.rename(name) if useLeadOrient and not matchOrient: controls[0].setRotation( start.getRotation(space='world'), space='world' ) parent( controls[1].getChildren(), controls[0] ) delete( pdil.dagObj.zero(controls[1]) ) del controls[1] controls[0] = pdil.nodeApi.RigController.convert(controls[0]) controls[0].container = grp stretchAttr, jointLenMultiplier = util.makeStretchySpline(controls[0], mainIk) connectingCurve = addConnectingCurve(controls) controls[0].visibility >> connectingCurve.visibility # Make twist for everything but hide them all and drive the ones that overlap # with spline controllers by the spline control. if not twistInfDist: numJoints = countJoints(start, end) twistInfDist = int(math.ceil( numJoints - len(controls) ) / float(len(controls) - 1)) twistInfDist = max(1, twistInfDist) noInherit = group(em=True, p=grp, n='NoInheritTransform') pdil.dagObj.lock(noInherit) noInherit.inheritsTransform.set(False) noInherit.inheritsTransform.lock() # &&& If simplify curve is ON, the last joint gets constrained to the spinner? # Otherwise it gets constrained to the offset or stretch joint, which I think is correct. if allowOffset: # If allowOffset, make another chain to handle the difference in joint positions. offsetChain = util.dupChain( start, end, '{0}_offset' ) offsetChain[0].setParent(noInherit) hide(offsetChain[0]) twists, constraints = addTwistControls( offsetChain, start, end, twistInfDist) finalRigJoint = offsetChain[-1] else: twists, constraints = addTwistControls( stretchingChain, start, end, twistInfDist ) finalRigJoint = stretchingChain[-1] # Constrain the end to the last controller so it doesn't pop off at all, # but still respect the stretch attr. pointConstraint(finalRigJoint, end, e=True, rm=True) # Make a proxy that can allows respecting stretch being active or not. endProxy = duplicate(end, po=True)[0] endProxy.rename('endProxy') hide(endProxy) endProxy.setParent(grp) stretchAttr >> pdil.constraints.pointConst( controls[-1], endProxy, mo=True ) pdil.math.opposite(stretchAttr) >> pdil.constraints.pointConst( finalRigJoint, endProxy ) constraints.point >> pdil.constraints.pointConst( endProxy, end ) hide(twists) numControls = len(controls) numTwists = len(twists) for i, ctrl in enumerate(controls): index = int(round( i * ((numTwists - 1) / (numControls - 1)) )) util.drive( ctrl, 'twist', twists[index].attr('r' + jointAxis) ) space.add( ctrl, start.getParent(), 'local' ) parents = [start.getParent()] + controls[:-1] stretchingChain[0].setParent(noInherit) crv.setParent(noInherit) hide(crv, stretchingChain[0]) connectingCurve.setParent( noInherit ) mainIk.setParent(grp) # Do not want to scale but let rotate for "fk-like" space mode for ctrl, _parent in zip(controls, parents): pdil.dagObj.lock( ctrl, 's' ) if useLeadOrient: ctrl.setRotation( start.getRotation(space='world'), space='world' ) pdil.dagObj.zero(ctrl) space.addMain(ctrl) space.add( ctrl, _parent, 'parent') for i, ctrl in enumerate(controls[1:]): controls[0].subControl[str(i)] = ctrl # Must constrain AFTER controls (possibly) get orientd orientConstraint( controls[-1], finalRigJoint, mo=True ) # Setup advanced twist if twistStyle == TwistStyle.ADVANCED: # &&& Test using advancedTwist() to replace the code beloew util.advancedTwist(stretchingChain[0], stretchingChain[1], controls[0], controls[-1], mainIk) ''' startAxis = duplicate( start, po=True )[0] startAxis.rename( 'startAxis' ) startAxis.setParent( controls[0] ) endAxis = duplicate( start, po=True )[0] endAxis.rename( 'endAxis' ) endAxis.setParent( controls[-1] ) endAxis.t.set(0, 0, 0) mainIk.dTwistControlEnable.set(1) mainIk.dWorldUpType.set(4) startAxis.worldMatrix[0] >> mainIk.dWorldUpMatrix endAxis.worldMatrix[0] >> mainIk.dWorldUpMatrixEnd hide(startAxis, endAxis) ''' else: if twistStyle == TwistStyle.X: controls[-1].rx >> mainIk.twist elif twistStyle == TwistStyle.NEG_X: pdil.math.multiply(controls[-1].rx, -1.0) >> mainIk.twist elif twistStyle == TwistStyle.Y: controls[-1].ry >> mainIk.twist elif twistStyle == TwistStyle.NEG_Y: pdil.math.multiply(controls[-1].ry, -1.0) >> mainIk.twist elif twistStyle == TwistStyle.Z: controls[-1].rz >> mainIk.twist elif twistStyle == TwistStyle.NEG_Z: pdil.math.multiply(controls[-1].rz, -1.0) >> mainIk.twist # To make .twist work, the chain needs to follow parent joint follow = group(em=True, p=grp) target = start.getParent() pdil.dagObj.matchTo(follow, stretchingChain[0]) parentConstraint( target, follow, mo=1 ) follow.rename(target + '_follow') stretchingChain[0].setParent(follow) # Constraint the offset (if exists) to the stretch last to account for any adjustments. if allowOffset: util.constrainAtoB(offsetChain[:-1], stretchingChain[:-1]) pointConstraint(stretchingChain[-1], offsetChain[-1], mo=True) return controls[0], constraints