4

I have a text file with coordinates.In each line are positions of x, y, and z, for 9 objects. How could I for each line make Blender get coordinate from current line and create a keyframe for bone movement with it, using python? I was trying to solve it by my self, but for some strange reason, when I try to use file.read() and file.readlines() in the same script second one stops working. Thanks in advance for any answers and useful hints. Here is the file: https://www.dropbox.com/s/1eqf0zun2x5s89c/text3.txt?dl=0

First set of numbers is x of the first object second y and third z. Fourth is x of second object and so on. letters "e", "+" sings and 000 or 0001 after the plus signs are irrelevant and should be omitted.

Mzidare
  • 4,280
  • 1
  • 10
  • 24
  • Can you include the text file so we can help encoding it to an animation? – Bert VdB May 26 '17 at 11:51
  • Thanks of your interest in my question. I added a link to the file. – Mzidare May 26 '17 at 13:40
  • 1
    Why would you need to use both file.read and file.readlines for this case? If you need help scripting, post a MCVE. – Colonel Thirty Two May 29 '17 at 19:10
  • The e and the number following is not irrelevant. It is e-notation. Eg 1000 == 1.0e+003 Also suggest posting your script or a part thereof to determine a) the cause of your hassles, b) whether it is just a python issue and c) what you have tried so far. – batFINGER May 30 '17 at 13:00
  • Yes @brockmann it's 9 - thanks for noticing - corrected. The rig looks like separated bones (you can imagine them as not connected points in space). Each bone has to influence a mesh in it's nearest proximity(but this I'll take care of by hand, no need to include this part in the script). The mesh at this point will be just some subdivaded plane. The ultimate goal is to feed Blender with the text file and this way creating an animation of this bones. – Mzidare May 30 '17 at 16:27
  • @ColonelThirtyTwo I thought that file.read I need to get content of the file to then extract numeric values of it and file.readlines to iterate through lines of text. I was thinking like this (please don't laugh, it's my understanding of coding): for given single line, get x,y,z, values and execute code for changing bone position and adding keyframe, repeat for each line in the text file. Plus I would really like to know, why they don't work in one script, I don't know any possible reason for that. – Mzidare May 30 '17 at 16:34
  • @batFINGER Yes, I was considering possibility, that it's e-notation, It's irrelevant for my purpose though. My code for now is just "import bpy" :) – Mzidare May 30 '17 at 16:38
  • Congratulations import bpy is a good start. Ignoring the e notation of the linked input file will just produce rubbish , since 0.003 is to be treated the same as 30??? Is there any point then in having that link? BTW Similar questions have been asked before – batFINGER May 31 '17 at 09:18
  • @batFINGER It seams similar for someone, who has decent knowledge of python. For me something like this "for line in file:" is "magic" cause instead of "line" you can write f.ex. snot, or blob and you get exactly the same output. Why? In every other example, if you try to use some name, that is not variable earlier created, you got an error. In this example you can write anything you can think of and get some output,without any error. And there are many other stuff like this. – Mzidare May 31 '17 at 12:42
  • Nope, it is not "magic", rather it's very basic python.. Consider looking at https://stackoverflow.com and https://python.org etc. – batFINGER May 31 '17 at 13:02
  • Your last comment @batFINGER didn't actually add anything new. It's basically the same as after me saying "For me this Chinese sign is ununderstandable " you say "This is basic Chinese, look it up in dictionary" – Mzidare Jun 01 '17 at 06:54

1 Answers1

3

Hardest part is formatting the data (IMHO). I'd probably read the values and create a dictionary of location lists per object, which allows to structure and access the values in a reasonable way:

dict = {
    object-1 : [
       [row1-X, row1-Y, row1-Z], # Frame 1
       [row2-X, row2-Y, row2-Z], # Frame 2
       [row3-X, row3-Y, row3-Z], # Frame 3
       ...
    ], 
    object-2 : [
       [row1-X, row1-Y, row1-Z], # Frame 1
       [row2-X, row2-Y, row2-Z], # Frame 2
       [row3-X, row3-Y, row3-Z], # Frame 3
       ...
    ],
    ...
}

Reads the data and creates the dictionary

import bpy

file_data = [] # create an empty list
animation_data = {} # create an empty dictionary

# read the file
with open(<PATH-TO-FILE>) as f:
    content = f.readlines()
    for line in content:
        # store the line as list in file_data
        file_data.append(line.split())

# create location keyframe dictonary
for line in file_data:
    try: # try converting strings to floats
        floats = [float(i) for i in line] 
        # create composite list of this line
        composite = [floats[x:x+3] for x in range(0, len(floats),3)]
        # store it in the dict to organize the values
        for c, item in enumerate(composite): 
            #key = "object" +  str(c)
            animation_data.setdefault(c, []).append(item)
    except ValueError:
        print ("Incorrect data")

Adds one cube per dict item to the scene as well as the keys, starting at frame 1

if animation_data:
    for i in animation_data:
        bpy.ops.mesh.primitive_cube_add()
        # get the newly created cube
        obj = bpy.context.object
        # get keyframe location values
        obj_keyframes = animation_data.get(i)
        # start animation at frame 1
        for c, loc in enumerate(obj_keyframes, 1): 
            bpy.context.scene.frame_set(c) # set frame
            # move the cube and insert a keyframe
            obj.location = (loc[0], loc[1], loc[2])
            obj.keyframe_insert(data_path="location", index=-1) 

It's basically the same for loose bones of an armature

if animation_data:
    # get the selected object
    selected_obj = bpy.context.object
    if selected_obj.type == "ARMATURE":
        # continue if number of bones match the data
        if len(selected_obj.data.bones) == len(animation_data):
            for i in animation_data:
                # get the values from the dict
                obj_keyframes = animation_data.get(i)
                for c, loc in enumerate(obj_keyframes, 1):
                    bpy.context.scene.frame_set(c) # set the frame
                    # move the bone and insert a keyframe
                    selected_obj.pose.bones[i].location = (loc[0], loc[1], loc[2])
                    selected_obj.pose.bones[i].keyframe_insert(data_path="location", index=-1)
        else:
            print ("Data does not match number of bones")
brockmann
  • 12,613
  • 4
  • 50
  • 93
  • Thank you, I got already one more gyrus in my brain thanks to well explained peace of code of yours. My goal though is not to create new objects in scene, but to apply positions from the text file to already existing bones, which are in the same armature, but are not parented or connected to each other. – Mzidare May 30 '17 at 16:46
  • I hope you get the idea. Tried my best to explain the code in comments. Just let me know if there is something you do not understand @Mzidare – brockmann May 31 '17 at 12:45
  • The script works flawlessly. Thank You so much @brockmann – Mzidare Jun 01 '17 at 06:45
  • One more question though if you don't mind. How should I go about changing decimal place of the coordinates, to make movement more or less noticeable? – Mzidare Jun 01 '17 at 07:31
  • No problem @Mzidare, you are welcome. I'm not sure what your actual goal is but at a first glance, simple math within the floats list comprehension should do the trick floats = [ ( float(i) / 10 ) for i in line ] ...? – brockmann Jun 01 '17 at 08:41