I wrote this to help with cluster weighting using hot keys.
please use/modify at your own risk.
Happy Sculpting!
Nate
"""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
reload(naNudgeWeight)
#this is needed to tell what is the cluster to be painted
naNudgeWeight.InitSimpleWindow()
#these two lines can be used as a hot key to add weight
nudge = naNudgeWeight.NudgeCluster()
nudge.nudge(0.1)
#this can be used as a hotkey to remove weight
nudge = naNudgeWeight.NudgeCluster()
nudge.nudge(-0.1)
#this can be used as a hotkey to smooth weight
nudge = naNudgeWeight.NudgeCluster()
nudge.smooth()
#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
NA_ACTIVE_CLUSTER_ATTR = 'naNudge'
#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):
return
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():
removeTagging(cl)
def addTagging(clusterName):
removeAllTagging()
#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):
continue
if NA_ACTIVE_CLUSTER_ATTR in cmds.listAttr(clusterName,ud=True):
result = clusterName
break
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
WINDOW_HEIGHT = 150
SCROLL_LIST_HEIGHT = 100
def __init__(self):
self.weightContainerScrollList = None
self.initUI()
#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:
return
cmds.select(cmds.listConnections(clusterName))
def buttonPressedOK(self, *args):
#print 'buttonPressedOK()>>>'
clusterName = self.getActiveCluster()
if not clusterName:
print 'no weight container chosen'
return
print 'initializing weighting tool for cluster %s' %clusterName
removeAllTagging()
addTagging(clusterName)
#clear selection
cmds.select(clear=True)
self.closeUI()
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):
cmds.deleteUI(self.WINDOW_NAME)
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'
return
#some error checking
self.closeUI()
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,\
ams=False,\
da=True,\
sc=self.selectionChange
)
for wgtContainer in weightContainers:
cmds.textScrollList( self.weightContainerScrollList, edit=True, append=wgtContainer)
#cmds.setParent('..')
cmds.button(label="OK",command= self.buttonPressedOK )
cmds.button(label="help",c=self.buttonPressedHelp)
#cmds.setParent('..')
cmds.showWindow(self.WINDOW_NAME)
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
self._setCluster(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:
return
if not cmds.objectType(cluster) in self.CLUSTER_SUPPORTED:
return
curSel = cmds.ls(selection=True)
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'
return
clusterSet = clusterSetAll[0]
clusterSetShort = cmds.sets( clusterSet, query=True)
clusterExpandedComponents = getExpandedComponents(clusterSetShort)
if not clusterExpandedComponents:
return
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)
result.append(info)
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 :('
return
if not cmds.objectType(cluster) in self.CLUSTER_SUPPORTED:
return
#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 :('
return
if not cmds.objectType(cluster) in self.CLUSTER_SUPPORTED:
return
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 = cmds.ls(selection=True)
if not cmds.objectType(cluster) in ['cluster']:
return
#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 )
cmds.select( comp, replace=True)
cmds.percent( cluster, v = value )
#restore selection
cmds.select(curSel, 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))
return
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":
return
curSel = cmds.ls(selection=True)
#not sure of another way to smooth cluster weights
mel.eval('artSetToolAndSelectAttr( "artAttrCtx", ("cluster.%s.weights"));'%clusterName)
mel.eval("artAttrInitPaintableAttr;")
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;")
cmds.select(clear=True)
cmds.select(curSel,replace=True)
def findWeightContainers():
return cmds.ls(type="cluster")
def getExpandedComponents( sel = [] ):
"""
get expanded components supports cvs, vertices
"""
result = []
curSel = cmds.ls(selection=True)
#need selection to expand
cmds.select(sel,replace=True)
components = cmds.filterExpand(sm = 31) #VERTS
if not components:
components = cmds.filterExpand(sm = 28) #CV
if components:
result = components
#restore current selection
cmds.select(curSel, replace=True)
return result
Inspired by Brian Tindall's Art of moving points book