Created
September 30, 2024 05:02
-
-
Save sina-mansour/3871564599e90cb3af9b5624e170b657 to your computer and use it in GitHub Desktop.
A blender 4.2 python script for cloth simulation along with a transparent brain surface rendered by cycles engine
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Blender executed via: | |
# PYTHONPATH=/home/sina/miniconda3/envs/blender_env/bin/python blender --python-use-system-env | |
# Notes: | |
# Setting up blender to use a conda virtualenv: | |
# https://stackoverflow.com/questions/70639689/how-to-use-the-anaconda-environment-on-blender | |
# Blender Python API: | |
# https://docs.blender.org/api/current/info_quickstart.html | |
# Blender Python tips and tricks: | |
# https://docs.blender.org/api/current/info_tips_and_tricks.html | |
# Blender can be built from source to enable direct python execution: | |
# https://developer.blender.org/docs/handbook/building_blender/python_module/ | |
# Building blender: | |
# https://developer.blender.org/docs/handbook/building_blender/ | |
# Idea: conda can be used to build blender for a python package: | |
# https://docs.aws.amazon.com/deadline-cloud/latest/developerguide/create-conda-recipe-blender.html | |
# https://github.com/aws-deadline/deadline-cloud-samples/blob/mainline/conda_recipes/blender-4.2/recipe/meta.yaml | |
import bpy | |
import bmesh | |
import math | |
import numpy as np | |
import nibabel as nib | |
import matplotlib.pyplot as plt | |
from Connectome_Spatial_Smoothing import CSS as css | |
from cerebro import cerebro_brain_utils as cbu | |
from cerebro import cerebro_brain_viewer as cbv | |
# function to load brain surface geometries | |
def load_cortical_mesh(surface): | |
left_surface_file, right_surface_file = cbu.get_left_and_right_GIFTI_template_surface(surface) | |
# left_surface = nib.load(left_surface_file) | |
left_vertices, left_triangles = cbu.load_GIFTI_surface(left_surface_file) | |
return left_vertices, left_triangles | |
# function to clean the blender scene | |
def blender_clean_up(): | |
# make sure the active object is not in Edit Mode | |
if bpy.context.active_object and bpy.context.active_object.mode == "EDIT": | |
bpy.ops.object.editmode_toggle() | |
# make sure non of the objects are hidden from the viewport, selection, or disabled | |
for obj in bpy.data.objects: | |
obj.hide_set(False) | |
obj.hide_select = False | |
obj.hide_viewport = False | |
# select all the object and delete them (just like pressing A + X + D in the viewport) | |
bpy.ops.object.select_all(action="SELECT") | |
bpy.ops.object.delete() | |
# find all the collections and remove them | |
collection_names = [col.name for col in bpy.data.collections] | |
for name in collection_names: | |
bpy.data.collections.remove(bpy.data.collections[name]) | |
# in the case when you modify the world shader | |
# delete and recreate the world object | |
world_names = [world.name for world in bpy.data.worlds] | |
for name in world_names: | |
bpy.data.worlds.remove(bpy.data.worlds[name]) | |
# create a new world data block | |
bpy.ops.world.new() | |
bpy.context.scene.world = bpy.data.worlds["World"] | |
bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True) | |
# function to adjust animation frame setup | |
def frame_setup(start_frame, end_frame): | |
# Set the start and end frames of the animation | |
bpy.context.scene.frame_start = start_frame | |
bpy.context.scene.frame_end = end_frame | |
# function to create a mesh | |
def create_mesh_object(vertices, faces, mesh_name, object_name): | |
# Create a new mesh and object | |
mesh = bpy.data.meshes.new(mesh_name) | |
obj = bpy.data.objects.new(object_name, mesh) | |
# Link the object to the current scene collection | |
bpy.context.collection.objects.link(obj) | |
# Set the object as active and select it | |
bpy.context.view_layer.objects.active = obj | |
obj.select_set(True) | |
# Create the mesh from the vertices and faces | |
mesh.from_pydata(vertices, [], faces) | |
mesh.update() # Update the mesh to make sure it's updated in the scene | |
# Return the created object | |
return mesh, obj | |
# function to apply smooth shading to object | |
def shade_smooth(obj): | |
# Ensure the object is selected and active | |
bpy.context.view_layer.objects.active = obj | |
obj.select_set(True) | |
# Apply smooth shading | |
bpy.ops.object.shade_smooth() | |
# function to turn data to colors | |
def generate_vertex_colors(data, colormap=plt.cm.coolwarm, alpha=1.0): | |
# Normalize the data and map to colors | |
scaled_data = (data - data.min()) / (data.max() - data.min()) | |
vertex_data = np.zeros(left_vertices_white.shape[0]) * np.nan | |
vertex_data[np.array(left_cortical_surface_model.vertex_indices)] = scaled_data[left_cortical_surface_model.index_offset:left_cortical_surface_model.index_count] | |
vertex_colors = colormap(vertex_data) | |
# adjust alpha | |
vertex_colors[:, 3] = alpha | |
return vertex_colors | |
# Function to update vertex colors on a mesh | |
def update_vertex_colors(mesh, vertex_colors): | |
color_layer = mesh.vertex_colors.active.data | |
for loop_index, loop in enumerate(mesh.loops): | |
vertex_index = loop.vertex_index | |
color_layer[loop_index].color = vertex_colors[vertex_index] | |
# Function to add vertex colors to a mesh's vertex color layer | |
def create_layer(mesh, layer_name): | |
# Create a new vertex color layer or use an existing one | |
if layer_name not in mesh.vertex_colors: | |
mesh.vertex_colors.new(name=layer_name) | |
vertex_color_layer = mesh.vertex_colors[layer_name] | |
mesh.vertex_colors.active_index = [x[0] for x in mesh.vertex_colors.items()].index(layer_name) | |
return vertex_color_layer | |
# Function to add vertex colors to a mesh's vertex color layer | |
def add_vertex_colors_to_layer(mesh, vertex_colors, layer_name): | |
create_layer(mesh, layer_name) | |
update_vertex_colors(mesh, vertex_colors) | |
mesh.update() | |
# Function to create material and assign color to it | |
def assign_object_material_with_color(obj, color_layer_name, metallic, roughness): | |
# Create a new material if it doesn't exist | |
material = bpy.data.materials.new("VertexColorMaterial") | |
material.use_nodes = True | |
nodes = material.node_tree.nodes | |
links = material.node_tree.links | |
# Clear existing nodes | |
for node in nodes: | |
nodes.remove(node) | |
# Create nodes: Vertex Color node, Principled BSDF, and Material Output | |
vertex_color_node = nodes.new(type='ShaderNodeVertexColor') | |
vertex_color_node.layer_name = color_layer_name | |
principled_bsdf = nodes.new(type='ShaderNodeBsdfPrincipled') | |
material_output = nodes.new(type='ShaderNodeOutputMaterial') | |
# Connect vertex color layer to the Principled BSDF shader and then to the material output | |
links.new(vertex_color_node.outputs['Color'], principled_bsdf.inputs['Base Color']) | |
links.new(vertex_color_node.outputs['Alpha'], principled_bsdf.inputs['Alpha']) | |
links.new(principled_bsdf.outputs['BSDF'], material_output.inputs['Surface']) | |
# Other material properties | |
principled_bsdf.inputs["Metallic"].default_value = metallic | |
principled_bsdf.inputs["Roughness"].default_value = roughness | |
# Assign the material to the object | |
if len(obj.data.materials) > 0: | |
obj.data.materials[0] = material | |
else: | |
obj.data.materials.append(material) | |
return material | |
# Function to create object | |
def create_data_object(name, object_data): | |
obj = bpy.data.objects.new(name, object_data) | |
bpy.context.collection.objects.link(obj) | |
return obj | |
# Function to create empty | |
def create_empty(name, location): | |
# Create an empty object | |
bpy.ops.object.empty_add(type='PLAIN_AXES', location=location) | |
empty = bpy.context.object | |
empty.name = name | |
return empty | |
# Function to create plane | |
def create_plane(name, scale, location, rotation, subdivision_kws={}): | |
# Create a plane | |
bpy.ops.mesh.primitive_plane_add(location=location, rotation=rotation) | |
plane = bpy.context.object | |
plane.name = name | |
plane.scale[0] = scale[0] / 2 | |
plane.scale[1] = scale[1] / 2 | |
# subdivide if needed | |
if subdivision_kws['subdivide']: | |
# Enter edit mode to further subdivide | |
bpy.ops.object.mode_set(mode='EDIT') | |
bm = bmesh.from_edit_mesh(plane.data) | |
# Subdivide the mesh further to get 80 sections | |
bmesh.ops.subdivide_edges(bm, edges=bm.edges, cuts=subdivision_kws['cuts'], use_grid_fill=True,) | |
bmesh.update_edit_mesh(plane.data) | |
bpy.ops.object.mode_set(mode='OBJECT') | |
return plane | |
# Function to group a subset of plane vertices | |
def group_top_vertices(plane, group_name, epsilon=0.01): | |
# Create a vertex group for the top central vertices | |
vertex_group = plane.vertex_groups.new(name=group_name) | |
# Get the top vertices (considering Z direction) | |
top_z_coord = np.min([v.co.x for v in plane.data.vertices]) | |
max_y_coord = np.max([v.co.y for v in plane.data.vertices]) | |
mid_y_coord = np.mean([v.co.y for v in plane.data.vertices]) | |
y_selection_lim = (max_y_coord - mid_y_coord) / 3 | |
top_verts = [ | |
v for v in plane.data.vertices | |
if ( | |
(np.abs(v.co.x - top_z_coord) < epsilon) and | |
(np.abs(v.co.y - mid_y_coord) <= y_selection_lim) | |
) | |
] | |
# Assign the top vertices to the vertex group | |
for v in top_verts: | |
vertex_group.add([v.index], 1.0, 'ADD') | |
return vertex_group | |
# Function to set a hook modifier on vertex group | |
def set_hook_modifier(object_to_be_hooked, vertex_group_name, hook_object): | |
# Add a hook modifier to the plane and assign the top vertices | |
hook_modifier = object_to_be_hooked.modifiers.new(name="Hook", type='HOOK') | |
hook_modifier.object = hook_object | |
hook_modifier.vertex_group = vertex_group_name | |
return hook_modifier | |
# Function to add cloth physics to object | |
def add_cloth_physics(object, pin_vertex_group=None, vertex_mass=1): | |
# Ensure the object is a mesh | |
if object.type != 'MESH': | |
print(f"Error: {object.name} is not a mesh object.") | |
return | |
# Add Cloth modifier | |
cloth_modifier = object.modifiers.new(name="Cloth", type='CLOTH') | |
# Optional: Set the pinning vertex group (if specified) | |
if pin_vertex_group is not None and pin_vertex_group in object.vertex_groups: | |
cloth_modifier.settings.vertex_group_mass = pin_vertex_group | |
# Default settings for cloth simulation (you can customize these as needed) | |
cloth_modifier.settings.quality = 30 # Quality of the simulation (higher is slower but more accurate) | |
cloth_modifier.settings.time_scale = 5 # Speed Multiplier | |
cloth_modifier.settings.mass = vertex_mass # Mass of the cloth | |
cloth_modifier.settings.air_damping = 1 # Air resistance | |
cloth_modifier.settings.tension_stiffness = 10 # Stiffness of the cloth | |
cloth_modifier.settings.compression_stiffness = 10 | |
cloth_modifier.settings.shear_stiffness = 5 | |
cloth_modifier.settings.bending_stiffness = 0.2 | |
# Enable self-collision to prevent parts of the cloth from intersecting itself | |
cloth_modifier.collision_settings.use_self_collision = True | |
cloth_modifier.collision_settings.self_friction = 5 # Friction during self-collision | |
cloth_modifier.collision_settings.self_distance_min = 0.015 # Minimum distance between self-collisions (tweak if needed) | |
return cloth_modifier | |
# Function to add collision physics to object | |
def add_collision_physics(object): | |
# Ensure the object is a mesh | |
if object.type != 'MESH': | |
print(f"Error: {object.name} is not a mesh object.") | |
return | |
# Add collision modifier | |
collision_modifier = object.modifiers.new(name="Collision", type='COLLISION') | |
# Set default collision settings (you can customize these) | |
collision_modifier.settings.thickness_outer = 0.8 # Outer thickness of collision boundary | |
collision_modifier.settings.thickness_inner = 0.8 # Inner thickness of collision boundary | |
collision_modifier.settings.damping = 0.5 # Damping factor for collision | |
collision_modifier.settings.cloth_friction = 0.5 # Friction factor for collision | |
# Function to add subdivision modifier | |
def add_subdivision_modifier(obj, levels=3): | |
# Ensure the object is a mesh | |
if obj.type != 'MESH': | |
print(f"Error: {obj.name} is not a mesh object.") | |
return | |
# Add Cloth modifier | |
subdivision_modifier = obj.modifiers.new(name="Subdivision", type='SUBSURF') | |
# Set subdivision settings | |
subdivision_modifier.levels = levels # Viewport levels | |
subdivision_modifier.render_levels = levels # render levels | |
def add_cloth_thickness(obj, thickness=0.001): | |
# Ensure the object is a mesh | |
if obj.type != 'MESH': | |
print(f"Error: {obj.name} is not a mesh object.") | |
return | |
# Add a Solidify modifier for thickness | |
solidify_mod = obj.modifiers.new(name="Solidify", type='SOLIDIFY') | |
# Set the thickness | |
solidify_mod.thickness = thickness | |
# Optional: Set other solidify settings | |
solidify_mod.offset = -1.0 | |
solidify_mod.use_rim = True # Fill in any open edges | |
# Function to apply auto smooth shading | |
def enable_auto_smooth_shading(obj, angle=30.0): | |
# Ensure the object is selected and active | |
bpy.context.view_layer.objects.active = obj | |
obj.select_set(True) | |
# Enable Auto Smooth for the object | |
bpy.ops.object.shade_auto_smooth() | |
# Function to create a silk like material | |
def create_silk_material(): | |
# Create a new material | |
silk_material = bpy.data.materials.new(name="Royal_Red_Silk") | |
# Enable the use of nodes (necessary for Principled BSDF) | |
silk_material.use_nodes = True | |
# Get the material's node tree | |
nodes = silk_material.node_tree.nodes | |
links = silk_material.node_tree.links | |
# Clear any default nodes | |
for node in nodes: | |
nodes.remove(node) | |
# Add a Principled BSDF shader node | |
bsdf = nodes.new(type='ShaderNodeBsdfPrincipled') | |
bsdf.location = (0, 0) | |
# Set the base color to royal red | |
bsdf.inputs['Base Color'].default_value = (0.3, 0.01, 0.03, 1) # RGB + Alpha (Red with a bit of saturation) | |
# Set a low roughness for silk-like smoothness | |
bsdf.inputs['Roughness'].default_value = 0.5 # Lower roughness makes the surface shiny | |
# Set a low roughness for silk-like material | |
bsdf.inputs['Metallic'].default_value = 0.7 | |
# # Specular and sheen-related properties for silk fabric | |
# bsdf.inputs['Specular IOR Level'].default_value = 0.8 # Adjust specular reflection | |
# bsdf.inputs['Specular Tint'].default_value = (1, 0.1, 0.1, 1) # White specular reflection | |
# # Anisotropy for directional fabric-like sheen | |
# bsdf.inputs['Anisotropic'].default_value = 0.8 # High anisotropy | |
# bsdf.inputs['Anisotropic Rotation'].default_value = 0.2 # Low anisotropic rotation | |
# # Sheen settings for fabric glow | |
# bsdf.inputs['Sheen Weight'].default_value = 0.5 # Moderate sheen | |
# bsdf.inputs['Sheen Roughness'].default_value = 0.1 # Smooth sheen | |
# bsdf.inputs['Sheen Tint'].default_value = (0.9, 0.05, 0.05, 1) # Slight red tint to sheen | |
# Add an Output node and link it to the BSDF | |
output = nodes.new(type='ShaderNodeOutputMaterial') | |
output.location = (400, 0) | |
links.new(bsdf.outputs['BSDF'], output.inputs['Surface']) | |
return silk_material | |
# Function to assign material to object | |
def assign_material_to_object(material, obj): | |
# Ensure the object is a mesh | |
if obj.type != 'MESH': | |
print(f"Error: {obj.name} is not a mesh object.") | |
return | |
# Assign the material to the object | |
if obj.data.materials: | |
# If there are materials, assign to first slot | |
obj.data.materials[0] = material | |
else: | |
# If no materials, append a new one | |
obj.data.materials.append(material) | |
# Function to add a camera | |
def create_camera(camera_location, target_location): | |
camera_data = bpy.data.cameras.new(name="Camera") | |
camera_object = create_data_object("Camera", camera_data) | |
camera_object.location = camera_location | |
# Add a target (empty object) for the camera to focus on | |
target_object = create_data_object("CameraTarget", None) | |
target_object.location = target_location | |
# Add the "Track To" constraint to the camera to make it always look at the target | |
constraint = camera_object.constraints.new(type='TRACK_TO') | |
constraint.target = target_object | |
# parent to target to aid camera rotations | |
camera_object.parent = target_object | |
# Set the camera as the active camera for the scene | |
bpy.context.scene.camera = camera_object | |
return camera_object, target_object | |
# Function to add a sunlight | |
def create_sunlight(name, rotation, strength=1.0): | |
# Add a new sun light to the scene | |
light_data = bpy.data.lights.new(name=name, type='SUN') | |
light_object = create_data_object(name, light_data) | |
# Position the light in the scene | |
light_object.rotation_euler = rotation | |
# Set the strength of the light | |
light_data.energy = 1.0 # You can adjust this value to make the scene brighter or dimmer | |
return light_object | |
# Function to change background color and lighting | |
def set_background(background_color, ambient_light_strength=1.0): | |
bpy.context.scene.world.use_nodes = True | |
bg = bpy.context.scene.world.node_tree.nodes.get("Background") | |
bg.inputs[0].default_value = (0, 0, 0, 1) # Change to desired RGB (1,1,1) for white ambient light | |
bg.inputs[1].default_value = 0.5 # Strength of the ambient light | |
# Function to set the resolution of renders | |
def set_render_properties(resolution_x=1920, resolution_y=1080, resolution_percentage=100, fps=24): | |
bpy.context.scene.render.resolution_x = resolution_x # Width in pixels | |
bpy.context.scene.render.resolution_y = resolution_y # Height in pixels | |
bpy.context.scene.render.resolution_percentage = resolution_percentage # Scale | |
bpy.context.scene.render.fps = fps # frame rate | |
# Render a single frame to a PNG | |
def render_single_frame(frame, filepath, engine='BLENDER_EEVEE_NEXT', transparent=True): | |
# Set the current frame to render | |
bpy.context.scene.frame_set(frame) | |
# Set render output file path | |
bpy.context.scene.render.filepath = filepath | |
# Set render engine to 'CYCLES' or 'BLENDER_EEVEE_NEXT' | |
bpy.context.scene.render.engine = engine | |
# Set the file format to PNG | |
bpy.context.scene.render.image_settings.file_format = 'PNG' | |
# Make the background transparent | |
if transparent: | |
bpy.context.scene.render.film_transparent = True | |
bpy.context.scene.render.image_settings.color_mode = 'RGBA' # Enable alpha (transparency) | |
# Trigger the render and save the image | |
bpy.ops.render.render(write_still=True) | |
# Render an animation to MP4 | |
def render_animation(filepath, engine='BLENDER_EEVEE_NEXT', render=False): | |
# Set render output file path | |
bpy.context.scene.render.filepath = filepath | |
# Set render engine to 'CYCLES' or 'BLENDER_EEVEE_NEXT' | |
bpy.context.scene.render.engine = engine | |
bpy.context.scene.render.use_persistent_data = True # Added to speedup rendering | |
if engine=='CYCLES': | |
bpy.context.scene.cycles.adaptive_threshold = 0.1 # Added to speedup rendering | |
# Set the file format to FFmpeg video | |
bpy.context.scene.render.image_settings.file_format = 'FFMPEG' | |
# Set the codec and encoding options | |
bpy.context.scene.render.ffmpeg.format = 'MPEG4' | |
bpy.context.scene.render.ffmpeg.codec = 'H264' | |
bpy.context.scene.render.ffmpeg.constant_rate_factor = 'HIGH' | |
# Render the animation | |
if render: | |
bpy.ops.render.render(animation=True) | |
# Render an animation with transparency to WEBM | |
def render_transparent_animation(filepath, engine='BLENDER_EEVEE_NEXT'): | |
# Set render output file path | |
bpy.context.scene.render.filepath = filepath | |
# Set render engine to 'CYCLES' or 'BLENDER_EEVEE_NEXT' | |
bpy.context.scene.render.engine = engine | |
bpy.context.scene.render.use_persistent_data = True # Added to speedup rendering | |
if engine=='CYCLES': | |
bpy.context.scene.cycles.adaptive_threshold = 0.1 # Added to speedup rendering | |
# Enable transparency for the world background | |
bpy.context.scene.render.film_transparent = True | |
# Set the file format to FFmpeg video | |
bpy.context.scene.render.image_settings.file_format = 'FFMPEG' | |
# Set the codec and encoding options | |
bpy.context.scene.render.ffmpeg.format = 'WEBM' # or 'QUICKTIME' for .mov | |
bpy.context.scene.render.ffmpeg.codec = 'VP9' # 'WEBM' or 'QTRLE' for QuickTime | |
# Ensure the color mode is RGBA (for transparency) | |
bpy.context.scene.render.image_settings.color_mode = 'RGBA' | |
# Render the animation | |
bpy.ops.render.render(animation=True) | |
# loading brain data | |
################################################################################ | |
################################################################################ | |
left_vertices_white, left_triangles_white = load_cortical_mesh('white') | |
left_vertices_pial, left_triangles_pial = load_cortical_mesh('inflated') | |
dscalar = nib.load(cbu.cifti_template_file) | |
brain_models = [x for x in dscalar.header.get_index_map(1).brain_models] | |
left_cortical_surface_model, right_cortical_surface_model = brain_models[0], brain_models[1] | |
gradients = nib.load("/mnt/local_storage/Research/Codes/fMRI/DataStore/Templates/principal_gradient/hcp.gradients.dscalar.nii") | |
# Blender scripting starts here | |
################################################################################ | |
################################################################################ | |
# First clean the scene | |
blender_clean_up() | |
################################################################################ | |
################################################################################ | |
################################################################################ | |
# Frame timing | |
start_frame = 1 | |
end_frame = 200 | |
frame_setup(start_frame=start_frame, end_frame=end_frame) | |
################################################################################ | |
################################################################################ | |
################################################################################ | |
# Now create the mesh | |
mesh, left_cortex_obj = create_mesh_object( | |
left_vertices_white, left_triangles_white, mesh_name="MyMesh", object_name="MyObject" | |
) | |
################################################################################ | |
################################################################################ | |
################################################################################ | |
# Apply smooth shading | |
shade_smooth(left_cortex_obj) | |
################################################################################ | |
################################################################################ | |
################################################################################ | |
# handling colors | |
# first create vertex color layer | |
vertex_color_layer = add_vertex_colors_to_layer( | |
mesh=mesh, layer_name="Principal_Gradient", | |
vertex_colors=generate_vertex_colors(gradients.get_fdata()[0], colormap=plt.cm.autumn, alpha=0.5), | |
) | |
# then apply to a material for an object | |
material = assign_object_material_with_color( | |
obj=left_cortex_obj, color_layer_name="Principal_Gradient", | |
metallic=0.8, roughness=0.6 | |
) | |
################################################################################ | |
################################################################################ | |
################################################################################ | |
# create a cloth | |
# first create the plane | |
cuts=100 | |
cloth = create_plane( | |
name="ClothPlane", scale=(200, 300), | |
location=( | |
left_vertices_white[:,0].min() - 1, | |
left_vertices_white[:,1].mean(), | |
left_vertices_white[:,2].mean() | |
), rotation=(0,math.radians(90),0), subdivision_kws={'subdivide': True, 'cuts': cuts} | |
) | |
# select some of the vertices from the top of the cloth and make vertex group | |
cloth_top_group_name = "cloth_top" | |
cloth_top = group_top_vertices(plane=cloth, group_name=cloth_top_group_name) | |
# create a hook object | |
cloth_hook = create_empty( | |
name="cloth_hook", | |
location=( | |
left_vertices_white[:,0].min() - 1, | |
left_vertices_white[:,1].mean(), | |
left_vertices_white[:,2].mean() + 100 | |
) | |
) | |
# Add a hook modifier to the plane and assign the top vertices | |
hook_modifier = set_hook_modifier( | |
object_to_be_hooked=cloth, | |
vertex_group_name=cloth_top_group_name, | |
hook_object=cloth_hook | |
) | |
# Add cloth physics to the plane | |
cloth_modifier = add_cloth_physics(object=cloth, pin_vertex_group=cloth_top_group_name, vertex_mass=(80/cuts)) | |
# Subdivide the cloth after physics to render realistic | |
add_subdivision_modifier(obj=cloth, levels=3) | |
# Add collision physics to the brain | |
add_collision_physics(object=left_cortex_obj) | |
# Auto smooth shading for the cloth | |
enable_auto_smooth_shading(cloth) | |
# Make the cloth a bit thicker | |
add_cloth_thickness(cloth) | |
# Create the silk material | |
silk_material = create_silk_material() | |
# Make the cloth look silky | |
assign_material_to_object(silk_material, cloth) | |
# Use hook to animate the cloth | |
cloth_hook.keyframe_insert(data_path="location", frame=10) | |
cloth_hook.location = ( | |
0, | |
left_vertices_white[:,1].mean(), | |
left_vertices_white[:,2].mean() + 100 | |
) | |
cloth_hook.keyframe_insert(data_path="location", frame=40) | |
cloth_hook.location = ( | |
40, | |
left_vertices_white[:,1].mean(), | |
left_vertices_white[:,2].mean() + 0 | |
) | |
cloth_hook.keyframe_insert(data_path="location", frame=100) | |
cloth_hook.location = ( | |
1600, | |
left_vertices_white[:,1].mean(), | |
left_vertices_white[:,2].mean() + 0 | |
) | |
cloth_hook.keyframe_insert(data_path="location", frame=200) | |
################################################################################ | |
################################################################################ | |
################################################################################ | |
# Add a camera object | |
camera_object, target_object = create_camera( | |
camera_location=(-260, 0, 0), | |
target_location=left_vertices_white.mean(0) | |
) | |
# camera animation | |
for i in range(end_frame): | |
# Keyframe the rotation of the empty (animate around the Z axis) | |
target_object.rotation_euler = ( | |
0, | |
np.cos(2 * np.pi * i / end_frame)**3 * math.radians(4), | |
np.sin(4 * np.pi * i / end_frame) * math.radians(2) | |
) | |
target_object.keyframe_insert(data_path="rotation_euler", frame=i + 1) | |
################################################################################ | |
################################################################################ | |
################################################################################ | |
# add multiple surrounding light sources | |
light_count = 4 | |
for i in range(light_count + 1): | |
# Add a new sun light to the scene | |
create_sunlight( | |
name=f"SunLight_{i}", | |
rotation=(np.pi / 2, 0, (-np.pi * (i / light_count) / 2) - (np.pi/2)), | |
strength=1.0 | |
) | |
# and a light source from above | |
create_sunlight( | |
name=f"SunLight_{light_count + 1}", | |
rotation=(0, 0, 0), | |
strength=2.0 | |
) | |
################################################################################ | |
print("here") | |
################################################################################ | |
################################################################################ | |
# Set the world background color | |
set_background(background_color=(0, 0, 0, 1), ambient_light_strength=0.5) | |
################################################################################ | |
################################################################################ | |
################################################################################ | |
# Set render resolution | |
set_render_properties(resolution_x=1000, resolution_y=720) | |
################################################################################ | |
################################################################################ | |
################################################################################ | |
# render a single frame and store as image | |
# render_single_frame(frame=1, filepath="/mnt/local_storage/Research/Codes/fMRI/DataStore/tmp/blender/rendered_image.png", engine='BLENDER_EEVEE_NEXT') | |
################################################################################ | |
################################################################################ | |
################################################################################ | |
# render animation | |
render_animation(filepath="/mnt/local_storage/Research/Codes/fMRI/DataStore/tmp/blender/rendered_movie.mp4", engine='BLENDER_EEVEE_NEXT') | |
################################################################################ | |
################################################################################ | |
################################################################################ | |
# bpy.ops.screen.animation_play() | |
################################################################################ | |
################################################################################ | |
################################################################################ | |
# # Finally save the project | |
# # Specify the file path where you want to save the Blender project | |
# file_path = "/mnt/local_storage/Research/Codes/fMRI/DataStore/tmp/blender/project_cloth.blend" | |
# # Save the current Blender file | |
# bpy.ops.wm.save_as_mainfile(filepath=file_path) | |
################################################################################ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Check out this video.
rendered_movie_4.2_cloth_5.mp4