PDA

View Full Version : Count & Reset Userdata?


Horganovski
02-15-2010, 03:56 AM
Hi Folks,

I'd like to create a Coffee script that will count and reset all of the userdata sliders on the selected object. I plan to expand on this and use it to copy the UD to another object rather than just resetting it, but the main thing right now is just how do I iterate through all of the userdata IDs on the object?

The script needs to work on different objects with different amounts of userdata sliders so how do I make it count how many IDs the object has?

Here's what I have so far, but I get a syntax error when I add the {i} variable to the end of the #ID_USERDATA:

Any ideas appreciated.


var i,obj;

obj=doc->GetActiveObject();

for (i=0; obj#ID_USERDATA!=Null; i++)
{
obj#ID_USERDATA:(i)=0;

}


Cheers,
Brian

Darter
02-15-2010, 06:12 AM
Unfortunately COFFEE can't be used to iterate through User Data ID's.

See this thread (http://www.plugincafe.com/forum/forum_posts.asp?TID=4393) at Plugin Cafe for more information. The link to the thread with the workaround is dead but it can be found here (http://www.plugincafe.com/%5Cforum/forum_posts.asp?TID=3946).

Horganovski
02-15-2010, 12:23 PM
Arrgh! Yet again I find myself fighting the limitations of Coffee (it's a bit rubbish it seems, pity it can't handle things like this like Mel can).

I'll see if I can use that workaround somehow, not sure if it's going to be practical though as I could have a case where there's any amount of userdata on an object.

What I want to do really is go through the userdata on a selected object, store each value in an array and then apply them again. My original intention was to create a C4D version of 'TweenMachine (http://www.justinanimator.com/mel-tweenMachine.php)', an animation workflow plugin for Maya. I have the basics of position and rotation working (using a brute force method of moving the playhead back and forward a frame to get the values as I can't access the keys themselves I think in Coffee either) but this Userdata issue is a stumbling block to getting it working properly.

Thanks anyway, this saves me searching for a solution

Maxon, please either dump Coffee, or update it to give us more access to the workings of the program with it! Things like this are very frustrating and I seem to run into them every time I try and do something that is possible in other programs.

Cheers,
Brian

tcastudios
07-09-2010, 08:07 PM
ID_USERDATA ( or its pure ID 700) is a subcontainer holding the Userdata Data.

Treat it as such and iterate read and set is done as per normal.


Cheers
Lennart

Horganovski
07-09-2010, 08:21 PM
Thanks for your reply Lennart. I'm not sure I have enough knowledge to make use of what you said though.

I'm not looking to have it spelled out for me, but do you think it's possible for a script to count how many user data sliders there are on a selected object and then store the value of each one in an array?
I asked about this on the plugincafe and was told that it's not possible.
In any tests I've tried the script seems to return an error and stop running as soon as it looks for a userdata entry that's not on the object, for example if I put a variable in brackets after the userdata ID.
Cheers,
Brian

tcastudios
07-09-2010, 08:38 PM
I don't see why it should be impossible.
Once you have the userdata Container...

var ud_bc = myobject->GetContiner()->GetContainer(ID_USERDATA);

...you use the regular GetData(i) and SetData(ud_bc,i)

Cheers
Lennart

Horganovski
07-09-2010, 10:08 PM
Many thanks Lennart, that does look like it would work for what I want to achieve, off to make some tests I go :)

Cheers,
Brian

tcastudios
07-09-2010, 10:13 PM
Yea, go ahead :)
I use it to iterate thru masses of object links
in lack of getting the Incl/Excl List gizmo to work in coffee.
I'm waiting for Python to support it, Sebastian knows about it.

Cheers
Lennart

Scott Ayers
07-09-2010, 10:42 PM
*Edit-- Cross posted with Lennart--Looks like he's got a solution for you.

Horganovski
07-09-2010, 11:47 PM
Well.. I hit a snag pretty quick that I can't figure out.

Here's an example of what I'm trying :


var i=1,test,checkVar=1;

var obj=doc->GetActiveObject();
var ud_bc=obj->GetContainer()->GetContainer(ID_USERDATA);


while (checkVar==1)
{
test=ud_bc->GetData(i);
if (test==nil) {checkVar=0; return;}
println(test);
i++;
}


This will count through the userdata entries on a selected object and print their values to the console.. but there's one fundamental flaw - it works fine if all of the UD sliders are at a value other than zero, but if any are then it gets a 'nil' value and stops running. How can it differentiate between a userdata slider being at zero, or not actually existing on the object? I guess I could just have it loop until 50 or something so that it will always count all userdata (even if it doesn't exist!) but that seems really inefficient.

I tried searching the SDK for 'Userdata' and nothing comes up :(

If I could get it to check the name of the userdata slider maybe that would work, but so far I can't find something that will work for that.

Cheers,
Brian

tcastudios
07-10-2010, 12:12 AM
Before trying to read you need to check if there is an ud entry
as well as read it as a float (so it's not read as nil)
Such as:

if(ud_bc->GetIndexId(i-1) != -1) // -1 means nothing there, stop
test = ud_bc->GetData(float(i));


Also, by the nature of userdata creation (Manage Userdata)
a new entry set to a 0 value is NIL.
Either set the default value at anything but 0,
or simply nudge the fader to "load it" as live.
(The float will keep it live)

Cheers
Lennart

Horganovski
07-10-2010, 02:38 AM
Many thanks again Lennart, I managed to get that working (I had to change the != to == ) but unfortunately for the application I had in mind I don't think it's going to work.

There are 2 issues that I don't think I can get around - one is that if any UD IDs are not there in sequential order (this sometimes happens while building a character rig as UD can be added/deleted, grouped or generally reorganized and in this case the script stops counting when if finds one missing in the sequential list as it counts.

I can get around this by adding extra 'dummy' UD to fill in the gaps so to speak, but there's another issue that I can't seem to get around and that's the fact that I need the script to be able to check all selected objects for UD, some of which may not have any. I guess again I could add dummy UD to every controller in the rig, but it's hardly ideal and makes the script less useful for general usage.

I think the best option is probably for me to start learning Python so that I can actually set and manipulate individual keyframes, which is what I want to do really, and I believe Coffee can't do this.

Many thanks though, I've got some ideas here that will come in handy for other applications I'm sure :)

Cheers,
Brian

Cairyn
07-10-2010, 08:26 AM
There are 2 issues that I don't think I can get around - one is that if any UD IDs are not there in sequential order (this sometimes happens while building a character rig as UD can be added/deleted, grouped or generally reorganized and in this case the script stops counting when if finds one missing in the sequential list as it counts.


User Data, or any container at all, is a map rather than an array (from an IT perspective). Therefore you get two issues: the container contains a few arbitrary IDs which may well be in the thousands, and all other IDs are null (actually, there is no map value at all, to save memory). And: "null" is actually 0, so you don't know whether an indexed value is there but 0 (or "false"), or not there at all (ID doesn't exist).

But actually, the BaseContainer contains a method to iterate through its content even if you don't know the valid IDs. This method is GetIndexId(). You will pass a real index (not an ID) that describes the position in the container map, and it will return either NOTOK (if there are no more map elements), or the actual ID of the object that is stored at this physical position.

It's actually a common problem with mapping container :-)


// assume cnt is a BaseContainer object
var index = 0; // map position, valid positions start at 0
var id; // id found at that position
while (NOTOK != (id = cnt->GetIndexId(index))) {
var obj = cnt->GetData(id);
println(index, " - ", id, " : ", obj);
index++;
}


This will for example, yield you the following output for a standard cube with standard values:


0 - 1100 : [200.000000,200.000000,200.000000]
1 - 1105 : 40.000000
2 - 1107 : 0
3 - 1101 : 0
4 - 1102 : 1
5 - 1103 : 1
6 - 1104 : 1
7 - 1106 : 5


As you can see, the IDs are not only not sequential, they are not even ordered! But this iteration method will yield you the available IDs (and their objects) with a minimum of iteration steps, and it works even if the object is nil (null) - this is essential if you are working with links, for example.

However.

First, a user data element is not actually set until the value has been changed by the user, or there is a default value != 0. Don't ask me, I just found out that the container is filled "late"...

Second, this method will not give you the sematics of the objects it finds. It will simply list the values, and you can check the data type of the object, but it does not provide the name of the object, or the GUI element that handles it.

User data containers are generally not meant to handle arbitrary data in an arbitrary way - the user (or the application that makes use of the user data) should know the semantics connected to an ID. If you need additional runtime information, you can in theory store it in the user data - e.g. by adding string fields that connect semantical information to the index of the data field, or the data itself, but you'd have to parse these strings yourself... -, but complex data requirements would usually call for a plugin tag instead that holds the data.

Horganovski
07-10-2010, 04:17 PM
Many thanks, for your detailed reply. I must admit a lot of it goes over my head, but I will study the example you posted and see what I can do with it.

I'm starting to think that for what I want to do I will be better trying to access the keyframes recorded on the object, rather than trying to iterate through the UD. Again though I believe this is not something Coffee can do, so I guess it's time for me to start learning Python :)

Cheers,
Brian

Scott Ayers
07-10-2010, 08:41 PM
As far as I can tell. Py4D just imports the Coffee CTrack() functions and calls them CKey() functions. So I don't know if you'll gain anything by using python for this.
So before you write off Coffee. Take a look through my notes on using the CTrack() functions.
They're in example form. So they should be a lot easier to figure out how they work than the Function(param1, param2) parts list format.

I still don't know how to use all of the functions in that class because (like most people) I need to see them in application form too. The typical SDK parts list of Function(param1, param2) only tells me the parts needed to perform the task. And that's rarely enough information for me to figure out how to actually use them. But I've managed to figure out a few of them.

-ScottA

Scott Ayers
07-11-2010, 02:19 AM
Here's another method I just came up with to find the previous and next key values in a sequence that might also come in handy:
// Create an object and record a key at frame zero
// Go to frame 10 and Move it to another position then record another key
// Go to frame 20 and move it again and record a third key

var track = op->GetFirstCTrack();
var curve = track->GetCurve(CC_CURVE, Null);
var first = curve->GetKey(0)->GetValue();
var second = curve->GetKey(1+1)->GetValue();
var third = curve->GetKey(2-1)->GetValue();

println(first); // the 1st key's value. Value of the key at frame zero
println(second); // the 3rd key's value. Result of getting the second key. Then getting the next key
println(third); // the 2nd key's value. Result of getting the third key. Then getting the previous key

You aren't restricted to using -1 or +1.
You can get the value of any specific key by using the correct increment or decrement number based from where you are starting from.

*Edit--I should probably point out that you can also use these values to update keys as well.
Example:
curve->GetKey(2)->SetValue(curve,second); // Sets the third key to same value as second key.





-ScottA

Horganovski
07-11-2010, 08:55 PM
Many thanks for that, it's definitely along the lines of what I'm looking for.

What I would need to work out next would be
1 - how to get the previous and next keys relative to the current position the playhead is at on the timeline.
2 - how to iterate through all tracks on the selected object, so that it can look for position,rotation and any userdata tracks that might be on there.

But this seems like it would work once I can figure those two out.

I've read before that Coffee cant create new tracks/keys, but I guess if I add a CallCommand(12410);// Record key on Active Objects
to the script at the start to set a key at the current frame, I could then use the SetValue function to change it.

Starting to think I might be able to make the 'tweener' script in Coffee after all :)

Thanks again,
Cheers,
Brian

Horganovski
07-11-2010, 09:19 PM
Ah, now I'm getting places!

This is getting very close to what I want to do (apart from the two issues mentioned above).

To use this create an object, record a key at frame zero, move the object and record another key at frame 10. Then go to a frame in between those, select the object and run the script.
A new key will be recorded on the current frame that favors the next key, and repeatedly running the script will 'nudge' the value of the current frame towards the next one (my original script that only worked for pos/rot also had a version that worked in reverse, nudging back towards the previous frame, but it didn't access the keys at all and was pretty clunky)


// Create an object and record a key at frame zero
// Go to frame 10 and Move it to another position then record another key
// Select the object, go to a frame in between those two and run the script to record a new key that is
// half way between the value on the current frame and the next key

var op = doc->GetActiveObject();
CallCommand(12410);// Record key on Active Objects - this creates a new key at the current frame
var track = op->GetFirstCTrack();
var curve = track->GetCurve(CC_CURVE, Null);
var first = curve->GetKey(0)->GetValue();
var second = curve->GetKey(1+1)->GetValue();
var third = curve->GetKey(2-1)->GetValue();

var first = curve->GetKey(0)->GetValue();
var second = curve->GetKey(1+1)->GetValue();
var third = curve->GetKey(2-1)->GetValue();

var blend = (second+((third-second)/2));

curve->GetKey(1)->SetValue(curve,blend); // Sets the second key to half way between it's current value and the value of third key.


println(first); // the 1st key's value. Value of the key at frame zero
println(second); // the 3rd key's value. Result of getting the second key. Then getting the next key
println(third); // the 2nd key's value. Result of getting the third key. Then getting the previous key
println(blend);


So If I can figure out issues 1 and 2 so that this will work wherever the playhead is (and looks for the previous and next keys from that position) and can find all keys on the object it will work and we'll have a C4D version of the SmartTween script! This will be awesome for speeding up animation workflow and building breakdowns and eases without pulling curves around in the timeline.

For issue 1 I guess all I have to do is get the 'number' of the key that the script just set on the current frame, then the previous key will be one less than that, the next one more. Is there a way to do this, ie some sort of KeyId or something like that?
Cheers,
Brian

tcastudios
07-11-2010, 09:30 PM
Brian, you should examine the scripts done by Rick Barret
for the_monkey, manipulating keys found in the download of
mikes NAB presentation at Cineversity:

http://www.cineversity.com/tutorials/lesson.asp?tid=1774

Still, it -is- possible to read/set/count userdata, even with different
amounts of userdata in any order or even deleted/added.

Cheers
Lennart

Horganovski
07-11-2010, 09:56 PM
Many thanks Lennart, looking at those scripts now it seems that's the solution for issue two, ie how to iterate through all tracks on the selected object.
So just that last thing now, how to work out which are the previous and next keys from the current timeline position and it should work!

I have to get some work done now, but I'll try and add in the iterate tracks function to the current script later on.

Re - userdata, that would be useful, but for this particular application I think dealing directly with the keys is more suitable for my purposes.

Cheers,
Brian

Horganovski
07-11-2010, 10:25 PM
Heh heh, couldn''t resist trying out the iterate part of it.. and it works!

To make sure the script 'nudges' the keyframes on the userdata you have to make sure to use Add Keyframe Selection to all of the coordinate and userdata entries you want to key, I always do that on character rigs anyway, since it makes it much easier to key the entire pose.

So here's the current working version, only thing missing now is being able to find which keys are the previous and next keys from the current timeline position rather than only working with the 1st and 3rd keys on the track. Really close now :)


// Create an object and record a key at frame zero
// Go to frame 10 and Move it to another position then record another key
// Go to a frame inbetween those two, select the object and run the script to 'nudge' towards the next key
// for keying userdata make sure to add anything that should be keyed using Add Keyframe Selection

var op = doc->GetActiveObject();
CallCommand(12410);// Record key on Active Objects
var track = op->GetFirstCTrack();

while (track)// iterating through all recorder tracks on the object
{
var curve = track->GetCurve(CC_CURVE, Null);
var first = curve->GetKey(0)->GetValue();
var second = curve->GetKey(1+1)->GetValue();
var third = curve->GetKey(2-1)->GetValue();

var first = curve->GetKey(0)->GetValue();
var second = curve->GetKey(1+1)->GetValue();
var third = curve->GetKey(2-1)->GetValue();

var blend = (second+((third-second)/2));

curve->GetKey(1)->SetValue(curve,blend); // Sets the second key to half way between it's current value and the value of third key.

track = track->GetNext(); // Get the next track
}


Ok.. now I have to get to work.. but I'll look at that 'finding the current keys' issue later ;)

I can see from Ricks script the 'GetKeyCount()' function, which will tell me how many keys are on the track, hopefully there's an equivalent key position, or frame, time etc that will tell me where they are on the timeline. - edit - looking in the SDK I see there's a GetTime() function, so I guess I'll have to use GetKeyCount, and then iterate through to find the one on the same frame as the playhead and then just count back one and forward one to find the previous and next keys.. unless there's a simpler way?

Cheers,
Brian

Scott Ayers
07-11-2010, 10:49 PM
Keys are listed as an index starting from Key(0).
The first key's index number will be zero no matter where it is on the timeline.
So if you have two keys set on the sequence of a track key(0) and key(1). Then add one between them. Key(1) automatically gets bumped up to key(2) and the new key becomes Key(1).

To get the value of a key based on where the scrubber is should be doable using the base time class: // BaseTime class method used for manipulating the slider
var bt = doc->GetTime();
bt->SetFrame(15, 30); // Set the slider to Frame 15 using 30fps in memory;
doc->SetTime(bt);// Execute the slider change

Then to find the keys you want to get their values from. In this example the Rotation.P track.
Search through the tracks for the track's name: while(track->GetName() != "Rotation . P")// change this to whatever track you're searching for
{
track = track->GetNext();//keep looking if it isn't found
}
var curve = track->GetCurve(CC_CURVE, FALSE); // Get the F-curve

Once that's done.
Get the values of the keys on that track you want using the :GetKey->GetValue(); method.
Then go looking for the other previous or past keys using increments and decrements applied to the same GetKey()->GetValue method.
Then set any new key values based on what you've got.

The time line doesn't seem to like do too much of this stuff without some sort of traffic cop directing the changes. I've run into some pretty funky stuff in there when doing this stuff. And I think there needs to be some type of message(MSG_UPDATE) in there to keep it all predictable. And that's one of the things I don't know how to do.

There is also this rather cool looking function that looks very handy. But I haven't figured it out yet either:
Remap(LReal time, LReal* ret_time, LONG* ret_cycle)

-ScottA

Horganovski
07-11-2010, 11:04 PM
Thanks again Scott, that gets me one step closer again, now I can use this


var bt = doc->GetTime();
var fps= doc->GetFps();
var currentFrame = bt->GetFrame(fps);
println(currentFrame);


to find out what frame the playhead is on, now it's just a matter of iterating through all the keys on the trackusing GetCount and GetTime to find which one is on the same frame, then counting back one and forward one to find the values I need to work from.

Nearly there, I think between this and taking Ricks script apart I have all of the pieces. just a matter of putting them together in the right way :)

Cheers,
Brian

Horganovski
07-11-2010, 11:23 PM
Aha.. I have it I think, here's how to count all the keys and work out what frames they are on :


// counting number of keys on the track

var keyTotal = curve->GetKeyCount();// count how many keys are on the track
println("Total number of keys on track is ", keyTotal);

var k; for(k=0; k<(keyTotal-1); k++)// iterating through the keys to find which is on the current frame - no need to check the last one
{

var keytime = curve->GetKey(k)->GetTime();
var keytimeFrame = keytime->GetFrame(fps);

println("Found Key at Frame ", keytimeFrame);
}


SmartTween 4D is almost there :), just some tidying up next, reordering stuff a little and a conditional statement and I'll have it!

Cheers,
Brian

Horganovski
07-12-2010, 01:24 AM
Done! It's all working now, in fact I'm testing it out right now while working on an animation job and it's working great. It's much faster than my old version and works perfectly with Userdata too.

Many thanks again for your help guys.

I'm going to record a quick screen capture later on to show it in use and explain how best to use it, I'll make it available to download then for anyone who may find it useful. I know I will :)

Cheers,
Brian

tcastudios
07-12-2010, 01:26 AM
Congrats!


Cheers
Lennart

Horganovski
07-12-2010, 02:28 AM
Thanks Lennart, it's a nice feeling when you finally get something like this working :)

Cheers,
Brian

Horganovski
07-12-2010, 04:14 PM
Everything seems to be working fine with the scripts now, so I've released them into the wild :)
http://forums.cgsociety.org/showthread.php?f=47&t=900915

Cheers,
Brian

Scott Ayers
07-12-2010, 10:05 PM
Still, it -is- possible to read/set/count userdata, even with different
amounts of userdata in any order or even deleted/added.
Cheers
Lennart

I'm very interested in learning more about this Lennart.
So if you would like to continue talking about this. Or have any examples for what you've discovered. I'm still very interested in seeing what you've found.

-ScottA

Horganovski
07-12-2010, 10:26 PM
^ Me too, I'm happy I got the script I wanted working, but being able to get better control over UD will still be very handy for other applications.

Cheers,
Brian

CGTalk Moderation
07-12-2010, 10:26 PM
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.