How to export 2D-coordinates for use in After Effects?

I am looking for a scripted way to export the 2D coordinates of an object to be used in Adobe After Effects. My Python knowledge is basically zero, so I could use some help.

I’ve found this script by HowieM (https://howiem.org/wordpress/2014/02/07/getting-camera-tracking-data-from-blender-to-after-effects/), but it throws errors:

# use this script to export the resulting screen coordinates
# of the currently active object in 3D space. Coords get 
# output to the console -h

import bpy
from bpy_extras.object_utils import world_to_camera_view

scene = bpy.context.scene

# needed to rescale 2d coordinates
render = scene.render
res_x = render.resolution_x
res_y = render.resolution_y

rnd = lambda i: round(i,1)

print("====================")

for j in range(1845,1902):   # frame range you're interested in    
    scene.frame_set(j)
    obj = bpy.context.active_object
    coords_2d = world_to_camera_view(scene, cam, (obj.matrix_world *  obj.location))
    print("\t{}\t{}\t{}\t".format(j, rnd(res_x*coords_2d.x), rnd(res_y*(1-coords_2d.y))))

The first error was NameError: name 'cam' is not defined, so I’ve added

cam = bpy.context.scene.camera

To the variables, which at least fixed that one. Still, execution of the script threw another error:

TypeError: Element-wise multiplication: not supported between 'Matrix' and 'Vector' types

which happens in the penultimate line. How could I fix that?

Ideally I’d like to have a few more features in this, especially using the currently set frame range (instead having to change it in the script all the time) and the ability to copy the generated coordinates to the clipboard automatically on execution, so that I can just paste them into a 2D layer in After Effects. I’d prefer the clipboard way over outputting a file, because this would require extra steps which I’d like to avoid. Is this at all possible inside of Blender?

The script you found was written for an older version of blender than you are currently using. I believe it was Blender 2.80 that changed the matrix math. Use @ instead of * in the line:

coords_2d = world_to_camera_view(scene, cam, (obj.matrix_world * obj.location))

Thanks! That fixed the error thrown.

I’ve optimized this thing a bit, now writing into a text file, for the time being (until I find out, how to copy the output to the clipboard instead). Also I’m now reading all the data, like frame ranges, from the scene, so nothing needs to be defined in the script.

import bpy

from bpy_extras.object_utils import world_to_camera_view

TextFile=open('D:\\coordinates.txt','w')

scene = bpy.context.scene
cam = bpy.context.scene.camera

# variables
render = scene.render
res_x = render.resolution_x
res_y = render.resolution_y
render_first = scene.frame_start
render_last = scene.frame_end
render_fps = scene.render.fps
rnd = lambda i: round(i,1)

TextFile.write("Adobe After Effects 8.0 Keyframe Data\n")
#print("\n")
TextFile.write("\tUnits Per Second\t" + str(render_fps) + "\n")
TextFile.write("\tSource Width\t" + str(res_x) + "\n")
TextFile.write("\tSource Height\t" + str(res_y) + "\n")
TextFile.write("\tSource Pixel Aspect Ratio\t1\n")
TextFile.write("\tComp Pixel Aspect Ratio\t1\n")
TextFile.write("\n")
TextFile.write("Transform\tPosition\n")
TextFile.write("\tFrame\tX pixels\tY pixels\tZ pixels\n") 

for j in range(render_first,render_last):   
    scene.frame_set(j)
    obj = bpy.context.active_object
    coords_2d = world_to_camera_view(scene, cam, (obj.location))
    TextFile.write("\t{}\t{}\t{}\t0\n".format(j, rnd(res_x*coords_2d.x), rnd(res_y*(1-coords_2d.y))))

TextFile.write("\n")
TextFile.write("End of Keyframe Data")
TextFile.close()

I also had to throw out the obj.matrix_world part of the calculation, as it produced wrong coordinates. Now it seems to be working fine!

So, the big question is, how can I get this output to the clipboard when running the script? It wouldn’t mind if the text-file is created as an intermediate step.

Python script to copy text to clipboard - Stack Overflow

Thanks, but there are so many “solutions” in that thread, I feel a bit lost. At a quick glance it looks like they are just copying a defined string to the clipboard. I am creating more than just one string with my script, so how would I copy the whole output to the clipboard in one go? Or can I load my saved text-file as a single string?

import subprocess

def copy2clip(txt):
    cmd='echo '+txt.strip()+'|clip'
    return subprocess.check_call(cmd, shell=True)

if you don’t want to use pip libraries. If you don’t mind using pip libraries, use:

try:
    from pyperclip import copy
except:
    python_exe = os.path.join(sys.prefix, 'bin', 'python.exe')
    # upgrade pip
    subprocess.call([python_exe, "-m", "ensurepip"])
    subprocess.call([python_exe, "-m", "pip", "install", "--upgrade", "pip"])
    # install required packages
    subprocess.call([python_exe, "-m", "pip", "install", "pyperclip"])
from pyperclip import copy
copy('The text to be copied to the clipboard.')

To get the whole file as a string:

with open("file.txt") as f:
    text = f.readlines()

Thanks, but I can’t get it working:

import bpy
import subprocess

def copy2clip(txt):
    cmd='echo '+txt.strip()+'|clip'
    return subprocess.check_call(cmd, shell=True)

from bpy_extras.object_utils import world_to_camera_view

TextFilePath=('D:\\coordinates.txt')
TextFile=open(TextFilePath,'w')

scene = bpy.context.scene
cam = bpy.context.scene.camera

# variables
render = scene.render
res_x = render.resolution_x
res_y = render.resolution_y
render_first = scene.frame_start
render_last = scene.frame_end
render_fps = scene.render.fps
rnd = lambda i: round(i,1)

TextFile.write("Adobe After Effects 8.0 Keyframe Data\n")
TextFile.write("\tUnits Per Second\t" + str(render_fps) + "\n")
TextFile.write("\tSource Width\t" + str(res_x) + "\n")
TextFile.write("\tSource Height\t" + str(res_y) + "\n")
TextFile.write("\tSource Pixel Aspect Ratio\t1\n")
TextFile.write("\tComp Pixel Aspect Ratio\t1\n")
TextFile.write("\n")
TextFile.write("Transform\tPosition\n")
TextFile.write("\tFrame\tX pixels\tY pixels\tZ pixels\n") 

for j in range(render_first,render_last):   
    scene.frame_set(j)
    obj = bpy.context.active_object
    coords_2d = world_to_camera_view(scene, cam, (obj.location))
    TextFile.write("\t{}\t{}\t{}\t0\n".format(j, rnd(res_x*coords_2d.x), rnd(res_y*(1-coords_2d.y))))

TextFile.write("\n")
TextFile.write("End of Keyframe Data")
TextFile.close()

with open(TextFilePath) as f:
    text = f.readlines()
    
copy2clip(text)

Is it correct to use that command in the end? Because that’s the one that causes this error:
AttributeError: 'list' object has no attribute 'strip'

I don’t know how to use the copy2clip that gets defined in the beginning then.

Ah my bad I forgot the second part of converting a file to a string. Use this instead, and it’ll work :slight_smile:

with open(TextFilePath) as f:
    text = "".join(f.readlines())

Unfortunately that still doesn’t work. Nothing gets into the clipboard, instead the first line (and only the first line) of the text file is printed in the console.

yeah the default Python clipboard functionality is super buggy. It’s hard to say what precisely is going wrong. Try the second option, the one with pyperclip

First of all, thanks for all your help already :slight_smile:

I’ve tried pyperclip before, installed it via pip install pyperclip, which worked (version 1.8.2), but as soon as I try to import pyperclip in Blender, it just states
ModuleNotFoundError: No module named 'pyperclip'

You have to install Pyperclip in Blender using Blender’s pip. Blender runs on its own Python installation, so pip installs aren’t loaded in Blender. That code I wrote with pyperclip is specifically written for Blender, it will install pyperclip into Blender’s python installation :slight_smile:

for windows:

import subprocess

TextFile='C:\\tmp\\coordinates.txt'

with open(TextFile) as f:
    s = "".join(f.readlines())

subprocess.run("clip", universal_newlines=True, input=s)

Pro tip- "".join(f.readlines()) is significantly faster than concatenating with for line in lines: s+= line :slight_smile:

Also, if you’re using an with open() as f, no need to close(), as the with open() automatically closes the file when it’s done using it

1 Like

Surprised bpy.context.window_manager.clipboard hasn’t been mentioned :slight_smile:

bpy.context.window_manager.clipboard = "Hello World!"
2 Likes

I didn’t know that existed, learned something new today :slight_smile: in that case, the final working code would be:

with open(file) as f: 
    bpy.context.window_manager.clipboard = “”.join(f.readlines())
2 Likes

Thanks a lot! That last command finally worked. So, here’s my final script, which copies the 2D-coordinates of the selected object to the clipboard (using the active camera, render size, frame rate, and frame range) to be pasted as keyframes into a position property in Adobe After Effects:

import bpy
import subprocess
from bpy_extras.object_utils import world_to_camera_view

TextFilePath='C:\\tmp\\coordinates.txt'
TextFile=open(TextFilePath,'w')

scene = bpy.context.scene
cam = bpy.context.scene.camera

render = scene.render
res_x = render.resolution_x
res_y = render.resolution_y
render_first = scene.frame_start
render_last = scene.frame_end
render_fps = scene.render.fps
rnd = lambda i: round(i,1)

TextFile.write("Adobe After Effects 8.0 Keyframe Data\n")
TextFile.write("\tUnits Per Second\t" + str(render_fps) + "\n")
TextFile.write("\tSource Width\t" + str(res_x) + "\n")
TextFile.write("\tSource Height\t" + str(res_y) + "\n")
TextFile.write("\tSource Pixel Aspect Ratio\t1\n")
TextFile.write("\tComp Pixel Aspect Ratio\t1\n")
TextFile.write("\n")
TextFile.write("Transform\tPosition\n")
TextFile.write("\tFrame\tX pixels\tY pixels\tZ pixels\n") 

for j in range(render_first,render_last):   
    scene.frame_set(j)
    obj = bpy.context.active_object
    coords_2d = world_to_camera_view(scene, cam, (obj.location))
    TextFile.write("\t{}\t{}\t{}\t0\n".format(j, rnd(res_x*coords_2d.x), rnd(res_y*(1-coords_2d.y))))

TextFile.write("\n")
TextFile.write("End of Keyframe Data")
TextFile.close()
    
with open(TextFilePath) as f: 
    bpy.context.window_manager.clipboard = "".join(f.readlines())

Thanks for all your help!

3 Likes

That looks like great code! I’m glad you got it working :slight_smile:

1 Like

thanx for the script , really helpful!