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()