Animation keyframe exporting

138 views
Skip to first unread message

likage

unread,
Dec 19, 2016, 7:47:54 PM12/19/16
to Python Programming for Autodesk Maya
Hi all, I am trying to export out the animation keyframes of some of the channel attributes of objects.
While my code does works, however it is not exporting out the whole range (I am exporting them based on the time slider values).

import maya.cmds as cmds
import json
# Specify your directory..
file_export_dir
= 'C:\Users\abc\Desktop\export_test.txt'

all_data
= {}
attributes
= ['translateX', 'translateY', 'translateZ', 'rotateX', 'rotateY', 'rotateZ']

def get_frame_range():
   
# get the frame range in the time slider
    start_frame
= int(cmds.playbackOptions(query=True, min=True))
    end_frame
= int(cmds.playbackOptions(query=True, max=True))
   
return(start_frame, end_frame)

def export_keyframe(sel):
    attr_data
= {}
   
(start_frame, end_frame) = get_frame_range()

   
for time in range(start_frame , (end_frame + 1)):
       
#>>> print "Frames iteration : ", time
        cmds
.currentTime(time)
       
# Get the value in each attribute
        attr_data
['frame'] = str(time)
       
for attribute in attributes:
            attr_value
= cmds.getAttr( sel + '.' + attribute)
           
# Assign the value into the dict
            attr_data
[attribute] = str(attr_value)
       
# Store the data into the main
        all_data
[sel] = attr_data
   
# Write the overall data into file
   
with open(file_export_dir, 'w') as outfile:
        outfile
.write(json.dumps(all_data, indent=4, sort_keys=True))
       

def main():
    sels
= cmds.ls(sl=True, l=True)
   
if not len(sels):
        cmds
.warning('Nothing is selected!')
       
       
# Create the key and assign an empty value
        all_data
[str(sel)] = None
        export_keyframe
(sel)

I am using nested dictionaries to store my data, where dict `attr_data` will stores the frame number and the values of the channel attributes, then this dict will then later be 'added' as a value to the dict `all_data`
In the text file, te results will be displayed as:
{
   
"|locator2": {
       
"frame": "5",
       
"rotateX": "2.0",
       
"rotateY": "2.0",
       
"rotateZ": "2.0",
       
"translateX": "2.5",
       
"translateY": "2.5",
       
"translateZ": "2.5"
   
}
}

In this text file of mine, as I mentioned earlier that it works, it is only exporting out the values of the last frame as determined in the time slider. If I replaced `attr_data` from dict to list, I will then be able to get all the attribute, names and frames exported correctly however, it will be in the form of '<attribute name> <attribute value> <frame number> \n'

I used dictionary thinking that it will be easier for me to get the values easily and if there is a need for me to debug, it will be easier to see too (the list method I had, simply prints all out with no indentation)

How can I improve this code of mine?

To test the code, simply create a locator, set the time slider to '1' to '5', add in some keyframes in the attributes - namely the translate and rotate attributes..

Justin Israel

unread,
Dec 19, 2016, 8:34:25 PM12/19/16
to python_in...@googlegroups.com
You have a bug in your code. "attr_data" is being initialized once at the start of your function, and then you update it during each loop and assign it to your "all_data" dict. But I believe you are thinking that each time you assign it, that it is automatically doing a deep copy for you. This is not the case. You are assigning a reference to the same dictionary each time, and basically overwriting it on each loop (which is overwritting the reference to that same dictionary in your "all_data"

Change it from this:

def export_keyframe(sel):
    attr_data = {}
    ...
​

To this:

def export_keyframe(sel):
    ...
    
for time in range(start_frame , (end_frame + 1
)):
        attr_data = {}
        ...
​

Justin


--
You received this message because you are subscribed to the Google Groups "Python Programming for Autodesk Maya" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_m...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/python_inside_maya/4d49de7f-054c-497a-91e7-0179402f5045%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

likage

unread,
Dec 19, 2016, 8:49:40 PM12/19/16
to Python Programming for Autodesk Maya
Hi Justin, thanks for getting back.. 
I tried your method, and I am still getting the same issue. The keyframes are still only being written once, same as the output that I have pasted in my first post.

Actually, now I am doubting if using dictionaries are the right way to do so?
Cause supposedly if my code does works, I assume the following is the output I will be seeing, though I am unsure if the recurring key names - 'frame', 'rotateX' etc will pose an issue later on or during the phase when such data are being inserted into the dictionary

{
   
"|locator1": {
       
{
           
"frame": "1",
           
"rotateX": "1.0",
           
"rotateY": "1.0",
           
"rotateZ": "1.0",
           
"translateX": "1.5",
           
"translateY": "1.5",
           
"translateZ": "1.5"
       
},
       
{
           
"frame": "2",
           
"rotateX": "1.01",
           
"rotateY": "1.01",
           
"rotateZ": "1.01",
           
"translateX": "1.51",
           
"translateY": "1.51",
           
"translateZ": "1.51"
       
}
   
}
}

Justin Israel

unread,
Dec 19, 2016, 8:57:18 PM12/19/16
to Python Programming for Autodesk Maya
I just noticed another bug in your code that is causing the behaviour you mentioned. 

# Store the data into the main 
all_data[sel] = attr_data

This line is also just constantly replacing the top level key with the last frames data. I think you need to be clear about what kind of data structure you really want to end up with. Because the structure you listed in your last email isn't even a valid python dictionary. Do you want to be able to map the frame number to the data? Or do you just want a list of results?

Like this, where there is a mapping to each frame number?

{
    "|locator1": {
        "1": {
            "frame": "1", 
            "rotateX": "1.0", 
            "rotateY": "1.0", 
            "rotateZ": "1.0", 
            "translateX": "1.5", 
            "translateY": "1.5", 
            "translateZ": "1.5"
        },
        "2": {
            "frame": "2", 
            "rotateX": "1.01", 
            "rotateY": "1.01", 
            "rotateZ": "1.01", 
            "translateX": "1.51", 
            "translateY": "1.51", 
            "translateZ": "1.51"
        }
    }
}

Or like this, where its just a list of results?

{
    "|locator1": [
        {
            "frame": "1", 
            "rotateX": "1.0", 
            "rotateY": "1.0", 
            "rotateZ": "1.0", 
            "translateX": "1.5", 
            "translateY": "1.5", 
            "translateZ": "1.5"
        },
        {
            "frame": "2", 
            "rotateX": "1.01", 
            "rotateY": "1.01", 
            "rotateZ": "1.01", 
            "translateX": "1.51", 
            "translateY": "1.51", 
            "translateZ": "1.51"
        }
    ]
}

Pick one, and we can go from there to adjust your code. Basically you just need to update that "all_data" assignment to either map the frame number to your attr_data instance, or append it to a list.

Justin


--
You received this message because you are subscribed to the Google Groups "Python Programming for Autodesk Maya" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_m...@googlegroups.com.

likage

unread,
Dec 20, 2016, 12:33:25 AM12/20/16
to Python Programming for Autodesk Maya
 {
    "|locator1": [
        {
            "frame": "1", 
            "rotateX": "1.0", 
            "rotateY": "1.0", 
            "rotateZ": "1.0", 
            "translateX": "1.5", 
            "translateY": "1.5", 
            "translateZ": "1.5"
        },
        {
            "frame": "2", 
            "rotateX": "1.01", 
            "rotateY": "1.01", 
            "rotateZ": "1.01", 
            "translateX": "1.51", 
            "translateY": "1.51", 
            "translateZ": "1.51"
        }
    ]
}
 
I was actually thinking of using this in the first place. But instead of it being in list, I conjure it in terms of dictionary...

{ 
    "|locator1": {
        "1": {
            "frame": "1", 
            "rotateX": "1.0", 
            "rotateY": "1.0", 
            "rotateZ": "1.0", 
            "translateX": "1.5", 
            "translateY": "1.5", 
            "translateZ": "1.5"
        },
        "2": {
            "frame": "2", 
            "rotateX": "1.01", 
            "rotateY": "1.01", 
            "rotateZ": "1.01", 
            "translateX": "1.51", 
            "translateY": "1.51", 
            "translateZ": "1.51"
        }
    }
}
This works for me too.. It is just that it never occurs to me of using '1', 2' etc as the key for I am thinking it in terms of using the object's name to store the data.. 
Need more practice with dictionary  

Justin Israel

unread,
Dec 20, 2016, 1:25:18 AM12/20/16
to python_in...@googlegroups.com
On Tue, Dec 20, 2016 at 6:33 PM likage <dissid...@gmail.com> wrote:
 
This works for me too.. It is just that it never occurs to me of using '1', 2' etc as the key for I am thinking it in terms of using the object's name to store the data.. 
Need more practice with dictionary  

The thing is that the key of a dictionary needs to be a non-mutable type, which can be reliably hashed and not change after it becomes a key. A dictionary cannot be a key to another dictionary because its content can change. So if you want to be able to map your time-based results within a selected path, then you have to use some other key. I just chose the time as each key since its the variable that is different between each loop.

You can try changing your function to something like this:
def export_keyframe(sel):
    ...
    all_data[sel] = {} # init empty time results


    for time in range(start_frame , (end_frame + 1
)):
        attr_data = {}
        cmds.currentTime(time)
        
# Get the value in each attribute
        attr_data['frame'] = str(time)
        for attribute in attributes:
            attr_value = cmds.getAttr( sel + '.' + attribute)
            # Assign the value into the dict

            attr_data[attribute] = str(attr_value)
        
# Store the data into the main

        all_data[sel][time] = attr_data # assign using time key
    ...
​

Justin

--
You received this message because you are subscribed to the Google Groups "Python Programming for Autodesk Maya" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_m...@googlegroups.com.

likage

unread,
Dec 20, 2016, 3:16:58 PM12/20/16
to Python Programming for Autodesk Maya
Thanks, it works!

I have a few questions..

1. `all_data[sel] = {}`, why can't I wrote it as `all_data[sel] = None`? I tried and I got an error that states ''NoneType' object does not support item assignment'
I asked this because since I have no values to append in the first place, wouldn't using None be more practical? Or, do correct me if I am wrong, since we are dealing with dictionaries, initializing an empty dict is then the correct way?

2. Do I still need to have `attr_data = {}` still needs to be placed under the `for time ... end_frame +1))` line? Would that not cause it to be loop continuously? P.S, Again, I tried placing it outside of the `for` statement as initially seen in my post, and it works too..

3. This is slightly unrelated but what can I do to refine writing logic process? There are times in which I seem to get it (but it is in a big chunk where it can be further refine), and times like this, where I am unable to..


Justin Israel

unread,
Dec 20, 2016, 3:45:26 PM12/20/16
to python_in...@googlegroups.com
On Wed, Dec 21, 2016 at 9:17 AM likage <dissid...@gmail.com> wrote:
Thanks, it works!

I have a few questions..

1. `all_data[sel] = {}`, why can't I wrote it as `all_data[sel] = None`? I tried and I got an error that states ''NoneType' object does not support item assignment'
I asked this because since I have no values to append in the first place, wouldn't using None be more practical? Or, do correct me if I am wrong, since we are dealing with dictionaries, initializing an empty dict is then the correct way?

To me, it does not seem practical to initialize the key with a None value, because you are going to have to just replace that later with a dictionary if you end up wanting to assign anything.

all_data[sel] = None
for ...
    all_data[sel][frame] = data
 
That would crash because the value of all_data[sel] is None, and you can't treat None like a dictionary. It just makes sense to me to start with an empty dictionary and fill it. Otherwise, you could switch to a defaultdict and populate it on demand. I just went for the easiest example to get your functional.


2. Do I still need to have `attr_data = {}` still needs to be placed under the `for time ... end_frame +1))` line? Would that not cause it to be loop continuously? P.S, Again, I tried placing it outside of the `for` statement as initially seen in my post, and it works too..

Yes, as far as I can tell from reading your code, you need to initialize a new attr_data at the start of each iteration of a frame. While it is not going to cause a crash if you initialize it once outside all of the for loops, what it is doing is corrupting your data. Look carefully at how you are using it. In each loop you replace the same keys (the attributes) with new values, and then assign that attr_data to all_data. It would seem you think that the assignment will deep copy the dictionary each time, but it does not. You are simply corrupting values from the previous loops.

Consider this smaller example:

  1. def test(broken=False):
  2. all_data = {}
  3. attr_data = {}
  4.  
  5. for i in xrange(5):
  6.  
  7. if not broken:
  8. attr_data = {}
  9.  
  10. for j, attr in enumerate(['a','b','c']):
  11. attr_data[attr] = i + j
  12.  
  13. all_data[i] = attr_data
  14.  
  15. print all_data
 
If you don't create a new attr_data for each loop, your output data will be only the values of the last loop, for all of the keys, because you are always updating the same reference to the same dictionary.


3. This is slightly unrelated but what can I do to refine writing logic process? There are times in which I seem to get it (but it is in a big chunk where it can be further refine), and times like this, where I am unable to..

I'm not clear on what you mean by this. But if you are asking how to make it easier to reason about building data structures processing lots of values and logic, then maybe what you want to do is break it up into smaller functions and then compose those functions in another. This may help you by having to look at less context at one time. That is, maybe you have a function that can just produce a new attr_data dict, given some input. And then you just assign that output to another data structure as needed.

Justin
 


--
You received this message because you are subscribed to the Google Groups "Python Programming for Autodesk Maya" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_m...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages