blender, python, らせん階段

らせん状にキューブを並べて照明当ててカメラ回そう、という思いつきで、作りました。
全体で124行あって、そのうちキューブを並べるのは20行くらい。照明部分とカメラ部分は他で作ったものの再利用です。もっと階段に見えるように改良が必要な感じがします。
動画1秒はtwitterで[この動画](https://mobile.twitter.com/naohiko7/status/1327607226660397056)です。
![bph38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690216/3a0f6546-6275-3aaf-2aba-2c1e9c485cc0.png)



```python3
# nh38 ==5階建ラセン。2020.11.14
import bpy
import math
# ========= DELETE ALL mesh, light, camera, みな削除する2行 =========
for item in bpy.data.objects:
	bpy.data.objects.remove(item)
#==================らせん状にキューブを並べる約20行
s_per_floor = 36 #s_per_floor: number of steps in each floor
#floors = 3 # floors==== number of floors (stories) 
floors = 5 # floors==== number of floors (stories)
floorheight = 3  # floorheight : height of each floor (meter)
xc = 2 # center x of double helix
yc = 2 # center y of double helix
zc = 0 # center z
radius1 = 5 

stepheight = floorheight / s_per_floor
totalsteps = floors * s_per_floor
for i in range(totalsteps):
    x1 = math.cos(6.28 * i / s_per_floor )*radius1 + xc
    y1 = math.sin(6.28 * i / s_per_floor )*radius1 + yc
    z1 = i *  stepheight +zc
    bpy.ops.mesh.primitive_cube_add(size=1, enter_editmode=False, align='WORLD', location=(x1, y1, z1), scale=(1, 1, 1), rotation=(0, 0, (6.28 * i / s_per_floor )))
#===================================

# ============== "light" spot1, spot2, spot3
# create light datablock, set attributes
#light_data = bpy.data.lights.new(name="light_spot1", type='SPOT')
light_data = bpy.data.lights.new(name="light_spot1", type='SUN')
light_data.energy = 5
# create new object with our light datablock
light_object1 = bpy.data.objects.new(name="light_spot1", object_data=light_data)
# link light object
bpy.context.collection.objects.link(light_object1)
# make it active 
bpy.context.view_layer.objects.active = light_object1
#change location
light_object1.location = (5, -10, 50)
light_object1.delta_rotation_euler = (1.3, 0, 0.3) #ゼロゼロゼロで真下を向く。
# update scene, if needed
dg = bpy.context.evaluated_depsgraph_get() 
dg.update()

light_data = bpy.data.lights.new(name="light_spot2", type='SPOT')
light_data.energy = 20000
# create new object with our light datablock
light_object2 = bpy.data.objects.new(name="light_spot2", object_data=light_data)
# link light object
bpy.context.collection.objects.link(light_object2)
# make it active 
bpy.context.view_layer.objects.active = light_object2
#change location
light_object2.location = (10, 10, 20)
light_object2.delta_rotation_euler = (0.1, 0, 0.3) #ゼロゼロゼロで真下を向く。
# update scene, if needed
dg = bpy.context.evaluated_depsgraph_get() 
dg.update()

light_data = bpy.data.lights.new(name="light_spot3", type='SPOT')
light_data.energy = 20000
# create new object with our light datablock
light_object3 = bpy.data.objects.new(name="light_spot3", object_data=light_data)
# link light object
bpy.context.collection.objects.link(light_object3)
# make it active 
bpy.context.view_layer.objects.active = light_object3
#change location
light_object3.location = (-5, 5, 20)
light_object3.delta_rotation_euler = (0.1, 0, 0.3) #ゼロゼロゼロで真下を向く。
# update scene, if needed
dg = bpy.context.evaluated_depsgraph_get() 
dg.update()
# ================ end of light

#  ================== = camera movement START
bpy.ops.curve.primitive_bezier_circle_add(enter_editmode=False, align='WORLD', location=(5, 5, 25))
bpy.context.object.scale[0] = 30
bpy.context.object.scale[1] = 30
bpy.ops.object.empty_add(type='CUBE', align='WORLD', location=(0,0,0))
bpy.ops.object.camera_add(enter_editmode=False, align='VIEW', location=(0,0,0), rotation=(0, 0, 0))

bpy.data.objects['Empty'].select_set(True)
bpy.data.objects['Camera'].select_set(True)

bpy.context.view_layer.objects.active = bpy.data.objects['Empty']
bpy.ops.object.parent_set(type='OBJECT')

bpy.data.objects['Camera'].select_set(False)
bpy.data.objects['Empty'].select_set(True)

bpy.ops.object.constraint_add(type='FOLLOW_PATH')
bpy.context.object.constraints["Follow Path"].target = bpy.data.objects["BezierCircle"]

bpy.context.object.constraints["Follow Path"].use_curve_follow = True
bpy.context.object.constraints["Follow Path"].use_fixed_location = True

bpy.data.objects['Empty'].select_set(False)
bpy.data.objects['Camera'].select_set(True)

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

#bpy.context.object.constraints["Track To"].target = bpy.data.objects["Cube.016"]
bpy.context.object.constraints["Track To"].target = bpy.data.objects["Cube.052"]
bpy.context.object.constraints["Track To"].up_axis = 'UP_Y'
bpy.context.object.constraints["Track To"].track_axis = 'TRACK_NEGATIVE_Z'  #5m00sec
#Camera Keyframe #(Insert keyframe to object's Offset Factor Python API - stack exchange)
bpy.data.objects['Camera'].select_set(False)
bpy.data.objects['Empty'].select_set(True)
bpy.context.scene.frame_current = 1
bpy.context.object.constraints["Follow Path"].offset_factor = 0
ob = bpy.context.object
# ob.constraints['Follow Path']
# bpy.data.objects['Empty'].constraints["Follow Path"]
# [bpy.data.objects['Empty'].constraints["Follow Path"]]
con = ob.constraints.get("Follow Path")
con.offset_factor = 0.0
con.keyframe_insert("offset_factor", frame=1)
con.offset_factor = 0.25
con.keyframe_insert("offset_factor", frame=8)
con.offset_factor = 0.50
con.keyframe_insert("offset_factor", frame=16)
con.offset_factor = 0.75
con.keyframe_insert("offset_factor", frame=23)
con.offset_factor = 0.99
con.keyframe_insert("offset_factor", frame=30)
# ==== END of camera movement
```