Search code examples
pythonanimationblendervertices

Extracting Vertex Coordinates for Each Frame with Cloth Modifier Applied


I want to create an animation where I extract vertex coordinates, store them, and reanimate the object without physics using the stored coordinate data. I'm currently capturing vertex coordinates for each frame and saving them to a file:

def get_vertices_info(obj_name):
    obj = bpy.data.objects[obj_name]
    obj_data = obj.data
    vertices_data = {}

    for vertex_index, vertex in enumerate(obj_data.vertices):
        vertex_data = []

        for frame in range(bpy.context.scene.frame_start, bpy.context.scene.frame_end + 1):
            bpy.context.scene.frame_set(frame)
            vertex_world_co = obj.matrix_world @ vertex.co
            frame_data = tuple(vertex_world_co)
            vertex_data.append(frame_data)

        vertices_data[f"Vertex {vertex_index + 1}"] = vertex_data

    return vertices_data
#(wind_strength, wind_directions, wind_coordinates) = get_wind_info("Wind")
vertices_data = get_vertices_info("Plane")

data = {
    "wind": {
        "strength": wind_strength,
        "directions": wind_directions,
        "coordinates": wind_coordinates
    },
    "verticies": vertices_data
}

And then animate on another scene:

new_scene = bpy.data.scenes.new("Animated Scene")

original_obj = bpy.data.objects['Plane']
flag = original_obj.copy()
flag.data = original_obj.data.copy()
flag.modifiers.remove(flag.modifiers.get("Cloth"))

new_scene.collection.objects.link(flag)
bpy.context.window.scene = new_scene

vertex_data = list(data["meshes"].values())

# https://blender.stackexchange.com/questions/36902/how-to-keyframe-mesh-vertices-in-python
def insert_keyframe(fcurves, frame, values):
    for fcu, val in zip(fcurves, values):
        fcu.keyframe_points.insert(frame, val, options={'FAST'})

obj = bpy.context.active_object
mesh = flag.data
action = bpy.data.actions.new("MeshAnimation")

mesh.animation_data_create()
mesh.animation_data.action = action

data_path = "vertices[%d].co"

values = vertex_data
frames = list(range(0,len(values[0]) + 1))

for i,v in enumerate(mesh.vertices):
    fcurves = [action.fcurves.new(data_path % v.index, index =  i) for i in range(3)]
    co_rest = v.co

    for t, value in zip(frames, values[i]):
        insert_keyframe(fcurves, t, value)

If I collect data from an object without a cloth modifier, it partially works.(probably i messed up with the coordinates directions). However, when the object has a cloth modifier applied, plane remaining stationary.

Scene collected data from. Applied rigid body: enter image description here

Animated scene using collected data: Animate with rigid body

Here's the scenario where I want to collect data (with a cloth modifier applied): enter image description here

However, when I animate with this data, nothing occurs; the plane remains static.

I tried couple of different functions as I suspect the issue lies in data collection. Here is the collected data with rigid body:

 "verticies": {
        "Vertex 1": [
            [
                0.0683371052145958,
                -1.5310348272323608,
                0.931662917137146
            ],
            [
                0.5171984434127808,
                -1.5432056188583374,
                0.40158990025520325
            ],
            ...

Here is with cloth:

"verticies": {
        "Vertex 1": [       
            [
                0.0683371052145958,
                -1.5310348272323608,
                0.931662917137146
            ],
            [
                0.0683371052145958,
                -1.5310348272323608,
                0.931662917137146
            ],

As you can see, with rigid body, vertex position changes over time but with cloth it remains static.


Solution

  • I figure it out! Thanks to this question for introducing me to bmesh

    Here is the revisited save function:

    def get_vertices_frame_by_frame(end_frame):
        data = {}
        flag_obj = bpy.data.objects.get("Flag")
        for frame in range(1,end_frame + 1):
            bpy.context.scene.frame_current = frame
            dg = bpy.context.evaluated_depsgraph_get()
    
            bm = bmesh.new()
            bm.from_object(flag_obj, dg)
    
            vert_tuples = tuple((vert.co.x, vert.co.y, vert.co.z) for vert in bm.verts)
            
            data[f"Frame-{frame}"] = vert_tuples
            
            bm.free()
        return data
    
    
    vertices_data = get_vertices_by_frame(30)
    
    data = {
        "vertices" : vertices_data
    }
    

    And load function:

    vertex_data_by_frame = data["vertices"].values()
    
    for frame_num, vertices in enumerate(vertex_data_by_frame):
        for i, v in enumerate(vertices):
            vertex = flag.data.vertices[i]
            vertex.co = v
            vertex.keyframe_insert(data_path="co", frame=frame_num + 1)