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:
- 00:00 - 04:00 Intro and Overview
- 04:00 - 07:30 Defining the key variables
- 07:30 - 12:00 Body of the script
- 12:00 - 16:30 Using the console to test parts of your script
- 16:30 - 20:50 The FOR loop
- 20:50 - 28:30 Replicating the animation data
- 28:30 - 32:30 Delta location
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.