blender/release/scripts/flt_export.py

724 lines
23 KiB
Python
Raw Normal View History

#!BPY
# flt_export.py is an OpenFlight exporter for blender.
# Copyright (C) 2005 Greg MacDonald
#
# 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.
""" Registration info for Blender menus:
Name: 'OpenFlight (.flt)...'
Blender: 237
Group: 'Export'
Tip: 'Export to OpenFlight v16.0 (.flt)'
"""
__author__ = "Greg MacDonald"
__version__ = "1.2 10/20/05"
__url__ = ("blender", "elysiun", "Author's homepage, http://sourceforge.net/projects/blight/")
__bpydoc__ = """\
This script exports v16.0 OpenFlight files. OpenFlight is a
registered trademark of MultiGen-Paradigm, Inc.
Run from "File->Export" menu.
Options are available from Blender's "Scripts Config Editor," accessible through
the "Scripts->System" menu from the scripts window.
Features:<br>
* Heirarchy retained.<br>
* Normals retained.<br>
* First texture exported.<br>
* Diffuse material color is exported as the face color, material color, or both
depending on the option settings.<br>
* Double sided faces are exported as two faces.<br>
* Object transforms exported.
Things To Be Aware Of:<br>
* Object names are exported, not mesh or data names.
* Material indices that don't have a material associated with them will confuse the
exporter. If a warning appears about this, correct it by deleting the offending
material indices in Blender.
What's Not Handled:<br>
* Animations.<br>
* Vetex colors.<br>
"""
import Blender
from flt_filewalker import FltOut
class ExporterOptions:
def __init__(self):
self.defaults = { 'Diffuse Color To OpenFlight Material': False,
'Diffuse Color To OpenFlight Face': True}
d = Blender.Registry.GetKey('flt_export', True)
if d == None or d.keys() != self.defaults.keys():
d = self.defaults
Blender.Registry.SetKey('flt_export', d, True)
self.verbose = 1
self.tolerance = 0.001
self.use_mat_color = d['Diffuse Color To OpenFlight Material']
self.use_face_color = d['Diffuse Color To OpenFlight Face']
options = ExporterOptions()
FLOAT_TOLERANCE = options.tolerance
identity_matrix = [[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0]]
def is_identity(m):
for i in xrange(4):
for j in xrange(4):
if abs(m[i][j] - identity_matrix[i][j]) > FLOAT_TOLERANCE:
return False
return True
class MaterialDesc:
def __init__(self):
self.name = 'Blender'
# Colors, List of 3 floats.
self.diffuse = [1.0, 1.0, 1.0]
self.specular = [1.0, 1.0, 1.0]
# Scalars
self.ambient = 0.1 # [0.0, 1.0]
self.emissive = 0.0 # [0.0, 1.0]
self.shininess = 32.0 # Range is [0.0, 128.0]
self.alpha = 1.0 # Range is [0.0, 1.0]
class VertexDesc:
def __init__(self, co=None, no=None, uv=None):
if co: self.x, self.y, self.z = tuple(co)
else: self.x = self.y = self.z = 0.0
if no: self.nx, self.ny, self.nz = tuple(no)
else: self.nx = self.ny = self.nz = 0.0
if uv: self.u, self.v = tuple(uv)
else: self.u = self.v = 0.0
class GlobalResourceRepository:
def new_face_name(self):
self.face_name += 1
return 'f%i' % (self.face_name-1)
def vertex_count(self):
return len(self.vertex_lst)
def request_vertex_desc(self, i):
return self.vertex_lst[i]
def request_vertex_index(self, desc):
match = None
for i, v in enumerate(self.vertex_lst):
if\
abs(v.x - desc.x) > FLOAT_TOLERANCE or\
abs(v.y - desc.y) > FLOAT_TOLERANCE or\
abs(v.z - desc.z) > FLOAT_TOLERANCE or\
abs(v.nx - desc.nx) > FLOAT_TOLERANCE or\
abs(v.ny - desc.ny) > FLOAT_TOLERANCE or\
abs(v.nz - desc.nz) > FLOAT_TOLERANCE or\
abs(v.u - desc.u) > FLOAT_TOLERANCE or\
abs(v.v - desc.v) > FLOAT_TOLERANCE:
pass
else:
match = i
break
if match != None:
return match
else:
self.vertex_lst.append(desc)
return len(self.vertex_lst) - 1
def request_texture_index(self, filename):
match = None
for i in xrange(len(self.texture_lst)):
if self.texture_lst[i] != filename:
continue
match = i
break
if match != None:
return match
else:
self.texture_lst.append(filename)
return len(self.texture_lst) - 1
def request_texture_filename(self, index):
return self.texture_lst[index]
def texture_count(self):
return len(self.texture_lst)
def request_material_index(self, desc):
match = None
for i in xrange(len(self.material_lst)):
if self.material_lst[i].diffuse != desc.diffuse:
continue
if self.material_lst[i].specular != desc.specular:
continue
if self.material_lst[i].ambient != desc.ambient:
continue
if self.material_lst[i].emissive != desc.emissive:
continue
if self.material_lst[i].shininess != desc.shininess:
continue
if self.material_lst[i].alpha != desc.alpha:
continue
match = i
break
if match != None:
return i
else:
self.material_lst.append(desc)
return len(self.material_lst) - 1
def request_material_desc(self, index):
return self.material_lst[index]
def material_count(self):
return len(self.material_lst)
# Returns not actual index but one that includes intensity information.
# color_index = 127*intensity + 128*actual_index
def request_color_index(self, col):
r,g,b = tuple(col)
m = max(r, g, b)
if m > 0.0:
intensity = m / 1.0
r = int(round(r/m * 255.0))
g = int(round(g/m * 255.0))
b = int(round(b/m * 255.0))
brightest = [r, g, b]
else:
brightest = [255, 255, 255]
intensity = 0.0
match = None
for i in xrange(len(self.color_lst)):
if self.color_lst[i] != brightest:
continue
match = i
break
if match != None:
index = match
else:
length = len(self.color_lst)
if length <= 1024:
self.color_lst.append(brightest)
index = length
else:
if options.verbose >= 1:
print 'Warning: Exceeded max color limit.'
index = 0
color_index = int(round(127.0*intensity)) + 128*index
return color_index
# Returns color from actual index.
def request_max_color(self, index):
return self.color_lst[index]
def color_count(self):
return len(self.color_lst)
def __init__(self):
self.vertex_lst = []
self.texture_lst = []
self.material_lst = []
self.color_lst = [[255, 255, 255]]
self.face_name = 0
class Node:
# Gathers info from blender needed for export.
# The =[0] is a trick to emulate c-like static function variables
# that are persistant between calls.
def blender_export(self, level=[0]):
if self.object:
if options.verbose >= 2:
print '\t' * level[0], self.name, self.object.getType()
level[0] += 1
for child in self.children:
child.blender_export()
level[0] -= 1
# Exports this node's info to file.
def write(self):
pass
def write_matrix(self):
if self.matrix and not is_identity(self.matrix):
self.header.fw.write_short(49) # Matrix opcode
self.header.fw.write_ushort(68) # Length of record
for i in xrange(4):
for j in xrange(4):
self.header.fw.write_float(self.matrix[i][j])
def write_push(self):
self.header.fw.write_short(10)
self.header.fw.write_ushort(4)
def write_pop(self):
self.header.fw.write_short(11)
self.header.fw.write_ushort(4)
def write_longid(self, name):
length = len(name)
if length >= 8:
self.header.fw.write_short(33) # Long ID opcode
self.header.fw.write_ushort(length+5) # Length of record
self.header.fw.write_string(name, length+1) # name + zero terminator
# Initialization sets up basic tree structure.
def __init__(self, parent, header, object, object_lst):
self.header = header
self.object = object
if object:
self.name = self.object.getName()
self.matrix = self.object.getMatrix('localspace')
else:
self.name = 'no name'
self.matrix = None
self.children = []
self.parent = parent
if parent:
parent.children.append(self)
left_over = object_lst[:]
self.child_objects = []
# Add children to child list and remove from left_over list.
# Pop is faster then remove
i = len(object_lst)
while i:
i-=1
if object_lst[i].parent == object:
self.child_objects.append(left_over.pop(i))
# Spawn children.
self.has_object_child = False # For Database class.
for child in self.child_objects:
if child.getType() == 'Mesh':
BlenderMesh(self, header, child, left_over)
self.has_object_child = True
else: # Treat all non meshes as emptys
BlenderEmpty(self, header, child, left_over)
class FaceDesc:
def __init__(self):
self.vertex_index_lst = []
self.texture_index = -1
self.material_index = -1
self.color_index = 127
class BlenderMesh(Node):
def blender_export(self):
Node.blender_export(self)
mesh = self.object.getData()
mesh_hasuv = mesh.hasFaceUV()
# Gather materials and textures.
tex_index_lst = []
mat_index_lst = []
color_index_lst = []
materials = mesh.getMaterials()
if not materials:
materials = [Blender.Material.New()]
for mat in materials:
# Gather Color.
if options.use_face_color:
color_index_lst.append(self.header.GRR.request_color_index(mat.getRGBCol()))
else:
color_index_lst.append(127) # white
# Gather Texture.
mtex_lst = mat.getTextures()
index = -1
mtex = mtex_lst[0] # Not doing multi-texturing at the moment.
if mtex != None:
tex = mtex_lst[0].tex
if tex != None:
image = tex.getImage()
if image != None:
filename = image.getFilename()
index = self.header.GRR.request_texture_index(filename)
tex_index_lst.append(index)
# Gather Material
mat_desc = MaterialDesc()
mat_desc.name = mat.getName()
mat_desc.alpha = mat.getAlpha()
mat_desc.shininess = mat.getSpec() * 64.0 # 2.0 => 128.0
if options.use_mat_color:
mat_desc.diffuse = mat.getRGBCol()
else:
mat_desc.diffuse = [1.0, 1.0, 1.0]
mat_desc.specular = mat.getSpecCol()
amb = mat.getAmb()
mat_desc.ambient = [amb, amb, amb]
emit = mat.getEmit()
mat_desc.emissive = [emit, emit, emit]
mat_index_lst.append(self.header.GRR.request_material_index(mat_desc))
# Faces described as lists of indices into the GRR's vertex_lst.
for face in mesh.faces:
face_v = face.v # Faster access
# Create vertex description list for each face.
if mesh_hasuv:
vertex_lst = [VertexDesc(v.co, v.no, face.uv[i]) for i, v in enumerate(face_v)]
else:
vertex_lst = [VertexDesc(v.co, v.no) for i, v in enumerate(face_v)]
index_lst = []
for vert_desc in vertex_lst:
index_lst.append(self.header.GRR.request_vertex_index(vert_desc))
face_desc = FaceDesc()
face_desc.vertex_index_lst = index_lst
if face.materialIndex < len(materials):
face_desc.color_index = color_index_lst[face.materialIndex]
face_desc.texture_index = tex_index_lst[face.materialIndex]
face_desc.material_index = mat_index_lst[face.materialIndex]
else:
if options.verbose >=1:
print 'Warning: Missing material for material index. Materials will not be imported correctly. Fix by deleting abandoned material indices in Blender.'
self.face_lst.append(face_desc)
# Export double sided face as 2 faces with opposite orientations.
if mesh_hasuv and face.mode & Blender.NMesh.FaceModes['TWOSIDE']:
# Create vertex description list for each face. they have a face mode, so we know they have a UV too.
vertex_lst = [VertexDesc(v.co, -v.no, face.uv[i]) for i, v in enumerate(face_v)]
vertex_lst.reverse() # Reversing flips the face.
index_lst = []
for vert_desc in vertex_lst:
index_lst.append(self.header.GRR.request_vertex_index(vert_desc))
face_desc = FaceDesc()
face_desc.vertex_index_lst = index_lst
if face.materialIndex < len(materials):
face_desc.color_index = color_index_lst[face.materialIndex]
face_desc.texture_index = tex_index_lst[face.materialIndex]
face_desc.material_index = mat_index_lst[face.materialIndex]
else:
if options.verbose >=1:
print 'Error: No material for material index. Delete abandoned material indices in Blender.'
self.face_lst.append(face_desc)
def write_faces(self):
for face_desc in self.face_lst:
face_name = self.header.GRR.new_face_name()
self.header.fw.write_short(5) # Face opcode
self.header.fw.write_ushort(80) # Length of record
self.header.fw.write_string(face_name, 8) # ASCII ID
self.header.fw.write_int(-1) # IR color code
self.header.fw.write_short(0) # Relative priority
self.header.fw.write_char(0) # Draw type
self.header.fw.write_char(0) # Draw textured white.
self.header.fw.write_ushort(0) # Color name index
self.header.fw.write_ushort(0) # Alt color name index
self.header.fw.write_char(0) # Reserved
self.header.fw.write_char(1) # Template
self.header.fw.write_short(-1) # Detail tex pat index
self.header.fw.write_short(face_desc.texture_index) # Tex pattern index
self.header.fw.write_short(face_desc.material_index) # material index
self.header.fw.write_short(0) # SMC code
self.header.fw.write_short(0) # Feature code
self.header.fw.write_int(0) # IR material code
self.header.fw.write_ushort(0) # transparency 0 = opaque
self.header.fw.write_uchar(0) # LOD generation control
self.header.fw.write_uchar(0) # line style index
self.header.fw.write_int(0x00000000) # Flags
self.header.fw.write_uchar(2) # Light mode
self.header.fw.pad(7) # Reserved
self.header.fw.write_uint(-1) # Packed color
self.header.fw.write_uint(-1) # Packed alt color
self.header.fw.write_short(-1) # Tex map index
self.header.fw.write_short(0) # Reserved
self.header.fw.write_uint(face_desc.color_index) # Color index
self.header.fw.write_uint(127) # Alt color index
self.header.fw.write_short(0) # Reserved
self.header.fw.write_short(-1) # Shader index
self.write_longid(face_name)
self.write_push()
# Vertex list record
self.header.fw.write_short(72) # Vertex list opcode
num_verts = len(face_desc.vertex_index_lst)
self.header.fw.write_ushort(4*num_verts+4) # Length of record
for vert_index in face_desc.vertex_index_lst:
# Offset into vertex palette
self.header.fw.write_int(vert_index*64+8)
self.write_pop()
def write(self):
if self.open_flight_type == 'Object':
self.header.fw.write_short(4) # Object opcode
self.header.fw.write_ushort(28) # Length of record
self.header.fw.write_string(self.name, 8) # ASCII ID
self.header.fw.pad(16)
self.write_longid(self.name)
self.write_matrix()
if self.face_lst != []:
self.write_push()
self.write_faces()
self.write_pop()
else:
self.header.fw.write_short(2) # Group opcode
self.header.fw.write_ushort(44) # Length of record
self.header.fw.write_string(self.name, 8) # ASCII ID
self.header.fw.pad(32)
self.write_longid(self.name)
# Because a group can contain faces as well as children.
self.write_push()
self.write_faces()
for child in self.children:
child.write()
self.write_pop()
def __init__(self, parent, header, object, object_lst):
Node.__init__(self, parent, header, object, object_lst)
self.face_lst = []
if self.children:
self.open_flight_type= 'Group'
else: # Empty list.
self.open_flight_type = 'Object'
class BlenderEmpty(Node):
def write(self):
self.header.fw.write_short(2) # Group opcode
self.header.fw.write_ushort(44) # Length of record
self.header.fw.write_string(self.name, 8) # ASCII ID
self.header.fw.pad(32)
self.write_longid(self.name)
self.write_matrix()
if self.children: # != []
self.write_push()
for child in self.children:
child.write()
self.write_pop()
class Database(Node):
def write_header(self):
if options.verbose >= 2:
print 'Writing header.'
self.fw.write_short(1) # Header opcode
self.fw.write_ushort(324) # Length of record
self.fw.write_string('db', 8) # ASCII ID
self.fw.write_int(1600) # Revision Number
self.fw.pad(44)
self.fw.write_short(1) # Unit multiplier.
self.fw.write_char(0) # Units, 0 = meters
self.fw.write_char(0) # texwhite on new faces 0 = false
self.fw.write_uint(0x80000000) # misc flags set to saving vertex normals
self.fw.pad(24)
self.fw.write_int(0) # projection type, 0 = flat earth
self.fw.pad(30)
self.fw.write_short(1) # double precision
self.fw.pad(140)
self.fw.write_int(0) # ellipsoid model, 0 = WSG 1984
self.fw.pad(52)
def write_vert_pal(self):
if options.verbose >= 2:
print 'Writing vertex palette.'
# Write record for vertex palette
self.fw.write_short(67) # Vertex palette opcode.
self.fw.write_short(8) # Length of record
self.fw.write_int(self.GRR.vertex_count() * 64 + 8) # Length of everything.
# Write records for individual vertices.
for i in xrange(self.GRR.vertex_count()):
desc = self.GRR.request_vertex_desc(i)
self.fw.write_short(70) # Vertex with color normal and uv opcode.
self.fw.write_ushort(64) # Length of record
self.fw.write_ushort(0) # Color name index
self.fw.write_short(0x2000) # Flags set to no color
self.fw.write_double(desc.x)
self.fw.write_double(desc.y)
self.fw.write_double(desc.z)
self.fw.write_float(desc.nx)
self.fw.write_float(desc.ny)
self.fw.write_float(desc.nz)
self.fw.write_float(desc.u)
self.fw.write_float(desc.v)
self.fw.pad(12)
def write_tex_pal(self):
if options.verbose >= 2:
print 'Writing texture palette.'
# Write record for texture palette
for i in xrange(self.GRR.texture_count()):
self.fw.write_short(64) # Texture palette opcode.
self.fw.write_short(216) # Length of record
self.fw.write_string(self.GRR.request_texture_filename(i), 200) # Filename
self.fw.write_int(i) # Texture index
self.fw.write_int(0) # X
self.fw.write_int(0) # Y
def write_mat_pal(self):
if options.verbose >= 2:
print 'Writing material palette.'
for i in xrange(self.GRR.material_count()):
desc = self.GRR.request_material_desc(i)
self.fw.write_short(113) # Material palette opcode.
self.fw.write_short(84) # Length of record
self.fw.write_int(i) # Material index
self.fw.write_string(desc.name, 12) # Material name
self.fw.write_uint(0x80000000) # Flags
self.fw.write_float(desc.ambient[0]) # Ambient color.
self.fw.write_float(desc.ambient[1]) # Ambient color.
self.fw.write_float(desc.ambient[2]) # Ambient color.
self.fw.write_float(desc.diffuse[0]) # Diffuse color.
self.fw.write_float(desc.diffuse[1]) # Diffuse color.
self.fw.write_float(desc.diffuse[2]) # Diffuse color.
self.fw.write_float(desc.specular[0]) # Specular color.
self.fw.write_float(desc.specular[1]) # Specular color.
self.fw.write_float(desc.specular[2]) # Specular color.
self.fw.write_float(desc.emissive[0]) # Emissive color.
self.fw.write_float(desc.emissive[1]) # Emissive color.
self.fw.write_float(desc.emissive[2]) # Emissive color.
self.fw.write_float(desc.shininess)
self.fw.write_float(desc.alpha)
self.fw.write_int(0) # Reserved
def write_col_pal(self):
if options.verbose >= 2:
print 'Writing color palette.'
self.fw.write_short(32) # Color palette opcode.
self.fw.write_short(4228) # Length of record
self.fw.pad(128)
count = self.GRR.color_count()
for i in xrange(count):
col = self.GRR.request_max_color(i)
self.fw.write_uchar(255) # alpha
self.fw.write_uchar(col[2]) # b
self.fw.write_uchar(col[1]) # g
self.fw.write_uchar(col[0]) # r
self.fw.pad(max(4096-count*4, 0))
def write(self):
self.write_header()
self.write_vert_pal()
self.write_tex_pal()
self.write_mat_pal()
self.write_col_pal()
# Wrap everything in a group if it has an object child.
if self.has_object_child:
self.header.fw.write_short(2) # Group opcode
self.header.fw.write_ushort(44) # Length of record
self.header.fw.write_string('g1', 8) # ASCII ID
self.header.fw.pad(32)
self.write_push()
for child in self.children:
child.write()
self.write_pop()
def __init__(self, scene, fw):
self.fw = fw
self.scene = scene
self.all_objects = scene.getChildren()
self.GRR = GlobalResourceRepository()
Node.__init__(self, None, self, None, self.all_objects)
def fs_callback(filename):
Blender.Window.WaitCursor(True)
if Blender.sys.exists(filename):
r = Blender.Draw.PupMenu('Overwrite ' + filename + '?%t|Yes|No')
if r != 1:
if options.verbose >= 1:
print 'Export cancelled.'
return
time1 = Blender.sys.time() # Start timing
fw = FltOut(filename)
db = Database(Blender.Scene.GetCurrent(), fw)
if options.verbose >= 1:
print 'Pass 1: Exporting from Blender.\n'
db.blender_export()
if options.verbose >= 1:
print 'Pass 2: Writing %s\n' % filename
db.write()
fw.close_file()
if options.verbose >= 1:
print 'Done in %.4f sec.\n' % (Blender.sys.time() - time1)
Blender.Window.WaitCursor(False)
if options.verbose >= 1:
print '\nOpenFlight Exporter'
print 'Version:', __version__
print 'Author: Greg MacDonald'
print __url__[2]
print
fname = Blender.sys.makename(ext=".flt")
Blender.Window.FileSelector(fs_callback, "Export OpenFlight v16.0", fname)