Blender is an open source suite for 3D modelling, sculpting,
animation, compositing, rendering, preparation of assets for its own
game engine and exporting to others, etc. .blend
is its own binary
format that saves whole state of suite: current scene, animations,
all software settings, extensions, etc.
Internally, .blend format is a hybrid semi-self-descriptive
format. On top level, it contains a simple header and a sequence of
file blocks, which more or less follow typical TLV
pattern. Pre-last
block would be a structure with code DNA1
, which is a essentially
a machine-readable schema of all other structures used in this file.
This page hosts a formal specification of .blend file format of Blender using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.
// Code generated by kaitai-struct-compiler from a .ksy source file. DO NOT EDIT.
import (
"github.com/kaitai-io/kaitai_struct_go_runtime/kaitai"
"bytes"
)
/**
* Blender is an open source suite for 3D modelling, sculpting,
* animation, compositing, rendering, preparation of assets for its own
* game engine and exporting to others, etc. `.blend` is its own binary
* format that saves whole state of suite: current scene, animations,
* all software settings, extensions, etc.
*
* Internally, .blend format is a hybrid semi-self-descriptive
* format. On top level, it contains a simple header and a sequence of
* file blocks, which more or less follow typical [TLV
* pattern](https://en.wikipedia.org/wiki/Type-length-value). Pre-last
* block would be a structure with code `DNA1`, which is a essentially
* a machine-readable schema of all other structures used in this file.
*/
type BlenderBlend_Endian int
const (
BlenderBlend_Endian__Be BlenderBlend_Endian = 86
BlenderBlend_Endian__Le BlenderBlend_Endian = 118
)
var values_BlenderBlend_Endian = map[BlenderBlend_Endian]struct{}{86: {}, 118: {}}
func (v BlenderBlend_Endian) isDefined() bool {
_, ok := values_BlenderBlend_Endian[v]
return ok
}
type BlenderBlend_PtrSize int
const (
BlenderBlend_PtrSize__Bits64 BlenderBlend_PtrSize = 45
BlenderBlend_PtrSize__Bits32 BlenderBlend_PtrSize = 95
)
var values_BlenderBlend_PtrSize = map[BlenderBlend_PtrSize]struct{}{45: {}, 95: {}}
func (v BlenderBlend_PtrSize) isDefined() bool {
_, ok := values_BlenderBlend_PtrSize[v]
return ok
}
type BlenderBlend struct {
Hdr *BlenderBlend_Header
Blocks []*BlenderBlend_FileBlock
_io *kaitai.Stream
_root *BlenderBlend
_parent kaitai.Struct
_f_sdnaStructs bool
sdnaStructs []*BlenderBlend_DnaStruct
}
func NewBlenderBlend() *BlenderBlend {
return &BlenderBlend{
}
}
func (this BlenderBlend) IO_() *kaitai.Stream {
return this._io
}
func (this *BlenderBlend) Read(io *kaitai.Stream, parent kaitai.Struct, root *BlenderBlend) (err error) {
this._io = io
this._parent = parent
this._root = root
tmp1 := NewBlenderBlend_Header()
err = tmp1.Read(this._io, this, this._root)
if err != nil {
return err
}
this.Hdr = tmp1
for i := 0;; i++ {
tmp2, err := this._io.EOF()
if err != nil {
return err
}
if tmp2 {
break
}
tmp3 := NewBlenderBlend_FileBlock()
err = tmp3.Read(this._io, this, this._root)
if err != nil {
return err
}
this.Blocks = append(this.Blocks, tmp3)
}
return err
}
func (this *BlenderBlend) SdnaStructs() (v []*BlenderBlend_DnaStruct, err error) {
if (this._f_sdnaStructs) {
return this.sdnaStructs, nil
}
this._f_sdnaStructs = true
this.sdnaStructs = []*BlenderBlend_DnaStruct(this.Blocks[len(this.Blocks) - 2].Body.(*BlenderBlend_Dna1Body).Structs)
return this.sdnaStructs, nil
}
/**
* DNA1, also known as "Structure DNA", is a special block in
* .blend file, which contains machine-readable specifications of
* all other structures used in this .blend file.
*
* Effectively, this block contains:
*
* * a sequence of "names" (strings which represent field names)
* * a sequence of "types" (strings which represent type name)
* * a sequence of "type lengths"
* * a sequence of "structs" (which describe contents of every
* structure, referring to types and names by index)
* @see <a href="https://archive.blender.org/wiki/index.php/Dev:Source/Architecture/File_Format/#Structure_DNA">Source</a>
*/
type BlenderBlend_Dna1Body struct {
Id []byte
NameMagic []byte
NumNames uint32
Names []string
Padding1 []byte
TypeMagic []byte
NumTypes uint32
Types []string
Padding2 []byte
TlenMagic []byte
Lengths []uint16
Padding3 []byte
StrcMagic []byte
NumStructs uint32
Structs []*BlenderBlend_DnaStruct
_io *kaitai.Stream
_root *BlenderBlend
_parent *BlenderBlend_FileBlock
}
func NewBlenderBlend_Dna1Body() *BlenderBlend_Dna1Body {
return &BlenderBlend_Dna1Body{
}
}
func (this BlenderBlend_Dna1Body) IO_() *kaitai.Stream {
return this._io
}
func (this *BlenderBlend_Dna1Body) Read(io *kaitai.Stream, parent *BlenderBlend_FileBlock, root *BlenderBlend) (err error) {
this._io = io
this._parent = parent
this._root = root
tmp4, err := this._io.ReadBytes(int(4))
if err != nil {
return err
}
tmp4 = tmp4
this.Id = tmp4
if !(bytes.Equal(this.Id, []uint8{83, 68, 78, 65})) {
return kaitai.NewValidationNotEqualError([]uint8{83, 68, 78, 65}, this.Id, this._io, "/types/dna1_body/seq/0")
}
tmp5, err := this._io.ReadBytes(int(4))
if err != nil {
return err
}
tmp5 = tmp5
this.NameMagic = tmp5
if !(bytes.Equal(this.NameMagic, []uint8{78, 65, 77, 69})) {
return kaitai.NewValidationNotEqualError([]uint8{78, 65, 77, 69}, this.NameMagic, this._io, "/types/dna1_body/seq/1")
}
tmp6, err := this._io.ReadU4le()
if err != nil {
return err
}
this.NumNames = uint32(tmp6)
for i := 0; i < int(this.NumNames); i++ {
_ = i
tmp7, err := this._io.ReadBytesTerm(0, false, true, true)
if err != nil {
return err
}
this.Names = append(this.Names, string(tmp7))
}
tmp9, err := this._io.Pos()
if err != nil {
return err
}
tmp8 := (4 - tmp9) % 4
if tmp8 < 0 {
tmp8 += 4
}
tmp10, err := this._io.ReadBytes(int(tmp8))
if err != nil {
return err
}
tmp10 = tmp10
this.Padding1 = tmp10
tmp11, err := this._io.ReadBytes(int(4))
if err != nil {
return err
}
tmp11 = tmp11
this.TypeMagic = tmp11
if !(bytes.Equal(this.TypeMagic, []uint8{84, 89, 80, 69})) {
return kaitai.NewValidationNotEqualError([]uint8{84, 89, 80, 69}, this.TypeMagic, this._io, "/types/dna1_body/seq/5")
}
tmp12, err := this._io.ReadU4le()
if err != nil {
return err
}
this.NumTypes = uint32(tmp12)
for i := 0; i < int(this.NumTypes); i++ {
_ = i
tmp13, err := this._io.ReadBytesTerm(0, false, true, true)
if err != nil {
return err
}
this.Types = append(this.Types, string(tmp13))
}
tmp15, err := this._io.Pos()
if err != nil {
return err
}
tmp14 := (4 - tmp15) % 4
if tmp14 < 0 {
tmp14 += 4
}
tmp16, err := this._io.ReadBytes(int(tmp14))
if err != nil {
return err
}
tmp16 = tmp16
this.Padding2 = tmp16
tmp17, err := this._io.ReadBytes(int(4))
if err != nil {
return err
}
tmp17 = tmp17
this.TlenMagic = tmp17
if !(bytes.Equal(this.TlenMagic, []uint8{84, 76, 69, 78})) {
return kaitai.NewValidationNotEqualError([]uint8{84, 76, 69, 78}, this.TlenMagic, this._io, "/types/dna1_body/seq/9")
}
for i := 0; i < int(this.NumTypes); i++ {
_ = i
tmp18, err := this._io.ReadU2le()
if err != nil {
return err
}
this.Lengths = append(this.Lengths, tmp18)
}
tmp20, err := this._io.Pos()
if err != nil {
return err
}
tmp19 := (4 - tmp20) % 4
if tmp19 < 0 {
tmp19 += 4
}
tmp21, err := this._io.ReadBytes(int(tmp19))
if err != nil {
return err
}
tmp21 = tmp21
this.Padding3 = tmp21
tmp22, err := this._io.ReadBytes(int(4))
if err != nil {
return err
}
tmp22 = tmp22
this.StrcMagic = tmp22
if !(bytes.Equal(this.StrcMagic, []uint8{83, 84, 82, 67})) {
return kaitai.NewValidationNotEqualError([]uint8{83, 84, 82, 67}, this.StrcMagic, this._io, "/types/dna1_body/seq/12")
}
tmp23, err := this._io.ReadU4le()
if err != nil {
return err
}
this.NumStructs = uint32(tmp23)
for i := 0; i < int(this.NumStructs); i++ {
_ = i
tmp24 := NewBlenderBlend_DnaStruct()
err = tmp24.Read(this._io, this, this._root)
if err != nil {
return err
}
this.Structs = append(this.Structs, tmp24)
}
return err
}
type BlenderBlend_DnaField struct {
IdxType uint16
IdxName uint16
_io *kaitai.Stream
_root *BlenderBlend
_parent *BlenderBlend_DnaStruct
_f_name bool
name string
_f_type bool
type string
}
func NewBlenderBlend_DnaField() *BlenderBlend_DnaField {
return &BlenderBlend_DnaField{
}
}
func (this BlenderBlend_DnaField) IO_() *kaitai.Stream {
return this._io
}
func (this *BlenderBlend_DnaField) Read(io *kaitai.Stream, parent *BlenderBlend_DnaStruct, root *BlenderBlend) (err error) {
this._io = io
this._parent = parent
this._root = root
tmp25, err := this._io.ReadU2le()
if err != nil {
return err
}
this.IdxType = uint16(tmp25)
tmp26, err := this._io.ReadU2le()
if err != nil {
return err
}
this.IdxName = uint16(tmp26)
return err
}
func (this *BlenderBlend_DnaField) Name() (v string, err error) {
if (this._f_name) {
return this.name, nil
}
this._f_name = true
this.name = string(this._parent._parent.Names[this.IdxName])
return this.name, nil
}
func (this *BlenderBlend_DnaField) Type() (v string, err error) {
if (this._f_type) {
return this.type, nil
}
this._f_type = true
this.type = string(this._parent._parent.Types[this.IdxType])
return this.type, nil
}
/**
* DNA struct contains a `type` (type name), which is specified as
* an index in types table, and sequence of fields.
*/
type BlenderBlend_DnaStruct struct {
IdxType uint16
NumFields uint16
Fields []*BlenderBlend_DnaField
_io *kaitai.Stream
_root *BlenderBlend
_parent *BlenderBlend_Dna1Body
_f_type bool
type string
}
func NewBlenderBlend_DnaStruct() *BlenderBlend_DnaStruct {
return &BlenderBlend_DnaStruct{
}
}
func (this BlenderBlend_DnaStruct) IO_() *kaitai.Stream {
return this._io
}
func (this *BlenderBlend_DnaStruct) Read(io *kaitai.Stream, parent *BlenderBlend_Dna1Body, root *BlenderBlend) (err error) {
this._io = io
this._parent = parent
this._root = root
tmp27, err := this._io.ReadU2le()
if err != nil {
return err
}
this.IdxType = uint16(tmp27)
tmp28, err := this._io.ReadU2le()
if err != nil {
return err
}
this.NumFields = uint16(tmp28)
for i := 0; i < int(this.NumFields); i++ {
_ = i
tmp29 := NewBlenderBlend_DnaField()
err = tmp29.Read(this._io, this, this._root)
if err != nil {
return err
}
this.Fields = append(this.Fields, tmp29)
}
return err
}
func (this *BlenderBlend_DnaStruct) Type() (v string, err error) {
if (this._f_type) {
return this.type, nil
}
this._f_type = true
this.type = string(this._parent.Types[this.IdxType])
return this.type, nil
}
type BlenderBlend_FileBlock struct {
Code string
LenBody uint32
MemAddr []byte
SdnaIndex uint32
Count uint32
Body interface{}
_io *kaitai.Stream
_root *BlenderBlend
_parent *BlenderBlend
_raw_Body []byte
_f_sdnaStruct bool
sdnaStruct *BlenderBlend_DnaStruct
}
func NewBlenderBlend_FileBlock() *BlenderBlend_FileBlock {
return &BlenderBlend_FileBlock{
}
}
func (this BlenderBlend_FileBlock) IO_() *kaitai.Stream {
return this._io
}
func (this *BlenderBlend_FileBlock) Read(io *kaitai.Stream, parent *BlenderBlend, root *BlenderBlend) (err error) {
this._io = io
this._parent = parent
this._root = root
tmp30, err := this._io.ReadBytes(int(4))
if err != nil {
return err
}
tmp30 = tmp30
this.Code = string(tmp30)
tmp31, err := this._io.ReadU4le()
if err != nil {
return err
}
this.LenBody = uint32(tmp31)
tmp32, err := this._root.Hdr.Psize()
if err != nil {
return err
}
tmp33, err := this._io.ReadBytes(int(tmp32))
if err != nil {
return err
}
tmp33 = tmp33
this.MemAddr = tmp33
tmp34, err := this._io.ReadU4le()
if err != nil {
return err
}
this.SdnaIndex = uint32(tmp34)
tmp35, err := this._io.ReadU4le()
if err != nil {
return err
}
this.Count = uint32(tmp35)
switch (this.Code) {
case "DNA1":
tmp36, err := this._io.ReadBytes(int(this.LenBody))
if err != nil {
return err
}
tmp36 = tmp36
this._raw_Body = tmp36
_io__raw_Body := kaitai.NewStream(bytes.NewReader(this._raw_Body))
tmp37 := NewBlenderBlend_Dna1Body()
err = tmp37.Read(_io__raw_Body, this, this._root)
if err != nil {
return err
}
this.Body = tmp37
default:
tmp38, err := this._io.ReadBytes(int(this.LenBody))
if err != nil {
return err
}
tmp38 = tmp38
this._raw_Body = tmp38
}
return err
}
func (this *BlenderBlend_FileBlock) SdnaStruct() (v *BlenderBlend_DnaStruct, err error) {
if (this._f_sdnaStruct) {
return this.sdnaStruct, nil
}
this._f_sdnaStruct = true
if (this.SdnaIndex != 0) {
tmp39, err := this._root.SdnaStructs()
if err != nil {
return nil, err
}
this.sdnaStruct = tmp39[this.SdnaIndex]
}
return this.sdnaStruct, nil
}
/**
* Identifier of the file block
*/
/**
* Total length of the data after the header of file block
*/
/**
* Memory address the structure was located when written to disk
*/
/**
* Index of the SDNA structure
*/
/**
* Number of structure located in this file-block
*/
type BlenderBlend_Header struct {
Magic []byte
PtrSizeId BlenderBlend_PtrSize
Endian BlenderBlend_Endian
Version string
_io *kaitai.Stream
_root *BlenderBlend
_parent *BlenderBlend
_f_psize bool
psize int8
}
func NewBlenderBlend_Header() *BlenderBlend_Header {
return &BlenderBlend_Header{
}
}
func (this BlenderBlend_Header) IO_() *kaitai.Stream {
return this._io
}
func (this *BlenderBlend_Header) Read(io *kaitai.Stream, parent *BlenderBlend, root *BlenderBlend) (err error) {
this._io = io
this._parent = parent
this._root = root
tmp40, err := this._io.ReadBytes(int(7))
if err != nil {
return err
}
tmp40 = tmp40
this.Magic = tmp40
if !(bytes.Equal(this.Magic, []uint8{66, 76, 69, 78, 68, 69, 82})) {
return kaitai.NewValidationNotEqualError([]uint8{66, 76, 69, 78, 68, 69, 82}, this.Magic, this._io, "/types/header/seq/0")
}
tmp41, err := this._io.ReadU1()
if err != nil {
return err
}
this.PtrSizeId = BlenderBlend_PtrSize(tmp41)
tmp42, err := this._io.ReadU1()
if err != nil {
return err
}
this.Endian = BlenderBlend_Endian(tmp42)
tmp43, err := this._io.ReadBytes(int(3))
if err != nil {
return err
}
tmp43 = tmp43
this.Version = string(tmp43)
return err
}
/**
* Number of bytes that a pointer occupies
*/
func (this *BlenderBlend_Header) Psize() (v int8, err error) {
if (this._f_psize) {
return this.psize, nil
}
this._f_psize = true
var tmp44 int8;
if (this.PtrSizeId == BlenderBlend_PtrSize__Bits64) {
tmp44 = 8
} else {
tmp44 = 4
}
this.psize = int8(tmp44)
return this.psize, nil
}
/**
* Size of a pointer; all pointers in the file are stored in this format
*/
/**
* Type of byte ordering used
*/
/**
* Blender version used to save this file
*/