import struct
import os
import random
from math import *
class BPhysWriter:
# 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, f, flags):
f.write(b"BPHYSICS")
self.f = f
self.count=0
flavor=1
f.write(struct.pack("iii", flavor, self.count, flags))
self.format = ""
for i in range(len(BPhysWriter.column_words)):
if 0 != (flags & (1<<i)):
self.format += BPhysWriter.column_formats[i]
def store(self, *elements):
i=self.count
f = self.f
self.count+=1
f.seek(12)
f.write(struct.pack("i", self.count))
f.seek(0,2)
f.write(struct.pack(self.format, *elements))
# 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 mission1():
cache_pattern = "blendcache_particle-loop/synth_%06d_00.bphys"
create_particle_cache(cache_pattern, 1, 250)
ncols=40
nrows = ncols
particleCount = ncols*nrows
def locationForParticle(pnum, frame):
u = pnum%ncols
v = floor(pnum/ncols)
x = (u/ncols -0.5)*8
y = (v/nrows -0.5)*8
r = sqrt(x*x+y*y)
loopPeriodFrames = 250
phase1 = 2*pi * frame/loopPeriodFrames
z = 0.2*cos(r*5+phase1*5)
return x,y,z
def create_particle_cache(cache_pattern, frame_start, frame_end):
fpattern = cache_pattern
with open(fpattern % 0, "wb") as out:
phys = BPhysWriter(out, 0x40)
# XXX blender 2.80 does not fully respect this file
# and will rewrite the particle timings if
# you use Alt-A to play long enough to reach the end and start a second play cycle.
life = 9999
for i in range(particleCount):
tm = i / (10 * particleCount)
phys.store(tm, tm + life, life)
for frame in range(frame_start, frame_end + 1):
fname = fpattern % frame
with open(fname, "wb") as out:
phys = BPhysWriter(out, 3)
good_count = 0
for idx in range(particleCount):
x, y, z = locationForParticle(idx, frame)
if (frame == frame_start):
print(idx, x, y, z)
phys.store(idx, x, y, z)
print("wrote %s"%fname)
def set_pad(list, idx, val):
"""list[idx] = val
even if list is short, we'll pad it with None-s until we can fit the value in the correct slot"""
while len(list) < idx:
list.append(None)
list.insert(idx, val)
#
#
#
mission1()
|
Blender python API quick-start
Syntax highlighting by Pygments.