== blender file format ==
authorLuca Bonavita <mindrones@gmail.com>
Sat, 30 Oct 2010 13:25:24 +0000 (13:25 +0000)
committerLuca Bonavita <mindrones@gmail.com>
Sat, 30 Oct 2010 13:25:24 +0000 (13:25 +0000)
Hello, from the bconf 2010 from Jeroen and Luca. Our first combined commit :)

Automatically create sdna documentations from Trunk.

Usage:
        blender2.5 -b -P BlendFileDnaExporter_25.py [-- [options]]
Options:
        --dna-keep-blend:      doesn't delete the produced blend file DNA export to html
        --dna-debug:           sets the logging level to DEBUG (lots of additional info)
        --dna-versioned'       saves version informations in the html and blend filenames
        --dna-overwrite-css'   overwrite dna.css, useful when modifying css in the script
Examples:
        default:       % blender2.5 -b -P BlendFileDnaExporter_25.py
        with options:  % blender2.5 -b -P BlendFileDnaExporter_25.py -- --dna-keep-blend --dna-debug

doc/blender_file_format/BlendFileDnaExporter_25.py [new file with mode: 0755]
doc/blender_file_format/BlendFileReader.py [new file with mode: 0644]
doc/blender_file_format/mystery_of_the_blend.css [new file with mode: 0644]
doc/blender_file_format/mystery_of_the_blend.html [new file with mode: 0644]

diff --git a/doc/blender_file_format/BlendFileDnaExporter_25.py b/doc/blender_file_format/BlendFileDnaExporter_25.py
new file mode 100755 (executable)
index 0000000..77656f4
--- /dev/null
@@ -0,0 +1,477 @@
+#! /usr/bin/env python3
+
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, 
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation, 
+# Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+#
+# ***** END GPL LICENCE BLOCK *****
+
+######################################################
+#
+#    Name:
+#        dna.py
+#        
+#    Description:
+#        Creates a browsable DNA output to HTML.
+#        
+#    Author:
+#        Jeroen Bakker
+#        
+#    Version:
+#        v0.1 (12-05-2009) - migration of original source code to python.
+#           Added code to support blender 2.5 branch
+#        v0.2 (25-05-2009) - integrated with BlendFileReader.py
+#        
+#    Input:
+#        blender build executable
+#        
+#    Output:
+#        dna.html
+#        dna.css (will only be created when not existing)
+#
+#    Startup:
+#        ./blender -P BlendFileDnaExporter.py
+#
+#    Process:
+#        1: write blend file with SDNA info
+#        2: read blend header from blend file
+#        3: seek DNA1 file-block
+#        4: read dna record from blend file
+#        5: close and eventually delete temp blend file
+#        6: export dna to html and css
+#        7: quit blender
+#
+######################################################
+
+import struct
+import sys
+import getopt                   # command line arguments handling
+from string import Template     # strings completion
+
+
+# logs
+import logging
+log = logging.getLogger("BlendFileDnaExporter")
+
+if '--dna-debug' in sys.argv:
+    logging.basicConfig(level=logging.DEBUG)
+else:
+    logging.basicConfig(level=logging.INFO)
+
+
+class DNACatalogHTML:
+    '''
+    DNACatalog is a catalog of all information in the DNA1 file-block
+    '''
+
+    def __init__(self, catalog, bpy_module = None):
+        self.Catalog = catalog
+        self.bpy = bpy_module
+    
+    def WriteToHTML(self, handle):
+            
+        dna_html_template = """
+            <!DOCTYPE html PUBLIC -//W3C//DTD HTML 4.01 Transitional//EN http://www.w3.org/TR/html4/loose.dtd>
+            <html>
+            <head>
+                <link rel="stylesheet" type="text/css" href="dna.css" media="screen, print" />
+                <meta http-equiv="Content-Type" content="text/html"; charset="ISO-8859-1" />
+                <title>The mystery of the blend</title>
+            </head>
+            <body>
+                <div class=title>
+                    Blender ${version}<br/>
+                    Internal SDNA structures
+                </div>
+                Architecture: ${bitness} ${endianness}<br/>
+                Build revision: <a href="https://svn.blender.org/svnroot/bf-blender/!svn/bc/${revision}/trunk/">${revision}</a><br/>
+                File format reference: <a href="mystery_of_the_blend.html">The mystery of the blend</a> by Jeroen Bakker<br/>
+                <h1>Index of blender structures</h1>
+                <ul class=multicolumn>
+                    ${structs_list}
+                </ul>
+                ${structs_content}
+            </body>
+            </html>"""
+        
+        header = self.Catalog.Header
+        bpy = self.bpy
+        
+        # ${version} and ${revision}
+        if bpy:
+            version = '.'.join(map(str, bpy.app.version))
+            revision = bpy.app.build_revision[:-1]
+        else:
+            version = str(header.Version)
+            revision = 'Unknown'
+        
+        # ${bitness}
+        if header.PointerSize == 8:
+            bitness = '64 bit'
+        else:
+            bitness = '32 bit'
+
+        # ${endianness}
+        if header.LittleEndianness:
+            endianess= 'Little endianness'
+        else:
+            endianess= 'Big endianness'
+        
+        # ${structs_list}
+        log.debug("Creating structs index")
+        structs_list = ''
+        list_item = '<li class="multicolumn">({0}) <a href="#{1}">{1}</a></li>\n'
+        structureIndex = 0
+        for structure in self.Catalog.Structs:
+            structs_list += list_item.format(structureIndex, structure.Type.Name)
+            structureIndex+=1
+
+        # ${structs_content}
+        log.debug("Creating structs content")
+        structs_content = ''
+        for structure in self.Catalog.Structs:
+            log.debug(structure.Type.Name)
+            structs_content += self.Structure(structure)
+           
+        d = dict(
+            version = version, 
+            revision = revision, 
+            bitness = bitness, 
+            endianness = endianess, 
+            structs_list = structs_list, 
+            structs_content = structs_content
+        )
+
+        dna_html = Template(dna_html_template).substitute(d)
+        dna_html = self.format(dna_html)
+        handle.write(dna_html)
+    
+    def Structure(self, structure):
+        struct_table_template = """
+            <table><a name="${struct_name}"></a>
+                <caption><a href="#${struct_name}">${struct_name}</a></caption>
+                <thead>
+                    <tr>
+                        <th>reference</th>
+                        <th>structure</th>
+                        <th>type</th>
+                        <th>name</th>
+                        <th>offset</th>
+                        <th>size</th>
+                    </tr>
+                </thead>
+                <tbody>
+                ${fields}
+                </tbody>
+            </table>
+            <label>Total size: ${size} bytes</label><br/>
+            <label>(<a href="#top">top</a>)</label><br/>"""
+        
+        d = dict(
+            struct_name = structure.Type.Name, 
+            fields = self.StructureFields(structure, None, 0), 
+            size = str(structure.Type.Size)
+        )
+        
+        struct_table = Template(struct_table_template).substitute(d)
+        return struct_table
+        
+    def StructureFields(self, structure, parentReference, offset):
+        fields = ''
+        for field in structure.Fields:
+            fields += self.StructureField(field, structure, parentReference, offset)
+            offset += field.Size(self.Catalog.Header)
+        return fields
+        
+    def StructureField(self, field, structure, parentReference, offset):
+        structure_field_template = """
+            <tr>
+                <td>${reference}</td>
+                <td>${struct}</td>
+                <td>${type}</td>
+                <td>${name}</td>
+                <td>${offset}</td>
+                <td>${size}</td>
+            </tr>"""
+        
+        if field.Type.Structure == None or field.Name.IsPointer():
+
+            # ${reference}
+            reference = field.Name.AsReference(parentReference)
+
+            # ${struct}
+            if parentReference != None:
+                struct = '<a href="#{0}">{0}</a>'.format(structure.Type.Name)
+            else:
+                struct = structure.Type.Name
+            
+            # ${type}
+            type = field.Type.Name
+            
+            # ${name}
+            name = field.Name.Name
+            
+            # ${offset}
+            # offset already set
+            
+            # ${size}
+            size = field.Size(self.Catalog.Header)
+        
+            d = dict(
+                reference = reference, 
+                struct = struct, 
+                type = type, 
+                name = name, 
+                offset = offset, 
+                size = size
+            )
+            
+            structure_field = Template(structure_field_template).substitute(d)
+        
+        elif field.Type.Structure != None:
+            reference = field.Name.AsReference(parentReference)
+            structure_field = self.StructureFields(field.Type.Structure, reference, offset) 
+
+        return structure_field
+
+    def indent(self, input, dent, startswith = ''):
+        output = ''
+        if dent < 0:
+            for line in input.split('\n'):
+                dent = abs(dent)
+                output += line[dent:] + '\n'   # unindent of a desired amount
+        elif dent == 0:
+            for line in input.split('\n'):
+                output += line.lstrip() + '\n'  # remove indentation completely
+        elif dent > 0:
+            for line in input.split('\n'):
+                output += ' '* dent + line + '\n'
+        return output
+    
+    def format(self, input):
+        diff = {
+            '\n<!DOCTYPE':'<!DOCTYPE', 
+            '\n</ul>'    :'</ul>', 
+            '<a name'         :'\n<a name', 
+            '<tr>\n'     :'<tr>', 
+            '<tr>'       :'  <tr>', 
+            '</th>\n'    :'</th>', 
+            '</td>\n'    :'</td>', 
+            '<tbody>\n'  :'<tbody>'
+        }
+        output = self.indent(input, 0)
+        for key, value in diff.items():
+            output = output.replace(key, value)
+        return output
+
+    def WriteToCSS(self, handle):
+        '''
+        Write the Cascading stylesheet template to the handle
+        It is expected that the handle is a Filehandle
+        '''
+        css = """
+            @CHARSET "ISO-8859-1";
+            
+            body {
+                font-family: verdana;
+                font-size: small;
+            }
+            
+            div.title {
+                font-size: large;
+                text-align: center;
+            }
+            
+            h1 {
+                page-break-before: always;
+            }
+
+            h1, h2 {
+                background-color: #D3D3D3;
+                color:#404040;
+                margin-right: 3%;
+                padding-left: 40px;
+            }
+            
+            h1:hover{
+                background-color: #EBEBEB;
+            }
+
+            h3 {
+                padding-left: 40px;
+            }
+            
+            table {
+                border-width: 1px;
+                border-style: solid;
+                border-color: #000000;
+                border-collapse: collapse;
+                width: 94%;
+                margin: 20px 3% 10px;
+            }
+            
+            caption {
+                margin-bottom: 5px;
+            }
+            
+            th {
+                background-color: #000000;
+                color:#ffffff;
+                padding-left:5px;
+                padding-right:5px;
+            }
+            
+            tr {
+            }
+            
+            td {
+                border-width: 1px;
+                border-style: solid;
+                border-color: #a0a0a0;
+                padding-left:5px;
+                padding-right:5px;
+            }
+            
+            label {
+                float:right;
+                margin-right: 3%;
+            }
+            
+            ul.multicolumn {
+                list-style:none;
+                float:left;
+                padding-right:0px;
+                margin-right:0px;
+            }
+
+            li.multicolumn {
+                float:left;
+                width:200px;
+                margin-right:0px;
+            }
+            
+            a {
+                color:#a000a0;
+                text-decoration:none;
+            }
+            
+            a:hover {
+                color:#a000a0;
+                text-decoration:underline;
+            }
+            """
+            
+        css = self.indent(css, 0)
+
+        handle.write(css)
+
+
+def usage():
+    print("\nUsage: \n\tblender2.5 -b -P BlendFileDnaExporter_25.py [-- [options]]")
+    print("Options:")
+    print("\t--dna-keep-blend:      doesn't delete the produced blend file DNA export to html")
+    print("\t--dna-debug:           sets the logging level to DEBUG (lots of additional info)")
+    print("\t--dna-versioned'       saves version informations in the html and blend filenames")
+    print("\t--dna-overwrite-css'   overwrite dna.css, useful when modifying css in the script")
+    print("Examples:")
+    print("\tdefault:       % blender2.5 -b -P BlendFileDnaExporter_25.py")
+    print("\twith options:  % blender2.5 -b -P BlendFileDnaExporter_25.py -- --dna-keep-blend --dna-debug\n")
+
+    
+######################################################
+# Main
+######################################################
+
+def main():
+    
+    import os, os.path
+
+    try:
+        bpy = __import__('bpy')
+
+        # Files
+        if '--dna-versioned' in sys.argv:
+            blender_version = '_'.join(map(str, bpy.app.version))
+            filename = 'dna-{0}-{1}_endian-{2}-r{3}'.format(sys.arch, sys.byteorder, blender_version, bpy.app.build_revision[2:-1])
+        else:
+            filename = 'dna'
+        dir = os.path.dirname(__file__)
+        Path_Blend = os.path.join(dir, filename + '.blend') # temporary blend file
+        Path_HTML  = os.path.join(dir, filename + '.html')  # output html file
+        Path_CSS   = os.path.join(dir, 'dna.css')           # output css file
+
+        # create a blend file for dna parsing
+        if not os.path.exists(Path_Blend):
+            log.info("1: write temp blend file with SDNA info")
+            log.info("   saving to: " + Path_Blend)
+            try:
+                bpy.ops.wm.save_as_mainfile(filepath = Path_Blend, copy = True, compress = False)
+            except:
+                log.error("Filename {0} does not exist and can't be created... quitting".format(Path_Blend))
+                return
+        else:
+            log.info("1: found blend file with SDNA info")
+            log.info("   " + Path_Blend)
+        
+        # read blend header from blend file
+        log.info("2: read file:")
+        
+        if not dir in sys.path:
+            sys.path.append(dir)
+        import BlendFileReader
+        
+        handle = BlendFileReader.openBlendFile(Path_Blend)
+        blendfile = BlendFileReader.BlendFile(handle)
+        catalog = DNACatalogHTML(blendfile.Catalog, bpy)
+
+        # close temp file
+        handle.close()
+        
+        # deleting or not?
+        if '--dna-keep-blend' in sys.argv:
+            # keep the blend, useful for studying hexdumps
+            log.info("5: closing blend file:")
+            log.info("   {0}".format(Path_Blend))
+        else:
+            # delete the blend
+            log.info("5: close and delete temp blend:")
+            log.info("   {0}".format(Path_Blend))
+            os.remove(Path_Blend)
+        
+        # export dna to xhtml
+        log.info("6: export sdna to xhtml file")
+        handleHTML = open(Path_HTML, "w")
+        catalog.WriteToHTML(handleHTML)
+        handleHTML.close()
+
+        # only write the css when doesn't exist or at explicit request
+        if not os.path.exists(Path_CSS) or '--dna-overwrite-css' in sys.argv:
+            handleCSS = open(Path_CSS, "w")
+            catalog.WriteToCSS(handleCSS)
+            handleCSS.close()
+
+        # quit blender
+        if not bpy.app.background:
+            log.info("7: quit blender")
+            bpy.ops.wm.exit_blender()
+    
+    except ImportError:
+        log.warning("  skipping, not running in Blender")
+        usage()
+        sys.exit(2)
+        
+
+if __name__ == '__main__':
+    main()
diff --git a/doc/blender_file_format/BlendFileReader.py b/doc/blender_file_format/BlendFileReader.py
new file mode 100644 (file)
index 0000000..7003af1
--- /dev/null
@@ -0,0 +1,446 @@
+#! /usr/bin/env python3
+
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, 
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation, 
+# Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+#
+# ***** END GPL LICENCE BLOCK *****
+
+######################################################
+# Importing modules
+######################################################
+
+import os
+import struct
+import gzip
+import tempfile
+
+import logging
+log = logging.getLogger("BlendFileReader")
+
+######################################################
+# module global routines
+######################################################
+
+def ReadString(handle, length):
+    '''
+    ReadString reads a String of given length or a zero terminating String
+    from a file handle
+    '''
+    if length != 0:
+        return handle.read(length).decode()
+    else:
+        # length == 0 means we want a zero terminating string
+        result = ""
+        s = ReadString(handle, 1)
+        while s!="\0":
+            result += s
+            s = ReadString(handle, 1)
+        return result
+
+
+def Read(type, handle, fileheader):
+    '''
+    Reads the chosen type from a file handle
+    '''
+    def unpacked_bytes(type_char, size):
+        return struct.unpack(fileheader.StructPre + type_char, handle.read(size))[0]
+    
+    if type == 'ushort':
+        return unpacked_bytes("H", 2)   # unsigned short
+    elif type == 'short':
+        return unpacked_bytes("h", 2)   # short
+    elif type == 'uint':
+        return unpacked_bytes("I", 4)   # unsigned int
+    elif type == 'int':
+        return unpacked_bytes("i", 4)   # int
+    elif type == 'float':
+        return unpacked_bytes("f", 4)   # float
+    elif type == 'ulong':
+        return unpacked_bytes("Q", 8)   # unsigned long
+    elif type == 'pointer':
+        # The pointersize is given by the header (BlendFileHeader).
+        if fileheader.PointerSize == 4:
+            return Read('uint', handle, fileheader)
+        if fileheader.PointerSize == 8:
+            return Read('ulong', handle, fileheader)
+
+
+def openBlendFile(filename):
+    '''
+    Open a filename, determine if the file is compressed and returns a handle
+    '''
+    handle = open(filename, 'rb')
+    magic = ReadString(handle, 7)
+    if magic in ("BLENDER", "BULLETf"):
+        log.debug("normal blendfile detected")
+        handle.seek(0, os.SEEK_SET)
+        return handle
+    else:
+        log.debug("gzip blendfile detected?")
+        handle.close()
+        log.debug("decompressing started")
+        fs = gzip.open(filename, "rb")
+        handle = tempfile.TemporaryFile()
+        data = fs.read(1024*1024) 
+        while data: 
+            handle.write(data) 
+            data = fs.read(1024*1024) 
+        log.debug("decompressing finished")
+        fs.close()
+        log.debug("resetting decompressed file")
+        handle.seek(0, os.SEEK_SET)
+        return handle
+
+
+def Align(handle):
+    '''
+    Aligns the filehandle on 4 bytes
+    '''
+    offset = handle.tell()
+    trim = offset % 4
+    if trim != 0:
+        handle.seek(4-trim, os.SEEK_CUR)
+
+
+######################################################
+# module classes
+######################################################
+
+class BlendFile:
+    '''
+    Reads a blendfile and store the header, all the fileblocks, and catalogue 
+    structs foound in the DNA fileblock
+    
+    - BlendFile.Header  (BlendFileHeader instance)
+    - BlendFile.Blocks  (list of BlendFileBlock instances)
+    - BlendFile.Catalog (DNACatalog instance)
+    '''
+    
+    def __init__(self, handle):
+        log.debug("initializing reading blend-file")
+        self.Header = BlendFileHeader(handle)
+        self.Blocks = []
+        fileblock = BlendFileBlock(handle, self)
+        found_dna_block = False
+        while not found_dna_block:
+            if fileblock.Header.Code in ("DNA1", "SDNA"):
+                self.Catalog = DNACatalog(self.Header, handle)
+                found_dna_block = True
+            else:
+                fileblock.Header.skip(handle)
+            
+            self.Blocks.append(fileblock)
+            fileblock = BlendFileBlock(handle, self)
+        
+        # appending last fileblock, "ENDB"
+        self.Blocks.append(fileblock)
+    
+    # seems unused?
+    """
+    def FindBlendFileBlocksWithCode(self, code):
+        #result = []
+        #for block in self.Blocks:
+            #if block.Header.Code.startswith(code) or block.Header.Code.endswith(code):
+                #result.append(block)
+        #return result
+    """
+
+
+class BlendFileHeader:
+    '''
+    BlendFileHeader allocates the first 12 bytes of a blend file.
+    It contains information about the hardware architecture.
+    Header example: BLENDER_v254
+    
+    BlendFileHeader.Magic             (str)
+    BlendFileHeader.PointerSize       (int)
+    BlendFileHeader.LittleEndianness  (bool)
+    BlendFileHeader.StructPre         (str)   see http://docs.python.org/py3k/library/struct.html#byte-order-size-and-alignment
+    BlendFileHeader.Version           (int)
+    '''
+    
+    def __init__(self, handle):
+        log.debug("reading blend-file-header")
+        
+        self.Magic = ReadString(handle, 7)
+        log.debug(self.Magic)
+        
+        pointersize = ReadString(handle, 1)
+        log.debug(pointersize)
+        if pointersize == "-":
+            self.PointerSize = 8
+        if pointersize == "_":
+            self.PointerSize = 4
+                    
+        endianness = ReadString(handle, 1)
+        log.debug(endianness)
+        if endianness == "v":
+            self.LittleEndianness = True
+            self.StructPre = "<"
+        if endianness == "V":
+            self.LittleEndianness = False
+            self.StructPre = ">"
+        
+        version = ReadString(handle, 3)
+        log.debug(version)
+        self.Version = int(version)
+        
+        log.debug("{0} {1} {2} {3}".format(self.Magic, self.PointerSize, self.LittleEndianness, version))
+
+
+class BlendFileBlock:
+    '''
+    BlendFileBlock.File     (BlendFile)
+    BlendFileBlock.Header   (FileBlockHeader)
+    '''
+    
+    def __init__(self, handle, blendfile):
+        self.File = blendfile
+        self.Header = FileBlockHeader(handle, blendfile.Header)
+        
+    def Get(self, handle, path):
+        log.debug("find dna structure")
+        dnaIndex = self.Header.SDNAIndex
+        dnaStruct = self.File.Catalog.Structs[dnaIndex]
+        log.debug("found " + dnaStruct.Type.Name)
+        handle.seek(self.Header.FileOffset, os.SEEK_SET)
+        return dnaStruct.GetField(self.File.Header, handle, path)
+
+
+class FileBlockHeader:
+    '''
+    FileBlockHeader contains the information in a file-block-header.
+    The class is needed for searching to the correct file-block (containing Code: DNA1)
+
+    Code        (str)
+    Size        (int)
+    OldAddress  (pointer)
+    SDNAIndex   (int)
+    Count       (int)
+    FileOffset  (= file pointer of datablock)
+    '''
+    
+    def __init__(self, handle, fileheader):
+        self.Code = ReadString(handle, 4).strip()
+        if self.Code != "ENDB":
+            self.Size = Read('uint', handle, fileheader)
+            self.OldAddress = Read('pointer', handle, fileheader)
+            self.SDNAIndex = Read('uint', handle, fileheader)
+            self.Count = Read('uint', handle, fileheader)
+            self.FileOffset = handle.tell()
+        else:
+            self.Size = Read('uint', handle, fileheader)
+            self.OldAddress = 0
+            self.SDNAIndex = 0
+            self.Count = 0
+            self.FileOffset = handle.tell()
+        #self.Code += ' ' * (4 - len(self.Code))
+        log.debug("found blend-file-block-fileheader {0} {1}".format(self.Code, self.FileOffset))
+
+    def skip(self, handle):
+        handle.read(self.Size)
+
+
+class DNACatalog:
+    '''
+    DNACatalog is a catalog of all information in the DNA1 file-block
+    
+    Header = None
+    Names = None
+    Types = None
+    Structs = None
+    '''
+    
+    def __init__(self, fileheader, handle):
+        log.debug("building DNA catalog")
+        self.Names=[]
+        self.Types=[]
+        self.Structs=[]
+        self.Header = fileheader
+        
+        SDNA = ReadString(handle, 4)
+        
+        # names
+        NAME = ReadString(handle, 4)
+        numberOfNames = Read('uint', handle, fileheader)
+        log.debug("building #{0} names".format(numberOfNames))
+        for i in range(numberOfNames):
+            name = ReadString(handle,0)
+            self.Names.append(DNAName(name))
+        Align(handle)
+
+        # types
+        TYPE = ReadString(handle, 4)
+        numberOfTypes = Read('uint', handle, fileheader)
+        log.debug("building #{0} types".format(numberOfTypes))
+        for i in range(numberOfTypes):
+            type = ReadString(handle,0)
+            self.Types.append(DNAType(type))
+        Align(handle)
+
+        # type lengths
+        TLEN = ReadString(handle, 4)
+        log.debug("building #{0} type-lengths".format(numberOfTypes))
+        for i in range(numberOfTypes):
+            length = Read('ushort', handle, fileheader)
+            self.Types[i].Size = length
+        Align(handle)
+
+        # structs
+        STRC = ReadString(handle, 4)
+        numberOfStructures = Read('uint', handle, fileheader)
+        log.debug("building #{0} structures".format(numberOfStructures))
+        for structureIndex in range(numberOfStructures):
+            type = Read('ushort', handle, fileheader)
+            Type = self.Types[type]
+            structure = DNAStructure(Type)
+            self.Structs.append(structure)
+
+            numberOfFields = Read('ushort', handle, fileheader)
+            for fieldIndex in range(numberOfFields):
+                fTypeIndex = Read('ushort', handle, fileheader)
+                fNameIndex = Read('ushort', handle, fileheader)
+                fType = self.Types[fTypeIndex]
+                fName = self.Names[fNameIndex]
+                structure.Fields.append(DNAField(fType, fName))
+
+
+class DNAName:
+    '''
+    DNAName is a C-type name stored in the DNA.
+    
+    Name = str
+    '''
+    
+    def __init__(self, name):
+        self.Name = name
+        
+    def AsReference(self, parent):
+        if parent == None:
+            result = ""
+        else:
+            result = parent+"."
+            
+        result = result + self.ShortName()
+        return result
+
+    def ShortName(self):
+        result = self.Name;
+        result = result.replace("*", "")
+        result = result.replace("(", "")
+        result = result.replace(")", "")
+        Index = result.find("[")
+        if Index != -1:
+            result = result[0:Index]
+        return result
+        
+    def IsPointer(self):
+        return self.Name.find("*")>-1
+
+    def IsMethodPointer(self):
+        return self.Name.find("(*")>-1
+
+    def ArraySize(self):
+        result = 1
+        Temp = self.Name
+        Index = Temp.find("[")
+
+        while Index != -1:
+            Index2 = Temp.find("]")
+            result*=int(Temp[Index+1:Index2])
+            Temp = Temp[Index2+1:]
+            Index = Temp.find("[")
+        
+        return result
+
+
+class DNAType:
+    '''
+    DNAType is a C-type stored in the DNA
+
+    Name = str
+    Size = int
+    Structure = DNAStructure
+    '''
+    
+    def __init__(self, aName):
+        self.Name = aName
+        self.Structure=None
+
+
+class DNAStructure:
+    '''
+    DNAType is a C-type structure stored in the DNA
+    
+    Type = DNAType
+    Fields = [DNAField]
+    '''
+    
+    def __init__(self, aType):
+        self.Type = aType
+        self.Type.Structure = self
+        self.Fields=[]
+        
+    def GetField(self, header, handle, path):
+        splitted = path.partition(".")
+        name = splitted[0]
+        rest = splitted[2]
+        offset = 0;
+        for field in self.Fields:
+            if field.Name.ShortName() == name:
+                log.debug("found "+name+"@"+str(offset))
+                handle.seek(offset, os.SEEK_CUR)
+                return field.DecodeField(header, handle, rest)
+            else:
+                offset += field.Size(header)
+
+        log.debug("error did not find "+path)
+        return None
+
+
+class DNAField:
+    '''
+    DNAField is a coupled DNAType and DNAName.
+    
+    Type = DNAType
+    Name = DNAName
+    '''
+
+    def __init__(self, aType, aName):
+        self.Type = aType
+        self.Name = aName
+        
+    def Size(self, header):
+        if self.Name.IsPointer() or self.Name.IsMethodPointer():
+            return header.PointerSize*self.Name.ArraySize()
+        else:
+            return self.Type.Size*self.Name.ArraySize()
+
+    def DecodeField(self, header, handle, path):
+        if path == "":
+            if self.Name.IsPointer():
+                return Read('pointer', handle, header)
+            if self.Type.Name=="int":
+                return Read('int', handle, header)
+            if self.Type.Name=="short":
+                return Read('short', handle, header)
+            if self.Type.Name=="float":
+                return Read('float', handle, header)
+            if self.Type.Name=="char":
+                return ReadString(handle, self.Name.ArraySize())
+        else:
+            return self.Type.Structure.GetField(header, handle, path)
+
diff --git a/doc/blender_file_format/mystery_of_the_blend.css b/doc/blender_file_format/mystery_of_the_blend.css
new file mode 100644 (file)
index 0000000..df287b5
--- /dev/null
@@ -0,0 +1,204 @@
+@CHARSET "ISO-8859-1";
+
+table {
+       border-width: 1px;
+       border-style: solid;
+       border-color: #000000;
+       border-collapse: collapse;
+       width: 94%;
+       margin: 10px 3%;
+}
+
+DIV.title {
+       font-size: 30px;
+       font-weight: bold;
+       text-align: center
+}
+
+DIV.subtitle {
+       font-size: large;
+       text-align: center
+}
+
+DIV.contact {
+       margin:30px 3%; 
+}
+
+@media print {
+       DIV.contact {
+               margin-top: 300px;
+       }
+       DIV.title {
+               margin-top: 400px;
+       }
+}
+
+label {
+       font-weight: bold;
+       width: 100px;
+       float: left;
+}
+
+label:after {
+       content: ":";
+}
+
+TH {
+       background-color: #000000;
+       color: #ffffff;
+       padding-left: 5px;
+       padding-right: 5px;
+}
+
+TR {
+}
+
+TD {
+       border-width: 1px;
+       border-style: solid;
+       border-color: #a0a0a0;
+       padding-left: 5px;
+       padding-right: 5px;
+}
+
+BODY {
+       font-family: verdana;
+       font-size: small;
+}
+
+H1 {
+       page-break-before: always;
+}
+
+H1, H2, H3, H4 {
+       margin-top: 30px;
+       margin-right: 3%;
+       padding: 3px 3%;
+       color: #404040;
+       cursor: pointer;
+}
+
+H1, H2 {
+       background-color: #D3D3D3;
+}
+
+H3, H4 {
+       padding-top: 5px;
+       padding-bottom: 5px;
+}
+
+H1:hover, H2:hover, H3:hover, H4:hover {
+       background-color: #EBEBEB;
+}
+
+CODE.evidence {
+       font-size:larger
+}
+
+CODE.block {
+       color: #000000;
+       background-color: #DDDC75;
+       margin: 10px 0;
+       padding: 5px;
+       border-width: 1px;
+       border-style: dotted;
+       border-color: #000000;
+       white-space: pre;
+       display: block;
+       font-size: 2 em;
+}
+
+ul {
+       margin: 10px 3%;
+}
+
+li {
+       margin: 0 -15px;
+}
+
+ul.multicolumn {
+       list-style: none;
+       float: left;
+       padding-right: 0px;
+       margin-right: 0px;
+}
+
+li.multicolumn {
+       float: left;
+       width: 200px;
+       margin-right: 0px;
+}
+
+@media screen {
+       p {
+               margin: 10px 3%;
+               line-height: 130%;
+       }
+}
+
+span.fade {
+       color: gray;
+}
+
+span.header {
+       color: green;
+}
+
+span.header-greyed {
+       color: #4CBE4B;
+}
+
+span.data {
+       color: blue;
+}
+
+span.data-greyed {
+       color: #5D99C4;
+}
+
+span.descr {
+       color: red;
+}
+
+div.box {
+       margin: 15px 3%;
+       border-style: dotted;
+       border-width: 1px;
+}
+
+div.box-solid {
+       margin: 15px 3%;
+       border-style: solid;
+       border-width: 1px;
+}
+
+p.box-title {
+       font-style: italic;
+       font-size: 110%;
+       cursor: pointer;
+}
+
+p.box-title:hover {
+       background-color: #EBEBEB;
+}
+
+p.code {
+       font-family: "Courier New", Courier, monospace;
+}
+
+a {
+       color: #a000a0;
+       text-decoration: none;
+}
+
+a:hover {
+       color: #a000a0;
+       text-decoration: underline;
+}
+
+td.skip {
+       color: #808080;
+       padding-top: 10px;
+       padding-bottom: 10px;
+       text-align: center;
+}
diff --git a/doc/blender_file_format/mystery_of_the_blend.html b/doc/blender_file_format/mystery_of_the_blend.html
new file mode 100644 (file)
index 0000000..b34493f
--- /dev/null
@@ -0,0 +1,835 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+    <link rel="stylesheet" type="text/css" href="mystery_of_the_blend.css" media="screen, print">
+    <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
+    <title>The mystery of the blend</title>
+</head>
+
+<body>
+<div class="title">The mystery of the blend</div>
+<div class="subtitle">The blender file-format explained</div>
+<div class="contact">
+<label>Author</label> Jeroen Bakker<br>
+<label>Email</label> <a href="mailto:j.bakker@atmind.nl">j.bakker@atmind.nl</a><br>
+<label>Website</label> <a href="http://www.atmind.nl/blender/">http://www.atmind.nl/blender</a><br>
+<label>Version</label> 06-10-2010<br>
+</div>
+
+<a name="introduction" href="#introduction" ><h2>Introduction</h2></a>
+</a>
+
+<p>In this article I will describe the
+ blend-file-format with a request to tool-makers to support blend-file. 
+   
+</p>
+<p>First I'll describe how Blender works with blend-files. You'll notice
+ why the blend-file-format is not that well documented, as from 
+Blender's perspective this is not needed.
+We look at the global file-structure of a blend-file (the file-header 
+and file-blocks).
+After this is explained, we go deeper to the core of the blend-file, the
+ DNA-structures. They hold the blue-prints of the blend-file and the key
+ asset of understanding blend-files.
+When that's done we can use these DNA-structures to read information 
+from elsewhere in the blend-file.
+
+</p>
+<p>
+In this article we'll be using the default blend-file from Blender 2.54,
+ with the goal to read the output resolution from the Scene. 
+The article is written to be programming language independent and I've 
+setup a web-site for support.
+</p>
+
+<a name="loading-and-saving-in-blender" href="#loading-and-saving-in-blender">
+<h2>Loading and saving in Blender</h2>
+</a>
+
+<p>
+Loading and saving in Blender is very fast and Blender is known to 
+have excellent downward and upward compatibility. Ton Roosendaal 
+demonstrated that in December 2008 by loading a 1.0 blend-file using 
+Blender 2.48a [ref: <a href="http://www.blendernation.com/2008/12/01/blender-dna-rna-and-backward-compatibility/">http://www.blendernation.com/2008/12/01/blender-dna-rna-and-backward-compatibility/</a>].
+</p>
+
+<p>
+Saving complex scenes in Blender is done within seconds. Blender 
+achieves this by saving data in memory to disk without any 
+transformations or translations. Blender only adds file-block-headers to
+ this data. A file-block-header contains clues on how to interpret the 
+data. After the data, all internally Blender structures are stored. 
+These structures will act as blue-prints when Blender loads the file. 
+Blend-files can be different when stored on different hardware platforms
+ or Blender releases. There is no effort taken to make blend-files 
+binary the same. Blender creates the blend-files in this manner since 
+release 1.0. Backward and upwards compatibility is not implemented when 
+saving the file, this is done during loading.
+</p>
+
+<p>
+When Blender loads a blend-file, the DNA-structures are read first. 
+Blender creates a catalog of these DNA-structures. Blender uses this 
+catalog together with the data in the file, the internal Blender 
+structures of the Blender release you're using and a lot of 
+transformation and translation logic to implement the backward and 
+upward compatibility. In the source code of blender there is actually 
+logic which can transform and translate every structure used by a 
+Blender release to the one of the release you're using [ref: <a href="http://download.blender.org/source/blender-2.48a.tar.gz">http://download.blender.org/source/blender-2.48a.tar.gz</a>
+ <a href="https://svn.blender.org/svnroot/bf-blender/tags/blender-2.48-release/source/blender/blenloader/intern/readfile.c">blender/blenloader/intern/readfile.c</a> lines 
+4946-7960]. The more difference between releases the more logic is 
+executed. 
+</p>
+
+<p>
+The blend-file-format is not well documented, as it does not differ from
+ internally used structures and the file can really explain itself.
+</p>
+
+<a name="global-file-structure" href="#global-file-structure">
+<h2>Global file-structure</h2>
+</a>
+
+<p>
+This section explains how the global file-structure can be read.
+</p>
+
+<ul>
+<li>A blend-file always start with the <b>file-header</b></li>
+<li>After the file-header, follows a list of <b>file-blocks</b> (the default blend file of Blender 2.48 contains more than 400 of these file-blocks).</li>
+<li>Each file-block has a <b>file-block header</b> and <b>file-block data</b></li>
+<li>At the end of the blend-file there is a section called "<a href="#structure-DNA" style="font-weight:bold">Structure DNA</a>", which lists all the internal structures of the Blender release the file was created in</li>
+<li>The blend-file ends with a file-block called 'ENDB'</li>
+</ul>
+
+<!-- file scheme -->
+<div class="box-solid" style="width:20%; margin-left:35%; font-size:0.8em;">
+
+    <p class="code"><b>File.blend</b></p>
+
+    <div class="box"><p class="code">File-header</p></div>
+
+    <div class="box-solid"><p class="code">File-block</p>
+        <div class="box"><p class="code">Header</p></div>
+        <div class="box"><p class="code">Data</p></div>
+    </div>
+    
+    <div class="box" style="border-style:dashed"><p class="code">File-block</p></div>
+    <div class="box" style="border-style:dashed"><p class="code">File-block</p></div>
+
+    <div class="box-solid"><p class="code">File-block 'Structure DNA'</p>
+        <div class="box"><p class="code">Header ('DNA1')</p></div>
+        <div class="box-solid">
+            <p class="code">Data ('SDNA')</p>
+            <div class="box">
+                <p class="code">Names ('NAME')</p>
+            </div>
+            <div class="box">
+                <p class="code">Types ('TYPE')</p>
+            </div>
+            <div class="box">
+                <p class="code">Lengths ('TLEN')</p>
+            </div>
+            <div class="box">
+                <p class="code">Structures ('STRC')</p>
+            </div>
+        </div>
+    </div>
+
+    <div class="box-solid"><p class="code">File-Block 'ENDB'</p></div>
+
+</div><!-- end of file scheme -->
+
+<a name="file-header" href="#file-header">
+<h3>File-Header</h3>
+</a>
+
+<p>
+The first 12 bytes of every blend-file is the file-header. The 
+file-header has information on Blender (version-number) and the PC the 
+blend-file was saved on (pointer-size and endianness). This is required 
+as all data inside the blend-file is ordered in that  way, because no  
+translation or transformation  is done during saving. 
+The next table describes the information in the file-header.
+</p>
+
+<table>
+<caption>File-header</caption>
+<thead>
+<tr><th>reference</th>
+    <th>structure</th>
+    <th>type</th>
+    <th>offset</th>
+    <th>size</th></tr>
+</thead>
+<tbody>
+<tr><td>identifier</td>
+    <td>char[7]</td>
+    <td>File identifier (always 'BLENDER')</td>
+    <td>0</td>
+    <td>7</td></tr>
+<tr><td>pointer-size</td>
+    <td>char</td>
+    <td>Size of a pointer; all pointers in the file are stored in this format. '_' means 4 bytes or 32 bit and '-' means 8 bytes or 64 bits.</td>
+    <td>7</td>
+    <td>1</td></tr>
+<tr><td>endianness</td>
+    <td>char</td>
+    <td>Type of byte ordering used; 'v' means little endian and 'V' means big endian.</td>
+    <td>8</td>
+    <td>1</td></tr>
+<tr><td>version-number</td>
+    <td>char[3]</td>
+    <td>Version of Blender the file was created in; '254' means version 2.54</td>
+    <td>9</td>
+    <td>3</td></tr>
+</tbody>
+</table>
+
+<p>
+<a href="http://en.wikipedia.org/wiki/Endianness">Endianness</a> addresses the way values are ordered in a sequence of bytes(see the <a href="#example-endianess">example</a> below):
+</p>
+
+<ul>
+    <li>in a big endian ordering, the largest part of the value is placed on the first byte and 
+    the lowest part of the value is placed on the last byte,</li>
+    <li>in a little endian ordering, largest part of the value is placed on the last byte
+    and the smallest part of the value is placed on the first byte.</li>
+</ul>
+
+<p> 
+Nowadays, little-endian is the most commonly used.
+</p>
+
+<a name="example-endianess"></a>
+<div class="box">
+<p onclick="location.href='#example-endianess'" class="box-title">
+Endianess Example
+</p>
+<p>
+Writing the integer <code class="evidence">0x4A3B2C1Dh</code>, will be ordered:
+<ul>
+<li>in big endian as <code class="evidence">0x4Ah</code>, <code class="evidence">0x3Bh</code>, <code class="evidence">0x2Ch</code>, <code class="evidence">0x1Dh</code></li>
+<li>in little endian as <code class="evidence">0x1Dh</code>, <code class="evidence">0x2Ch</code>, <code class="evidence">0x3Bh</code>, <code class="evidence">0x4Ah</code></li>
+</ul>
+</p>
+</div>
+
+<p>
+Blender supports little-endian and big-endian.<br>
+This means that when the endianness 
+is different between the blend-file and the PC your using, Blender changes it to the byte ordering 
+of your PC.
+</p>
+
+<a name="example-file-header"></a>
+<div class="box">
+<p onclick="location.href='#example-file-header'" class="box-title">
+File-header Example
+</p>
+
+<p>
+This hex-dump describes a file-header created with <code>blender</code> <code>2.54.0</code> on <code>little-endian</code> hardware with a <code>32 bits</code> pointer length.
+<code class="block">                               <span class="descr">pointer-size  version-number
+                                     |             |</span>
+0000 0000: [42 4C 45 4E 44 45 52]  [5F]  [76]  [32 35 34]        BLENDER_v254  <span class="descr">
+                      |                   |
+                  identifier          endianness</span></code>
+</p>
+</div>
+
+<a name="file-blocks" href="#file-blocks"><h3>File-blocks</h3></a>
+
+<p>
+File-blocks contain a "<a href="#file-block-header">file-block header</a>" and "file-block data". 
+</p>
+
+<a name="file-block-header" href="#file-block-header"><h3>File-block headers</h3></a>
+
+<p>
+The file-block-header describes:
+</p>
+
+<ul>
+<li>the type of information stored in the 
+file-block</li>
+<li>the total length of the data</li>
+<li>the old memory 
+pointer at the moment the data was written to disk</li>
+<li>the number of items of this information</li>
+</ul>
+
+<p>
+As we can see below, depending on the pointer-size stored in the file-header, a file-block-header 
+can be 20 or 24 bytes long, hence it is always aligned at 4 bytes.
+</p>
+
+<table>
+<caption>File-block-header</caption>
+<thead>
+<tr>
+<th>reference</th>
+    <th>structure</th>
+    <th>type</th>
+    <th>offset</th>
+    <th>size</th></tr></thead>
+<tbody>
+<tr><td>code</td>
+    <td>char[4]</td>
+    <td>File-block identifier</td>
+    <td>0</td>
+    <td>4</td></tr>
+<tr><td>size</td>
+    <td>integer</td>
+    <td>Total length of the data after the file-block-header</td>
+    <td>4</td>
+    <td>4</td></tr>
+<tr><td>old memory address</td>
+    <td>void*</td>
+    <td>Memory address the structure was located when written to disk</td>
+    <td>8</td>
+    <td>pointer-size (4/8)</td></tr>
+<tr><td>SDNA index</td>
+    <td>integer</td>
+    <td>Index of the SDNA structure</td>
+    <td>8+pointer-size</td>
+    <td>4</td></tr>
+<tr><td>count</td>
+    <td>integer</td>
+    <td>Number of structure located in this file-block</td>
+    <td>12+pointer-size</td>
+    <td>4</td></tr>
+</tbody>
+</table>
+
+<p>
+The above table describes how a file-block-header is structured:
+</p>
+
+<ul>
+<li><code>Code</code> describes different types of file-blocks. The code determines with what logic the data must be read. <br>  
+These codes also allows fast finding of data like Library, Scenes, Object or Materials as they all have a specific code. </li>
+<li><code>Size</code> contains the total length of data after the file-block-header. 
+After the data a new file-block starts. The last file-block in the file 
+has code 'ENDB'.</li>
+<li><code>Old memory address</code> contains the memory address when the structure 
+was last stored. When loading the file the structures can be placed on 
+different memory addresses. Blender updates pointers to these structures
+ to the new memory addresses.</li>
+<li><code>SDNA index</code> contains the index in the DNA structures to be used when 
+reading this file-block-data. <br>
+More information about this subject will be explained in the <a href="#reading-scene-information">Reading scene information section</a>.</li>
+<li><code>Count</code> tells how many elements of the specific SDNA structure can be found in the data.</li> 
+</ul>
+
+<a name="example-file-block-header"></a>
+<div class="box">
+<p onclick="location.href='#example-file-block-header'" class="box-title">
+Example
+</p>
+<p>
+This hex-dump describes a File-block (= <span class="header">File-block header</span> + <span class="data">File-block data</span>) created with <code>blender</code> <code>2.54</code> on <code>little-endian</code> hardware with a <code>32 bits</code> pointer length.<br>
+<code class="block"><span class="descr">             file-block 
+           identifier='SC'  data size=1404  old pointer   SDNA index=150
+                 |              |              |              |</span>
+0000 4420: <span class="header">[53 43 00 00]  [7C 05 00 00]  [68 34 FB 0B]  [96 00 00 00]</span>    SC.. `... ./.. .... 
+0000 4430: <span class="header">[01 00 00 00]</span>  <span class="data">[xx xx xx xx    xx xx xx xx    xx xx xx xx</span>     .... xxxx xxxx xxxx<span class="descr">
+                 |              |
+              count=1      file-block data (next 1404 bytes)</span>
+</code>
+</p>
+
+<ul>
+<li>The code <code>'SC'+0x00h</code> identifies that it is a Scene. </li>
+<li>Size of the data is 1404 bytes (0x0000057Ch = 0x7Ch + 0x05h * 256 = 124 + 1280)</li>
+<li>The old pointer is 0x0BFB3468h</li>
+<li>The SDNA index is 150 (0x00000096h = 6 + 9 * 16 = 6 + 144)</li>
+<li>The section contains a single scene (count = 1).</li>
+</ul>
+
+<p>
+Before we can interpret the data of this file-block we first have to read the DNA structures in the file. 
+The section "<a href="#structure-DNA">Structure DNA</a>" will show how to do that. 
+</p>
+</div>
+
+<a name="structure-DNA" href="#structure-DNA"><h2>Structure DNA</h2></a>
+
+<a name="DNA1-file-block" href="#DNA1-file-block"><h3>The DNA1 file-block</h3></a>
+
+<p>
+Structure DNA is stored in a file-block with code 'DNA1'. It can be just before the 'ENDB' file-block.
+</p>
+
+<p>
+The 'DNA1' file-block contains all internal structures of the Blender release the 
+file was created in. <br>
+These structure can be described as C-structures: they can hold fields, arrays and 
+pointers to other structures, just like a normal C-structure.
+
+<p>
+<code class="block">struct SceneRenderLayer {
+    struct SceneRenderLayer *next, *prev;
+    char name[32];
+    struct Material *mat_override;
+    struct Group *light_override;
+    unsigned int lay;
+    unsigned int lay_zmask;
+    int layflag;
+    int pad;
+    int passflag;
+    int pass_xor;
+};
+</code>
+</p>
+
+<p>
+For example,a blend-file created with Blender 2.54 the 'DNA1' file-block is 57796 bytes long and contains 398 structures.
+</p>
+
+<a name="DNA1-file-block-header" href="#DNA1-file-block-header"><h3>DNA1 file-block-header</h3></a>
+
+<p>
+The DNA1 file-block header follows the same rules of any other file-block, see the example below.
+</p>
+
+<a name="example-DNA1-file-block-header"></a>
+<div class="box">
+<p onclick="location.href='#example-DNA1-file-block-header'" class="box-title">
+Example
+</p>
+<p>
+This hex-dump describes the file-block 'DNA1' header created with <code>blender</code> <code>2.54.0</code> on <code>little-endian</code> hardware with a <code>32 bits</code> pointer length.<br>
+<code class="block"><span class="descr">      (file-block 
+       identifier='DNA1') data size=57796   old pointer   SDNA index=0
+                  |              |              |              |</span>
+0004 B060   <span class="header">[44 4E 41 31]  [C4 E1 00 00]  [C8 00 84 0B]  [00 00 00 00]</span>  DNA1............
+0004 B070   <span class="header">[01 00 00 00]</span>  <span class="fade">[53 44 4E 41    4E 41 4D 45    CB 0B 00 00</span>   ....<span class="fade">SDNANAME....</span><span class="descr">
+                  |              |
+              count=1    'DNA1' file-block data (next 57796 bytes)</span>
+</code>
+</p>
+</div>
+
+<a name="DNA1-file-block-data" href="#DNA1-file-block-data"><h3>DNA1 file-block data</h3></a>
+<p>
+The next section describes how this information is ordered in the <b>data</b> of the 'DNA1' file-block.
+</p>
+
+<table>
+<caption>Structure of the DNA file-block-data</caption>
+<thead>
+    <tr><th colspan="2">repeat condition</th>
+    <th>name</th>
+    <th>type</th>
+    <th>length</th>
+    <th>description</th></tr>
+</thead>
+<tbody>
+<tr><td></td>
+    <td></td>
+    <td>identifier</td>
+    <td>char[4]</td>
+    <td>4</td>
+    <td>'SDNA'</td></tr>
+<tr><td></td>
+    <td></td>
+    <td>name identifier</td>
+    <td>char[4]</td>
+    <td>4</td>
+    <td>'NAME'</td></tr>
+<tr><td></td>
+    <td></td>
+    <td>#names</td>
+    <td>integer</td>
+    <td>4</td>
+    <td>Number of names follows</td></tr>
+<tr><td>for(#names)</td>
+    <td></td>
+    <td>name</td>
+    <td>char[]</td>
+    <td>?</td>
+    <td>Zero terminating string of name, also contains pointer and simple array definitions (e.g. '*vertex[3]\0')</td></tr>
+<tr><td></td>
+    <td></td>
+    <td>type identifier</td>
+    <td>char[4]</td>
+    <td>4</td>
+    <td>'TYPE' this field is aligned at 4 bytes</td></tr>
+<tr><td></td>
+    <td></td>
+    <td>#types</td>
+    <td>integer</td>
+    <td>4</td>
+    <td>Number of types follows</td></tr>
+<tr><td>for(#types)</td>
+    <td></td>
+    <td>type</td>
+    <td>char[]</td>
+    <td>?</td>
+    <td>Zero terminating string of type (e.g. 'int\0')</td></tr>
+<tr><td></td>
+    <td></td>
+    <td>length identifier</td>
+    <td>char[4]</td>
+    <td>4</td>
+    <td>'TLEN' this field is aligned at 4 bytes</td></tr>
+<tr><td>for(#types)</td>
+    <td></td>
+    <td>length</td>
+    <td>short</td>
+    <td>2</td>
+    <td>Length in bytes of type (e.g. 4)</td></tr>
+<tr><td></td>
+    <td></td>
+    <td>structure identifier</td>
+    <td>char[4]</td>
+    <td>4</td>
+    <td>'STRC' this field is aligned at 4 bytes</td></tr>
+<tr><td></td>
+    <td></td>
+    <td>#structures</td>
+    <td>integer</td>
+    <td>4</td>
+    <td>Number of structures follows</td></tr>
+<tr><td>for(#structures)</td>
+    <td></td>
+    <td>structure type</td>
+    <td>short</td>
+    <td>2</td>
+    <td>Index in types containing the name of the structure</td></tr>
+<tr><td>..</td>
+    <td></td>
+    <td>#fields</td>
+    <td>short</td>
+    <td>2</td>
+    <td>Number of fields in this structure</td></tr>
+<tr><td>..</td>
+    <td>for(#field)</td>
+    <td>field type</td>
+    <td>short</td>
+    <td>2</td>
+    <td>Index in type</td></tr>
+<tr><td>for end</td>
+    <td>for end</td>
+    <td>field name</td>
+    <td>short</td>
+    <td>2</td>
+    <td>Index in name</td></tr>
+</tbody>
+</table>
+
+<p>
+As you can see, the structures are stored in 4 arrays: names, types, 
+lengths and structures. Every structure also contains an array of 
+fields. A field is the combination of a type and a name. From this 
+information a catalog of all structures can be constructed. 
+The names are stored as how a C-developer defines them. This means that 
+the name also defines pointers and arrays.
+(When a name starts with '*' it is used as a pointer. when the name 
+contains for example '[3]' it is used as a array of 3 long.)
+In the types you'll find simple-types (like: 'integer', 'char', 
+'float'), but also complex-types like 'Scene' and 'MetaBall'. 
+'TLEN' part describes the length of the types. A 'char' is 1 byte, an 
+'integer' is 4 bytes and a 'Scene' is 1376 bytes long.
+</p>
+
+<div class="box">
+<p class="box-title">
+Note
+</p>
+<p>
+All identifiers, are arrays of 4 chars, hence they are all aligned at 4 bytes.
+</p>
+</div>
+
+<a name="example-DNA1-file-block-data"></a>
+<div class="box">
+<p onclick="location.href='#example-DNA1-file-block-data'" class="box-title">
+Example
+</p>
+<p>
+Created with <code>blender</code> <code>2.54.0</code> on <code>little-endian</code> hardware with a <code>32 bits</code> pointer length.
+</p>
+
+<a name="DNA1-data-array-names" href="#DNA1-data-array-names"><h4>The names array</h4></a>
+<p>
+The first names are: *next, *prev, *data, *first, *last, x, y, xmin, xmax, ymin, ymax, *pointer, group, val, val2, type, subtype, flag, name[32], ...
+<code class="block"><span class="descr">    file-block-data identifier='SDNA'  array-id='NAME'     number of names=3019
+                                |              |             |</span>
+0004 B070    <span class="fade">01 00  00 00 [53 44  4E 41]</span><span class="data">[4E  41 4D 45] [CB 0B 00  00]</span>  <span class="fade">....SDNA</span>NAME....
+0004 B080   <span class="data">[2A 6E  65 78  74 00][2A 70  72  65 76 00] [2A 64 61  74</span>   *next.*prev.*dat<span class="descr">
+                      |                    |                   | 
+                  '*next\0'            '*prev\0'            '*dat'</span><span class="fade">
+                                  ....
+                                  .... (3019 names)</span>
+</code>
+</p>
+
+<div class="box">
+<p class="box-title">
+Note
+</p>
+<p>
+While reading the DNA you'll will come across some strange 
+names like '(*doit)()'. These are method pointers and Blender updates 
+them to the correct methods.
+</p>
+</div>
+
+<a name="DNA1-data-array-types" href="#DNA1-data-array-types"><h4>The types array</h4></a>
+<p>
+The first types are: char, uchar, short, ushort, int, long, ulong, float, double, void, Link, LinkData, ListBase, vec2s, vec2f, ...
+<code class="block"><span class="descr">                                                      array-id='TYPE'
+                                                            |</span>
+0005 2440    <span class="fade">6F 6C 64 5B   34 5D 5B 34 5D 00 00 00</span>   [54 59 50  45]  <span class="fade">old[4][4]...</span>TYPE
+0005 2450   [C9 01 00 00] [63 68 61 72 00]  [75 63 68 61 72 00][73   ....char.uchar.s<span class="descr">
+                  |                |                 |           |
+       number of types=457     'char\0'          'uchar\0'      's'</span><span class="fade">
+                                  ....
+                                  .... (457 types)</span>
+</code>
+</p>
+
+<a name="DNA1-data-array-lengths" href="#DNA1-data-array-lengths"><h4>The lengths array</h4></a>
+<p>
+<code class="block"><span class="descr">                                                 char    uchar    ushort   short
+                                 array-id       length   length   length   length
+                                   'TLEN'          1        1        2        2</span>
+0005 3AA0    <span class="fade">45 00    00 00</span>   [54 4C    45 4E]  [01 00]  [01 00]  [02 00]  [02 00]  <span class="fade">E...</span>TLEN........
+                                  <span class="fade">....</span>
+0005 3AC0   [08 00]  [04 00]  [08 00]  [10 00]  [10 00]  [14 00]  [4C 00]  [34 00]  ............L.4.<span class="descr">
+               8        4        8       
+           ListBase   vec2s    vec2f    ... etc
+           length     len      length   </span><span class="fade">
+                                  ....
+                                  .... (457 lengths, same as number of types)</span>
+</code>
+</p>
+
+<a name="DNA1-data-array-structures" href="#DNA1-data-array-structures"><h4>The structures array</h4></a>
+<p>
+<code class="block"><span class="descr">                                                                 array-id='STRC'
+                                                                        |</span>
+0005 3E30    <span class="fade">40 00 38 00   60 00   00 00     00 00     00 00</span>    [53 54     52 43]  <span class="fade">@.8.`.......</span>STRC
+0005 3E40   [8E 01 00 00] [0A 00] [02 00]   [0A 00]   [00 00]   [0A 00]   [01 00]  ................<span class="descr">
+                 398         10      2         10        0         10        0  
+              number of    index    fields    index     index     index     index
+             structures   in <a href="#DNA1-data-array-types">types</a>           in <a href="#DNA1-data-array-types">types</a>  in <a href="#DNA1-data-array-names">names</a>  in <a href="#DNA1-data-array-types">types</a>   in <a href="#DNA1-data-array-names">names</a></span><span class="fade">
+                          '                  '----------------'  '-----------------'                                    '
+                          '                      field 0                field 1    '
+                          '--------------------------------------------------------'
+                                             structure 0
+                                  ....
+                                  .... (398 structures, each one describeing own type, and type/name for each field)</span>
+</code>
+</p>
+</div>
+
+<p>
+The DNA structures inside a Blender 2.48 blend-file can be found at <a href="http://www.atmind.nl/blender/blender-sdna.html">http://www.atmind.nl/blender/blender-sdna.html</a>.
+
+If we understand the DNA part of the file it is now possible to read 
+information from other parts file-blocks. The next section will tell us 
+how.
+</p>
+
+<a name="reading-scene-information" href="#reading-scene-information"><h2>Reading scene information</h2></a>
+
+<p>
+Let us look at <a href="#example-file-block-header">the file-block header we have seen earlier</a>:<br>
+</p>
+<ul>
+<li>the file-block identifier is <code>'SC'+0x00h</code></li>
+<li>the SDNA index is 150</li>
+<li>the file-block size is 1404 bytes</li>
+</ul>
+<p>
+Now note that:
+<ul>
+<li>the structure at index 150 in the DNA is a structure of type 'Scene' (counting from 0).</li>
+<li>the associated type ('Scene') in the DNA has the length of 1404 bytes.</li>
+</ul>
+</p>
+
+<p> 
+We can map the Scene structure on the data of the file-blocks. 
+But before we can do that, we have to flatten the Scene-structure.
+
+<code class="block">struct Scene {
+    ID id;              <span class="descr">// 52 bytes long (ID is different a structure)</span>
+    AnimData *adt;      <span class="descr">// 4 bytes long (pointer to an AnimData structure)</span>
+    Object *camera;     <span class="descr">// 4 bytes long (pointer to an Object structure)</span>
+    World *world;       <span class="descr">// 4 bytes long (pointer to an Object structure)</span>
+    ...
+    float cursor[3];    <span class="descr">// 12 bytes long (array of 3 floats)</span>
+    ...
+};
+</code>
+
+The first field in the Scene-structure is of type 'ID' with the name 'id'.
+Inside the list of DNA structures there is a structure defined for type 'ID' (structure index 17).
+<code class="block">struct ID {
+    void *next, *prev;
+    struct ID *newid;
+    struct Library *lib;
+    char name[24];
+    short us;
+    short flag;
+    int icon_id;
+    IDProperty *properties;
+};
+</code>
+
+The first field in this structure has type 'void' and name '*next'. <br>
+Looking in the structure list there is no structure defined for type 'void': it is a simple type and therefore the data should be read. 
+The name '*next' describes a pointer.
+As we see, the first 4 bytes of the data can be mapped to 'id.next'.
+</p>
+
+<p>
+Using this method we'll map a structure to its data. If we want to 
+read a specific field we know at which offset in the data it is located 
+and how much space it takes.<br>
+The next table shows the output of this flattening process for some 
+parts of the Scene-structure. Not all rows are described in the table as
+ there is a lot of information in a Scene-structure.
+</p>
+
+<table>
+<caption>Flattened SDNA structure 150: Scene</caption>
+<thead>
+<tr><th>reference</th>
+    <th>structure</th>
+    <th>type</th><th>name</th>
+    <th>offset</th><th>size</th>
+    <th>description</th></tr>
+</thead>
+<tbody>
+<tr><td>id.next</td><td><a href="#struct:ID">ID</a></td>
+    <td>void</td><td>*next</td>
+    <td>0</td>
+    <td>4</td>
+    <td>Refers to the next scene</td></tr>
+<tr><td>id.prev</td><td><a href="#struct:ID">ID</a></td>
+    <td>void</td><td>*prev</td>
+    <td>4</td>
+    <td>4</td>
+    <td>Refers to the previous scene</td></tr>
+<tr><td>id.newid</td><td><a href="#struct:ID">ID</a></td>
+    <td>ID</td><td>*newid</td>
+    <td>8</td>
+    <td>4</td>
+    <td></td></tr>
+<tr><td>id.lib</td><td><a href="#struct:ID">ID</a></td>
+    <td>Library</td><td>*lib</td>
+    <td>12</td>
+    <td>4</td>
+    <td></td></tr>
+<tr><td>id.name</td><td><a href="#struct:ID">ID</a></td>
+    <td>char</td><td>name[24]</td>
+    <td>16</td>
+    <td>24</td>
+    <td>'SC'+the name of the scene as displayed in Blender</td></tr>
+<tr><td>id.us</td><td><a href="#struct:ID">ID</a></td>
+    <td>short</td><td>us</td>
+    <td>40</td>
+    <td>2</td>
+    <td></td></tr>
+<tr><td>id.flag</td><td><a href="#struct:ID">ID</a></td>
+    <td>short</td><td>flag</td><td>42</td><td>2</td>
+    <td></td></tr>
+<tr><td>id.icon_id</td><td><a href="#struct:ID">ID</a></td>
+    <td>int</td><td>icon_id</td><td>44</td>
+    <td>4</td>
+    <td></td></tr>
+<tr><td>id.properties</td><td><a href="#struct:ID">ID</a></td>
+    <td>IDProperty</td><td>*properties</td>
+    <td>48</td>
+    <td>4</td>
+    <td></td></tr>
+<tr><td>adt</td><td>Scene</td><td>AnimData</td>
+    <td>*adt</td>
+    <td>52</td>
+    <td>4</td>
+    <td></td></tr>
+<tr><td>camera</td><td>Scene</td>
+    <td>Object</td>
+    <td>*camera</td>
+    <td>56</td>
+    <td>4</td>
+    <td>Pointer to the current camera</td></tr>
+<tr><td>world</td><td>Scene</td>
+    <td>World</td>
+    <td>*world</td>
+    <td>60</td>
+    <td>4</td>
+    <td>Pointer to the current world</td></tr>
+
+<tr><td class="skip" colspan="7">Skipped rows</td></tr>
+
+<tr><td>r.xsch</td><td><a href="#struct:RenderData">RenderData</a>
+    </td><td>short</td><td>xsch</td><td>382</td><td>2</td>
+    <td>X-resolution of the output when rendered at 100%</td></tr>
+<tr><td>r.ysch</td><td><a href="#struct:RenderData">RenderData</a>
+    </td><td>short</td><td>ysch</td><td>384</td><td>2</td>
+    <td>Y-resolution of the output when rendered at 100%</td></tr>
+<tr><td>r.xparts</td><td><a href="#struct:RenderData">RenderData</a>
+    </td><td>short</td><td>xparts</td><td>386</td><td>2</td>
+    <td>Number of x-part used by the renderer</td></tr>
+<tr><td>r.yparts</td><td><a href="#struct:RenderData">RenderData</a>
+    </td><td>short</td><td>yparts</td><td>388</td><td>2</td>
+    <td>Number of x-part used by the renderer</td></tr>
+
+<tr><td class="skip" colspan="7">Skipped rows</td></tr>
+
+<tr><td>gpd</td><td>Scene</td><td>bGPdata</td><td>*gpd</td><td>1376</td><td>4</td>
+    <td></td></tr>
+<tr><td>physics_settings.gravity</td><td><a href="#struct:PhysicsSettings">PhysicsSettings</a>
+    </td><td>float</td><td>gravity[3]</td><td>1380</td><td>12</td>
+    <td></td></tr>
+<tr><td>physics_settings.flag</td><td><a href="#struct:PhysicsSettings">PhysicsSettings</a>
+    </td><td>int</td><td>flag</td><td>1392</td><td>4</td>
+    <td></td></tr>
+<tr><td>physics_settings.quick_cache_step</td><td><a href="#struct:PhysicsSettings">PhysicsSettings</a>
+    </td><td>int</td><td>quick_cache_step</td><td>1396</td><td>4</td>
+    <td></td></tr>
+<tr><td>physics_settings.rt</td><td><a href="#struct:PhysicsSettings">PhysicsSettings</a>
+    </td><td>int</td><td>rt</td><td>1400</td><td>4</td>
+    <td></td></tr>
+</tbody>
+</table>
+
+<p>
+We can now read the X and Y resolution of the Scene:
+<ul>
+<li>the X-resolution is located on offset 382 of the file-block-data and must be read as a 
+short.</li>
+<li>the Y-resolution is located on offset 384 and is also a short</li>
+</ul>
+</p>
+
+<div class="box">
+<p class="box-title">
+Note
+</p>
+<p>
+An array of chars can mean 2 things. The field contains readable 
+text or it contains an array of flags (not humanly readable).
+</p>
+</div>
+
+<div class="box">
+<p class="box-title">
+Note
+</p>
+<p>
+A file-block containing a list refers to the DNA structure and has a count larger
+than 1. For example Vertexes and Faces are stored in this way.
+</p>
+</div>
+
+</body>
+</html>
+