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