# Script to make copies of a shape key, each one with an isolated translate channel

I have been reading Jason Osipa's "Stop Staring - Facial Modeling and Animation Done Right". In the book, there is an interesting Mel script I am trying to port to Blender, so far with no success

Basically, the script copies a shape_key andcreates 3 new shape keys, and in each one, the vertices translations in each axis are isolated. Thus we have three new shape keys, one being for the X axis, another being for the Y axis and another for the Z axis. The three combined are equal to the original shape_key.

I know I can access the coordinates of the vertices in the shape key through:

object.data.shape_keys.key_blocks['Key 1'].data[#].co


('Key 1' being the shape key name and # the vertex index)

I also know I can access the vertices in their original position through:

bpy.context.object.active_shape_key.data[#].co


(# being the vertex index)

Thus, I thought the code would be something like:

import bpy

context = bpy.context
scene = context.scene
obj = context.object
shape_name = obj.active_shape_key.name

bpy.ops.object.shape_key_remove(all=False)   #Hack to select the last shape_key in the list

shape_name = obj.active_shape_key.name #Update the active_shape_key.name

for vert in obj.data.vertices: #Isolate the translation on the Z axis first
obj.data.shape_keys.key_blocks[shape_name].data[vert.index].co.x = obj.active_shape_key.data[vert.index].co.x
obj.data.shape_keys.key_blocks[shape_name].data[vert.index].co.y = obj.active_shape_key.data[vert.index].co.y

obj.active_shape_key_index = obj.active_shape_key_index - 2

shape_name = obj.active_shape_key.name

for vert in obj.data.vertices: #Isolate the translation on the X axis
obj.data.shape_keys.key_blocks[shape_name].data[vert.index].co.y = obj.active_shape_key.data[vert.index].co.y
obj.data.shape_keys.key_blocks[shape_name].data[vert.index].co.z = obj.active_shape_key.data[vert.index].co.z

obj.active_shape_key_index = obj.active_shape_key_index + 1

shape_name = obj.active_shape_key.name

for vert in obj.data.vertices: #Isolate the translation on the Y axis
obj.data.shape_keys.key_blocks[shape_name].data[vert.index].co.x = obj.active_shape_key.data[vert.index].co.x
obj.data.shape_keys.key_blocks[shape_name].data[vert.index].co.z = obj.active_shape_key.data[vert.index].co.z


However, it does not work and it's driving me mad. Any suggestions?

• Are you planning to make an addon from this? If not mind if I do? – sambler Jan 16 '15 at 10:18
• @sambler, Hi I only noticed today your offer to make the add-on out of this. I wouldn't mind, But since I already made the add-on, I will post the code as a reply to my original question. I would like to put your name on the script as co-author, if you will not mind. :-) – Eduardo Jorge Teixeira Jan 22 '15 at 22:06
• That's fine. I only offered to turn your code into an addon if you didn't feel up to doing it. – sambler Jan 23 '15 at 15:37

First point would be that shape_key_add(from_mix=True) will copy the shape key as it is, you may want to be sure that the shapekey is in effect when you copy it.

obj.active_shape_key.value = obj.active_shape_key.slider_max


Rather than changing the active shapekey for each step, get the index of each as you create them and use that when adjusting the copy. The new shapekey will be added to the end of the list so it's index will be len(key_blocks)-1

xshape_idx = len(obj.data.shape_keys.key_blocks)-1


Now the key point -

obj.data.shape_keys.key_blocks[shape_name].data[vert.index].co.x
= obj.active_shape_key.data[vert.index].co.x


you set obj.active_shape_key_index then you copy active_shape_key.data[vert.index] into key_blocks[shape_name].data[vert.index] that is two ways to the same data. I would suggest setting the unwanted locations to the Basis shapekey locations.

obj.data.shape_keys.key_blocks[xshape_idx].data[vert.index].co.x
= obj.data.shape_keys.key_blocks['Basis'].data[vert.index].co.x


I finished up with -

import bpy

context = bpy.context
scene = context.scene
obj = context.object
shape_name = obj.active_shape_key.name

## enable shapekey before copying
skey_value = obj.active_shape_key.value
obj.active_shape_key.value = obj.active_shape_key.slider_max

xshape_idx = len(obj.data.shape_keys.key_blocks)-1
yshape_idx = len(obj.data.shape_keys.key_blocks)-1
zshape_idx = len(obj.data.shape_keys.key_blocks)-1

## reset shapekey
obj.active_shape_key.value = skey_value

for vert in obj.data.vertices: #Isolate the translation on the X axis
obj.data.shape_keys.key_blocks[xshape_idx].data[vert.index].co.y = obj.data.shape_keys.key_blocks['Basis'].data[vert.index].co.y
obj.data.shape_keys.key_blocks[xshape_idx].data[vert.index].co.z = obj.data.shape_keys.key_blocks['Basis'].data[vert.index].co.z

for vert in obj.data.vertices: #Isolate the translation on the Y axis
obj.data.shape_keys.key_blocks[yshape_idx].data[vert.index].co.x = obj.data.shape_keys.key_blocks['Basis'].data[vert.index].co.x
obj.data.shape_keys.key_blocks[yshape_idx].data[vert.index].co.z = obj.data.shape_keys.key_blocks['Basis'].data[vert.index].co.z

for vert in obj.data.vertices: #Isolate the translation on the Z axis
obj.data.shape_keys.key_blocks[zshape_idx].data[vert.index].co.x = obj.data.shape_keys.key_blocks['Basis'].data[vert.index].co.x
obj.data.shape_keys.key_blocks[zshape_idx].data[vert.index].co.y = obj.data.shape_keys.key_blocks['Basis'].data[vert.index].co.y

• Thanks for that, works great. BTW: An addon would be nice to have :) – p2or Jan 16 '15 at 10:26
• Thank you for your help sambler! :-D I learned quite a bit from your answer. I will try to create the addon next week and post it over here. Once again, my thanks! – Eduardo Jorge Teixeira Jan 16 '15 at 16:12

bl_info = {
"name": "Split Shape Key",
"author": "Eduardo Teixeira, Sambler",
"version": (1, 0),
"blender": (2, 73, 0),
"location": "Object > Animation > Split Shape Key",
"description": "Takes a shape key and splits its vertices translation into three new shape keys, one per axis, preserving the original",
"warning": "",
#"wiki_url":"",
#"tracker_url": "",
"category": "Animation",
}

import bpy

class SplitShapeKey(bpy.types.Operator):
"""Takes a shape key and splits its vertices translation"""\
""" into three new shape keys, one per axis, preserving the original """
bl_idname = "anim.split_shape_key"
bl_label = "Split Shape Key"
bl_options = {'REGISTER', 'UNDO'}

def execute(self, context):
context = bpy.context
scene = context.scene
obj = context.object
shape_name = obj.active_shape_key.name

## enable shapekey before copying
skey_value = obj.active_shape_key.value
obj.active_shape_key.value = obj.active_shape_key.slider_max

xshape_idx = len(obj.data.shape_keys.key_blocks)-1
yshape_idx = len(obj.data.shape_keys.key_blocks)-1
zshape_idx = len(obj.data.shape_keys.key_blocks)-1

## reset shapekey
obj.active_shape_key.value = skey_value

for vert in obj.data.vertices: #Isolate the translation on the X axis
obj.data.shape_keys.key_blocks[xshape_idx].data[vert.index].co.y = obj.data.shape_keys.key_blocks['Basis'].data[vert.index].co.y
obj.data.shape_keys.key_blocks[xshape_idx].data[vert.index].co.z = obj.data.shape_keys.key_blocks['Basis'].data[vert.index].co.z

for vert in obj.data.vertices: #Isolate the translation on the Y axis
obj.data.shape_keys.key_blocks[yshape_idx].data[vert.index].co.x = obj.data.shape_keys.key_blocks['Basis'].data[vert.index].co.x
obj.data.shape_keys.key_blocks[yshape_idx].data[vert.index].co.z = obj.data.shape_keys.key_blocks['Basis'].data[vert.index].co.z

for vert in obj.data.vertices: #Isolate the translation on the Z axis
obj.data.shape_keys.key_blocks[zshape_idx].data[vert.index].co.x = obj.data.shape_keys.key_blocks['Basis'].data[vert.index].co.x
obj.data.shape_keys.key_blocks[zshape_idx].data[vert.index].co.y = obj.data.shape_keys.key_blocks['Basis'].data[vert.index].co.y

return{'FINISHED'}

class SplitShapePanel(bpy.types.Panel):
"""Creates a Panel in the Tools Window > Animation Tab"""

bl_space_type = 'VIEW_3D'
bl_region_type = 'TOOLS'
bl_category = 'Animation'
bl_label = "Split Shape Key"
bl_context = "objectmode"
bl_options = {'DEFAULT_CLOSED'}

def draw(self, context):
layout = self.layout

row = layout.row()
row.operator("anim.split_shape_key")

self.layout.operator(SplitShapeKey.bl_idname)

def register():
bpy.utils.register_class(SplitShapeKey)