import random

import time

import math

import bpy

import mathutils

def purge_orphans():

if bpy.app.version >= (3, 0, 0):

bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)

else:

result = bpy.ops.outliner.orphans_purge()

if result.pop() != "CANCELLED":

purge_orphans()

def clean_scene():

planation with example

if bpy.context.active_object and bpy.context.active_object.mode == "EDIT":

bpy.ops.object.editmode_toggle()

for obj in bpy.data.objects:

obj.hide_set(False)

obj.hide_select = False

obj.hide_viewport = False

bpy.ops.object.select_all(action="SELECT")

bpy.ops.object.delete()

collection_names = [col.name for col in bpy.data.collections]

for name in collection_names:

bpy.data.collections.remove(bpy.data.collections[name])

world_names = [world.name for world in bpy.data.worlds]

for name in world_names:

bpy.data.worlds.remove(bpy.data.worlds[name])

bpy.ops.world.new()

bpy.context.scene.world = bpy.data.worlds["World"]

purge_orphans()

def active_object():

return bpy.context.active_object

def time_seed():

seed = time.time()

print(f"seed: {seed}")

random.seed(seed)

bpy.context.window_manager.clipboard = str(seed)

return seed

def add_ctrl_empty(name=None):

bpy.ops.object.empty_add(type="PLAIN_AXES", align="WORLD")

empty_ctrl = active_object()

if name:

empty_ctrl.name = name

else:

empty_ctrl.name = "empty.cntrl"

return empty_ctrl

def make_active(obj):

bpy.ops.object.select_all(action="DESELECT")

obj.select_set(True)

bpy.context.view_layer.objects.active = obj

def track_empty(obj):

empty = add_ctrl_empty(name=f"empty.tracker-target.{obj.name}")

make_active(obj)

bpy.ops.object.constraint_add(type="TRACK_TO")

bpy.context.object.constraints["Track To"].target = empty

return empty

def setup_camera(loc, rot, frame_count):

bpy.ops.object.camera_add(location=loc, rotation=rot)

camera = active_object()

bpy.context.scene.camera = camera

camera.data.lens = 70

camera.data.passepartout_alpha = 0.9

empty = track_empty(camera)

camera.data.dof.use_dof = True

camera.data.dof.focus_object = empty

camera.data.dof.aperture_fstop = 0.35

start_value = camera.data.lens

mid_value = camera.data.lens - 10

loop_param(camera.data, "lens", start_value, mid_value, frame_count)

return empty

def set_1080px_square_render_res():

bpy.context.scene.render.resolution_x = 1080

bpy.context.scene.render.resolution_y = 1080

def set_scene_props(fps, loop_seconds):

frame_count = fps * loop_seconds

scene = bpy.context.scene

scene.frame_end = frame_count

world = bpy.data.worlds["World"]

if "Background" in world.node_tree.nodes:

world.node_tree.nodes["Background"].inputs[0].default_value = (0, 0, 0, 1)

scene.render.fps = fps

scene.frame_current = 1

scene.frame_start = 1

scene.eevee.use_bloom = True

scene.eevee.bloom_intensity = 0.005

scene.eevee.use_gtao = True

scene.eevee.gtao_distance = 4

scene.eevee.gtao_factor = 5

scene.eevee.taa_render_samples = 64

if bpy.app.version < (4, 0, 0):

scene.view_settings.look = "Very High Contrast"

else:

scene.view_settings.look = "AgX - Very High Contrast"

set_1080px_square_render_res()

def setup_scene(i=0):

fps = 30

loop_seconds = 10

frame_count = fps * loop_seconds

project_name = "stack_spin"

bpy.context.scene.render.image_settings.file_format = "FFMPEG"

bpy.context.scene.render.ffmpeg.format = "MPEG4"

bpy.context.scene.render.filepath = f"/tmp/project_{project_name}/loop_{i}.mp4"

seed = 0

if seed:

random.seed(seed)

else:

time_seed()

clean_scene()

set_scene_props(fps, loop_seconds)

loc = (0, 0, 7)

rot = (0, 0, 0)

setup_camera(loc, rot, frame_count)

context = {

"frame_count": frame_count,

}

return context

def make_fcurves_linear():

for fc in bpy.context.active_object.animation_data.action.fcurves:

fc.extrapolation = "LINEAR"

def get_random_color():

return random.choice(

[

[0.92578125, 1, 0.0, 1],

[0.203125, 0.19140625, 0.28125, 1],

[0.8359375, 0.92578125, 0.08984375, 1],

[0.16796875, 0.6796875, 0.3984375, 1],

[0.6875, 0.71875, 0.703125, 1],

[0.9609375, 0.9140625, 0.48046875, 1],

[0.79296875, 0.8046875, 0.56640625, 1],

[0.96484375, 0.8046875, 0.83984375, 1],

[0.91015625, 0.359375, 0.125, 1],

[0.984375, 0.4609375, 0.4140625, 1],

[0.0625, 0.09375, 0.125, 1],

[0.2578125, 0.9140625, 0.86328125, 1],

[0.97265625, 0.21875, 0.1328125, 1],

[0.87109375, 0.39453125, 0.53515625, 1],

[0.8359375, 0.92578125, 0.08984375, 1],

[0.37109375, 0.29296875, 0.54296875, 1],

[0.984375, 0.4609375, 0.4140625, 1],

[0.92578125, 0.16796875, 0.19921875, 1],

[0.9375, 0.9609375, 0.96484375, 1],

[0.3359375, 0.45703125, 0.4453125, 1],

]

)

def render_loop():

bpy.ops.render.render(animation=True)

def apply_emission_material(obj):

mat = bpy.data.materials.new(name="Material")

mat.use_nodes = True

mat.node_tree.nodes["Principled BSDF"].inputs[27].default_value = 0

mat.node_tree.nodes["Principled BSDF"].inputs[2].default_value = 0.25

mat.node_tree.nodes["Principled BSDF"].inputs[1].default_value = 1

obj.data.materials.append(mat)

def add_lights():

rotation = (math.radians(60), 0.0, math.radians(180))

bpy.ops.object.light_add(type="SUN", rotation=rotation)

bpy.context.object.data.energy = 6

bpy.context.object.data.diffuse_factor = 0.05

bpy.context.object.data.angle = math.radians(60)

def loop_param(obj, param_name, start_value, mid_value, frame_count):

frame = 1

setattr(obj, param_name, start_value)

obj.keyframe_insert(param_name, frame=frame)

frame = frame_count / 2

setattr(obj, param_name, mid_value)

obj.keyframe_insert(param_name, frame=frame)

frame = frame_count

setattr(obj, param_name, start_value)

obj.keyframe_insert(param_name, frame=frame)

def set_keyframe_to_ease_in_out(obj):

for fcurve in obj.animation_data.action.fcurves:

for kf in fcurve.keyframe_points:

kf.interpolation = "BACK"

kf.easing = "EASE_IN_OUT"

def create_shape(vertices, radius, rotation, location):

bpy.ops.mesh.primitive_cylinder_add(vertices=vertices, radius=radius, depth=0.1)

obj = active_object()

obj.location = location

obj.rotation_euler = rotation

bpy.ops.object.modifier_add(type='WIREFRAME')

bpy.context.object.modifiers["Wireframe"].thickness = 0.1

bpy.context.object.modifiers["Wireframe"].use_relative_offset = True

bpy.ops.object.modifier_add(type='SUBSURF')

bpy.context.object.modifiers["Subdivision"].levels = 2

bpy.context.object.modifiers["Subdivision"].subdivision_type = 'SIMPLE'

bpy.ops.object.modifier_add(type='SUBSURF')

bpy.context.object.modifiers["Subdivision.001"].levels = 2

bpy.context.object.modifiers["Subdivision.001"].subdivision_type = 'CATMULL_CLARK'

bpy.ops.object.shade_smooth()

apply_emission_material(obj)

return obj

def gen_centerpiece(context):

vertices = 6

radius = 1

radius_step = 0.15

shape_count = 30

z_location_step = -0.15

current_location = mathutils.Vector((0,0,0))

z_rotation_step = math.radians(5)

current_rotation = mathutils.Euler((0,0,0))

start_frame_step = 5

end_frame = context["frame_count"] - 10

for i in range(shape_count):

start_frame = start_frame_step * i

current_location.z = z_location_step * i

current_rotation.z = z_rotation_step * i

radius = radius + radius_step

shape_obj = create_shape(vertices, radius, current_rotation, current_location)

shape_obj.keyframe_insert("rotation_euler", frame=start_frame)

one_turn = 360 / vertices

shape_obj.rotation_euler.z += math.radians(one_turn*2)

shape_obj.keyframe_insert("rotation_euler", frame=end_frame)

set_keyframe_to_ease_in_out(shape_obj)

shape_obj.scale = (1-0.060*i, 1-0.020*i, 1-0.020*i) # Initial scale

shape_obj.keyframe_insert(data_path="scale", frame=1)

shape_obj.scale = (1, 1, 1) # Final scale

shape_obj.keyframe_insert(data_path="scale", frame=bpy.context.scene.frame_end)

def main():

context = setup_scene()

gen_centerpiece(context)

add_lights()

if _name_ == "_main_":

main()