.blend file format of Blender: C++98/STL 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.

Usage

Runtime library

All parsing code for C++98/STL generated by Kaitai Struct depends on the C++/STL runtime library. You have to install it before you can parse data.

For C++, the easiest way is to clone the runtime library sources and build them along with your project.

Code

Using Kaitai Struct in C++/STL usually consists of 3 steps.

  1. We need to create an STL input stream (std::istream). One can open local file for that, or use existing std::string or char* buffer.
    #include <fstream>
    
    std::ifstream is("path/to/local/file.blend", std::ifstream::binary);
    
    #include <sstream>
    
    std::istringstream is(str);
    
    #include <sstream>
    
    const char buf[] = { ... };
    std::string str(buf, sizeof buf);
    std::istringstream is(str);
    
  2. We need to wrap our input stream into Kaitai stream:
    #include "kaitai/kaitaistream.h"
    
    kaitai::kstream ks(&is);
    
  3. And finally, we can invoke the parsing:
    blender_blend_t data(&ks);
    

After that, one can get various attributes from the structure by invoking getter methods like:

data.hdr() // => get hdr

C++98/STL source code to parse .blend file format of Blender

blender_blend.h

#ifndef BLENDER_BLEND_H_
#define BLENDER_BLEND_H_

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

#include "kaitai/kaitaistruct.h"
#include <stdint.h>
#include <vector>

#if KAITAI_STRUCT_VERSION < 9000L
#error "Incompatible Kaitai Struct C++/STL API: version 0.9 or later is required"
#endif

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

class blender_blend_t : public kaitai::kstruct {

public:
    class dna_struct_t;
    class file_block_t;
    class dna1_body_t;
    class header_t;
    class dna_field_t;

    enum ptr_size_t {
        PTR_SIZE_BITS_64 = 45,
        PTR_SIZE_BITS_32 = 95
    };

    enum endian_t {
        ENDIAN_BE = 86,
        ENDIAN_LE = 118
    };

    blender_blend_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = 0, blender_blend_t* p__root = 0);

private:
    void _read();
    void _clean_up();

public:
    ~blender_blend_t();

    /**
     * DNA struct contains a `type` (type name), which is specified as
     * an index in types table, and sequence of fields.
     */

    class dna_struct_t : public kaitai::kstruct {

    public:

        dna_struct_t(kaitai::kstream* p__io, blender_blend_t::dna1_body_t* p__parent = 0, blender_blend_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~dna_struct_t();

    private:
        bool f_type;
        std::string m_type;

    public:
        std::string type();

    private:
        uint16_t m_idx_type;
        uint16_t m_num_fields;
        std::vector<dna_field_t*>* m_fields;
        blender_blend_t* m__root;
        blender_blend_t::dna1_body_t* m__parent;

    public:
        uint16_t idx_type() const { return m_idx_type; }
        uint16_t num_fields() const { return m_num_fields; }
        std::vector<dna_field_t*>* fields() const { return m_fields; }
        blender_blend_t* _root() const { return m__root; }
        blender_blend_t::dna1_body_t* _parent() const { return m__parent; }
    };

    class file_block_t : public kaitai::kstruct {

    public:

        file_block_t(kaitai::kstream* p__io, blender_blend_t* p__parent = 0, blender_blend_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~file_block_t();

    private:
        bool f_sdna_struct;
        dna_struct_t* m_sdna_struct;
        bool n_sdna_struct;

    public:
        bool _is_null_sdna_struct() { sdna_struct(); return n_sdna_struct; };

    private:

    public:
        dna_struct_t* sdna_struct();

    private:
        std::string m_code;
        uint32_t m_len_body;
        std::string m_mem_addr;
        uint32_t m_sdna_index;
        uint32_t m_count;
        dna1_body_t* m_body;
        bool n_body;

    public:
        bool _is_null_body() { body(); return n_body; };

    private:
        blender_blend_t* m__root;
        blender_blend_t* m__parent;
        std::string m__raw_body;
        kaitai::kstream* m__io__raw_body;

    public:

        /**
         * Identifier of the file block
         */
        std::string code() const { return m_code; }

        /**
         * Total length of the data after the header of file block
         */
        uint32_t len_body() const { return m_len_body; }

        /**
         * Memory address the structure was located when written to disk
         */
        std::string mem_addr() const { return m_mem_addr; }

        /**
         * Index of the SDNA structure
         */
        uint32_t sdna_index() const { return m_sdna_index; }

        /**
         * Number of structure located in this file-block
         */
        uint32_t count() const { return m_count; }
        dna1_body_t* body() const { return m_body; }
        blender_blend_t* _root() const { return m__root; }
        blender_blend_t* _parent() const { return m__parent; }
        std::string _raw_body() const { return m__raw_body; }
        kaitai::kstream* _io__raw_body() const { return m__io__raw_body; }
    };

    /**
     * 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)
     * \sa https://archive.blender.org/wiki/index.php/Dev:Source/Architecture/File_Format/#Structure_DNA Source
     */

    class dna1_body_t : public kaitai::kstruct {

    public:

        dna1_body_t(kaitai::kstream* p__io, blender_blend_t::file_block_t* p__parent = 0, blender_blend_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~dna1_body_t();

    private:
        std::string m_id;
        std::string m_name_magic;
        uint32_t m_num_names;
        std::vector<std::string>* m_names;
        std::string m_padding_1;
        std::string m_type_magic;
        uint32_t m_num_types;
        std::vector<std::string>* m_types;
        std::string m_padding_2;
        std::string m_tlen_magic;
        std::vector<uint16_t>* m_lengths;
        std::string m_padding_3;
        std::string m_strc_magic;
        uint32_t m_num_structs;
        std::vector<dna_struct_t*>* m_structs;
        blender_blend_t* m__root;
        blender_blend_t::file_block_t* m__parent;

    public:
        std::string id() const { return m_id; }
        std::string name_magic() const { return m_name_magic; }
        uint32_t num_names() const { return m_num_names; }
        std::vector<std::string>* names() const { return m_names; }
        std::string padding_1() const { return m_padding_1; }
        std::string type_magic() const { return m_type_magic; }
        uint32_t num_types() const { return m_num_types; }
        std::vector<std::string>* types() const { return m_types; }
        std::string padding_2() const { return m_padding_2; }
        std::string tlen_magic() const { return m_tlen_magic; }
        std::vector<uint16_t>* lengths() const { return m_lengths; }
        std::string padding_3() const { return m_padding_3; }
        std::string strc_magic() const { return m_strc_magic; }
        uint32_t num_structs() const { return m_num_structs; }
        std::vector<dna_struct_t*>* structs() const { return m_structs; }
        blender_blend_t* _root() const { return m__root; }
        blender_blend_t::file_block_t* _parent() const { return m__parent; }
    };

    class header_t : public kaitai::kstruct {

    public:

        header_t(kaitai::kstream* p__io, blender_blend_t* p__parent = 0, blender_blend_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~header_t();

    private:
        bool f_psize;
        int8_t m_psize;

    public:

        /**
         * Number of bytes that a pointer occupies
         */
        int8_t psize();

    private:
        std::string m_magic;
        ptr_size_t m_ptr_size_id;
        endian_t m_endian;
        std::string m_version;
        blender_blend_t* m__root;
        blender_blend_t* m__parent;

    public:
        std::string magic() const { return m_magic; }

        /**
         * Size of a pointer; all pointers in the file are stored in this format
         */
        ptr_size_t ptr_size_id() const { return m_ptr_size_id; }

        /**
         * Type of byte ordering used
         */
        endian_t endian() const { return m_endian; }

        /**
         * Blender version used to save this file
         */
        std::string version() const { return m_version; }
        blender_blend_t* _root() const { return m__root; }
        blender_blend_t* _parent() const { return m__parent; }
    };

    class dna_field_t : public kaitai::kstruct {

    public:

        dna_field_t(kaitai::kstream* p__io, blender_blend_t::dna_struct_t* p__parent = 0, blender_blend_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~dna_field_t();

    private:
        bool f_type;
        std::string m_type;

    public:
        std::string type();

    private:
        bool f_name;
        std::string m_name;

    public:
        std::string name();

    private:
        uint16_t m_idx_type;
        uint16_t m_idx_name;
        blender_blend_t* m__root;
        blender_blend_t::dna_struct_t* m__parent;

    public:
        uint16_t idx_type() const { return m_idx_type; }
        uint16_t idx_name() const { return m_idx_name; }
        blender_blend_t* _root() const { return m__root; }
        blender_blend_t::dna_struct_t* _parent() const { return m__parent; }
    };

private:
    bool f_sdna_structs;
    std::vector<dna_struct_t*>* m_sdna_structs;

public:
    std::vector<dna_struct_t*>* sdna_structs();

private:
    header_t* m_hdr;
    std::vector<file_block_t*>* m_blocks;
    blender_blend_t* m__root;
    kaitai::kstruct* m__parent;

public:
    header_t* hdr() const { return m_hdr; }
    std::vector<file_block_t*>* blocks() const { return m_blocks; }
    blender_blend_t* _root() const { return m__root; }
    kaitai::kstruct* _parent() const { return m__parent; }
};

#endif  // BLENDER_BLEND_H_

blender_blend.cpp

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

#include "blender_blend.h"
#include "kaitai/exceptions.h"

blender_blend_t::blender_blend_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, blender_blend_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = this;
    m_hdr = 0;
    m_blocks = 0;
    f_sdna_structs = false;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void blender_blend_t::_read() {
    m_hdr = new header_t(m__io, this, m__root);
    m_blocks = new std::vector<file_block_t*>();
    {
        int i = 0;
        while (!m__io->is_eof()) {
            m_blocks->push_back(new file_block_t(m__io, this, m__root));
            i++;
        }
    }
}

blender_blend_t::~blender_blend_t() {
    _clean_up();
}

void blender_blend_t::_clean_up() {
    if (m_hdr) {
        delete m_hdr; m_hdr = 0;
    }
    if (m_blocks) {
        for (std::vector<file_block_t*>::iterator it = m_blocks->begin(); it != m_blocks->end(); ++it) {
            delete *it;
        }
        delete m_blocks; m_blocks = 0;
    }
}

blender_blend_t::dna_struct_t::dna_struct_t(kaitai::kstream* p__io, blender_blend_t::dna1_body_t* p__parent, blender_blend_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_fields = 0;
    f_type = false;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void blender_blend_t::dna_struct_t::_read() {
    m_idx_type = m__io->read_u2le();
    m_num_fields = m__io->read_u2le();
    m_fields = new std::vector<dna_field_t*>();
    const int l_fields = num_fields();
    for (int i = 0; i < l_fields; i++) {
        m_fields->push_back(new dna_field_t(m__io, this, m__root));
    }
}

blender_blend_t::dna_struct_t::~dna_struct_t() {
    _clean_up();
}

void blender_blend_t::dna_struct_t::_clean_up() {
    if (m_fields) {
        for (std::vector<dna_field_t*>::iterator it = m_fields->begin(); it != m_fields->end(); ++it) {
            delete *it;
        }
        delete m_fields; m_fields = 0;
    }
}

std::string blender_blend_t::dna_struct_t::type() {
    if (f_type)
        return m_type;
    m_type = _parent()->types()->at(idx_type());
    f_type = true;
    return m_type;
}

blender_blend_t::file_block_t::file_block_t(kaitai::kstream* p__io, blender_blend_t* p__parent, blender_blend_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m__io__raw_body = 0;
    f_sdna_struct = false;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void blender_blend_t::file_block_t::_read() {
    m_code = kaitai::kstream::bytes_to_str(m__io->read_bytes(4), std::string("ASCII"));
    m_len_body = m__io->read_u4le();
    m_mem_addr = m__io->read_bytes(_root()->hdr()->psize());
    m_sdna_index = m__io->read_u4le();
    m_count = m__io->read_u4le();
    n_body = true;
    {
        std::string on = code();
        if (on == std::string("DNA1")) {
            n_body = false;
            m__raw_body = m__io->read_bytes(len_body());
            m__io__raw_body = new kaitai::kstream(m__raw_body);
            m_body = new dna1_body_t(m__io__raw_body, this, m__root);
        }
        else {
            m__raw_body = m__io->read_bytes(len_body());
        }
    }
}

blender_blend_t::file_block_t::~file_block_t() {
    _clean_up();
}

void blender_blend_t::file_block_t::_clean_up() {
    if (!n_body) {
        if (m__io__raw_body) {
            delete m__io__raw_body; m__io__raw_body = 0;
        }
        if (m_body) {
            delete m_body; m_body = 0;
        }
    }
}

blender_blend_t::dna_struct_t* blender_blend_t::file_block_t::sdna_struct() {
    if (f_sdna_struct)
        return m_sdna_struct;
    n_sdna_struct = true;
    if (sdna_index() != 0) {
        n_sdna_struct = false;
        m_sdna_struct = _root()->sdna_structs()->at(sdna_index());
    }
    f_sdna_struct = true;
    return m_sdna_struct;
}

blender_blend_t::dna1_body_t::dna1_body_t(kaitai::kstream* p__io, blender_blend_t::file_block_t* p__parent, blender_blend_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_names = 0;
    m_types = 0;
    m_lengths = 0;
    m_structs = 0;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void blender_blend_t::dna1_body_t::_read() {
    m_id = m__io->read_bytes(4);
    if (!(id() == std::string("\x53\x44\x4E\x41", 4))) {
        throw kaitai::validation_not_equal_error<std::string>(std::string("\x53\x44\x4E\x41", 4), id(), _io(), std::string("/types/dna1_body/seq/0"));
    }
    m_name_magic = m__io->read_bytes(4);
    if (!(name_magic() == std::string("\x4E\x41\x4D\x45", 4))) {
        throw kaitai::validation_not_equal_error<std::string>(std::string("\x4E\x41\x4D\x45", 4), name_magic(), _io(), std::string("/types/dna1_body/seq/1"));
    }
    m_num_names = m__io->read_u4le();
    m_names = new std::vector<std::string>();
    const int l_names = num_names();
    for (int i = 0; i < l_names; i++) {
        m_names->push_back(kaitai::kstream::bytes_to_str(m__io->read_bytes_term(0, false, true, true), std::string("UTF-8")));
    }
    m_padding_1 = m__io->read_bytes(kaitai::kstream::mod((4 - _io()->pos()), 4));
    m_type_magic = m__io->read_bytes(4);
    if (!(type_magic() == std::string("\x54\x59\x50\x45", 4))) {
        throw kaitai::validation_not_equal_error<std::string>(std::string("\x54\x59\x50\x45", 4), type_magic(), _io(), std::string("/types/dna1_body/seq/5"));
    }
    m_num_types = m__io->read_u4le();
    m_types = new std::vector<std::string>();
    const int l_types = num_types();
    for (int i = 0; i < l_types; i++) {
        m_types->push_back(kaitai::kstream::bytes_to_str(m__io->read_bytes_term(0, false, true, true), std::string("UTF-8")));
    }
    m_padding_2 = m__io->read_bytes(kaitai::kstream::mod((4 - _io()->pos()), 4));
    m_tlen_magic = m__io->read_bytes(4);
    if (!(tlen_magic() == std::string("\x54\x4C\x45\x4E", 4))) {
        throw kaitai::validation_not_equal_error<std::string>(std::string("\x54\x4C\x45\x4E", 4), tlen_magic(), _io(), std::string("/types/dna1_body/seq/9"));
    }
    m_lengths = new std::vector<uint16_t>();
    const int l_lengths = num_types();
    for (int i = 0; i < l_lengths; i++) {
        m_lengths->push_back(m__io->read_u2le());
    }
    m_padding_3 = m__io->read_bytes(kaitai::kstream::mod((4 - _io()->pos()), 4));
    m_strc_magic = m__io->read_bytes(4);
    if (!(strc_magic() == std::string("\x53\x54\x52\x43", 4))) {
        throw kaitai::validation_not_equal_error<std::string>(std::string("\x53\x54\x52\x43", 4), strc_magic(), _io(), std::string("/types/dna1_body/seq/12"));
    }
    m_num_structs = m__io->read_u4le();
    m_structs = new std::vector<dna_struct_t*>();
    const int l_structs = num_structs();
    for (int i = 0; i < l_structs; i++) {
        m_structs->push_back(new dna_struct_t(m__io, this, m__root));
    }
}

blender_blend_t::dna1_body_t::~dna1_body_t() {
    _clean_up();
}

void blender_blend_t::dna1_body_t::_clean_up() {
    if (m_names) {
        delete m_names; m_names = 0;
    }
    if (m_types) {
        delete m_types; m_types = 0;
    }
    if (m_lengths) {
        delete m_lengths; m_lengths = 0;
    }
    if (m_structs) {
        for (std::vector<dna_struct_t*>::iterator it = m_structs->begin(); it != m_structs->end(); ++it) {
            delete *it;
        }
        delete m_structs; m_structs = 0;
    }
}

blender_blend_t::header_t::header_t(kaitai::kstream* p__io, blender_blend_t* p__parent, blender_blend_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    f_psize = false;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void blender_blend_t::header_t::_read() {
    m_magic = m__io->read_bytes(7);
    if (!(magic() == std::string("\x42\x4C\x45\x4E\x44\x45\x52", 7))) {
        throw kaitai::validation_not_equal_error<std::string>(std::string("\x42\x4C\x45\x4E\x44\x45\x52", 7), magic(), _io(), std::string("/types/header/seq/0"));
    }
    m_ptr_size_id = static_cast<blender_blend_t::ptr_size_t>(m__io->read_u1());
    m_endian = static_cast<blender_blend_t::endian_t>(m__io->read_u1());
    m_version = kaitai::kstream::bytes_to_str(m__io->read_bytes(3), std::string("ASCII"));
}

blender_blend_t::header_t::~header_t() {
    _clean_up();
}

void blender_blend_t::header_t::_clean_up() {
}

int8_t blender_blend_t::header_t::psize() {
    if (f_psize)
        return m_psize;
    m_psize = ((ptr_size_id() == blender_blend_t::PTR_SIZE_BITS_64) ? (8) : (4));
    f_psize = true;
    return m_psize;
}

blender_blend_t::dna_field_t::dna_field_t(kaitai::kstream* p__io, blender_blend_t::dna_struct_t* p__parent, blender_blend_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    f_type = false;
    f_name = false;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void blender_blend_t::dna_field_t::_read() {
    m_idx_type = m__io->read_u2le();
    m_idx_name = m__io->read_u2le();
}

blender_blend_t::dna_field_t::~dna_field_t() {
    _clean_up();
}

void blender_blend_t::dna_field_t::_clean_up() {
}

std::string blender_blend_t::dna_field_t::type() {
    if (f_type)
        return m_type;
    m_type = _parent()->_parent()->types()->at(idx_type());
    f_type = true;
    return m_type;
}

std::string blender_blend_t::dna_field_t::name() {
    if (f_name)
        return m_name;
    m_name = _parent()->_parent()->names()->at(idx_name());
    f_name = true;
    return m_name;
}

std::vector<blender_blend_t::dna_struct_t*>* blender_blend_t::sdna_structs() {
    if (f_sdna_structs)
        return m_sdna_structs;
    m_sdna_structs = static_cast<blender_blend_t::dna1_body_t*>(blocks()->at((blocks()->size() - 2))->body())->structs();
    f_sdna_structs = true;
    return m_sdna_structs;
}