3
$\begingroup$

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

obj.shape_key_add(name=str(shape_name) + "_X", from_mix=True)
obj.shape_key_add(name=str(shape_name) + "_Y", from_mix=True)
obj.shape_key_add(name=str(shape_name) + "_Z", from_mix=True)
bpy.ops.object.shape_key_add(from_mix=False)
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?

$\endgroup$
  • $\begingroup$ Are you planning to make an addon from this? If not mind if I do? $\endgroup$ – sambler Jan 16 '15 at 10:18
  • $\begingroup$ @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. :-) $\endgroup$ – Eduardo Jorge Teixeira Jan 22 '15 at 22:06
  • $\begingroup$ That's fine. I only offered to turn your code into an addon if you didn't feel up to doing it. $\endgroup$ – sambler Jan 23 '15 at 15:37
3
$\begingroup$

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

obj.shape_key_add(name=str(shape_name) + "_X", from_mix=True)
xshape_idx = len(obj.data.shape_keys.key_blocks)-1
obj.shape_key_add(name=str(shape_name) + "_Y", from_mix=True)
yshape_idx = len(obj.data.shape_keys.key_blocks)-1
obj.shape_key_add(name=str(shape_name) + "_Z", from_mix=True)
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
$\endgroup$
  • $\begingroup$ Thanks for that, works great. BTW: An addon would be nice to have :) $\endgroup$ – p2or Jan 16 '15 at 10:26
  • $\begingroup$ 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! $\endgroup$ – Eduardo Jorge Teixeira Jan 16 '15 at 16:12
2
$\begingroup$

Here's the addon code:

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

        obj.shape_key_add(name=str(shape_name) + "_X", from_mix=True)
        xshape_idx = len(obj.data.shape_keys.key_blocks)-1
        obj.shape_key_add(name=str(shape_name) + "_Y", from_mix=True)
        yshape_idx = len(obj.data.shape_keys.key_blocks)-1
        obj.shape_key_add(name=str(shape_name) + "_Z", from_mix=True)
        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")

def menu_func(self, context):
    self.layout.operator(SplitShapeKey.bl_idname)

def register():
    bpy.utils.register_class(SplitShapeKey)
    bpy.types.VIEW3D_MT_object_animation.append(menu_func)
    bpy.utils.register_class(SplitShapePanel)

def unregister():
    bpy.utils.unregister_class(SplitShapeKey)
    bpy.types.VIEW3D_MT_object_animation.append(menu_func)
    bpy.utils.register_class(SplitShapePanel)    

if __name__ == "__main__":
register()
$\endgroup$
  • $\begingroup$ Thanks for this! A way to enhance this (to avoid problems in the future) is to make sure all other keys are muted \ temporarily zeroed before calling the 'shape_key_add' function, otherwise you risk adding other shapekeys into the mix of the new ones. $\endgroup$ – R. Navega May 6 '18 at 1:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.