Shape Key Tools

So I’ve been rigging a character lately and I really needed a way of copying individually selected vertex positions from one shape key to another. What actually happenned was I had to fix a topology error and bmesh screwed up the positions of the edited virtices in all the shape keys. I started to manually edit the position of each vertice to put it back to the basis position and this took forever so I wrote this script to fix each one.

What it does:

it adds an operator to the mesh toolshelf that takes the selected vertices and moves them to the position of the basis for the shape key. I made it an operator because often I sculpt or edit a shape key with mirror on unintentionally and this allows me to select the parts that I didn’t intend to move and put them back to basis position.

RECENTLY ADDED: Select only verts modified by the shape key. This button is extremely helpful for identifying and fixing shape keys with unwanted modifications to the mesh in them.

I thought I would turn this into an official add on (with formatting and such) so that myself or others could add functionality as needed for helping to create shape keys.

the code is kind of slow, but hey it works.

Here’s the code:

# ##### 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 #####

# <pep8 compliant>

bl_info = {
    "name": "Shape Key Tools",
    "author": "Nick Keeline(nrk)",
    "version": (0, 0),
    "blender": (2, 6, 3),
    "location": "View3D > Tool Shelf > Shape Key Tools",
    "description": "Lets you modify Shape Keys (e.g. copy paste vert positions etc)",
    "wiki_url": "None yet"
                "NA",
    "tracker_url": "NA"
                   "",
    "category": "Mesh"}

import bpy
import bmesh
#from bpy.props import BoolProperty, EnumProperty
from bpy.types import Operator, Panel

def getCurrentSkeyandBasisKeyIndexes(obj, mesh):

        #Find the indices for current key and it's basis
        SKeyIndex = obj.active_shape_key_index
        skey = mesh.shape_keys
        skeys = skey.key_blocks.keys()
        #Thiskey = skeys[SKeyIndex]
        rkey = skey.reference_key
        
        i=0
        for s in skeys:
            if s.title() == rkey.name:
                BasisIndex = i
            i+=1
            
        #print(BasisIndex)  
        #print(SKeyIndex)    
        
        return BasisIndex, SKeyIndex

def getBMeshForSkeyIndex(mesh, SKeyIndex):
    
        bm = bmesh.new()
        bm.from_mesh(mesh, use_shape_key=True, shape_key_index=SKeyIndex)

        bmv = bm.verts
        bmv.index_update()    
        
        return bm, bmv
    
class VIEW3D_PT_tools_shapetools(Panel):
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'TOOLS'

    bl_label = "Shape Key Tools"
    bl_context = "mesh_edit"

    def draw(self, context):
        active_obj = context.active_object
        layout = self.layout
        col = layout.column(align=True)

        col.operator("shapetools.to_basis_pos", text="Move Verts To Basis Pos")
        col.operator("shapetools.select_dir_basis_pos", text="Select Verts diff from basis pos")

class SelectDifferenceFromBasisPos(Operator):
    """Various Tools for working with Shape Keys"""
    bl_idname = "shapetools.select_dir_basis_pos"
    bl_label = "Select Verts different from basis pos"
    bl_register = True
    bl_undo = True

    @classmethod
    def poll(cls, context):
        if not context.active_object:
            return False
        else:
            return (context.active_object.type == 'MESH')

    def execute(self, context):    
        
        bpy.ops.mesh.select_all(action='DESELECT')
        
        bpy.ops.object.editmode_toggle()
        bpy.ops.object.editmode_toggle()

        #print('Another Run')                
        myobj = bpy.context.active_object
        mymesh = myobj.data
        
        (BIndex, SIndex) = getCurrentSkeyandBasisKeyIndexes(myobj,mymesh)
        
        verts = []

        (bmBasis, bmvBasis) = getBMeshForSkeyIndex(mymesh, BIndex)

        (bcShape2Edit, bcvShape2Edit) = getBMeshForSkeyIndex(mymesh, SIndex)

        i = 0
        for v in bmvBasis:
            verts.append(v.co)
                #print(v.co)
                
        
        for v in bcvShape2Edit:
            if v.co != verts[i]:        
                #print(v.co)
                v.select = True
            i += 1
                
        bpy.ops.object.editmode_toggle()
        bcShape2Edit.to_mesh(mymesh)
        bpy.ops.object.editmode_toggle()

        bcShape2Edit.free()
        bmBasis.free()
        
        return {'FINISHED'}
    
class MoveToBasisPos(Operator):
    """Various Tools for working with Shape Keys"""
    bl_idname = "shapetools.to_basis_pos"
    bl_label = "Move Verts To Basis Pos"
    bl_register = True
    bl_undo = True

    @classmethod
    def poll(cls, context):
        if not context.active_object:
            return False
        else:
            return (context.active_object.type == 'MESH')

    def execute(self, context):    
        
        bpy.ops.object.editmode_toggle()
        bpy.ops.object.editmode_toggle()

        #print('Another Run')                
        myobj = bpy.context.active_object
        mymesh = myobj.data
        
        (BIndex, SIndex) = getCurrentSkeyandBasisKeyIndexes(myobj,mymesh)
        
        verts = []

        (bmBasis, bmvBasis) = getBMeshForSkeyIndex(mymesh, BIndex)

        (bcShape2Edit, bcvShape2Edit) = getBMeshForSkeyIndex(mymesh, SIndex)

        i = 0
        for v in bmvBasis:
            if v.select == True:
                #print(v.co)
                verts.append(v.co)
        
        for v in bcvShape2Edit:
            if v.select == True:        
                #print(v.co)
                v.co = verts[i]
                i += 1
                
        bpy.ops.object.editmode_toggle()
        bcShape2Edit.to_mesh(mymesh)
        bpy.ops.object.editmode_toggle()

        bcShape2Edit.free()
        bmBasis.free()

        return {'FINISHED'}


def register():
    bpy.utils.register_module(__name__)


def unregister():
    bpy.utils.unregister_module(__name__)

if __name__ == "__main__":
    register()

I’m working on a set of shape key tools myself… I’ve only done mirroring shape keys so far, but I’ll make some more functions when I think of them :wink: Would you mind if I added this to my tools (obviously stating you as the author)?

Go for it, I assume the mirror is different than the mirror shape key that’s in the pulldown arrow

Attachments


Yep it is. It basically just duplicates the shape key and then mirrors it, handling names and all that. I think thats what 2.49 used to do, I don’t remember.

Cool, I’m probably going to add another operator that selects all of the vertices that are different positions than the basis for you. I’ll post it here when I get a chance and you can put it in your tool too if you want.

Awesome, that’d be great

New addition to the script. I added a button that selects all of the vertices on a mesh that have been modified by the shape key. This combined with the reset to basis position allows a user to reset unwanted modifications made by the shape key back to their basis positions.

Great, thanks man. These’ll help nicely

I just discovered this useful and long awaited addon, I didn’t tried it yet, but I’m going to do, but I’m sure it works.
In this very moment I’d need a way to ‘subtract’ a shape key from another, so to isolate the differences between them, so I would say that the feature of adding or subtracting a choosable key to the selected one (only on selected vertices) could be an useful addition to the script, IMHO.

I’m tempted to do it myself, by copying your script like a monkey, but I could last forever…

Thank you very much, great job!!

paolo

I’ll give it a try in the next day or so and post it up for you.

Very kind of you, NRK!! Thanks.

paolo

I started into it and realized I needed to save a global xyz coordinates into an xyz array of variables to pull it off the way I wanted (I intend to make a copy and paste functionality where you can either paste absolute or paste by amount that adds and you can set the amount to negative numbers). Anyway as soon as I figure out how to save xyz (or if anyone could give me some example code) I’ll finish it up quite easily. All this long winded to say I still plan on doing this, it’s just going to take some more time as I’m swamped at work and home right now.

During these days I used your addon quite intensively, and I can confirm that it works fine, it’s a little slow but what matters is having its functionality.

About the adding features such what i suggested, I’m wondering myself how they should be intended and realized, and my feeling there is only one way it could be useful, the others not at all, and I can’t grasp it yet.

Anyway I read you have your plans, and I trust in your proved skill, just please, try to make things easy as much as possible.

Have a good time at work and at home, and thank you very much!

paolo

Yeah, I know it’s slow, but I didn’t write it to do good programming, I wrote it because I needed it for my production. Some day when I have lots of time (or someone else does) they can speed it up in C or use BMesh to properly access just the selected vertices without going through ALL of them(which my code does and makes it slow) because I don’t know bmesh well enough to do that.

Anyway, I’m glad it worked for you.

Cheers,

NRK

!!!Your script works for me!!! Thank you ,man!!!Is great…

During this period I’m using it daily, very useful !!

Thank you again NRK!

paolo

really handy! thankyou!

Okay, I investigated it a bit.
Here are my results:

There is the history where all selected vertices are being added - though I don’t know if they are removed if unselected but at least there exist operators for that case - but on the other hand history is history, so it’s not possible to change. Strange then that there are these API functions to remove vertices of history …

And bpy.types.mesh has total_selected_vertices so that makes it sound very possible that there is some reference/collection of the vertices in it also - but where, I can’t say either …
We need one of our Python/API pros or anybody that knows something about selected edit_mode mesh vertices.

For the history API see here:
http://www.blender.org/documentation/blender_python_api_2_64_release/bmesh.types.html?highlight=selected vertices#selection-history

And here go for the total vertices selected storage variable:
http://www.blender.org/documentation/blender_python_api_2_64_release/bpy.types.Mesh.html#bpy.types.Mesh.total_edge_sel

Hope this gets us forward.
Greeting,
Jon Ardaron

Thanks gents for the interest.

Thanks @faerietree for the info, I’ll research your links when I get some time… in summary the problem I have is I didn’t find a way in python to get a list of vertices that are selected. What I’m doing is checking all the vertices in the basis and then ALL of the vertices in in the currently selected shape key. If the selected boolean is true I move the vertex to it’s basis position. The script would be much faster if I could get a list of just the selected vertices and not run a for loop through EVERY vertex in the mesh.

I was going to write a script that does exactly that, reset vertices to their basic position in a shape key. Then I thought, I should check if there isn’t a tool like that yet… And I found this!!! tested it, and it works perfectly!!! Thank you for your script! I think a tool like that should be in the trunk, so useful!

Mathias.