Monday, July 27, 2015

New Tool In The Works

I'm really excited about this one. I wrote it in PyMel and this was my first go at using Python in a true object-oriented fashion. Prior to this script I've been writing all my tools using maya.cmds so it feels good to finally jump away from a procedural approach and work in more of a "pythonic" format. For now I'm just posting an image of the interface for my new script but once I have a few days to check for bugs, I'll provide a link to download this thing.


Right now the most interesting thing I'll say about the tool itself, is it allows you to create an "EZ Selection" (which is a specified list of verts, edges, or faces saved to memory). What makes this little tool so powerful is it allows you essentially do away with selection sets (or the like) and you can literally deselect any number of components, do some other work, and still come back to your "EZ Selection" any point during your session.

I got the idea for this script while working on a rig (painting weights, vert by vert) and after creating dozens of selection sets I decided there had to be a more efficient way to work. Though it doesn't seem like it, what I discovered is, once you start adding a bunch of selection sets to your scene, Maya can start slowing down pretty significantly. Especially when you have to deal with thousands of verts and are working tediously to get your weights painted just right, a tool like this can come in handy rather quickly. It not only saves time—it keeps your scene light and saves your brain from getting fried.

Monday, July 13, 2015

Force Deformation Order

The other day I ran into a seemingly simple problem. I was trying to add a new blendshape to my rig but for some reason I wasn't able to get some verts to behave properly. To make a long story short, I wrote this:

(Shelf Button)
 

If you're like me and ever ran into this issue, you might have noticed that Maya's input window doesn't always solve the problem like you might expect. This tool is meant to provide an alternative. 
At the moment I'm still testing things out with just about every rig I can find to see if I run across any bugs. Currently the only deformers this tool supports are tweaks, blendshapes, and skinClusters (since that's all I needed at the time) but I plan on adding more features if it makes creating blendshapes and reordering deformers overall, easier.

If anyone's interested in looking at the code or using the tool yourself, below is the script written in Python. To throw this thing on your shelf you can use the following three lines. Let me know if this tool becomes of use to anyone and any comments or suggestions would be great too!

Download the script here:
http://bit.ly/1I315zV




# =====================================
# Force Deformation Order - BETA v.1.0
# =====================================

import maya.cmds as mc

# ==============
# Window
# ==============

# If window already exists, it will be replaced by new window
FDOName = "forceDOrder_UI"
if mc.window("forceDOrder_UI", exists=True):
    mc.deleteUI("forceDOrder_UI", window=True)

def FDO_UI():
    # QuiCam UI
    FDOWin = mc.window(FDOName, title="Force Deformation Order", menuBar=True)
    parentLayout = mc.columnLayout(adj=True)
    instructionMenu = mc.menu(label="Instructions")
    mc.menuItem(label="Open Instruction Window", parent=instructionMenu, command="forceDeformationOrder.instructionsWindow()")
    mc.setParent("..")
    mc.separator(parent=parentLayout, height=10, style="in")
    mc.button(parent=parentLayout, label="Load List", width=100, height=30, command="forceDeformationOrder.loadList()")
    mc.separator(parent=parentLayout, height=10, style="in")
    childLayout = mc.rowLayout(parent=parentLayout, numberOfColumns=2, adjustableColumn=True)
    mc.textScrollList("textBox")
    mc.columnLayout(parent=childLayout, adj=True)
    mc.button(label="Move Up", width=100, height=40, command="forceDeformationOrder.moveUp()")
    mc.button(label="Move Down", width=100, height=40, command="forceDeformationOrder.moveDown()")
    mc.columnLayout(parent=parentLayout, adj=True)
    mc.separator(height=10, style="in")
    mc.button(label="Remove All", width=100, height=30, command="forceDeformationOrder.removeAllList()")
    mc.separator(height=10, style="in")
    mc.separator(height=10, style="in")
    mc.button(label="Force Deformation Order", width=100, height=30, backgroundColor=[1, 0.2, 0.2], command="forceDeformationOrder.forceOrder()")
    mc.showWindow(FDOWin)

# Instructions Window
def instructionsWindow():
    # If window already exists, it will be replaced by new window
    instructionsName = "instructWin_UI"
    if mc.window("instructWin_UI", exists=True):
        mc.deleteUI("instructWin_UI", window=True)
        
    # Instructions
    instructionsWin = mc.window(instructionsName, title=" FDO - Instructions")
    mc.columnLayout(adj=True, width=500)
    mc.text(wordWrap=True, al="center", label="1.) Select one piece of geometry to rearrange your object's deformation order.")
    mc.text(wordWrap=True, al="center", label="2.) When you have made your selection, load your list and specify the order of your inputs.")
    mc.text(wordWrap=True, al="center", label="3.) Once you are satisfied with the order of your list, click 'Force Deformation Order'.")
    mc.text(wordWrap=True, al="center", font="boldLabelFont", label="Note: Make sure your geometry is selected before running any commands.")
    mc.separator(height=10, style="in")
    mc.separator(height=10, style="in")
    mc.text(wordWrap=True, al="center", font="boldLabelFont", backgroundColor=[0.8, 0.35, 0.35], label="Suggestions or Comments - http://b-animated.blogspot.com/")
    mc.showWindow(instructionsWin)

# ==========
# Commands
# ==========

# Load List
def loadList():
    # Store geo variable for selection...
    selectedGeo = mc.ls(sl=True)
    
    # Query geometry - obtain deformer inputs
    queryGeo = mc.deformer(query=True, geometry=True)
    for i in queryGeo:
        geoShape = i
    listDeforms = mc.listSets(type=2, object=geoShape)
    mc.select(listDeforms, replace=True, noExpand=True) # needs 'noExpand' for sets
    
    masterList = []
    
    # Check for "skinCluster" nodes... 
    for everySkin in listDeforms:
        listConnects = mc.listConnections(everySkin, type="skinCluster")
        masterList.append(listConnects)
    
    # Check for "blendShape" nodes...
    for everyBS in listDeforms:
        listConnects = mc.listConnections(everyBS, type="blendShape")
        masterList.append(listConnects)
    
    # Check for "tweak" nodes...
    for everyTweak in listDeforms:
        listConnects = mc.listConnections(everyTweak, type="tweak")
        masterList.append(listConnects)
    
    # Delete "None" objects..
    for everyNone in masterList:
        masterList.remove(None) # ... run twice to make sure
        masterList.remove(None)

    # Add to list
    mc.textScrollList("textBox", query=True, allItems=True)
    for everyInput in masterList: 
        mc.textScrollList("textBox", edit=True, append=everyInput)
    listCount = len(masterList)
    mc.select(selectedGeo, replace=True) # select geo before spitting warning
    mc.warning(listCount, " Deformer(s) Added To List.")

# Remove all from list
def removeAllList():
    mc.textScrollList("textBox", query=True, allItems=True) 
    mc.textScrollList("textBox", edit=True, removeAll=True)       
    mc.warning("All Items Removed From List.")

# List Control
def moveUp():
    # Set up index to move item up
    currentIndex = mc.textScrollList("textBox", query=True, selectIndexedItem=True) # current index
    selInt = currentIndex.pop() - 1 # pop 'list' out of brackets - now 'int'
    
    # Set up string to specify which item to move up
    selStr = mc.textScrollList("textBox", query=True, selectItem=True) # shape node for selected item
    moveItem = [selInt] + selStr # new list
    
    # Move item up
    mc.textScrollList("textBox", edit=True, appendPosition=moveItem) # ...requires one integer value and one string
        
    # Delete selected item to aviod duplicates
    removeDup = mc.textScrollList("textBox", query=True, selectIndexedItem=True) # produce index
    mc.textScrollList("textBox", edit=True, removeIndexedItem=removeDup) # remove duplicate from list
    mc.textScrollList("textBox", edit=True, selectIndexedItem=selInt) # select new list item
    
def moveDown():
    # Set up index to move item up
    currentIndex = mc.textScrollList("textBox", query=True, selectIndexedItem=True) # current index
    selInt = currentIndex.pop() + 2 # pop 'list' out of brackets - now 'int'
    
    # Set up string to specify which item to move up
    selStr = mc.textScrollList("textBox", query=True, selectItem=True) # shape node for selected item
    moveItem = [selInt] + selStr # new list
    
    # Move item up
    mc.textScrollList("textBox", edit=True, appendPosition=moveItem) # ...requires one integer value and one string
    
    # Delete selected item to aviod duplicates
    removeDup = mc.textScrollList("textBox", query=True, selectIndexedItem=True) # produce index
    mc.textScrollList("textBox", edit=True, removeIndexedItem=removeDup) # remove duplicate from list
    mc.textScrollList("textBox", edit=True, selectIndexedItem=selInt -1) # select new list item
    
# Force order based on list
def forceOrder():
    # Store geo variable for selection...
    selectedGeo = mc.ls(sl=True, long=True) # GEO (long name)
    
    # Compare new list to old defomation order
    listShapes = mc.textScrollList("textBox", query=True, allItems=True) # LIST
    
    # Convert shape nodes to transform nodes
    listAll = [] # empty list
    
    for everyItem in listShapes:
        wrapQuotes = everyItem.__str__() # wrap in quotes
        listAll.append(wrapQuotes) # toss everything into empty list
        
    # Slice itemsi in 'listAll' so we are only dealing with two items at a time (needed for reorderDeformers)
    splitTwo = -2 # set to -2 to set up sliceCount 
    
    for i in listAll:
        splitTwo += 1 # add 1 for every iteration
        sliceCount = listAll.__getslice__(splitTwo, splitTwo +2) # slice tuple     
        sliceTotal = len(sliceCount) # gather length
        
        if sliceTotal == 2: # if there are 2 values returned... move forward
            RDList = sliceCount.__getslice__(0,2) # isolate deformer's list - shape nodes   
            RDFinals = RDList[0], RDList[1], selectedGeo[0].__str__() # convert...
            
            # *** For every tuple, run reorderDeformers command ***
            RDMaster = map(str, RDFinals) # covert type tuple to list...
            mc.reorderDeformers(RDMaster[0], RDMaster[1], RDMaster[2]) # * REORDER DEFORMERS *
            # three values (Dform1, Dform2, geo affected) - Dform2 placed over 1
            
    # Once order has been changed, we'll reselect geo to refresh input list
    mc.select(clear=True)
    mc.select(selectedGeo, replace=True)
    mc.warning("Deformation Order has been rearranged.") # final warning
            
# Script by Bryan Godoy
# http://b-animated.blogspot.com/
# https://twitter.com/b_animates