text: """
  A GUI for applying a shoulder constraint.
  
  \b Requirements:
  AM_ShoulderConstraint.py
  
  To use this tool, first select your shoulder object, then add the constrained
  object (your first shoulder twist joint) to your selection and enter the
  desired data into the option fields before pressing either the Create or Apply
  button.
  
  \par Shoulder Constraint Options:
  - \b Spine \b Object:
  Specifies the name of the object to use for computing the shoulder's elevation
  angle. The shoulder constraint is designed with the expectation that this is
  the terminal spine node that is the most direct parent of the shoulder joints
  (i.e. the ribcage). Though this will produce perfectly valid values if any
  intermediate joints exist (collar bone, scapula), such an intermediate joint
  could be used instead, provided that the axes given for the spine node (below)
  are transformed into the intermediate joint\'s local space.'
  - \b Raised \b Angle \b Offset:
  Specifies the amount that the first twist joint's up-vector constraint rotates
  back when the shoulder is raised. A value between 0 and 90 is ideal and should
  eliminate flipping in a normal human range of motion. The default value of 45
  is recommended in most cases.'
  - \b Shoulder \b Aim \b Axis:
  Corresponds to the axis in the upper arm's local space that aims toward the
  elbow joint.
  - \b Shoulder \b Front \b Axis:
  Corresponds to the axis in the upper arm's local space that points toward the
  character's front.
  - \b Spine \b Aim \b Axis:
  Corresponds to the axis in the specified spine joint's local space that aims
  toward the next vertebra (up).
  - \b Spine \b Front \b Axis:
  Corresponds to the axis in the specified spine joint's local space that aims
  toward the character's front.
  
  \b Creation \b Info:
  
  \b Donations: http://adammechtley.com/donations/
  
  \b License: The MIT License
  
  Copyright (c) 2011 Adam Mechtley (http://adammechtley.com)
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the 'Software'), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:
  
  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.
  
  THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
  
  \namespace amTools.rigging.shoulderConstraint
  """
  
  import sys
  import maya.cmds as cmds
  import amTools.utilities as utils
  import amTools.utilities.ui as amui
  
  # verify requirements
  utils.plugins.verifyPlugin('AM_ShoulderConstraint', __file__)
  
  ## options window name
  kConstraintOptionsWindow = 'am_shoulderConstraintOptionsWindow'
  ## name of the tool
  kToolName = 'Shoulder Constraint'
  ## current version of the tool
  kVersionNumber = '1.04'
  ## date of current version
  kVersionDate = '2011.03.27'
  
  def menuItem(*args):
  	"""This function calls optionsWindow() from a menu item"""
  	optionsWindow()
  
  def optionsWindow():
  	"""This function creates an options window for the am_shoulderConstraint command. 
  	When executing it, first select the shoulder to which you are constraining, then 
  	add the object to be constrained to your selection."""
  	# create the main interface
  	if cmds.window(kConstraintOptionsWindow, q=True, ex=True):
  		cmds.deleteUI(kConstraintOptionsWindow)
  	mainWindow = cmds.window(kConstraintOptionsWindow, title='%s Options'%kToolName, menuBar=True, wh=(545,350))
  	
  	# build the menu bar
  	cmds.menu(label='Help')
  	amui.helpMenuItem(kToolName, __file__)
  	amui.aboutMenuItem(kToolName, kVersionNumber, kVersionDate)
  	
  	mainForm = cmds.formLayout(nd=100)
  	
  	# build the section to get information for the shoulder constraint
  	mainFrame = cmds.formLayout(nd=100)
  	
  	# attempt to guess what the spine is if there is a selection when the GUI is created
  	spineText = 'CenterSpine'
  	sel = cmds.ls(sl=True, l=True, type='transform')
  	if sel and len(sel) > 0: # BUG: in Maya 8.5, a selection of length 0 returns None rather than an empty list
  		try:
  			collar = cmds.listRelatives(sel[0], p=True, f=True) # the shoulder should be the first object in the selection list
  			spine = cmds.listRelatives(collar[0], p=True, f=True)
  			spineText = spine[0]
  		except: pass
  	
  	if_spine = cmds.textFieldGrp(label='Spine Object:', tx=spineText)
  	if_raisedAngleOffset = cmds.floatSliderGrp(v=45, min=0, max=90, fmn=-180, fmx=180, label='Raised Angle Offset:', field=True)
  	if_shoulderAimAxis = cmds.floatFieldGrp(v1=1, v2=0, v3=0, nf=3, pre=4, label='Shoulder Aim Axis:')
  	if_shoulderFrontAxis = cmds.floatFieldGrp(v1=0, v2=0, v3=1, nf=3, pre=4, label='Shoulder Front Axis:')
  	if_spineAimAxis = cmds.floatFieldGrp(v1=1, v2=0, v3=0, nf=3, pre=4, label='Spine Aim Axis:')
  	if_spineFrontAxis = cmds.floatFieldGrp(v1=0, v2=0, v3=1, nf=3, pre=4, label='Spine Front Axis:')
  	
  	# position the input fields for the shoulder constraint
  	cmds.formLayout(mainFrame, edit=True, attachForm=[(if_spine, 'left', 30), (if_spine, 'top', 5)], attachNone=[(if_spine, 'right'), (if_spine, 'bottom')])
  	cmds.formLayout(mainFrame, edit=True, attachForm=[(if_raisedAngleOffset, 'left', 30)], attachNone=[(if_raisedAngleOffset, 'right'), (if_raisedAngleOffset, 'bottom')], attachControl=[(if_raisedAngleOffset, 'top', 5, if_spine)])
  	cmds.formLayout(mainFrame, edit=True, attachForm=[(if_shoulderAimAxis, 'left', 30)], attachNone=[(if_shoulderAimAxis, 'right'), (if_shoulderAimAxis, 'bottom')], attachControl=[(if_shoulderAimAxis, 'top', 5, if_raisedAngleOffset)])
  	cmds.formLayout(mainFrame, edit=True, attachForm=[(if_shoulderFrontAxis, 'left', 30)], attachNone=[(if_shoulderFrontAxis, 'right'), (if_shoulderFrontAxis, 'bottom')], attachControl=[(if_shoulderFrontAxis, 'top', 5, if_shoulderAimAxis)])
  	cmds.formLayout(mainFrame, edit=True, attachForm=[(if_spineAimAxis, 'left', 30)], attachNone=[(if_spineAimAxis, 'right'), (if_spineAimAxis, 'bottom')], attachControl=[(if_spineAimAxis, 'top', 5, if_shoulderFrontAxis)])
  	cmds.formLayout(mainFrame, edit=True, attachForm=[(if_spineFrontAxis, 'left', 30)], attachNone=[(if_spineFrontAxis, 'right'), (if_spineFrontAxis, 'bottom')], attachControl=[(if_spineFrontAxis, 'top', 5, if_spineAimAxis)])
  	
  	cmds.setParent('..') # go up to mainFrame
  	
  	# create the buttons to execute the script
  	cmd_create='amTools.rigging.shoulderConstraint.doOptions ("%s", "%s", "%s", "%s", "%s", "%s")'%(
  		if_spine, 
  		if_raisedAngleOffset, 
  		if_shoulderAimAxis, 
  		if_shoulderFrontAxis, 
  		if_spineAimAxis, 
  		if_spineFrontAxis)
  	utils.ui.threeButtonLayout(mainForm, mainWindow, cmd_create)
  	
  	cmds.showWindow(mainWindow)
  
  def doOptions(input_spine, input_raisedAngleOffset, input_shoulderAimAxis, input_shoulderFrontAxis, input_spineAimAxis, input_spineFrontAxis):
  	"""Specifies the function called when the apply or create button is clicked"""
  	try:
  		# validate selection
  		selection = utils.dg.validateSelection(type='transform', msg='Please select your shoulder object and then the twist object to constrain.', exact=2)
  		
  		# validate spine
  		spine = cmds.textFieldGrp(input_spine, q=True, tx=True)
  		utils.dg.verifyNode(spine)
  		
  		# create the shoulder constraint
  		cmds.am_shoulderConstraint(
  			selection[1],
  			spineObject = spine,
  			shoulderObject = selection[0],
  			rao=cmds.floatSliderGrp(input_raisedAngleOffset, q=True, v=True), 
  			sha=cmds.floatFieldGrp(input_shoulderAimAxis, q=True, v=True), 
  			shf=cmds.floatFieldGrp(input_shoulderFrontAxis, q=True, v=True), 
  			spa=cmds.floatFieldGrp(input_spineAimAxis, q=True, v=True), 
  			spf=cmds.floatFieldGrp(input_spineFrontAxis, q=True, v=True))
  	except: raise
  
  def unitTest():
  	"""A simple unit test for the am_shoulderConstraint command"""
  	# make joints
  	jr = 0.1
  	spine = cmds.joint(n='spine', p=[0,0,0], rad=jr)
  	neck = cmds.joint(n='neck', p=[0,1,0], rad=jr)
  	cmds.joint(spine, e=True, oj='xzy', sao='zup')
  	cmds.joint(neck, e=True, o=[0,0,0])
  	cmds.select(spine)
  	collar = cmds.joint(n='collar', p=[0.2,1,0.1], rad=jr)
  	cmds.joint(collar, e=True, o=[0,0,-90])
  	shoulder = cmds.joint(n='shoulder', p=[0.4,1,0], rad=jr)
  	elbow = cmds.joint(n='elbow', p=[0.5,0.5,-0.1], rad=jr)
  	cmds.joint(shoulder, e=True, oj='xzy', sao='zup')
  	hand = cmds.joint(n='hand', p=[0.6,0,0], rad=jr)
  	cmds.joint(elbow, e=True, oj='xzy', sao='zup')
  	cmds.joint(hand, e=True, o=[0,0,0])
  	cmds.select(shoulder)
  	twist = cmds.joint(n='twist', rad=jr*2)
  		
  	# test create mode
  	print '---TESTING CREATE---'
  	def verifyObjects(outNum, scNode, expSpine, expShoulder, expTwist):
  		"""Print test results"""
  		print '%s (test %i):'%(scNode, outNum)
  		print'\tspine: %s'%(expSpine == cmds.am_shoulderConstraint(scNode, q=True, sp=True))
  		print'\tshldr: %s'%(expShoulder == cmds.am_shoulderConstraint(scNode, q=True, sh=True))
  		print'\ttwist: %s'%(expTwist == cmds.listConnections('%s.rotate'%scNode, scn=True)[0])
  	sc = cmds.am_shoulderConstraint([spine,shoulder,twist])
  	verifyObjects(0, sc, spine, shoulder, twist)
  	cmds.delete(sc)
  	sc = cmds.am_shoulderConstraint([shoulder,twist], spineObject=spine)
  	verifyObjects(1, sc, spine, shoulder, twist)
  	cmds.delete(sc)
  	sc = cmds.am_shoulderConstraint([spine,twist], shoulderObject=shoulder)
  	verifyObjects(2, sc, spine, shoulder, twist)
  	
  	# test edit mode
  	def compareVectors(listVec, tupleVec):
  		"""Return test result comparing vectors"""
  		return listVec[0]==tupleVec[0] and listVec[1]==tupleVec[1] and listVec[2]==tupleVec[2]
  	def verifyValues(outNum, scNode, expRao, expSha, expShf, expSpa, expSpf):
  		"""Print test results"""
  		print '%s (test %i):'%(scNode, outNum)
  		print'\trao: %s'%(expRao == cmds.am_shoulderConstraint(scNode, q=True, rao=True))
  		print'\tsha: %s'%(expSha == cmds.am_shoulderConstraint(scNode, q=True, sha=True))
  		print'\tshf: %s'%(expShf == cmds.am_shoulderConstraint(scNode, q=True, shf=True))
  		print'\tspa: %s'%(expSpa == cmds.am_shoulderConstraint(scNode, q=True, spa=True))
  		print'\tspf: %s'%(expSpf == cmds.am_shoulderConstraint(scNode, q=True, spf=True))
  	print '---TESTING EDIT---'
  	cmds.am_shoulderConstraint(sc, e=True, sp=collar)
  	verifyObjects(0, sc, collar, shoulder, twist)
  	cmds.am_shoulderConstraint(sc, e=True, sh=elbow)
  	verifyObjects(1, sc, collar, elbow, twist)
  	cmds.am_shoulderConstraint(sc, e=True, sh=shoulder, sp=spine)
  	verifyObjects(2, sc, spine, shoulder, twist)
  	rao = 90.0
  	sha = [2,0,0]
  	shf = [0,0,2]
  	spa = [1.5,0,0]
  	spf = [1.2,0,0]
  	cmds.am_shoulderConstraint(sc, e=True, rao=rao, sha=sha, shf=shf, spa=spa, spf=spf)
  	verifyValues(0, sc, rao, sha, shf, spa, spf)
  	
  	# test query mode
  	print '---TESTING QUERY---'
  	print 'shldr: %s'%(cmds.am_shoulderConstraint(sc, q=True, sh=True) == cmds.listConnections('%s.shoulder'%sc)[0])
  	print 'spine: %s'%(cmds.am_shoulderConstraint(sc, q=True, sp=True) == cmds.listConnections('%s.spine'%sc)[0])
  	print 'offst: %s'%(cmds.am_shoulderConstraint(sc, q=True, rao=True) == cmds.getAttr('%s.raisedOffset'%sc))
  	print 'shAim: %s'%compareVectors(cmds.am_shoulderConstraint(sc, q=True, sha=True), cmds.getAttr('%s.shoulderAim'%sc)[0])
  	print 'shFor: %s'%compareVectors(cmds.am_shoulderConstraint(sc, q=True, shf=True), cmds.getAttr('%s.shoulderFront'%sc)[0])
  	print 'spAim: %s'%compareVectors(cmds.am_shoulderConstraint(sc, q=True, spa=True), cmds.getAttr('%s.spineAim'%sc)[0])
  	print 'spFor: %s'%compareVectors(cmds.am_shoulderConstraint(sc, q=True, spf=True), cmds.getAttr('%s.spineFront'%sc)[0])
  	
  	cmds.select(spine)