.blend file format of Blender: Go parsing library

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.

Application

Blender

File extension

blend

KS implementation details

License: CC0-1.0

References

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.

Go source code to parse .blend file format of Blender

blender_blend.go

// 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
 */