I have a plane floating on an ocean. My script outputs a CSV file with the position of the plane's four corners. How can I use an operator to fill this CSV with the position of the points over time, as the plane floats and moves on the ocean waves? For example, in intervals of 0.1 second. Here is the code I have so far.

import bpy

outputFile = 'G:/BLENDER/ocean/plane_coordinates.csv'

verts = [ bpy.context.object.matrix_world * v.co for v in    bpy.context.object.data.vertices ]

csvLines = [ ";".join([ str(v) for v in co ]) + "\n" for co in verts ]

f = open( outputFile, 'w' )
f.writelines( csvLines )

It works fine as it is. I need to find a way to make it reiterate every 0.1 sec and have the CSV file fill up as it goes. Let's say for 200 sec total. Thank you all very much.


If modifiers are used...

If the plane is only transformed, ie the object is only translated rotated or scaled then the object world times vertex coord will give the global location of the vertex.

ob.matrix_world * v.co for v in ob.data.vertices 

If however a modifier is used, then this is a different kettle of fish, as these modifications will not show from the base mesh vertex locations.

I propose using a method similar to https://blender.stackexchange.com/a/57038/15543 Parent an empty in global space to each vertex, loop thru frames and export the empty locations. Remove the empties to clean up.

Here is an example method, prints the vertex locations of context object mesh for each 0.1 seconds elapsed from from animation start frame to end frame. (change the print to write to CSV file to suit. The print shows the time in seconds, frame (as a float), vertex index and coordinate)

Note the use of bpy.utils.time_from_frame and time_to_frame and scene.frame_set(f, subframe=f % 1) to set the frame to match time, using just the frame or the time (it worries about all the nuances of fps for us.

import bpy
from bpy.utils import time_to_frame, time_from_frame

def export_vert_locs(context):
    scene = context.scene
    obj = context.object
    me = obj.data
    empties = []
    # add empties to verts
    for v in me.vertices:
        empty = bpy.data.objects.new("Empty", None)
        empty.parent = obj
        empty.parent_type = 'VERTEX'
        empty.parent_vertices = [v.index] * 3
        empties.append((v.index, empty))

    t = time_from_frame(scene.frame_start).total_seconds()
    end = time_from_frame(scene.frame_end).total_seconds()
    while t < end:
        f = time_to_frame(t)
        scene.frame_set(int(f), subframe=f % 1)

        for index, empty in empties:
            print("frame %3.1f" % f, "%4.2f secs" % t,  "Vert %d location: " % index, empty.matrix_world.to_translation())
        t += 0.1
    # remove the empties
    for i, empty in empties:

# test call

Sample output, frame rate 24 fps, start frame 1. every 0.1 seconds is 2.4 frames.

frame 1.0 0.04 secs Vert 0 location:  <Vector (-1.0000, -1.0000, 0.0000)>
frame 1.0 0.04 secs Vert 1 location:  <Vector (1.0000, -1.0000, 0.0000)>
frame 1.0 0.04 secs Vert 2 location:  <Vector (-1.0000, 1.0000, 0.0000)>
frame 1.0 0.04 secs Vert 3 location:  <Vector (1.0000, 1.0000, 0.0000)>
frame 3.4 0.14 secs Vert 0 location:  <Vector (-0.9997, -0.9998, 5.9995)>
frame 3.4 0.14 secs Vert 1 location:  <Vector (1.0003, -0.9998, 5.9995)>
frame 3.4 0.14 secs Vert 2 location:  <Vector (-0.9997, 1.0002, 5.9995)>
frame 3.4 0.14 secs Vert 3 location:  <Vector (1.0003, 1.0002, 5.9995)>
frame 5.8 0.24 secs Vert 0 location:  <Vector (-0.9868, -0.9893, 5.9774)>
frame 5.8 0.24 secs Vert 1 location:  <Vector (1.0132, -0.9893, 5.9774)>

Alternatives to getting modified vert locations would require using ob_to_mesh() each frame or bmesh.from_object(...) to get the deformed locations.


Patched together from some previous scripts I had worked on. Please note there may be PEP8 style issues. Also note that since you had no example of how you wished to separate the time interval (or corresponding animation frame number) with regards to the location coordinates I just added it as a line to the csv file prior to the vertex coordinates. This script will add a panel in the 3d viewport which will allow you to choose a directory path and file name as well as a property to select different time intervals. The attached script was tested on Windows based machine and may require alteration for Mac or Linux (file location syntax). Also since this was just patched together there may be lines that can be safely removed.

import bpy
import os
from bpy.types import Operator
from bpy.types import Panel

bl_info = {
    "name": "Blank1",
    "author": "Your Name Here",
    "version": (1, 0),
    "blender": (2, 79, 0),
    "location": "View3D > Tools panel 'T' > Blank1 > Blank2",
    "description": "",
    "warning": "",
    "wiki_url": "",
    "category": "",

def initprop():
    bpy.types.Scene.timefloat = bpy.props.FloatProperty(
    bpy.types.Scene.fpath = bpy.props.StringProperty(
        name="File Path",
        description="Directory to save to",
    bpy.types.Scene.fname = bpy.props.StringProperty(
        name="Add File Name",

def delprop():
    del bpy.types.Scene.timefloat
    del bpy.types.Scene.fpath
    del bpy.types.Scene.fname

def begin_files(scene):
    f = open(scene.fpath + scene.fname, 'w')

def append_files(scene, frame):
    verts = [bpy.context.object.matrix_world * v.co
        for v in bpy.context.object.data.vertices]
    csvLines = [";".join([str(v) for v in co]) + "\n" for co in verts]

    f = open(scene.fpath + scene.fname, 'a')
    f.write("frame # :%s" % frame)

    f = open(scene.fpath + scene.fname, 'a')

class FileGenTool():
    def main(context):
        scene = context.scene
        totalframes = scene.frame_end-scene.frame_start+1
        totaltime = (totalframes//scene.render.fps)
        frameinterval = int(totalframes/scene.render.fps*scene.timefloat)

        for frame in range(scene.frame_start,
            scene.frame_end+1, frameinterval):
                append_files(scene, frame)

class FileGenButton(Operator):
    """Execute generation"""
    bl_idname = "file.basegen_operator"
    bl_label = "Generate"

    def poll(cls, context):
        scene = context.scene
        totalframes = scene.frame_end-scene.frame_start+1
        objvalid = context.active_object and context.selected_objects != []
        framesvalid = (scene.frame_end-scene.frame_start+1) > 0
        frameintvalid = (totalframes//scene.render.fps) > 0
        filevalid = (scene.fpath is not "") and (
            scene.fname is not "")
        return (objvalid and framesvalid and
            frameintvalid and filevalid) is True

    def execute(self, context):
        return {'FINISHED'}

class VIEW3D_PT_Blank1_Blank2(Panel):
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'TOOLS'
    bl_category = "Blank1"
    bl_context = "objectmode"
    bl_label = "Blank2"

    def draw(self, context):
        scene = context.scene
        layout = self.layout
        box = layout.box()
        col = box.column(align=True)
        col.label(text="File info:")
        col.prop(scene, "fpath", text="Directory Path")
        col.prop(scene, "fname", text="File Name")

        box = layout.box()
        col = box.column(align=True)
        totalframes = scene.frame_end-scene.frame_start+1
        totaltime = (totalframes//scene.render.fps)
        frameinterval = int(totalframes/scene.render.fps*scene.timefloat)
        col.label(text="Approximate time interval")
        col.prop(scene, "timefloat", text="Time in seconds :")

        box = layout.box()
        col = box.column(align=True)
        col.label(text="related scene info:")
        col.label(text="total frames : %s" % totalframes)
        col.label(text="frame rate : %s" % scene.render.fps)
        col.label(text="total time : %s" % totaltime)
        col.label(text="frame interval : %s" % frameinterval)
        if context.active_object and context.selected_objects != []:
            col.label(text="Active object : %s" % context.active_object.name)

        col = layout.column(align=True)
        col.operator("file.basegen_operator", text="Generate CSV")

def register():

def unregister():

if __name__ == "__main__":

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.