Blender 2.9, Python ビル、自動車、動画

ゆっくり進めたBlender動画制作ですが2ヶ月半でひとまず動画
[「ひらの眼科の駐車場案内の動画」 ](https://twitter.com/hiranoeye/status/1321265260888686595)
ができました。ここで一段落。
ほぼ全部をpythonで記述したい狙いはあったけど、最後にブックオフの看板あたりは手作業でテクスチャー貼り付けしました。4秒間の動画に2ヶ月半もかかったのはシロウトの効率の悪さですが、それはともかくとして「2ヶ月半、途中でやめなかった」達成感もあります。続けられた理由は

+ 製作途中を少しずつtwitterやQiitaで投稿したこと。未完成でも発信することで自分を前向きにできた。
+ 職場、自宅で短時間ずつ作業するよう保存場所はクラウド(dropbox)に置いたこと。
+ 英語日本語の両方でpythonスクリプト用例をたくさん調べたこと。

製作途中を発表するかどうかは好き嫌いもあるでしょうが僕は途中である程度人に見せる場を作った方がうまく進む体質のようです。日本語python資料もたくさん揃った2020年のネット環境ですが、「blender 2.7ではなく2.8, しかもpython」の条件で集める資料は文章、動画とも英語日本語両方で探さないとなかなか出てきません。
以下に書いたのがpythonスクリプト全体ですが、動画を再現するにはスクリプトに加えて画像テクスチャー2枚（看板の画像）が必要です。
![14.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690216/ec3d347e-4290-2454-96e5-742cfd58857b.jpeg)
![21.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690216/eb1e6604-b4b2-80d0-6be5-b695bb0b90b5.jpeg)
![34.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690216/9e84f7d3-0b07-deec-c60b-fecbaa772460.jpeg)
![51.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690216/c1925fb1-f2e6-d538-aaf5-782698b3ab5e.jpeg)



```python3
#bpy_nh36,  車体、地図建物。階段, 看板2枚　2020.10.28作業
import bpy
import bmesh
import math
from math import radians
from mathutils import Matrix

#========= DELETE ALL mesh, light, camera, みな削除する2行 =========
for item in bpy.data.objects:
    bpy.data.objects.remove(item)
  
def hex_to_rgb( hex_value ): #=== hex color code to RGB
    b = (hex_value & 0xFF) / 255.0
    g = ((hex_value >> 8) & 0xFF) / 255.0
    r = ((hex_value >> 16) & 0xFF) / 255.0
    return r, g, b

def add_material(obj, material_name, h):  #=== material to cube 
    material = bpy.data.materials.get(material_name)
    if material is None:
        material = bpy.data.materials.new(material_name)
    material.use_nodes = True
    principled_bsdf = material.node_tree.nodes['Principled BSDF']
    if principled_bsdf is not None:
        principled_bsdf.inputs[0].default_value = (*hex_to_rgb(h), 1)  
    obj.active_material = material
# =================== definition , create apartment #FThick=1.5 meter (3 meter per floor )
xy_bump = 1.05 #  building wide and narrow cube xy_bump ( 1 wide cube plus 1 narrow cube makes one floor)
def create_apartment(x_origin, y_origin, z_rot, NumOfStories, xScale, yScale, FThick, odd_color, even_color):
    for i in range (0, 2*NumOfStories, 2):
        bpy.ops.mesh.primitive_cube_add(size=2, enter_editmode=False, align='WORLD', location=(x_origin, y_origin, i * FThick), rotation=(0.0, 0.0, z_rot), scale=(xScale, yScale, FThick))
        obj = bpy.context.object
#        add_material( obj, "color01", odd_color )  
        add_material( obj, "", odd_color )  
        bpy.ops.mesh.primitive_cube_add(size=2, enter_editmode=False, align='WORLD', location=(x_origin, y_origin, FThick + i * FThick), rotation=(0.0, 0.0, z_rot), scale=(xScale*xy_bump, yScale*xy_bump, FThick))
        obj = bpy.context.object
#        add_material( obj, "color02", even_color ) 
        add_material( obj, "", even_color )    
  
# ======== create now z_rot:rotation,  NumOfStories:floors, 
# create_apartment(x_origin, y_origin, z_rot, NumOfStories, xScale, yScale, FThick, odd_color, even_color):
create_apartment(-21, -27, 1/180*3.14, 5, 15, 25, 1.5, 0x2F2F2F, 0xffe4b5) #ryowa #DARKSLATEGRAY#2F4F4F
create_apartment(-31, 4, -30/180*3.14, 15, 15, 18, 1.5, 0x2F2F2F, 0xffe4b5)  #HBF #DARKSLATEGRAY#2F4F4F
create_apartment(-25, 20, -30/180*3.14, 8, 18, 15, 1.5, 0xffe4b5, 0x222222) #apt5 bldg
create_apartment(-23, 40, 0/180*3.14, 2, 15, 30, 1.2, 0xeeeeee, 0x6495ed)  #boff #MAROON#800000 ,#DARKGRAY#A9A9A9  ##lightskyblue#87cefa #cornflowerblue#6495ed

create_apartment(-20, 68, 0/180*3.14, 4, 9, 9, 1.5, 0x2F2F2F, 0xffe4b5) #cosmos -20, 67, 10m 10m
create_apartment(14, 70, 0/180*3.14, 4, 22, 11, 1.5, 0x2F2F2F, 0xffe4b5) #seikei  x14,y70, 22m 11m
create_apartment(-50, 53, 0/180*3.14, 10, 10, 10, 1.5, 0x2F2F2F, 0xffe4b5) #lumiere_k1 -49,53,  scale 6,6
create_apartment(-60, 55, 0/180*3.14, 10, 12, 12, 1.5, 0x2F2F2F, 0xffe4b5) #lumiere_k2 -49,53,  scale 6,6
# ====== create kanbans ...  2 planes
bpy.ops.mesh.primitive_plane_add(size=4, enter_editmode=False, align='WORLD', location=(-15, 48, 6.25), rotation=(3.14/2, 0, 3.14/2), scale=(1,1,1))
bpy.context.active_object.name = 'kanban_book'
bpy.ops.mesh.primitive_plane_add(size=4, enter_editmode=False, align='WORLD', location=(-15, 13, 3.5), rotation=(3.14/2, 0, 3.14*0.4), scale=(1,1,1))
bpy.ops.transform.resize(value=(1, 3, 0.7)) 
bpy.context.active_object.name = 'kanban_higa'

#==================== beginning of STAIRS . skew rectangle
pi = math.pi
# URL https://tamaki-py.hatenablog.com/entry/2019/06/03/223545
#'''オブジェクトモードから始めなければなりません. scene が empty（空）であるときスクリプトは動きません.'''
## オブジェクトモードに変更.
#bpy.ops.object.mode_set(mode='OBJECT')
## 全てのオブジェクトを選択.
#bpy.ops.object.select_all(action='SELECT')
## 選択された全てのオブジェクトを削除.
#bpy.ops.object.delete()
#'''立方体を作成し, 編集モードに切り替え.'''
# 立方体を配置.
#######(-49,60, 0)
# === stairs east face
bpy.ops.mesh.primitive_cube_add(size=1, location=(-51, 61, 0)) ##lumiere_koi_bldg stairs
bpy.ops.transform.resize(value=(0.1, 4, 1.4))

# 編集モードに切り替え.
bpy.ops.object.mode_set(mode='EDIT')
# 全てのメッシュを非選択状態にする.
bpy.ops.mesh.select_all(action='DESELECT')
'''面モードにし, 一つの面を回転させる.'''
# 面モードに設定.
bpy.ops.mesh.select_mode(type="FACE")
# bmesh オブジェクトのインスタンス化.
b_mesh = bmesh.from_edit_mesh(bpy.context.object.data)
b_mesh.faces.ensure_lookup_table()
b_mesh.faces[3].select = True # ===  face number (3) south face 
bpy.ops.transform.translate(value=(0, 0, 3)) #=== move a face upward
bpy.ops.object.mode_set(mode='OBJECT')

for i in range(8):
    bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"linked":False, "mode":'TRANSLATION'}, TRANSFORM_OT_translate={"value":(0, 0, 3), "orient_type":'GLOBAL', "orient_matrix":((1, 0, 0), (0, 1, 0), (0, 0, 1)), "orient_matrix_type":'GLOBAL', "constraint_axis":(False, False, False), })
# === lumiere_koi_bldg stairs west face
bpy.ops.mesh.primitive_cube_add(size=1, location=(-53, 61, 0)) ##lumiere_koi_bldg stairs
bpy.ops.transform.resize(value=(0.1, 4, 1.4))

# 編集モードに切り替え.
bpy.ops.object.mode_set(mode='EDIT')
# 全てのメッシュを非選択状態にする.
bpy.ops.mesh.select_all(action='DESELECT')
'''面モードにし, 一つの面を回転させる.'''
# 面モードに設定.
bpy.ops.mesh.select_mode(type="FACE")
# bmesh オブジェクトのインスタンス化.
b_mesh = bmesh.from_edit_mesh(bpy.context.object.data)
b_mesh.faces.ensure_lookup_table()
b_mesh.faces[1].select = True # ===  face number (3) south face 
bpy.ops.transform.translate(value=(0, 0, 3)) #=== move a face upward
bpy.ops.object.mode_set(mode='OBJECT')
for i in range(8):
    bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"linked":False, "mode":'TRANSLATION'}, TRANSFORM_OT_translate={"value":(0, 0, 3), "orient_type":'GLOBAL', "orient_matrix":((1, 0, 0), (0, 1, 0), (0, 0, 1)), "orient_matrix_type":'GLOBAL', "constraint_axis":(False, False, False), })
# === lumiere_koi_bldg stairs north rectrangle face 
bpy.ops.mesh.primitive_cube_add(size=1, location=(-52, 63, 0)) ##lumiere_koi_bldg stairs
bpy.ops.transform.resize(value=(2,0.1,1.4)) #(0.1, 4, 1.4))
for i in range(8):
    bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"linked":False, "mode":'TRANSLATION'}, TRANSFORM_OT_translate={"value":(0, 0, 3), "orient_type":'GLOBAL', "orient_matrix":((1, 0, 0), (0, 1, 0), (0, 0, 1)), "orient_matrix_type":'GLOBAL', "constraint_axis":(False, False, False), })
#============================ end of STAIRS 

## === hodoukyo horizontal part
bpy.ops.mesh.primitive_cube_add(size=1, location=(-52, 67, 2.5))
bpy.ops.transform.resize(value=(2, 8, 1))
## === hodoukyo "v-shape" arch over the bridge
vlen = 12
vb_height =5 #apex height
ct_x = -52 # center x 
ct_y =  67 # center y
for i in range(-vlen, (vlen +1) ):  #（  (-3, 3)と書けばマイナス側3個原点に1個、プラス側2個　合計6個）
    x= i/2
    y= i/2
    z= vb_height - abs(i) / 8
    bpy.ops.mesh.primitive_cube_add(size=0.3, location=(ct_x + x*0.3, ct_y + y*0.3, z))
    bpy.ops.mesh.primitive_cube_add(size=0.3, location=(ct_x + x*0.3, ct_y -y*0.3, z))
    bpy.ops.mesh.primitive_cube_add(size=0.3, location=(ct_x + x*0.3, ct_y + -vlen*0.3/2, 3.5))
    bpy.ops.mesh.primitive_cube_add(size=0.3, location=(ct_x + x*0.3, ct_y + vlen*0.3/2, 3.5))
## === hodoukyo stairs part
bpy.ops.mesh.primitive_cube_add(size=1, location=(-49, 70, 1.3))
bpy.ops.transform.resize(value=(6, 2, 0.3))
# URL https://suzulang.com/blender-2-8-python-rotate/
# objに現在のアクティブなオブジェクトを指定する
obj = bpy.context.active_object

def rotate_object(rot_mat):
    # decompose world_matrix's components, and from them assemble 4x4 matrices
    orig_loc, orig_rot, orig_scale = obj.matrix_world.decompose()
    #
    orig_loc_mat   = Matrix.Translation(orig_loc)
    orig_rot_mat   = orig_rot.to_matrix().to_4x4()
    orig_scale_mat = (Matrix.Scale(orig_scale[0],4,(1,0,0)) @ 
                      Matrix.Scale(orig_scale[1],4,(0,1,0)) @ 
                      Matrix.Scale(orig_scale[2],4,(0,0,1)))
    #
    # assemble the new matrix
    obj.matrix_world = orig_loc_mat @ rot_mat @ orig_rot_mat @ orig_scale_mat 
# Matrix.Rotationで回転行列を作成
# Matrix.Rotation( ラジアンの回転角 , 4 , 回転軸 )
# 4 は4x4行列の意味
# 回転軸は(x,y,z)形式の他に 'X' や 'Y' や'Z' のように軸名を指定することもできる
rotate_object( Matrix.Rotation( radians(30), 4, (0,1,0) ) )


## === end of  hodoukyo

# =====  road
bpy.ops.mesh.primitive_plane_add(size=2, enter_editmode=False, align='WORLD', location=(-7, 90, 0), scale=(1, 1, 1))
bpy.ops.transform.resize(value=(3, 200, 1))
bpy.ops.mesh.primitive_plane_add(size=2, enter_editmode=False, align='WORLD', location=(-0.6, 120, 0), scale=(1, 1, 1), rotation=(0.0, 0.0, -6/180*3.14))
bpy.ops.transform.resize(value=(100, 1, 1))

# Add a plane for ground  ==================
bpy.ops.mesh.primitive_plane_add(size=400.0, align='WORLD', location=(0.0, 0.0, -0.5), rotation=(0.0, 0.0, 0.0) )
matp = bpy.data.materials.new('Plane')
matp.diffuse_color = (0.08, 0.08, 0.03, 0) 
obj = bpy.context.view_layer.objects.active
obj.data.materials.append(matp)

# ================== world - surface - background (背景) 
bpy.data.worlds["World"].node_tree.nodes["Background"].inputs[0].default_value = (0.01, 0.15, 0.25, 1)
bpy.data.worlds["World"].node_tree.nodes["Background"].inputs[1].default_value = 0.7      

# ============== "light"
# 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 = (20, -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 = (-20, 60, 40)
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 = (-50, 60, 40)
light_object3.delta_rotation_euler = (0.1, 0, 0.3) #ゼロゼロゼロで真下を向く。
# update scene, if needed
dg = bpy.context.evaluated_depsgraph_get() 
dg.update()


# ================
#======= KOI HONMACHI Map ======
bpy.ops.object.load_reference_image(filepath="/Users/nh7/Dropbox/blenderF/bpykmapR2.jpg")
bpy.context.active_object.name = 'plane_map1'

bpy.context.object.scale[0] = 106
bpy.context.object.scale[1] = 106
bpy.context.object.location[0] = -58.6
bpy.context.object.location[1] = 70.7
#======= KOI HONMACHI Map ======END 


#======= car body manufacturing start
bpy.ops.mesh.primitive_cube_add(size=1, location=(0, 0, 0), scale=(1, 1, 1))
bpy.context.active_object.name = 'b_base'
bpy.ops.transform.resize(value=(1.5,3.4,0.9))
bpy.ops.mesh.primitive_plane_add(size=1, location=(0, -0.45, 0.4))
bpy.context.active_object.name = 'b_top'
bpy.ops.object.editmode_toggle()
bpy.ops.mesh.inset(thickness=0.1, depth=0.6)
bpy.ops.object.editmode_toggle()
bpy.ops.transform.resize(value=(1.5,2.5,1))
bpy.data.objects['b_base'].select_set(True)
bpy.data.objects['b_top'].select_set(True)
for x in bpy.context.selected_objects: # same COLOR on tires and body
    objdd =  x.data
    mat = bpy.data.materials.new('Car_color')
    #h = 0xdc143c  ###0xdc143c= crimson
    h = 0x800000 #ox800000=maroon
    mat.diffuse_color = (*hex_to_rgb(h), 1) #======   COLOR
    objdd.materials.append(mat)
bpy.data.objects['b_base'].select_set(False)
bpy.data.objects['b_top'].select_set(False)

bpy.ops.mesh.primitive_cylinder_add(vertices=16, radius=0.3 , depth=1.6, end_fill_type='NGON', location=(0,1.0,-0.3) )
bpy.context.active_object.name = 'tire_fr'
bpy.ops.transform.rotate(value=90/180*3.14, orient_axis='Y' )
bpy.ops.mesh.primitive_cylinder_add(vertices=16, radius=0.3 , depth=1.6, end_fill_type='NGON', location=(0,-1.2,-0.3) )
bpy.context.active_object.name = 'tire_rr'
bpy.ops.transform.rotate(value=90/180*3.14, orient_axis='Y' )
bpy.data.objects['tire_fr'].select_set(True)
bpy.data.objects['tire_rr'].select_set(True)
for x in bpy.context.selected_objects: # same COLOR on tires and body
    objdd =  x.data
    mat = bpy.data.materials.new('Car_color') 
    h = 0x2f4f4f ###gray?????darkslategray
    mat.diffuse_color = (*hex_to_rgb(h), 1) #======   COLOR
    objdd.materials.append(mat)


bpy.data.objects['b_base'].select_set(True)
bpy.data.objects['b_top'].select_set(True)
bpy.data.objects['tire_fr'].select_set(True)
bpy.data.objects['tire_rr'].select_set(True)
bpy.ops.object.join()
bpy.context.active_object.name = 'joined_car1'
bpy.data.objects['joined_car1'].select_set(True)


#======= END of car body manufacturing 

# ================ CAR movement with KEY FRAME 
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 60
bpy.context.scene.frame_current = 10

bpy.data.objects['joined_car1'].select_set(True)
objects = bpy.data.objects

bpy.context.scene.frame_current = 1 # set frame to 1
objects['joined_car1'].location = (-8,-9,1)  # set location
bpy.ops.transform.rotate(value=0/180*3.14, orient_axis='Z' )
bpy.ops.anim.keyframe_insert_menu(type='Location') # KEY FRAME
bpy.ops.anim.keyframe_insert_menu(type='Rotation')

bpy.context.scene.frame_current = 10 # set frame to 10
objects['joined_car1'].location = (-8,15,1)
bpy.ops.transform.rotate(value=0/180*3.14, orient_axis='Z' )
bpy.ops.anim.keyframe_insert_menu(type='Location') # KEY FRAME
bpy.ops.anim.keyframe_insert_menu(type='Rotation')

bpy.context.scene.frame_current = 20
objects['joined_car1'].location = (-8,53,1)
bpy.ops.transform.rotate(value=-2/180*3.14, orient_axis='Z' )
bpy.ops.anim.keyframe_insert_menu(type='Location') # KEY FRAME
bpy.ops.anim.keyframe_insert_menu(type='Rotation')

bpy.context.scene.frame_current = 22
objects['joined_car1'].location = (-10,59,1)
bpy.ops.transform.rotate(value=-30/180*3.14, orient_axis='Z' )
#objects['car_body'].rotation = (0,0,15/180*3.14)
bpy.ops.anim.keyframe_insert_menu(type='Location') # KEY FRAME
bpy.ops.anim.keyframe_insert_menu(type='Rotation')

bpy.context.scene.frame_current = 25
objects['joined_car1'].location = (-14,60,1)
bpy.ops.transform.rotate(value=-30/180*3.14, orient_axis='Z' )
#objects['car_body'].rotation = (0,0,15/180*3.14)
bpy.ops.anim.keyframe_insert_menu(type='Location') # KEY FRAME
bpy.ops.anim.keyframe_insert_menu(type='Rotation')

bpy.context.scene.frame_current = 40
objects['joined_car1'].location = (-30,64,1)
bpy.ops.transform.rotate(value=-30/180*3.14, orient_axis='Z' )
#objects['car_body'].rotation = (0,0,30/180*3.14)
bpy.ops.anim.keyframe_insert_menu(type='Location') # KEY FRAME
bpy.ops.anim.keyframe_insert_menu(type='Rotation')

bpy.context.scene.frame_current = 50
objects['joined_car1'].location = (-45,65,1)
#bpy.ops.transform.rotate(value=0/180*3.14, orient_axis='Z' )
bpy.ops.anim.keyframe_insert_menu(type='Location') # KEY FRAME
bpy.ops.anim.keyframe_insert_menu(type='Rotation')

bpy.context.scene.frame_current = 60
objects['joined_car1'].location = (-65,65,1)
bpy.ops.transform.rotate(value=0/180*3.14, orient_axis='Z' )
#objects['car_body'].rotation = (0,0,30/180*3.14)
bpy.ops.anim.keyframe_insert_menu(type='Location') # KEY FRAME
bpy.ops.anim.keyframe_insert_menu(type='Rotation')

bpy.ops.transform.rotate(value=0, orient_axis='Z' )

# ================ END of CAR movement

# ================ make a new CURVE for camera movement
# ==== nunoheya BEGIN
curvedata = bpy.data.curves.new("my curve", type='CURVE')    
curvedata.dimensions = '3D'    

polyline = curvedata.splines.new('BEZIER')
polyline.bezier_points.add(2) # 制御点数ー１個

polyline.bezier_points[0].co = -1,-61,18
polyline.bezier_points[0].handle_left = -1,-74,18
polyline.bezier_points[0].handle_right = -6,-45,18

polyline.bezier_points[1].co = 0,30,18
polyline.bezier_points[1].handle_left = -3,15,18
polyline.bezier_points[1].handle_right = 0,43,18

polyline.bezier_points[2].co = -18,62,20
polyline.bezier_points[2].handle_left = 0,58,20
polyline.bezier_points[2].handle_right = -30,62,20

obj = bpy.data.objects.new("my bezier", curvedata) 
# 新しいCollectionを作成
newCol = bpy.data.collections.new('Collection 1')
# 現在のシーンにコレクションをリンク
bpy.context.scene.collection.children.link(newCol)
# コレクションにオブジェクトをリンク
newCol.objects.link(obj)
# ==== nunoheya END


#  ================== = camera movement
#bpy.ops.curve.primitive_bezier_circle_add(enter_editmode=False, align='WORLD', location=(20, 20, 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"].target = bpy.data.objects["my bezier"]

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["joined_car1"]
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=12)
con.offset_factor = 0.50
con.keyframe_insert("offset_factor", frame=18)
con.offset_factor = 0.70
con.keyframe_insert("offset_factor", frame=30)
con.offset_factor = 0.80
con.keyframe_insert("offset_factor", frame=50)
con.offset_factor = 0.99
con.keyframe_insert("offset_factor", frame=60)

# ==== END of camera movement

# ======== to EDIT mode, then OBJECT mode ===

#「# 編集モードへの移行」2.8向けに2行ほど改良。
 
# 編集モードへの移行
# 引数   arg_objectname：指定オブジェクト名
# 戻り値
def set_mode_edit(arg_objectname='Default'):
  # 他のオブジェクトに操作を適用しないよう全てのオブジェクトを走査する
  for ob in bpy.context.scene.objects:
    # 非選択状態に設定する
    #ob.select=False  #blender2.7
    ob.select_set(False)
  # 指定オブジェクトを取得する
  selectob = bpy.data.objects[arg_objectname]
  # 変更オブジェクトをアクティブに変更する
  #bpy.context.scene.objects.active = selectob  #blender2.7
  bpy.context.view_layer.objects.active = selectob
  #
  # 編集モードに移行する
  bpy.ops.object.mode_set(mode='EDIT', toggle=False)
  return
# 関数の実行例
set_mode_edit('my bezier')
# =====
#「# オブジェクトモードへの移行」2.8向けに2行ほど改良。

# オブジェクトモードへの移行
# 引数   arg_objectname：指定オブジェクト名
# 戻り値
def set_mode_object(arg_objectname='Default'):
  # 他のオブジェクトに操作を適用しないよう全てのオブジェクトを走査する
    for ob in bpy.context.scene.objects:
    # 非選択状態に設定する
    #ob.select=False
        ob.select_set(False)
  # 指定オブジェクトを取得する
    selectob = bpy.data.objects[arg_objectname]
  # 変更オブジェクトをアクティブに変更する
  #bpy.context.scene.objects.active = selectob
    bpy.context.view_layer.objects.active = selectob
  # オブジェクトモードに移行する
    bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
    return
# 関数の実行例
set_mode_object('my bezier')
# ======== to EDIT mode, then OBJECT mode ===   END


```