Last active
August 25, 2022 07:21
-
-
Save rBrenick/e6779d98a51facd80afba87083445793 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
__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