Rigify 0.5 advanced generation options, fixes and improvements. removed CREDITS and README files

This commit is contained in:
Lucio Rossi 2017-06-10 14:29:54 +02:00
parent 8d5d983116
commit c60d7e3257
6 changed files with 107 additions and 308 deletions

View File

@ -1,32 +0,0 @@
A big thank you to all the people listed here for supporting Rigify.
Original prototyping and development, and Python API support:
- Campbell Barton
Development:
- PitchiPoy Animation Productions
- Kfir Merlaub
- Tamir Lousky
General financial support:
- Benjamin Tolputt
- Nesterenko Viktoriya
- Jeff Hogan
- PitchiPoy Animation Productions
IK/FK snapping financial support:
- Benjamin Tolputt
- Nesterenko Viktoriya
- Leslie Chih
- Isaac Ah-Loe
- Casey "TheLorax" Jones
### Rigify Version 0.5 ###
Development:
- Lucio Rossi
Design:
- Ivan Cappiello
General financial support:
- Mad Entertainment Animation

View File

@ -1,250 +0,0 @@
INTRODUCTION
------------
Rigify is an auto-rigging system based on a "building blocks" paradigm. The
user can create a rig by putting together any combination of rig types, in any
configuration that they want.
A rig type is something like "biped arm" or "spine" or "finger".
The input to the Rigify system is something called a "metarig". It is an
armature that contains data about how to construct the rig. In particular, it
contains bones in the basic configuration of the rig, with some bones tagged
to indicate the rig type.
For example, a metarig might contain a chain of three bones, the root-most of
which is tagged as being a biped arm. When given as input to Rigify, Rigify
will then generate a fully-featured biped arm rig in the same position and
proportions as the 3-bone chain.
One could also have another chain of bones, the root-most of which is tagged as
being a spine. And the root-most bone of the arm chain could be the child of
any of those spine bones. Then the rig that Rigify generates would be a
spine rig with an arm rig attached to it.
THE GUTS OF RIGIFY, SUMMARIZED
------------------------------
The concept behind rigify is fairly simple. It recieves an armature as input
with some of the bones tagged as being certain rig types (arm, leg, etc.)
When Rigify recieves that armature as input, the first thing it does is
duplicate the armature. From here on out, the original armature is totally
ignored. Only the duplicate is used. And this duplicate armature object will
become the generated rig.
Rigify next prepends "ORG-" to all of the bones. These are the "original"
bones of the metarig, and they are used as the glue between rig types, as I
will explain later.
Rigify then generates the rig in two passes. The first pass is the
"information gathering" stage.
The information gathering stage doesn't modify the armature at all. It simply
gathers information about it. Or, rather, it lets the rig types gather
information about it.
It traverses the bones in a root-most to leaf-most order, and whenever it
stumbles upon a bone that has a rig type tagged on it, it creates a rig-type
python object (rig types will be explained further down) for that rig type,
and executes the resulting python object's information gathering code.
At the end of the information gathering stage, Rigify has a collection of
python objects, each of which know all the information they need to generate
their own bit of the rig.
The next stage is the rig generation stage. This part is pretty simple. All
Rigify does is it loops over all of the rig-type python objects that it created
in the previous stage (also in root-most to leaf-most order), and executes
their rig-generate code. All of the actual rig generation happens in the
rig-type python objects.
And that's pretty much it. As you can see, most of the important code is
actually in the rig types themselves, not in Rigify. Rigify is pretty sparse
when it comes right down to it.
There is one final stage to rig generation. Rigify checks all of the bones
for "DEF-", "MCH-", and "ORG-" prefixes, and moves those bones to their own
layers. It also sets all of the "DEF-" bones to deform, and sets all other
bones to _not_ deform. And finally, it looks for any bone that does not have
a parent, and sets the root bone (which Rigify creates) as their parent.
THE GUTS OF A RIG TYPE, BASIC
-----------------------------
A rig type is simply a python module containing a class named "Rig", and some
optional module functions. The Rig class has only two methods:
__init__() and generate()
__init__() is the "information gathering" code for the rig type. When Rigify
loops through the bones and finds a tagged bone, it will create a python
object from the Rig class, executing this method.
In addition to the default "self" parameter, __init__() needs to take the
armature object, the name of the bone that was tagged, and a parameters object.
A proper rig-type __init__() will look like this:
def __init__(self, obj, bone_name, params):
# code goes here
At the bare minimum, you are going to want to store the object and bone name
in the rig type object for later reference in the generate() method. So:
def __init__(self, obj, bone_name, params):
self.obj = obj
self.org_bone = bone_name
Most rig types involve more than just that one bone, though, so you will also
want to store the names of any other relevant bones. For example, maybe the
parent of the tagged bone is important to the rig type:
def __init__(self, obj, bone_name, params):
self.obj = obj
self.org_bone = bone_name
self.org_parent = obj.data.bones[bone_name].parent.name
It is important that you store the _names_ of the bones, and not direct
references. Due to the inner workings of Blender's armature system, direct
edit-bone and pose-bone references are lost when flipping in and out of
armature edit mode. (Arg...)
Remember that it is critical that the information-gathering method does _not_
modify the armature in any way. This way all of the rig type's info-gathering
methods can execute on a clean armature. Many rig types depend on traversing
parent-child relationships to figure out what bones are relevant to them, for
example.
Next is the generate() method. This is the method that Rigify calls to
actually generate the rig. It takes the form:
def generate(self):
# code goes here
It doesn't take any parameters beyond "self". So you have to store any
information you need with the __init__() method.
generate() pretty much has free reign to do whatever it wants, with the exception
of two simple rules:
1. Other than the "ORG-" bones, do not touch anything that is not created by
the rig type (this prevents rig types from messing each other up).
2. Even with "ORG-" bones, the only thing you are allowed to do is add children
and add constraints. Do not rename them, do not remove children or
constraints, and especially do not change their parents. (Adding constraints
and adding children are encouraged, though. ;-)) This is because the "ORG-"
bones are the glue that holds everything together, and changing them beyond
adding children/constraints ruins the glue, so to speak.
In short: with the exception of adding children/constraints to "ORG-"
bones, only mess with things that you yourself create.
It is also generally a good idea (though not strictly required) that the rig
type add constraints to the "ORG-" bones it was generated from so that the
"ORG-" bones move with the animation controls.
For example, if I make a simple arm rig type, the controls that the animator
uses should also move the "ORG-" bones. That way, any other rig-types that are
children of those "ORG-" bones will move along with them. For example, any
fingers on the end of the arm.
Also, any bones that the animator should not directly animate with should have
their names prefixed with "DEF-" or "MCH-". The former if it is a bone that
is intended to deform the mesh, the latter if it is not.
It should be obvious, then, that a bone cannot be both an animation control and
a deforming bone in Rigify. This is on purpose.
Also note that there are convenience functions in utils.py for prepending
"DEF-" and "MCH-" to bone names: deformer() and mch()
There is also a convenience function for stripping "ORG-" from a bone name:
strip_org()
Which is useful for removing "ORG-" from bones you create by duplicating
the "ORG-" bones.
I recommend you use these functions instead of manually adding/stripping
these prefixes. That way if the prefixes are changed, it can be changed in
one place (those functions) and all the rig types will still work.
THE GUTS OF A RIG TYPE, ADVANCED
--------------------------------
If you look at any of the rig types included with Rigify, you'll note that they
have several functions outside of the Rig class.
THESE ADDITIONAL FUNCTIONS ARE _NOT_ REQUIRED for a rig type to function. But
they can add some nifty functionality to your rig.
Here are the additional functions relevant to Rigify, with brief decriptions of
what they are for:
RIG PARAMETERS
--------------
For many rig types, it is handy for the user to be able to tweak how they are
generated. For example, the included biped arm rig allows the user to specify
the axis of rotation for the elbow.
There are two functions necessary to give a rig type user-tweakable parameters:
add_parameters()
parameters_ui()
add_parameters() takes an IDPropertyGroup as input, and adds its parameters
to that group as RNA properties. For example:
def add_parameters(params):
params.toggle_param = bpy.props.BoolProperty(name="Test toggle:", default=False, description="Just a test, not really used for anything.")
parameters_ui() recieves a Blender UILayout object and an IDPropertyGroup
containing the parameters added by add_parameters(). It creates a GUI in the
UILayout for the user to tweak those parameters. For example:
def parameters_ui(layout, params):
r = layout.row()
r.prop(params, "toggle_param")
SAMPLE METARIG
--------------
It is a good idea for all rig types to have a sample metarig that the user can
add to their own metarig. This is what the create_sample() function is for.
create_sample() takes the current armature object as input, and adds the bones
for its rig-type's metarig. For example:
def create_sample(obj):
bpy.ops.object.mode_set(mode='EDIT')
arm = obj.data
bone = arm.edit_bones.new('Bone')
bone.head[:] = 0.0000, 0.0000, 0.0000
bone.tail[:] = 0.0000, 0.0000, 1.0000
bone.roll = 0.0000
bone.use_connect = False
bpy.ops.object.mode_set(mode='OBJECT')
pbone = obj.pose.bones[bone]
pbone.rigify_type = 'copy'
pbone.rigify_parameters.add()
Obviously, this isn't something that you generally want to hand-code,
especially with more complex samples. When in edit-mode on an armature,
there is a "Rigify Dev Tools" panel in the View3d tools panel containing a
button labeled "Encode Sample to Python". This button will generate the python
code for create_sample() from the armature you are editing. The generated code
appears in a text block called "metarig_sample.py"
IMPLEMENTATION RIGS
-------------------
Starting from version 0.5 you can create a Rig class as an implementation of a wrapper class.
This happens for limb rigs for example, where super_limb is kind of a wrapper class for arm, leg and paws.
To declare a class as an implementation just declare an IMPLEMENTATION constant in the module and set it to True.
Implementation classes are shown in the metarig samples list and generate a sample if a proper create_sample function is implemented, but cannot be directly assigned as a rigify type.
GENERATING A PYTHON UI
----------------------
The generate() method can also, optionally, return python code as a single
string. This python code is added to the "rig properties" panel that gets
auto-generated along with the rig. This is useful for exposing things like
IK/FK switches in a nice way to the animator.
The string must be returned in a list, e.g.:
return ["my python code"]
The reason it needs to be put in a list is to leave room for expanding the API
in the future, for returning additional information.

View File

@ -286,24 +286,38 @@ def register():
IDStore.rigify_types = bpy.props.CollectionProperty(type=RigifyName)
IDStore.rigify_active_type = bpy.props.IntProperty(name="Rigify Active Type", description="The selected rig type")
IDStore.rigify_advanced_generation = bpy.props.BoolProperty(name="Rigify Advanced Generation",
description="Rigify Advanced Generation Parameters",
IDStore.rigify_advanced_generation = bpy.props.BoolProperty(name="Advanced Options",
description="Enables/disables advanced options for Rigify rig generation",
default=False)
def update_mode(self, context):
if self.rigify_generate_mode == 'new':
self.rigify_force_widget_update = False
IDStore.rigify_generate_mode = bpy.props.EnumProperty(name="Rigify Generate Rig Mode",
description="'Generate Rig' mode. In 'overwrite' mode the features of the target rig will be updated as defined by the metarig. In 'new' mode a new rig will be created as defined by the metarig. Current mode",
update=update_mode,
items=(('overwrite', 'overwrite', ''),
('new', 'new', '')))
IDStore.rigify_force_widget_update = bpy.props.BoolProperty(name="Force Widget Update",
description="Rigify Force Widget Update",
description="Forces Rigify to delete and rebuild all the rig widgets. if unset, only missing widgets will be created",
default=False)
IDStore.rigify_target_rigs = bpy.props.CollectionProperty(type=RigifyName)
IDStore.rigify_target_rig = bpy.props.StringProperty(name="Rigify Target Rig",
description="The Rig, Generate will run upon",
description="Defines which rig to overwrite. If unset, a new one called 'rig' will be created.",
default="")
IDStore.rigify_rig_uis = bpy.props.CollectionProperty(type=RigifyName)
IDStore.rigify_rig_ui = bpy.props.StringProperty(name="Rigify Target Rig UI",
description="The Rig UI to overwrite",
description="Defines the UI to overwrite. It should always be the same as the target rig. If unset, 'rig_ui.py' will be used",
default="")
IDStore.rigify_rig_basename = bpy.props.StringProperty(name="Rigify Rig Name",
description="Defines the name of the Rig. If unset, in 'new' mode 'rig' will be used, in 'overwrite' mode the target rig name will be used",
default="")
if (ui and 'legacy' in str(ui)) or bpy.context.user_preferences.addons['rigify'].preferences.legacy_mode:
# update legacy on restart or reload
bpy.context.user_preferences.addons['rigify'].preferences.legacy_mode = True
@ -326,11 +340,13 @@ def unregister():
del IDStore.rigify_types
del IDStore.rigify_active_type
del IDStore.rigify_advanced_generation
del IDStore.rigify_generate_mode
del IDStore.rigify_force_widget_update
del IDStore.rigify_target_rig
del IDStore.rigify_target_rigs
del IDStore.rigify_rig_uis
del IDStore.rigify_rig_ui
del IDStore.rigify_rig_basename
bpy.utils.unregister_class(RigifyName)
bpy.utils.unregister_class(RigifyParameters)

View File

@ -81,15 +81,30 @@ def generate_rig(context, metarig):
# object to generate the rig in.
print("Fetch rig.")
name = id_store.rigify_target_rig or "rig"
rig_new_name = ""
rig_old_name = ""
if id_store.rigify_rig_basename:
rig_new_name = id_store.rigify_rig_basename + "_rig"
try:
obj = scene.objects[name]
except KeyError:
obj = bpy.data.objects.new(name, bpy.data.armatures.new(name))
if id_store.rigify_generate_mode == 'overwrite':
name = id_store.rigify_target_rig or "rig"
try:
obj = scene.objects[name]
rig_old_name = name
obj.name = rig_new_name or name
except KeyError:
rig_old_name = name
name = rig_new_name or name
obj = bpy.data.objects.new(name, bpy.data.armatures.new(name))
obj.draw_type = 'WIRE'
scene.objects.link(obj)
else:
name = rig_new_name or "rig"
obj = bpy.data.objects.new(name, bpy.data.armatures.new(name)) # in case name 'rig' exists it will be rig.001
obj.draw_type = 'WIRE'
scene.objects.link(obj)
id_store.rigify_target_rig = obj.name
obj.data.pose_position = 'POSE'
# Get rid of anim data in case the rig already existed
@ -102,17 +117,22 @@ def generate_rig(context, metarig):
scene.objects.active = obj
# Remove wgts if force update is set
if "WGTS" in scene.objects and id_store.rigify_force_widget_update:
wgts_group_name = "WGTS_" + (rig_old_name or obj.name)
if wgts_group_name in scene.objects and id_store.rigify_force_widget_update:
bpy.ops.object.select_all(action='DESELECT')
for i, lyr in enumerate(WGT_LAYERS):
if lyr:
context.scene.layers[i] = True
for wgt in bpy.data.objects["WGTS"].children:
for wgt in bpy.data.objects[wgts_group_name].children:
wgt.select = True
bpy.ops.object.delete(use_global=False)
for i, lyr in enumerate(WGT_LAYERS):
if lyr:
context.scene.layers[i] = False
if rig_old_name:
bpy.data.objects[wgts_group_name].name = "WGTS_" + obj.name
wgts_group_name = "WGTS_" + obj.name
# Remove all bones from the generated rig armature.
bpy.ops.object.mode_set(mode='EDIT')
@ -281,7 +301,7 @@ def generate_rig(context, metarig):
t.tick("Create root bone: ")
# Create Group widget
wgts_group_name = "WGTS"
# wgts_group_name = "WGTS"
if wgts_group_name not in scene.objects:
if wgts_group_name in bpy.data.objects:
bpy.data.objects[wgts_group_name].user_clear()
@ -291,6 +311,18 @@ def generate_rig(context, metarig):
scene.objects.link(wgts_obj)
wgts_obj.layers = WGT_LAYERS
t.tick("Create main WGTS: ")
#
# if id_store.rigify_generate_mode == 'new':
# bpy.ops.object.select_all(action='DESELECT')
# for wgt in bpy.data.objects[wgts_group_name].children:
# wgt.select = True
# for i, lyr in enumerate(WGT_LAYERS):
# if lyr:
# context.scene.layers[i] = True
# bpy.ops.object.make_single_user(obdata=True)
# for i, lyr in enumerate(WGT_LAYERS):
# if lyr:
# context.scene.layers[i] = False
#----------------------------------
try:
@ -410,7 +442,7 @@ def generate_rig(context, metarig):
# Assign shapes to bones
# Object's with name WGT-<bone_name> get used as that bone's shape.
for bone in bones:
wgt_name = (WGT_PREFIX + obj.data.bones[bone].name)[:63] # Object names are limited to 63 characters... arg
wgt_name = (WGT_PREFIX + obj.name + '_' + obj.data.bones[bone].name)[:63] # Object names are limited to 63 characters... arg
if wgt_name in context.scene.objects:
# Weird temp thing because it won't let me index by object name
for ob in context.scene.objects:
@ -439,13 +471,24 @@ def generate_rig(context, metarig):
layer_layout += [(l.name, l.row)]
# Generate the UI script
rig_ui_name = id_store.rigify_rig_ui or 'rig_ui.py'
if id_store.rigify_generate_mode == 'overwrite':
rig_ui_name = id_store.rigify_rig_ui or 'rig_ui.py'
else:
rig_ui_name = 'rig_ui.py'
if rig_ui_name in bpy.data.texts.keys():
if id_store.rigify_generate_mode == 'overwrite' and rig_ui_name in bpy.data.texts.keys():
script = bpy.data.texts[rig_ui_name]
script.clear()
else:
script = bpy.data.texts.new("rig_ui.py")
rig_ui_old_name = ""
if id_store.rigify_rig_basename:
rig_ui_old_name = script.name
script.name = id_store.rigify_rig_basename + "_rig_ui.py"
id_store.rigify_rig_ui = script.name
script.write(UI_SLIDERS % rig_id)
for s in ui_scripts:
script.write("\n " + s.replace("\n", "\n ") + "\n")
@ -465,14 +508,15 @@ def generate_rig(context, metarig):
# Add rig_ui to logic
skip = False
ctrls = obj.game.controllers
for c in ctrls:
if 'Python' in c.name and c.text.name == 'rig_ui.py':
if 'Python' in c.name and c.text.name == script.name:
skip = True
break
if not skip:
bpy.ops.logic.controller_add(type='PYTHON', object=obj.name)
ctrl = obj.game.controllers[-1]
ctrl.text = bpy.data.texts['rig_ui.py']
ctrl.text = bpy.data.texts[script.name]
t.tick("The rest: ")

View File

@ -69,19 +69,33 @@ class DATA_PT_rigify_buttons(bpy.types.Panel):
if show_warning:
layout.label(text=WARNING, icon='ERROR')
layout.operator("pose.rigify_generate", text="Generate Rig")
layout.prop(id_store, "rigify_advanced_generation")
layout.operator("pose.rigify_generate", text="Generate Rig", icon='POSE_HLT')
if id_store.rigify_advanced_generation:
icon = 'UNLOCKED'
else:
icon = 'LOCKED'
layout.prop(id_store, "rigify_advanced_generation", toggle=True, icon=icon)
if id_store.rigify_advanced_generation:
row = layout.row(align=True)
row.prop(id_store, "rigify_generate_mode", expand=True)
main_row = layout.row(align=True).split(percentage=0.3)
col1 = main_row.column()
col2 = main_row.column()
col1.label(text="Target Rig")
col1.label(text="Target UI")
col1.label(text="Rig Name")
row = col1.row()
row.label(text="Target Rig")
row.enabled = (id_store.rigify_generate_mode == "overwrite")
row = col1.row()
row.label(text="Target UI")
row.enabled = (id_store.rigify_generate_mode == "overwrite")
row = col2.row(align=True)
row.prop(id_store, "rigify_rig_basename", text="", icon="SORTALPHA")
row = col2.row(align=True)
for i in range(0, len(id_store.rigify_target_rigs)):
id_store.rigify_target_rigs.remove(0)
@ -92,6 +106,7 @@ class DATA_PT_rigify_buttons(bpy.types.Panel):
row.prop_search(id_store, "rigify_target_rig", id_store, "rigify_target_rigs", text="",
icon='OUTLINER_OB_ARMATURE')
row.enabled = (id_store.rigify_generate_mode == "overwrite")
for i in range(0, len(id_store.rigify_rig_uis)):
id_store.rigify_rig_uis.remove(0)
@ -102,8 +117,12 @@ class DATA_PT_rigify_buttons(bpy.types.Panel):
row = col2.row()
row.prop_search(id_store, "rigify_rig_ui", id_store, "rigify_rig_uis", text="", icon='TEXT')
row.enabled = (id_store.rigify_generate_mode == "overwrite")
layout.prop(id_store, "rigify_force_widget_update")
row = layout.row()
row.prop(id_store, "rigify_force_widget_update")
if id_store.rigify_generate_mode == 'new':
row.enabled = False
if show_update_metarig:
layout.label(text="Some bones have old legacy rigify_type. Click to upgrade", icon='ERROR')

View File

@ -412,8 +412,9 @@ def create_widget(rig, bone_name, bone_transform_name=None):
if bone_transform_name is None:
bone_transform_name = bone_name
obj_name = WGT_PREFIX + bone_name
obj_name = WGT_PREFIX + rig.name + '_' + bone_name
scene = bpy.context.scene
id_store = bpy.context.window_manager
# Check if it already exists in the scene
if obj_name in scene.objects:
@ -437,8 +438,9 @@ def create_widget(rig, bone_name, bone_transform_name=None):
# Move object to bone position and set layers
obj_to_bone(obj, rig, bone_transform_name)
if 'WGTS' in bpy.data.objects.keys():
obj.parent = bpy.data.objects['WGTS']
wgts_group_name = 'WGTS_' + rig.name
if wgts_group_name in bpy.data.objects.keys():
obj.parent = bpy.data.objects[wgts_group_name]
obj.layers = WGT_LAYERS
return obj