├── .gitignore ├── README.md ├── images ├── trippy.gif ├── x_axis.gif ├── xz.gif └── z_axis.gif ├── planes_rotation_x_axis.py ├── planes_rotation_y_axis.py ├── planes_rotation_z_axis.py ├── planes_rotations_xz.py ├── planes_utils.py ├── trippy_tunnel.py └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ## About 6 | Simple examples of Blender animations based on Python scripts. 7 | 8 | ## How to 9 | Open in Blender: `blender --python the_script.py` 10 | 11 | Convert to GIF: `convert -delay 4 -loop 0 *.png animation.gif` 12 | 13 | Make a video: `ffmpeg -framerate 30 -f image2 -i '/tmp/%*.png' -c:v libx264 -profile:v high -crf 16 -pix_fmt yuv420p blender_render.mp4` 14 | 15 | ## Scripts 16 | ##### [trippy_tunnel.py](trippy_tunnel.py) 17 | ![](images/trippy.gif) 18 | 19 | ##### [planes_rotation_x_axis.py](planes_rotation_x_axis.py) 20 | ![](images/x_axis.gif) 21 | 22 | ##### [planes_rotation_z_axis.py](planes_rotation_z_axis.py) 23 | ![](images/z_axis.gif) 24 | 25 | ##### [planes_rotations_xz.py](planes_rotations_xz.py) 26 | ![](images/xz.gif) -------------------------------------------------------------------------------- /images/trippy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benckx/blender-python-examples/c16d72ead208343237b94b01f49f43566ca49511/images/trippy.gif -------------------------------------------------------------------------------- /images/x_axis.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benckx/blender-python-examples/c16d72ead208343237b94b01f49f43566ca49511/images/x_axis.gif -------------------------------------------------------------------------------- /images/xz.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benckx/blender-python-examples/c16d72ead208343237b94b01f49f43566ca49511/images/xz.gif -------------------------------------------------------------------------------- /images/z_axis.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benckx/blender-python-examples/c16d72ead208343237b94b01f49f43566ca49511/images/z_axis.gif -------------------------------------------------------------------------------- /planes_rotation_x_axis.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import bpy 4 | 5 | # load modules for IDE 6 | try: 7 | from utils import * 8 | from planes_utils import * 9 | except: 10 | pass 11 | 12 | # load modules dynamically for Blender 13 | directory = os.path.basename(bpy.data.filepath) 14 | files_names = ['utils.py', 'planes_utils.py'] 15 | 16 | for file_name in files_names: 17 | file_path = os.path.join(directory, file_name) 18 | exec(compile(open(file_path).read(), file_path, 'exec')) 19 | 20 | max_x = 6 21 | max_y = 6 22 | plane_size = 2 23 | 24 | planes = init_basic_planes_scene(max_x, max_y, plane_size) 25 | 26 | # animate 27 | frame_begin = 0 28 | frame_end = 40 29 | bpy.context.scene.frame_end = frame_end 30 | 31 | for x in range(max_x): 32 | for y in range(max_y): 33 | plane = planes[x][y] 34 | add_rotation(plane, 'x', 180, frame_begin, frame_end) 35 | -------------------------------------------------------------------------------- /planes_rotation_y_axis.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import bpy 4 | 5 | # load modules for IDE 6 | try: 7 | from utils import * 8 | from planes_utils import * 9 | except: 10 | pass 11 | 12 | # load modules dynamically for Blender 13 | directory = os.path.basename(bpy.data.filepath) 14 | files_names = ['utils.py', 'planes_utils.py'] 15 | 16 | for file_name in files_names: 17 | file_path = os.path.join(directory, file_name) 18 | exec(compile(open(file_path).read(), file_path, 'exec')) 19 | 20 | max_x = 6 21 | max_y = 6 22 | plane_size = 2 23 | 24 | planes = init_basic_planes_scene(max_x, max_y, plane_size) 25 | 26 | # animate 27 | frame_begin = 0 28 | frame_end = 40 29 | bpy.context.scene.frame_end = frame_end 30 | 31 | for x in range(max_x): 32 | for y in range(max_y): 33 | plane = planes[x][y] 34 | add_rotation(plane, 'y', 180, frame_begin, frame_end) 35 | -------------------------------------------------------------------------------- /planes_rotation_z_axis.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import bpy 4 | 5 | # load modules for IDE 6 | try: 7 | from utils import * 8 | from planes_utils import * 9 | except: 10 | pass 11 | 12 | # load modules dynamically for Blender 13 | directory = os.path.basename(bpy.data.filepath) 14 | files_names = ['utils.py', 'planes_utils.py'] 15 | 16 | for file_name in files_names: 17 | file_path = os.path.join(directory, file_name) 18 | exec(compile(open(file_path).read(), file_path, 'exec')) 19 | 20 | max_x = 6 21 | max_y = 6 22 | plane_size = 2 23 | 24 | planes = init_basic_planes_scene(max_x, max_y, plane_size) 25 | 26 | # animate 27 | frame_begin = 0 28 | frame_end = 20 29 | bpy.context.scene.frame_end = frame_end 30 | 31 | i = 0 32 | for x in range(max_x): 33 | for y in range(max_y): 34 | plane = planes[x][y] 35 | add_rotation(plane, 'z', 90, frame_begin, frame_end) 36 | -------------------------------------------------------------------------------- /planes_rotations_xz.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import bpy 4 | 5 | # load modules for IDE 6 | try: 7 | from utils import * 8 | from planes_utils import * 9 | except: 10 | pass 11 | 12 | # load modules dynamically for Blender 13 | directory = os.path.basename(bpy.data.filepath) 14 | files_names = ['utils.py', 'planes_utils.py'] 15 | 16 | for file_name in files_names: 17 | file_path = os.path.join(directory, file_name) 18 | exec(compile(open(file_path).read(), file_path, 'exec')) 19 | 20 | max_x = 6 21 | max_y = 6 22 | plane_size = 2 23 | 24 | planes = init_basic_planes_scene(max_x, max_y, plane_size) 25 | 26 | # animate 27 | frame_begin = 0 28 | frame_end = 40 29 | bpy.context.scene.frame_end = frame_end 30 | 31 | for x in range(max_x): 32 | for y in range(max_y): 33 | plane = planes[x][y] 34 | add_rotation(plane, 'x', 180, frame_begin, frame_end) 35 | add_rotation(plane, 'z', 180, frame_begin, frame_end) 36 | -------------------------------------------------------------------------------- /planes_utils.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | # load modules for IDE 4 | try: 5 | from utils import * 6 | except: 7 | pass 8 | 9 | 10 | def create_planes(max_x, max_y, plane_size): 11 | # create planes 12 | for x in range(max_x): 13 | for y in range(max_y): 14 | bpy.ops.mesh.primitive_plane_add(location=(x * plane_size, y * plane_size, 0)) 15 | 16 | # 2 dimensional array 17 | planes = [[0 for x in range(max_y)] for y in range(max_x)] 18 | 19 | # put my planes in the array 20 | for obj in bpy.data.objects: 21 | x = int(obj.location.x / plane_size) 22 | y = int(obj.location.y / plane_size) 23 | planes[x][y] = obj 24 | 25 | return planes 26 | 27 | 28 | def init_basic_planes_scene(max_x, max_y, plane_size, cam_distance=None): 29 | clear_scene() 30 | set_render_resolution((max_x - 2) * 100, (max_y - 2) * 100) 31 | 32 | planes = create_planes(max_x, max_y, plane_size) 33 | 34 | material = make_red() 35 | for x in range(max_x): 36 | for y in range(max_y): 37 | plane = planes[x][y] 38 | set_material(plane, material) 39 | 40 | # create camera 41 | cam_x = max_x - plane_size / 2 42 | cam_y = max_y - plane_size / 2 43 | if cam_distance is None: 44 | cam_z = -((max_x + 2) * 2) 45 | else: 46 | cam_z = -cam_distance 47 | 48 | # position the camera so (0, 0) is in the top left corner 49 | cam_rotation = (0, math.radians(180), math.radians(180)) 50 | bpy.ops.object.camera_add(location=(cam_x, cam_y, cam_z), rotation=cam_rotation) 51 | 52 | # one light in the middle of each side of the rectangle, at the same distance 53 | distance_from_planes = 40 54 | locations = [] 55 | locations.append((-distance_from_planes, max_y * plane_size / 2)) 56 | locations.append((max_x * plane_size / 2, -distance_from_planes)) 57 | locations.append((max_x * plane_size + distance_from_planes, max_y * plane_size / 2)) 58 | locations.append((max_x * plane_size / 2, max_y * plane_size + distance_from_planes)) 59 | 60 | light_z = -40 61 | for location in locations: 62 | bpy.ops.object.lamp_add(type='AREA', location=(location[0], location[1], light_z), rotation=cam_rotation) 63 | 64 | return planes 65 | -------------------------------------------------------------------------------- /trippy_tunnel.py: -------------------------------------------------------------------------------- 1 | import math 2 | import os 3 | import random 4 | 5 | import bpy 6 | 7 | # load modules for IDE 8 | try: 9 | from utils import * 10 | from planes_utils import * 11 | except: 12 | pass 13 | 14 | # load modules dynamically for Blender 15 | directory = os.path.basename(bpy.data.filepath) 16 | files_names = ['utils.py', 'planes_utils.py'] 17 | 18 | for file_name in files_names: 19 | file_path = os.path.join(directory, file_name) 20 | exec(compile(open(file_path).read(), file_path, 'exec')) 21 | 22 | # parameters 23 | distance = 10 24 | torus_size = 1 25 | torus_count = 50 26 | frame_length = 10 27 | light_distance = 9 28 | angle_limit_max = 20 29 | angle_limit_min = -1 * angle_limit_max 30 | 31 | 32 | def animate_in_circle(obj, begin, end, radius, direction): 33 | length = end - begin + 1 34 | for i in range(begin, end): 35 | degree = (i - begin) * (360 / length) 36 | if i % 5 == 0: 37 | insert_location_keyframe(obj, i) 38 | insert_rotation_keyframe(obj, i) 39 | 40 | x, y = get_polar_coordinates(radius, math.radians(degree)) 41 | obj.location[0] = x * 1.5 42 | obj.location[2] = direction * y * 1.5 43 | 44 | insert_location_keyframe(obj, i + 10) 45 | insert_rotation_keyframe(obj, i + 10) 46 | 47 | 48 | clear_scene() 49 | 50 | # world settings 51 | W = bpy.context.scene.world 52 | W.horizon_color = (0, 0, 0) 53 | W.light_settings.use_environment_light = True 54 | W.light_settings.environment_energy = 0.1 55 | 56 | # create camera 57 | bpy.ops.object.camera_add(location=(0, -5, 0), rotation=(math.radians(90), 0, 0)) 58 | for obj in get_objects('Camera'): 59 | camera = obj 60 | 61 | # create torus and put them in array 62 | for i in range(0, torus_count): 63 | bpy.ops.mesh.primitive_torus_add(location=(0, 0, 0)) 64 | 65 | tunnel_torus = [] 66 | 67 | i = 0 68 | for obj in get_objects('Torus'): 69 | tunnel_torus.append(obj) 70 | obj.name = 'myTorus_' + str(i) 71 | i += 1 72 | 73 | # create materials 74 | colors = [] 75 | colors.append(make_material('C1', (51 / 256, 145 / 256, 148 / 256), (1, 1, 1), 1)) 76 | colors.append(make_material('C2', (251 / 256, 107 / 256, 65 / 256), (1, 1, 1), 1)) 77 | colors.append(make_material('C3', (246 / 256, 216 / 256, 107 / 256), (1, 1, 1), 1)) 78 | 79 | # rotate torus by 90, change scale and assign color 80 | count = 0 81 | 82 | for torus in tunnel_torus: 83 | torus.rotation_euler[0] = math.radians(90) 84 | torus.scale = (torus_size, torus_size, torus_size * 2) 85 | 86 | set_material(torus, colors[count]) 87 | if count < len(colors) - 1: 88 | count += 1 89 | else: 90 | count = 0 91 | 92 | # place tunnel torus 93 | pos_x = 0 94 | pos_y = 0 95 | pos_z = 0 96 | angle1 = 0 97 | angle2 = 0 98 | sign1 = 1 99 | sign2 = 1 100 | 101 | for torus in tunnel_torus: 102 | delta1 = random.randint(3, 6) * sign1 103 | delta2 = random.randint(3, 6) * sign2 104 | angle1 += delta1 105 | angle2 += delta2 106 | 107 | torus.rotation_euler[1] = math.radians(angle1) 108 | torus.rotation_euler[2] = math.radians(angle2) 109 | 110 | torus.location[0] = pos_x - distance * math.sin(math.radians(delta1)) 111 | torus.location[1] = pos_y + distance * math.cos(math.radians(delta1)) 112 | torus.location[2] = pos_z + torus_size * math.sin(math.radians(delta2)) 113 | 114 | pos_x = torus.location[0] 115 | pos_y = torus.location[1] 116 | pos_z = torus.location[2] 117 | 118 | # change sign randomly 119 | if random.randint(0, 10) == 0: 120 | sign1 *= -1 121 | 122 | if random.randint(0, 10) == 0: 123 | sign2 *= -1 124 | 125 | # sometimes, create a big delta 126 | if random.randint(0, 30) == 0: 127 | delta1 = random.randint(7, 10) * sign1 128 | 129 | if random.randint(0, 30) == 0: 130 | delta1 = random.randint(7, 10) * sign1 131 | 132 | # limit angle variation 133 | if angle1 >= angle_limit_max: 134 | sign1 = -1 135 | if angle1 <= angle_limit_min: 136 | sign1 = 1 137 | 138 | if angle2 >= angle_limit_max: 139 | sign2 = -1 140 | if angle2 <= angle_limit_min: 141 | sign2 = 1 142 | 143 | # bigger wire torus 144 | material = make_material('C4', (255 / 256, 45 / 256, 48 / 256), (1, 1, 1), 1) 145 | material.type = 'HALO' 146 | material.alpha = 0.2 147 | 148 | big_torus_rate = 3 149 | 150 | for i in range(0, int(math.floor(torus_count / big_torus_rate))): 151 | bpy.ops.mesh.primitive_torus_add(location=(0, 0, 0)) 152 | 153 | big_torus = [] 154 | 155 | i = 0 156 | for obj in get_objects('Torus'): 157 | big_torus.append(obj) 158 | obj.name = 'bigTorus_' + str(i) 159 | set_material(obj, material) 160 | i += 1 161 | 162 | count = 0 163 | for torus in big_torus: 164 | model = tunnel_torus[count * big_torus_rate] 165 | torus.scale = (torus_size * 5, torus_size * 5, torus_size * 5) 166 | torus.location = model.location 167 | torus.rotation_euler = model.rotation_euler 168 | count += 1 169 | 170 | # create lights 171 | light_counts = 0 172 | for torus in tunnel_torus: 173 | if light_counts % 3 == 0: 174 | bpy.ops.object.lamp_add(type='POINT', location=(0, torus.location[1], light_distance)) 175 | 176 | light_counts += 1 177 | 178 | # place lights in array and change settings 179 | lights = [] 180 | for obj in get_objects('Point'): 181 | lights.append(obj) 182 | obj.data.energy = 15 183 | obj.data.shadow_method = 'RAY_SHADOW' 184 | 185 | # init camera animation 186 | insert_location_keyframe(camera, 0) 187 | insert_rotation_keyframe(camera, 0) 188 | 189 | bpy.context.scene.frame_current = 0 190 | bpy.context.scene.frame_end = frame_length * torus_count 191 | 192 | # animate camera (synchronize position and rotation with tunnel) 193 | frame_count = 0 194 | count = 2 195 | for torus in tunnel_torus: 196 | camera.location = torus.location 197 | camera.rotation_euler = torus.rotation_euler 198 | 199 | insert_location_keyframe(camera, frame_count) 200 | insert_rotation_keyframe(camera, frame_count) 201 | 202 | frame_count += frame_length 203 | 204 | # animate torus scale 205 | shift = 2 206 | frame_count = 0 207 | final_scale = torus_size * 2.5 208 | for i in range(shift, len(tunnel_torus)): 209 | insert_scale_keyframe(tunnel_torus[i], frame_count) 210 | 211 | tunnel_torus[i].scale = (final_scale, final_scale, final_scale) 212 | 213 | # they scale up on their different axis at different time, this creates some kind of "rubbery" effect 214 | for idx in [0, 1, 2]: 215 | frame = frame_count + frame_length * random.randint(1, 4) / 3 216 | insert_scale_keyframe(tunnel_torus[i], frame, indexes=[idx]) 217 | 218 | frame_count += frame_length 219 | 220 | # animate big torus rotation 221 | sign = 1 222 | frame_count = 0 223 | for torus in big_torus: 224 | sign = sign * -1 225 | frame_count = 0 226 | while frame_count <= frame_length * torus_count: 227 | insert_rotation_keyframe(torus, frame_count, indexes=[1]) 228 | torus.rotation_euler[1] += sign * math.radians(15) 229 | insert_rotation_keyframe(torus, frame_count + frame_length, indexes=[1]) 230 | frame_count += frame_length 231 | 232 | # animate lights 233 | nbr_tours = 15 234 | tour_length = (frame_length * torus_count) / nbr_tours 235 | count = 0 236 | 237 | for light in lights: 238 | count += 1 239 | if count % 2 == 0: 240 | direction = 1 241 | else: 242 | direction = -1 243 | 244 | for i in range(nbr_tours): 245 | begin_anim = i * int(tour_length) 246 | end_anim = (i + 1) * int(tour_length) 247 | 248 | animate_in_circle(light, begin_anim, end_anim, light_distance, direction) 249 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | import bpy 4 | 5 | 6 | def set_render_resolution(x, y): 7 | bpy.data.scenes[0].render.resolution_x = x * 2 8 | bpy.data.scenes[0].render.resolution_y = y * 2 9 | 10 | 11 | # found on http://wiki.blender.org/index.php/Dev:2.5/Py/Scripts/Cookbook/Code_snippets/Materials_and_textures 12 | def make_material(name, diffuse, specular, alpha): 13 | mat = bpy.data.materials.new(name) 14 | mat.diffuse_color = diffuse 15 | mat.diffuse_shader = 'LAMBERT' 16 | mat.diffuse_intensity = 1.0 17 | mat.specular_color = specular 18 | mat.specular_shader = 'COOKTORR' 19 | mat.specular_intensity = 0.5 20 | mat.alpha = alpha 21 | mat.ambient = 1 22 | return mat 23 | 24 | 25 | def make_red(): 26 | return make_material('Red', (1, 0, 0), (1, 1, 1), 1) 27 | 28 | 29 | def make_blue(): 30 | return make_material('BlueSemi', (0, 0, 1), (0.5, 0.5, 0), 0.5) 31 | 32 | 33 | def set_material(obj, mat): 34 | if obj.data.materials: 35 | # assign to 1st material slot 36 | obj.data.materials[0] = mat 37 | else: 38 | # no slots 39 | obj.data.materials.append(mat) 40 | 41 | 42 | def get_objects(prefix): 43 | result = [] 44 | for o in bpy.data.objects: 45 | if o.name.startswith(prefix): 46 | result.append(o) 47 | 48 | return result 49 | 50 | 51 | # clear all existing objects (Cube, Lamp, Camera) 52 | def clear_scene(): 53 | bpy.ops.object.select_all(action='DESELECT') 54 | for obj in bpy.context.scene.objects: 55 | obj.select = True 56 | 57 | bpy.ops.object.delete() 58 | 59 | 60 | def _insert_keyframe(obj, data_path, frame, indexes): 61 | if indexes is None: 62 | indexes = [0, 1, 2] 63 | 64 | for i in indexes: 65 | obj.keyframe_insert(data_path, index=i, frame=frame) 66 | 67 | 68 | def insert_location_keyframe(obj, frame, indexes=None): 69 | _insert_keyframe(obj, 'location', frame, indexes) 70 | 71 | 72 | def insert_rotation_keyframe(obj, frame, indexes=None): 73 | _insert_keyframe(obj, 'rotation_euler', frame, indexes) 74 | 75 | 76 | def insert_scale_keyframe(obj, frame, indexes=None): 77 | _insert_keyframe(obj, 'scale', frame, indexes) 78 | 79 | 80 | def add_rotation(obj, axis, angle, frame_begin, frame_end): 81 | axis_int = ['x', 'y', 'z'].index(axis) 82 | obj.keyframe_insert('rotation_euler', index=axis_int, frame=frame_begin) 83 | obj.rotation_euler[axis_int] = math.radians(angle) 84 | obj.keyframe_insert('rotation_euler', index=axis_int, frame=frame_end) 85 | 86 | 87 | def get_polar_coordinates(radius, angle): 88 | return radius * math.cos(angle), radius * math.sin(angle) 89 | --------------------------------------------------------------------------------