Quick actions

cmd+k|ctrl+k

Navigation

Languages

Blender Script - flag

Snippet info

Language

Python

Visibility

public

Author

awesome2023

Created

2024-01-15T05:49:54.682909Z

Updated

2024-01-15T05:49:54.682909Z


###########################################################################
#
# Python Script To Read Data From A CSV File & Create Animated 3D Bars
#
# This script is originally written by 5 Minutes Blender YouTube channel
#
###########################################################################

import bpy
import math
import random
import csv

context = bpy.context
scene = context.scene

###########################################################################
###########################################################################
#
# CHANGE THE FOLLOWING INPUT AS PER YOUR CSV FILE
# BE CAREFUL - ANY WRONG INPUT WILL RAISE AN ERROR.

csv_file_path = r"C:\MyTest\Database.csv"
font_file_path = r"C:\MyTest\Arialbd.ttf"
rank_column = 1
name_column = 2
data_column = 3
display_data_column1 = 5
display_data_column2 = 6
display_pic_column = 7
anim_speed_factor = 2 # Enter a value between 1 (slow) and 3 (fast)

###########################################################################
###########################################################################

anim_start_frame = 1
camera_start_frame = 21
data_start_position = 0
distance_bet_data  = 3
plate_duration = 6
plate_step_duration = 2
plate_thickness = 0.3
max_moving_plates = 25
data_norm_factor = 5
min_diff_factor = 20
max_diff_factor = 12 * plate_thickness
min_data_height = 6

# Save the current location of the 3D cursor
saved_cursor_loc = scene.cursor.location.xyz

# Initialize the variables.
data_counter = 0
anim_curr_frame = anim_start_frame
speed_multiplier = 10 - anim_speed_factor
anim_length = speed_multiplier * 7
anim_interval = speed_multiplier * 7
rank_list = []
name_list = []
data_list = []
display_data_list1 = []
display_data_list2 = []
display_pic_list = []

# Read the csv file and store the data in an array
with open(csv_file_path, 'r') as file:
    csvreader = csv.DictReader(file)
    for row in csvreader:
        key = str(list(row)[rank_column-1])
        rank_list.append(int(row[key]))
        key = str(list(row)[name_column-1])
        name_list.append(str(row[key]))
        key = str(list(row)[data_column-1])
        data_list.append(float(row[key]))
        key = str(list(row)[display_data_column1-1])
        display_data_list1.append(str(row[key]))
        key = str(list(row)[display_data_column2-1])
        display_data_list2.append(str(row[key]))
        key = str(list(row)[display_pic_column-1])
        display_pic_list.append(str(row[key]))

number_of_data = len(name_list)
data_height_mean = sum(data_list) / len(data_list)

# Create a normalized data list
normalized_data_list = [data_list[count] * data_norm_factor/data_height_mean for count in range(number_of_data)]
if (min(normalized_data_list) < min_data_height):
    mandatory_increment = min_data_height - min(normalized_data_list)
    normalized_data_list = [x + mandatory_increment for x in normalized_data_list]
    
height_threshold = (max(normalized_data_list) - min(normalized_data_list)) / min_diff_factor

# Maintain a minimum & a maximum height-difference between two successive data bars
for i in range(number_of_data-1):
    if (normalized_data_list[i] < normalized_data_list[i+1]) and \
        (normalized_data_list[i+1] - normalized_data_list[i] < height_threshold):
        for j in range(i+1,number_of_data):
            normalized_data_list[j] += height_threshold
    elif (normalized_data_list[i] > normalized_data_list[i+1]) and \
        (normalized_data_list[i] - normalized_data_list[i+1] < height_threshold):
        for j in range(i+1,number_of_data):
            normalized_data_list[j] -= height_threshold
    elif (normalized_data_list[i] < normalized_data_list[i+1]) and \
        (normalized_data_list[i+1] - normalized_data_list[i] > max_diff_factor):
        height_adjustment = normalized_data_list[i+1] - normalized_data_list[i] - max_diff_factor
        for j in range(i+1,number_of_data):
            normalized_data_list[j] -= height_adjustment
    elif (normalized_data_list[i] > normalized_data_list[i+1]) and \
        (normalized_data_list[i] - normalized_data_list[i+1] > max_diff_factor):
        height_adjustment = normalized_data_list[i] - normalized_data_list[i+1] - max_diff_factor
        for j in range(i+1,number_of_data):
            normalized_data_list[j] += height_adjustment

# Create a new material for the bars
material_1 = bpy.data.materials.new(name="anim_material_1")
material_1.use_nodes = True
if material_1.node_tree:
    material_1.node_tree.links.clear()
    material_1.node_tree.nodes.clear()
nodes = material_1.node_tree.nodes
links = material_1.node_tree.links
output = nodes.new(type='ShaderNodeOutputMaterial')
shader = nodes.new(type='ShaderNodeBsdfPrincipled')
shader.location = (-300, 0)
shader.inputs[0].default_value = (0.15, 0.0, 0.0, 1)
links.new(shader.outputs[0], output.inputs[0])

# Create a new material for the text
material_2 = bpy.data.materials.new(name="anim_material_2")
material_2.use_nodes = True
if material_2.node_tree:
    material_2.node_tree.links.clear()
    material_2.node_tree.nodes.clear()
nodes = material_2.node_tree.nodes
links = material_2.node_tree.links
output = nodes.new(type='ShaderNodeOutputMaterial')
shader = nodes.new(type='ShaderNodeBsdfPrincipled')
shader.location = (-300, 0)
shader.inputs[0].default_value = (1, 1, 1, 1)
links.new(shader.outputs[0], output.inputs[0])

# Add an empty and set its initial position
bpy.ops.object.empty_add()
empty = bpy.context.active_object
empty.location = [0,0,10]
empty.hide_set(True)
empty.hide_render = True
    
# Set initial camera position
camera = bpy.data.objects['Camera']
camera.location = [-3,-15,10]
camera.rotation_euler = [math.radians(80),0,0]
bpy.data.cameras['Camera'].passepartout_alpha = 1.0
bpy.data.cameras['Camera'].clip_start = 0.1
bpy.data.cameras['Camera'].clip_end = 1000

# Add a track-to constraint for the camera
camera.animation_data_clear()
camera.constraints.clear()
track_to = camera.constraints.new('TRACK_TO')
track_to.target = empty
track_to.track_axis = 'TRACK_NEGATIVE_Z'
track_to.up_axis = 'UP_Y'

# Set light properties
Light = bpy.data.objects['Light']
Light.data.type = "SUN"
Light.data.energy = 4
Light.data.use_shadow = True
Light.data.use_contact_shadow = True
Light.data.contact_shadow_distance = 1.2
Light.rotation_euler = [math.radians(37),math.radians(3),math.radians(40)]

# Create new collections
new_col1 = bpy.data.collections.new(name="Data Bars")
new_col2 = bpy.data.collections.new(name="Data Names")
new_col3 = bpy.data.collections.new(name="Data Numbers")
new_col4 = bpy.data.collections.new(name="Data Pics")
new_col5 = bpy.data.collections.new(name="Data Plates")
new_col6 = bpy.data.collections.new(name="Ranks")
bpy.context.scene.collection.children.link(new_col1)
bpy.context.scene.collection.children.link(new_col2)
bpy.context.scene.collection.children.link(new_col3)
bpy.context.scene.collection.children.link(new_col4)
bpy.context.scene.collection.children.link(new_col5)
bpy.context.scene.collection.children.link(new_col6)

# Create the bars in a loop

while (data_counter < number_of_data):
    
    data_height = normalized_data_list[data_counter]
    data_rank = str(rank_list[data_counter])
    data_name = str(name_list[data_counter])
    display_data1 = str(display_data_list1[data_counter])
    display_data2 = str(display_data_list2[data_counter])

    # Add a cube and set its dimensions
    bpy.ops.mesh.primitive_cube_add()
    obj = bpy.context.active_object
    obj.name = "Bar_" + str(data_counter+1)
    obj.dimensions = [1.5,1,data_height]
    obj.location = [data_start_position,0,data_height/2]
    bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)

    for other_col in obj.users_collection:
        other_col.objects.unlink(obj)
    if obj.name not in new_col1.objects:
        new_col1.objects.link(obj)

    # Set origin to the bottom of the cube
    scene.cursor.location = (data_start_position,0,0)
    bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
    
    # Add a bevel modifier
    bevel_mod = obj.modifiers.new(name="Bevelmodifier", type='BEVEL')
    bevel_mod.width = 0.05
    bevel_mod.segments = 10

    # Calculate the correct plate thickness, no. of plates & animation duration & interval
    num_of_plates = int(data_height/plate_thickness)
    plate_thickness = data_height/num_of_plates
    num_of_moving_plates = num_of_plates
    
    if (num_of_moving_plates > max_moving_plates):
        num_of_moving_plates = max_moving_plates
    
    # Animate the height of the cube and also move the empty & the camera
    anim_curr_frame += anim_length-1
    obj.dimensions = [1.4,0.9,0]
    obj.keyframe_insert(data_path="scale", frame = anim_curr_frame)
    anim_curr_frame += 1
    obj.dimensions = [1.4,0.9,data_height]
    obj.keyframe_insert(data_path="scale", frame = anim_curr_frame)
    anim_curr_frame += 10
    obj.dimensions = [1.5,1,data_height]
    obj.keyframe_insert(data_path="scale", frame = anim_curr_frame)

    if (data_counter == number_of_data - 1):

        empty.location = [data_start_position,0,data_height-0.75]
        empty.keyframe_insert(data_path="location", frame = anim_curr_frame - 20)
        camera.location = [data_start_position,-15,data_height]
        camera.keyframe_insert(data_path="location", frame = anim_curr_frame - 20)
    
    else:

        empty.location = [data_start_position,0,data_height-0.75]
        empty.keyframe_insert(data_path="location", frame = anim_curr_frame - 20)
        camera.location = [data_start_position,-15,data_height]
        camera.keyframe_insert(data_path="location", frame = anim_curr_frame - 20)

        next_data_height = normalized_data_list[data_counter+1]
        adjustment = anim_length - anim_length * min(int(next_data_height/plate_thickness), max_moving_plates) / max_moving_plates
        empty.location = [data_start_position+0.25,0,data_height-0.75]
        empty.keyframe_insert(data_path="location", frame = anim_curr_frame + anim_interval*2/3 + adjustment + (3-anim_speed_factor))
        camera.location = [data_start_position+0.25,-15,data_height]
        camera.keyframe_insert(data_path="location", frame = anim_curr_frame + anim_interval*2/3 + adjustment + (3-anim_speed_factor))
    
        next_target_position = next_data_height - min(int(next_data_height/plate_thickness), max_moving_plates) * plate_thickness
        empty.location = [data_start_position+distance_bet_data,0,next_target_position+2]
        empty.keyframe_insert(data_path="location", frame = anim_curr_frame + anim_interval + 10 + (3-anim_speed_factor)*3)
        camera.location = [data_start_position+distance_bet_data,-15,next_target_position+3]
        camera.keyframe_insert(data_path="location", frame = anim_curr_frame + anim_interval + 10 + (3-anim_speed_factor)*3)

    if (data_counter == 0):
    
        empty.location = [data_start_position,0,1.2]
        empty.keyframe_insert(data_path="location", frame = anim_start_frame)
        camera.location = [data_start_position,-25,2]
        camera.keyframe_insert(data_path="location", frame = anim_start_frame)
        empty.location = [data_start_position,0,1.2]
        empty.keyframe_insert(data_path="location", frame = camera_start_frame)
        camera.location = [data_start_position,-15,2]
        camera.keyframe_insert(data_path="location", frame = camera_start_frame)

    obj.hide_viewport = True
    obj.hide_render = True
    obj.hide_set(True)
    
    plate_anim_start = anim_curr_frame - 10 - (plate_duration + plate_step_duration * (num_of_moving_plates - 1))
    plate_anim_frame = plate_anim_start
    plate_list = []
    
    # Create the plates in a loop and animate them
    for count in range(num_of_plates):

        bpy.ops.mesh.primitive_cube_add()
        plate = bpy.context.active_object
        plate.name = "Plate_" + str(data_counter+1) + "_" + str(count+1)
        plate.dimensions = [1.49,0.99,plate_thickness]
        plate.location = [data_start_position,0,data_height+3]
        bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)

        for other_col in plate.users_collection:
            other_col.objects.unlink(plate)
        if plate.name not in new_col5.objects:
            new_col5.objects.link(plate)

        # Add a bevel modifier
        bevel_mod = plate.modifiers.new(name="Bevelmodifier", type='BEVEL')
        bevel_mod.width = 0.05
        bevel_mod.segments = 10

        # Animate the plate
        random_start_loc = random.uniform(2.5, 3.5)
        plate.location = [data_start_position,0,data_height+random_start_loc]
        plate.keyframe_insert(data_path="location", frame = plate_anim_frame)
        if (num_of_plates - count <= num_of_moving_plates):
            plate_anim_frame += plate_duration      # No increment if it is not a moving plate
        plate.location = [data_start_position,0,plate_thickness*(count+1)-(plate_thickness/2)]
        plate.keyframe_insert(data_path="location", frame = plate_anim_frame)
        if (num_of_plates - count <= num_of_moving_plates):
            plate_anim_frame += -plate_duration     # No change if it is not a moving plate
            plate_anim_frame += plate_step_duration # No change if it is not a moving plate
    
        # Hide render visibility until it is needed.
        if (num_of_plates - count <= num_of_moving_plates):
            plate.hide_render = True
            plate.keyframe_insert(data_path="hide_render", frame = plate_anim_start-1)
            plate.hide_render = False
            plate.keyframe_insert(data_path="hide_render", frame = plate_anim_start)
        else:
            plate.hide_render = True
            plate.keyframe_insert(data_path="hide_render", frame = plate_anim_start-21)
            plate.hide_render = False
            plate.keyframe_insert(data_path="hide_render", frame = plate_anim_start-20)
        
        # Assign the first material created above
        plate.data.materials.append(material_1)
        
        # Create a duplicate copy of the plate
        bpy.ops.object.duplicate(linked=False)
        plate_copy = bpy.context.active_object
        plate_list.append(plate_copy)
    
    # Merge plate copies into a single object and animate the same
    
    bpy.ops.object.select_all(action='DESELECT')
    for each_plate in plate_list:
        each_plate.select_set(True)
        
    context.view_layer.objects.active = plate_list[0]
    bpy.ops.object.join()
    
    # Set origin to the middle of the cube
    scene.cursor.location = (data_start_position,0,data_height/2)
    bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
    
    joined_obj = plate_list[0]
    joined_obj.name = "Plate_" + str(data_counter+1) + "_merged"
    bpy.ops.object.select_all(action='DESELECT')
    
    joined_obj.animation_data_clear()
    joined_obj.hide_viewport = True
    joined_obj.hide_render = True
    joined_obj.hide_set(True)

    # Animate the z-location of the merged object
    plate_anim_start = anim_curr_frame - 10 - (plate_duration + plate_step_duration * (num_of_moving_plates - 2))
    plate_anim_frame = plate_anim_start
    joined_obj.location = [data_start_position,0,(data_height/2) - (plate_thickness * num_of_moving_plates)]
    joined_obj.keyframe_insert(data_path="location", frame = plate_anim_frame)
    for count in range(num_of_moving_plates):
        if (count == 0):
            plate_anim_frame += plate_duration
        else:
            plate_anim_frame += plate_step_duration
        joined_obj.location = [data_start_position,0,(data_height/2) - (plate_thickness * (num_of_moving_plates-(count+1)))]
        joined_obj.keyframe_insert(data_path="location", frame = plate_anim_frame)

    # Add the rank text
    bpy.ops.object.text_add()
    ob = bpy.context.active_object
    ob.name = "Rank_" + str(data_counter+1)
    ob.data.body = data_rank
    ob.data.align_x = "CENTER"
    ob.data.align_y = "BOTTOM_BASELINE"
    ob.data.extrude = 0.1
    ob.data.font = bpy.data.fonts.load(font_file_path)

    ob.location = [data_start_position,0,data_height]
    ob.scale = [2, 2, 2]
    ob.rotation_euler = [math.radians(90),0,math.radians(15)]
    bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)

    for other_col in ob.users_collection:
        other_col.objects.unlink(ob)
    if ob.name not in new_col6.objects:
        new_col6.objects.link(ob)
    
    # Animate the z-scale of the rank text
    ob.scale = [1,0,1]
    ob.keyframe_insert(data_path="scale", frame = anim_curr_frame - 10)
    ob.scale = [1,1.1,1]
    ob.keyframe_insert(data_path="scale", frame = anim_curr_frame - 2)
    ob.scale = [1,0.95,1]
    ob.keyframe_insert(data_path="scale", frame = anim_curr_frame + 3)
    ob.scale = [1,1.025,1]
    ob.keyframe_insert(data_path="scale", frame = anim_curr_frame + 6)
    ob.scale = [1,0.985,1]
    ob.keyframe_insert(data_path="scale", frame = anim_curr_frame + 8)
    ob.scale = [1,1,1]
    ob.keyframe_insert(data_path="scale", frame = anim_curr_frame + 10)

    # Hide render visibility until it is needed.
    ob.hide_render = True
    ob.keyframe_insert(data_path="hide_render", frame = anim_curr_frame-11)
    ob.hide_render = False
    ob.keyframe_insert(data_path="hide_render", frame = anim_curr_frame-10)

    # Assign the second material created above
    ob.data.materials.append(material_2)
    
    # Add the 1st caption
    bpy.ops.object.text_add()
    ob = bpy.context.active_object
    ob.name = "Name_" + str(data_counter+1)
    ob.data.body = data_name
    ob.data.align_x = "CENTER"
    ob.data.align_y = "CENTER"
    ob.data.extrude = 0.01
    ob.data.font = bpy.data.fonts.load(font_file_path)

    ob.location = [data_start_position,-0.7,data_height-6.5*plate_thickness]
    ob.scale = [0.4, 0.4, 0.1]
    ob.rotation_euler = [math.radians(90),0,0]
    bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)

    for other_col in ob.users_collection:
        other_col.objects.unlink(ob)
    if ob.name not in new_col2.objects:
        new_col2.objects.link(ob)
    
    # Assign the second material created above
    ob.data.materials.append(material_2)
    
    # Add a shrinkwrap modifier
    shrink_mod = ob.modifiers.new(name="Shrinkwrapmodifier", type='SHRINKWRAP')
    shrink_mod.offset = 0.001
    shrink_mod.target = obj
    
    #bpy.ops.object.convert(target='MESH') # Needed for cycles render

    # Add the 2nd caption
    bpy.ops.object.text_add()
    ob = bpy.context.active_object
    ob.name = "Data_" + str(data_counter+1)
    ob.data.body = display_data1
    ob.data.align_x = "CENTER"
    ob.data.align_y = "CENTER"
    ob.data.extrude = 0.01
    ob.data.font = bpy.data.fonts.load(font_file_path)

    ob.location = [data_start_position,-0.7,data_height-7.5*plate_thickness]
    ob.scale = [0.35, 0.35, 0.1]
    ob.rotation_euler = [math.radians(90),0,0]
    bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)

    for other_col in ob.users_collection:
        other_col.objects.unlink(ob)
    if ob.name not in new_col3.objects:
        new_col3.objects.link(ob)
    
    # Assign the second material created above
    ob.data.materials.append(material_2)
    
    # Add a shrinkwrap modifier
    shrink_mod = ob.modifiers.new(name="Shrinkwrapmodifier", type='SHRINKWRAP')
    shrink_mod.offset = 0.001
    shrink_mod.target = obj

    #bpy.ops.object.convert(target='MESH') # Needed for cycles render

    # Add the 3rd caption
    bpy.ops.object.text_add()
    ob = bpy.context.active_object
    ob.name = "Data_" + str(data_counter+1)
    ob.data.body = display_data2
    ob.data.align_x = "CENTER"
    ob.data.align_y = "CENTER"
    ob.data.extrude = 0.01
    ob.data.font = bpy.data.fonts.load(font_file_path)

    ob.location = [data_start_position,-0.7,data_height-8.5*plate_thickness]
    ob.scale = [0.35, 0.35, 0.1]
    ob.rotation_euler = [math.radians(90),0,0]
    bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)

    for other_col in ob.users_collection:
        other_col.objects.unlink(ob)
    if ob.name not in new_col3.objects:
        new_col3.objects.link(ob)
    
    # Assign the second material created above
    ob.data.materials.append(material_2)
    
    # Add a shrinkwrap modifier
    shrink_mod = ob.modifiers.new(name="Shrinkwrapmodifier", type='SHRINKWRAP')
    shrink_mod.offset = 0.001
    shrink_mod.target = obj

    #bpy.ops.object.convert(target='MESH') # Needed for cycles render

    # Add the display picture
    bpy.ops.mesh.primitive_plane_add()
    ob = bpy.context.active_object
    ob.name = "Picture_" + str(data_counter+1)

    ob.location = [data_start_position,-0.7,data_height-0.86]
    ob.dimensions = [1.41, 1.6, 1]
    ob.rotation_euler = [math.radians(90),0,0]
    bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)

    for other_col in ob.users_collection:
        other_col.objects.unlink(ob)
    if ob.name not in new_col4.objects:
        new_col4.objects.link(ob)

    # Create a new material and assign
    dp_material = bpy.data.materials.new(name="anim_dp_material"+str(data_counter+1))
    dp_material.use_nodes = True
    if dp_material.node_tree:
        dp_material.node_tree.links.clear()
        dp_material.node_tree.nodes.clear()
    nodes = dp_material.node_tree.nodes
    links = dp_material.node_tree.links
    output = nodes.new(type='ShaderNodeOutputMaterial')
    shader = nodes.new(type='ShaderNodeBsdfPrincipled')
    shader.location = (-300, 0)
    shader.inputs[0].default_value = (0.05, 0.05, 0.05, 1)
    shader.inputs[7].default_value = 0.05
    texture = nodes.new(type='ShaderNodeTexImage')
    texture.location = (-700, 0)
    texture.image = bpy.data.images.load(display_pic_list[data_counter])
    links.new(texture.outputs["Color"], shader.inputs["Base Color"])
    links.new(texture.outputs["Alpha"], shader.inputs["Alpha"])
    links.new(shader.outputs[0], output.inputs[0])
    dp_material.blend_method = 'BLEND'
    ob.data.materials.append(dp_material)
    
    # Add a shrinkwrap modifier
    shrink_mod = ob.modifiers.new(name="Shrinkwrapmodifier", type='SHRINKWRAP')
    shrink_mod.offset = 0.001
    shrink_mod.target = obj
    
    # Hide render visibility until it is needed.
    ob.hide_render = True
    ob.keyframe_insert(data_path="hide_render", frame = anim_curr_frame)
    ob.hide_render = False
    ob.keyframe_insert(data_path="hide_render", frame = anim_curr_frame+1)

    # Create a duplicate copy of the display picture
    bpy.ops.object.duplicate(linked=False)
    ob_copy = bpy.context.active_object

    # Add a subdivision modifier and a shrinkwrap modifier
    ob_copy.modifiers.clear()
    subd_mod = ob_copy.modifiers.new(name="Subdivisionmodifier", type='SUBSURF')
    subd_mod.subdivision_type = "SIMPLE"
    subd_mod.levels = 6
    subd_mod.render_levels = 6
    shrink_mod = ob_copy.modifiers.new(name="Shrinkwrapmodifier", type='SHRINKWRAP')
    shrink_mod.offset = 0.001
    shrink_mod.target = joined_obj

    # Hide render visibility after it's purpose is over.
    ob_copy.hide_render = False
    ob_copy.keyframe_insert(data_path="hide_render", frame = anim_curr_frame)
    ob_copy.hide_render = True
    ob_copy.keyframe_insert(data_path="hide_render", frame = anim_curr_frame+1)

    #increase the loop counters
    data_counter += 1
    data_start_position += distance_bet_data
    anim_curr_frame += anim_interval

# Clean-up work
# Reset 3D cursor location back to the original
scene.cursor.location.xyz = saved_cursor_loc
context.active_object.select_set(False)

# Set the current frame to frame# 1
scene.frame_set(1)

# Set the scene length
scene.frame_start = 1
scene.frame_end = anim_curr_frame + 50

#https://drive.google.com/file/d/1XESugeNehYLBuyog8Qog0KwcJCIXpscs/view
#CSV-To-Blender-Data-Comparison-Special-Script.txt。
INFO