IK chain script WIP - need some help

Become a member of the CGSociety

Connect, Share, and Learn with our Large Growing CG Art Community. It's Free!

THREAD CLOSED
 
Thread Tools Search this Thread Display Modes
Old 06 June 2012   #1
IK chain script WIP - need some help

I'm trying to code up a script to streamline working with IK chains. I have a number of goals for down the road, but in the immediate future, I am trying to get the following two issues sorted:

1) Switching back and forth between IK and FK across a timeline will often result in popping. This can be fixed by selecting the chain solver, choosing the motion panel, and
clicking IK/FK snap. I think this should happen automatically, as I can't imagine many situations where someone would WANT a sudden, jarring pop from one position to another.

2) The IK chain object and the bones in the IK chain all have their own individual keyframes. So in order to rescale the timing of an IK chain animation, it is necessary to select all objects in the chain at a time and move their keys together. (In regards to adding and deleting keys on part of an IK chain, it seems to be somewhat inconsistent in whether or not it creates/destroys keys on the other parts). I think in most cases it would be much easier to treat an IK chain as a single object when it comes to keyframes.

I believe I have the first of these sorted out, but am having a bit of difficulty with the second. The solution I am currently using is a bit cumbersome, and causes issues when it comes to undo and redo.

The script may look long, but I promise it is really pretty simple. I've broken it up into a number of functions to make it easier to work with, and have tried to label all of the parts and what they do as clearly and concisely as possible.

It's a bit of a project, but I think that once it is working, this will be an valuable tool for any animator who uses Max.

-- Clear node event callback
   CB_TryUpdateSceneIKChains = undefined
   gc()
   
   -- Initialize scene IK chain arrays
   ------------
   sceneIKChains = #() -- array of all IK chains within the scene
   sceneIKChainFrames = #() -- array of all IK chain frames
   sceneIKChainKeyTransforms = #() -- array of all IK chain keyframe transforms
   ------------
   
   
   ------------
   -- Get all of an object's descendents, down to an optional stopping point
   --   Works in conjunction with getSceneIKChains function
   ------------
   fn IKChain startObj endObj: =
   (
   	if boneArray == undefined do boneArray = #()
   	append boneArray startObj
   	if startObj.children.count == 1 do if startObj != endObj do IKChain startObj.children[1] endObj:endObj
   
   	boneArray
   ) -- end getChain function
   ------------
   
   
   ------------
   -- Get all elements of scene IK chains
   --   Works in conjuction with getChain function
   ------------
   fn getSceneIKChains =
   (
   	for o in objects where classOf o == IK_Chain_Object do
   	(
   		startBone = o.controller.startJoint
   		endBone = o.controller.endJoint
   
   		global boneArray = #()
   		theChain = IKChain startBone endObj:endBone
   		boneArray = undefined
   
   		chainArray = #()
   		append chainArray o
   		for i in theChain do append chainArray i
   
   		append sceneIKChains chainArray
   	)
   
   	sceneIKChains
   ) -- end getSceneIKChains function
   ------------
   
   
   ------------
   -- Collect the key frames of an object within a chain
   --   Works in conjuction with getIKChainFrames function
   ------------
   fn objFrames track =
   (
   	local keyFrames = #()
   	if isController track.controller do (keyFrames = for k in track.controller.keys collect k.time.frame as integer)
   	for i = 1 to track.numSubs do join keyFrames (objFrames track[i])
   	keyFrames = sort (makeUniqueArray keyFrames)
   
   	keyFrames
   ) -- end objFrames function
   ------------
   
   
   ------------
   -- Get all keyframes of all objects associated with an IK chain
   --   Works in conjuction with chainObjFrames function
   ------------
   fn getIKChainFrames obj =
   (
   	local chain; for a in sceneIKChains do (for o in a where o == obj do chain = a)
   	local theFrames = #()
   	for i in chain do join theFrames (objFrames i[#transform])
   	theFrames = sort (makeUniqueArray theFrames)
   
   	theFrames
   ) -- end getIKChainFrames function
   ------------
   
   
   ------------
   -- Get the transform values of all keyframes in an IK chain
   --   The IK chain is specified using the associated control object
   ------------
   fn getIKChainKeyTransforms obj =
   (
   	local chain; for a in sceneIKChains do (for o in a where o == obj do chain = a)
   	local chainFrames = getIKChainFrames obj
   	local keyTransforms =	#()
   
   	for i = 1 to chain.count do
   	(
   		append keyTransforms #(chain[i])
   		for f in chainFrames do at time f append keyTransforms[i] #(f, chain[i].transform)
   	)
   
   	keyTransforms
   ) -- end getIKChainKeyTransforms function
   ------------
   
   
   ------------
   -- Add a keyframe to an object on a specified frame
   ------------
   fn addKeyframes track frame =
   (
   	if isController track.controller do addNewKey track frame
   	for i = 1 to track.numSubs do addKeyframes track[i] frame
   ) -- end addKeyframes function
   ------------
   
   
   ------------
   -- Move a keyframe of an object from one frame to another
   ------------
   fn moveKeyframes track old new =
   (
   	if isController track.controller do
   	(
   		for k = 1 to track.controller.keys.count where track.controller.keys[k].time == old do
   		(
   			try (moveKey track.controller k (new - old)) catch ()
   		) -- end k loop
   	)
   
   	for i = 1 to track.numSubs do moveKeyframes track[i] old new
   ) -- end moveKeyframe function
   ------------
   
   
   ------------
   -- Delete a specified keyframe from an object
   ------------
   fn deleteKeyframes track frame =
   (
   	if isController track.controller do
   	(
   		try
   		(
   			tc = track.controller
   			for k = 1 to tc.keys.count where tc.keys[k].time == frame do deleteKey tc k
   		) catch()
   	)
   
   	for i = 1 to track.numSubs do deleteKeyframes track[i] frame
   ) -- end deleteKeyframes function
   ------------
   
   
   ------------
   -- Match an IK chain's IK and FK transforms
   ------------
   fn matchIKFK obj =
   (
   	local chain; for a in sceneIKChains do (for o in a where o == obj do chain = a)
   	local IKObj = chain[1]
   
   	with animate on
   	(
   		if IKObj.controller.enabled == 1
   			then
   			(
   				transformArray = #()
   				for i = 2 to (chain.count - 1) do append transformArray chain[i].transform
   
   				IKObj.controller.enabled = 0
   				for i = 2 to (chain.count - 1) do	chain[i].transform = transformArray[i - 1]
   				IKObj.controller.enabled = 1
   			)
   			else IKObj.pos = chain[chain.count].pos
   	)
   ) -- end matchIKFK function
   ------------
   
   
   ------------
   -- Update IK chain array values
   ------------
   fn updateIKChainArrays initial:false = 
   (
   	sceneIKChainFrames = #()
   	sceneIKChainKeyTransforms	= #()
   
   	for i in sceneIKChains do
   	(
   		if initial == true do
   		(
   			for f in (getIKChainFrames i[1]) do at time f matchIKFK i[1]
   		)
   		append sceneIKChainFrames (getIKChainFrames i[1])
   		append sceneIKChainKeyTransforms (getIKChainKeyTransforms i[1])
   	)
   )
   ------------
   
   
   ------------
   -- Add, move, delete, or change the keyframes of an IK chain using any object in the chain
   ------------
   fn updateSceneIKChains =
   (
   	IKChainKeyTransforms = #()
   	selObject = selection[1]
   	selObjectFrames = sort (makeUniqueArray (objFrames selObject[#transform]))
   
   	for a = 1 to sceneIKChains.count do -- arrays
   	(
   		for s = 1 to sceneIKChains[a].count where sceneIKChains[a][s] == selObject do -- match selected object
   		(
   			local IKObject = sceneIKchains[a][1] -- IK chain object
   
   			if selObjectFrames as string != sceneIKChainFrames[a] as string -- keyframes have been changed
   				then
   				(
   					oldCount = sceneIKChainFrames[a].count
   					newCount = selObjectFrames.count
   
   					case of
   					(
   						-- Keyframes added
   						------------
   						(newCount > oldCount):
   						(
   							for f = 1 to selObjectFrames.count where findItem sceneIKChainFrames[a] selObjectFrames[f] == 0 do
   							(
   								for o in sceneIKChains[a] do addKeyframes o selObjectFrames[f]
   								at time selObjectFrames[f] matchIKFK selObject
   							)
   						) -- end case (newCount > oldCount)
   						------------
   
   						-- Keyframes moved
   						------------
   						(newCount == oldCount):
   						(
   							local oldFrames = #()
   							local newFrames = #()
   
   							for f = 1 to sceneIKChainFrames[a].count where (sceneIKChainFrames[a][f] != selObjectFrames[f]) do
   							(
   								append oldFrames sceneIKChainFrames[a][f]; append newFrames selObjectFrames[f]
   							)
   
   							if newFrames[1] > oldFrames[1]
   								then
   								(
   									for f = newFrames.count to 1 by -1 do
   									(
   										for o in sceneIKChains[a] do moveKeyframes o oldFrames[f] newFrames[f]
   										at time newFrames[f] matchIKFK selObject
   									)
   								)
   								else
   								(
   									for f = 1 to newFrames.count do
   									(
   										for o in sceneIKChains[a] do moveKeyframes o oldFrames[f] newFrames[f]
   										at time newFrames[f] matchIKFK selObject
   									)
   								) -- end if/then/else
   
   						) -- end case (newCount == oldCount)
   						------------
   
   						-- Keyframes deleted
   						------------
   						(newCount < oldCount):
   						(
   							for f = 1 to sceneIKChainFrames[a].count where findItem selObjectFrames sceneIKChainFrames[a][f] == 0 do
   							(
   								for o in sceneIKChains[a] where o != selObject do deleteKeyframes o sceneIKChainFrames[a][f]
   							)
   						) -- end case (newCount < oldCount)
   						------------
   
   					) -- end case/of
   				)
   				else
   				(
   					IKChainFrames = getIKChainFrames selObject
   					IKChainKeyTransforms[a]	= getIKChainKeyTransforms selObject
   
   					for o = 1 to IKChainKeyTransforms[a].count do
   					(
   						for k = 2 to IKChainKeyTransforms[a][o].count do
   						(
   							-- Keyframe transform values
   							------------
   							oldVal = sceneIKChainKeyTransforms[a][o][k][2]
   							newVal = IKChainKeyTransforms[a][o][k][2]
   
   							if oldVal as string != newVal as string do at time IKChainKeyTransforms[a][o][k][1] matchIKFK selObject
   							------------
   
   						) -- end k loop
   					) -- end o loop
   				) -- end if/then/else (selObjectFrames as string != sceneIKChainFrames[a] as string)
   
   		) -- end s loop
   	) -- end a loop
   
   	updateIKChainArrays()
   ) -- end updateSceneIKChainKeyTransforms function
   ------------
   
   fn tryUpdateSceneIKChains ev nd = try (updateSceneIKChains()) catch ()
   
   getSceneIKChains()
   updateIKChainArrays initial:true
   CB_TryUpdateSceneIKChains = NodeEventCallback mouseUp:true controllerOtherEvent:tryUpdateSceneIKChains



Edit:
Note, regarding the first part, this is what I'm talking about. The top series (A) is what Max does when you go from a frame with IK enabled set to 'on' to one where it is set to 'off'. The bottom one (B) is how it works after or while running my script.



Edit again:

I think I have found a solution, haven't really tried too hard to break it yet though.

All I did was replace
fn tryUpdateSceneIKChains ev nd = try (updateSceneIKChains()) catch ()

with
callbacks.removeScripts id:#undoCB
global actionIsUndo = false
callbacks.addScript #sceneUndo "actionIsUndo = true" id:#undoCB

fn tryUpdateSceneIKChains ev nd =
(
	if actionIsUndo == false
		then undo on (try (updateSceneIKChains()) catch ())
		else actionIsUndo = false
)


Ironically, I spent a bunch of hours trying to figure it out before deciding to ask for help. Then I let it be for a few hours and this solution just jumped into my head out of nowhere.

Let me be clear though: even though I have this working, I still would like to make it better.

Optimization techniques, added features... anything that anyone can think of to help me out, would be awesome.

Last edited by Malkalypse : 06 June 2012 at 11:22 PM.
 
Old 06 June 2012   #2
Thread automatically closed

This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.
 
Thread Closed share thread



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off
CGSociety
Society of Digital Artists
www.cgsociety.org

Powered by vBulletin
Copyright 2000 - 2006,
Jelsoft Enterprises Ltd.
Minimize Ads
Forum Jump
Miscellaneous

All times are GMT. The time now is 10:45 PM.


Powered by vBulletin
Copyright ©2000 - 2017, Jelsoft Enterprises Ltd.