Friday, November 2, 2018

C++ Vector

Description:
Vectors are re-sizable arrays which are very useful for storing data. I did this exercise as a way to learn about memory management as well as basic recursion.

Functions:

Resize:
One of the first functions needed in order for the vector to work is a private function to resize the vector. It works by doubling the size of the vector whenever it runs out of space. It's important to keep this function private because we don't want the size of the vector doubled unless we need it to be since doubling takes up quite a lot of memory. The function does two things. First it makes a new array that is twice the size of the old one. It then transfers the values from the old vector to the new vector and sets empty spaces at the end to the default value of zero. Once everything is finished it deletes the old vector to free the memory and then changes the vector pointer to point at the new vector. 

void intVector::resize()
{
  int temp = size;// holds the intial size of the array
  size = temp * 2;// doubles size 
  int* tempArray = new int[size];//makes a new array twice the size of the old one
  
  //stores values from old array into the new one
  for (int i = 0; i < temp; i++)
  {
    tempArray[i] = p[i];
  }
  
   //sets empty spaces at end of vector to zero 
  int tempInt = size - capacity;//vairble for number of spaces at the end of the array 
  for (int j = 0; j < tempInt; j++)
  {
    tempArray[capacity + j] = 0;
  }
  delete[] p;//free memory for the old array by deleteing it 
  p = tempArray;//sets array pointer to point at the new array 
 
}
 





Push back:
This is a simple function for adding values to our vector. It works by placing the values at the end of the vector. It starts by checking to be sure that there is room for the new values in the vector. If there is not it calls resize to make space in the vector. It then puts the new value after the other values and increments capacity to keep track of the number of values in the vector. 


void intVector::push_back(int a)
{
  //if all array elements are full call resize to double array 
  if (capacity == size)
  {
    resize();
  }
  //place user value at the end of the array 
  p[capacity] = a;
  capacity++;//increment capacity to keep track of elements in the array 

}





Shrink to fit:
This function shrinks the vector so that the size is equal to the number of elements in the vector. This is useful for make sure that the vector is taking up the least about of space in memory as possible. It works by making a vector the size of the number of elements. It then transfers the values over to the new vector.


void intVector::shrink_to_fit()
{
   //make a new array the size of the elements currently in the array 
  int* temp_array = new int[capacity]; 
  //copy over values from old array 
  for (int k = 0; k < capacity; k++)
  {
    temp_array[k] = p[k];
  }
  
  size = capacity;//set new size to capacity 
  delete[] p;//delete old array to free memory 
  p = temp_array;//set array pointer to point at new array 
}





Print vector:
This is a function used to check the current size and capacity of the vector as well as what values are contained inside. It was very useful as a debugging tool while I was writing the other functions in this class. 


void intVector::print_vector() const
{
  //print the size and capacity of the vector
  cout << "Size:" << size << endl;
  cout << "Capacity:" << capacity << endl;
  
  //prints elements in the array
  for (int i =0; i < size; i++)
  {
    cout<< p[i] << endl;
  }
}





Push front:
This is  a function that adds a user defined value to the front of the vector. It's a bit more complicated than push back because you have to shift all the current values to the right by one. To start check to see if there is space in the vector. If there is not resize the vector. Next make a new vector that is the same size as the current vector. Make the user defined value the first element of the vector then transfer the values from the old vector to the new vector starting at the second element of the vector. Once the values are transferred set any empty spaces to zero. Then delete old vector and change the vector pointer to point at the new vector. 


void intVector::push_front(int b)
{
  //if all the elements in the array are full call resize
  if (capacity == size)
  {
   resize(); 
  }
  

  int* tempArray = new int[size]; // make a new array 
  tempArray[0] = b;//set the first element equal to the user given value 
  //copy over elements from the old array 
  for(int i = 0; i < capacity; i++)
  {
    tempArray[i+1] = p[i];
  }
  
  capacity++;
  
  //sets empty spaces at end of vector to zero 
  int tempInt = size - capacity;
  for (int j = 0; j < tempInt; j++)
  {
    tempArray[capacity + j] = 0;
  }
  
  delete[] p;//deletes old array to free memory 
  p = tempArray;//sets pointer to point at new array 
}





Resize:
As a way to give the user control over the size of the vector I also wrote a public version of the resize function. This function works by taking in a user define size and then making a new vector that is that size and transferring any current values from the old vector to the new vector.


void intVector::resize(int c)
{
  //if the user gives a size smaller than the current capacity give them a warning 
  if (c < capacity)
  {
    cout << "New vector is too small. Some data will be lost." << endl;
    capacity = c;
  }
  
  size = c;//set new size
  int* tempArray = new int[c];// make a new array the size of c 
  
  //copy over values from old array
  for (int i = 0; i < capacity; i++)
  {
    tempArray[i] = p[i];  
  }
  
  //sets empty spaces at end of vector to zero 
  int tempInt = size - capacity;
  for (int j = 0; j < tempInt; j++)
  {
    tempArray[capacity + j] = 0;
  }
  
  delete[] p;//delete old array to free memory 
  p = tempArray;//set array pointer to point at new array 
}





Sort:
This function uses the merge sort algorithm to organize the values from least to greatest inside the vector. It starts by using recursion to divide the vector into smaller arrays the size of 2 or 1. This allows for each piece of the vector to be organized least to greatest. It then merges them back together by checking which of the smaller arrays has the smallest value, placing that value into the original array and then beginning the check again until all values are back in the vector. 


void intVector::sort(int* p, int size)
{
  //make a break case
  if(size == 2)
  {
 //if the second element is smaller than the first flip them  
    if(p[0] > p[1])
    {
      int temp = p[0];
      p[0] = p[1];
      p[1] = temp;
      return; 
    }
 
  return;
  }
  else if (size == 1)
  {
    return;
  }
  
  int smallSizeA, smallSizeB;
  //checks if array size is divisable by 2 
  if(size % 2 == 0)
  {
  //sets both small size to be old size divided by two 
     smallSizeA = size/2;
     smallSizeB = size/2;
  }
  //if it's not make one small size bigger to accomidate the remaineder 
  else 
  {
     smallSizeA = size/2;
     smallSizeB = (size/2) + 1;
  }
  
  //make two new arrays to hold the halves of the first array
  int* arryA = new int[smallSizeA];
  int* arryB = new int[smallSizeB];
  
  //this puts the elements of the first array into the two new arrays 
  for(int i = 0; i < size; i++)
  {
    if (i < smallSizeA)
    {
      arryA[i] = p[i];
    }
    else
    {
      arryB[i-smallSizeA] = p[i];
    }
  }

  //use recursion to breakdown into sizes 2 or 1   
  sort(arryA, smallSizeA);
  sort(arryB, smallSizeB);
  //merge arrays back together sorted 
  int i = 0, j = 0;
  //while both arrays have numbers in them 
  while(i < smallSizeA && j < smallSizeB)
  {
 //if the value of the first arry is less 
    if (arryA[i] < arryB[j])
    {
   //insert value from arrayA back into the first array 
      p[i+j] = arryA[i];
      i++;

    } 
    //if value of the second array is less or equal to  
    else 
    {
      //insert value from arrayB back into the first array 
      p[i+j] = arryB[j];
      j++;

    }
  }
  
  //if there are no more values in arrayA
  if(i == smallSizeA)
  {
 //while there are still values in arrayB insert them back into first array 
    while(j < smallSizeB)
    {
      p[i+j] = arryB[j];
      j++;
    }
  }
  //if there are no more values in arrayB
  if(j == smallSizeB)
    {
   //while there are still values in arrayA insert them back into first array 
      while(i < smallSizeA)
      {
      p[i+j] = arryA[i];
      i++;
      }
    }
  //delete small arrays to free memory 
  delete[] arryA;
  delete[] arryB;  
}





Insert:
This function allows the user to insert a value where every they want to inside the vector.
It then takes the values that were at and after the given insert position and shifts them one to the right. If the given position was not in the vector the vector resizes until it is. 



void intVector::insert(int val, int pos)
{
  //check and see if pos is greater than vector size
  //if it is resize until it's not 
  if(pos > size || pos == capacity)
  {
   while(pos >= size)
   {
     resize();
   }
  }
  
  // going to need to store all values at and to the right of the postion in a temp array
  if(pos < capacity)
  {
  //need size of new temp array(capacity - pos)
  int tempSize = capacity - pos;
  //get values to store
  int* temp = storeValues(pos, tempSize); 
  //place values back into the array shifted one to the right 
  for(int i = 0; i < tempSize; i++)
  {
    p[pos + 1 + i] = temp[i];
  }
  
  }
  //overwrite postion with given value
  p[pos] = val;  
  capacity++;
  
}





Remove:
This function allows the user to remove values from the vector by giving a position inside the vector. If there are values after the deleted value the function then shifts them one to the left so there are no gaps in data. 


void intVector::remove(int pos)
{
  //check postion against size 
  //if it's greater return 
  if(pos > size)
  {
    cout << "position not in vector" << endl; 
    return; 
  }
  
  p[pos] = 0;
  
  //if there were values after the deleted value
  if(pos < capacity)
  {
    //need size of new temp array(capacity - pos)
    int tempSize = capacity - pos;
    int tempPos = pos + 1;
 //make a temp array with values 
    int* temp = storeValues(tempPos, tempSize);
    //shift values one to the left 
     for(int i = 0; i < tempSize; i++)
    {
      p[pos + i] = temp[i];
    }
 //set the now empty place to zero 
    p[capacity - 1] = 0;
  }
  capacity--;
}





Link to program on github: https://github.com/irisra1/C-projects/tree/master/Vector

Thursday, November 1, 2018

Unity Basic Player Controller

Description:
As a first step towards learning about how animation works in unity I began a project and started writing a very simple player controller in C#. Currently, the player can move forwards, backwards, left, right and diagonally between all directions.


Script:
Here is my basic player controller script written in C# :

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerControl : MonoBehaviour {
    //public varibles
    public float MoveSpeed = 5F;
    Rigidbody body;
    // Use this for initialization
    void Start ()
    {
        //get ridgid body 
        body = gameObject.GetComponent<Rigidbody>();

 }
 
 // Update is called once per frame
 void Update ()
      
    {
        //reset velocity to zero
        body.velocity = Vector3.zero;
        
        if (Input.GetAxis("Horizontal") != 0)
        {
            body.velocity += Vector3.right * MoveSpeed * Input.GetAxis("Horizontal");
            print("horizontal input recived");
        }
        if (Input.GetAxis("Vertical") != 0)
        {
            body.velocity += Vector3.forward * MoveSpeed * Input.GetAxis("Vertical");
            print("vertical input recived");
        }
        
 }
}

One of the first things that is useful to know is what public variables are. They are variables that are set with the keyword public in front of them which allows for them to be edited in the editor. This is useful because it allows for iteration without having to go into the script and change the values there.  In the case of my script I have one called MoveSpeed which is a float I use to determine how fast I want the player to move. Having this available in editor makes it much easier to fine tune. 

In the start function which is called once when the game object is created. In the case of the player character it is called at the start of the game. I get the ridged body of the player so I can manipulate it. I get it here so it only gets grabbed once. 

Update is called on every frame and it is here that I check for input to determine how to move the player. One of the things I learn was that in order for the player to move in multiple directions I need to use the += operator to change the velocity so that it would be accumulative. This does necessitate  resetting to zero at the start of update or setting a max value of the velocity or the player will accelerate on every frame. 

Link to project on github: https://github.com/irisra1/Player-Controller

Sunday, August 12, 2018

Create Control Rig Script

Script Description:

This script I wrote for generating control rigs for skinning skeletons. It allows the user to make control chains that are either FK, IK or an FK/IK switch. Once the user has made some controls the second half the UI is for assembling the pieces into a cohesive rig. The user can make a world control and cog control. They can also parent controls together and parent all top level controls to the world control.


UI:


The UI for this script is divided into two parts. The top of the UI is for making controls and the bottom is for assembling those controls into a complete rig.








Functions: 

In order to build the controls I had to be able to duplicate joints to be control joints. One of the challenges I ran into with this was that originally I had been duplicating the parent joint with its children. The problem was that it meant that lots of extra joints were being created. For example, if I was trying to make the arm controls the hand would get duplicated as well. As a solution I ended up duplicating only the parent joint without the children and then making a list of the duplicated joints. Once I had that list I parented them together to match the skinning skeleton hierarchy. Giving me the joints I needed without anything extra.

#duplicates joints based off given joint list
def duplicateJoints(self, jointList, prefix):
 dupJointList = []
 #duplicates all joints in joint list without their children and adds them to a list
 for joint in jointList:
  dupJoint = pm.duplicate(joint, name = prefix + str(joint), po = True)
  pm.parent(dupJoint[0], world = True)
  dupJointList.append(dupJoint)
 
 #parents the joints together     
 for i in range(len(dupJointList) -1):
  prnt = dupJointList[i +1]
  child = dupJointList[i]
  self.parentObject(prnt[0], child[0])
  
 #hides the created joints to clean up the viewport
 pm.hide(dupJointList[len(dupJointList) - 1])
 
 #returns the duplicated joints
 return dupJointList






One of the more complicated things to write for this script was the function for making the IK chains. One of the reasons was that IK chains have to be made in a chain of joints that is at least two joints in length so right off the bat I had to make a check for if the start joint was equal to the end joint and then give the user a warning if they were. 

The next challenge was figuring out how to place the pole vectors. Ideally they are out in space from the center of the chain. So I gave the user the ability to specify the middle joint in the chain as well as a set of radio buttons to specify if the pole vector should be in front of, behind, to the right of, or to the left of the model. For placing the pole vector I assumed z positive was forward. Then using the world space location of the middle joint along with where the user specified the pole vector should go in relation to the model, I placed the pole vector in the world. Once it's placed I constrain it to the handle with a pole vector constraint. 

The other thing I had to solve was the rotation at the end of the IK chain. To do this I looked for the first child joint of the end joint in the chain. Duplicate the child joint to make an IK joint and then make a chain from the end of the IK chain to the child joint. Once the handle is made I parent it to the IK handle for the entire chain thus enabling the IK for the original chain to rotate. Since I can't be sure that there is a child joint if I can't find one I give the user a warning saying that the end of the chain won't be able to rotate. I leave the IK chain in the scene since the translation is still functional and depending on the users needs it might work just fine.

#makes Ik handle for given start and end joints
def makeIk(self, ctrlType):
 if self.startJoint == self.endJoint:
  ikChainWarning = QtWidgets.QMessageBox()
  ikChainWarning.setWindowTitle('Warning')
  ikChainWarning.setText('Can\'t make an Ik chain one joint in length. Please select two different joints to continue.')
  ikChainWarning.exec_()
  #deletes the created control joints that were made by the make controls function
  ctrlJoint = self.findCtrlJoint(self.startJoint)
  pm.delete(ctrlJoint)
 else:
  #makes joints to be driven by ik controls directly 
  ikJoints = self.duplicateJoints(self.jointList, 'IK_')
  #for the joints in the list of ikJoints
  for j in range(len(ikJoints)):
   #if not being used for an IK/FK switch constrain control joints
   if self.switch == False:
    ctrlJoint = self.findCtrlJoint(self.jointList[j])
    self.parentConstrainObject(ikJoints[j], ctrlJoint)
   
  #makes the Ik handle
  handle = pm.ikHandle(sj = ikJoints[len(ikJoints) - 1][0], ee = ikJoints[0][0])
  #hides the handle to clean up the veiwport
  pm.hide(handle[0])
 
  #makes a control 
  ctrl = self.getSetPostion((10.0 * self.scaleFactor), 'IK_' + str(self.endJoint), self.endJoint, ctrlType)
  
  #hides attributes on the ik control that the user doesn't need
  attrList = ['scaleX', 'scaleY', 'scaleZ', 'visibility']
  for attr in attrList:
   pm.setAttr(str(ctrl[1][0]) + '.' + attr,  k = False, cb = False)
  
  #parents the IK handle under the ctrl 
  self.parentObject(ctrl[1][0], handle[0])
  
  #makes a control for the pole vector 
  poleCtrl = self.getSetPostion((5.0 * self.scaleFactor), 'IK' + str(self.middleJoint) + '_Pole_Vector', self.middleJoint, ctrlType)
  
  #hides attributes the pole vector control doesn't need 
  for attr in attrList:
   pm.setAttr(str(poleCtrl[1][0]) + '.' + attr,  k = False, cb = False)
  
  #finds offset group for the pole vector control
  children = pm.listRelatives(poleCtrl[0], ad = True)
  offset = children[len(children)-1]
  
  #gets the world space postion of the joint to use for placing the pole vector 
  jointTrans = pm.xform(self.middleJoint, query = True, ws = True, t = True)
  #determines the x, y and z values to move the pole vector control based off user selection 
  #if the user selected front 
  if self.poleVectorPostion == -2:
   x = jointTrans[0]
   y = jointTrans[1]
   z = (100 * self.scaleFactor)
  #if the user selected back
  elif self.poleVectorPostion == -3:
   x = jointTrans[0]
   y = jointTrans[1]
   z = (-100 * self.scaleFactor)
  #if the user selcted right 
  elif self.poleVectorPostion == -4:
   x = (-100 * self.scaleFactor)
   y = jointTrans[1]
   z = jointTrans[2]
  #if the user selcted left 
  elif self.poleVectorPostion == -5:
     x = (100 * self.scaleFactor)
     y = jointTrans[1]
     z = jointTrans[2]
    #if the user didn't make a selection gives a warning 
  else:
     poleVectorPostionWarning = QtWidgets.QMessageBox()
     poleVectorPostionWarning.setWindowTitle('Warning')
     poleVectorPostionWarning.setText('No pole vector postion selected. Please select a pole vector postion to continue.')
     poleVectorPostionWarning.exec_()
    
     #removes non functioning partial rig
     pm.delete(ikJoints)
     pm.delete(poleCtrl)
     pm.delete(ctrl)
     ctrlJoint = self.findCtrlJoint(self.startJoint)
     pm.delete(ctrlJoint)
     return
  
  #moves the pole vector
  pm.xform(offset, ws = True, t = (x,y,z))
  #constrains the ik handle to the pole vector 
  pm.poleVectorConstraint(poleCtrl[1][0], handle[0])
     
  #makes a control for the top of the ik chain and constrains the ik chain to it 
  topCtrl = self.getSetPostion((10.0 * self.scaleFactor), 'Ik_' + str(self.startJoint), self.startJoint, ctrlType)
  self.parentConstrainObject(topCtrl[1][0], ikJoints[len(ikJoints) - 1])
  
  #locks and hides unnecessary attributes from the control
  for attr in attrList:
   pm.setAttr(str(topCtrl[1][0]) + '.' + attr, k = False, cb = False)
     
  #tries to find a child joint to make a secondary ik handle to enable rotation of the end of the ik chain
  try:
   #finds the children of the end joint 
   childJoint = pm.listRelatives(self.endJoint, c = True, type = 'joint')[0]
   childJointList = [childJoint]
   
   #makes an ik joint and parents it to the rest of the ik chain 
   ikChildJoint = self.duplicateJoints(childJointList, 'IK_')
   self.parentObject(ikJoints[0][0], ikChildJoint[0])
 
   #makes an ik handle and parents it the other handle to enable rotation at the end of the ik chain 
   ikChildHandle = pm.ikHandle(sj = ikJoints[0][0], ee = ikChildJoint[0][0])
   self.parentObject(handle[0], ikChildHandle[0])
   
   #hides the ik handles 
   pm.hide(ikChildHandle[0])
  
  #if there are no child joints lets the user know that rotation won't work but leaves the control    
  except:
   noChildWarning = QtWidgets.QMessageBox()
   noChildWarning.setWindowTitle('Warning')
   noChildWarning.setText('No child joints after the end of the IK chain. The control on the end of the chain will not be able to rotate.')
   noChildWarning.exec_()

  #returns the list of ik joints and all the controls for use in ik/fk switching function
  return ikJoints, ctrl, poleCtrl, topCtrl






To help keep making FK chains simple I divided it up into two functions. One for making the FK controls and one for assembling the chain. The make control function handles placing the controls, and constraining the FK joints to them. 


#this function sets up an fk control given a skinning joint 
def makeFKCtrl(self, skJoint, fkJoint, name, ctrlType):
 joint = self.findCtrlJoint(skJoint)
 
 #if this chain is not for an IK/FK switch parent constrain control joint to the fk joint 
 if self.switch == False:
  #constrains the control joint to the fk joint 
  self.parentConstrainObject(fkJoint, joint)
 
 #makes a control and places it on the fk joint
 ctrl = self.getSetPostion((10.0 * self.scaleFactor), name, fkJoint, ctrlType)
 #constrains the fk joint to the fk control 
 self.parentConstrainObject(ctrl[1], fkJoint)
 
 #hides unnecessary attributes on the control 
 attrList = ['translateX', 'translateY', 'translateZ', 'scaleX', 'scaleY', 'scaleZ', 'visibility']
 for attr in attrList:
  pm.setAttr(str(ctrl[1][0]) + '.' + attr, k = False, cb = False)
 
 return ctrl






The FK chain function calls the make FK controls function the right number of times and parents the controls together to make the chain.


#makes a chain of fk controls for all the specified joint and it's children     
def makeFKChain(self, ctrlType):
 #makes joints to be driven by fk controls directly 
 fkJoints = self.duplicateJoints(self.jointList, 'FK_')

 #ctrlList will contain ctrl and prnt groups 
 #starting with bottom of the chain 
 self.ctrlList = []
 #makes controls and adds there prnt groups and ctrls to a list
 for joint in range(len(fkJoints)):
  childName = str(fkJoints[joint][0])
  childCtrl = self.makeFKCtrl(self.jointList[joint], fkJoints[joint], childName, ctrlType)
  self.ctrlList.append(childCtrl)
 
 #parents the fk controls together 
 for i in range(len(self.ctrlList)-1):
  prnt = self.ctrlList[i +1]
  child = self.ctrlList[i]
  self.parentObject(prnt[1][0], child[0])
  
 return fkJoints

 






Like the FK function I divided making the IK/FK switch into two different functions one for making a control to hold the IK/FK switch attribute and one for connecting attributes and placing the control in the world.  With the function that makes the control I use the setAttr function to lock and hide all the attributes on the control since it's just made for holding the attribute to switch the mode of the joint chain from FK to IK. I then use the addAttr function to add a float attribute to be the IK/FK switch attribute. 

#makes a control to hold an IK, FK switch 
def makeIkFkSwitchCtrl(self, name, ctrlType):
 ctrl = self.makeCtrl((5.0 * self.scaleFactor), name, ctrlType)
 #gets all the keyable attributes(ones found in channel box)
 channelAttr =  pm.listAttr(ctrl[1][0], k = True)
 #this locks and hides the attributes 
 for attr in channelAttr:
  pm.setAttr(ctrl[1][0] + '.' + attr, l = True, k = False, cb = False)
 #adds attribute for IK/FK switch      
 pm.addAttr(ctrl[1][0], ln = 'IK_FK_Switch', at = 'float', max = 1.0, min = 0.0, k = True)
 return ctrl
 






I made use of a reverse node that I made with the shadingNode pymel function to make the FK input opposite the IK input since it reverses the value of the input. For example, if the switch attribute is set to 1 the reverse node gives off the value of 0. Once I made one I connected both the visibility of the controls and the weight of the IK and FK joints using the connectAttr function. I connected the IK directly to the switch attribute and the FK to the value from the reverse node. I also place the IK/FK switch on the opposite side of the body from the pole vector so that it would be placed with a predictable pattern. 


#makes an Ik/Fk switch     
def ikFkSwitch(self, fkJoint, IkJoint, skJoint):
 #makes a control for the ik/fk switch attribute
 ctrl = self.makeIkFkSwitchCtrl('Switch', 'Extra')

 #gets the Ik/Fk switch attribute off the Ik/Fk control
 switchAttr =  pm.listAttr(ctrl[1][0], c = True, k = True)[0]

 #constrains the rest of the joints and keeps track of them in a list
 constraintList = []
 for j in range(len(fkJoint)):
  ctrlJoint = self.findCtrlJoint(self.jointList[j])
  childC = pm.parentConstraint(fkJoint[j], IkJoint[0][j], ctrlJoint)
  constraintList.append(childC)

 #make a reverse node
 reverseNode = pm.shadingNode('reverse', asUtility = True)
 #connect the switch attribute to the input of the reverse node 
 pm.connectAttr(ctrl[1][0] + '.' + switchAttr, reverseNode + '.input.inputX')
   
 #connect the constraint weights to the fk/ik switch 
 #also connects control visibility to the switch 
 for constraint in range(len(constraintList)):
  attributes = pm.listAttr(constraintList[constraint], c = True, k = True)
  pm.connectAttr(ctrl[1][0] + '.' + switchAttr, constraintList[constraint] + '.' + attributes[len(attributes) -1])
  pm.connectAttr(reverseNode + '.output.outputX', constraintList[constraint] + '.' + attributes[len(attributes) -2]) 
  pm.connectAttr(reverseNode + '.output.outputX', self.ctrlList[constraint][1][0] + '.visibility') 
  pm.connectAttr(ctrl[1][0] + '.' + switchAttr, IkJoint[constraint + 1][0] + '.visibility')

 #positions the Ik/Fk switch opposite to the position of the pole vector
 ctrlJoint = self.findCtrlJoint(skJoint)
 self.parentConstrainObject(ctrlJoint, ctrl[0])
 child = pm.listRelatives(ctrl[0], c = True, type = 'transform')
 offset = child[0]
 ctrlJointTrans = pm.xform(ctrlJoint, query = True, ws = True, t = True)
 #if the user picked front
 if self.poleVectorPostion == -2:
  x = ctrlJointTrans[0]
  y = ctrlJointTrans[1]
  z = (-20 * self.scaleFactor)
 #if the user picked back 
 elif self.poleVectorPostion == -3:
  x = ctrlJointTrans[0]
  y = ctrlJointTrans[1]
  z = (20 * self.scaleFactor)
 #if the user picked right
 elif self.poleVectorPostion == -4:
  x = (20 * self.scaleFactor)
  y = ctrlJointTrans[1]
  z = ctrlJointTrans[2]
 #if the user picked left
 else:
  x = (-20 * self.scaleFactor)
  y = ctrlJointTrans[1]
  z = ctrlJointTrans[2]
 #places the switch in the world
 pm.xform(offset, ws = True, t = (x, y, z))

Pin Control Script

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])