XYZ Shapes

Hi!

I am more of an animator/modeller and starting to explore the Python API in Blender.
I have been reading Jason Osipa’s “Stop Staring - Facial Modeliing and Animation Done Right”. Along the way, I am adapting some of the techniques done with Maya (that’s what the author uses) into my Blender workflow. Along the way, I also manage to adapt some Maya Mel scripts by using modifiers, drivers and seldom some fairly simple scripts. However I got stuck trying to translate one Mel Script.

The script basically picks the selected shape key and creates three new partial copies of it. In the first copy, we have the shape key animation along the X axis. On the second copy we have shape key animation along the Y axis. The third one is the animation along the Z action. I hope I was clear on this.

So far I made this:


import bpy

scene = bpy.context.scene
obj = scene.objects.active
shape_index = bpy.context.object.active_shape_key_index
bpy.ops.object.shape_key_add(from_mix=True)
bpy.ops.object.shape_key_add(from_mix=True)
bpy.ops.object.shape_key_add(from_mix=True)

#do something here

bpy.context.object.active_shape_key_index = bpy.context.object.active_shape_key_index - 2

#do something here

bpy.context.object.active_shape_key_index = bpy.context.object.active_shape_key_index + 1

#do something here


The idea is to create the copies, and delete the axes that will not be used in each one of them.

I also looked into the bmesh module API to find some command that could delete the animation in each axis, like we can do in the graph editor, but I did not find anything. Maybe I am using a wrong approach to this problem. :mad:

As a sidenote, I am also planning to give a name to each copy, for example “_X”, “_Y”, “_Z”, but still have not found a way to do that.

I tried to assign a var to bpy.context.object.active_shape_key_index object , so I don’t have to write all of it while I am scripting, but somehow the script ceases to select the shape keys if I do that. :mad:

My plan is to turn this into an addon later, right now I prefer to focus on funtionality.

Can anyone help me with this one?

Thanks! :o

Well, I have made some advancements

Here is the code:


import bpy

context = bpy.context
scene = context.scene
obj = context.object
shape_name = obj.active_shape_key.name
shape_keys = obj.data.shape_keys
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)
#do something here
obj.active_shape_key_index = obj.active_shape_key_index - 2

#do something here
obj.active_shape_key_index = obj.active_shape_key_index + 1

#do something here

Now the script creates 3 new shape keys with an “_X”, “_Y” and “_Z” postfixes.
To have the last shaped_key selected, I created another shape key and then deleted it immediately. Yes, it is an ugly hack. If anyone knows a more elegant solution, I will be thankfull for the input.

I still don’t have a clue how to access the vertex data in a shape key, so I can separate the vertices movement into these three new shape keys.

Any ideas? :slight_smile:

So I managed to get it with the precious help from sambler from Stackexchange:


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'}
    
    @classmethod
    def poll(cls, context):
        return context.active_object.active_shape_key is not None
    
    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()


Hope it is useful to you all.

1 Like