mirror of
https://github.com/blender/blender-addons.git
synced 2025-08-16 15:35:05 +00:00
Moved corrective shape key addon to trunk
[[Split portion of a mixed commit.]]
This commit is contained in:
497
animation_add_corrective_shape_key.py
Normal file
497
animation_add_corrective_shape_key.py
Normal file
@ -0,0 +1,497 @@
|
|||||||
|
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License
|
||||||
|
# as published by the Free Software Foundation; either version 2
|
||||||
|
# of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software Foundation,
|
||||||
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
#
|
||||||
|
# ##### END GPL LICENSE BLOCK #####
|
||||||
|
|
||||||
|
bl_addon_info = {
|
||||||
|
'name': 'Corrective shape keys',
|
||||||
|
'author': 'Ivo Grigull (loolarge), Tal Trachtman',
|
||||||
|
'version': (1, 0),
|
||||||
|
'blender': (2, 5, 5),
|
||||||
|
'location': 'Object Data > Shape Keys (Search: corrective) ',
|
||||||
|
'description': 'Creates a corrective shape key for the current pose',
|
||||||
|
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
|
||||||
|
"Scripts/Animation/Corrective_Shape_Key",
|
||||||
|
"tracker_url": "https://projects.blender.org/tracker/index.php?"\
|
||||||
|
"func=detail&aid=22129&group_id=153&atid=468",
|
||||||
|
'category': 'Animation'}
|
||||||
|
|
||||||
|
"""
|
||||||
|
This script transfer the shape from an object (base mesh without
|
||||||
|
modifiers) to another object with modifiers (i.e. posed Armature).
|
||||||
|
Only two objects must be selected.
|
||||||
|
The first selected object will be added to the second selected
|
||||||
|
object as a new shape key.
|
||||||
|
|
||||||
|
- Original 2.4x script by ? (brecht?)
|
||||||
|
- Unpose-function reused from a script by Tal Trachtman in 2007
|
||||||
|
http://www.apexbow.com/randd.html
|
||||||
|
- Converted to Blender 2.5 by Ivo Grigull
|
||||||
|
|
||||||
|
Limitations:
|
||||||
|
- Target mesh may not have any transformation at object level,
|
||||||
|
it will be set to zero.
|
||||||
|
- Fast/Armature method does not work with Bone envelopes or dual quaternions,
|
||||||
|
both settings will be disabled in the modifier
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import mathutils
|
||||||
|
|
||||||
|
|
||||||
|
iterations = 20
|
||||||
|
threshold = 1e-16
|
||||||
|
|
||||||
|
def reset_transform(ob):
|
||||||
|
m = mathutils.Matrix()
|
||||||
|
ob.matrix_local = m
|
||||||
|
|
||||||
|
# flips rotation matrix
|
||||||
|
def flip_matrix_direction(m):
|
||||||
|
mat = mathutils.Matrix()
|
||||||
|
|
||||||
|
mat[0][0] = m[0][0]
|
||||||
|
mat[0][1] = m[1][0]
|
||||||
|
mat[0][2] = m[2][0]
|
||||||
|
|
||||||
|
mat[1][0] = m[0][1]
|
||||||
|
mat[1][1] = m[1][1]
|
||||||
|
mat[1][2] = m[2][1]
|
||||||
|
|
||||||
|
mat[2][0] = m[0][2]
|
||||||
|
mat[2][1] = m[1][2]
|
||||||
|
mat[2][2] = m[2][2]
|
||||||
|
|
||||||
|
return mat
|
||||||
|
|
||||||
|
# this version is for shape_key data
|
||||||
|
def extractX(ob, mesh):
|
||||||
|
x = []
|
||||||
|
|
||||||
|
for i in range(0, len(mesh)):
|
||||||
|
v = mesh[i]
|
||||||
|
x += [mathutils.Vector(v.co)]
|
||||||
|
|
||||||
|
return x
|
||||||
|
|
||||||
|
# this version is for mesh data
|
||||||
|
def extractX_2(ob, mesh):
|
||||||
|
x = []
|
||||||
|
|
||||||
|
for i in range(0, len(mesh.vertices)):
|
||||||
|
v = mesh.vertices[i]
|
||||||
|
x += [mathutils.Vector(v.co)]
|
||||||
|
|
||||||
|
return x
|
||||||
|
|
||||||
|
def extractMappedX(ob, mesh):
|
||||||
|
totvert = len(mesh)
|
||||||
|
|
||||||
|
mesh = ob.create_mesh( bpy.context.scene, True, 'PREVIEW' )
|
||||||
|
|
||||||
|
x = []
|
||||||
|
|
||||||
|
# cheating, the original mapped verts happen
|
||||||
|
# to be at the end of the vertex array
|
||||||
|
for i in range(len(mesh.vertices)-totvert, len(mesh.vertices)):
|
||||||
|
v = mesh.vertices[i]
|
||||||
|
x += [mathutils.Vector(v.co)]
|
||||||
|
|
||||||
|
mesh.user_clear()
|
||||||
|
bpy.data.meshes.remove(mesh)
|
||||||
|
|
||||||
|
return x
|
||||||
|
|
||||||
|
def applyX(ob, mesh, x ):
|
||||||
|
for i in range(0, len(mesh)):
|
||||||
|
v = mesh[i]
|
||||||
|
v.co = x[i]
|
||||||
|
|
||||||
|
ob.data.update()
|
||||||
|
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
def func_add_corrective_pose_shape( source, target):
|
||||||
|
|
||||||
|
ob_1 = target
|
||||||
|
mesh_1 = target.data
|
||||||
|
ob_2 = source
|
||||||
|
mesh_2 = source.data
|
||||||
|
|
||||||
|
reset_transform(target)
|
||||||
|
|
||||||
|
# If target object doesn't have Basis shape key, create it.
|
||||||
|
try:
|
||||||
|
num_keys = len( mesh_1.shape_keys.keys )
|
||||||
|
except:
|
||||||
|
basis = ob_1.shape_key_add()
|
||||||
|
basis.name = "Basis"
|
||||||
|
ob_1.data.update()
|
||||||
|
|
||||||
|
|
||||||
|
key_index = ob_1.active_shape_key_index
|
||||||
|
# Insert new shape key
|
||||||
|
if key_index == 0:
|
||||||
|
new_shapekey = ob_1.shape_key_add()
|
||||||
|
new_shapekey.name = "Shape_" + ob_2.name
|
||||||
|
new_shapekey_name = new_shapekey.name
|
||||||
|
|
||||||
|
key_index = len(mesh_1.shape_keys.keys)-1
|
||||||
|
ob_1.active_shape_key_index = key_index
|
||||||
|
|
||||||
|
# else, the active shape will be used (updated)
|
||||||
|
|
||||||
|
ob_1.show_only_shape_key = True
|
||||||
|
|
||||||
|
vgroup = ob_1.active_shape_key.vertex_group
|
||||||
|
ob_1.active_shape_key.vertex_group = ""
|
||||||
|
|
||||||
|
mesh_1_key_verts = mesh_1.shape_keys.keys[ key_index ].data
|
||||||
|
|
||||||
|
|
||||||
|
x = extractX(ob_1, mesh_1_key_verts)
|
||||||
|
|
||||||
|
targetx = extractX_2(ob_2, mesh_2)
|
||||||
|
|
||||||
|
for iteration in range(0, iterations):
|
||||||
|
dx = [[], [], [], [], [], []]
|
||||||
|
|
||||||
|
mapx = extractMappedX(ob_1, mesh_1_key_verts)
|
||||||
|
|
||||||
|
# finite differencing in X/Y/Z to get approximate gradient
|
||||||
|
for i in range(0, len(mesh_1.vertices)):
|
||||||
|
epsilon = (targetx[i] - mapx[i]).length
|
||||||
|
|
||||||
|
if epsilon < threshold:
|
||||||
|
epsilon = 0.0
|
||||||
|
|
||||||
|
dx[0] += [x[i] + 0.5*epsilon*mathutils.Vector([1, 0, 0])]
|
||||||
|
dx[1] += [x[i] + 0.5*epsilon*mathutils.Vector([-1, 0, 0])]
|
||||||
|
dx[2] += [x[i] + 0.5*epsilon*mathutils.Vector([0, 1, 0])]
|
||||||
|
dx[3] += [x[i] + 0.5*epsilon*mathutils.Vector([0, -1, 0])]
|
||||||
|
dx[4] += [x[i] + 0.5*epsilon*mathutils.Vector([0, 0, 1])]
|
||||||
|
dx[5] += [x[i] + 0.5*epsilon*mathutils.Vector([0, 0, -1])]
|
||||||
|
|
||||||
|
for j in range(0, 6):
|
||||||
|
applyX(ob_1, mesh_1_key_verts, dx[j] )
|
||||||
|
dx[j] = extractMappedX(ob_1, mesh_1_key_verts)
|
||||||
|
|
||||||
|
# take a step in the direction of the gradient
|
||||||
|
for i in range(0, len(mesh_1.vertices)):
|
||||||
|
epsilon = (targetx[i] - mapx[i]).length
|
||||||
|
|
||||||
|
if epsilon >= threshold:
|
||||||
|
Gx = list((dx[0][i] - dx[1][i])/epsilon)
|
||||||
|
Gy = list((dx[2][i] - dx[3][i])/epsilon)
|
||||||
|
Gz = list((dx[4][i] - dx[5][i])/epsilon)
|
||||||
|
G = mathutils.Matrix(Gx, Gy, Gz)
|
||||||
|
G = flip_matrix_direction(G)
|
||||||
|
|
||||||
|
x[i] += (targetx[i] - mapx[i]) * G
|
||||||
|
|
||||||
|
applyX(ob_1, mesh_1_key_verts, x )
|
||||||
|
|
||||||
|
|
||||||
|
ob_1.active_shape_key.vertex_group = vgroup
|
||||||
|
|
||||||
|
# set the new shape key value to 1.0, so we see the result instantly
|
||||||
|
ob_1.active_shape_key.value = 1.0
|
||||||
|
|
||||||
|
#mesh_1.update()
|
||||||
|
ob_1.show_only_shape_key = False
|
||||||
|
|
||||||
|
|
||||||
|
class add_corrective_pose_shape(bpy.types.Operator):
|
||||||
|
'''Adds first object as shape to second object for the current pose while maintaining modifiers (i.e. anisculpt, avoiding crazy space) Beware of slowness!!!'''
|
||||||
|
|
||||||
|
bl_idname = "object.add_corrective_pose_shape"
|
||||||
|
bl_label = "Add object as corrective pose shape"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return context.active_object != None
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
|
||||||
|
if len(context.selected_objects) > 2:
|
||||||
|
print("Select source and target objects please")
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
selection = context.selected_objects
|
||||||
|
target = context.active_object
|
||||||
|
if context.active_object == selection[0]:
|
||||||
|
source = selection[1]
|
||||||
|
else:
|
||||||
|
source = selection[0]
|
||||||
|
|
||||||
|
#~ print(source)
|
||||||
|
#~ print(target)
|
||||||
|
func_add_corrective_pose_shape( source, target)
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
def func_object_duplicate_flatten_modifiers(ob, scene):
|
||||||
|
mesh = ob.create_mesh( bpy.context.scene, True, 'PREVIEW' )
|
||||||
|
name = ob.name + "_clean"
|
||||||
|
new_object = bpy.data.objects.new( name, mesh)
|
||||||
|
new_object.data = mesh
|
||||||
|
scene.objects.link(new_object)
|
||||||
|
return new_object
|
||||||
|
|
||||||
|
class object_duplicate_flatten_modifiers(bpy.types.Operator):
|
||||||
|
'''Duplicates the selected object with modifiers applied'''
|
||||||
|
|
||||||
|
bl_idname = "object.object_duplicate_flatten_modifiers"
|
||||||
|
bl_label = "Duplicate and apply all"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return context.active_object != None
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
new_object = func_object_duplicate_flatten_modifiers( context.active_object, context.scene )
|
||||||
|
context.scene.objects.active = new_object
|
||||||
|
|
||||||
|
for n in bpy.data.objects:
|
||||||
|
if n != new_object:
|
||||||
|
n.select = False
|
||||||
|
else:
|
||||||
|
n.select = True
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def flip_matrix_direction_4x4(m):
|
||||||
|
mat = mathutils.Matrix()
|
||||||
|
|
||||||
|
mat[0][0] = m[0][0]
|
||||||
|
mat[0][1] = m[1][0]
|
||||||
|
mat[0][2] = m[2][0]
|
||||||
|
mat[0][3] = m[3][0]
|
||||||
|
|
||||||
|
mat[1][0] = m[0][1]
|
||||||
|
mat[1][1] = m[1][1]
|
||||||
|
mat[1][2] = m[2][1]
|
||||||
|
mat[1][3] = m[3][1]
|
||||||
|
|
||||||
|
mat[2][0] = m[0][2]
|
||||||
|
mat[2][1] = m[1][2]
|
||||||
|
mat[2][2] = m[2][2]
|
||||||
|
mat[2][3] = m[3][2]
|
||||||
|
|
||||||
|
mat[3][0] = m[0][3]
|
||||||
|
mat[3][1] = m[1][3]
|
||||||
|
mat[3][2] = m[2][3]
|
||||||
|
mat[3][3] = m[3][3]
|
||||||
|
return mat
|
||||||
|
|
||||||
|
|
||||||
|
def unposeMesh(meshObToUnpose, meshObToUnposeWeightSrc, armatureOb):
|
||||||
|
psdMeshData = meshObToUnpose
|
||||||
|
|
||||||
|
psdMesh = psdMeshData
|
||||||
|
I = mathutils.Matrix() #identity matrix
|
||||||
|
|
||||||
|
meshData = meshObToUnposeWeightSrc.data
|
||||||
|
mesh = meshData
|
||||||
|
|
||||||
|
armData = armatureOb.data
|
||||||
|
|
||||||
|
pose = armatureOb.pose
|
||||||
|
pbones = pose.bones
|
||||||
|
|
||||||
|
|
||||||
|
for index, v in enumerate(mesh.vertices):
|
||||||
|
# above is python shortcut for:index goes up from 0 to tot num of verts in mesh,
|
||||||
|
# with index incrementing by 1 each iteration
|
||||||
|
|
||||||
|
psdMeshVert = psdMesh[index]
|
||||||
|
|
||||||
|
listOfBoneNameWeightPairs = []
|
||||||
|
for n in mesh.vertices[index].groups:
|
||||||
|
try:
|
||||||
|
name = meshObToUnposeWeightSrc.vertex_groups[n.group].name
|
||||||
|
weight = n.weight
|
||||||
|
is_bone = False
|
||||||
|
for i in armData.bones:
|
||||||
|
if i.name == name:
|
||||||
|
is_bone = True
|
||||||
|
break
|
||||||
|
# ignore non-bone vertex groups
|
||||||
|
if is_bone:
|
||||||
|
listOfBoneNameWeightPairs.append( [name, weight] )
|
||||||
|
except:
|
||||||
|
print('error')
|
||||||
|
pass
|
||||||
|
|
||||||
|
weightedAverageDictionary = {}
|
||||||
|
totalWeight = 0
|
||||||
|
for pair in listOfBoneNameWeightPairs:
|
||||||
|
totalWeight += pair[1]
|
||||||
|
|
||||||
|
for pair in listOfBoneNameWeightPairs:
|
||||||
|
if (totalWeight>0): #avoid divide by zero!
|
||||||
|
weightedAverageDictionary[pair[0]] = pair[1]/totalWeight
|
||||||
|
else:
|
||||||
|
weightedAverageDictionary[pair[0]] = 0
|
||||||
|
|
||||||
|
sigma = mathutils.Matrix(I-I) #Matrix filled with zeros
|
||||||
|
|
||||||
|
list = []
|
||||||
|
for n in pbones:
|
||||||
|
list.append(n)
|
||||||
|
list.reverse()
|
||||||
|
|
||||||
|
for pbone in list:
|
||||||
|
if pbone.name in weightedAverageDictionary:
|
||||||
|
#~ print("found key %s", pbone.name)
|
||||||
|
vertexWeight = weightedAverageDictionary[pbone.name]
|
||||||
|
m = pbone.matrix_channel.copy()
|
||||||
|
#m = flip_matrix_direction_4x4(m)
|
||||||
|
sigma += (m - I) * vertexWeight
|
||||||
|
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
#~ print("no key for bone " + pbone.name)
|
||||||
|
|
||||||
|
sigma = I + sigma
|
||||||
|
sigma.invert()
|
||||||
|
psdMeshVert.co = psdMeshVert.co * sigma
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def func_add_corrective_pose_shape_fast(source, target):
|
||||||
|
|
||||||
|
|
||||||
|
reset_transform(target)
|
||||||
|
|
||||||
|
# If target object doesn't have Basis shape key, create it.
|
||||||
|
try:
|
||||||
|
num_keys = len( target.data.shape_keys.keys )
|
||||||
|
except:
|
||||||
|
basis = target.shape_key_add()
|
||||||
|
basis.name = "Basis"
|
||||||
|
target.data.update()
|
||||||
|
|
||||||
|
key_index = target.active_shape_key_index
|
||||||
|
|
||||||
|
if key_index == 0:
|
||||||
|
|
||||||
|
# Insert new shape key
|
||||||
|
new_shapekey = target.shape_key_add()
|
||||||
|
new_shapekey.name = "Shape_" + source.name
|
||||||
|
new_shapekey_name = new_shapekey.name
|
||||||
|
|
||||||
|
key_index = len(target.data.shape_keys.keys)-1
|
||||||
|
target.active_shape_key_index = key_index
|
||||||
|
|
||||||
|
# else, the active shape will be used (updated)
|
||||||
|
|
||||||
|
target.show_only_shape_key = True
|
||||||
|
|
||||||
|
shape_key_verts = target.data.shape_keys.keys[ key_index ].data
|
||||||
|
|
||||||
|
try:
|
||||||
|
vgroup = target.active_shape_key.vertex_group
|
||||||
|
target.active_shape_key.vertex_group = ''
|
||||||
|
except:
|
||||||
|
print("blub")
|
||||||
|
pass
|
||||||
|
|
||||||
|
# copy the local vertex positions to the new shape
|
||||||
|
verts = source.data.vertices
|
||||||
|
for n in range( len(verts)):
|
||||||
|
shape_key_verts[n].co = verts[n].co
|
||||||
|
|
||||||
|
# go to all armature modifies and unpose the shape
|
||||||
|
for n in target.modifiers:
|
||||||
|
if n.type == 'ARMATURE' and n.show_viewport:
|
||||||
|
#~ print("got one")
|
||||||
|
n.use_bone_envelopes = False
|
||||||
|
n.use_deform_preserve_volume = False
|
||||||
|
n.use_vertex_groups = True
|
||||||
|
armature = n.object
|
||||||
|
unposeMesh( shape_key_verts, target, armature)
|
||||||
|
break
|
||||||
|
|
||||||
|
# set the new shape key value to 1.0, so we see the result instantly
|
||||||
|
target.data.shape_keys.keys[ target.active_shape_key_index].value = 1.0
|
||||||
|
|
||||||
|
try:
|
||||||
|
target.active_shape_key.vertex_group = vgroup
|
||||||
|
except:
|
||||||
|
#~ print("bluba")
|
||||||
|
pass
|
||||||
|
|
||||||
|
target.show_only_shape_key = False
|
||||||
|
target.data.update()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class add_corrective_pose_shape_fast(bpy.types.Operator):
|
||||||
|
'''Adds 1st object as shape to 2nd object as pose shape (only 1 armature)'''
|
||||||
|
|
||||||
|
bl_idname = "object.add_corrective_pose_shape_fast"
|
||||||
|
bl_label = "Add object as corrective shape faster"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return context.active_object != None
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
|
||||||
|
if len(context.selected_objects) > 2:
|
||||||
|
print("Select source and target objects please")
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
selection = context.selected_objects
|
||||||
|
target = context.active_object
|
||||||
|
if context.active_object == selection[0]:
|
||||||
|
source = selection[1]
|
||||||
|
else:
|
||||||
|
source = selection[0]
|
||||||
|
|
||||||
|
print(source)
|
||||||
|
print(target)
|
||||||
|
func_add_corrective_pose_shape_fast( source, target)
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## GUI
|
||||||
|
def vgroups_draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
|
||||||
|
layout.row().operator("object.add_corrective_pose_shape_fast", text='Add as corrective pose-shape (fast, armatures only)', icon='COPY_ID') # icon is not ideal
|
||||||
|
layout.row().operator("object.add_corrective_pose_shape", text='Add as corrective pose-shape (slow, all modifiers)', icon='COPY_ID') # icon is not ideal
|
||||||
|
|
||||||
|
def modifiers_draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
|
||||||
|
layout.operator("object.object_duplicate_flatten_modifiers" )
|
||||||
|
|
||||||
|
|
||||||
|
def register():
|
||||||
|
bpy.types.MESH_MT_shape_key_specials.append( vgroups_draw )
|
||||||
|
bpy.types.DATA_PT_modifiers.append( modifiers_draw )
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
pass
|
Reference in New Issue
Block a user