This is my first post at XSIBlog, so I want to thank Patrick and the other authors for keeping this going. I have always wanted to share but time is always against me not to mention having something worthy of sharing.
PLEASE NOTE: The following python scripts require this procedure to work properly.
A thread started at XSI Base simply named, facial animation (questions), many facial rigging and animation concepts were shared. The two concepts discussed mostly were your traditional shape animation controlled by in scene controllers (a la Osipa) and also using Control Splines or curves with many complex rig elements that give you the desired motion. At the end of the day shape animation gets you pretty far with many character types ( realistic and stylized ) and setup time is pretty fast. The setup time is also important here, Softimage considers this a feature for FaceRobot that they market and rightfully so. Time is money, producers know this as should you! So if you will hear me out I am going to explain a way for you to continue to use shape animation and still get the low level control over subtle face movements.
The Static KineState
What is the Static KineState? I am sure anyone spending any amount of time rigging knows something about it. So lets define it for those that don’t.
The Static KineState indicates that an envelope is applied to the skeleton element. The StaticKineState is the initial position of the skeleton when the envelope was applied, and is used for envelope calculations.
If this property is storing these initial values then I must be able to change them. This is the gem that makes this rig work.
Now we are going to make the rig. This is not intended to be a tutorial so I apologize if I miss steps, all the project files are included at the bottom. So lets go over the rig!
I am starting with Primitive>Character>Face Man and I first made a quick “smile” shape, followed by a typical neck, head, and jaw for the character. I used nulls instead of joints for simplicity sake.
Now I am going to make a clone of this enveloped head and immediately remove the duplicated shape cluster, envelope cluster, and the “ShapeWeights” property. The clone uses the “CopyOp”, with this we get all the deformations from the first head which include the envelope deform and shape deform. Now we need to setup the local deformers for the cloned mesh.
These local deformers use an “Object To Cluster” constraint. I like to use edge clusters but you could use any SubComponent you choose. So I picked an edge in the corner of the mouth and ran this handy script that creates the cluster, the object, constrains them, and makes the local deformer. You need to select a component on the original head, and run the script.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
#imports import win32com.client from win32com.client import constants #globals xsi = win32com.client.Dispatch( "XSI.Application" ).Application xsiPrint = xsi.LogMessage def setupDorrito(): if xsi.Selection < 1: xsiPrint("Select an component!",constants.siError) return False root = xsi.ActiveSceneRoot #create cluster constrained null, setup its look clsCnsNull = root.AddNull("clsCnsNull") clsCnsNull.primary_icon.value = 0 clsCnsNull.size.value = 0.1 clsCnsNull.shadow_icon.value = 7 clsCnsNull.shadow_colour_custom.value = 1 clsCnsNull.B.value = 1 clsCnsNull.G.value = 0.5 #make cluster from selection subComponent = xsi.Selection(0).subComponent cnsCls = subComponent.CreateCluster("cnsCls") #constrain object to cluster cns = clsCnsNull.Kinematics.AddConstraint("ObjectToCluster",cnsCls) cns.tangent.value = 1 cns.dirx.value = 0 cns.diry.value = -1 cns.upvct_active.value = 1 cns.upx.value = 1 cns.upy.value = 0 #make dorrito dorrito = root.AddNull("dorrito") dorrito.primary_icon.value = 0 dorrito.size.value = 0.2 dorrito.shadow_icon.value = 8 dorrito.shadow_colour_custom.value = 1 dorrito.G.value = 1 dorrito.shadow_offsetX.value = 0.1 dorrito.shadow_scaleX.value = 0 clsCnsNull.AddChild(dorrito) dorrito.kinematics.local.transform = XSIMath.CreateTransform() setupDorrito()
At work we call this controller a “Dorrito” because when the first “on the face” rig came out, one of our animators, Jason Taylor, started calling it that and it stuck. So my controller is a flat pyramid that looks like a Dorrito, so I suggest you do the same :) Now I will add an envelope to the cloned mesh and choose this Dorrito as a deformer. I now need to paint the cloned mesh’s envelope. The way I do this is add another deformer to the deformer list, set all the points to 100 % of this new deformer, and then choose the Dorrito and paint it’s influence. Once I am happy with the deformation I just remove the extra deformer from the envelope and I get zero weights except for where I painted the Dorrito’s influence. This deformer moves with the cluster constrained null, but it causes this nasty double transform when I rotate the head!
Why is this happening? Because you have the first envelope on the original head which is deforming the points based on the transformation of the deformers (neck, head, and jaw). Then you have a “CopyOp” and another envelope above that is getting transformed by the same movement so XSI just does it twice! What you want to do is subtract that first envelope transformation from the second envelope transformation. The Static KineState gives you access to that intial position. If you take your deformer’s Static KineState property and set it’s parameters equal to the global transform parameters of your cluster constrained null you will effectively be constantly reseting the deformer’s initial position. Here is a script to setup the expressions between the cluster constrained null and the local deformer. Just select cluster constrained null first, then the Dorrito.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
#imports import win32com.client from win32com.client import constants #globals xsi = win32com.client.Dispatch( "XSI.Application" ).Application xsiPrint = xsi.LogMessage def setupStaticState(): defList =  clsList =  for i in xrange(0,len(xsi.Selection),2): clsList.append(xsi.Selection(i)) defList.append(xsi.Selection(i+1)) for a,b in zip(defList,clsList): defKineState = a.Properties("Static KineState") clsKine = b.Properties("Kinematics") defKineState.orix.AddExpression(clsKine.FullName + ".global.rotx") defKineState.oriy.AddExpression(clsKine.FullName + ".global.roty") defKineState.oriz.AddExpression(clsKine.FullName + ".global.rotz") defKineState.posx.AddExpression(clsKine.FullName + ".global.posx") defKineState.posy.AddExpression(clsKine.FullName + ".global.posy") defKineState.posz.AddExpression(clsKine.FullName + ".global.posz") setupStaticState()
Now you can rotate the head or jaw and the deformer just rides the mesh, you can even use shape animation and the deformer just rides on top of that with no double transform.
There are many ways to rig a face and none should be discarded from any rigger's toolbox. I hope other riggers will get some use out of this technique and will share their work with us. Before I go, I want to thank my rigging supervisor Remi McGill for suggesting the usage of the Static KineState and Blur for allowing me to share with the community. Below are the project files which includes the scripts. Thanks for reading!