Script Description:
The next script I worked on which I once again wrote for both Maya and Motion Builder was a simple tool for pinning controls. It’s an animation tool to be used on IK controls to hold them in the same place over a period of time. It also does some blending after the end of the hold to smooth out the transition back to the animation.UI:
For this script I wrote a very basic UI. I wanted to give
the user the ability to know what control they were selecting and then have a
button for pinning and another button for baking down the animation layer with the pinning into
the base animation layer.
Functions:
One of the challenges with this script was that because the
keys that determine pinning are made on a separate animation layer that modifies the base animation layer, I had to be sure that they were set in a range that had keys on the base
animation layer. I started by using the Motion Builder system functions to swap the current layer to the base animation layer to allow me to work off of it. Once I had the animation nodes I took the first node and got the time for both the start and end frame of the base animation. I then compared them to the times of the keys set on the animation layer. If they did not fall in the range of the keys on the base animation layer then I gave the user a warning and returned that the time was not valid otherwise I return that the time is valid.
def timeCheck(self): timeValid = True self.system.CurrentTake.SetCurrentLayer(0) self.baseAnimNodes = self.getAnimNodes() #finds the first key on the base animation curve firstFrame = self.baseAnimNodes[0].Nodes[0].FCurve.Keys[0].Time #finds the last key on the base animation curve lastFrame = self.baseAnimNodes[0].Nodes[0].FCurve.Keys[len(self.baseAnimNodes[0].Nodes[0].FCurve.Keys)-1].Time #checks that the keys to be edited are in range of the base animation if self.startTime < firstFrame or self.endTime > lastFrame: utils.makeWarning('Frame range is not valid.') timeValid = False return timeValid
This was how I checked that the time was valid in Maya. I used the animation layer class in Maya to get the nodes for the animation layer that the user set keys on. Then using the keyframe function from pymel in query mode with the tc flag for time change to get a list of times that the layer has keys set on it. I then repeated the process for the base layer though I used the getBaseAnimCurves function from the animation layer class to find the animation nodes and be sure that they are from the base animation layer. Once I have both like in the other version of the script I compare times to be sure that the keys chosen are valid.
#checks that user frame range is within the range of frames on the control def isValid(self): valid = True #gets nodes for pin layer self.layerNodes = self.affectingLayers[0].getAnimCurves() layerKeyTimesList = pm.keyframe(self.layerNodes[0], query = True, tc = True) #finds the first frame that the control is keyed on self.firstFrame = layerKeyTimesList[0] #finds the last frame the control is keyed on self.lastFrame = layerKeyTimesList[len(layerKeyTimesList)-1] #gets nodes for base layer self.baseAnimNode = self.affectingLayers[0].getBaseAnimCurves() baseKeyTimes = pm.keyframe(self.baseAnimNode[0], query = True, tc = True) #finds first frame self.baseFirstFrame = baseKeyTimes[0] #finds last frame self.baseLastFrame = baseKeyTimes[len(baseKeyTimes) -1] #checks that frames on pin layer are in range of the first and last frame on base layer if self.firstFrame < self.baseFirstFrame or self.lastFrame > self.baseLastFrame: timeInvalidWarning = QtWidgets.QMessageBox() timeInvalidWarning.setWindowTitle('Warning') timeInvalidWarning.setText('Frame range is not valid.') timeInvalidWarning.exec_() valid = False return valid
The tricky part of this script was figuring out how Motion Builder handles animation layers and then using that information to set
keys to pin the control. What I found was that I needed the difference between
the first frame which was the one being held and the current frame on the base
animation layer. Then I set a key on the frame with the value of the difference
causing it to match the start frame.
#this function sets keys pinning the control in place def setKeyValue(self): #gets anim nodes for translation and rotation self.animLayerNodes = self.getAnimNodes() #gets FBTime objects for given frames self.startTime = self.animLayerNodes[0].Nodes[0].FCurve.Keys[0].Time self.endTime = self.animLayerNodes[0].Nodes[0].FCurve.Keys[len(self.animLayerNodes[0].Nodes[0].FCurve.Keys)-1].Time #gets frame numbers for the keys self.startFrame = self.startTime.GetFrame() self.endFrame = self.endTime.GetFrame() #checks that given frames are in a valid time range frameNum = self.frameNumberCheck() valid = self.timeCheck() if valid and frameNum: #FBTime to be incremented in the for loop incTime = self.startTime #sets new keys in a loop for key in range(self.startFrame, self.endFrame): #adds one frame to current time incTime += FBTime(0,0,0,1,0) #sets new keys for all three fcurves for rotation and translation for i in range(2): for j in range(3): self.system.CurrentTake.SetCurrentLayer(0) firstValue = self.getKeyValue(i, j, self.startTime, self.baseAnimNodes) currentValue = self.getKeyValue(i, j, incTime, self.baseAnimNodes) setValue = firstValue - currentValue self.system.CurrentTake.SetCurrentLayer(self.numLayers-1) self.animLayerNodes[i].Nodes[j].FCurve.KeyAdd(incTime, setValue) #blend end of hold self.zeroKey()
Setting keys worked very differently in Maya because
instead of just setting keys with values that were the difference between the
two keys I had to find the difference then add it to the start frame value to
get the value to set my pin keys at.
def setKeys(self): numKeysValid = self.frameNumberCheck() #checks to see if frame range is valid valid = self.isValid() #if the frame range is valid if valid == True and numKeysValid == True: #gets the value from the first hold frame self.startValues = self.getKeyValue(self.firstFrame, self.baseAnimNode) incTime = self.firstFrame for key in range(int(self.firstFrame), int(self.lastFrame)): incTime += 1.0 self.currentValues = self.getKeyValue(incTime, self.baseAnimNode) list = [] #list of possible control attributes self.attList = ['visablitiy','translateX', 'translateY', 'translateZ', 'rotateX', 'rotateY', 'rotateZ', 'scaleX', 'scaleY', 'scaleZ'] for v in range(10): value = (self.startValues[v] - self.currentValues[v]) + self.currentValues[v] if value == 0: value = self.startValues[v] list.append(value) pm.setKeyframe(t = int(incTime), al = self.affectingLayers[0], at = self.attList[v], v = value) #adds a transtion key between end of hold and the rest of the animation self.zeroKey()
To blend the end of the hold with the rest of the animation
I wrote this function to set a zero key five frames after the end of the hold
on the new layer. In Motion Builder this was fairly easy to do. I set a key
with values that match the value of the start frame on each of the different
FCurves.
#adds a base key at the end of the hold to blend into the rest of the animation def zeroKey(self): #gets the key to adjust setKey = self.endTime + FBTime(0,0,0,5,0) #sets a key with the same value as the first animation layer key for i in range(2): for j in range(3): setValue = self.getKeyValue(i, j, self.startTime, self.animLayerNodes) self.animLayerNodes[i].Nodes[j].FCurve.KeyAdd(setKey,setValue)
Setting zero keys in Maya works very differently than in
motion builder. In order to set one in Maya I had to get the value of the key at the current time on the base animation layer and then set a key with the same value on the
animation layer that I was pinning the control from.
#sets a zero key five frames after the hold to create a blend def zeroKey(self): self.zeroValues = self.getKeyValue(self.lastFrame + 5.0, self.baseAnimNode) for v in range(10): pm.setKeyframe(t = self.lastFrame + 5.0, al = self.affectingLayers[0], at = self.attList[v], value = self.zeroValues[v])
No comments:
Post a Comment