Welcome to the first Riggy Bits, a series about creating better rigs, faster. This will include novel ideas for rigging characters, props, and effects. It will also include workflow tips, to do and not to do's, and how-to's. I want to keep these articles brief, practical, accurate, and reusable. So without further ado, Riggy Bits #1!
This Riggy Bit is for Blender artists with a moderate level of experience that want to learn how to work more efficiently. In particular, you should understand armatures, pose-mode, parenting, and constraints. You should be proficient in basic Python, and spend some time learning it for yourself (there's a great tutorial on the Blender Cloud by Dr. Sybren A. Stüvel. So go ahead, support the Blender Institute by joining the Blender Cloud)
Using Scripts to speed up your workflow in Blender
Recently, I was rigging a character using the Copy Rotation constraint. I didn't know for sure what I was doing, and I was trying to make changes to several chains of nine bones each --some of which had multiple constraints. I needed to try several different patterns of constraint settings on many different bones. In this example, we begin with two chains of bones constrained to copy the controller's rotation (World <--> World). (In the examples, the change will be visible on the leftmost chain. The center chain has the same local space as the rightmost chain, so it looks the same in both examples)
Say you want to change the constraints to work in (Local <--> Local) space. If you don't use scripts to do it, you have to do it the slow way:
This is terrible! An entire minute of clicking on small boxes just to try something out! What if the change doesn't do what you want? Expect to repeat the process in reverse, or wear out your CTRL and Z keys (but for big files, undo can be slow, too). If you don't know ahead of time what you're doing, this kind of tedious task can kill your momentum when rigging, and delay you when you have deadlines looming. If you have a lot of decision making to do, mistakes creep in quickly. Some constraints will use the wrong settings when, because of boredom, you stop paying attention.
Above is the result of the previous constraint change. Look at the left chain of bones to see the difference.
There's a better way!
I knew that doing it by hand wasn't a viable option for me. I was dealing with about fifty bones! Moreover, I knew I would have to try several different sets of options before I got what I wanted. So I decided to use Python scripts to speed up the process. Here's the fast way (watch the "space" of the constraint, in the bottom right corner. It changes for all of the bones as soon as the script is run):
Look at that! The same result in less than half the time! It's much easier, and it saves me time to try many different settings, without wearing myself out. It's easy to reuse, simply by changing the space variables.
So how does it work?
The script is actually very simple. Let's take a look:
for b in C.selected_pose_bones: for c in b.constraints: try: if (c.owner_space == 'LOCAL'): c.owner_space = 'LOCAL_WITH_PARENT' c.use_offset = False if (c.target_space == 'LOCAL'): c.target_space = 'LOCAL_WITH_PARENT' c.use_offset = False except: pass
I love Blender's Python API because it is organized in the same way as the UI. An armature object is made of bones, which may be in Pose Mode or Edit Mode. Since I'm in pose mode, I ask Blender for all of the pose bones I have selected in the current context (the viewport). Then, for each bone Blender gives me, I ask for a list of its constraints. Finally, I ask each constraint if it is doing something the way I want it to or not. If it's not doing what I want, I correct its behavior and move on to the next constraint.
Here's a line-by line comment of my code.
for b in C.selected_pose_bones: # CAPITAL C # this line loops through the bones I have selected. #C = bpy.context, this is always pre-defined in Blender's Python console for c in b.constraints: # lower case c - different variable #this line loops through the constraints in each bone (b) try: #we have to use a try/except block, in case the current constraint # doesn't have a particular property. #I prefer to write code that never raises exceptions, but this is # meant to be quick to write and run so it doesn't slow me down #Here, just about any logic can be applied. The first thing to do # is test for something useful. #In this case, I want to change the constraint from # (LOCAL <--> LOCAL) to (LOCAL WITH PARENT <--> LOCAL WITH PARENT) if (c.owner_space == 'LOCAL'): #I'm checking the first property I want to change, since if it is # already right, I don't want to change it. #I might just as well test the name of the constraint here: # if (c.name == "Desired Name"): c.owner_space = 'LOCAL_WITH_PARENT' # Change the owner space c.use_offset = False #This is a property I like to change often if (c.target_space == 'LOCAL'): #Since I'm changing two properties I'm checking twice. It isn't # necesary, but it doesn't matter. The script is fast enough anyway. c.target_space = 'LOCAL_WITH_PARENT' #change target space c.use_offset = False except: pass #In this case, i didn't want anything to happen if the constraint # didn't meet any of the things I tested for, so I wrote "pass" #However, if you want to do something here, you can! # for example: # b.constraints.remove(c) #this will delete the constraint
Taking it further:
The script above is a template. We can adapt this code to do many different tasks, but first let's start with just the guts of the code from above.
for b in C.selected_pose_bones: # loop through selected bones for c in b.constraints: # loop through constraints try: if (someCondition): #do something to constraints that pass the test except: pass #or do something to constraints that failed the test. #Python also gives you these options # else - for code that runs when there are no errors # finally- for code that always runs.
Now, let's say we have a bunch of bones. Some have a copy rotation constraint. Some have a stretch-to constraint. Some have a limit scale constraint, some have two. We don't want to mess with the second limit scale constraint, or even touch the properties of one bone, named "MECH-KeyBone". That's a lot of stuff to keep in mind, but it's easy to write the code to do it.
Since this script is long, I opted to use the RUN SCRIPT button instead of the console. To make a script work with the run script button, you have to add the line
import bpy to the top of your scripts. I also defined the variable C again - it's already defined in the console, but not in my script.
import bpy C = bpy.context for b in C.selected_pose_bones: if (b.name != "MECH-KeyBone"): # don't want to change this bone for c in b.constraints: try: if (c.type == "COPY_ROTATION"): c.owner_space = 'LOCAL' c.target_space = 'LOCAL' if (c.type == "LIMIT_SCALE"): if (c.name != "Limit Scale.001"): #the second constraint c.use_min_x = True c.min_x = 0.1 c.use_max_x = True c.max_x = 4 c.use_min_y = False if (c.type == "STRETCH_TO"): c.influence = 0.5 except: pass
See how the script satisfies all of the conditions we set for it? All copy rotation constraints now operate in (Local <--> Locale) space. All stretch-to constraints are at 0.5 influence, and only the first Limit Scale constraint has been changed. Finally, MECH-KeyBone remains untouched. Scripts are the solution to these types of complex problems, because they can operate on any number of conditions without forgetting their place, like a human does!
The takeaway: It's a good habit to learn scripting, to write scripts that are easy to adapt, and to keep them in your text editor to copy and paste them into your console.
I keep about a dozen scripts in my smallUsefulScripts file. Find and replace in bone/constraint names, changing format, fixing Left/Right suffixes, removing constraints by name, etc. I also write longer scripts for complex tasks, like creating a unique constraint for several bones based n a template. Try to write a list of tasks you do far too often when rigging. These are the tasks you should automate!
An Important Nuance:
This is a little outside the scope of this article, but it's important to know that Blender draws a distinction between Pose Bones and Edit Bones. It's an important distinction that keeps rigs and animation curves working, even when the objects in question are linked and appended to different .blend files. I'll probably describe the usefulness of this feature in another Riggy Bit. Anyways, because there is a distinction, you will have to write your scripts for either Pose Mode or Edit Mode. It's easy to do, just change "selected_pose_bones" to "selected_editable_bones".
Know also that constraints don't belong to edit bones. They only belong to pose bones. Edit bones only exist in edit mode- you can't change them or even query them in any other context- because they aren't there! Blender creates them whenever you enter edit mode and destroys them on exit. They're only "fresh" if you access them in edit mode, without changing modes. In the past, I've written scripts with extremely confusing, bad behavior because I tried to access edit mode data after leaving and re-entering Edit Mode. This undefined behavior is really hard to track down and debug, since it might behave perfectly normally some of the time. It's essentially random. Don't do this.
Finally, know that pose bones belong to the object, so they always stay in context. However, it's a little harder to access them in other modes besides Pose, because you can't use bpy.context to query the selection (since after all, edit bones aren't pose bones). Instead, you have to find the pose bone with the same name as the edit bone.
for eb in C.selected_editable_bones: for pb in C.active_object.pose.bones: if (pb.name == eb.name): #do something # there's a prettier way to write this with one for-loop. Can you figure it out? # hint: zip()
There's actually one more conception of bones in Blender's Python API, "bone". This belongs to the armature data of an armature object, and has all the same properties as edit bones. However, these bones can't be edited (their properties are read-only). Like Pose Bones, these bones always stay in context, so if you need edit-mode information outside of Edit mode, you can query the armature data's bone. You can also get there from the pose bone by accessing "PoseBone.bone". It may seem confusing, but this way of organizing things keeps data safe unless you want to change it (by entering edit mode). It gives Blender a reference to the current state of the bone and its rest state. Recently, I've been using another software to rig for a short film, and I miss Blender's modes so, so much. Don't take them for granted!
Here are a few tips to make the process a little easier to use in your own rigs.
#1: Use the info bar
Often, you don't know what the Python words are for the data you want to modify. Fortunately, the info bar shows all Python commands that Blender receives from the actions of the User. It also shows the name of the properties you've changed, and what you've changed them to. Most of the time, this will be relative to the context (bpy.context) or the current ui (bpy.ops.ui). Usually, you'll see commands listed in the form of operators (bpy.ops). Take care: operators are good enough for quick scripts (in the Console), but because they rely on the context and because they can be slow, it's usually best to go through bpy.data instead (to modify the data directly). This is a topic for a more advanced coding tutorial, so I won't go into it here.
#2 Use "Copy Data Path":
Whenever you need to get to the Python Code for anything in Blender's UI, just hover your mouse over, right click, and choose Copy Data Path. What you get is usually only valid in its own context, though, so it can be confusing at times.
In the above example, Copy Data Path gives me a property for the Cube's modifier: modifiers["Mirror"].use_clip. However, if I wanted to get to this value in a script, this is the code I would need:
for ob in bpy.context.selected_objects: for m in ob.modifiers: try: m.use_clip = True except: pass
The console doesn't always know what context you want it to work in. It's better to loop through objects and their modifiers, or at least go there directly (bpy.data.objects["Cube"].modifiers["Mirror"].use_clip = True). Pay attention when using Copy Data Path, and learn the Python API well enough to figure out how to get to the property in question! Always write your scripts to be easy to use, just by copying and pasting or changing a variable before running.
#3 Use Blender's Python API Documentation
This is obvious, but usually the answers are found in the documentation.
#4 Use Python Tooltips
For versions of Blender 2.79 and older, this is the default behavior. For Blender 2.8 and above, you'll need to enable it. I'll go ahead and enable Developer Extras, too, since I like to know the indices of vertices in a mesh. It helps me to write Python code that uses meshes.
Python tooltips are great, because they show you the generic path to the data, as well as the specific path to the data. The generic path shows the type of the data and the property underneath the mouse. This is used when looping through a group (like "LimitScaleConstraint.use_min", which is c.use_min above), or searching the reference. The specific path shows how to get to only the data in question- the particular property in the specific modifier on the exact Suzanne.002 in the .blend file. If you ever need to address a property directly, use this path.
#5 The Console
This one is so useful as a rigger, it deserves its own Riggy Bit. Windows users can show it by choosing "Window> Toggle System Console" from the menu at the top. For Mac and linux users, you'll need to start Blender from the console. Fortunately, for Mac users, there's an easy way: simply open the Package Contents of your Blender folder and find the MACOS folder. Inside, there will be a black box icon named "blender". Doulble click to launch from the console. Source: https://blender.stackexchange.com/questions/102860/how-to-open-system-console-to-get-output-from-blender-on-mac-os-10 (This goes into a little more detail).
As for Linux users... you use Linux. I'm sure you know how to use the console!
Thanks for reading this first Riggy Bit. My website is still under construction, so please leave your feedback on my Blender.Community post. I'm eager to keep writing these, so leave some ideas in the comments!
Also, feel free to check out some useful code examples!