These are some tools i wrote for creating/editing shapekeys in blender.
please modify/use at your own risk.
Happy Scripting and Sketching!
Nate
naShapekeyUtilsAddon from Nathan Anozie on Vimeo.
#naShapekeyUtilAddOn.py #modify use at your own risk import bpy import os import bmesh ####add on portion bl_info = { "name":"shapkey editing tools", "description":"tool to edit shapekeys", "category": "Object", "author":"Nathaniel Anozie", "blender":(2,79,0) } from bpy.types import( Operator, Panel, PropertyGroup ) from bpy.props import( StringProperty, PointerProperty ) class splitSymmetricShapeKeyOperator(Operator): """split selected shapekey. first select single mesh with shapekey in object mode and highlight shapekey wish to split. """ bl_idname = "obj.splitsymmetricshapekey" #needs to be all lowercase bl_label = "splitSymmetricShapeKey" bl_options = {"REGISTER"} def execute(self, context): splitSymmetricShapeKey(obj=context.selected_objects[0],removeSourceShapeKey = False, context = context) return {'FINISHED'} class mirrorShapekeyTopologyOperator(Operator): """first select single mesh with shapekey in object mode. """ bl_idname = "obj.mirrorshapekeytopology" bl_label = "mirrorShapekeyTopology" bl_options = {"REGISTER"} def execute(self, context): mirrorShapekeyTopology(obj=context.selected_objects[0],context=context) return {'FINISHED'} class mergeShapekeysOperator(Operator): """merge selected meshes shapekeys into one mesh """ bl_idname = "obj.mergeshapekeys" bl_label = "mergeShapekeys" bl_options = {"REGISTER"} def execute(self, context): mergeShapekeys(context=context) return {'FINISHED'} class zeroKeyOnSelectedVerticesOperator(Operator): """set selected vertices to basis position """ bl_idname = "obj.zerokeyonselectedvertices" bl_label = "zeroKeyOnSelectedVertices" bl_options = {"REGISTER"} def execute(self, context): zeroKeyOnSelectedVertices(context=context) return {'FINISHED'} class putMeshesInRowOperator(Operator): """arrange selected meshes in row with a little gap """ bl_idname = "obj.putmeshesinrow" bl_label = "putMeshesInRow" bl_options = {"REGISTER"} def execute(self, context): putMeshesInRow(context.selected_objects,gridWidth=1) return {'FINISHED'} class importObjOperator(Operator): """import objs in a directory """ bl_idname = "obj.importobj" bl_label = "importObj" bl_options = {"REGISTER"} def execute(self, context): path = context.scene.importobj_prop.importObjPath dirpath = bpy.path.abspath(path) print("importObjOperator dir path: %s" %(dirpath)) importObj(dirpath) return {'FINISHED'} class naShapekeyUtilPanel(Panel): bl_label = "naShapekeyUtil Panel" bl_space_type = "VIEW_3D" #needed for ops working properly bl_region_type = "UI" def draw(self, context): layout = self.layout layout.operator( "obj.splitsymmetricshapekey") layout.operator( "obj.mirrorshapekeytopology") layout.operator( "obj.mergeshapekeys") layout.operator( "obj.zerokeyonselectedvertices") layout.operator( "obj.putmeshesinrow") #for import obj layout.label(text = "import all obj in directory") layout.prop(context.scene.importobj_prop, "importObjPath") layout.operator( "obj.importobj") ## class importObjProperties(PropertyGroup): importObjPath = StringProperty( name = "Browse Directory", description = "Pick directory with .obj files", maxlen = 200, subtype = 'FILE_PATH' ) def register(): bpy.utils.register_class(splitSymmetricShapeKeyOperator) bpy.utils.register_class(mirrorShapekeyTopologyOperator) bpy.utils.register_class(mergeShapekeysOperator) bpy.utils.register_class(zeroKeyOnSelectedVerticesOperator) bpy.utils.register_class(putMeshesInRowOperator) bpy.utils.register_class(importObjOperator) bpy.utils.register_class(naShapekeyUtilPanel) bpy.utils.register_class(importObjProperties) bpy.types.Scene.importobj_prop = PointerProperty( type = importObjProperties ) def unregister(): bpy.utils.unregister_class(splitSymmetricShapeKeyOperator) bpy.utils.unregister_class(mirrorShapekeyTopologyOperator) bpy.utils.unregister_class(mergeShapekeysOperator) bpy.utils.unregister_class(zeroKeyOnSelectedVerticesOperator) bpy.utils.unregister_class(putMeshesInRowOperator) bpy.utils.unregister_class(importObjOperator) bpy.utils.unregister_class(naShapekeyUtilPanel) bpy.utils.unregister_class(importObjProperties) del bpy.types.Scene.importobj_prop if __name__ == "__main__": register() #### def splitSymmetricShapeKey(obj = None, removeSourceShapeKey = False, context = None): """going from symmetric shape key to a .L and .R shapekey works in xz plane only. duplicate shapekey twice > on one shape key set -x side to basis vert position, on other set +x side to basis vert position option to remove source shapekey after split """ def duplicateShapeKeyAtIndex(obj = None, sourceIndex = None, context = None): #returns index of created shapekey result = None obj.active_shape_key_index = sourceIndex obj.show_only_shape_key = True bpy.ops.object.shape_key_add(from_mix=True) result = context.object.active_shape_key_index return result #duplicate shape key twice #then zero out appropriate side of meshes sourceIndex = obj.active_shape_key_index sourceName = obj.data.shape_keys.key_blocks[sourceIndex].name leftIndex = duplicateShapeKeyAtIndex(obj,sourceIndex,context) rightIndex = duplicateShapeKeyAtIndex(obj,sourceIndex,context) obj.data.shape_keys.key_blocks[leftIndex].name = sourceName+'.L' obj.data.shape_keys.key_blocks[rightIndex].name = sourceName+'.R' context.object.active_shape_key_index = leftIndex zeroSelectedKeyInX(sign="-",includeCenter = False, context=context) #to avoid double transformation of center vertices bpy.context.object.active_shape_key_index = rightIndex zeroSelectedKeyInX(sign="+",includeCenter = True, context=context) #optionally delete source shape key if removeSourceShapeKey: obj.active_shape_key_index = sourceIndex bpy.ops.object.shape_key_remove(all=False) def mirrorShapekeyTopology(obj=None, context = None): """for getting all shapekeys have mirrored topology first copy out all shapekeys to a new mesh > each mesh no shapekeys > each mesh symmetric topology second, remove all shapekeys on mesh we wish to mirror > make its topology mirrored third, apply all created meshes as shapekeys on mesh note doesnt preserve drivers on shapkeys. """ dupObjs = [] #list of tuples obj, shapekey name def makeMeshUsingShapekeyIndex( obj=None, index = 1,context=None ): #result mesh no shapekeys. its shape would have matched shapekey at index dupMeshObj = None bpy.ops.object.select_all(action='DESELECT') obj.select = True context.scene.objects.active = obj bpy.ops.object.duplicate() dupMeshObj = context.selected_objects[-1] #first remove all shape keys on dupped object #then transfer just the given index onto dupped object #finally remove the basis shape and then last the only shapekey to get duped object at pose of index bpy.ops.object.shape_key_remove(all=True) obj.active_shape_key_index = index bpy.ops.object.select_all(action='DESELECT') obj.select = True dupMeshObj.select = True context.scene.objects.active = dupMeshObj bpy.ops.object.shape_key_transfer() bpy.ops.object.select_all(action='DESELECT') dupMeshObj.select = True context.scene.objects.active = dupMeshObj dupMeshObj.active_shape_key_index = 0 bpy.ops.object.shape_key_remove(all=False) bpy.ops.object.shape_key_remove(all=True) return dupMeshObj def getShapekeyName( geoName = None, dupObjsL = [] ): #dependent on data format tuples for arg in dupObjsL: if arg[0].name == geoName: return arg[1] if not obj.data.shape_keys: return countShapeKeys = len(obj.data.shape_keys.key_blocks) for i in range(1,countShapeKeys): dupObj = makeMeshUsingShapekeyIndex(obj,i,context) #make dupped object symmetric deleteHalfMesh(dupObj) makeMeshWhole(dupObj) dupObjs.append( (dupObj, obj.data.shape_keys.key_blocks[i].name) ) #done duping all objects #remove all shapekeys from source object #make it mirrored #note not preserving drivers on shapekeys bpy.ops.object.select_all(action='DESELECT') obj.select = True context.scene.objects.active = obj bpy.ops.object.shape_key_remove(all=True) deleteHalfMesh(obj) makeMeshWhole(obj) # #apply all of the duped objects as shapekeys bpy.ops.object.select_all(action='DESELECT') for dupTuple in dupObjs: dObj = dupTuple[0] dObj.select = True obj.select = True context.scene.objects.active = obj bpy.ops.object.join_shapes() #fix names of shapekeys to match original for j in range(1,len(obj.data.shape_keys.key_blocks)): kblock = obj.data.shape_keys.key_blocks[j] n = getShapekeyName( kblock.name, dupObjs ) kblock.name = n #cleanup bpy.ops.object.select_all(action='DESELECT') for dupTuple in dupObjs: dObj = dupTuple[0] dObj.select = True context.scene.objects.active = dupObjs[0][0] bpy.ops.object.delete() #restore selection obj.select = True context.scene.objects.active = obj def importObj(shapeDir = ''): """import all .obj in shapeDir, use obj names for blender geo names """ if not os.path.exists(shapeDir): return #find all objs in folder objFileNames = [] toNames = [] #names to use for meshes in blender #if its a folder get all objs in it if os.path.isdir(shapeDir): for fpath in os.listdir(shapeDir): ff,fext = os.path.splitext(fpath) if fext.lower() == ".obj": objFileNames.append(fpath) toNames.append(ff) else: #import just the single obj path fpath = shapeDir fffull,fext = os.path.splitext(fpath) if fext.lower() == ".obj": fpathshort = os.path.split(fffull)[-1] objFileNames.append(fpath) toNames.append(fpathshort) #import objs into blender for f,toName in zip(objFileNames,toNames): fileName = os.path.join(shapeDir,f) importedObj = bpy.ops.import_scene.obj(filepath = fileName, use_split_objects = False, use_split_groups = False) obj = bpy.context.selected_objects[0] obj.name = toName obj.data.name = toName def putMeshesInRow(meshObjs = [], gridWidth = 1): """ #position given mesh objects in grid > nice to use bounding box #input how far apart want mesh """ meshes = [m for m in meshObjs if m.type == 'MESH'] xpos = 0 for mesh in meshes: mesh.location = (xpos,0,0) xdim = mesh.dimensions[0] xpos += xdim+gridWidth #move a whole width of object plus given x offset def mergeShapekeys(context = None): """ 1.given bunch of meshes with shape keys put all those shape keys on last selected object. last selection is target mesh """ targetMesh = context.active_object sourceMeshes = [msh for msh in context.selected_objects if msh.name != targetMesh.name] if len(sourceMeshes) == 0: print("please select 1 or more source meshes then last target mesh") return for sourceMesh in sourceMeshes: #selection bpy.ops.object.select_all(action='DESELECT') sourceMesh.select = True targetMesh.select = True context.scene.objects.active = targetMesh #changing active shapekey index to transfer shapes shapeKey = sourceMesh.data.shape_keys #if no shape key on source mesh skip it if not shapeKey: continue maxShapeKeyIndex = len(shapeKey.key_blocks) for i in range(1,maxShapeKeyIndex): sourceMesh.active_shape_key_index = i bpy.ops.object.shape_key_transfer() targetMesh.show_only_shape_key = False #cleanup selection bpy.ops.object.select_all(action='DESELECT') targetMesh.select = True context.scene.objects.active = targetMesh def zeroKeyOnSelectedVertices(context = None): """ this zero out all selected vertices of shape key. """ obj = context.active_object curMode = None if context.object: curMode = context.object.mode bpy.ops.object.mode_set(mode='OBJECT') #if something is selected go to object mode #assumes basis shape at index 0 vertIndex = [v.index for v in obj.data.vertices if v.select] for i in vertIndex: basisx = obj.data.shape_keys.key_blocks[0].data[i].co.x basisy = obj.data.shape_keys.key_blocks[0].data[i].co.y basisz = obj.data.shape_keys.key_blocks[0].data[i].co.z #modify selected shape key activeIndex = obj.active_shape_key_index if activeIndex > 0: #zero out shape obj.data.shape_keys.key_blocks[activeIndex].data[i].co.x = basisx obj.data.shape_keys.key_blocks[activeIndex].data[i].co.y = basisy obj.data.shape_keys.key_blocks[activeIndex].data[i].co.z = basisz #restore mode if curMode: bpy.ops.object.mode_set(mode=curMode) def zeroSelectedKeyInX(sign="+", includeCenter = False, context = None): """ this zero out all vertices of shape key that are in direction of x axis. supports positive or negative x axis optionally zero out center vertices """ def zeroShape(obj=None,vid=None): #zero shape on given vertex id basisx = obj.data.shape_keys.key_blocks[0].data[vid].co.x basisy = obj.data.shape_keys.key_blocks[0].data[vid].co.y basisz = obj.data.shape_keys.key_blocks[0].data[vid].co.z activeIndex = obj.active_shape_key_index if activeIndex > 0: #zero out shape obj.data.shape_keys.key_blocks[activeIndex].data[vid].co.x = basisx obj.data.shape_keys.key_blocks[activeIndex].data[vid].co.y = basisy obj.data.shape_keys.key_blocks[activeIndex].data[vid].co.z = basisz obj = context.active_object #assumes basis shape at index 0 verts = obj.data.vertices if sign == "+": for i in range(len(verts)): basisx = obj.data.shape_keys.key_blocks[0].data[i].co.x if includeCenter: if basisx >= 0: zeroShape(obj,i) else: if basisx > 0: zeroShape(obj,i) else: for i in range(len(verts)): basisx = obj.data.shape_keys.key_blocks[0].data[i].co.x if includeCenter: if basisx <= 0: zeroShape(obj,i) else: if basisx < 0: zeroShape(obj,i) def zeroAllKeys(): """ when first testing shapekeys. this zeros them all out again """ obj = bpy.context.active_object allKeys = obj.data.shape_keys.key_blocks.keys() for key in allKeys: obj.data.shape_keys.key_blocks[key].value = 0 def removeDigitShapeKeys(): """noticed in fbx import from zbrush extra shape keys with digits at end this removes those shape keys """ obj = bpy.context.active_object allKeys = obj.data.shape_keys.key_blocks.keys() keysEndDigit = [x for x in allKeys if x[-1].isdigit()] #print(keysEndDigit) for shape in keysEndDigit: index = obj.data.shape_keys.key_blocks.keys().index(shape) obj.active_shape_key_index = index bpy.ops.object.shape_key_remove() def makeMeshWhole(obj): """default mirror from +x to -x of selected mesh, assumes no mirror modifiers on mesh to start """ bpy.ops.object.modifier_add(type='MIRROR') bpy.ops.object.modifier_apply(modifier='Mirror') def deleteHalfMesh(obj): """ default deletes -x side of selected mesh. standalone need give it bpy.context.object for selected object """ #get current position curpos = () curpos = (obj.location.x,obj.location.y,obj.location.z) #put obj at origin setLocation(obj,(0,0,0)) selectedObj = obj #bpy.context.selected_objects[0] bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.select_all( action='DESELECT') bpy.ops.mesh.select_mode(type='FACE') bm = bmesh.from_edit_mesh(selectedObj.data) for face in bm.faces: faceWorldPos = selectedObj.matrix_world*face.calc_center_median() #calc_center_median same as face.center using obj.data.polygons #[0] > 0.0 would be delete right half of mesh #[1] < 0.0 would be delete in y direction if faceWorldPos[0] < 0.0: face.select = True bm.select_flush(True) bpy.ops.mesh.delete(type='FACE') bpy.ops.object.mode_set(mode='OBJECT') #restore location setLocation(obj,curpos) def setLocation(obj,pos): """ does location only. pos tuple (2.3,0,0) """ obj.location.x = pos[0] obj.location.y = pos[1] obj.location.z = pos[2] """TODO -going from a .L shapekey to a .R shapekey try to use blenders mirror shapekey ops -simple animation of shapekeys """ """ import bpy import sys sys.path.append("/users/Nathaniel/Documents/src_blender/python/naBlendShape") import naShapekeyUtilAddOn as mod import imp imp.reload(mod) #mod.importObj('/Users/Nathaniel/Documents/src_blender/python/snippets/pipeTools') """ """for making a control made two bone armature. parented one bone to other. created cube,circle shapes. in pose mode added shapes to bones. translating parent bone moves child without changing its local transforms so they can be used for driving blendshape. (changed roll bone 180 so up was positive down negative) """ #inspired by #https://blender.stackexchange.com/questions/1412/efficient-way-to-get-selected-vertices-via-python-without-iterating-over-the-en #https://blender.stackexchange.com/questions/111661/creating-shape-keys-using-python #https://blenderartists.org/t/delete-shape-key-by-name-via-python/521762/3 #https://stackoverflow.com/questions/14471177/python-check-if-the-last-characters-in-a-string-are-numbers #https://stackoverflow.com/questions/3964681/find-all-files-in-a-directory-with-extension-txt-in-python #https://stackoverflow.com/questions/541390/extracting-extension-from-filename-in-python #https://stackoverflow.com/questions/8933237/how-to-find-if-directory-exists-in-python #https://blender.stackexchange.com/questions/18035/code-inside-function-not-working-as-it-should #https://blender.stackexchange.com/questions/43820/how-to-use-the-file-browsers-with-importhelper-execute-function #https://blender.stackexchange.com/questions/42654/ui-how-to-add-a-file-browser-to-a-panel #https://blender.stackexchange.com/questions/23258/trouble-file-stringproperty-subtype-file-path