Sunday, March 3, 2013

Tool to split Geometry for Rigging

Hi,
Hope you find this tool I wrote for splitting geometry helpful.
Cheers,
Nate


##@file cutGeo.py 
#
#Cut geometry in Maya useful for previs rigging (Tested Maya 2008)
#
#@author Nathaniel Anozie
#
#
#@note Inspired by Jason Schleifer's Animator Friendly Rigging
#@note Inspired by Hamish Mckenzie (macaronikazoo dot com) learning about for zip in python
##
import maya.cmds as cmds

#na_cutGeoUI.mel
def cutGeo_print():
   
    arg = """Character needs these properties
    world +z -front char
    world +x -left of char when char facing front
    world +y -top char

    POSTION
    -make a nurbs plane and position it to fit limb of character (use a duplicate of original so can go back)
   
    MAKE GEO SPLITS
    -cutGeo( <> ) should make the geo splits, draw controls, have controls moving joint
   
    CLEAN
    -put controls onto own layer
    -select all geos and delete history
    -(might need to make small change on automatic cuts or remove parts)
    -(might need to adjust animator control diameter)
   
   
    GET GEO MOVING with control
    -select geo in order
        geo = cmds.ls(selection=True)  
    -select joint in order
        jnt = cmds.ls(selection=True)
    -parentEach( geo , jnt )
   
    import maya.cmds as cmds
    select -cl  ;
    select -r polySurface2 ;
    select -tgl polySurface6 ;
    select -tgl polySurface5 ;
    a = cmds.ls(selection=True)
    select -cl  ;
    select -r joint2 ;
    select -tgl joint3 ;
    select -tgl joint4 ;
    j = cmds.ls(selection=True)
    import cutGeo as p
    p.parentEach(a,j)
    """
    print arg;

   
   
##splits geo and draws guides from cv selection, it doesn't get geo to be parented to controls however.
#
##
def cutGeo(character = 'pCube1', drawControl = False):
    cutGeo_print()
   
    import maya.mel as mel

    sel = []
    sel = cmds.ls(selection=True)
   
    try:
        cmds.select(cmds.ls(selection=True),replace=True)
    except TypeError:
        print 'Requires either cvs,locators ... selected so it has positions to cut geometry >>%s'
        return 1
    try:
        cmds.select(character,replace=True)
    except TypeError:
        print 'Requires a piece of geometry as input>>'
        return 1
   
    #so control can be in okay direction, make 1 joint chain using all selected and have it ok orientation
    #
    t = []
    cmds.select(sel,replace=True)
    t = getJointPos()
    guideJoints = []    
    guideJoints = makeGuideJoint(t)
       
   
    ##so have animator controllers available --they aren't active yet
    #
    if len(guideJoints) >= 2:
        splitR = [[]] #will hold all cut of geo rotation
        splitT= [[]] #will hold all cut of geo translation
       
        print('Beginning loop for cut points <<<\n')
        for i in range(0,len(guideJoints)-1):
            jnt = guideJoints[i] #looking at one joint
            #saving data for splitting of geo
            splitRot = cmds.xform(jnt,query=True,ws=True,rotation=True)
            splitRotate = [0,90,splitRot[2]]
            splitTranslate = cmds.xform(jnt,query=True,ws=True,translation=True)        
            splitR.append(splitRotate)
            splitT.append(splitTranslate)  
           
            #draw controls if required
            if drawControl:
                #match local axis of control with joint
                anim = geoToControlSize( character )
               
                setToPos(obj = anim, joint=jnt)
                cmds.makeIdentity(anim,apply=True,t=False,s=False,n=False,r=True)
                setAxisToPos(obj = anim, joint=jnt)
               
                #controls cvs rotated in local z ex by 90
                cmds.select(anim,replace=True)
                mel.eval("selectCurveCV all;")
                cmds.rotate(0.0, 0.0, -90.0,eu=True,ocp=True) #rotate cvs only,
                #bug doesnt allow other axis for cvs to rotate about
                setShapeToPos(obj = anim, joint = jnt) #also removes transform
                print('Ending loop for cut points >>>\n')   
    
        #make all geo cuts at once so dont have trouble finding separated geos
        #
        splitR = splitR[1:] #need everything but first empty thing
        splitT = splitT[1:]
        splitGeo(character,translation = splitT,rotation = splitR )
        ##
       
        #possibly remove guide joints
        if (drawControl == False) and len(guideJoints) > 0:
            mel.eval('delete '+guideJoints[0])
            #map( lambda x: mel.eval('delete '+guideJoints[x]), range(0,len(guideJoints)))
           
           
        #BUG GEO NOT parented to joints
    else:
        print '<>'
       

       
##mirror the joints using a default axis
#
##
def makeMirrorGuideJoint(guideMirrorRootJoint=[]):
    guideJoints = []
    if len(guideMirrorRootJoint) == 1:
        cmds.mirrorJoint(guideMirrorRootJoint[0],mirrorYZ=True,mirrorBehavior=True)
        root = cmds.ls(selection=True)
        cmds.select(root,hierarchy=True,replace=True)
        print guideJoints
        guideJoints = cmds.ls(selection=True,type='joint')
    return guideJoints
   
    
       
       
       
       
       

##given list of 3 tuples make joints with a default orientation
#
##
def makeGuideJoint(t=[]):
    import maya.mel as mel   
    guideJoints = []
    cmds.select(replace=True, clear = True)
    map( lambda x: guideJoints.append( cmds.joint(p=t[x]) ), range(0,len(t)))
    cmds.select(guideJoints[0],replace=True)
    mel.eval('joint -e -oj xzy -secondaryAxisOrient yup -ch -zso;')
    return guideJoints
 
   
   
           
def cutGeo_unitTest(): 
    #make low poly character
    #get this from parameter ---
    char = cmds.polyCube(w=3)
    cmds.select(replace=True, clear = True)
   
    #add control
    #get this from parameter ---
    pointForGeo = cmds.joint(p=[0.0,0,0])
    cmds.joint(p=[2.0,0.5,0])
    cmds.joint(pointForGeo,e=True, zso=True, oj='xyz', sao='yup') #orient joint
   
    setToPos(obj = char, joint=pointForGeo)
    anim = geoToControlSize( char )
    setToPos(obj = anim, joint=pointForGeo)
 
    #get control perpendicular to geo by freeze control rotate and set its local z 90
    cmds.makeIdentity(anim,apply=True,t=False,s=False,n=False,r=True)
    cmds.setAttr( anim+'.rotateZ', 90.0 ) #dependent on axis...
   
    setAxisToPos(obj = anim, joint=pointForGeo)
   
    #bug splitGeo not working with an array of split points which is needed
    #otherwise hard to find the geos to split
    #
    #add split to mesh
    #How to adjust for other points we want split
    #do split point have to be at a joint point?
    splitRotate = cmds.xform(pointForGeo,query=True,ws=True,rotation=True)
    splitGeo(char,translation = [[0,0,0]],rotation = [ [0,90,splitRotate[2]] ] )
   

   
   
##shape should now move joint
#
#first select animator control (transform) then select joint
#
#@bug no error checking (ex: object is a nurbscurve transform), assume object lra matches joint
##
def setShapeToPos(obj = 'nurbsCircle1', joint = 'joint1'):
    cmds.select('nurbsCircle1',replace=True)
    shape = cmds.pickWalk(d='down')   
    cmds.parent(shape,joint,add=True,shape=True)
    #removing original transform
    cmds.delete(obj)

   
   
##geo should now be moved by joint
#
#first select geometry (transform) then select joint
#
#@bug no error checking (ex: object is a poly transform), object is not already parented to joint
##
def setGeoToPos(obj = 'pCube1', joint = 'joint1'):
    cmds.parent(obj,joint) #would get bug if its already a child of joint
  

##we want to make joints to fit character, this returns list of positions based on selected control vertices
#
#@note Inspired by Kyle Mistlin-Rude (kylemr dot blogspot dot com), learning python list comprehension
#
#@bug no error checking, ex: not verifying selected are of good type
#nice if surface is a plane with 1 u patch and degree linear
##
def getJointPos():
    patches = cmds.ls(sl=True)
    positions = [cmds.xform(obj, q=True, ws=True, translation = True )  for obj in patches  ]
    return positions
   
   
##
#@param geometry list
#@param joint list
#@note assumes they both have identical order and number, so we want first of one
#to be moved by first of other list ...
##
   
   






def charGeo_unitTest():
    character = cmds.polyCube(w=5)
    return character


   
##this gives where (translation, rotation? i think only 2 options) to split the character from a joint
#
##
"""
def fromJointToGeoSplitPoint_unitTest1():
    character = cmds.polyCube(w=5)
    point = [0,0,0]
    pointForGeo = cmds.joint(p=point)
    cmds.joint(p=[2,0,0])
    cmds.select(clear=True)
    normalGeo = cmds.polyPlane(w=1,h=1,ax=[1,0,0],cuv=2,ch=1,subdivisionsWidth=1,subdivisionsHeight=1)
    #make the normal be pointing in direction of joint, so ex leg will be split along height of leg not with
    cmds.parentConstraint(pointForGeo,normalGeo)
"""

##set object to another's position
#
#@param object we want to re-position
#@param thing that has rotation for our poly piece
##
def setToPos(obj = 'pCube1', joint='joint1'):
    cnt = cmds.parentConstraint(joint,obj)   
    cmds.delete(cnt)
   
def setAxisToPos(obj = 'pCube1', joint='joint1'): 
    ##fixing lra to match joint
    cmds.parent(obj,joint)
    cmds.makeIdentity(obj,apply=True,t=False,s=False,n=False,r=True)
    cmds.parent(obj,w=True) #would get bug if joint was world

   
##cut a polygon geo at given points. the rotation helps determine normals for cuts try a couple.
#
#@param character poly
#@param world translation for cut
#@param world euler rotation for cut
#
#
##
def splitGeo(character='pCube1',translation = [[0,0,0]],rotation = [[0,90,0]]):
    for t, r in zip(translation,rotation):
        splitGeoAtPoint(character,tSplit = t, rSplit = r )
       
    shape = getShape(character) #check size is 1, and type is mesh ?? or nurbsSurface   
    separatePolyCut(characterShape=shape)   

##remove history and put at world selected
#@bug requires something selected
##
def cleanSplitGeo():
    cmds.parent(w=True)
    cmds.delete(ch=True)
   


   
##cut a polygon geo at single point. the rotation helps determine normals for cuts try a couple.
#
#
#
#@note
#euler rotate [0,90,0] for arms? if world and character are
#+z -front char
#+x -right char
#+y -top char
#r[0,0,0] will make the cut normal with +z axis
#r[0,90,0] will rotate the plane in world y +90 making cut normal +x axis which would be what want for right and left arms
#a rotation in world +x would make cut normal in +y making cut okay for legs,torso,neck, and head :))
#i think this is for arms, for legs [90,0,0] ??
##
def splitGeoAtPoint(character='pCube1',tSplit = [0,0,0],rSplit = [0,90,0] ):
    #make cut ex: polyCut4
    cmds.polyCut(character,ch=1,pc=tSplit,ro=rSplit)

   
##get shape from poly
#
#@bugs no error checking (ex: found a polyShape or nurbsSurface ...
##
def getShape(character='pCube1'):
    cmds.select(character, replace=True)
    cmds.pickWalk(direction='down')
    shape = cmds.ls(selection=True) #check size is 1, and type is mesh ?? or nurbsSurface
    return shape
   

def separatePolyCut(characterShape='pCube1Shape1'):
    #finish with all cuts --change offsets to 0 and change extract faces to true
   
    pCutConnections = cmds.listConnections(characterShape,type = 'polyCut')
   
    pCut=[]
    if pCutConnections is not None:
        pCut = pCutConnections
    else:
        print "<>"
        return 1
       
    if len(pCut) > 0:
        pCut = list(set(pCut)) #remove duplicates python 2.5 >
       
        for obj in pCut:
            cmds.setAttr( obj+'.extractOffsetX', 0.0 )
            cmds.setAttr( obj+'.extractOffsetY', 0.0 )
            cmds.setAttr( obj+'.extractOffsetZ', 0.0 )
            cmds.setAttr( obj+'.extractFaces', 1 )

        #separate the shape ex:  polySeparate -ch 1 pCubeShape2;   
        cmds.polySeparate( characterShape, ch = 1)
       
   
   
def splitGeo_unitTest():
    character = cmds.polyCube(w=5)
    splitGeo(character,tSplit = [0,0,0],rSplit = [0,90,0] )
   
   
   
##want to be able to divide character in any default pose :))
#
##
def getJointWorldRotation(joint='joint1'):
    return cmds.xform(joint,query=True,ws=True,translation=True)
   
    #cmds.xform('joint3',query=True,ws=True,rotation=True)
    #Result: [0.0, 0.0, -45.000000000000014]
    #Result: [0.66666666666666674, 1.3333333333333333, 0.0]
   
   
   
##make circular animator control and fit it to geo
#
#@param poly
#
#@bug little error checking --index out of bounds, existence of poly, type is poly...
#
#
#inspired by Bryan Ewert (ewertb dot com) learning about arc length of curve
#
#@bug if geo non horizontal --i think it assumes geo is in a specific direction, i think changing whether to use x,y,z of
#bounding may help??
##

def geoToControlSize( character = 'pCube1' ):
    radius = 1 #default
   
    if cmds.objExists('pCube1'):
        #assume character no frozen rotate channels
        dupchar = cmds.duplicate(character)
       
        #bug i dont think this line is useful, i think only for straight limbs?
        cmds.setAttr( dupchar[0]+'.rotate', 0.0,0.0,0.0,type="double3")
       
        #get the objects bounding box to figure out animcontrol radius
        bbox=cmds.polyEvaluate(dupchar,b=True)
        #remove not needed geo
        cmds.delete(dupchar)
       
        #for arms interested in world z and world y to get an approx perimeter
        #reason for minus is we want the length not just the coordinate, reason two parenth cause polyEvaluate
        #returned a list of lists a tuple
        #with crossection perimeter figure out animator control radius
        #bounding box (xleft, xright),(yleft,yright),(zleft,zright) ..all in world coordinates
        bbperimeter = 2*( (bbox[2][1]-bbox[2][0]) + (bbox[1][1]-bbox[1][0]) )   
       
        pi = 3.14159;
        #radius = (cmds.arclen(animControl))/(2*pi)
        #make this percent zero if want animcontrol to hug geo, .3 if want anim to be wider than geo ...
        padpct = .1
        radius = (bbperimeter)/(2*pi)
        radius = radius*(1 + padpct)
       
    #this verifies its working okay
    #but the direction needs to change like for arms nr=[1,0,0], for legs this might have decimals etc.
    #so i think parent constraint stuff would help
    animAll = cmds.circle(ch=1, nr=[0,1,0],sw=360,r=radius,d=3) #use projected curve that is fit arround character instead
    anim = animAll[0]
   

    return anim
   
   

def geoToControlSize_unitTest():
    char = cmds.polyCube(w=4)
    geoToControlSize( char )
 
##parent each element of child list to each element of parent list, lists should be identical number
#
##
def parentEach(child = ['pCube1'],parent = ['joint1']):
    for c, p in zip(child,parent):
        cmds.parent(c,p)


##Divide character by some selected faces, its not working for a real character yet
#
##
def breakCharByFace():
   
    #pFace=['pCube1.f[1]','pCube1.f[4]','pCube1.f[7]','pCube1.f[10]']
    pFace = cmds.filterExpand(sm=34) #should be empty if wrong types selected
   
    if len(pFace) > 0:
        #make character and select faces to divide character by
        #cmds.polyCube(subdivisionsX=3)
       
        cmds.select( pFace,replace=True)
        pShape = 'pCubeShape1'
        #polyShape = pShape
        polyShape = findShapeFromComponent(pFace[0])
       
        #do the separating
        face = cmds.filterExpand(sm=34)
        chip = cmds.polyChipOff(face,keepFacesTogether=True,ch=1,dup=0,off=0)
        pieces = cmds.polySeparate(polyShape,ch=True)
    else:
        print "<>"

##given a poly component return its shape
#
##
def findShapeFromComponent(component='pCube1.f[4]'):
    result = []
    sel = cmds.ls(selection=True)
   
    polyArg = component.split('.')[0]
   
    #save it if kind find a poly shape of poly
    cmds.select(polyArg,replace=True)
    poly = cmds.filterExpand(sm=12)
    if len(poly) == 1:
        result = cmds.pickWalk(direction='down')
   
    cmds.select(sel,replace=True)   
    return result