#-
# ==========================================================================
# Copyright (C) 2011 Autodesk, Inc. and/or its licensors.  All 
# rights reserved.
#
# The coded instructions, statements, computer programs, and/or related 
# material (collectively the "Data") in these files contain unpublished 
# information proprietary to Autodesk, Inc. ("Autodesk") and/or its 
# licensors, which is protected by U.S. and Canadian federal copyright 
# law and by international treaties.
#
# The Data is provided for use exclusively by You. You have the right 
# to use, modify, and incorporate this Data into other products for 
# purposes authorized by the Autodesk software license agreement, 
# without fee.
#
# The copyright notices in the Software and this entire statement, 
# including the above license grant, this restriction and the 
# following disclaimer, must be included in all copies of the 
# Software, in whole or in part, and all derivative works of 
# the Software, unless such copies or derivative works are solely 
# in the form of machine-executable object code generated by a 
# source language processor.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. 
# AUTODESK DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED 
# WARRANTIES INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF 
# NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 
# PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE, OR 
# TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS LICENSORS 
# BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL, 
# DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK 
# AND/OR ITS LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY 
# OR PROBABILITY OF SUCH DAMAGES.
#
# ==========================================================================
#+

"""

This example show a custom solver at work.  Two nCloth objects are created,
one is disconnected from the default nucleus solver, and hooked to
this custom solver node, which will just compute a sine wave motion on
the cloth in time.

A custom solver needs to have a minimum of 3 attributes:

-startState     To be connected from the cloth object to the solver
-currentState   To be connected from the cloth object to the solver
-nextState      To be connected from the solver object to the cloth

and usually a 4th attribute that is the current time.

The idea is that when a solve is needed, the cloth object will pull on the
nextState attribute.  At this point the solver should pull on either the
currentState attribute or the startState, depending on the current time.
Once it has the state information, the solver can extract that information,
solve one step, and stuff that information back into the MnCloth to 
complete the solve.

Below is some example code to test this plugin:

#**************************************************************************
# Note: Before running this code, make sure the plugin spTestNucleusNode is loaded!
#**************************************************************************
import maya.cmds as cmds
import maya.mel as mel
def setupCustomSolverScene():
    cmds.file( f=True, new=True )

    pPlane1 = cmds.polyPlane( w=5, h=5, sx=10, sy=10, ax=(0,1,0), cuv=2, ch=1 )
    cmds.move( -10, 0, 0, r=True )
    mel.eval( 'createNCloth 0' )

    pPlane2 = cmds.polyPlane( w=5, h=5, sx=10, sy=10, ax=(0,1,0), cuv=2, ch=1 )
    mel.eval( 'createNCloth 0' )

    # Hookup plane2 (the cloth object created for plane2 is named nClothShape2) to our custom solver instead.

    # First, disconnect it from the default nucleus solver:
    cmds.disconnectAttr( 'nClothShape2.currentState', 'nucleus1.inputActive[1]' )
    cmds.disconnectAttr( 'nClothShape2.startState', 'nucleus1.inputActiveStart[1]' )
    cmds.disconnectAttr( 'nucleus1.outputObjects[1]', 'nClothShape2.nextState' )

    # create our custom solver:
    cmds.createNode( 'spTestNucleusNode' )

    # Hookup plane2 to our custom solver:
    cmds.connectAttr( 'spTestNucleusNode1.nextState[0]', 'nClothShape2.nextState' )
    cmds.connectAttr( 'nClothShape2.currentState', 'spTestNucleusNode1.currentState[0]' )
    cmds.connectAttr( 'nClothShape2.startState', 'spTestNucleusNode1.startState[0]' )
    cmds.connectAttr( 'time1.outTime', 'spTestNucleusNode1.currentTime' )

"""

import sys, math
import maya.OpenMaya as OpenMaya
import maya.OpenMayaMPx as OpenMayaMPx
import maya.OpenMayaFX as OpenMayaFX

class testNucleusNode(OpenMayaMPx.MPxNode):
	kPluginNodeName = "spTestNucleusNode"
	kPluginNodeId = OpenMaya.MTypeId(0x85006)
	startState = OpenMaya.MObject()
	currentState = OpenMaya.MObject()
	nextState = OpenMaya.MObject()
	currentTime = OpenMaya.MObject()

	def __init__(self):
		OpenMayaMPx.MPxNode.__init__(self)

	def compute(self, plug, data):
		if plug == testNucleusNode.nextState:
			if plug.isArray():
				# don't support evaluation of the whole array plug, only its elements
				return OpenMaya.kUnknownParameter
			logicalIndex = plug.logicalIndex()
			# get the value of the currentTime 
			currTime = data.inputValue(testNucleusNode.currentTime).asTime()
			# pull on start state or current state depending on the current time.
			if currTime.value() <= 0.0:
				multiDataHandle = data.inputArrayValue(testNucleusNode.startState)
				multiDataHandle.jumpToElement( logicalIndex )
				inputData = multiDataHandle.inputValue().data()
			else:
				multiDataHandle = data.inputArrayValue(testNucleusNode.currentState)
				multiDataHandle.jumpToElement( logicalIndex )
				inputData = multiDataHandle.inputValue().data()
					
			inputNData = OpenMayaFX.MFnNObjectData(inputData)
			nObj = inputNData.getClothObjectPtr()
			
			points = OpenMaya.MFloatPointArray()
			nObj.getPositions(points)
			for ii in range( points.length() ):
				points[ii].y = math.sin( points[ii].x + currTime.value()*4.0*(3.1415/180.0) )
			nObj.setPositions(points)

			data.setClean(plug)
		elif plug == testNucleusNode.currentState:
			data.setClean(plug)
		elif plug == testNucleusNode.startState:
			data.setClean(plug)
		else:
			return OpenMaya.kUnknownParameter

	@staticmethod
	def creator():
		return OpenMayaMPx.asMPxPtr( testNucleusNode() )

	@staticmethod
	def initializer():
		tAttr = OpenMaya.MFnTypedAttribute()

		testNucleusNode.startState = tAttr.create("startState", "sst", OpenMaya.MFnData.kNObject)
		tAttr.setWritable(True)
		tAttr.setStorable(True)
		tAttr.setHidden(True)
		tAttr.setArray(True)

		testNucleusNode.currentState = tAttr.create("currentState", "cst", OpenMaya.MFnData.kNObject)
		tAttr.setWritable(True)
		tAttr.setStorable(True)
		tAttr.setHidden(True)
		tAttr.setArray(True)

		testNucleusNode.nextState = tAttr.create("nextState", "nst", OpenMaya.MFnData.kNObject)
		tAttr.setWritable(True)
		tAttr.setStorable(True)
		tAttr.setHidden(True)
		tAttr.setArray(True)

		uniAttr = OpenMaya.MFnUnitAttribute()
		testNucleusNode.currentTime = uniAttr.create( "currentTime", "ctm" , OpenMaya.MFnUnitAttribute.kTime, 0.0 )

		testNucleusNode.addAttribute(testNucleusNode.startState)
		testNucleusNode.addAttribute(testNucleusNode.currentState)
		testNucleusNode.addAttribute(testNucleusNode.nextState)
		testNucleusNode.addAttribute(testNucleusNode.currentTime)

		testNucleusNode.attributeAffects(testNucleusNode.startState, testNucleusNode.nextState)
		testNucleusNode.attributeAffects(testNucleusNode.currentState, testNucleusNode.nextState)
		testNucleusNode.attributeAffects(testNucleusNode.currentTime, testNucleusNode.nextState)

# initialize the script plug-in
def initializePlugin(mobject):
	mplugin = OpenMayaMPx.MFnPlugin(mobject)
	try:
		mplugin.registerNode( testNucleusNode.kPluginNodeName, testNucleusNode.kPluginNodeId, testNucleusNode.creator, testNucleusNode.initializer )
	except:
		sys.stderr.write( "Failed to register node: %s" % testNucleusNode.kPluginNodeName )
		raise

# uninitialize the script plug-in
def uninitializePlugin(mobject):
	mplugin = OpenMayaMPx.MFnPlugin(mobject)
	try:
		mplugin.deregisterNode( testNucleusNode.kPluginNodeId )
	except:
		sys.stderr.write( "Failed to deregister node: %s" % testNucleusNode.kPluginNodeName )
		raise
	
