Blender Script - flag

Run Settings
LanguagePython
Language Version
Run Command
########################################################################### # # 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。
Editor Settings
Theme
Key bindings
Full width
Lines