Ignore empty frames


#1

I’m triyng to write script what will be write for me information in max listener about quaternion rotation and position for animation.

on CopyBones pressed do
     	(
     		nframes = "sliderTime = "
     		rframes = "$.rotation = "
     		pframes = "$.pos = "
     		keystart = animationrange.start
     		keyend = animationrange.end-1
     	  
     		if selection.count == 1 then
     		(
     		
     		format nframes
     		sliderTime = 0f
     		print sliderTime
     		format rframes
     		print $.rotation
     		format pframes
     		print $.pos
     
     			for i = keystart to keyend do
     		(
     			sliderTime +=1
     			format nframes
     			print sliderTime
     			format rframes
     			print $.rotation
     			format pframes
     			print $.pos
     		)
     	)
     	else
     (
     	messagebox "You must select any bone first!"
     )
     )
Working good but information about rot and pos is write for all frames. I want to ignore frames what haven't keysframes.

For example

I have key in 1st then 5th and 10th, 12th frame.

In max listener I want to see something like that:
0f
5f
10f

12f

and not like now

0f
1f
2f
3f
4f
5f
6f
7f
8f
9f
10f
11f
12f

How I can do it?


#2

You can check your controller keys array.

Try using :

$.position.controller.keys
$.rotation.controller.keys


#3

Ok but can you (or someone else) explain me how I can check my controller keys array? My scripting level isn’t good :slight_smile: I think this should be something with if.

For example this should like that:

sliderTime = 0f
$.rotation = (quat -0.0402838 -0.692195 0.0462476 -0.7191)
$.pos = [0.0814999,4.36233e-005,-0.00849035]
sliderTime = 1f
$.rotation = (quat -0.0398559 -0.692551 0.0462876 -0.718778)
$.pos = [0.0819329,0.000305913,-0.00848681]
sliderTime = 3f
$.rotation = (quat -0.0373623 -0.694563 0.0465611 -0.716951)
$.pos = [0.0844062,0.0018139,-0.00846642]
sliderTime = 5f
$.rotation = (quat -0.0348128 -0.696512 0.0469076 -0.715163)
$.pos = [0.0868516,0.00332114,-0.00844604]
sliderTime = 8f
$.rotation = (quat -0.0343639 -0.696845 0.0469752 -0.714856)
$.pos = [0.087274,0.00358315,-0.0084425]
sliderTime = 10f
$.rotation = (quat -0.0343639 -0.696845 0.0469752 -0.714856)
$.pos = [0.087274,0.00358315,-0.0084425]

The keyframes are in 0, 1, 3, 5, 8 and 10 frame. Empty frames are ignore.


#4

Here is the main idea :

You are parsing all the frames between keystart and keyend.

We have the ability to know where keyframes have been set, using the code I gave you.

What you have to do now is :

  • Create a new empty array named keyframesArray, it will contain the frames where keyframes has been set
  • In this array, join the keyframes of the position controller
  • Repeat this for the rotation controller

At last, you won’t write :

for i = keystart to keyend do

But you’ll write :

for f in keyframesArray do
(
     sliderTimer = f
[...]
)

Give it a try, I’ve written in bold the words you can look for in the maxscript documentation. I’ll help you if you have further questions.


#5

I’m was trying to do what you wrote but 3ds Max give me error

>> MAXScript Rollout Handler Exception: – Unable to convert: #keys(0f, 6f, 14f, 20f, 30f, 51f, 59f, 70f, 76f, 81f, 87f, 92f) to type: Time <<

The problem is with: sliderTime = f

If I understood correctly I must use for loop in for loop (two for loop; 1. for i = keystart to keyend do ; 2. for f in keyframesArray do) ?

I have this:

on CopyBones pressed do
 	(
 		keyframesArray = #()
 		join keyframesArray #($.rotation.controller.keys)
 		
 		nframes = "sliderTime = "
 		rframes = "$.rotation = "
 		keystart = animationrange.start
 		keyend = animationrange.end-1
 
 		
 		if selection.count == 1 then
 		(
 		
 		format nframes
 		sliderTime = 0f
 		print sliderTime
 		format rframes
 		print $.rotation
 
 			for i = keystart to keyend do
 			(
 				for f in keyframesArray do
 				(
 				sliderTime = f
 				format nframes
 				print sliderTime
 				format rframes
 				print $.rotation
 				)
 			
 
 			)
 		)
 	else
 	(
 	messagebox "You must select any bone first!"
 	)
 	)

Sorry but I’m lame in scripting :sad:


#6

I was expecting you to write the error message, it means that you’re following the good way :slight_smile:

The thing is, the values you are storing in the array are keyframes, try to write keyframesArray[1] once the values are gathered to see the result. This is an example of a keyframe, it can look like this :

#Bezier Float key(2 @ 50f)

Note that they’re two values : a controller value, and a frame time. What you want to do now, is to access the frame time. You can do it this way :

keyframe.time

Now, about the loops. You don’t have to (and you must not in your case) write a loop inside a loop.

Your first loop was saying :

  • Let’s parse all the frames of the animation range, and print their values

The second loop you wrote says :

  • We know where the keyframes are set, let’s parse those frames

If you put the two loops together you would have (in pseudo-code) :

At frame 0 :
     at frame 0
     at frame 6
     at frame 14
     ....
At frame 1 :
     at frame 0
     at frame 6
     at frame 14
     ....

You don’t need the first loop anymore. What you want your script to do is :

At frame 0 : print
At frame 6 : print
At frame 14 : print

#7

Yes it is writing information about keyframes:

#Euler XYZ key(1 @ 0f)
#Euler XYZ key(2 @ 6f)
#Euler XYZ key(3 @ 14f)
#Euler XYZ key(4 @ 20f)
#Euler XYZ key(5 @ 30f)
#Euler XYZ key(6 @ 51f)
#Euler XYZ key(7 @ 59f)
#Euler XYZ key(8 @ 70f)
#Euler XYZ key(9 @ 76f)
#Euler XYZ key(10 @ 81f)
#Euler XYZ key(11 @ 87f)
#Euler XYZ key(12 @ 92f)

but I don’t get it. I’m trying to do it with keyframe.time but I’ve still the same error Unable to convert: to type: Time. I don’t know how I must use this keyframe.time. I found some things about it but I don’t get it :sad:
Maybe… Can you write how exactly look for loop or you can give me more instructions how I can write correctly it.


#8

How do you write your for loop ?
I don’t need all the lines, just the beginning with the slidertime line.


#9

Here it’s code.

on CopyBones pressed do
    	 (
    		 keyframesArray = #()
    		 join keyframesArray #($.rotation.controller.keys)
    		 
    		 nframes = "sliderTime = "
    		 rframes = "$.rotation = "
    		 keystart = animationrange.start
    		 keyend = animationrange.end-1
    
    		print keyframesArray[1] --for test
    		 
    		 if selection.count == 1 then
    		 (
    
    				 for f in keyframesArray[1] do
    				 (
    				 sliderTime = f --it give me error Unable to convert: to type: Time
    				 format nframes
    				 print sliderTime
    				 format rframes
    				 print $.rotation
    				 )
    		 )
    	 else
    	 (
    	 messagebox "You must select any bone first!"
    	 )
    	 )

#10
sliderTime = f --it give me error Unable to convert: to type: Time

You exactly have spotted the error. You should be able to find the good syntax.
Remember :

  • sliderTime expects a value that looks like “10f”
  • f’s value is “(1 @ 10f)”

Your code is almost correct, and I gave you the value some lines ago, try again :slight_smile:


#11

I don’t want to interfere with your learning process but if you like I could show how to script this kind of tasks…


#12

If you please, that’d be great.


#13

here is a basic function to get at key position the value of a controller:


fn getRotationData node = 
(
	for k in node.rotation.controller.keys collect at time k.time #(k.time, node.rotation) 
)

if you want to collect only data on some particular time interval we can add the condition:

	
fn getRotationData node range:animationrange = 
(
	for k in node.rotation.controller.keys where k.time >= range.start and k.time <= range.end collect at time k.time #(k.time, node.rotation) 
)

usually it’s much cleaner to return data in some readable format… the way of doing this is the using of structures:


struct KeyValue (time, value)
fn getRotationData node range:animationrange = 
(
	for k in node.rotation.controller.keys where k.time >= range.start and k.time <= range.end collect at time k.time (KeyValue time:k.time value:node.rotation) 
)

if we want to have an option of using or not using time interval we can add new condition (#allKeys for example):


struct KeyValue (time, value)
fn getRotationData node range: = if isvalidnode node do
(
	if range == unsupplied do range = animationrange -- default interval
	for k in node.rotation.controller.keys where range == #allKeys or (k.time >= range.start and k.time <= range.end) collect at time k.time (KeyValue time:k.time value:node.rotation) 
)
/*
-- samples:
getRotationData $ -- get keys on animation range interval for selected node
getRotationData node range:#allKeys -- get all keys for specified node
getRotationData node range:(interval 20 40) -- get keys on specified interval for specified node
*/


#14

it’s the basic. but in real life the task is more complicated. usually the save animation function has to collect not only values of controller but also key parameters (in/out tangent type, in/out tangent value, etc.), and type of controller. only this can more or less guaranty that saved and loaded animation will be close enough.


#15

and of course before collecting the data we have to check that inspected controller is animatable and keyable.


#16
fn getRotationData node range: = if isvalidnode node do

wow, I never saw this syntax


#17

It’s printing very well now:

(keyValue time:0f value:(quat -0.0084074 -0.726134 -0.0760803 -0.683279))
      (keyValue time:6f value:(quat -0.0083271 -0.730019 -0.0814009 -0.678511))
      (keyValue time:14f value:(quat -0.0083271 -0.730019 -0.0814009 -0.678511))
      (keyValue time:20f value:(quat 0.00883731 -0.735388 -0.117566 -0.667311))
      (keyValue time:30f value:(quat -0.0290219 -0.73919 -0.149244 -0.656111))
      (keyValue time:51f value:(quat -0.0290223 -0.73919 -0.149243 -0.656111))
      (keyValue time:59f value:(quat 0.0431588 -0.746856 -0.156649 -0.644829))
      (keyValue time:70f value:(quat 0.043159 -0.746856 -0.156649 -0.644829))
      (keyValue time:76f value:(quat -0.029703 -0.742674 -0.166747 -0.64788))
      (keyValue time:81f value:(quat -0.023978 -0.720314 -0.0932344 -0.686935))
      (keyValue time:87f value:(quat -0.0138081 -0.709417 -0.0797173 -0.70013))
      (keyValue time:92f value:(quat -0.00840715 -0.726134 -0.0760805 -0.683279))
 but I would like to see like I wrote:

    sliderTime = 0f
         $.rotation = (quat -0.0402838 -0.692195 0.0462476 -0.7191)
         $.pos = [0.0814999,4.36233e-005,-0.00849035]
         sliderTime = 1f
         $.rotation = (quat -0.0398559 -0.692551 0.0462876 -0.718778)
         $.pos = [0.0819329,0.000305913,-0.00848681]
         sliderTime = 3f
         $.rotation = (quat -0.0373623 -0.694563 0.0465611 -0.716951)
         $.pos = [0.0844062,0.0018139,-0.00846642]
         sliderTime = 5f
         $.rotation = (quat -0.0348128 -0.696512 0.0469076 -0.715163)
         $.pos = [0.0868516,0.00332114,-0.00844604]
         sliderTime = 8f
         $.rotation = (quat -0.0343639 -0.696845 0.0469752 -0.714856)
         $.pos = [0.087274,0.00358315,-0.0084425]
         sliderTime = 10f
         $.rotation = (quat -0.0343639 -0.696845 0.0469752 -0.714856)
         $.pos = [0.087274,0.00358315,-0.0084425] 					 

Why? I need apply this information to another object because I don’t know how I can write Copy and Paste function :slight_smile:


#18

Try looking for the “format” command. Here’s an example :

format "% : %
" valueName value

But as DenisT was saying, this doesn’t copy the keyframes tangents, so you’ll certainly encounter artefacts.


#19

just make a decision. one is to go your own way, the another one is to do respect somebody’s else experience.


#20

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.