Tuesday, August 13, 2013

Tool to Make realtime edits on both blendshapes from one blendshape

Happy Tuesday,
This is a tool I wrote to help make realtime edits on both blendshapes from one blendshape. There may be some bugs but hope it is helpful.

Helpful Maya Commands: CreateWrap.

Basically the tool scales one mesh in minus x then uses that to wrap to right blendshape hence driving right Blendshape from Left.

Here's a short video I made on its use.


#make realtime edits of right blendshape by editing left blendshape. 
#@note if left blendshape is other x axis change mirrorAx to 'Y' or 'Z' it should still work

#@author Nathaniel Anozie
#ogbonnawork at
#Modify at your own risk

#how to use this
import na_editBlend
import maya.cmds as cmds
from na_editBlend import Face

#finalMeshArg = 'pCube1'
defaultMeshArg = 'pCube2'
rightBlendArg = 'pCube3'
leftBlendArg = 'pCube4'

sel = [leftBlendArg,rightBlendArg,defaultMeshArg],replace=True)
smoothFace = Face()


#last updated: 08/10/2013 -- working on real rig scene testing
#last updated: 08/10/2013 -- added cleaning
#last updated: 08/09/2013 -- working on cleaning
#last updated: 08/09/2013 -- initial release
#last updated: 08/08/2013 -- working on initial release
#last updated: 08/07/2013 -- working on initial release
#last updated: 08/02/2013 -- working on initial release

import maya.cmds as cmds
import maya.mel as mel
import sys
from pprint import pprint

class Face(object):

    def __init__(self, mirrorAx = 'X'):
        sel =
        if len(sel) == 3:
            leftBlendArg = sel[0]
            rightBlendArg = sel[1]
            defaultMeshArg = sel[2]
            cmds.error('Select Left Mesh, Right Mesh, then Default Mesh!!!\n')
        self.mirrorAx = mirrorAx #could be Y or Z
        #self.finalMeshArg = finalMeshArg #cmds.duplicate(finalMeshArg)
        self.defaultMeshArg = defaultMeshArg
        self.rightBlendArg = rightBlendArg
        self.rightBlendNodeName = 'right_blend' #should error out if a blend node is name this already
        self.leftBlendArg = leftBlendArg

        #duplicate defaultMesh twice making intermediate1, and scaleMinusMesh
        intermediate1Ar = cmds.duplicate(defaultMeshArg)
        intermediate1 = intermediate1Ar[0]
        scaleMinusMeshAr = cmds.duplicate(defaultMeshArg)
        scaleMinusMesh = scaleMinusMeshAr[0]
        dupRightBlendAr = cmds.duplicate(rightBlendArg)
        dupRightBlend = dupRightBlendAr[0]
        self.intermediate1 = intermediate1
        self.scaleMinusMesh = scaleMinusMesh
        self.dupRightBlend = dupRightBlend
        #brainstorming how to clean up all the mess this makes and still have clean meshes that have edits
        print('it might make sense to copy meshes from scene. and have something to replace a blendshape with a different mesh. replacement should preserve blendshape use of animator controls ex: expressions, connections ... \n')
    def startSculpting(self):
        print('[startSculpting] Begin Presetup for sculpting on blendshape ...\n') 

        mirrorAxis = self.mirrorAx 
        #finalMesh = self.finalMeshArg 
        defaultMesh = self.defaultMeshArg
        rightBlend = self.rightBlendArg
        rightBlendNodeName = self.rightBlendNodeName
        leftBlend = self.leftBlendArg
        intermediate1 = self.intermediate1
        scaleMinusMesh = self.scaleMinusMesh
        dupRightBlend = self.dupRightBlend
        #pprint( [defaultMesh,rightBlend,leftBlend,intermediate1] )
        #move to rightBlend, intermediate1 and scaleMinusMesh (world space)
        cntAr1 = cmds.parentConstraint(rightBlend,intermediate1, mo = 0)
        cmds.delete( cntAr1[0] )
        cntAr2 = cmds.parentConstraint(rightBlend,scaleMinusMesh, mo = 0)
        cmds.delete( cntAr2[0] )
        cntAr3 = cmds.parentConstraint(rightBlend,dupRightBlend, mo = 0)
        cmds.delete( cntAr3[0] )
        #set scaleX -1 scaleMinusMesh
        cmds.setAttr(  (scaleMinusMesh + ".scale"+mirrorAxis), -1.0 )
        #make leftBlend -> intermediate1 (via intermediate1_blend)
        cmds.blendShape(leftBlend,intermediate1,n = 'intermediate1_blend')
        #make intermediate1 -> scaleMinusMesh (via scaleMinusMesh_blend)
        cmds.blendShape(intermediate1,scaleMinusMesh,n = 'scaleMinusMesh_blend')
        #Try moving scale Here ????
        #put intermediate1 blendWeight at 1 (via intermediate1_blend)
        cmds.setAttr( 'intermediate1_blend.w[0]', 1.0 )
        #put scaleMinusMesh blendWeight at 1 (via scaleMinusMesh_blend)
        cmds.setAttr( 'scaleMinusMesh_blend.w[0]', 1.0 )
        ##This should make right blendshape be edited in realtime
        ##can connect scale - mesh directly to right blend need a mesh in between
        #make scaleMinusMesh -> dupRightBlend via wrap, because vertex order different
        #make sure duplicate right blendshape mesh looks like the right blendshape
        print '[startSculpting] Making scaled mesh %s -- drive duplicate of right blendshape %s \n' %(scaleMinusMesh,dupRightBlend),replace = True),add = True)
        #cmds.deformer( scaleMinusMesh, type = 'wrap' ) #LOOK UP THE CORRECT WRAP DEFORMER COMMAND
        #make dupRightBlend -> rightBlend (via rightBlend_blend), put rightBlend blendWeight at 1 (via rightBlend_blend)
        cmds.blendShape(dupRightBlend,rightBlend,n = rightBlendNodeName)
        cmds.setAttr( 'right_blend.w[0]', 1.0 )
        #so can start editing left blend hide some things, should only see finalMesh and leftBlend, finalMesh should get updated on both sides in realtime
        hideAr = [rightBlend,defaultMesh,intermediate1,scaleMinusMesh,dupRightBlend]
        [ cmds.setAttr( (x+'.visibility'),0 ) for x in hideAr ]
    def stopSculpting(self):
        #delete wraps, blendshapes, extra meshes, okay to delete history on meshes
        print('[stopSculpting] Begin Finishing ...\n') 
        self.intermediate1 = intermediate1
        self.scaleMinusMesh = scaleMinusmesh
        self.dupRightBlend = dupRightBlend
        self.rightBlendNodeName = 'right_blend'
        #clean up right blendshape not messing with its outputs
        rightBlendShapeAr = cmds.pickWalk(self.rightBlendArg, direction = 'down')
        rightBlendShape = rightBlendShapeAr[0]
        cmds.disconnectAttr( self.rightBlendNodeName+'.'+'outputGeometry[0]', rightBlendShape+'.'+'inMesh'  )
        #remove extras