Learning the basics of MEL scripting by building an arm rig
Kiel Figgins - 3dFiggins.com
This tutorial is intended for people with little to no scripting experience, but are curious on how a script or process is automated. As such, we are going to automate building a simple IK arm rig, so somethings will make sense and others won't, but will over time. Also, this is only about sparking further interest in the topic, not a deep dive into each facet or building an amazing arm rig :)
First off, I find it useful to think of learning to script like following a receipe, where you're not really coding, but rather automating a series of manual steps. This is also why I always recommend learning to rig and learning to script at the same time, as one of the core tenants for rigging is repetition. Repetition in building the setup then having to build it again for the opposite side or on another character. So even if you don't know how script, I'd highly recommend writing down the steps you take to build your favourite setups, so as you learn more scripting, the steps are laid out for you to chip away at. As listed above, the more explicit your written steps, the faster and more reliable your results, which in the end is point of automation: to get the same results as quickly as possible.
Overview
Broad overview of MEL Scripting
Example MEL script to build an Arm Rig
Line by Line breakdown of the Example Script
How to Create Custom Control Shapes
Conclusion and Moving Forward
Broad overview of MEL Scripting
To start the process, you'll first need to open the Script Editor (Windows > General Editors > Script Editor). A new window will open that has two sections, we'll be focusing on the lower tabbed section. If you've never opened the Script Editor before, there should be two tabs by default, a MEL and a Python. Click the MEL tab. So, as we get started, here's a broad overview of some key MEL points
MEL reads like a sentence, left to right where ; is a period or end of the sentence
Variables are noted with a $ , A variable is a container that holds information, like the name of an object, what's currently selected or a numeric value. We use variables so that our code is modular enough to reuse without rewriting, such as building the left arm then the right, you can run the same code changing the variable $side from "L" to "R".
Variables have multiple types, such as string, float, integer, but also for if they carry multiple sets of information, called Arrays. These arrays are noted with brackets at the end [], so string $sel[] , again there's a lot to learn here so feel free to investigate further
// means the line is commented out or not evaluated by Maya when the script is run. This is typically used to help add notes to the script. It's best to comment your code as you go along to help it make sense later to you or anyone else who may pick it up
You can run code inside the Script Editor's MEL tab by selecting all (ctrl+a) or selecting some lines and pressing (ctrl+enter). This comes in handy when writing small snips and evaluating the process as you go
Lastly, as you dig deeper into scripting, there are four resources to look into:
Google - Search for 'maya mel parentConstraint', typically the top result is from help.autodesk.com with a page telling you about your topic. Or if you search more broadly, you'll likely find forum posts of people asking similar question and hopefully being able to use their replies.
Other MEL scripts - Even if you don't understand the entire tool, seeing how other's have written a tool or function can give you great insight on how to format your own or clue you in on the key function you're missing
Echo All Commands - In the Script Editor's top window, you'll see it write out what you're doing in Maya. Selecting objects, creating polys, etc. You can use this as a base to start scripting your own process, but if you need to dig deeper you can turn on Script Editor > History > Echo All Commands. This will start to flood your window with everything Maya is doing and give you a bit more under the hood visibilty
whatIs - This is a MEL command you can run if you want to deep dive into Maya functions. For example, right click on a channel in the channel box > Break Connections. The Script Editor will say, CBdeleteConnection "Tip_Placer.v"; Then in the tabbed area, type : whatIs CBdeleteConnection; The results are a location to a .mel file where the MEL procedure is located. From here you can open that file and see what command is actually doing
Example MEL script to build an Arm Rig
1. Create a new Scene in Maya and open the Script Editor ( Windows > General Editors > Script Editor), once open click on a MEL tab
2. Copy/Paste all of the following code into the MEL tab (or from the downloadable script kfLearningMEL.mel)
/*
Learning to Script while Rigging - Building a basic arm rig
-Paste all these lines into a MEL tab
-Select the lines you with to run (or press ctrl+a to select all of them)
-Press (ctrl+enter) to evaluate the selected lines
To Mirror the setup and create the right side
1. Create the L side arm rig
2. Set the scaleX to -1 on Arm_Rig_Placers group
3. Change the $side variable to "R" (be sure to run that line of code again to save the change)
4. Select all the code starting at 'creating the top group' (and skipping creating the placers as they're already created)
5. Press (ctrl+enter) to evaluate the selected lines of code
*/
//User Inputs
string $side = "L";
/////////////////////////////////////////////////////////////////////////////////////////
//Work
//Create a top group for placers to help with Mirroring
string $placerGrp = `group -empty -name Arm_Rig_Placers`;
//Create Placers
string $shoulderPlacer[] = `spaceLocator -name Shoulder_Placer`;
string $elbowPlacer[] = `spaceLocator -name Elbow_Placer`;
string $wristPlacer[] = `spaceLocator -name Wrist_Placer`;
string $tipPlacer[] = `spaceLocator -name Tip_Placer`;
//Parent Placers
parent $shoulderPlacer $placerGrp;
parent $elbowPlacer $shoulderPlacer;
parent $wristPlacer $elbowPlacer;
parent $tipPlacer $wristPlacer;
//Lock Channels to keep planer for IK
setAttr -lock true ($elbowPlacer[0] + ".ty");
setAttr -lock true ($elbowPlacer[0] + ".tz");
setAttr -lock true ($elbowPlacer[0] + ".rx");
setAttr -lock true ($elbowPlacer[0] + ".rz");
setAttr -lock true ($wristPlacer[0] + ".ty");
setAttr -lock true ($wristPlacer[0] + ".tz");
setAttr -lock true ($tipPlacer[0] + ".ty");
setAttr -lock true ($tipPlacer[0] + ".tz");
//Rough Positions and orientations
setAttr ($shoulderPlacer[0] + ".tx") 1.452;
setAttr ($shoulderPlacer[0] + ".ty") 13.473;
setAttr ($shoulderPlacer[0] + ".tz") 0;
setAttr ($shoulderPlacer[0] + ".rx") 3.226;
setAttr ($shoulderPlacer[0] + ".ry") 2.615;
setAttr ($shoulderPlacer[0] + ".rz") -65.501;
setAttr ($elbowPlacer[0] + ".tx") 3.154;
setAttr ($elbowPlacer[0] + ".ry") -26.237;
setAttr ($wristPlacer[0] + ".tx") 2.402;
setAttr ($wristPlacer[0] + ".rx") 3.89;
setAttr ($wristPlacer[0] + ".ry") .725;
setAttr ($wristPlacer[0] + ".rz") -14.43;
setAttr ($tipPlacer[0] + ".tx") 1;
//Create a top group for all the elements to be created
string $topGrp = `group -em -n ($side + "_Arm_Rig")`;
//Create Joints
select -cl;
string $shoulderJoint = `joint -name ("j_" + $side + "_Shoulder")`;
select -cl;
string $elbowJoint = `joint -name ("j_" + $side + "_Elbow")`;
select -cl;
string $wristJoint = `joint -name ("j_" + $side + "_Wrist")`;
//Create Joint Align groups
string $shoulderAlign = `group -em -name Shoulder_Align`;
string $elbowAlign = `group -em -name Elbow_Align`;
string $wristAlign = `group -em -name Wrist_Align`;
parent $shoulderJoint $shoulderAlign;
parent $elbowJoint $elbowAlign;
parent $wristJoint $wristAlign;
//Align Joints
delete `parentConstraint $shoulderPlacer $shoulderAlign`;
delete `parentConstraint $elbowPlacer $elbowAlign`;
delete `parentConstraint $wristPlacer $wristAlign`;
//Parent Joints
parent $elbowJoint $shoulderJoint;
parent $wristJoint $elbowJoint;
parent $shoulderJoint $topGrp;
//Delete unneeded / empty groups to keep outliner clean
delete $shoulderAlign $elbowAlign $wristAlign;
//Create IKhandle
string $ik[] = `ikHandle -startJoint $shoulderJoint -endEffector $wristJoint -solver ikRPsolver -name ("rig_" + $side + "_IK_Arm")`;
//Create IK Control
string $temp = `curve -d 1 -p 0 0 -1 -p 0.707107 0 -0.707107 -p 1 0 0 -p 0.707107 0 0.707107 -p 0 0 1 -p -0.707107 0 0.707107 -p -1 0 0 -p -0.707107 0 -0.707107 -p 0 0 -1 -k 0 -k 1 -k 2 -k 3 -k 4 -k 5 -k 6 -k 7 -k 8`;
string $conIK = `rename $temp ("CTRL_" + $side + "_IK_Arm")`;
//Create Group above the IK Control to position at the Wrist
string $conIKAdjust = `group -em -n ($conIK + "Adjust")`;
parent $conIK $conIKAdjust;
//Align IK Control Adjust group to Wrist Joint
delete `parentConstraint $wristJoint $conIKAdjust`;
//Lock and Hide unused channels on the control
setAttr -lock true -keyable false -channelBox false ($conIK + ".sx");
setAttr -lock true -keyable false -channelBox false ($conIK + ".sy");
setAttr -lock true -keyable false -channelBox false ($conIK + ".sz");
setAttr -lock true -keyable false -channelBox false ($conIK + ".v");
//Parent the IK handle to the IK Control
parent $ik[0] $conIK;
//Orient Constraint, while maintaining offset, the wrist joint to the IK Control
orientConstraint -mo $conIK $wristJoint;
//Parent the IK Control Adjust to the top group to keep hierarchy clean
parent $conIKAdjust $topGrp;
//Create Pole Control
$temp = `curve -d 1 -p -0.5 0.5 -0.5 -p 0.5 0.5 -0.5 -p 0.5 0.5 0.5 -p -0.5 0.5 0.5 -p -0.5 0.5 -0.5 -p -0.5 -0.5 -0.5 -p -0.5 -0.5 0.5 -p -0.5 0.5 0.5 -p 0.5 0.5 0.5 -p 0.5 -0.5 0.5 -p -0.5 -0.5 0.5 -p -0.5 0.5 0.5 -p 0.5 0.5 0.5 -p 0.5 0.5 -0.5 -p 0.5 -0.5 -0.5 -p 0.5 -0.5 0.5 -p 0.5 0.5 0.5 -p 0.5 0.5 -0.5 -p 0.5 -0.5 -0.5 -p -0.5 -0.5 -0.5 -p -0.5 0.5 -0.5 -k 0 -k 1 -k 2 -k 3 -k 4 -k 5 -k 6 -k 7 -k 8 -k 9 -k 10 -k 11 -k 12 -k 13 -k 14 -k 15 -k 16 -k 17 -k 18 -k 19 -k 20`;
string $conPole = `rename $temp ("CTRL_" + $side + "_IK_Arm_Pole")`;
//Create Group above the Pole Control to position at the Elbow
string $conPoleAdjust = `group -em -n ($conPole + "Adjust")`;
parent $conPole $conPoleAdjust;
//Align Pole Control Adjust group to Elbow Joint
delete `pointConstraint $elbowJoint $conPoleAdjust`;
//Lock and Hide unused channels on the control
setAttr -lock true -keyable false -channelBox false ($conPole + ".rx");
setAttr -lock true -keyable false -channelBox false ($conPole + ".ry");
setAttr -lock true -keyable false -channelBox false ($conPole + ".rz");
setAttr -lock true -keyable false -channelBox false ($conPole + ".sx");
setAttr -lock true -keyable false -channelBox false ($conPole + ".sy");
setAttr -lock true -keyable false -channelBox false ($conPole + ".sz");
setAttr -lock true -keyable false -channelBox false ($conPole + ".v");
//Create the pole vector constraint
poleVectorConstraint $conPole $ik[0];
//Parent the Pole Control Adjust to the top group to keep hierarchy clean
parent $conPoleAdjust $topGrp;
//Select IK Control to test results
select $conIK;
//Print Success
print ("\n\nSUCCESS: " + $side + " Arm Rig Created!\n\n");
Line by Line breakdown of the Example Script
Let's go through the script line by line:
At the top of the script, I used /* and */ to add a bunch of text explaining what the script does, where I left off, problems I hit and so on. Anything inside these characters will not be evaluated by Maya when the script is run, so you can be more freeform with the formatting.
/*
Learning to Script while Rigging - Building a basic arm rig
-Paste all these lines into a MEL tab
-Select the lines you with to run (or press Ctrl+A to select all of them)
-Press (ctrl+enter) to evaluate the selected lines
To Mirror the setup and create the right side
1. Create the L side arm rig
2. Set the scaleX to -1 on Arm_Rig_Placers group
3. Change the $side variable to "R" (be sure to run that line of code again to save the change)
4. Select all the code starting at 'creating the top group' (and skipping creating the placers as they're already created)
5. Press (ctrl+enter) to evaluate the selected lines of code
*/
User Inputs are the specific details you're passing to the script, can you think of this as the fields you'd see in a finished tool's UI. This information can include the name (ie, Arm), the side (ie, L or Lf), an option to mirror the setup and so on. In this example, we are only expecting one variable from the user, which is the the naming for which arm they want to build.
//User Inputs
string $side = "L";
This script breaker is just a habit that I do, to help me know where the user inputs stop and where the script begins. As your scripts get more complex and the inputs get more numerous the line may blur a bit.
/////////////////////////////////////////////////////////////////////////////////////////
//Work
So now the scripting actual begins. The first line is commented out and giving a brief description what this section of code does. As mentioned above MEL reads like a sentence so if you try to read it, it would say "Define variable $placerGrp with the following, a created group that is empty with the name Arm_Rig_Placers." There's a lot of formating and bits to explain there, but again this will start to make more sense as you try it yourself. In short, the `` mean "do this action with these settings", in this case create an empty group with a specific name. The results of that action, ie the new group, is then defined as a variable that you can call later in the script.
//Create a top group for placers to help with Mirroring
string $placerGrp = `group -empty -name Arm_Rig_Placers`;
Next is creating the actual placers objects for our Arm Rig, in this case we're using locators. Using the same format as the $placerGrp above, we create each one. You'll notice that there are [] at the end of the variable, this means the variable is an array, or holding a set of information not just a single piece. You'll see how this is used later on.
//Create Placers
string $shoulderPlacer[] = `spaceLocator -name Shoulder_Placer`;
string $elbowPlacer[] = `spaceLocator -name Elbow_Placer`;
string $wristPlacer[] = `spaceLocator -name Wrist_Placer`;
string $tipPlacer[] = `spaceLocator -name Tip_Placer`;
With the placers created, I want to parent them in a meaningful way so that when I go to orient them my character they aren't just free floating. Again, reading the line as a sentence, "Parent the Shoulder_Placer locator to the Arm_Rig_Placers group" and so on.
//Parent Placers
parent $shoulderPlacer $placerGrp;
parent $elbowPlacer $shoulderPlacer;
parent $wristPlacer $elbowPlacer;
parent $tipPlacer $wristPlacer;
Now a bit of rigging and foresight comes into play. I want to lock the channels on the placers to my IK arm rig will build correctly. To do so, I only want the elbow to translate along the translate X axis to make the upper arm longer, and only rotate Y as the elbow is a hinge joint. Locking the other channels make sure the arm joints stay planer and the IK chain and pole vector work as intended. I also want to lock the wrist translate Y and Z so that it can also only make the forearm longer.
//Lock Channels to keep planer for IK
setAttr -lock true ($elbowPlacer[0] + ".ty");
setAttr -lock true ($elbowPlacer[0] + ".tz");
setAttr -lock true ($elbowPlacer[0] + ".rx");
setAttr -lock true ($elbowPlacer[0] + ".rz");
setAttr -lock true ($wristPlacer[0] + ".ty");
setAttr -lock true ($wristPlacer[0] + ".tz");
setAttr -lock true ($tipPlacer[0] + ".ty");
setAttr -lock true ($tipPlacer[0] + ".tz");
If you're building a rough placer template for a larger AutoRig, you can speed up the process of placing the placers to a character if they're in a rough humaniod shape to begin with. Here I've taken the values from moving the Placers to fit my model and copied the values from the channelbox and pasted them per line. The sentence reads, "Set the attribute on Shoulder_Placer's translate X channel to the value of 1.452."
//Rough Positions and orientations
setAttr ($shoulderPlacer[0] + ".tx") 1.452;
setAttr ($shoulderPlacer[0] + ".ty") 13.473;
setAttr ($shoulderPlacer[0] + ".tz") 0;
setAttr ($shoulderPlacer[0] + ".rx") 3.226;
setAttr ($shoulderPlacer[0] + ".ry") 2.615;
setAttr ($shoulderPlacer[0] + ".rz") -65.501;
setAttr ($elbowPlacer[0] + ".tx") 3.154;
setAttr ($elbowPlacer[0] + ".ry") -26.237;
setAttr ($wristPlacer[0] + ".tx") 2.402;
setAttr ($wristPlacer[0] + ".rx") 3.89;
setAttr ($wristPlacer[0] + ".ry") .725;
setAttr ($wristPlacer[0] + ".rz") -14.43;
setAttr ($tipPlacer[0] + ".tx") 1;
Here we start to use the User Input from the top of the script. We're about to create the actual rig elements, so we want to keep the Outliner and Hierarchy clean while we work, so we want to create a top group to hold all the elements about to be created. And to make sure the name doesn't conflict with any other top group, we use the User Input of $side, "L", to say, "Create an empty group with the name L_Arm_Rig."
//Create a top group for all the elements to be created
string $topGrp = `group -em -n ($side + "_Arm_Rig")`
Now to create the joints. Something new here is the 'select -cl;' line. This means clear your selection, as if you clicked an empty area in your viewport. The reason we do this is to prevent the joint creation's default behaviour of parenting newly created joints to whats currently selected. Beyond that, the creation is the same as above, only now you can see that we're using $side in the middle of a name with the + on each end. You can make more and more complex names using this approach.
//Create Joints
select -cl;
string $shoulderJoint = `joint -name ("j_" + $side + "_Shoulder")`;
select -cl;
string $elbowJoint = `joint -name ("j_" + $side + "_Elbow")`;
select -cl;
string $wristJoint = `joint -name ("j_" + $side + "_Wrist")`;
This is a bit tricky if you're not familiar with rigging. Effectively, if you just create a joint chain at the Placers, the rotation axis will be off or not aligned to the placers (see the second image). Then if you try instead to build joints at origin and Align them to the locators, they'll have non-zero rotation on them, and that can cause issues once you start building your rigging elements. So what we'll do instead is build the joints at origin, parent them in a group at origin, align that group to the placer, then parent the joints in a chain. Again, there's more to understanding this, but we'll skip that for now. So for now, we'll create the empty groups and then parent the joints to each one.
//Create Joint Align groups
string $shoulderAlign = `group -em -name Shoulder_Align`;
string $elbowAlign = `group -em -name Elbow_Align`;
string $wristAlign = `group -em -name Wrist_Align`;
parent $shoulderJoint $shoulderAlign;
parent $elbowJoint $elbowAlign;
parent $wristJoint $wristAlign;
This again is kind of a quick hack to getting one object to align with another. It says,"Parent Constrain the Shoulder_Align group to the Shoulder_Placer Locator, then delete the Parent Constraint." This aligns the group to the placer and removes the connection of the constraint, while leaving the necessary values. We'll repeat this for each joint.
//Align Joints
delete `parentConstraint $shoulderPlacer $shoulderAlign`;
delete `parentConstraint $elbowPlacer $elbowAlign`;
delete `parentConstraint $wristPlacer $wristAlign`;
Now that the joints are aligned, we need to parent them like a typical arm chain and at the end, parent the shoulder joint to the top group to keep the Outliner clean.
//Parent Joints
parent $elbowJoint $shoulderJoint;
parent $wristJoint $elbowJoint;
parent $shoulderJoint $topGrp;
Since we no longer need the Align groups, we'll delete them all at once here.
//Delete unneeded / empty groups to keep outliner clean
delete $shoulderAlign $elbowAlign $wristAlign;
Now this next line can look scary, but it's building off what we've done so far. It says,"Create an ikHandle starting at j_L_Shoulder and ending at j_L_Wrist, that uses and RotatePlane solver (so we can hook up a Pole Vector Control), then name it rig_L_IK_Arm." Now you may be wondering,"How am I supposed to know those flags for start joint and such?", well there are a few ways. First, if you have your Script Editor open and manually do those steps, clicking on Create IKHandle, clicking on the shoulder joint then the wrist joint, you'd see the Script Editor show the ikHandle code. You could then do a Google search for 'maya mel ikHandle' and look at the help docs. At the bottom of the help docs are examples of how to write the full line of code along with what the actual flags mean.
//Create IKhandle
string $ik[] = `ikHandle -startJoint $shoulderJoint -endEffector $wristJoint -solver ikRPsolver -name ("rig_" + $side + "_IK_Arm")`;
Here we'll be building our first control that the animator will use to manipulate the rig. The actual curve shape creation is covered later in the tutorial, you can check it out here. Beyond creating the control shape, we need to rename it, which in this instance is a little different. Typically we would use the -name flag in the curve creation, however, doing so would only name the transform of the object, not the shape node, see the image for an example. However, if we use the rename command, it renames both appropriately. This may seem small, and does require a bit more explaination, but for now we'll move on. So this section reads,"Create a curve with these settings and store it to a temporary variable. Then rename the newly created curve as CTRL_L_IK_Arm and store it as a new variable."
//Create IK Control
string $temp = `curve -d 1 -p 0 0 -1 -p 0.707107 0 -0.707107 -p 1 0 0 -p 0.707107 0 0.707107 -p 0 0 1 -p -0.707107 0 0.707107 -p -1 0 0 -p -0.707107 0 -0.707107 -p 0 0 -1 -k 0 -k 1 -k 2 -k 3 -k 4 -k 5 -k 6 -k 7 -k 8`;
string $conIK = `rename $temp ("CTRL_" + $side + "_IK_Arm")`;
Here we're creating a group above the control. We can use this parent group to move and align the control to the wrist joint and since the transforms are on the parent group, the channelbox for the control itself remain 0,0,0.
//Create Group above the IK Control to align at the Wrist
string $conIKAdjust = `group -em -n ($conIK + "Adjust")`;
parent $conIK $conIKAdjust;
Using the same approach as aligning the joints to the placers, we'll create and delete a parent constraint on the control group to align it to the wrist joint. This does introduce a slight issue though. Since the arm is at an angle, the IK control will also be at an angle, meaning the translate axis' will no longer be globally oriented. If you wanted to keep the global translate, you'd need to put the rotation on the IK control itself. Long story short, but you can read more about it here http://www.3dfiggins.com/Store/Support/FAQ/#IKValues , since this tutorial is not about building an amazing arm rig, we'll move on for now.
//Align IK Control Adjust group to Wrist Joint
delete `parentConstraint $wristJoint $conIKAdjust`;
Since the IK control is only going to be animated on translate and rotate, we'll lock and hide the unused channels to keep it cleaner when it's animated.
//Lock and Hide unused channels on the control
setAttr -lock true -keyable false -channelBox false ($conIK + ".sx");
setAttr -lock true -keyable false -channelBox false ($conIK + ".sy");
setAttr -lock true -keyable false -channelBox false ($conIK + ".sz");
setAttr -lock true -keyable false -channelBox false ($conIK + ".v");
If you were to move the IK control, it wouldn't effect the arm. We need to parent the IK handle itself to the control to get the desired results. You'll notice this is the same parent command but a slightly different formating for the $ik variable. Since the $ik variable is an array, shown with the [], the variable contains two pieces of information, the first is the actual IK handle, the second is the end effector. Since we only want to parent the IK handle, we specify that by saying $ik[0]. This would be read as,"Parent the first item in the IK array, the IK handle, to CTRL_L_IK_Arm."
//Parent the IK handle to the IK Control
parent $ik[0] $conIK;
Now you can move the IK control and the arm bends with it! But if you try to rotate the IK control, the joint doesn't respond. So we need to create an orient constraint, and we'll use the -mo (maintain offset) flag. This flag says that no matter the angle of the IK control, constrain the wrist joint to the IK control without changing the orientation.
//Orient Constraint, while maintaining offset, the wrist joint to the IK Control
orientConstraint -mo $conIK $wristJoint;
Now that we've created and constrained the IK Control, clean up the hierarchy by parenting the the control group to the top group.
//Parent the IK Control Adjust to the top group to keep hierarchy clean
parent $conIKAdjust $topGrp;
Now that you've gone through the process of creating the IK control, creating the Pole Vector control will follow most of the same steps. You can also copy/paste the code you created for the IK control, and edit it slightly to work for the pole vector. A lot of scripting is like this, figuring it out in one area and reusing the code again in another. As such the following descriptions will be much the same as above. So here we're creating curve and renaming it to make sure the shape node is also renamed.
//Create Pole Control
$temp = `curve -d 1 -p -0.5 0.5 -0.5 -p 0.5 0.5 -0.5 -p 0.5 0.5 0.5 -p -0.5 0.5 0.5 -p -0.5 0.5 -0.5 -p -0.5 -0.5 -0.5 -p -0.5 -0.5 0.5 -p -0.5 0.5 0.5 -p 0.5 0.5 0.5 -p 0.5 -0.5 0.5 -p -0.5 -0.5 0.5 -p -0.5 0.5 0.5 -p 0.5 0.5 0.5 -p 0.5 0.5 -0.5 -p 0.5 -0.5 -0.5 -p 0.5 -0.5 0.5 -p 0.5 0.5 0.5 -p 0.5 0.5 -0.5 -p 0.5 -0.5 -0.5 -p -0.5 -0.5 -0.5 -p -0.5 0.5 -0.5 -k 0 -k 1 -k 2 -k 3 -k 4 -k 5 -k 6 -k 7 -k 8 -k 9 -k 10 -k 11 -k 12 -k 13 -k 14 -k 15 -k 16 -k 17 -k 18 -k 19 -k 20`;
string $conPole = `rename $temp ("CTRL_" + $side + "_IK_Arm_Pole")`;
Creating a group above the control that we can position at the elbow joint so when we apply the pole vector constraint the arm angle doesn't change.
//Create Group above the Pole Control to position at the Elbow
string $conPoleAdjust = `group -em -n ($conPole + "Adjust")`;
parent $conPole $conPoleAdjust;
Aligning the Pole control group to the elbow joint, but in this instance we're using a point constraint since the pole vector control is animated in translate only, not having rotation above it will keep the translate animation curves clean and readable in the Graph Editor.
//Align Pole Control Adjust group to Elbow Joint
delete `pointConstraint $elbowJoint $conPoleAdjust`;
Since the Pole Control is only animated with translate channels, we'll lock and hide the rotate, scale and visibility channels to keep the control clean for the animator.
//Lock and Hide unused channels on the control
setAttr -lock true -keyable false -channelBox false ($conPole + ".rx");
setAttr -lock true -keyable false -channelBox false ($conPole + ".ry");
setAttr -lock true -keyable false -channelBox false ($conPole + ".rz");
setAttr -lock true -keyable false -channelBox false ($conPole + ".sx");
setAttr -lock true -keyable false -channelBox false ($conPole + ".sy");
setAttr -lock true -keyable false -channelBox false ($conPole + ".sz");
setAttr -lock true -keyable false -channelBox false ($conPole + ".v");
With the Pole control created, we need to create the actual constraint. You can see the code used by having the Script Editor open when you do the process manually and seeing what gets printed. You'll also notice that we're again using the $ik variable as an array, we're we only want the constraint applied to the first item in the array, in this case the actual ik Handle.
//Create the pole vector constraint
poleVectorConstraint $conPole $ik[0];
With the Pole control created and the constraint hooked up, let's keep the Outliner clean by parenting the control group to the top group.
//Parent the Pole Control Adjust to the top group to keep hierarchy clean
parent $conPoleAdjust $topGrp;
Now that the arm rig is complete, I like to select the IK control at the end, so I can quickly test and check the results.
//Select IK Control to test results
select $conIK;
To finalize the script and to give you or the user insight on the script's progress, you can use a print command to say,"Print two blank lines, then print SUCCESS: L Arm Rig Created!, then print two more blank lines."
//Print Success
print ("\n\nSUCCESS: " + $side + " Arm Rig Created!\n\n");
How to Create Custom Control Shapes
Creating custom control shapes not only make your rig more appealing, but also help convey what the character is doing even if the mesh is hidden. By default, a lot of setup artists will just use a nurbs circle, though funcional, there are quiet a few issues with this from an animation stand point.
A nurbs circle doesn't show on axis rotation, like how much a control is twisting
Once a rig gets animated and controls start to overlap, circles upon circles can be tedious to identify and select the one you want
From certain angles, the shape will almost vanish as there's not verticality to the shape
Rarely is a circle representational of the volume that control is manipulating
To you can create you own custom shapes and save them to a library that your future rigs and scripts can pull from.
1. Open the Script Editor ( Windows > General Editors > Script Editors )
2. Create a polygon object at origin, in this case a polyCube
3. Create a linear CV curve ( Create > CV Curve > Option Box - (X) Linear
4. Set the viewport to wireframe and while holding down the 'v' key (to turn on Vertex Snapping), click on each corner of the polyCube
5. When done, click 'Enter' on your keyboard to finish the shape
6. In the Script Editor's output window, you'll see a new line
curve -d 1 -p -0.5 0.5 -0.5 -p 0.5 0.5 -0.5 -p 0.5 0.5 0.5 -p -0.5 0.5 0.5 -p -0.5 0.5 -0.5 -p -0.5 -0.5 -0.5 -p -0.5 -0.5 0.5 -p -0.5 0.5 0.5 -p 0.5 0.5 0.5 -p 0.5 -0.5 0.5 -p -0.5 -0.5 0.5 -p -0.5 0.5 0.5 -p 0.5 0.5 0.5 -p 0.5 -0.5 0.5 -p 0.5 -0.5 -0.5 -p 0.5 0.5 -0.5 -p 0.5 -0.5 -0.5 -p -0.5 -0.5 -0.5 -k 0 -k 1 -k 2 -k 3 -k 4 -k 5 -k 6 -k 7 -k 8 -k 9 -k 10 -k 11 -k 12 -k 13 -k 14 -k 15 -k 16 -k 17 ;
7. Copy this line and save it to a text document along with other custom shapes you've created. Now your library of shapes can be used and shared on future rigs
Conclusion and Moving Forward
Hopefully this tutorial has shown you that learning to MEL script can be fairly straight forward as well as showing the potential of learning such a skill set. While scripting the rig at the same time you're building it will front load the time it takes, you'll realize quickly that you can update, edit and recreate the setup so much faster with each new iteration. This not only takes some of the headache out of rigging a new character, but gives you additional knowledge to solve other projects by reusing snippets of code in other instances.
If you'd like to continue learning more, there are additional resources online. For this script specifically, the next steps may include making the placer creation, rig creation into separate procedures, and making mirroring an option to building a user interface to wrap it all together. Then it would improving the actual rig being created, and moving on to another limb like a leg or spine and so on.
All and all, being able to understand the basics of scripting will allow you to rig faster and build upon your ideas without having to remember every detail. Good luck!
Other Opinions, Further References, Typos, and Grammar Issues please contact KielFiggins22@gmail.com