== blender file format ==
[blender.git] / doc / blender_file_format / BlendFileDnaExporter_25.py
1 #! /usr/bin/env python3
2
3 # ***** BEGIN GPL LICENSE BLOCK *****
4 #
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.
9 #
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.
14 #
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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 #
19 # ***** END GPL LICENCE BLOCK *****
20
21 ######################################################
22 #
23 #    Name:
24 #        dna.py
25 #        
26 #    Description:
27 #        Creates a browsable DNA output to HTML.
28 #        
29 #    Author:
30 #        Jeroen Bakker
31 #        
32 #    Version:
33 #        v0.1 (12-05-2009) - migration of original source code to python.
34 #           Added code to support blender 2.5 branch
35 #        v0.2 (25-05-2009) - integrated with BlendFileReader.py
36 #        
37 #    Input:
38 #        blender build executable
39 #        
40 #    Output:
41 #        dna.html
42 #        dna.css (will only be created when not existing)
43 #
44 #    Startup:
45 #        ./blender -P BlendFileDnaExporter.py
46 #
47 #    Process:
48 #        1: write blend file with SDNA info
49 #        2: read blend header from blend file
50 #        3: seek DNA1 file-block
51 #        4: read dna record from blend file
52 #        5: close and eventually delete temp blend file
53 #        6: export dna to html and css
54 #        7: quit blender
55 #
56 ######################################################
57
58 import struct
59 import sys
60 import getopt                   # command line arguments handling
61 from string import Template     # strings completion
62
63
64 # logs
65 import logging
66 log = logging.getLogger("BlendFileDnaExporter")
67
68 if '--dna-debug' in sys.argv:
69     logging.basicConfig(level=logging.DEBUG)
70 else:
71     logging.basicConfig(level=logging.INFO)
72
73
74 class DNACatalogHTML:
75     '''
76     DNACatalog is a catalog of all information in the DNA1 file-block
77     '''
78
79     def __init__(self, catalog, bpy_module = None):
80         self.Catalog = catalog
81         self.bpy = bpy_module
82     
83     def WriteToHTML(self, handle):
84             
85         dna_html_template = """
86             <!DOCTYPE html PUBLIC -//W3C//DTD HTML 4.01 Transitional//EN http://www.w3.org/TR/html4/loose.dtd>
87             <html>
88             <head>
89                 <link rel="stylesheet" type="text/css" href="dna.css" media="screen, print" />
90                 <meta http-equiv="Content-Type" content="text/html"; charset="ISO-8859-1" />
91                 <title>The mystery of the blend</title>
92             </head>
93             <body>
94                 <div class=title>
95                     Blender ${version}<br/>
96                     Internal SDNA structures
97                 </div>
98                 Architecture: ${bitness} ${endianness}<br/>
99                 Build revision: <a href="https://svn.blender.org/svnroot/bf-blender/!svn/bc/${revision}/trunk/">${revision}</a><br/>
100                 File format reference: <a href="mystery_of_the_blend.html">The mystery of the blend</a> by Jeroen Bakker<br/>
101                 <h1>Index of blender structures</h1>
102                 <ul class=multicolumn>
103                     ${structs_list}
104                 </ul>
105                 ${structs_content}
106             </body>
107             </html>"""
108         
109         header = self.Catalog.Header
110         bpy = self.bpy
111         
112         # ${version} and ${revision}
113         if bpy:
114             version = '.'.join(map(str, bpy.app.version))
115             revision = bpy.app.build_revision[:-1]
116         else:
117             version = str(header.Version)
118             revision = 'Unknown'
119         
120         # ${bitness}
121         if header.PointerSize == 8:
122             bitness = '64 bit'
123         else:
124             bitness = '32 bit'
125
126         # ${endianness}
127         if header.LittleEndianness:
128             endianess= 'Little endianness'
129         else:
130             endianess= 'Big endianness'
131         
132         # ${structs_list}
133         log.debug("Creating structs index")
134         structs_list = ''
135         list_item = '<li class="multicolumn">({0}) <a href="#{1}">{1}</a></li>\n'
136         structureIndex = 0
137         for structure in self.Catalog.Structs:
138             structs_list += list_item.format(structureIndex, structure.Type.Name)
139             structureIndex+=1
140
141         # ${structs_content}
142         log.debug("Creating structs content")
143         structs_content = ''
144         for structure in self.Catalog.Structs:
145             log.debug(structure.Type.Name)
146             structs_content += self.Structure(structure)
147            
148         d = dict(
149             version = version, 
150             revision = revision, 
151             bitness = bitness, 
152             endianness = endianess, 
153             structs_list = structs_list, 
154             structs_content = structs_content
155         )
156
157         dna_html = Template(dna_html_template).substitute(d)
158         dna_html = self.format(dna_html)
159         handle.write(dna_html)
160     
161     def Structure(self, structure):
162         struct_table_template = """
163             <table><a name="${struct_name}"></a>
164                 <caption><a href="#${struct_name}">${struct_name}</a></caption>
165                 <thead>
166                     <tr>
167                         <th>reference</th>
168                         <th>structure</th>
169                         <th>type</th>
170                         <th>name</th>
171                         <th>offset</th>
172                         <th>size</th>
173                     </tr>
174                 </thead>
175                 <tbody>
176                 ${fields}
177                 </tbody>
178             </table>
179             <label>Total size: ${size} bytes</label><br/>
180             <label>(<a href="#top">top</a>)</label><br/>"""
181         
182         d = dict(
183             struct_name = structure.Type.Name, 
184             fields = self.StructureFields(structure, None, 0), 
185             size = str(structure.Type.Size)
186         )
187         
188         struct_table = Template(struct_table_template).substitute(d)
189         return struct_table
190         
191     def StructureFields(self, structure, parentReference, offset):
192         fields = ''
193         for field in structure.Fields:
194             fields += self.StructureField(field, structure, parentReference, offset)
195             offset += field.Size(self.Catalog.Header)
196         return fields
197         
198     def StructureField(self, field, structure, parentReference, offset):
199         structure_field_template = """
200             <tr>
201                 <td>${reference}</td>
202                 <td>${struct}</td>
203                 <td>${type}</td>
204                 <td>${name}</td>
205                 <td>${offset}</td>
206                 <td>${size}</td>
207             </tr>"""
208         
209         if field.Type.Structure == None or field.Name.IsPointer():
210
211             # ${reference}
212             reference = field.Name.AsReference(parentReference)
213
214             # ${struct}
215             if parentReference != None:
216                 struct = '<a href="#{0}">{0}</a>'.format(structure.Type.Name)
217             else:
218                 struct = structure.Type.Name
219             
220             # ${type}
221             type = field.Type.Name
222             
223             # ${name}
224             name = field.Name.Name
225             
226             # ${offset}
227             # offset already set
228             
229             # ${size}
230             size = field.Size(self.Catalog.Header)
231         
232             d = dict(
233                 reference = reference, 
234                 struct = struct, 
235                 type = type, 
236                 name = name, 
237                 offset = offset, 
238                 size = size
239             )
240             
241             structure_field = Template(structure_field_template).substitute(d)
242         
243         elif field.Type.Structure != None:
244             reference = field.Name.AsReference(parentReference)
245             structure_field = self.StructureFields(field.Type.Structure, reference, offset) 
246
247         return structure_field
248
249     def indent(self, input, dent, startswith = ''):
250         output = ''
251         if dent < 0:
252             for line in input.split('\n'):
253                 dent = abs(dent)
254                 output += line[dent:] + '\n'   # unindent of a desired amount
255         elif dent == 0:
256             for line in input.split('\n'):
257                 output += line.lstrip() + '\n'  # remove indentation completely
258         elif dent > 0:
259             for line in input.split('\n'):
260                 output += ' '* dent + line + '\n'
261         return output
262     
263     def format(self, input):
264         diff = {
265             '\n<!DOCTYPE':'<!DOCTYPE', 
266             '\n</ul>'    :'</ul>', 
267             '<a name'         :'\n<a name', 
268             '<tr>\n'     :'<tr>', 
269             '<tr>'       :'  <tr>', 
270             '</th>\n'    :'</th>', 
271             '</td>\n'    :'</td>', 
272             '<tbody>\n'  :'<tbody>'
273         }
274         output = self.indent(input, 0)
275         for key, value in diff.items():
276             output = output.replace(key, value)
277         return output
278
279     def WriteToCSS(self, handle):
280         '''
281         Write the Cascading stylesheet template to the handle
282         It is expected that the handle is a Filehandle
283         '''
284         css = """
285             @CHARSET "ISO-8859-1";
286             
287             body {
288                 font-family: verdana;
289                 font-size: small;
290             }
291             
292             div.title {
293                 font-size: large;
294                 text-align: center;
295             }
296             
297             h1 {
298                 page-break-before: always;
299             }
300
301             h1, h2 {
302                 background-color: #D3D3D3;
303                 color:#404040;
304                 margin-right: 3%;
305                 padding-left: 40px;
306             }
307             
308             h1:hover{
309                 background-color: #EBEBEB;
310             }
311
312             h3 {
313                 padding-left: 40px;
314             }
315             
316             table {
317                 border-width: 1px;
318                 border-style: solid;
319                 border-color: #000000;
320                 border-collapse: collapse;
321                 width: 94%;
322                 margin: 20px 3% 10px;
323             }
324             
325             caption {
326                 margin-bottom: 5px;
327             }
328             
329             th {
330                 background-color: #000000;
331                 color:#ffffff;
332                 padding-left:5px;
333                 padding-right:5px;
334             }
335             
336             tr {
337             }
338             
339             td {
340                 border-width: 1px;
341                 border-style: solid;
342                 border-color: #a0a0a0;
343                 padding-left:5px;
344                 padding-right:5px;
345             }
346             
347             label {
348                 float:right;
349                 margin-right: 3%;
350             }
351             
352             ul.multicolumn {
353                 list-style:none;
354                 float:left;
355                 padding-right:0px;
356                 margin-right:0px;
357             }
358
359             li.multicolumn {
360                 float:left;
361                 width:200px;
362                 margin-right:0px;
363             }
364             
365             a {
366                 color:#a000a0;
367                 text-decoration:none;
368             }
369             
370             a:hover {
371                 color:#a000a0;
372                 text-decoration:underline;
373             }
374             """
375             
376         css = self.indent(css, 0)
377
378         handle.write(css)
379
380
381 def usage():
382     print("\nUsage: \n\tblender2.5 -b -P BlendFileDnaExporter_25.py [-- [options]]")
383     print("Options:")
384     print("\t--dna-keep-blend:      doesn't delete the produced blend file DNA export to html")
385     print("\t--dna-debug:           sets the logging level to DEBUG (lots of additional info)")
386     print("\t--dna-versioned        saves version informations in the html and blend filenames")
387     print("\t--dna-overwrite-css    overwrite dna.css, useful when modifying css in the script")
388     print("Examples:")
389     print("\tdefault:       % blender2.5 -b -P BlendFileDnaExporter_25.py")
390     print("\twith options:  % blender2.5 -b -P BlendFileDnaExporter_25.py -- --dna-keep-blend --dna-debug\n")
391
392     
393 ######################################################
394 # Main
395 ######################################################
396
397 def main():
398     
399     import os, os.path
400
401     try:
402         bpy = __import__('bpy')
403
404         # Files
405         if '--dna-versioned' in sys.argv:
406             blender_version = '_'.join(map(str, bpy.app.version))
407             filename = 'dna-{0}-{1}_endian-{2}-r{3}'.format(sys.arch, sys.byteorder, blender_version, bpy.app.build_revision[2:-1])
408         else:
409             filename = 'dna'
410         dir = os.path.dirname(__file__)
411         Path_Blend = os.path.join(dir, filename + '.blend') # temporary blend file
412         Path_HTML  = os.path.join(dir, filename + '.html')  # output html file
413         Path_CSS   = os.path.join(dir, 'dna.css')           # output css file
414
415         # create a blend file for dna parsing
416         if not os.path.exists(Path_Blend):
417             log.info("1: write temp blend file with SDNA info")
418             log.info("   saving to: " + Path_Blend)
419             try:
420                 bpy.ops.wm.save_as_mainfile(filepath = Path_Blend, copy = True, compress = False)
421             except:
422                 log.error("Filename {0} does not exist and can't be created... quitting".format(Path_Blend))
423                 return
424         else:
425             log.info("1: found blend file with SDNA info")
426             log.info("   " + Path_Blend)
427         
428         # read blend header from blend file
429         log.info("2: read file:")
430         
431         if not dir in sys.path:
432             sys.path.append(dir)
433         import BlendFileReader
434         
435         handle = BlendFileReader.openBlendFile(Path_Blend)
436         blendfile = BlendFileReader.BlendFile(handle)
437         catalog = DNACatalogHTML(blendfile.Catalog, bpy)
438
439         # close temp file
440         handle.close()
441         
442         # deleting or not?
443         if '--dna-keep-blend' in sys.argv:
444             # keep the blend, useful for studying hexdumps
445             log.info("5: closing blend file:")
446             log.info("   {0}".format(Path_Blend))
447         else:
448             # delete the blend
449             log.info("5: close and delete temp blend:")
450             log.info("   {0}".format(Path_Blend))
451             os.remove(Path_Blend)
452         
453         # export dna to xhtml
454         log.info("6: export sdna to xhtml file")
455         handleHTML = open(Path_HTML, "w")
456         catalog.WriteToHTML(handleHTML)
457         handleHTML.close()
458
459         # only write the css when doesn't exist or at explicit request
460         if not os.path.exists(Path_CSS) or '--dna-overwrite-css' in sys.argv:
461             handleCSS = open(Path_CSS, "w")
462             catalog.WriteToCSS(handleCSS)
463             handleCSS.close()
464
465         # quit blender
466         if not bpy.app.background:
467             log.info("7: quit blender")
468             bpy.ops.wm.exit_blender()
469     
470     except ImportError:
471         log.warning("  skipping, not running in Blender")
472         usage()
473         sys.exit(2)
474         
475
476 if __name__ == '__main__':
477     main()