"""
A GUI for modeling ribbons along selected curves (as for hair).

\b Requirements:
AM_Ribbon.py

To use this tool, select one or more NURBS curves and enter the desired data
into the option fields before pressing either the Create or Apply button.

\par Make Ribbons Options:
- \b Width: Specifies the width of the new ribbons at their base.
- \b Divisions \b Type:
Specifies how to compute divisions for the new ribbons. Static computation uses
the Divisions slider, while dynamic computation uses the Divisions per Unit
slider.
- \b Divisions:
Specifies a static number of divisions to apply to the new ribbons.
- \b Divisions \b per \b Unit:
Specifies a rate of divisions per centimeter. Allows multiple ribbons of
varying lengths to have similarly-sized faces.
- \b Taper:
Specifies the scale of the ribbon at its end, linearly interpolated down its
length.
- \b Front \b Twist:
Specifies the rotation of the ribbon around the curve at its base.
- \b Length \b Twist:
Specifies the rotation of the ribbon around the curve at the end, linearly
interpolated down its length.
- \b Base \b Up \b Vector:
Specifies the world space up-vector for orienting the first face on the ribbon.
\par UV Generation Options:
- \b UV \b Scale:
Specifies the height of the ribbon's UV layout in homogeneous UV coordinate
space.
- \b UV \b Normalization:
Specifies if and how the UVs of the new ribbons should be normalized. Selecting
None uses the UV Scale slider, while the other two options normalize all new
ribbons to either the longest or shortest ribbon being created.
- \b Pin \b UVs:
Specifies if the UV layout of the new ribbons should be pinned at the top (base
of the ribbon) or the bottom (end of the ribbon) when scaling is applied.
	

\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.modeling.makeRibbons
"""

import sys
import maya.cmds as cmds
import amTools.utilities as utils
import amTools.utilities.ui as amui

# verify requirements
utils.plugins.verifyPlugin('AM_Ribbon', __file__)

## options window name
kMakeRibbonsOptionsWindow = 'am_makeRibbonsOptionsWindow'
## name of the tool
kToolName = 'Make Ribbons'
## current version of the tool
kVersionNumber = '1.02'
## 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 making ribbons along selected hair curves."""
	# create the main interface
	if cmds.window(kMakeRibbonsOptionsWindow, q=True, ex=True):
		cmds.deleteUI(kMakeRibbonsOptionsWindow)
	mainWindow = cmds.window(kMakeRibbonsOptionsWindow, title='%s Options'%kToolName, menuBar=True, wh=(545,465))
	
	# 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 about the new ribbons
	if_width = cmds.floatSliderGrp(v=3.0, min=0.0, max=100, fmn=0.0, fmx=100, label='Width:', field=True)
	if_divisionsType = cmds.radioButtonGrp(label='Compute Divisions:', labelArray2=['Statically', 'Dynamically'], numberOfRadioButtons=2, select=2)
	if_divisions = cmds.intSliderGrp(v=1, min=1, max=100, fmn=0, fmx=100, label='Divisions:', field=True, en=False)
	if_divisionsPerUnit = cmds.floatSliderGrp(v=0.5, min=0.0, max=10.0, fmn=0.0, fmx=100, label='Divisions per Unit:', field=True)
	cmds.radioButtonGrp(if_divisionsType, e=True, 
		cc=('amTools.modeling.makeRibbons.doRadioDivisions("%s", "%s", "%s")'%(if_divisionsType, if_divisions, if_divisionsPerUnit)))
	if_taper = cmds.floatSliderGrp(v=1.0, min=0.0, max=15.0, fmn=0.0, fmx=15.0, label='Taper:', field=True)
	if_frontTwist = cmds.floatSliderGrp(v=0.0, min=-180.0, max=180.0, fmn=-360.0, fmx=360.0, label='Front Twist:', field=True)
	if_lengthTwist = cmds.floatSliderGrp(v=0.0, min=-180.0, max=180.0, fmn=-180.0, fmx=180.0, label='Length Twist:', field=True)
	if_upVector = cmds.floatFieldGrp(v1=0, v2=1, v3=0, nf=3, pre=4, label='Base Up Vector:')
	
	# position the input fields for the new parents
	cmds.formLayout(mainForm, edit=True, attachForm=[(if_width, 'left', 30), (if_width, 'top', 5)], attachNone=[(if_width, 'right'), (if_width, 'bottom')])
	cmds.formLayout(mainForm, edit=True, attachForm=[(if_divisionsType, 'left', 30)], attachNone=[(if_divisionsType, 'right'), (if_divisionsType, 'bottom')], attachControl=[(if_divisionsType, 'top', 5, if_width)])
	cmds.formLayout(mainForm, edit=True, attachForm=[(if_divisions, 'left', 30)], attachNone=[(if_divisions, 'right'), (if_divisions, 'bottom')], attachControl=[(if_divisions, 'top', 5, if_divisionsType)])
	cmds.formLayout(mainForm, edit=True, attachForm=[(if_divisionsPerUnit, 'left', 30)], attachNone=[(if_divisionsPerUnit, 'right'), (if_divisionsPerUnit, 'bottom')], attachControl=[(if_divisionsPerUnit, 'top', 5, if_divisions)])
	cmds.formLayout(mainForm, edit=True, attachForm=[(if_taper, 'left', 30)], attachNone=[(if_taper, 'right'), (if_taper, 'bottom')], attachControl=[(if_taper, 'top', 5, if_divisionsPerUnit)])
	cmds.formLayout(mainForm, edit=True, attachForm=[(if_frontTwist, 'left', 30)], attachNone=[(if_frontTwist, 'right'), (if_frontTwist, 'bottom')], attachControl=[(if_frontTwist, 'top', 5, if_taper)])
	cmds.formLayout(mainForm, edit=True, attachForm=[(if_lengthTwist, 'left', 30)], attachNone=[(if_lengthTwist, 'right'), (if_lengthTwist, 'bottom')], attachControl=[(if_lengthTwist, 'top', 5, if_frontTwist)])
	cmds.formLayout(mainForm, edit=True, attachForm=[(if_upVector, 'left', 30)], attachNone=[(if_upVector, 'right'), (if_upVector, 'bottom')], attachControl=[(if_upVector, 'top', 5, if_lengthTwist)])
	
	# build the section to get information for the uv generation options
	uvFrame = eval('cmds.frameLayout(collapsable=True, label="UV Generation Options:" %s)'%amui.__frameAlignCenter__)
	uvForm = cmds.formLayout(nd=100)
	
	if_uvScale = cmds.floatSliderGrp(v=1.0, min=0.0, max=1.0, fmn=-1.0, fmx=1.0, label='UV Scale:', field=True, en=False)
	if_uvScaleType = cmds.radioButtonGrp(label='UV Normalization:', labelArray3=['None', 'Longest', 'Shortest'], numberOfRadioButtons=3, select=2)
	cmds.radioButtonGrp(if_uvScaleType, e=True, cc=('amTools.modeling.makeRibbons.doRadioUVs("%s", "%s")'%(if_uvScaleType, if_uvScale)))
	if_uvPinLocation = cmds.radioButtonGrp(label='Pin UVs:', labelArray2=['Top', 'Bottom'], numberOfRadioButtons=2, select=2)
	
	# position the input fields for the uv generation options
	cmds.formLayout(uvForm, edit=True, attachForm=[(if_uvScale, 'left', 30), (if_uvScale, 'top', 5)], attachNone=[(if_uvScale, 'right'), (if_uvScale, 'bottom')])
	cmds.formLayout(uvForm, edit=True, attachForm=[(if_uvScaleType, 'left', 30)], attachNone=[(if_uvScaleType, 'right'), (if_uvScaleType, 'bottom')], attachControl=[(if_uvScaleType, 'top', 5, if_uvScale)])
	cmds.formLayout(uvForm, edit=True, attachForm=[(if_uvPinLocation, 'left', 30)], attachNone=[(if_uvPinLocation, 'right'), (if_uvPinLocation, 'bottom')], attachControl=[(if_uvPinLocation, 'top', 5, if_uvScaleType)])
	
	cmds.setParent('..') # Go up to uvForm
	cmds.setParent('..') # Go up to mainForm
	
	# position the frame for the uv generation options
	cmds.formLayout(mainForm, edit=True, attachPosition=[(uvFrame, 'left', -1, 0), (uvFrame, 'right', -1, 100)], attachControl=[(uvFrame, 'top', 5, if_upVector)], attachNone=[(uvFrame, 'bottom')])
	
	# create the buttons to execute the script
	cmd_create = 'amTools.modeling.makeRibbons.doOptions ("%s", "%s", "%s", "%s", "%s", "%s", "%s", "%s", "%s", "%s", "%s")'%(if_width, if_divisionsType, if_divisions, if_divisionsPerUnit, if_taper, if_frontTwist, if_lengthTwist, if_upVector, if_uvScale, if_uvScaleType, if_uvPinLocation)
	amui.threeButtonLayout(mainForm, mainWindow, cmd_create)
	
	cmds.showWindow(mainWindow)

def doRadioDivisions(input_radio, input_divisions, input_divisionsPerUnit):
	inputState = (cmds.radioButtonGrp(input_radio, q=True, select=True) == 1)
	cmds.intSliderGrp(input_divisions, e=True, en=inputState)
	cmds.floatSliderGrp(input_divisionsPerUnit, e=True, en=(not inputState))

def doRadioUVs(input_radio, input_uvScaleSlider):
	inputState = (cmds.radioButtonGrp(input_radio, q=True, select=True) == 1)
	cmds.floatSliderGrp(input_uvScaleSlider, e=True, en=inputState)

def doOptions(input_width, input_divisionsType, input_divisions, input_divisionsPerUnit, input_taper, input_frontTwist, input_lengthTwist, input_upVector, input_uvScale, input_uvScaleType, input_uvPinLocation):
	"""This is the function called when the apply or create button is clicked"""
	try:
		utils.dg.validateSelection(type='transform', min=1)
		curves = utils.dg.getFilteredRelatives(cmds.ls(sl=True), 'nurbsCurve', False)
		if len(curves) is 0: raise Exception('None of the objects you have selected are NURBS curves.')
				
		# call the command
		if cmds.radioButtonGrp(input_divisionsType, q=True, select=True) == 1:
			cmds.am_ribbon(
				width = cmds.floatSliderGrp(input_width, q=True, v=True),
				divisions = cmds.floatSliderGrp(input_divisions, q=True, v=True),
				taper = cmds.floatSliderGrp(input_taper, q=True, v=True),
				twistBase = cmds.floatSliderGrp(input_frontTwist, q=True, v=True),
				twistLength = cmds.floatSliderGrp(input_lengthTwist, q=True, v=True),
				upVector = cmds.floatFieldGrp(input_upVector, q=True, v=True),
				uvScale = cmds.floatSliderGrp(input_uvScale, q=True, v=True),
				uvGroupScale = cmds.radioButtonGrp(input_uvScaleType, q=True, select=True) - 1,
				uvPin = cmds.radioButtonGrp(input_uvPinLocation, q=True, select=True) - 1)
		else:
			cmds.am_ribbon(
				width = cmds.floatSliderGrp(input_width, q=True, v=True),
				divisionsPerUnit = cmds.floatSliderGrp(input_divisionsPerUnit, q=True, v=True),
				taper = cmds.floatSliderGrp(input_taper, q=True, v=True),
				twistBase = cmds.floatSliderGrp(input_frontTwist, q=True, v=True),
				twistLength = cmds.floatSliderGrp(input_lengthTwist, q=True, v=True),
				upVector = cmds.floatFieldGrp(input_upVector, q=True, v=True),
				uvScale = cmds.floatSliderGrp(input_uvScale, q=True, v=True),
				uvGroupScale = cmds.radioButtonGrp(input_uvScaleType, q=True, select=True) - 1,
				uvPin = cmds.radioButtonGrp(input_uvPinLocation, q=True, select=True) - 1)
	except: raise

def unitTest():
	"""A simple unit test for the am_ribbon command"""
	curve = cmds.curve(d=3, 
		p=[[-3,0,-5], 
		[-2.75,0,-3.916667], 
		[-2.25,0,-1.75], 
		[0,0,0], 
		[2.25,0,1.75], 
		[2.75,0,3.916667], 
		[3,0,5]], 
		k=[0,0,0,1,2,3,4,4,4])
	
	# test create mode
	print '---TESTING CREATE---'
	ribbon = cmds.am_ribbon(curve)
	
	# test edit mode and query 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, rNode, expWid, expTap, expTwb, expTwl, expUpv, expUvs, expUvp):
		"""Print test results"""
		print '%s (test %i):'%(rNode, outNum)
		print'\twidth: %s'%(expWid == cmds.am_ribbon(rNode, q=True, w=True))
		print'\ttaper: %s'%(expTap == cmds.am_ribbon(rNode, q=True, t=True))
		print'\ttwBa:  %s'%(expTwb == cmds.am_ribbon(rNode, q=True, tb=True))
		print'\ttwLen: %s'%(expTwl == cmds.am_ribbon(rNode, q=True, tl=True))
		print'\tupVec: %s'%(compareVectors(expUpv, cmds.am_ribbon(rNode, q=True, up=True)))
		print'\tuvSca: %s'%(expUvs == cmds.am_ribbon(rNode, q=True, uv=True))
		print'\tuvPin: %s'%(expUvp == cmds.am_ribbon(rNode, q=True, uvp=True))
	print '---TESTING EDIT AND QUERY---'
	wid = 0.5
	taper = 0.5
	twistBase = -45.0
	twistLength = 135.0
	upVec = [0,2,0]
	uvScale = 2.0
	uvPin = 1
	cmds.am_ribbon(ribbon, e=True, w=wid, t=taper, tb=twistBase, tl=twistLength, up=upVec, uv=uvScale, uvp=uvPin)
	verifyValues(0, ribbon, wid, taper, twistBase, twistLength, upVec, uvScale, uvPin)
	div = 15
	cmds.am_ribbon(ribbon, e=True, d=div)
	print'\tdivs:  %s'%(div == cmds.am_ribbon(ribbon, q=True, d=True))
	dpu = 2.5
	cmds.am_ribbon(ribbon, e=True, dp=dpu)
	print'\tdpu:   %s'%(dpu - cmds.am_ribbon(ribbon, q=True, dp=True) < 0.01)