Python scripts let you automate processes that would take forever if you had to do them by hand.

Have you ever done the same thing over and over again, thinking: There's got to be a better way. Well good news, there is. With a little basic understanding of Python you can make Blender do a lot of things for you. So whenever you find yourself doing the same thing over and over, consider just writing a script for it. Here's how:

Download the training Blendfile including the finished script here!

The script is also available as an addon with GUI-elements here... (Rightclick -> Save As)

The entire scripts with written explanation (be aware that you need to set the identation correctly if you copy it into Blender's text editor!):

import bpy

loops = 20
distanceX = 1.083
distanceY = 0
distanceZ = 0
offset = 10

for i in range(loops):
bpy.ops.object.duplicate_move(OBJECT_OT_duplicate = {"linked":False})
	obj = bpy.context.active_object
	obj.delta_location[0] += distanceX
	obj.delta_location[1] += distanceY
	obj.delta_location[2] += distanceZ
	animData = obj.animation_data
	action = animData.action
	fcurves = action.fcurves
	for curve in fcurves:
		keyframePoints = curve.keyframe_points
		for keyframe in keyframePoints:
			keyframe.co[0] += offset
			keyframe.handle_left[0] += offset
			keyframe.handle_right[0] += offset

#The lines explained:

import bpy

#We need to import the Blender Python Library in order to make Python understand how to handle Blender objects.

loops = 20
distanceX = 1.083
distanceY = 0
distanceZ = 0
offset = 10

#Is a list of variables that you might want to alter when you are using the script for different tasks. 
#That is why I am storing them at the beginning instead of just putting the numbers in the according lines.
for i in range(loops): #calls a for loop, i is an arbitrary name of the ?counter? of this loop. #Range indicates that i is going to be an integer which in the first loop had the value 0 #and in the last loop it will have the value loops-1. bpy.ops.object.duplicate_move(OBJECT_OT_duplicate = {"linked":False}) #This is the viewport action for duplicating an object. #It is exactly what happens if you press SHIFT+D with the mouse over the viewport. obj = bpy.context.active_object #Defines a variable obj that stores the active object. #Note that the active object is the lastes duplicate, not the object that was active when we pressed ?run script?. obj.delta_location[0] += distanceX obj.delta_location[1] += distanceY obj.delta_location[2] += distanceZ #This alters the delta location of the latest object. #The delta location (or rotation) is added to the current location of the object. #This provides the great advantage that you can use location keyframes and the object will still be offset in space, #because the value is added to the values that the location keyframes hold. Not that the [0] indicates delta_location is an array, #and the [0] stands for its X-value, the [1] for its Y and of course the [2] for the Z-value of the location. animData = obj.animation_data action = animData.action fcurves = action.fcurves #This defines 3 new variables that make it easier to access properties of the animation of your object, #making the followinger a bit easier. for curve in fcurves: #This again calls a for loop, but this time it will not count up an integer, #but rather run this loop for every object stored in the fcurves of the object. #So in this case the variable curve will not simply hold a number, but all information the fcurve has. #Meaning, all its keyframes and bezier handles. for keyframe in keyframePoints: #For every loop of the curves loop this will call as many loops as there are keyframe points (points and handles) keyframe.co[0] += offset #This adds however many frames we defined in the variable offset above. #Again this is an array and the [0] indicates, we are altering the X value of the keyframe, ergo its time parameter. #[1] would alter its value. keyframe.handle_left[0] += offset keyframe.handle_right[0] += offset #Does the same thing to the bezier handles. #So in short: Thes script will duplicate the active object, transform the position of the copy #and move its keyframes in time by a number you specify.

#EDIT: if you want a random offset of your keyframes, rather than a static one, or want a random number of frames added or subtracted
#from the keyframe distance:

import random
#at the top of the script
rand = (0.5 -
random.random()) * 10 #creates a random number between -5 and +5, since random.random creates a random number between 0 and 1
newOffset = offset + rand #before animData = obj.animation_data
#and the replace the remaining "offsets" by "newOffset"


Thanks for the idea, Mitzkus.