I know that shapekeys can be transferred between meshes of similar topology with ease.
My case involves transferring shape keys from ManuelBastioni Lab Character Face to the moustache.
I have written a small script which helped me get this done, however it takes really long to execute. I'm a novice to python and blender scripting and hope you can advise me about improving this code or provide an alternative solution.
In this solution the time can be improved by deleting parts of the source mesh which you think should not affect the destination mesh.
#sorry if this script is poorly written.
#Originally created to transfer face shapekeys from Manuel Bastioni character face to a moustache made with mesh cards
#Please ensure that the destination and source meshes have the same names and number of shape keys
#place both the meshes at origin and overlap them with similar scale
#To quickly copy shape key names join both meshes and then separate them. Now destination mesh has the same names as source mesh
#import required modules
import bpy
import bmesh
from mathutils import Vector
from time import sleep
import datetime
# commonly used references
scene = bpy.context.scene
context = bpy.context
# step size to increase the sphere selection
increment_radius = .5
#set total vertices incase you want to run for less number of vertices also set specify_end_vertex to True
#set current_vertex index incase you want to continue from another index
#use_one_vertex will transfer the weight of the first vertex with the selection sphere
current_vertex_index = 0
total_vertices = 0
specify_end_vertex = False
use_one_vertex = False
#name of source mesh
src = "man"
#name of destination mesh
dest= "moustache"
#global variables which should not be set
key_name = ""
dest_mesh = None
src_mesh = None
dest_shape_key = None
dest_shape_key_index = 0
src_shape_key = None
src_shape_key_index = 0
print("*************************************************")
def deselect_all():
for ob in bpy.context.selected_objects:
ob.select = False
deselect_all()
bpy.ops.object.select_pattern(pattern=dest)
dest_mesh = bpy.context.selected_objects[0]
print(dest_mesh.name)
deselect_all()
bpy.ops.object.select_pattern(pattern=src)
src_mesh = bpy.context.selected_objects[0]
print(src_mesh.name)
if(specify_end_vertex == False):
total_vertices = len(dest_mesh.data.vertices)
print("Total vertices: ",total_vertices )
#select required vertices within a radius
def select_vertices(center, radius):
global src_mesh
n = 0
src_current_vertices = []
one_vertex = []
bms = bmesh.new()
radius_vec = center + Vector((0, 0, radius))
me = src_mesh.data
mwi = src_mesh.matrix_world.inverted()
# put sphere in local coords.
lco = mwi * center
r = mwi * (radius_vec) - lco
closest_length = r.length
# load mesh
bms.from_mesh(me)
# select verts within radius
for index, v in enumerate(bms.verts):
v.select = (v.co - lco).length <= r.length
n += v.select
if(v.select):
src_current_vertices.append(index)
if(use_one_vertex):
if((v.co - lco).length <= closest_length):
closest_length = (v.co - lco).length
one_vertex = []
one_vertex.append(index)
# flush selection, update mesh
bms.select_flush(False)
bms.to_mesh(me)
bms.clear()
bms.free()
#selects only index 0 of close vertices as reference
if(use_one_vertex):
if(n != 0 ):
src_current_vertices = []
src_current_vertices.append(one_vertex[0])
n = 1
# appending the length of the array to the end... I know i could use len() but want to be able to return a -1 incase no vertices are selected
src_current_vertices.append(n)
return src_current_vertices
#this select function initially starts (if level=0) by matching a point in same space as the source mesh and if it cant find similar positioned point we increment search radius
def select_required_verts(vert,rad,level=0):
if(level > 20):
return -1
verts = []
verts = select_vertices(vert, rad)
n = verts.pop()
if n == 0:
return select_required_verts(vert,rad + increment_radius, level + 1)
else:
verts.append(n)
return verts
#set the new vertex position on the shape key
def set_vertex_position(v_pos):
global current_vertex_index
bmg = bmesh.new()
bmg.from_mesh(dest_mesh.data)
bmg.verts.ensure_lookup_table()
layer_name = bmg.verts.layers.shape.get(key_name)
bmg.verts[current_vertex_index][layer_name] = v_pos
bmg.to_mesh(dest_mesh.data)
bmg.free()
def update_vertex():
global current_vertex_index
global total_vertices
global dest_mesh
global src_mesh
if(current_vertex_index >= total_vertices ):
return
bpy.ops.object.select_pattern(pattern=dest)
obj = context.active_object
current_vertex = obj.matrix_world * dest_mesh.data.vertices[current_vertex_index].co
print("Current vertex", current_vertex_index, " ", current_vertex)
deselect_all()
bpy.ops.object.select_pattern(pattern=src)
src_current_vertices = select_required_verts(current_vertex,0)
n = src_current_vertices.pop()
if(n == -1):
print("FAIL")
return
deselect_all()
src_total_distance = 0
src_directions = []
src_distances = []
bpy.context.object.active_shape_key_index = src_shape_key_index
src_shape_key.value = 0
bpy.ops.object.select_pattern(pattern=src)
me = src_mesh.to_mesh(scene, True, 'PREVIEW')
result_position = Vector()
for v in src_current_vertices:
result_position += me.vertices[v].co
bpy.data.meshes.remove(me)
result_position /= len(src_current_vertices)
#Calculate new positions
src_shape_key.value = 1
me = src_mesh.to_mesh(scene, True, 'PREVIEW')
result_position2 = Vector()
for v in src_current_vertices:
result_position2 += me.vertices[v].co
bpy.data.meshes.remove(me)
result_position2 /= len(src_current_vertices)
result = result_position2 - result_position + current_vertex
bpy.ops.object.select_pattern(pattern=dest)
bpy.context.scene.objects.active = dest_mesh
bpy.context.object.active_shape_key_index = dest_shape_key_index
src_shape_key.value = 0
set_vertex_position(result)
bpy.context.object.active_shape_key_index = 0
current_vertex_index += 1
#store shapekey index and reference in global variables
def get_shapekey(p_key_name):
global dest_shape_key
global dest_shape_key_index
global src_shape_key
global src_shape_key_index
deselect_all()
for index, sk in enumerate(dest_mesh.data.shape_keys.key_blocks):
if sk.name == p_key_name:
dest_shape_key = sk
dest_shape_key_index = index
bpy.context.object.active_shape_key_index = dest_shape_key_index
dest_shape_key.value = 0
deselect_all()
for index, sk in enumerate(src_mesh.data.shape_keys.key_blocks):
if sk.name == p_key_name:
src_shape_key = sk
src_shape_key_index = index
bpy.context.object.active_shape_key_index = src_shape_key_index
src_shape_key.value = 0
deselect_all()
#Iterate all shape keys in src mesh and get its reference and index and then update vertex positions
for shape_key_iter in src_mesh.data.shape_keys.key_blocks:
global key_name
key_name = shape_key_iter.name
#exclude basis shapekey and Expressions_IDHumans_max (used in Manuel Bastioni Lab
if(key_name!='basis' and key_name!='Expressions_IDHumans_max'):
get_shapekey(shape_key_iter.name)
while(current_vertex_index < total_vertices):
update_vertex()
current_vertex_index = 0
dest_shape_key.value = 0
update_vertex(...)
method above for instance) Can you provide a run down of the theory behind method you are using in code above to transfer shapekeys? . Have you considered 3 vertex parenting, moustache to man? $\endgroup$