PDA

View Full Version : Ignore empty frames


Luten
04-17-2012, 03:42 PM
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?

MrPingouin
04-17-2012, 07:15 PM
You can check your controller keys array.

Try using :

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

Luten
04-17-2012, 09:02 PM
Ok but can you (or someone else) explain me how I can check my controller keys array? My scripting level isn't good :) 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.

MrPingouin
04-17-2012, 10:38 PM
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.

Luten
04-18-2012, 01:02 AM
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:

MrPingouin
04-18-2012, 10:11 AM
I was expecting you to write the error message, it means that you're following the good way :)

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

Luten
04-18-2012, 02:10 PM
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.

MrPingouin
04-19-2012, 02:58 PM
How do you write your for loop ?
I don't need all the lines, just the beginning with the slidertime line.

Luten
04-19-2012, 03:29 PM
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!"
)
)

MrPingouin
04-19-2012, 03:42 PM
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 :)

denisT
04-19-2012, 04:13 PM
I don't want to interfere with your learning process but if you like I could show how to script this kind of tasks...

MrPingouin
04-19-2012, 04:22 PM
If you please, that'd be great.

denisT
04-19-2012, 04:27 PM
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
*/

denisT
04-19-2012, 04:33 PM
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.

denisT
04-19-2012, 04:36 PM
and of course before collecting the data we have to check that inspected controller is animatable and keyable.

MrPingouin
04-19-2012, 04:41 PM
fn getRotationData node range: = if isvalidnode node do

wow, I never saw this syntax

Luten
04-19-2012, 05:48 PM
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 :)

MrPingouin
04-19-2012, 05:50 PM
Try looking for the "format" command. Here's an example :

format "% : %\n" valueName value

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

denisT
04-20-2012, 01:42 AM
It's printing very well now:
...
but I would like to see like I wrote:
...
Why? I need apply this information to another object because I don't know how I can write Copy and Paste function
just make a decision. one is to go your own way, the another one is to do respect somebody's else experience.

CGTalk Moderation
04-20-2012, 01:42 AM
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.