Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'doc/blender_file_format/BlendFileReader.py')
-rw-r--r--doc/blender_file_format/BlendFileReader.py446
1 files changed, 446 insertions, 0 deletions
diff --git a/doc/blender_file_format/BlendFileReader.py b/doc/blender_file_format/BlendFileReader.py
new file mode 100644
index 00000000000..7003af10ac7
--- /dev/null
+++ b/doc/blender_file_format/BlendFileReader.py
@@ -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)
+