naPaintBlendshapeAddOn for Blender from Nathan Anozie on Vimeo.
Hi,
This is a tool to help modify shapekeys/blendshapes using painted weights. example used to paint out eyelids from brow down shape.
it works by first selecting blendshape then the default base mesh.
modify use at your own risk.
Happy Scripting! Sketching!
to install naPaintBlendshape.py needs to be in addon directory, and naPaintBlendshapeAddOn.py needs to be installed in blender via user preferences.
#naPaintBlendshape.py #this is a tool to help modify shapekeys using painted weights. example used to paint out eyelids from brow down shape key """need imp to reload script import bpy import sys #example if wanted to test script without addon part. change to your path here sys.path.append('/Users/Nathaniel/Documents/src_blender/python/snippets/blendshapes') import naPaintBlendshape as mod import imp imp.reload(mod) bpy.app.debug=True #temporary #first version for addon select shape then default mesh mod.paintBSWeights(bpy.context) mod.doIt(bpy.context) mod.cleanUp(bpy.context) """ import bpy import re def doIt(context = None): """ sculpt new blendshape using painted weights assumes painted mesh is selected it assumes a painted mesh is available with same name as shape mesh with _naPaint suffix """ if not context.active_object: print("requires paint mesh selected") return paintMeshName = context.active_object.name if not bpy.data.objects.get(paintMeshName): print("cannot find paint mesh %s. first click paint weight button" %paintMeshName) return pattern = re.compile(".*naPnt_(.*)_naPnt_(.*)") if not pattern.match(paintMeshName): print("cannot find default and blendshape from painted mesh name") return patternGroups = pattern.match(paintMeshName).groups() if not patternGroups or len(patternGroups) != 2: print("cannot find default and blendshape from painted mesh name") return defaultMeshObj = bpy.data.objects[patternGroups[0]] bsMeshObj = bpy.data.objects[patternGroups[1]] print("using defaultMesh:%s reference shape:%s painted weights:%s" %(defaultMeshObj.name,bsMeshObj.name,paintMeshName) ) weightsObj = bpy.data.objects[paintMeshName] newBS = weightsObj #maybe in future make another mesh for newBS wObj = weightsObj #get new position for vertex #set vertex position on newBS for i in range(len(wObj.data.vertices)): wt = wObj.data.vertices[i].groups[0].weight #assuming one vertex group on mesh posx = wt*bsMeshObj.data.vertices[i].co[0]+(1-wt)*defaultMeshObj.data.vertices[i].co[0] posy = wt*bsMeshObj.data.vertices[i].co[1]+(1-wt)*defaultMeshObj.data.vertices[i].co[1] posz = wt*bsMeshObj.data.vertices[i].co[2]+(1-wt)*defaultMeshObj.data.vertices[i].co[2] newBS.data.vertices[i].co[0] = posx newBS.data.vertices[i].co[1] = posy newBS.data.vertices[i].co[2] = posz def paintBSWeights(context = None): """ returns mesh to paint weights for blendshape assumes first selected is shape second selected is default mesh """ #get selection selected = context.selected_objects if not len(selected) == 2: print("please select shape then default mesh") return defaultMeshObj = context.active_object selected.remove(defaultMeshObj) bsMeshObj = selected[0] ### paintMeshName = 'naPnt_%s_naPnt_%s'%(defaultMeshObj.name,bsMeshObj.name) if bpy.data.objects.get(paintMeshName): print("painted mesh for selection already exists doing nothing") return weightsObj = duplicateMesh( context, defaultMeshObj) #rename it so we can keep track whether it was made already weightsObj.name = paintMeshName weightsObj.data.name = paintMeshName #bboxy = defaultMeshObj.dimensions.y #weightsObj.location = [0,-(bboxy+0.25*bboxy),0] #using y, and using bounding box to place new bs makeObjActive(context,weightsObj) #create vertex group for weights vgrp = weightsObj.vertex_groups.new(name='naPnt') verts = [] for vert in weightsObj.data.vertices: verts.append(vert.index) vgrp.add(verts,0.0,'ADD') #by default non of blendshape used bpy.ops.paint.weight_paint_toggle() return weightsObj def cleanUp(context = None): """ removes vertex group created, cleans up name assumes painted mesh is selected """ if not context.active_object: print("requires paint mesh selected") return obj = context.active_object removeVertexGroupsFromObj(obj) #bpy.ops.paint.weight_paint_toggle() #get out of paint mode #rename mesh simpleName = obj.name.split('naPnt_')[-1]+'_New' obj.name = simpleName obj.data.name = simpleName def duplicateMesh(context, sourceObj=None): """ return new mesh (ex: bpy.data.objects['Plane.001']) given sourceObj ex:bpy.data.objects['Plane'] and context bpy.context """ if not sourceObj: return scn = context.scene srcObj = sourceObj #bpy.data.objects['Plane'] #change this newObj = srcObj.copy() newObj.data = srcObj.data.copy() newObj.animation_data_clear() scn.objects.link(newObj) return newObj def makeObjActive(context,obj): bpy.ops.object.select_all(action='DESELECT') obj.select=True context.scene.objects.active = obj bpy.ops.object.mode_set(mode='OBJECT') def removeVertexGroupsFromObj(obj): for vgroup in obj.vertex_groups: obj.vertex_groups.remove(vgroup) #inspired by: #https://blenderartists.org/t/batch-delete-vertex-groups-script/449881/7 #https://blender.stackexchange.com/questions/7412/how-to-rename-selected-objects-using-python #https://blenderartists.org/t/how-to-test-if-an-object-exists/566990 #https://blender.stackexchange.com/questions/8459/get-blender-x-y-z-and-bounding-box-with-script #https://blender.stackexchange.com/questions/109700/how-to-clean-weight-groups-by-script #https://blender.stackexchange.com/questions/26852/python-move-object-on-local-axis #https://blender.stackexchange.com/questions/45099/duplicating-a-mesh-object
#naPaintBlendshapeAddOn.py #modify use at your own risk bl_info = { "name":"tool to help modify shapekeys using painted weights", "category": "Object" } import bpy from bpy.types import( Operator, Panel ) #assumes file lives in addons folder import naPaintBlendshape import imp imp.reload(naPaintBlendshape) class paintBSWeightsOperator(Operator): bl_idname = "obj.paintbsweights" #needs to be all lowercase bl_label = "1.paintBSWeights" bl_options = {"REGISTER"} def execute(self, context): naPaintBlendshape.paintBSWeights(context) return {'FINISHED'} class doItOperator(Operator): bl_idname = "obj.doit" bl_label = "2.doIt" bl_options = {"REGISTER"} def execute(self, context): #self.report({'INFO'}, "context.selected_objects: %s" %context.selected_objects ) naPaintBlendshape.doIt(context) return {'FINISHED'} class cleanUpOperator(Operator): bl_idname = "obj.cleanup" bl_label = "3.cleanUp" bl_options = {"REGISTER"} def execute(self, context): naPaintBlendshape.cleanUp(context) return {'FINISHED'} class naPaintBlendshapePanel(Panel): bl_label = "naPaintBlendshape Panel" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" def draw(self, context): layout = self.layout #layout.label(text = "Modeling tool") layout.operator( "obj.paintbsweights") layout.operator( "obj.doit") layout.operator( "obj.cleanup") def register(): bpy.utils.register_class(paintBSWeightsOperator) bpy.utils.register_class(doItOperator) bpy.utils.register_class(cleanUpOperator) bpy.utils.register_class(naPaintBlendshapePanel) def unregister(): bpy.utils.unregister_class(paintBSWeightsOperator) bpy.utils.unregister_class(doItOperator) bpy.utils.unregister_class(cleanUpOperator) bpy.utils.unregister_class(naPaintBlendshapePanel) if __name__ == "__main__": register()