Skip to content

Instantly share code, notes, and snippets.

@rBrenick
Last active August 25, 2022 07:21
Show Gist options
  • Save rBrenick/e6779d98a51facd80afba87083445793 to your computer and use it in GitHub Desktop.
Save rBrenick/e6779d98a51facd80afba87083445793 to your computer and use it in GitHub Desktop.
__author__ = "rBrenick - [email protected]"
__created__ = "22/02/2020"
"""
1. Pull this script into a python tab in the script editor (or create a python shelf button)
1. Select blendshaped mesh
2. Select mesh to apply blendshapes on
3. Run script
"""
import sys
import time
import pymel.core as pm
from maya import cmds
def get_vertex_positions(mesh_name):
"""
Thanks to Daniel Lima for this one
http://www.fevrierdorian.com/blog/post/2011/09/27/Quickly-retrieve-vertex-positions-of-a-Maya-mesh-%28English-Translation%29#c4406
"""
mesh_vtx_xforms = cmds.xform('{}.vtx[*]'.format(mesh_name), q=True, t=True)
return zip(mesh_vtx_xforms[0::3], mesh_vtx_xforms[1::3], mesh_vtx_xforms[2::3])
def get_blendshape_weight_names(blendshape_node):
weight_names = []
for weight_index in blendshape_node.weightIndexList():
weight_attr_name = cmds.listAttr(blendshape_node+".weight[{}]".format(weight_index), sn=True)[0]
weight_names.append(weight_attr_name)
return weight_names
def transfer_blendshapes_vtx_multiplied(src_mesh, tgt_mesh, blendshape_node=None, axis_multipliers=None, bind_to_blendshape=True):
"""
This method inspired by this God Of War GDC talk by Axel Grossman
https://vimeo.com/330507722
the meshes need to have identical vertex counts for this to work properly
src_mesh: mesh with blendshapes
tgt_mesh: mesh to apply blendshapes on
blendshape_node: if specific blendshape node should be indicated
axis_multipliers: extra multipliers for XYZ differences of vertices
bind_to_blendshape: whether to create a blendshape node or just return the meshes
"""
if axis_multipliers is None:
axis_multipliers = [0.2, 0.2, 0.1] # example multipliers
if blendshape_node is None:
blendshape_node = pm.listHistory(src_mesh, type="blendShape")[0]
blendshape_node = pm.PyNode(blendshape_node)
weight_names = get_blendshape_weight_names(blendshape_node)
blendshape_attrs = [blendshape_node + "." + attr_name for attr_name in weight_names]
# Reset blendshapes to zero
[cmds.setAttr(blendshape_attr, 0) for blendshape_attr in blendshape_attrs]
# get vertices of meshes
src_positions = get_vertex_positions(src_mesh)
target_positions = get_vertex_positions(tgt_mesh)
# calculate differences between vertices of src and tgt
mesh_diffs = [(a[0]-b[0], a[1]-b[1], a[2]-b[2]) for a, b in zip(src_positions, target_positions)]
blendshape_group = cmds.group(em=True, name=tgt_mesh+"_BlendshapeTransfer")
created_blendshape_meshes = []
for blendshape_attr in blendshape_attrs:
shape_name = blendshape_attr.split(".")[-1]
sys.stdout.write("Calculating offsets for: {}\n".format(shape_name))
# Make copy of the target mesh to apply vertex positions onto
morphed_mesh = pm.duplicate(tgt_mesh)[0]
morphed_mesh.setParent(blendshape_group)
morphed_mesh.rename(shape_name)
cmds.setAttr(blendshape_attr, 1)
# This feels like it should be queriable from the blendshape node directly, but I don't know how at the moment
blendshape_positions = get_vertex_positions(src_mesh)
final_positions = []
for base_pos, blendshape_pos, tgt_pos, mesh_diff in zip(src_positions, blendshape_positions, target_positions, mesh_diffs):
blendshape_diff = [a-b for a, b in zip(base_pos, blendshape_pos)] # blendshape vertex diff from base
# apply blendshape diff with mesh diff as multiplier
output_pos = [t - (bs_d + (abs(mesh_d) * axis_mult * bs_d)) for t, bs_d, mesh_d, axis_mult in zip(tgt_pos, blendshape_diff, mesh_diff, axis_multipliers)]
# t = target mesh vertex position
# bs_d = delta of vertex in blendshape from source mesh
# mesh_d = delta of vertex between source and target mesh
# axis_mult = axis_multiplier argument
final_positions.append(output_pos)
morphed_mesh.setPoints(final_positions)
created_blendshape_meshes.append(morphed_mesh)
cmds.setAttr(blendshape_attr, 0)
if bind_to_blendshape:
target_blendshape = pm.blendShape(created_blendshape_meshes, tgt_mesh)[0]
# connect to original shape channels for preview purposes
for blendshape_attr in blendshape_attrs:
shape_name = blendshape_attr.split(".")[-1]
pm.connectAttr(blendshape_attr, target_blendshape + "." + shape_name)
pm.delete(blendshape_group) # deletes the retargeted meshes, you can remove this line if you want.
return target_blendshape
else:
# I know. It's nasty to have different return types. But it's convenient here
return created_blendshape_meshes
def main():
start = time.time()
src_mesh, tgt_mesh = pm.selected()
transfer_blendshapes_vtx_multiplied(src_mesh.name(), tgt_mesh.name(), axis_multipliers=[0.2, 0.3, -0.1])
sys.stdout.write("Blendshapes Transferred. Ran in: {}\n".format(time.time() - start))
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment