e955c94ed3
Listing the "Blender Foundation" as copyright holder implied the Blender Foundation holds copyright to files which may include work from many developers. While keeping copyright on headers makes sense for isolated libraries, Blender's own code may be refactored or moved between files in a way that makes the per file copyright holders less meaningful. Copyright references to the "Blender Foundation" have been replaced with "Blender Authors", with the exception of `./extern/` since these this contains libraries which are more isolated, any changed to license headers there can be handled on a case-by-case basis. Some directories in `./intern/` have also been excluded: - `./intern/cycles/` it's own `AUTHORS` file is planned. - `./intern/opensubdiv/`. An "AUTHORS" file has been added, using the chromium projects authors file as a template. Design task: #110784 Ref !110783.
463 lines
14 KiB
Python
Executable File
463 lines
14 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# SPDX-FileCopyrightText: 2010-2022 Blender Authors
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
######################################################
|
|
#
|
|
# 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
|
|
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 = """
|
|
<!DOCTYPE html PUBLIC -//W3C//DTD HTML 4.01 Transitional//EN http://www.w3.org/TR/html4/loose.dtd>
|
|
<html>
|
|
<head>
|
|
<link rel="stylesheet" type="text/css" href="dna.css" media="screen, print" />
|
|
<meta http-equiv="Content-Type" content="text/html"; charset="ISO-8859-1" />
|
|
<title>The mystery of the blend</title>
|
|
</head>
|
|
<body>
|
|
<div class=title>
|
|
Blender ${version}<br/>
|
|
Internal SDNA structures
|
|
</div>
|
|
Architecture: ${bitness} ${endianness}<br/>
|
|
Build revision: <a href="https://svn.blender.org/svnroot/bf-blender/!svn/bc/${revision}/trunk/">${revision}</a><br/>
|
|
File format reference: <a href="mystery_of_the_blend.html">The mystery of the blend</a> by Jeroen Bakker<br/>
|
|
<h1>Index of blender structures</h1>
|
|
<ul class=multicolumn>
|
|
${structs_list}
|
|
</ul>
|
|
${structs_content}
|
|
</body>
|
|
</html>"""
|
|
|
|
header = self.Catalog.Header
|
|
bpy = self.bpy
|
|
|
|
# ${version} and ${revision}
|
|
if bpy:
|
|
version = '.'.join(map(str, bpy.app.version))
|
|
revision = bpy.app.build_hash
|
|
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 = '<li class="multicolumn">({0}) <a href="#{1}">{1}</a></li>\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 = """
|
|
<table><a name="${struct_name}"></a>
|
|
<caption><a href="#${struct_name}">${struct_name}</a></caption>
|
|
<thead>
|
|
<tr>
|
|
<th>reference</th>
|
|
<th>structure</th>
|
|
<th>type</th>
|
|
<th>name</th>
|
|
<th>offset</th>
|
|
<th>size</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
${fields}
|
|
</tbody>
|
|
</table>
|
|
<label>Total size: ${size} bytes</label><br/>
|
|
<label>(<a href="#top">top</a>)</label><br/>"""
|
|
|
|
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 = """
|
|
<tr>
|
|
<td>${reference}</td>
|
|
<td>${struct}</td>
|
|
<td>${type}</td>
|
|
<td>${name}</td>
|
|
<td>${offset}</td>
|
|
<td>${size}</td>
|
|
</tr>"""
|
|
|
|
if field.Type.Structure is None or field.Name.IsPointer():
|
|
|
|
# ${reference}
|
|
reference = field.Name.AsReference(parentReference)
|
|
|
|
# ${struct}
|
|
if parentReference is not None:
|
|
struct = '<a href="#{0}">{0}</a>'.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<!DOCTYPE': '<!DOCTYPE',
|
|
'\n</ul>': '</ul>',
|
|
'<a name': '\n<a name',
|
|
'<tr>\n': '<tr>',
|
|
'<tr>': ' <tr>',
|
|
'</th>\n': '</th>',
|
|
'</td>\n': '</td>',
|
|
'<tbody>\n': '<tbody>'
|
|
}
|
|
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 information 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
|
|
import 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}-{3}'.format(sys.arch, sys.byteorder, blender_version, bpy.app.build_hash)
|
|
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 dir not 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: %r" % Path_HTML)
|
|
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()
|