Tuesday, August 25, 2015

Dynamic parenting & space matching

USES:

This tool is useful when an object needs to have multiple parents and needs to switch which parent to follow within a scene. The space matching section prevents the object from popping to a different location when the parent is changed and makes sure that the object stays in place.
The top part of the window is used to parent the object to up to 3 drivers. It adds a "Follow" channel to the object, where the user can select the desired driver from a drop down list.
When assigning an object in the Space Matching section, it will load the list of parents from the "follow" channel. Switching parents with the buttons that load in this section will match the world space position of the object so that it remains in place, while switching parents by selecting from the list in the "Follow" channel of the object won't match the space and the object will pop to a different position.
The Space Matching section will work as well with dynamic parents that weren't created with the top part of this tool. For that case, there's a text input field to input the name of the channel that controls the parents.

INTERFACE:

Dynamic Parenting:

First select the object or objects to be parented and click on the "Object(s)" button. Then select what will be the world space (I usually assign the Master Control of the rig as World) and click on "World". Then select what will be the parent and click "Driver1". Driver2 is optional, you can assign a 2nd driver or leave it empty. Finally, click on "PARENT OBJECTS"
The script will parent the object under a locator (named objectName_loc) so that the translation and rotation channels of the object don't get locked by the parent constraint.

Space Matching:

If you created the dynamic parent using this tool, you can leave the default value ("follow") as the channel name. Else, type in the name of the channel that your object uses to select the parent (long name, not nice name*) and click on the "assign" button next to the text input field. Then, select the object with the dynamic parent and click on the top "assign" button. This will load one button for each parent. Clicking on the desired parent button will change the value on the object channel while keeping the object in the exact same place.

*The nice name is what Maya displays in the channel box, you will need to find out the long name of the attribute, which may or may not be the same. To find the long name, select the object and go to Modify/Edit attribute... Select the attribute from the list and look at the line that says New name, that will be the name you need to type in (case sensitive)

INSTALLATION:

To get the this tool download the script file and save it in your maya/version/scripts folder:

Then run the following code in the script editor (make sure to be in a python tab):
import spaceMatch
reload(spaceMatch)

You can save these lines in a shelf button for easy access.


If you don't want to download the file you can just copy the code below to the script editor and drag it onto your custom shelves so you can access it at any time (remember to save your shelves before quitting Maya)


import maya.cmds as mc
from functools import partial
#check if UI is already open
if mc.window('spaceMatchUI', query=True, exists=True):
mc.deleteUI('spaceMatchUI', window=True)
def warningMsg(msg, arg=None):
mc.confirmDialog(title='Warning', message=msg, button='ok')
#parenting functions
objects = None
world = None
driver1 = None
driver2 = None
def assignObjects(arg=None):
global objects
objects = mc.ls(sl=True)
if not objects:
warningMsg('Please select at least one object')
def assignWorld(arg=None):
global world
world = mc.ls(sl=True)
if not world:
warningMsg('Please select world')
def assignDriver1(arg=None):
global driver1
driver1 = mc.ls(sl=True)
if not driver1:
warningMsg('Please select driver')
def assignDriver2(arg=None):
global driver2
driver2 = mc.ls(sl=True)
if not driver2:
warningMsg('Please select driver')
def parentObjects(arg=None):
#Crates a locator parent for each object. Constraints locators to world/driver1/driver2 and sets set driven key
if objects and world and driver1:
for obj in objects:
#create locator
loc = mc.spaceLocator(name=obj+'_loc')
#parent constraint to object
constraint = mc.parentConstraint( obj, loc )
#break constraint
mc.delete(constraint)
#parent object to locator
mc.parent( obj, loc )
#freeze transforms
mc.makeIdentity(apply=True, t=1, r=1, s=1, n=0)
#turn locator display off
mc.select(loc)
loc_shape = mc.listRelatives(s=True)
mc.setAttr(loc_shape[0]+'.visibility',0)
#create constraints
loc_parentConstraint = mc.parentConstraint( world[0], loc, w=1.0, mo=True )
mc.parentConstraint(driver1[0], loc, w=0.0, mo=True )
if driver2:
mc.parentConstraint(driver2[0] , loc, w=0.0, mo=True)
#add attribute 'follow'
mc.select(obj)
enumNames = ''
if driver2:
enumNames = str(world[0])+':'+str(driver1[0])+':'+str(driver2[0])
else:
enumNames = str(world[0])+':'+str(driver1[0])
mc.addAttr( ln='follow', at='enum', enumName=enumNames, k=True )
#set set driven keys
#world
mc.setDrivenKeyframe( loc_parentConstraint[0] + '.' + str(world[0]) + 'W0', cd= obj + '.follow' )
mc.setDrivenKeyframe( loc_parentConstraint[0] + '.' + str(driver1[0]) + 'W1', cd= obj + '.follow' )
if driver2:
mc.setDrivenKeyframe( loc_parentConstraint[0] + '.' + str(driver2[0]) + 'W2', cd= obj + '.follow' )
#driver1
mc.setDrivenKeyframe( loc_parentConstraint[0] + '.' + str(world[0]) + 'W0', v=0.0, cd= obj + '.follow', dv=1 )
mc.setDrivenKeyframe( loc_parentConstraint[0] + '.' + str(driver1[0]) + 'W1',v=1.0, cd= obj + '.follow', dv=1 )
if driver2:
mc.setDrivenKeyframe( loc_parentConstraint[0] + '.' + str(driver2[0]) + 'W2',v=0.0, cd= obj + '.follow', dv=1 )
#driver2
if driver2:
mc.setDrivenKeyframe( loc_parentConstraint[0] + '.' + world[0] + 'W0', v=0.0, cd= obj + '.follow', dv=2 )
mc.setDrivenKeyframe( loc_parentConstraint[0] + '.' + driver1[0] + 'W1',v=0.0, cd= obj + '.follow', dv=2 )
mc.setDrivenKeyframe( loc_parentConstraint[0] + '.' + driver2[0] + 'W2', v=1.0, cd= obj + '.follow', dv=2 )
else:
warningMsg('Please select at least one object, one world and one driver')
#space matching functions
obj = None
layoutList = []
driverList = []
driversChannel = 'follow'
def clearButtons(arg=None):
global layoutList
for ly in layoutList:
mc.deleteUI(ly)
layoutList = []
def assignObj(arg=None):
clearButtons()
global layoutList
global obj
global driverList
sel = mc.ls(sl=True)
if sel:
obj = sel[0]
mc.text(objSelected, e=True, l=str(obj))
#find drivers
driverStr = mc.attributeQuery(str(driversChannel), node= obj, le=True)
driverList = driverStr[0].split(':')
#add buttons to layout
for driver in driverList:
mc.setParent( win )
ly = mc.columnLayout(adjustableColumn=True)
layoutList.append(ly)
mc.button(label=str(driver), command= partial(spaceMatch, driver))
mc.setParent( win )
else:
warningMsg('Please select object')
def assignChannel(arg=None):
global driversChannel
driversChannel = mc.textField(chanName, q=True, tx=True)
print(driversChannel)
def spaceMatch(drv, arg=None):
global obj
global driverList
#get ws tranlation and rotation
tValues = mc.xform( obj, ws=True, q=True, t=True)
rValues = mc.xform( obj, ws=True, q=True, ro=True)
tx,ty,tz = tValues[0],tValues[1],tValues[2]
rx,ry,rz = rValues[0],rValues[1],rValues[2]
#switch space
enumIndex = driverList.index(drv)
mc.setAttr(str(obj)+'.'+ str(driversChannel), enumIndex )
#set ws trans and rot
mc.xform( obj, a=True, ws=True, t=tValues)
mc.xform( obj, a=True, ws=True, ro=rValues)
######## GUI
win = mc.window('spaceMatchUI', title='Dynamic parenting & Space Matching')
mc.columnLayout( adjustableColumn=True )
#Dynamic parent frame
mc.frameLayout( label='DYNAMIC PARENTING', borderStyle='out' )
mc.rowColumnLayout(numberOfColumns=4, columnWidth=[(1, 100), (2, 100), (3, 100), (4, 100)])
mc. button(label='Object(s)', command= partial(assignObjects))
mc. button(label='World', command= partial(assignWorld))
mc. button(label='Driver1', command= partial(assignDriver1))
mc. button(label='Driver2', command= partial(assignDriver2))
mc.setParent( '..' )
mc.setParent( '..' )
mc. button(label='PARENT OBJECTS', command= partial(parentObjects))
#Space switch frame
mc.frameLayout( label='SPACE MATCHING', borderStyle='in' )
mc.rowColumnLayout(numberOfColumns=3, columnWidth=[(1, 100), (2, 200), (3, 100)])
mc.text(label='Selected object:', align='left')
objSelected = mc.text('select object', font='obliqueLabelFont', align='left')
mc. button(label='assign', command= partial(assignObj))
mc.text(label='Channel name:', align='left')
chanName = mc.textField(tx='follow')
mc. button(label='assign', command= partial(assignChannel))
mc.setParent( '..' )
mc.setParent( '..' )
#Drivers list
mc.frameLayout( label='Drivers list', borderStyle='out' )
#Show
mc.showWindow('spaceMatchUI')
view raw spaceMatching hosted with ❤ by GitHub

No comments:

Post a Comment