#!/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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 = """ The mystery of the blend
Blender ${version}
Internal SDNA structures
Architecture: ${bitness} ${endianness}
Build revision: ${revision}
File format reference: The mystery of the blend by Jeroen Bakker

Index of blender structures

${structs_content} """ 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 = '
  • ({0}) {1}
  • \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 = """ ${fields}
    ${struct_name}
    reference structure type name offset size


    """ 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 = """ ${reference} ${struct} ${type} ${name} ${offset} ${size} """ if field.Type.Structure is None or field.Name.IsPointer(): # ${reference} reference = field.Name.AsReference(parentReference) # ${struct} if parentReference is not None: struct = '{0}'.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 is not 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' :'', '\n' :'', '' :' ', '\n' :'', '\n' :'', '\n' :'' } 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 --background -noaudio --python 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 --background -noaudio --python BlendFileDnaExporter_25.py") print("\twith options: % blender2.5 --background -noaudio --python 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()