| __author__ = 'thoth'
import struct
import os.path
# http://blender.stackexchange.com/questions/15577/how-to-extract-convert-data-from-blender-cache-files-bphys-into-a-human-readabl
class CacheRowReader:
    # BPHYS_DATA_* from DNA_object_force.h
    column_words = (1,3,3,4,3,1,3,5)
    column_formats = ("i", "fff", "fff", "ffff", "fff", "f", "fff", "fffff")
    def __init__(self, flavor, count, data_type_flags):
        self.count = count
        self.flavor = flavor
        self.data_type_flags = data_type_flags
        rec_len=0
        unpack_format = ""
        for i in range(len(CacheRowReader.column_words)):
            if 0 != (data_type_flags&(1<<i)):
                rec_len += 4 * CacheRowReader.column_words[i]
                unpack_format += CacheRowReader.column_formats[i]
        self.rec_len = rec_len
        self.unpack_format = unpack_format
    @classmethod
    def parse(cls, f):
        magic = f.read(8)
        if magic != b'BPHYSICS':
            raise Exception("not a blender physics cache")
        flavor = f.read(12)
        (flavor,count,data_type_flags) = struct.unpack("iii", flavor)
        #print( "%d\t%d\t%d"%(flavor,count,data_type_flags))
        return CacheRowReader(flavor, count, data_type_flags)
    def read_row(self, f):
        """
        :param f: a file opened with "rb" (binary) mode
        :return:
        """
        raw = f.read(self.rec_len)
        if raw is None or len(raw)==0:
            return None
        if len(raw) != self.rec_len:
            raise Exception("short read (%d<%d)"%( len(raw), self.rec_len))
        columns = struct.unpack(self.unpack_format, raw)
        rval = dict()
        cursor =0
        # a lot of these clauses are untested.  Feel free to leave me a comment on the stackexchange answer.
        if 0 != (self.data_type_flags&1):
            rval['index'] = columns[cursor]
            cursor +=1
        if 0 != self.data_type_flags&2:
            rval['location'] = columns[cursor:cursor+3]
            rval['smoke_low'] = rval['location']
            cursor +=3
        if 0 != self.data_type_flags&4:
            rval['velocity'] = columns[cursor:cursor+3]
            rval['smoke_high'] = rval['velocity']
            cursor +=3
        if 0 != self.data_type_flags&8:
            rval['dynamicpaint'] = rval['rotation'] = columns[cursor:cursor+4]
            cursor +=4
        if 0 != self.data_type_flags&0x10:
            rval['xconst'] = rval['avelocity'] = columns[cursor:cursor+3]
            cursor +=3
        if 0 != self.data_type_flags&0x20:
            rval['size'] = columns[cursor]
            cursor +=1
        if 0 != self.data_type_flags&0x40:
            rval['times'] = columns[cursor:cursor+3]
            cursor +=3
        if 0 != self.data_type_flags&0x80:
            rval['boids'] = columns[cursor:cursor+5]
            cursor +=5
        return rval
def dump_one_file(fname):
    f = open(fname, "rb")
    reader = CacheRowReader.parse(f)
    print("%d\t%d\t%d"%(reader.flavor, reader.count, reader.data_type_flags))
    while True:
        row = reader.read_row(f)
        if row is None:
            break
        print(row)
def dump_times_file(fname):
    f = open(fname, "rb")
    reader = CacheRowReader.parse(f)
    while True:
        row = reader.read_row(f)
        if row is None:
            break
        print(row)
def maybe_discard(scn, name):
    obj = bpy.data.objects.get(name)
    if obj is None:
        return
    obj.name = "discard"
    try:
        scn.objects.unlink(obj)
    except:
        pass
def curve_from_particle_track(scn, path_pattern):
    i=0
    tracks = []
    while True:
        fname = path_pattern%i
        #print(fname)
        if not os.path.isfile(fname):
            break
        f = open(fname, "rb")
        if f is None:
            break
        reader = CacheRowReader.parse(f)
        if 0 == (reader.data_type_flags &1):
            pass  # this is probably the first cache file with only time info, let's ignore it
        else:
            while True:
                row = reader.read_row(f)
                if row is None: break
                #print(row)
                idx = row['index']
                while len(tracks) <= idx:
                    tracks.append( [] )
                tracks[idx].append(row['location'])
        f.close()
        i+=1
        #print(i)
    #print (tracks)
    name = "tracks"
    curve = bpy.data.curves.new(name, 'CURVE')
    for i in range(len(tracks)):
        locations = tracks[i]
        if len(locations) < 1:
            continue
        spline = curve.splines.new('POLY')
        spline.points.add(len(locations) - len(spline.points))
        for j in range(len(locations)):
            #print(locations[j])
            spline.points[j].co[0:3] = locations[j]
    maybe_discard(scn, name)
    obj = bpy.data.objects.new(name, curve)
    scn.objects.link(obj)
if True:
    fname = "/home/thoth/art/ornaments-in-space/blendcache_ornaments-in-space/loop_000001_00.bphys"
    #fname = "/home/thoth/art/ornaments-in-space/blendcache_ornaments-in-space/loop_000000_00.bphys"
    #fname = "/home/thoth/art/ornaments-in-space/blendcache_ornaments-in-space/7061727469636C6520656D69747465722E303031_000000_00.bphys"
    dump_one_file(fname)
elif True:
    dump_one_file("/var/tmp/blendcache_particles/bacon_000000_00.bphys")
elif True:
    dump_one_file("/var/tmp/blendcache_particle/pants_000045_00.bphys")
elif False:
    dump_times_file("/var/tmp/blendcache_particle/pants_000000_00.bphys")
else:
    curve_from_particle_track(bpy.context.scene, "/var/tmp/blendcache_particle/pants_%06d_00.bphys")
 | 
Blender python API quick-start
Syntax highlighting by Pygments.