3 # ***** BEGIN GPL LICENSE BLOCK *****
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software Foundation,
17 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 # ***** END GPL LICENCE BLOCK *****
21 ######################################################
23 ######################################################
31 log = logging.getLogger("BlendFileReader")
33 ######################################################
34 # module global routines
35 ######################################################
38 def ReadString(handle, length):
40 ReadString reads a String of given length or a zero terminating String
44 return handle.read(length).decode()
46 # length == 0 means we want a zero terminating string
48 s = ReadString(handle, 1)
51 s = ReadString(handle, 1)
55 def Read(type, handle, fileheader):
57 Reads the chosen type from a file handle
59 def unpacked_bytes(type_char, size):
60 return struct.unpack(fileheader.StructPre + type_char, handle.read(size))[0]
63 return unpacked_bytes("H", 2) # unsigned short
65 return unpacked_bytes("h", 2) # short
67 return unpacked_bytes("I", 4) # unsigned int
69 return unpacked_bytes("i", 4) # int
71 return unpacked_bytes("f", 4) # float
73 return unpacked_bytes("Q", 8) # unsigned long
74 elif type == 'pointer':
75 # The pointersize is given by the header (BlendFileHeader).
76 if fileheader.PointerSize == 4:
77 return Read('uint', handle, fileheader)
78 if fileheader.PointerSize == 8:
79 return Read('ulong', handle, fileheader)
82 def openBlendFile(filename):
84 Open a filename, determine if the file is compressed and returns a handle
86 handle = open(filename, 'rb')
87 magic = ReadString(handle, 7)
88 if magic in ("BLENDER", "BULLETf"):
89 log.debug("normal blendfile detected")
90 handle.seek(0, os.SEEK_SET)
93 log.debug("gzip blendfile detected?")
95 log.debug("decompressing started")
96 fs = gzip.open(filename, "rb")
97 handle = tempfile.TemporaryFile()
98 data = fs.read(1024 * 1024)
101 data = fs.read(1024 * 1024)
102 log.debug("decompressing finished")
104 log.debug("resetting decompressed file")
105 handle.seek(0, os.SEEK_SET)
111 Aligns the filehandle on 4 bytes
113 offset = handle.tell()
116 handle.seek(4 - trim, os.SEEK_CUR)
119 ######################################################
121 ######################################################
125 Reads a blendfile and store the header, all the fileblocks, and catalogue
126 structs foound in the DNA fileblock
128 - BlendFile.Header (BlendFileHeader instance)
129 - BlendFile.Blocks (list of BlendFileBlock instances)
130 - BlendFile.Catalog (DNACatalog instance)
133 def __init__(self, handle):
134 log.debug("initializing reading blend-file")
135 self.Header = BlendFileHeader(handle)
137 fileblock = BlendFileBlock(handle, self)
138 found_dna_block = False
139 while not found_dna_block:
140 if fileblock.Header.Code in ("DNA1", "SDNA"):
141 self.Catalog = DNACatalog(self.Header, handle)
142 found_dna_block = True
144 fileblock.Header.skip(handle)
146 self.Blocks.append(fileblock)
147 fileblock = BlendFileBlock(handle, self)
149 # appending last fileblock, "ENDB"
150 self.Blocks.append(fileblock)
154 def FindBlendFileBlocksWithCode(self, code):
156 #for block in self.Blocks:
157 #if block.Header.Code.startswith(code) or block.Header.Code.endswith(code):
158 #result.append(block)
163 class BlendFileHeader:
165 BlendFileHeader allocates the first 12 bytes of a blend file.
166 It contains information about the hardware architecture.
167 Header example: BLENDER_v254
169 BlendFileHeader.Magic (str)
170 BlendFileHeader.PointerSize (int)
171 BlendFileHeader.LittleEndianness (bool)
172 BlendFileHeader.StructPre (str) see http://docs.python.org/py3k/library/struct.html#byte-order-size-and-alignment
173 BlendFileHeader.Version (int)
176 def __init__(self, handle):
177 log.debug("reading blend-file-header")
179 self.Magic = ReadString(handle, 7)
180 log.debug(self.Magic)
182 pointersize = ReadString(handle, 1)
183 log.debug(pointersize)
184 if pointersize == "-":
186 if pointersize == "_":
189 endianness = ReadString(handle, 1)
190 log.debug(endianness)
191 if endianness == "v":
192 self.LittleEndianness = True
194 if endianness == "V":
195 self.LittleEndianness = False
198 version = ReadString(handle, 3)
200 self.Version = int(version)
202 log.debug("{0} {1} {2} {3}".format(self.Magic, self.PointerSize, self.LittleEndianness, version))
205 class BlendFileBlock:
207 BlendFileBlock.File (BlendFile)
208 BlendFileBlock.Header (FileBlockHeader)
211 def __init__(self, handle, blendfile):
212 self.File = blendfile
213 self.Header = FileBlockHeader(handle, blendfile.Header)
215 def Get(self, handle, path):
216 log.debug("find dna structure")
217 dnaIndex = self.Header.SDNAIndex
218 dnaStruct = self.File.Catalog.Structs[dnaIndex]
219 log.debug("found " + dnaStruct.Type.Name)
220 handle.seek(self.Header.FileOffset, os.SEEK_SET)
221 return dnaStruct.GetField(self.File.Header, handle, path)
224 class FileBlockHeader:
226 FileBlockHeader contains the information in a file-block-header.
227 The class is needed for searching to the correct file-block (containing Code: DNA1)
234 FileOffset (= file pointer of datablock)
237 def __init__(self, handle, fileheader):
238 self.Code = ReadString(handle, 4).strip()
239 if self.Code != "ENDB":
240 self.Size = Read('uint', handle, fileheader)
241 self.OldAddress = Read('pointer', handle, fileheader)
242 self.SDNAIndex = Read('uint', handle, fileheader)
243 self.Count = Read('uint', handle, fileheader)
244 self.FileOffset = handle.tell()
246 self.Size = Read('uint', handle, fileheader)
250 self.FileOffset = handle.tell()
251 #self.Code += ' ' * (4 - len(self.Code))
252 log.debug("found blend-file-block-fileheader {0} {1}".format(self.Code, self.FileOffset))
254 def skip(self, handle):
255 handle.read(self.Size)
260 DNACatalog is a catalog of all information in the DNA1 file-block
268 def __init__(self, fileheader, handle):
269 log.debug("building DNA catalog")
273 self.Header = fileheader
275 SDNA = ReadString(handle, 4)
278 NAME = ReadString(handle, 4)
279 numberOfNames = Read('uint', handle, fileheader)
280 log.debug("building #{0} names".format(numberOfNames))
281 for i in range(numberOfNames):
282 name = ReadString(handle, 0)
283 self.Names.append(DNAName(name))
287 TYPE = ReadString(handle, 4)
288 numberOfTypes = Read('uint', handle, fileheader)
289 log.debug("building #{0} types".format(numberOfTypes))
290 for i in range(numberOfTypes):
291 type = ReadString(handle, 0)
292 self.Types.append(DNAType(type))
296 TLEN = ReadString(handle, 4)
297 log.debug("building #{0} type-lengths".format(numberOfTypes))
298 for i in range(numberOfTypes):
299 length = Read('ushort', handle, fileheader)
300 self.Types[i].Size = length
304 STRC = ReadString(handle, 4)
305 numberOfStructures = Read('uint', handle, fileheader)
306 log.debug("building #{0} structures".format(numberOfStructures))
307 for structureIndex in range(numberOfStructures):
308 type = Read('ushort', handle, fileheader)
309 Type = self.Types[type]
310 structure = DNAStructure(Type)
311 self.Structs.append(structure)
313 numberOfFields = Read('ushort', handle, fileheader)
314 for fieldIndex in range(numberOfFields):
315 fTypeIndex = Read('ushort', handle, fileheader)
316 fNameIndex = Read('ushort', handle, fileheader)
317 fType = self.Types[fTypeIndex]
318 fName = self.Names[fNameIndex]
319 structure.Fields.append(DNAField(fType, fName))
324 DNAName is a C-type name stored in the DNA.
329 def __init__(self, name):
332 def AsReference(self, parent):
336 result = parent + "."
338 result = result + self.ShortName()
343 result = result.replace("*", "")
344 result = result.replace("(", "")
345 result = result.replace(")", "")
346 Index = result.find("[")
348 result = result[0:Index]
352 return self.Name.find("*") > -1
354 def IsMethodPointer(self):
355 return self.Name.find("(*") > -1
360 Index = Temp.find("[")
363 Index2 = Temp.find("]")
364 result *= int(Temp[Index + 1:Index2])
365 Temp = Temp[Index2 + 1:]
366 Index = Temp.find("[")
373 DNAType is a C-type stored in the DNA
377 Structure = DNAStructure
380 def __init__(self, aName):
382 self.Structure = None
387 DNAType is a C-type structure stored in the DNA
393 def __init__(self, aType):
395 self.Type.Structure = self
398 def GetField(self, header, handle, path):
399 splitted = path.partition(".")
403 for field in self.Fields:
404 if field.Name.ShortName() == name:
405 log.debug("found " + name + "@" + str(offset))
406 handle.seek(offset, os.SEEK_CUR)
407 return field.DecodeField(header, handle, rest)
409 offset += field.Size(header)
411 log.debug("error did not find " + path)
417 DNAField is a coupled DNAType and DNAName.
423 def __init__(self, aType, aName):
427 def Size(self, header):
428 if self.Name.IsPointer() or self.Name.IsMethodPointer():
429 return header.PointerSize * self.Name.ArraySize()
431 return self.Type.Size * self.Name.ArraySize()
433 def DecodeField(self, header, handle, path):
435 if self.Name.IsPointer():
436 return Read('pointer', handle, header)
437 if self.Type.Name == "int":
438 return Read('int', handle, header)
439 if self.Type.Name == "short":
440 return Read('short', handle, header)
441 if self.Type.Name == "float":
442 return Read('float', handle, header)
443 if self.Type.Name == "char":
444 return ReadString(handle, self.Name.ArraySize())
446 return self.Type.Structure.GetField(header, handle, path)