Wednesday, November 8, 2017


I wrote this to help with cluster weighting using hot keys.
please use/modify at your own risk.

Happy Sculpting!

"""useful for adjusting weights on selected components via hot keys. currently works with clusters only.
with skinclusters it would be more complicated to subtract weights.
later i want to add support for clusters on cvs of curves or nurbssurfaces.

@author Nathaniel Anozie
Modify at your own risk
last updated: 11/08/2017 -- initial release
last updated: 11/07/2017 -- preparing for initial release
last updated: 11/05/2017 -- initial work

import sys
sys.path.append('/Users/Nathaniel/Documents/src/python/naDeformation')#put your script path here or add to python scripts maya looks for
import naNudgeWeight

#this is needed to tell what is the cluster to be painted

#these two lines can be used as a hot key to add weight
nudge = naNudgeWeight.NudgeCluster()

#this can be used as a hotkey to remove weight
nudge = naNudgeWeight.NudgeCluster()

#this can be used as a hotkey to smooth weight
nudge = naNudgeWeight.NudgeCluster()

#please note when you’re done with the tool just run
naNudgeWeight.removeAllTagging() #so there is no naNudge attribute left on any clusters in scene

import maya.cmds as cmds
import maya.mel as mel
from functools import partial


#for a tagging mechanism so our hot keys can work by finding active weight container
def removeTagging(clusterName):
    if not cmds.listAttr(clusterName,ud=True):
    if NA_ACTIVE_CLUSTER_ATTR in cmds.listAttr(clusterName,ud=True):
        cmds.deleteAttr('%s.%s' %(clusterName,NA_ACTIVE_CLUSTER_ATTR) )
def removeAllTagging():
    for cl in findWeightContainers():
def addTagging(clusterName):
    #only one weight container gets tag, should stay same with skin clusters
    cmds.addAttr(clusterName, ln= NA_ACTIVE_CLUSTER_ATTR, at="bool")
def getActiveWeightContainerInScene():
    """searches scene for a tagged object.  
    in this case we find first active weight container. should be just one
    result = None
    for clusterName in findWeightContainers():
        if not cmds.listAttr(clusterName,ud=True):
        if NA_ACTIVE_CLUSTER_ATTR in cmds.listAttr(clusterName,ud=True):
            result = clusterName
    return result
class InitSimpleWindow(object):
    this kind of maya based ui can be used for other examples
    inspired by capper's techart online forum posting on #acessing controls from external functions
    WINDOW_NAME = 'naNudgeWindow'
    WINDOW_WIDTH = 250
    SCROLL_LIST_HEIGHT = 100    
    def __init__(self):
        self.weightContainerScrollList = None
    #these are a bunch of defs specific to the ui widgets used in window
    def getActiveCluster(self):
        result = None
        clusterSelected = cmds.textScrollList(self.weightContainerScrollList, q=True,si=True)
        if clusterSelected:
            result = clusterSelected[0]
        return result
    def selectionChange(self, *args):
        #print 'selectionChange event'
        clusterName = self.getActiveCluster()
        if not clusterName:
    def buttonPressedOK(self, *args):
        #print 'buttonPressedOK()>>>'
        clusterName = self.getActiveCluster()
        if not clusterName:
            print 'no weight container chosen'
        print 'initializing weighting tool for cluster %s' %clusterName
        #clear selection
    def buttonPressedHelp(self, *args):
        msg = 'After Picking a Cluster from list and pressing ok.  Use something like this for hotkeys:\n\n'
        msg += 'alt + --> add weight:\n'
        msg += 'nudge = naNudgeWeight.NudgeCluster()\n'
        msg += 'nudge.nudge(0.1)\n\n'
        msg += 'alt - --> remove weight:\n'
        msg += 'nudge = naNudgeWeight.NudgeCluster()\n'
        msg += 'nudge.nudge(-0.1)\n\n'    
        msg += 'nudge = naNudgeWeight.NudgeCluster()\n'
        msg += 'alt s --> smooth weight:\n'
        msg += 'nudge.smooth()'  
        cmds.confirmDialog( title='help', message=msg )
    def closeUI(self):
        if cmds.window(self.WINDOW_NAME,exists=True):
    def initUI(self):
        """run this each time we want to work on a new or different cluster.
        it sets an attribute on active cluser.
        Inspired by:
        Mark Jackson's online ui examples
        weightContainers = findWeightContainers()
        if not weightContainers:
            print 'Nothing to do, tool requires a supported weight container in scene.\
            supporting: clusters only'

        #some error checking
        cmds.window(self.WINDOW_NAME,title="naNudgeWeight v 1.0.0")#,
        cmds.columnLayout(w=self.WINDOW_WIDTH, adj=True)
        self.weightContainerScrollList = cmds.textScrollList(height = self.SCROLL_LIST_HEIGHT,\
        for wgtContainer in weightContainers:
            cmds.textScrollList( self.weightContainerScrollList, edit=True, append=wgtContainer)
        cmds.button(label="OK",command= self.buttonPressedOK )
        cmds.window(self.WINDOW_NAME, edit=True, widthHeight=(self.WINDOW_WIDTH,self.WINDOW_HEIGHT))#w=self.WINDOW_WIDTH
class NudgeCluster(object):
    tools for nudging weight on cluster
    Inspired by Brian Tindall
    CLUSTER_SUPPORTED = ['cluster'] #later add skinCluster
    COMPONENT_SUPPORTED = ['mesh'] #later add nurbs curve cvs, nurbs surface cvs
    def __init__(self, cluster = None):
        needed because there could be multiple clusters acting on selected
        components.  this will make sure we are acting on correct cluster.
        should be done on initialization.
        self.cluster = cluster
        if not cluster:
            print 'if dont manually initialize a cluster it will go by possible tags'
            print 'looking for active cluster...'
            activeWeightContainer = getActiveWeightContainerInScene()
            if activeWeightContainer and cmds.objectType(activeWeightContainer) in self.CLUSTER_SUPPORTED:
                print 'found active cluster  %s' %activeWeightContainer

    def getActiveWeightContainer(self):
        """will be called on every hot key press. returns active weight container
        result = None
        for cl in findWeightContainers():
            if NA_ACTIVE_CLUSTER_ATTR in cmds.listAttr(cl,ud=True):
                return cl
        return result
    def _setCluster(self,clusterName):
        self.cluster = clusterName
    def _getCluster(self):
        return self.cluster
    def _getWeightInfo(self):
        """ get current weight information on selected vertices. later could support nurbs curves/surfaces
        weightInfo = [{'cmp':'pPlane1.vtx[0]','wgt':0.95},{'cmp':'pPlane1.vtx[4]','wgt':0.85}]
        result = []
        cluster = self._getCluster()
        if not cluster:
        if not cmds.objectType(cluster) in self.CLUSTER_SUPPORTED:
        curSel =
        selExpandedComponents = getExpandedComponents(curSel)
        ###only consider components a member of same cluster.
        clusterSetAll = [x for x in cmds.listConnections(cluster) if cmds.objectType(x) == 'objectSet']
        if not clusterSetAll:
            print 'cluster is probably not of cluster type'
        clusterSet = clusterSetAll[0]
        clusterSetShort = cmds.sets( clusterSet, query=True)
        clusterExpandedComponents = getExpandedComponents(clusterSetShort)
        if not clusterExpandedComponents:
        print 'clusterExpandedComponents',clusterExpandedComponents
        print 'selExpandedComponents', selExpandedComponents
        #only consider components a member of same cluster
        for comp in selExpandedComponents:
            if comp in clusterExpandedComponents:
                info = {}
                info['cmp'] =  comp
                info['wgt'] = getWeight(cluster,comp)
        return result
    def nudge(self, delta = 0.05):
        add or subtract delta from selected components
        cluster = self._getCluster()
        if not cluster:
            print 'could not find a cluster to nudge :('
        if not cmds.objectType(cluster) in self.CLUSTER_SUPPORTED:
        #in case selection included components not members of our cluster it will skip them
        weightInfo = self._getWeightInfo()
        #print 'weightInfo', weightInfo
        for arg in weightInfo:
            component = arg['cmp']
            currentWeight = arg['wgt']
            #make sure weight between 0 and 1
            newWeight = min( max( currentWeight + delta, 0 ), 1.0 ) 
            setWeight( cluster, [component], newWeight )
    def smooth(self):
        cluster = self._getCluster()
        if not cluster:
            print 'could not find a cluster to nudge :('
        if not cmds.objectType(cluster) in self.CLUSTER_SUPPORTED:
        smoothClusterWeight( cluster )

def setWeight(cluster, component, value):
    set weight on provided components, and cluster to value.
    currently only supports cluster. later add skinCluster type.
    curSel =
    if not cmds.objectType(cluster) in ['cluster']:
    #later add in other support for skinCluster
    if cmds.objectType(cluster) == 'cluster':
        for comp in component:
            print 'setting weight cluster:%s, component:%s, value:%d' %(cluster,comp,value )
   comp, replace=True)
            cmds.percent( cluster, v = value )

    #restore selection, replace=True)

def getWeight(cluster, component):
    get weight. later add support for skinCluster type.
    maybe add support for list of components
    result = None
    if not cmds.objectType(component) in ['mesh']:
        print 'cannot support component type %s' %(cmds.objectType(component))
    if cmds.objectType(cluster) == 'cluster':
        result = cmds.percent(cluster,value=True,query=True)[0]
    return result

def smoothClusterWeight( clusterName ):
    """smooths weights on selected components for given cluster name
    inspired by
    Paul Molodowitch (online examples on smoothing cluster weights)
    if not cmds.objectType(clusterName)=="cluster":
    curSel =
    #not sure of another way to smooth cluster weights
    mel.eval('artSetToolAndSelectAttr( "artAttrCtx", ("cluster.%s.weights"));'%clusterName)
    mel.eval( "artAttrPaintOperation artAttrCtx Smooth;" )
    mel.eval( "artAttrCtx -e -clear `currentCtx`;")
    #this bit restores our mode to object, without it we would be in paint weight mode
    mel.eval("selectMode -object;")
    mel.eval("setToolTo selectSuperContext;"),replace=True)

def findWeightContainers():

def getExpandedComponents( sel = [] ):
    get expanded components supports cvs, vertices
    result = []
    curSel =
    #need selection to expand,replace=True)
    components = cmds.filterExpand(sm = 31) #VERTS
    if not components:
        components = cmds.filterExpand(sm = 28) #CV
    if components:
        result = components
    #restore current selection, replace=True)
    return result

Inspired by Brian Tindall's Art of moving points book

Saturday, October 28, 2017

mesh snap, join, and split maya script


I was working on merging a separate eye lid to existing face mesh and thought these tools would be handy so I wrote them.
use at your own risk.

first i wanted a way for the outer border of the eyelids to automatically snap to existing edges of the face.  So here is the snap script at work:

i wanted a way to quickly join the split eyelid mesh with existing face:

it should also work with joining multiple meshes:

finally wanted a way to split areas off of separate eyelid mesh i didn't need by edgeloop:
here is the personal script i wrote:

"""useful for merging split eyelids/or lips onto existing mesh

@author Nathaniel Anozie
Modify at your own risk
last updated: 10/28/2017 -- initial release

facial modeling
body modeling

for maya
wip for blender

description naSnapToMesh:
snaps given vertices to closest vertex on mesh, will use selection if no vertices specified. should support a selected edgeloop.
import naSeam

import maya.cmds as cmds

def naSnapToMesh(mesh, vertices=[]):
    """snaps given vertices to closest vertex on mesh, will use selection if no vertices specified.
    should support a selected edgeloop.
    #mesh = 'polySurface2' #this would be the entire face mesh, place want to merge to
    #vertices = ['polySurface1.vtx[0]', 'polySurface1.vtx[2]'] #these would be eyelid boundary vertices
    if not vertices:
        selection = cmds.filterExpand(cmds.polyListComponentConversion(toVertex=True),sm=31)
        vertices = selection
    if not vertices:
        print 'could not find vertices to snap'
    cpmNode = cmds.createNode('closestPointOnMesh')
    cmds.connectAttr(mesh+'.worldMesh[0]', cpmNode+'.inMesh')
    indexExternal = 2
    for vtxExternal in vertices:,replace=True),replace=True)
        posExternal = cmds.xform( vtxExternal, translation=True, query=True, ws=True)
        cmds.setAttr( cpmNode+'.inPositionX', posExternal[0] )
        cmds.setAttr( cpmNode+'.inPositionY', posExternal[1] )
        cmds.setAttr( cpmNode+'.inPositionZ', posExternal[2] )
        vtxIndex = cmds.getAttr( cpmNode+'.result.closestVertexIndex' )
        vtx = mesh+'.vtx[%s]'%(vtxIndex)
        vtxPos = cmds.xform( vtx, translation=True, query=True, ws=True)
        #actual moving of external mesh to meet mesh
        cmds.xform( vtxExternal, translation=vtxPos, ws=True)

def naJoin(meshes=[]):
    merges given or selected meshes
    if not meshes:
        meshes = #should check all selected are meshes
    if not meshes:
        print 'no meshes to combine'
    def _join(meshA,meshB):
        #meshA = 'polySurface2'
        #meshB = 'polySurface1'
        meshCombine = cmds.polyUnite(meshA,meshB) #combine meshes
        vertices = cmds.polyListComponentConversion(meshCombine,toVertex=True)
        cmds.polyMergeVertex(vertices)#merge all vertices
        meshC = [x for x in meshCombine if cmds.objectType(x) != 'polyUnite']
        cmds.delete(meshCombine, ch=True)#clean mesh
        return meshC[0]
    meshA = meshes[0]
    for meshB in meshes[1:]:
        meshA = _join(meshA,meshB)

def naSplit():
    split by selected edge loop. note select loop one inside then where want split to accomodate face conversion from selection.
    result meshes get parented to world.  i think works on one mesh at a time.
    edges =
    faces = cmds.polyListComponentConversion(edges, toFace=True)
    meshParent = faces[0].split('.')[0]
    mesh = cmds.listRelatives(meshParent,children=True,type='mesh')[0]
    sepResult = cmds.polySeparate(mesh)
    for x in sepResult:
        if not cmds.objExists(x):
        if cmds.objectType(x) != 'polySeparate':
hope you find it helpful.
Happy Scripting!

Wednesday, September 6, 2017

Peaceful Day

Thanks for listening,
Happy Sketching!