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( <
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