Ejemplo n.º 1
0
def replaceGivenConstraintTarget(constraint, targetToReplace, newTarget):
    '''
	replaces targetToReplace transform on the given constraint with the newTarget transform
	'''
    targetToReplace = apiExtensions.asMObject(targetToReplace)
    newTarget = apiExtensions.asMObject(newTarget)

    #nothing to do if the nodes are the same...
    if apiExtensions.cmpNodes(targetToReplace, newTarget):
        return

    usedTargetIndices = getAttr('%s.target' % constraint, multiIndices=True)
    for idx in usedTargetIndices:
        for attr in attributeQuery('target',
                                   node=constraint,
                                   listChildren=True):
            for connection in listConnections('%s.target[%s].%s' %
                                              (constraint, idx, attr),
                                              p=True,
                                              type='transform',
                                              d=False) or []:
                toks = connection.split('.')
                node = toks[0]

                if apiExtensions.cmpNodes(node, targetToReplace):
                    toks[0] = str(newTarget)
                    connectAttr('.'.join(toks),
                                '%s.target[%s].%s' % (constraint, idx, attr),
                                f=True)
Ejemplo n.º 2
0
    def connect(self, obj, slot=None):
        '''
		performs the actual connection of an object to a connect slot
		'''
        if not cmd.objExists(obj):
            return -1

        #if the user is trying to connect the trigger to itself, return zero which is the reserved slot for the trigger
        if apiExtensions.cmpNodes(self.obj, obj):
            return 0

        if slot is None:
            slot = self.nextSlot()

        if slot <= 0:
            return 0

        #make sure the connect isn't already connected - if it is, return the slot number
        existingSlots = self.isConnected(obj)
        if existingSlots:
            return self.getConnectSlots(obj)[0]

        conPrefix = 'zooTrig'
        prefixSize = len(conPrefix)

        slotPath = "%s.%s%d" % (self.obj, conPrefix, slot)
        if not objExists(slotPath):
            cmd.addAttr(self.obj, ln="%s%d" % (conPrefix, slot), at='message')

        cmd.connectAttr("%s.msg" % obj, slotPath, f=True)
        self.cacheConnect(slot)

        return slot
Ejemplo n.º 3
0
	def connect( self, obj, slot=None ):
		'''
		performs the actual connection of an object to a connect slot
		'''
		if not cmd.objExists(obj):
			return -1

		#if the user is trying to connect the trigger to itself, return zero which is the reserved slot for the trigger
		if apiExtensions.cmpNodes( self.obj, obj ):
			return 0

		if slot is None:
			slot = self.nextSlot()

		if slot <= 0:
			return 0

		#make sure the connect isn't already connected - if it is, return the slot number
		existingSlots = self.isConnected(obj)
		if existingSlots:
			return self.getConnectSlots(obj)[0]

		conPrefix = 'zooTrig'
		prefixSize = len(conPrefix)

		slotPath = "%s.%s%d" % (self.obj, conPrefix, slot)
		if not objExists( slotPath ):
			cmd.addAttr(self.obj,ln= "%s%d" % (conPrefix, slot), at='message')

		cmd.connectAttr( "%s.msg" % obj, slotPath, f=True )
		self.cacheConnect( slot )

		return slot
Ejemplo n.º 4
0
    def isSingular(self):
        if self.controlB is None:
            return True

        #a pair is also singular if controlA is the same as controlB
        #NOTE: cmpNodes does a rigorous comparison so it will catch a fullpath and a partial path that point to the same node
        if apiExtensions.cmpNodes(self.controlA, self.controlB):
            return True

        return False
Ejemplo n.º 5
0
    def isSingular(self):
        if self.controlB is None:
            return True

            # a pair is also singular if contorlA is the same as controlB
            # NOTE: cmpNodes does a rigorous comparison so it will catch a fullpath and a partial path that point to the same node
        if apiExtensions.cmpNodes(self.controlA, self.controlB):
            return True

        return False
Ejemplo n.º 6
0
    def Create(cls, controlA, controlB=None, axis=None):
        '''
		given two controls will setup the relationship between them

		NOTE: if controlB isn't given then it will only be able to mirror its current
		pose.  This is usually desirable on "central" controls like spine, head and
		neck controls
		'''

        #make sure we've been given transforms - mirroring doesn't make a whole lotta sense on non-transforms
        if not objectType(controlA, isAType='transform'):
            return None

        if controlB:

            #if controlA is the same node as controlB then set controlB to None - this makes it more obvious the pair is singular
            #NOTE: cmpNodes compares the actual MObjects, not the node names - just in case we've been handed a full path and a partial path that are the same node...
            if apiExtensions.cmpNodes(controlA, controlB):
                controlB = None
            elif not objectType(controlB, isAType='transform'):
                return None

        #see if we have a pair node for the controls already
        pairNode = cls.GetPairNode(controlA)
        if pairNode:
            #if no controlB has been given see whether the pairNode we've already got also has no controlB - if so, we're done
            if not controlB:
                new = cls(pairNode)
                if not new.controlB:
                    return new

            #if controlB HAS been given, check whether to see whether it has the same pairNode - if so, we're done
            if controlB:
                pairNodeB = cls.GetPairNode(controlB)
                if pairNode == pairNodeB:
                    return cls(pairNode)

        #otherwise create a new one
        pairNode = createNode('controlPair')
        connectAttr('%s.message' % controlA, '%s.controlA' % pairNode)
        if controlB:
            connectAttr('%s.message' % controlB, '%s.controlB' % pairNode)

        #name the node
        nodeName = '%s_mirrorConfig' if controlB is None else '%s_%s_exchangeConfig' % (
            controlA, controlB)
        pairNode = rename(pairNode, nodeName)

        #instantiate it and run the initial setup code over it
        new = cls(pairNode)
        new.setup(axis)

        return new
Ejemplo n.º 7
0
    def Create(cls, controlA, controlB=None, axis=None):
        '''
		given two controls will setup the relationship between them

		NOTE: if controlB isn't given then it will only be able to mirror its current
		pose.  This is usually desirable on "central" controls like spine, head and
		neck controls
		'''

        #make sure we've been given transforms - mirroring doesn't make a whole lotta sense on non-transforms
        if not objectType(controlA, isAType='transform'):
            return None

        if controlB:

            #if controlA is the same node as controlB then set controlB to None - this makes it more obvious the pair is singular
            #NOTE: cmpNodes compares the actual MObjects, not the node names - just in case we've been handed a full path and a partial path that are the same node...
            if apiExtensions.cmpNodes(controlA, controlB):
                controlB = None
            elif not objectType(controlB, isAType='transform'):
                return None

        #see if we have a pair node for the controls already
        pairNode = cls.GetPairNode(controlA)
        if pairNode:
            #if no controlB has been given see whether the pairNode we've already got also has no controlB - if so, we're done
            if not controlB:
                new = cls(pairNode)
                if not new.controlB:
                    return new

            #if controlB HAS been given, check whether to see whether it has the same pairNode - if so, we're done
            if controlB:
                pairNodeB = cls.GetPairNode(controlB)
                if pairNode == pairNodeB:
                    return cls(pairNode)

        #otherwise create a new one
        pairNode = createNode('controlPair')
        connectAttr('%s.message' % controlA, '%s.controlA' % pairNode)
        if controlB:
            connectAttr('%s.message' % controlB, '%s.controlB' % pairNode)

        #name the node
        nodeName = '%s_mirrorConfig' if controlB is None else '%s_%s_exchangeConfig' % (
            controlA, controlB)
        pairNode = rename(pairNode, nodeName)

        #instantiate it and run the initial setup code over it
        new = cls(pairNode)
        new.setup(axis)

        return new
Ejemplo n.º 8
0
def replaceGivenConstraintTarget( constraint, targetToReplace, newTarget ):
	'''
	replaces targetToReplace transform on the given constraint with the newTarget transform
	'''
	targetToReplace = apiExtensions.asMObject( targetToReplace )
	newTarget = apiExtensions.asMObject( newTarget )

	#nothing to do if the nodes are the same...
	if apiExtensions.cmpNodes( targetToReplace, newTarget ):
		return

	usedTargetIndices = getAttr( '%s.target' % constraint, multiIndices=True )
	for idx in usedTargetIndices:
		for attr in attributeQuery( 'target', node=constraint, listChildren=True ):
			for connection in listConnections( '%s.target[%s].%s' % (constraint, idx, attr), p=True, type='transform', d=False ) or []:
				toks = connection.split( '.' )
				node = toks[ 0 ]

				if apiExtensions.cmpNodes( node, targetToReplace ):
					toks[ 0 ] = str( newTarget )
					connectAttr( '.'.join( toks ), '%s.target[%s].%s' % (constraint, idx, attr), f=True )
Ejemplo n.º 9
0
def replaceConstraintTarget( constraint, newTarget, targetIndex=0 ):
	'''
	replaces the target at "targetIndex" with the new target
	'''
	newTarget = apiExtensions.asMObject( str( newTarget ) )

	for attr in attributeQuery( 'target', node=constraint, listChildren=True ):
		for connection in listConnections( '%s.target[%s].%s' % (constraint, targetIndex, attr), p=True, type='transform', d=False ) or []:
			toks = connection.split( '.' )
			node = toks[ 0 ]

			if not apiExtensions.cmpNodes( node, newTarget ):
				toks[ 0 ] = str( newTarget )
				connectAttr( '.'.join( toks ), '%s.target[%s].%s' % (constraint, targetIndex, attr), f=True )
Ejemplo n.º 10
0
def replaceConstraintTarget( constraint, newTarget, targetIndex=0 ):
	'''
	replaces the target at "targetIndex" with the new target
	'''
	newTarget = apiExtensions.asMObject( str( newTarget ) )

	for attr in attributeQuery( 'target', node=constraint, listChildren=True ):
		for connection in listConnections( '%s.target[%s].%s' % (constraint, targetIndex, attr), p=True, type='transform', d=False ) or []:
			toks = connection.split( '.' )
			node = toks[ 0 ]

			if not apiExtensions.cmpNodes( node, newTarget ):
				toks[ 0 ] = str( newTarget )
				connectAttr( '.'.join( toks ), '%s.target[%s].%s' % (constraint, targetIndex, attr), f=True )
Ejemplo n.º 11
0
    def getConnectSlots(self, obj):
        '''
		return a list of the connect slot indicies the given obj is connected to
		'''
        if apiExtensions.cmpNodes(self.obj, obj):
            return [0]

        conPrefix = 'zooTrig'
        prefixSize = len(conPrefix)

        slots = set()

        connections = cmd.listConnections(obj + '.msg', s=False, p=True) or []
        for con in connections:
            obj, attr = con.split('.')
            if not apiExtensions.cmpNodes(obj, self.obj):
                continue

            slot = attr[prefixSize:]
            if attr.startswith(conPrefix) and slot.isdigit():
                slots.add(int(slot))

        #we need to check against all the cache attributes to see if the object exists but has been disconnected somehow
        allSlots = self.connects()
        getAttr = cmd.getAttr
        for connect, slot in allSlots:
            cacheAttrpath = '%s.%s%dcache' % (self.obj, conPrefix, slot)
            if objExists(cacheAttrpath):
                cacheValue = getAttr(cacheAttrpath)
                if cacheValue == obj:
                    slots.add(slot)

        slots = list(slots)
        slots.sort()

        return slots
Ejemplo n.º 12
0
	def getConnectSlots( self, obj ):
		'''
		return a list of the connect slot indicies the given obj is connected to
		'''
		if apiExtensions.cmpNodes( self.obj, obj ):
			return [0]

		conPrefix = 'zooTrig'
		prefixSize = len( conPrefix )

		slots = set()

		connections = cmd.listConnections( obj +'.msg', s=False, p=True ) or []
		for con in connections:
			obj, attr = con.split('.')
			if not apiExtensions.cmpNodes( obj, self.obj ):
				continue

			slot = attr[ prefixSize: ]
			if attr.startswith(conPrefix) and slot.isdigit():
				slots.add( int(slot) )

		#we need to check against all the cache attributes to see if the object exists but has been disconnected somehow
		allSlots = self.connects()
		getAttr = cmd.getAttr
		for connect, slot in allSlots:
			cacheAttrpath = '%s.%s%dcache' % (self.obj, conPrefix, slot)
			if objExists( cacheAttrpath ):
				cacheValue = getAttr( cacheAttrpath )
				if cacheValue == obj:
					slots.add( slot )

		slots = list( slots )
		slots.sort()

		return slots
Ejemplo n.º 13
0
def getChain( startNode, endNode ):
	'''
	returns a list of all the joints from the given start to the end inclusive
	'''
	chainNodes = [ endNode ]
	for p in apiExtensions.iterParents( endNode ):
		if not p:
			raise ValueError( "Chain terminated before reaching the end node!" )

		chainNodes.append( p )
		if apiExtensions.cmpNodes( p, startNode ):  #cmpNodes is more reliable than just string comparing - cmpNodes casts to MObjects and compares object handles
			break

	chainNodes.reverse()

	return chainNodes
Ejemplo n.º 14
0
def getChain( startNode, endNode ):
	'''
	returns a list of all the joints from the given start to the end inclusive
	'''
	chainNodes = [ endNode ]
	for p in api.iterParents( endNode ):
		if not p:
			raise ValueError( "Chain terminated before reaching the end node!" )

		chainNodes.append( p )
		if apiExtensions.cmpNodes( p, startNode ):  #cmpNodes is more reliable than just string comparing - cmpNodes casts to MObjects and compares object handles
			break

	chainNodes.reverse()

	return chainNodes
Ejemplo n.º 15
0
def chainLength( startNode, endNode ):
	'''
	measures the length of the chain were it to be straightened out
	'''
	length = 0
	curNode = endNode
	for p in api.iterParents( endNode ):
		curPos = Vector( xform( curNode, q=True, ws=True, rp=True ) )
		parPos = Vector( xform( p, q=True, ws=True, rp=True ) )
		dif = curPos - parPos
		length += dif.get_magnitude()

		if apiExtensions.cmpNodes( p, startNode ):  #cmpNodes is more reliable than just string comparing - cmpNodes casts to MObjects and compares object handles
			break

		curNode = p

	return length
Ejemplo n.º 16
0
def chainLength( startNode, endNode ):
	'''
	measures the length of the chain were it to be straightened out
	'''
	length = 0
	curNode = endNode
	for p in apiExtensions.iterParents( endNode ):
		curPos = Vector( xform( curNode, q=True, ws=True, rp=True ) )
		parPos = Vector( xform( p, q=True, ws=True, rp=True ) )
		dif = curPos - parPos
		length += dif.get_magnitude()

		if apiExtensions.cmpNodes( p, startNode ):  #cmpNodes is more reliable than just string comparing - cmpNodes casts to MObjects and compares object handles
			break

		curNode = p

	return length
Ejemplo n.º 17
0
def add(src,
        tgt,
        name=None,
        space=None,
        maintainOffset=True,
        nodeWithParentAttr=None,
        skipTranslationAxes=(),
        skipRotationAxes=(),
        constraintType=CONSTRAINT_PARENT):

    global AXES
    AXES = list(AXES)

    if space is None:
        space = listRelatives(src, p=True, pa=True)[0]

    if nodeWithParentAttr is None:
        nodeWithParentAttr = src

    if not name:
        name = getNiceName(tgt)
        if name is None:
            name = camelCaseToNice(str(tgt))

    #if there is an existing constraint, check to see if the target already exists in its target list - if it does, return the condition used it uses
    attrState(space, ('t', 'r'), lock=False)
    existingConstraint = findConstraint(src)
    if existingConstraint:
        constraintType = nodeType(existingConstraint)
        constraintFunc = getattr(cmd, constraintType)
        targetsOnConstraint = constraintFunc(existingConstraint,
                                             q=True,
                                             tl=True)
        if tgt in targetsOnConstraint:
            idx = targetsOnConstraint.index(tgt)
            aliases = constraintFunc(existingConstraint,
                                     q=True,
                                     weightAliasList=True)
            cons = listConnections('%s.%s' %
                                   (existingConstraint, aliases[idx]),
                                   type='condition',
                                   d=False)

            return cons[0]

    #when skip axes are specified maya doesn't handle things properly - so make sure
    #ALL transform channels are connected, and remove unwanted channels at the end...
    preT, preR = getAttr('%s.t' % space)[0], getAttr('%s.r' % space)[0]
    if existingConstraint:
        chans = CONSTRAINT_CHANNELS[constraintType]
        for channel, constraintAttr in zip(*chans):
            for axis in AXES:
                spaceAttr = '%s.%s%s' % (space, channel, axis)
                conAttr = '%s.%s%s' % (existingConstraint, constraintAttr,
                                       axis)
                if not isConnected(conAttr, spaceAttr):
                    connectAttr(conAttr, spaceAttr)

    #get the names for the parents from the parent enum attribute
    cmdOptionKw = {'mo': True} if maintainOffset else {}
    if objExists('%s.parent' % nodeWithParentAttr):
        srcs, names = getSpaceTargetsNames(src)
        addAttr('%s.parent' % nodeWithParentAttr,
                e=True,
                enumName=':'.join(names + [name]))

        #if we're building a pointConstraint instead of a parent constraint AND we already
        #have spaces on the object, we need to turn the -mo flag off regardless of what the
        #user set it to, as the pointConstraint maintain offset has different behaviour to
        #the parent constraint
        if constraintType in (CONSTRAINT_POINT, CONSTRAINT_ORIENT):
            cmdOptionKw = {}
    else:
        addAttr(nodeWithParentAttr, ln='parent', at="enum", en=name)
        setAttr('%s.parent' % nodeWithParentAttr, keyable=True)

    #now build the constraint
    constraintFunction = getattr(cmd, constraintType)
    constraint = constraintFunction(tgt, space, **cmdOptionKw)[0]

    weightAliasList = constraintFunction(constraint,
                                         q=True,
                                         weightAliasList=True)
    targetCount = len(weightAliasList)
    constraintAttr = weightAliasList[-1]
    condition = shadingNode('condition',
                            asUtility=True,
                            n='%s_to_space_%s#' %
                            (getShortName(src), getShortName(tgt)))

    setAttr('%s.secondTerm' % condition, targetCount - 1)
    setAttr('%s.colorIfTrue' % condition, 1, 1, 1)
    setAttr('%s.colorIfFalse' % condition, 0, 0, 0)
    connectAttr('%s.parent' % nodeWithParentAttr, '%s.firstTerm' % condition)
    connectAttr('%s.outColorR' % condition,
                '%s.%s' % (constraint, constraintAttr))

    #find out what symbol to use to find the parent attribute
    parentAttrIdx = 0
    if not apiExtensions.cmpNodes(space, src):
        parentAttrIdx = triggered.addConnect(src, nodeWithParentAttr)

    #add the zooObjMenu commands to the object for easy space switching
    Trigger.CreateMenu(src, "parent to %s" % name,
                       ChangeSpaceCmd.Create(targetCount - 1, parentAttrIdx))

    #when skip axes are specified maya doesn't handle things properly - so make sure
    #ALL transform channels are connected, and remove unwanted channels at the end...
    for axis, value in zip(AXES, preT):
        if axis in skipTranslationAxes:
            attr = '%s.t%s' % (space, axis)
            delete(attr, icn=True)
            setAttr(attr, value)

    for axis, value in zip(AXES, preR):
        if axis in skipRotationAxes:
            attr = '%s.r%s' % (space, axis)
            delete(attr, icn=True)
            setAttr(attr, value)

    #make the space node non-keyable and lock visibility
    attrState(space, ['t', 'r', 's'], lock=True)
    attrState(space, 'v', *control.HIDE)

    return condition
Ejemplo n.º 18
0
def add( src, tgt,
         name=None,
         space=None,
         maintainOffset=True,
         nodeWithParentAttr=None,
         skipTranslationAxes=(),
         skipRotationAxes=(),
         constraintType=CONSTRAINT_PARENT ):

	global AXES
	AXES = list( AXES )

	if space is None:
		space = listRelatives( src, p=True, pa=True )[ 0 ]

	if nodeWithParentAttr is None:
		nodeWithParentAttr = src

	if not name:
		name = getNiceName( tgt )
		if name is None:
			name = camelCaseToNice( str( tgt ) )


	#if there is an existing constraint, check to see if the target already exists in its target list - if it does, return the condition used it uses
	attrState( space, ('t', 'r'), lock=False )
	existingConstraint = findConstraint( src )
	if existingConstraint:
		constraintType = nodeType( existingConstraint )
		constraintFunc = getattr( cmd, constraintType )
		targetsOnConstraint = constraintFunc( existingConstraint, q=True, tl=True )
		if tgt in targetsOnConstraint:
			idx = targetsOnConstraint.index( tgt )
			aliases = constraintFunc( existingConstraint, q=True, weightAliasList=True )
			cons = listConnections( '%s.%s' % (existingConstraint, aliases[ idx ]), type='condition', d=False )

			return cons[ 0 ]


	#when skip axes are specified maya doesn't handle things properly - so make sure
	#ALL transform channels are connected, and remove unwanted channels at the end...
	preT, preR = getAttr( '%s.t' % space )[0], getAttr( '%s.r' % space )[0]
	if existingConstraint:
		chans = CONSTRAINT_CHANNELS[ constraintType ]
		for channel, constraintAttr in zip( *chans ):
			for axis in AXES:
				spaceAttr = '%s.%s%s' %( space, channel, axis)
				conAttr = '%s.%s%s' % (existingConstraint, constraintAttr, axis)
				if not isConnected( conAttr, spaceAttr ):
					connectAttr( conAttr, spaceAttr )


	#get the names for the parents from the parent enum attribute
	cmdOptionKw = { 'mo': True } if maintainOffset else {}
	if objExists( '%s.parent' % nodeWithParentAttr ):
		srcs, names = getSpaceTargetsNames( src )
		addAttr( '%s.parent' % nodeWithParentAttr, e=True, enumName=':'.join( names + [name] ) )

		#if we're building a pointConstraint instead of a parent constraint AND we already
		#have spaces on the object, we need to turn the -mo flag off regardless of what the
		#user set it to, as the pointConstraint maintain offset has different behaviour to
		#the parent constraint
		if constraintType in ( CONSTRAINT_POINT, CONSTRAINT_ORIENT ):
			cmdOptionKw = {}
	else:
		addAttr( nodeWithParentAttr, ln='parent', at="enum", en=name )
		setAttr( '%s.parent' % nodeWithParentAttr, keyable=True )


	#now build the constraint
	constraintFunction = getattr( cmd, constraintType )
	constraint = constraintFunction( tgt, space, **cmdOptionKw )[ 0 ]


	weightAliasList = constraintFunction( constraint, q=True, weightAliasList=True )
	targetCount = len( weightAliasList )
	constraintAttr = weightAliasList[ -1 ]
	condition = shadingNode( 'condition', asUtility=True )
	condition = rename( condition, '%s_to_space_%s#' % (cleanShortName( src ), cleanShortName( tgt )) )

	setAttr( '%s.secondTerm' % condition, targetCount-1 )
	setAttr( '%s.colorIfTrue' % condition, 1, 1, 1 )
	setAttr( '%s.colorIfFalse' % condition, 0, 0, 0 )
	connectAttr( '%s.parent' % nodeWithParentAttr, '%s.firstTerm' % condition )
	connectAttr( '%s.outColorR' % condition, '%s.%s' % (constraint, constraintAttr) )


	#find out what symbol to use to find the parent attribute
	parentAttrIdx = 0
	if not apiExtensions.cmpNodes( space, src ):
		parentAttrIdx = triggered.addConnect( src, nodeWithParentAttr )


	#add the zooObjMenu commands to the object for easy space switching
	Trigger.CreateMenu( src,
	                    "parent to %s" % name,
	                    ChangeSpaceCmd.Create( targetCount-1, parentAttrIdx ) )


	#when skip axes are specified maya doesn't handle things properly - so make sure
	#ALL transform channels are connected, and remove unwanted channels at the end...
	for axis, value in zip( AXES, preT ):
		if axis in skipTranslationAxes:
			attr = '%s.t%s' % (space, axis)
			delete( attr, icn=True )
			setAttr( attr, value )

	for axis, value in zip( AXES, preR ):
		if axis in skipRotationAxes:
			attr = '%s.r%s' % (space, axis)
			delete( attr, icn=True )
			setAttr( attr, value )


	#make the space node non-keyable and lock visibility
	attrState( space, [ 't', 'r', 's' ], lock=True )
	attrState( space, 'v', *control.HIDE )


	return condition
Ejemplo n.º 19
0
    def doBuild(self,
                bicep,
                elbow,
                wrist,
                nameScheme=ARM_NAMING_SCHEME,
                alignEnd=True,
                **kw):
        idx = kw['idx']
        scale = kw['scale']

        parity = Parity(idx)
        suffix = parity.asName()

        worldPart = WorldPart.Create()
        worldControl = worldPart.control
        partsControl = worldPart.parts

        colour = ColourDesc('green') if parity == Parity.LEFT else ColourDesc(
            'red')

        #grab a list of 'bicep joints' - these are the child joints of the bicep that aren't the elbow or any of its
        #children.  these joints are usually involved in deformation related to the bicep so we want to capture them
        #to use for geometry extraction for the control representation
        bicepJoints = [bicep]
        for child in listRelatives(bicep, pa=True, type='joint') or []:
            if cmpNodes(child, elbow): continue
            bicepJoints.append(child)
            bicepJoints += listRelatives(child, type='joint', pa=True,
                                         ad=True) or []

        #grab the 'elbow joints' as per the above description
        elbowJoints = [elbow]
        for child in listRelatives(elbow, pa=True, type='joint') or []:
            if cmpNodes(child, wrist): continue
            elbowJoints.append(child)
            elbowJoints += listRelatives(child, type='joint', pa=True,
                                         ad=True) or []

        #print 'THE BIPS', bicepJoints
        #print 'THE BOWS', elbowJoints

        ### BUILD THE FK CONTROLS
        ikArmSpace = buildAlignedNull(wrist,
                                      "ik_%sSpace%s" % (nameScheme[0], suffix),
                                      parent=worldControl)
        fkArmSpace = buildAlignedNull(bicep,
                                      "fk_%sSpace%s" % (nameScheme[0], suffix))

        BONE_AXIS = AIM_AXIS + 3 if parity else AIM_AXIS
        driverUpper = buildControl("fk_%sControl%s" % (nameScheme[1], suffix),
                                   bicep,
                                   PivotModeDesc.MID,
                                   Shape_Skin(bicepJoints, axis=BONE_AXIS),
                                   colour=colour,
                                   asJoint=True,
                                   oriented=False,
                                   scale=scale,
                                   parent=fkArmSpace)
        driverMid = buildControl("fk_%sControl%s" % (nameScheme[2], suffix),
                                 elbow,
                                 PivotModeDesc.MID,
                                 Shape_Skin(elbowJoints, axis=BONE_AXIS),
                                 colour=colour,
                                 asJoint=True,
                                 oriented=False,
                                 scale=scale,
                                 parent=driverUpper)
        driverLower = buildControl("fk_%sControl%s" % (nameScheme[3], suffix),
                                   PlaceDesc(wrist,
                                             wrist if alignEnd else None),
                                   shapeDesc=Shape_Skin(wrist, axis=BONE_AXIS),
                                   colour=colour,
                                   asJoint=True,
                                   oriented=False,
                                   constrain=False,
                                   scale=scale)

        #don't parent the driverLower in the buildControl command otherwise the control won't be in worldspace
        parent(driverLower, driverMid)
        makeIdentity(driverLower)

        ### BUILD THE POLE CONTROL
        polePos = rigUtils.findPolePosition(driverLower, driverMid,
                                            driverUpper, 5)
        poleControl = buildControl("%s_poleControl%s" %
                                   (nameScheme[0], suffix),
                                   PlaceDesc(elbow, PlaceDesc.WORLD),
                                   shapeDesc=ShapeDesc('sphere', None),
                                   colour=colour,
                                   constrain=False,
                                   parent=worldControl,
                                   scale=scale * 0.5)
        poleControlSpace = getNodeParent(poleControl)
        attrState(poleControlSpace, 'v', lock=False, show=True)

        move(polePos[0],
             polePos[1],
             polePos[2],
             poleControlSpace,
             a=True,
             ws=True,
             rpr=True)
        move(polePos[0],
             polePos[1],
             polePos[2],
             poleControl,
             a=True,
             ws=True,
             rpr=True)
        makeIdentity(poleControlSpace, a=True, t=True)
        setAttr('%s.v' % poleControl, False)

        ### BUILD THE POLE SELECTION TRIGGER
        lineNode = buildControl("%s_poleSelectionTrigger%s" %
                                (nameScheme[0], suffix),
                                shapeDesc=ShapeDesc('sphere', None),
                                colour=ColourDesc('darkblue'),
                                scale=scale,
                                constrain=False,
                                oriented=False,
                                parent=ikArmSpace)
        lineStart, lineEnd, lineShape = buildAnnotation(lineNode)

        parent(lineStart, poleControl)
        delete(pointConstraint(poleControl, lineStart))
        pointConstraint(elbow, lineNode)
        attrState(lineNode, ('t', 'r'), *LOCK_HIDE)

        setAttr('%s.template' % lineStart,
                1)  #make the actual line unselectable

        #build the IK handle
        ikHandle = cmd.ikHandle(fs=1,
                                sj=driverUpper,
                                ee=driverLower,
                                solver='ikRPsolver')[0]
        limbControl = buildControl('%sControl%s' % (nameScheme[0], suffix),
                                   PlaceDesc(wrist,
                                             wrist if alignEnd else None),
                                   shapeDesc=Shape_Skin(wrist, axis=BONE_AXIS),
                                   colour=colour,
                                   scale=scale,
                                   constrain=False,
                                   parent=ikArmSpace)

        xform(limbControl, p=True, rotateOrder='yzx')
        setAttr('%s.snapEnable' % ikHandle, False)
        setAttr('%s.v' % ikHandle, False)

        addAttr(limbControl,
                ln='ikBlend',
                shortName='ikb',
                dv=1,
                min=0,
                max=1,
                at='double')
        setAttr('%s.ikBlend' % limbControl, keyable=True)
        connectAttr('%s.ikBlend' % limbControl, '%s.ikBlend' % ikHandle)

        attrState(ikHandle, 'v', *LOCK_HIDE)
        parent(ikHandle, partsControl)
        parentConstraint(limbControl, ikHandle)
        #parent( ikHandle, limbControl )  #

        poleVectorConstraint(poleControl, ikHandle)

        #setup constraints to the wrist - it is handled differently because it needs to blend between the ik and fk chains (the other controls are handled by maya)
        wristOrient = buildAlignedNull(wrist,
                                       "%s_follow%s" % (nameScheme[3], suffix),
                                       parent=partsControl)

        pointConstraint(driverLower, wrist)
        orientConstraint(wristOrient, wrist, mo=True)
        setItemRigControl(wrist, wristOrient)
        wristSpaceOrient = parentConstraint(limbControl,
                                            wristOrient,
                                            weight=0,
                                            mo=True)[0]
        wristSpaceOrient = parentConstraint(driverLower,
                                            wristOrient,
                                            weight=0,
                                            mo=True)[0]
        setAttr('%s.interpType' % wristSpaceOrient, 2)

        #connect the ikBlend of the arm controller to the orient constraint of the fk wrist - ie turn it off when ik is off...
        weightRevNode = shadingNode('reverse', asUtility=True)
        wristOrientAttrs = listAttr(wristSpaceOrient, ud=True)
        connectAttr('%s.ikBlend' % limbControl,
                    '%s.inputX' % weightRevNode,
                    f=True)
        connectAttr('%s.ikBlend' % limbControl,
                    '%s.%s' % (wristSpaceOrient, wristOrientAttrs[0]),
                    f=True)
        connectAttr('%s.outputX' % weightRevNode,
                    '%s.%s' % (wristSpaceOrient, wristOrientAttrs[1]),
                    f=True)

        #build expressions for fk blending and control visibility
        fkVisCond = shadingNode('condition', asUtility=True)
        poleVisCond = shadingNode('condition', asUtility=True)
        connectAttr('%s.ikBlend' % limbControl,
                    '%s.firstTerm' % fkVisCond,
                    f=True)
        connectAttr('%s.ikBlend' % limbControl,
                    '%s.firstTerm' % poleVisCond,
                    f=True)
        connectAttr('%s.outColorR' % fkVisCond, '%s.v' % driverUpper, f=True)
        connectAttr('%s.outColorG' % poleVisCond,
                    '%s.v' % poleControlSpace,
                    f=True)
        connectAttr('%s.outColorG' % poleVisCond, '%s.v' % limbControl, f=True)
        setAttr('%s.secondTerm' % fkVisCond, 1)

        #add set pole to fk pos command to pole control
        fkControls = driverUpper, driverMid, driverLower
        poleTrigger = Trigger(poleControl)
        poleConnectNums = [poleTrigger.connect(c) for c in fkControls]

        idx_toFK = poleTrigger.setMenuInfo(
            None, "move to FK position",
            'zooVectors;\nfloat $pos[] = `zooFindPolePosition "-start %%%s -mid %%%s -end %%%s"`;\nmove -rpr $pos[0] $pos[1] $pos[2] #;'
            % tuple(poleConnectNums))
        poleTrigger.setMenuInfo(
            None, "move to FK pos for all keys",
            'source zooKeyCommandsWin;\nzooSetKeyCommandsWindowCmd "eval(zooPopulateCmdStr(\\\"#\\\",(zooGetObjMenuCmdStr(\\\"#\\\",%%%d)),{}))";'
            % idx_toFK)

        ##build the post trace commands for the pole vectors - once they've been placed after a trace, its safe and almost always
        ##desireable to place the pole vectors a little more sensibly
        #zooSetPostTraceCmd $poleControl ( "zooVectors; zooPlacePole \"-obj # -start %"+ $poleConnectNums[0] +" -mid %"+ $poleConnectNums[1] +" -end %"+ $poleConnectNums[2] +" -key 1 -removeKey 1 -invalidMode 1\";" );

        #add IK/FK switching commands
        limbTrigger = Trigger(limbControl)
        handleNum = limbTrigger.connect(ikHandle)
        poleNum = limbTrigger.connect(poleControl)
        fkIdx = limbTrigger.createMenu(
            "switch to FK",
            "zooAlign \"\";\nzooAlignFK \"-ikHandle %%%d -offCmd setAttr #.ikBlend 0;\";"
            % handleNum)
        limbTrigger.createMenu(
            "switch to FK for all keys",
            'source zooKeyCommandsWin;\nzooSetKeyCommandsWindowCmd "eval(zooPopulateCmdStr(\\\"#\\\",(zooGetObjMenuCmdStr(\\\"#\\\",%%%d)),{}))";'
            % fkIdx)
        ikIdx = limbTrigger.createMenu(
            "switch to IK",
            'zooAlign "";\nzooAlignIK "-ikHandle %%%d -pole %%%d -offCmd setAttr #.ikBlend 1;";'
            % (handleNum, poleNum))
        limbTrigger.createMenu(
            "switch to IK for all keys",
            'source zooKeyCommandsWin;\nzooSetKeyCommandsWindowCmd "eval(zooPopulateCmdStr(\\\"#\\\",(zooGetObjMenuCmdStr(\\\"#\\\",%%%d)),{}))";'
            % ikIdx)

        #add all zooObjMenu commands to the fk controls
        for fk in fkControls:
            fkTrigger = Trigger(fk)
            c1 = fkTrigger.connect(ikHandle)
            c2 = fkTrigger.connect(poleControl)

            #"zooFlags;\nzooAlign \"\";\nzooAlignIK \"-ikHandle %%%d -pole %%%d\";\nselect %%%d;" % (c1, c2, c1) )
            fkTrigger.createMenu(
                'switch to IK',
                'zooAlign "";\nstring $cs[] = `listConnections %%%d.ikBlend`;\nzooAlignIK ("-ikHandle %%%d -pole %%%d -control "+ $cs[0] +" -offCmd setAttr "+ $cs[0] +".ikBlend 1;" );'
                % (c1, c1, c2))

        createLineOfActionMenu([limbControl] + list(fkControls),
                               (elbow, wrist))

        #add trigger commands
        Trigger.CreateTrigger(lineNode, Trigger.PRESET_SELECT_CONNECTED,
                              [poleControl])
        setAttr('%s.displayHandle' % lineNode, True)

        #turn unwanted transforms off, so that they are locked, and no longer keyable
        attrState(fkControls, ('t', 'radi'), *LOCK_HIDE)
        attrState(poleControl, 'r', *LOCK_HIDE)

        return limbControl, driverUpper, driverMid, driverLower, poleControl, ikArmSpace, fkArmSpace, ikHandle, wristOrient, lineNode
    def doBuild(self, bicep, elbow, wrist, nameScheme=ARM_NAMING_SCHEME, alignEnd=True, **kw):
        idx = kw["idx"]
        scale = kw["scale"]

        parity = Parity(idx)
        suffix = parity.asName()

        worldPart = WorldPart.Create()
        worldControl = worldPart.control
        partsControl = worldPart.parts

        colour = ColourDesc("green") if parity == Parity.LEFT else ColourDesc("red")

        # grab a list of 'bicep joints' - these are the child joints of the bicep that aren't the elbow or any of its
        # children.  these joints are usually involved in deformation related to the bicep so we want to capture them
        # to use for geometry extraction for the control representation
        bicepJoints = [bicep]
        for child in listRelatives(bicep, pa=True, type="joint") or []:
            if cmpNodes(child, elbow):
                continue
            bicepJoints.append(child)
            bicepJoints += listRelatives(child, type="joint", pa=True, ad=True) or []

            # grab the 'elbow joints' as per the above description
        elbowJoints = [elbow]
        for child in listRelatives(elbow, pa=True, type="joint") or []:
            if cmpNodes(child, wrist):
                continue
            elbowJoints.append(child)
            elbowJoints += listRelatives(child, type="joint", pa=True, ad=True) or []

            # print 'THE BIPS', bicepJoints
            # print 'THE BOWS', elbowJoints

            ### BUILD THE FK CONTROLS
        ikArmSpace = buildAlignedNull(wrist, "ik_%sSpace%s" % (nameScheme[0], suffix), parent=worldControl)
        fkArmSpace = buildAlignedNull(bicep, "fk_%sSpace%s" % (nameScheme[0], suffix))

        BONE_AXIS = AIM_AXIS + 3 if parity else AIM_AXIS
        driverUpper = buildControl(
            "fk_%sControl%s" % (nameScheme[1], suffix),
            bicep,
            PivotModeDesc.MID,
            Shape_Skin(bicepJoints, axis=BONE_AXIS),
            colour=colour,
            asJoint=True,
            oriented=False,
            scale=scale,
            parent=fkArmSpace,
        )
        driverMid = buildControl(
            "fk_%sControl%s" % (nameScheme[2], suffix),
            elbow,
            PivotModeDesc.MID,
            Shape_Skin(elbowJoints, axis=BONE_AXIS),
            colour=colour,
            asJoint=True,
            oriented=False,
            scale=scale,
            parent=driverUpper,
        )
        driverLower = buildControl(
            "fk_%sControl%s" % (nameScheme[3], suffix),
            PlaceDesc(wrist, wrist if alignEnd else None),
            shapeDesc=Shape_Skin(wrist, axis=BONE_AXIS),
            colour=colour,
            asJoint=True,
            oriented=False,
            constrain=False,
            scale=scale,
        )

        # don't parent the driverLower in the buildControl command otherwise the control won't be in worldspace
        parent(driverLower, driverMid)
        makeIdentity(driverLower)

        ### BUILD THE POLE CONTROL
        polePos = rigUtils.findPolePosition(driverLower, driverMid, driverUpper, 5)
        poleControl = buildControl(
            "%s_poleControl%s" % (nameScheme[0], suffix),
            PlaceDesc(elbow, PlaceDesc.WORLD),
            shapeDesc=ShapeDesc("sphere", None),
            colour=colour,
            constrain=False,
            parent=worldControl,
            scale=scale * 0.5,
        )
        poleControlSpace = getNodeParent(poleControl)
        attrState(poleControlSpace, "v", lock=False, show=True)

        move(polePos[0], polePos[1], polePos[2], poleControlSpace, a=True, ws=True, rpr=True)
        move(polePos[0], polePos[1], polePos[2], poleControl, a=True, ws=True, rpr=True)
        makeIdentity(poleControlSpace, a=True, t=True)
        setAttr("%s.v" % poleControl, False)

        ### BUILD THE POLE SELECTION TRIGGER
        lineNode = buildControl(
            "%s_poleSelectionTrigger%s" % (nameScheme[0], suffix),
            shapeDesc=ShapeDesc("sphere", None),
            colour=ColourDesc("darkblue"),
            scale=scale,
            constrain=False,
            oriented=False,
            parent=ikArmSpace,
        )
        lineStart, lineEnd, lineShape = buildAnnotation(lineNode)

        parent(lineStart, poleControl)
        delete(pointConstraint(poleControl, lineStart))
        pointConstraint(elbow, lineNode)
        attrState(lineNode, ("t", "r"), *LOCK_HIDE)

        setAttr("%s.template" % lineStart, 1)  # make the actual line unselectable

        # build the IK handle
        ikHandle = cmd.ikHandle(fs=1, sj=driverUpper, ee=driverLower, solver="ikRPsolver")[0]
        limbControl = buildControl(
            "%sControl%s" % (nameScheme[0], suffix),
            PlaceDesc(wrist, wrist if alignEnd else None),
            shapeDesc=Shape_Skin(wrist, axis=BONE_AXIS),
            colour=colour,
            scale=scale,
            constrain=False,
            parent=ikArmSpace,
        )

        xform(limbControl, p=True, rotateOrder="yzx")
        setAttr("%s.snapEnable" % ikHandle, False)
        setAttr("%s.v" % ikHandle, False)

        addAttr(limbControl, ln="ikBlend", shortName="ikb", dv=1, min=0, max=1, at="double")
        setAttr("%s.ikBlend" % limbControl, keyable=True)
        connectAttr("%s.ikBlend" % limbControl, "%s.ikBlend" % ikHandle)

        attrState(ikHandle, "v", *LOCK_HIDE)
        parent(ikHandle, partsControl)
        parentConstraint(limbControl, ikHandle)
        # parent( ikHandle, limbControl )  #

        poleVectorConstraint(poleControl, ikHandle)

        # setup constraints to the wrist - it is handled differently because it needs to blend between the ik and fk chains (the other controls are handled by maya)
        wristOrient = buildAlignedNull(wrist, "%s_follow%s" % (nameScheme[3], suffix), parent=partsControl)

        pointConstraint(driverLower, wrist)
        orientConstraint(wristOrient, wrist, mo=True)
        setItemRigControl(wrist, wristOrient)
        wristSpaceOrient = parentConstraint(limbControl, wristOrient, weight=0, mo=True)[0]
        wristSpaceOrient = parentConstraint(driverLower, wristOrient, weight=0, mo=True)[0]
        setAttr("%s.interpType" % wristSpaceOrient, 2)

        # connect the ikBlend of the arm controller to the orient constraint of the fk wrist - ie turn it off when ik is off...
        weightRevNode = shadingNode("reverse", asUtility=True)
        wristOrientAttrs = listAttr(wristSpaceOrient, ud=True)
        connectAttr("%s.ikBlend" % limbControl, "%s.inputX" % weightRevNode, f=True)
        connectAttr("%s.ikBlend" % limbControl, "%s.%s" % (wristSpaceOrient, wristOrientAttrs[0]), f=True)
        connectAttr("%s.outputX" % weightRevNode, "%s.%s" % (wristSpaceOrient, wristOrientAttrs[1]), f=True)

        # build expressions for fk blending and control visibility
        fkVisCond = shadingNode("condition", asUtility=True)
        poleVisCond = shadingNode("condition", asUtility=True)
        connectAttr("%s.ikBlend" % limbControl, "%s.firstTerm" % fkVisCond, f=True)
        connectAttr("%s.ikBlend" % limbControl, "%s.firstTerm" % poleVisCond, f=True)
        connectAttr("%s.outColorR" % fkVisCond, "%s.v" % driverUpper, f=True)
        connectAttr("%s.outColorG" % poleVisCond, "%s.v" % poleControlSpace, f=True)
        connectAttr("%s.outColorG" % poleVisCond, "%s.v" % limbControl, f=True)
        setAttr("%s.secondTerm" % fkVisCond, 1)

        # add set pole to fk pos command to pole control
        fkControls = driverUpper, driverMid, driverLower
        poleTrigger = Trigger(poleControl)
        poleConnectNums = [poleTrigger.connect(c) for c in fkControls]

        idx_toFK = poleTrigger.setMenuInfo(
            None,
            "move to FK position",
            'zooVectors;\nfloat $pos[] = `zooFindPolePosition "-start %%%s -mid %%%s -end %%%s"`;\nmove -rpr $pos[0] $pos[1] $pos[2] #;'
            % tuple(poleConnectNums),
        )
        poleTrigger.setMenuInfo(
            None,
            "move to FK pos for all keys",
            'source zooKeyCommandsWin;\nzooSetKeyCommandsWindowCmd "eval(zooPopulateCmdStr(\\"#\\",(zooGetObjMenuCmdStr(\\"#\\",%%%d)),{}))";'
            % idx_toFK,
        )

        ##build the post trace commands for the pole vectors - once they've been placed after a trace, its safe and almost always
        ##desireable to place the pole vectors a little more sensibly
        # zooSetPostTraceCmd $poleControl ( "zooVectors; zooPlacePole \"-obj # -start %"+ $poleConnectNums[0] +" -mid %"+ $poleConnectNums[1] +" -end %"+ $poleConnectNums[2] +" -key 1 -removeKey 1 -invalidMode 1\";" );

        # add IK/FK switching commands
        limbTrigger = Trigger(limbControl)
        handleNum = limbTrigger.connect(ikHandle)
        poleNum = limbTrigger.connect(poleControl)
        fkIdx = limbTrigger.createMenu(
            "switch to FK", 'zooAlign "";\nzooAlignFK "-ikHandle %%%d -offCmd setAttr #.ikBlend 0;";' % handleNum
        )
        limbTrigger.createMenu(
            "switch to FK for all keys",
            'source zooKeyCommandsWin;\nzooSetKeyCommandsWindowCmd "eval(zooPopulateCmdStr(\\"#\\",(zooGetObjMenuCmdStr(\\"#\\",%%%d)),{}))";'
            % fkIdx,
        )
        ikIdx = limbTrigger.createMenu(
            "switch to IK",
            'zooAlign "";\nzooAlignIK "-ikHandle %%%d -pole %%%d -offCmd setAttr #.ikBlend 1;";' % (handleNum, poleNum),
        )
        limbTrigger.createMenu(
            "switch to IK for all keys",
            'source zooKeyCommandsWin;\nzooSetKeyCommandsWindowCmd "eval(zooPopulateCmdStr(\\"#\\",(zooGetObjMenuCmdStr(\\"#\\",%%%d)),{}))";'
            % ikIdx,
        )

        # add all zooObjMenu commands to the fk controls
        for fk in fkControls:
            fkTrigger = Trigger(fk)
            c1 = fkTrigger.connect(ikHandle)
            c2 = fkTrigger.connect(poleControl)

            # "zooFlags;\nzooAlign \"\";\nzooAlignIK \"-ikHandle %%%d -pole %%%d\";\nselect %%%d;" % (c1, c2, c1) )
            fkTrigger.createMenu(
                "switch to IK",
                'zooAlign "";\nstring $cs[] = `listConnections %%%d.ikBlend`;\nzooAlignIK ("-ikHandle %%%d -pole %%%d -control "+ $cs[0] +" -offCmd setAttr "+ $cs[0] +".ikBlend 1;" );'
                % (c1, c1, c2),
            )

        createLineOfActionMenu([limbControl] + list(fkControls), (elbow, wrist))

        # add trigger commands
        Trigger.CreateTrigger(lineNode, Trigger.PRESET_SELECT_CONNECTED, [poleControl])
        setAttr("%s.displayHandle" % lineNode, True)

        # turn unwanted transforms off, so that they are locked, and no longer keyable
        attrState(fkControls, ("t", "radi"), *LOCK_HIDE)
        attrState(poleControl, "r", *LOCK_HIDE)

        return (
            limbControl,
            driverUpper,
            driverMid,
            driverLower,
            poleControl,
            ikArmSpace,
            fkArmSpace,
            ikHandle,
            wristOrient,
            lineNode,
        )