Today I will explain how a blend-shape based face rig works for autodesk Maya. Understand that blendShapes is an additive mesh shape deformer, that one after another shape gets activated and can be driven by a single controller with values from 0.0 to 1.0 to drive shapes: shape0 + shape0_5 + shape1_0.
I had trouble finding a face mesh to work with, so I headed over to AnimSchool and downloaded their Malcom Rig and extracted the head mesh for me to work on:
https://www.animschool.com/DownloadOffer.aspx
While the set-up of the controllers are rather simple, each controller had to have a maximum value of 1. In addition to that, here is work needs to be done in creating the shapes themselves. For this reason, I chose to play with OpenMaya::MFnBlendShape class to add, and remove shape targets.
To initialize a blend-shape node without targets (important step):
def create_blendshape(mesh_objects, name=""):
"""
creates a new blendShape from the array of mesh objects provided
:param mesh_objects: <tuple> array of mesh shapes.
:param name: <str> name of the blendshape.
:return: <OpenMayaAnim.MFnBlendShapeDeformer>
"""
blend_fn = OpenMayaAnim.MFnBlendShapeDeformer()
if isinstance(mesh_objects, (str, unicode)):
mesh_obj = object_utils.get_m_obj(mesh_objects)
blend_fn.create(mesh_obj, origin, normal_chain)
elif len(mesh_objects) > 1 and isinstance(mesh_objects, (tuple, list)):
mesh_obj_array = object_utils.get_m_obj_array(mesh_objects)
blend_fn.create(mesh_obj_array, origin, normal_chain)
else:
raise ValueError("Could not create blendshape.")
if name:
object_utils.rename_node(blend_fn.object(), name)
return blend_fn
Each blend-shape index starts from 5000 and ends at 6000, so to get indices we need to use OpenMaya.MIntArray(), please understand that we need to use MIntArray and not a list of integers because otherwise Maya will not accept those integers:
def get_weight_indices(blend_name=""):
"""
get the weight indices from the blendShape name provided.
:param blend_name: <str> the name of the blendShape node.
:return: <OpenMaya.MIntArray>
"""
blend_fn = get_deformer_fn(blend_name)
int_array = OpenMaya.MIntArray()
blend_fn.weightIndexList(int_array)
return int_array
Now we can add shape targets like by using the following code, the objects are accepted from targets_array and Maya’s specified index:
def add_target(targets_array, blend_name="", weight=1.0, index=0):
"""
adds a new target with the weight to this blend shape.
Maya has a fail-safe to get the inputTargetItem from 6000-5000
:param targets_array: <tuple> array of mesh shapes designated as targets.
:param blend_name: <str> the blendShape node to add targets to.
:param weight: <float> append this weight value to the target.
:param index: <int> specify the index in which to add a target to the blend node.
:return:
"""
blend_fn = get_deformer_fn(blend_name)
base_obj = get_base_object(blend_name)[0]
if isinstance(targets_array, (str, unicode)):
targets_array = targets_array,
targets_array = object_utils.get_m_shape_obj_array(targets_array)
length = targets_array.length()
if not index:
index = get_weight_indices(blend_fn.name()).length() + 1
# step = 1.0 / length - 1
for i in xrange(0, length):
# weight_idx = (i * step) * 1000/1000.0
blend_fn.addTarget(base_obj, index, targets_array[i], weight)
return True
One after another we an add all the shapes by code in whatever order of targets we want, adding in-betweens from 0.0 -> 1.0. I always choose to go in steps of “5”-ves. (0.0, 0.25, 0.5, 0.75, 1.0). This is because don’t really need to go any more complicated that that.
Over the course of constructing the blend-shape based rig, you need to have two base meshes: one for deformation and the other for duplicating mesh objects for sculpting. I used abSymMesh for mesh mirroring because the tool is already there and I did not need to re-invent another one. All in all I think I’ve done a good job with my face:
The complete module I used in this construction can be found at my GitHub page: