Added openflight import/export- Blight v1.2
[blender.git] / release / scripts / flt_export.py
1 #!BPY
2
3 # flt_export.py is an OpenFlight exporter for blender.
4 # Copyright (C) 2005 Greg MacDonald
5 #
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
10
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19
20 """ Registration info for Blender menus:
21 Name: 'OpenFlight (.flt)...'
22 Blender: 237
23 Group: 'Export'
24 Tip: 'Export to OpenFlight v16.0 (.flt)'
25 """
26
27 __author__ = "Greg MacDonald"
28 __version__ = "1.2 10/20/05"
29 __url__ = ("blender", "elysiun", "Author's homepage, http://sourceforge.net/projects/blight/")
30 __bpydoc__ = """\
31 This script exports v16.0 OpenFlight files.  OpenFlight is a
32 registered trademark of MultiGen-Paradigm, Inc.
33
34 Run from "File->Export" menu. 
35
36 Options are available from Blender's "Scripts Config Editor," accessible through
37 the "Scripts->System" menu from the scripts window.
38
39 Features:<br>
40 * Heirarchy retained.<br>
41 * Normals retained.<br>
42 * First texture exported.<br>
43 * Diffuse material color is exported as the face color, material color, or both
44 depending on the option settings.<br>
45 * Double sided faces are exported as two faces.<br>
46 * Object transforms exported.
47
48 Things To Be Aware Of:<br>
49 * Object names are exported, not mesh or data names.
50 * Material indices that don't have a material associated with them will confuse the
51 exporter. If a warning appears about this, correct it by deleting the offending
52 material indices in Blender.
53
54 What's Not Handled:<br>
55 * Animations.<br>
56 * Vetex colors.<br>
57 """
58
59 import Blender
60 import math
61 from flt_filewalker import FltOut
62
63 class ExporterOptions:
64     def __init__(self):
65         self.defaults = { 'Diffuse Color To OpenFlight Material': False,
66                           'Diffuse Color To OpenFlight Face': True}
67         
68         d = Blender.Registry.GetKey('flt_export', True)
69         
70         if d == None or d.keys() != self.defaults.keys():
71             d = self.defaults
72             Blender.Registry.SetKey('flt_export', d, True)
73         
74         self.verbose = 1
75         self.tolerance = 0.001
76         self.use_mat_color = d['Diffuse Color To OpenFlight Material']
77         self.use_face_color = d['Diffuse Color To OpenFlight Face']
78         
79 options = ExporterOptions()
80
81 def not_equal_float(f1, f2):
82     return math.fabs(f1 - f2) > options.tolerance
83
84 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]]
85
86 def is_identity(m):
87     for i in range(4):
88         for j in range(4):
89             if not_equal_float(m[i][j], identity_matrix[i][j]):
90                 return False
91     return True
92
93 class MaterialDesc:
94     def __init__(self):
95         self.name = 'Blender'
96
97         # Colors, List of 3 floats.
98         self.diffuse = [1.0, 1.0, 1.0]
99         self.specular = [1.0, 1.0, 1.0]
100
101         # Scalars
102         self.ambient = 0.1 # [0.0, 1.0]
103         self.emissive = 0.0 # [0.0, 1.0]
104         self.shininess = 32.0 # Range is [0.0, 128.0]
105         self.alpha = 1.0 # Range is [0.0, 1.0]
106
107 class VertexDesc:
108     def __init__(self):
109         self.x = 0.0
110         self.y = 0.0
111         self.z = 0.0
112         self.nx = 0.0
113         self.ny = 0.0
114         self.nz = 0.0
115         self.u = 0.0
116         self.v = 0.0
117
118 class GlobalResourceRepository:
119     def new_face_name(self):
120         n =  'f' + str(self.face_name)
121         self.face_name += 1
122         return n
123
124     def vertex_count(self):
125         return len(self.vertex_lst)
126
127     def request_vertex_desc(self, i):
128         return self.vertex_lst[i]
129
130     def request_vertex_index(self, desc):
131         match = None
132         for i in range(len(self.vertex_lst)):
133             if not_equal_float(self.vertex_lst[i].x, desc.x):
134                 continue
135             if not_equal_float(self.vertex_lst[i].y, desc.y):
136                 continue
137             if not_equal_float(self.vertex_lst[i].z, desc.z):
138                 continue
139             if not_equal_float(self.vertex_lst[i].nx, desc.nx):
140                 continue
141             if not_equal_float(self.vertex_lst[i].ny, desc.ny):
142                 continue
143             if not_equal_float(self.vertex_lst[i].nz, desc.nz):
144                 continue
145             if not_equal_float(self.vertex_lst[i].u, desc.u):
146                 continue
147             if not_equal_float(self.vertex_lst[i].v, desc.v):
148                 continue
149
150             match = i
151             break
152
153         if match != None:
154             return match
155         else:
156             self.vertex_lst.append(desc)
157             return len(self.vertex_lst) - 1
158
159     def request_texture_index(self, filename):
160         match = None
161         for i in range(len(self.texture_lst)):
162             if self.texture_lst[i] != filename:
163                 continue
164             match = i
165             break
166         if match != None:
167             return match
168         else:
169             self.texture_lst.append(filename)
170             return len(self.texture_lst) - 1
171
172     def request_texture_filename(self, index):
173         return self.texture_lst[index]
174
175     def texture_count(self):
176         return len(self.texture_lst)
177
178     def request_material_index(self, desc):
179         match = None
180         for i in range(len(self.material_lst)):
181             if self.material_lst[i].diffuse != desc.diffuse:
182                 continue
183             if self.material_lst[i].specular != desc.specular:
184                 continue
185             if self.material_lst[i].ambient != desc.ambient:
186                 continue
187             if self.material_lst[i].emissive != desc.emissive:
188                 continue
189             if self.material_lst[i].shininess != desc.shininess:
190                 continue
191             if self.material_lst[i].alpha != desc.alpha:
192                 continue
193             match = i
194             break
195
196         if match != None:
197             return i
198         else:
199             self.material_lst.append(desc)
200             return len(self.material_lst) - 1
201
202     def request_material_desc(self, index):
203         return self.material_lst[index]
204
205     def material_count(self):
206         return len(self.material_lst)
207
208     # Returns not actual index but one that includes intensity information.
209     # color_index = 127*intensity + 128*actual_index
210     def request_color_index(self, col):
211         r = col[0]
212         g = col[1]
213         b = col[2]
214         m = max(r, g, b)
215         if m > 0.0:
216             intensity = m / 1.0
217             r = int(round(r/m * 255.0))
218             g = int(round(g/m * 255.0))
219             b = int(round(b/m * 255.0))
220             brightest = [r, g, b]
221         else:
222             brightest = [255, 255, 255]
223             intensity = 0.0
224
225         match = None
226         for i in range(len(self.color_lst)):
227             if self.color_lst[i] != brightest:
228                 continue
229
230             match = i
231             break
232
233         if match != None:
234             index = match
235         else:
236             length = len(self.color_lst)
237             if length <= 1024:
238                 self.color_lst.append(brightest)
239                 index = length
240             else:
241                 if options.verbose >= 1:
242                     print 'Warning: Exceeded max color limit.'
243                 index = 0
244
245         color_index = int(round(127.0*intensity)) + 128*index
246         return color_index
247
248     # Returns color from actual index.
249     def request_max_color(self, index):
250         return self.color_lst[index]
251
252     def color_count(self):
253         return len(self.color_lst)
254
255     def __init__(self):
256         self.vertex_lst = []
257         self.texture_lst = []
258         self.material_lst = []
259         self.color_lst = [[255, 255, 255]]
260         self.face_name = 0
261
262 class Node:
263     # Gathers info from blender needed for export.
264     # The =[0] is a trick to emulate c-like static function variables
265     # that are persistant between calls.
266     def blender_export(self, level=[0]):
267         if self.object:
268             if options.verbose >= 2:
269                 for j in range(level[0]):
270                     print '  ',
271                 print self.name, type(self.object.getData())
272
273         level[0] += 1
274         
275         for child in self.children:
276             child.blender_export()
277             
278         level[0] -= 1
279
280     # Exports this node's info to file.
281     def write(self):
282         pass
283
284     def write_matrix(self):
285         if self.matrix and not is_identity(self.matrix):
286             self.header.fw.write_short(49)   # Matrix opcode
287             self.header.fw.write_ushort(68)  # Length of record
288             for i in range(4):
289                 for j in range(4):
290                     self.header.fw.write_float(self.matrix[i][j])
291
292     def write_push(self):
293         self.header.fw.write_short(10)
294         self.header.fw.write_ushort(4)
295
296     def write_pop(self):
297         self.header.fw.write_short(11)
298         self.header.fw.write_ushort(4)
299
300     def write_longid(self, name):
301         length = len(name)
302         if length >= 8:
303             self.header.fw.write_short(33)              # Long ID opcode
304             self.header.fw.write_ushort(length+5)       # Length of record
305             self.header.fw.write_string(name, length+1) # name + zero terminator
306
307     # Initialization sets up basic tree structure.
308     def __init__(self, parent, header, object, object_lst):
309         self.header = header
310         self.object = object
311         if object:
312             self.name = self.object.getName()
313             self.matrix = self.object.getMatrix('localspace')
314         else:
315             self.name = 'no name'
316             self.matrix = None
317
318         self.children = []
319         self.parent = parent
320         if parent:
321             parent.children.append(self)
322
323         left_over = object_lst[:]
324         self.child_objects = []
325
326         # Add children to child list and remove from left_over list.
327         for obj in object_lst:
328             if obj.getParent() == object:
329                 self.child_objects.append(obj)
330                 left_over.remove(obj)
331
332         # Spawn children.
333         self.has_object_child = False # For Database class.
334         for child in self.child_objects:
335             dat = child.getData()
336             t = type(dat)
337
338             if dat == None:
339                 BlenderEmpty(self, header, child, left_over)
340             if t == Blender.Types.NMeshType:
341                 BlenderMesh(self, header, child, left_over)
342                 self.has_object_child = True
343
344 class FaceDesc:
345     def __init__(self):
346         self.vertex_index_lst = []
347         self.texture_index = -1
348         self.material_index = -1
349         self.color_index = 127
350     
351 class BlenderMesh(Node):
352     def blender_export(self):
353         Node.blender_export(self)
354
355         mesh = self.object.getData()
356
357         # Gather materials and textures.
358         tex_index_lst = []
359         mat_index_lst = []
360         color_index_lst = []
361         materials = mesh.getMaterials()
362         
363         if materials == []:
364             materials = [Blender.Material.New()]
365         
366         for mat in materials:
367             # Gather Color.
368             if options.use_face_color:
369                 color_index_lst.append(self.header.GRR.request_color_index(mat.getRGBCol()))
370             else:
371                 color_index_lst.append(127) # white
372             # Gather Texture.
373             mtex_lst = mat.getTextures()
374
375             index = -1
376             mtex = mtex_lst[0] # Not doing multi-texturing at the moment.
377             if mtex != None:
378                 tex = mtex_lst[0].tex
379                 if tex != None:
380                     image = tex.getImage()
381                     if image != None:
382                         filename = image.getFilename()
383                         index = self.header.GRR.request_texture_index(filename)
384
385             tex_index_lst.append(index)
386
387             # Gather Material
388             mat_desc = MaterialDesc()
389             mat_desc.name = mat.getName()
390             mat_desc.alpha = mat.getAlpha()
391             mat_desc.shininess = mat.getSpec() * 64.0   # 2.0 => 128.0
392             if options.use_mat_color:
393                 mat_desc.diffuse = mat.getRGBCol()
394             else:
395                 mat_desc.diffuse = [1.0, 1.0, 1.0]
396
397             mat_desc.specular = mat.getSpecCol()
398             amb = mat.getAmb()
399             mat_desc.ambient = [amb, amb, amb]
400             emit = mat.getEmit()
401             mat_desc.emissive = [emit, emit, emit]
402
403             mat_index_lst.append(self.header.GRR.request_material_index(mat_desc))
404
405         # Faces described as lists of indices into the GRR's vertex_lst.
406         for face in mesh.faces:
407             # Create vertex description list for each face.
408             vertex_lst = []
409             for vertex in face.v:
410                 vert_desc = VertexDesc()
411                 vert_desc.x = vertex.co[0]
412                 vert_desc.y = vertex.co[1]
413                 vert_desc.z = vertex.co[2]
414                 vert_desc.nx = vertex.no[0]
415                 vert_desc.ny = vertex.no[1]
416                 vert_desc.nz = vertex.no[2]
417                 vertex_lst.append(vert_desc)
418
419             for j in range( min(len(face.uv),len(vertex_lst)) ):
420                 vertex_lst[j].u = face.uv[j][0]
421                 vertex_lst[j].v = face.uv[j][1]
422
423             index_lst = []
424             for vert_desc in vertex_lst:
425                 index_lst.append(self.header.GRR.request_vertex_index(vert_desc))
426             
427             face_desc = FaceDesc()
428             face_desc.vertex_index_lst = index_lst
429             
430             if face.materialIndex < len(materials):
431                 face_desc.color_index = color_index_lst[face.materialIndex]
432                 face_desc.texture_index = tex_index_lst[face.materialIndex]
433                 face_desc.material_index = mat_index_lst[face.materialIndex]
434             else:
435                 if options.verbose >=1:
436                     print 'Warning: Missing material for material index. Materials will not be imported correctly. Fix by deleting abandoned material indices in Blender.'
437
438             self.face_lst.append(face_desc)
439             
440             # Export double sided face as 2 faces with opposite orientations.
441             if mesh.hasFaceUV() and face.mode & Blender.NMesh.FaceModes['TWOSIDE']:
442                 # Create vertex description list for each face.
443                 vertex_lst = []
444                 for vertex in face.v:
445                     vert_desc = VertexDesc()
446                     vert_desc.x = vertex.co[0]
447                     vert_desc.y = vertex.co[1]
448                     vert_desc.z = vertex.co[2]
449                     vert_desc.nx = -vertex.no[0]
450                     vert_desc.ny = -vertex.no[1]
451                     vert_desc.nz = -vertex.no[2]
452                     vertex_lst.append(vert_desc)
453     
454                 for j in range( min(len(face.uv),len(vertex_lst)) ):
455                     vertex_lst[j].u = face.uv[j][0]
456                     vertex_lst[j].v = face.uv[j][1]
457     
458                 vertex_lst.reverse()
459                 
460                 index_lst = []
461                 for vert_desc in vertex_lst:
462                     index_lst.append(self.header.GRR.request_vertex_index(vert_desc))
463                 
464                 face_desc = FaceDesc()
465                 face_desc.vertex_index_lst = index_lst
466                 if face.materialIndex < len(materials):
467                     face_desc.color_index = color_index_lst[face.materialIndex]
468                     face_desc.texture_index = tex_index_lst[face.materialIndex]
469                     face_desc.material_index = mat_index_lst[face.materialIndex]
470                 else:
471                     if options.verbose >=1:
472                         print 'Error: No material for material index. Delete abandoned material indices in Blender.'
473     
474                 self.face_lst.append(face_desc)
475
476     def write_faces(self):
477         for face_desc in self.face_lst:
478             face_name = self.header.GRR.new_face_name()
479             
480             self.header.fw.write_short(5)                                   # Face opcode
481             self.header.fw.write_ushort(80)                                 # Length of record
482             self.header.fw.write_string(face_name, 8)                       # ASCII ID
483             self.header.fw.write_int(-1)                                    # IR color code
484             self.header.fw.write_short(0)                                   # Relative priority
485             self.header.fw.write_char(0)                                    # Draw type
486             self.header.fw.write_char(0)                                    # Draw textured white.
487             self.header.fw.write_ushort(0)                                  # Color name index
488             self.header.fw.write_ushort(0)                                  # Alt color name index
489             self.header.fw.write_char(0)                                    # Reserved
490             self.header.fw.write_char(1)                                    # Template
491             self.header.fw.write_short(-1)                                  # Detail tex pat index
492             self.header.fw.write_short(face_desc.texture_index)             # Tex pattern index
493             self.header.fw.write_short(face_desc.material_index)            # material index
494             self.header.fw.write_short(0)                                   # SMC code
495             self.header.fw.write_short(0)                                   # Feature code
496             self.header.fw.write_int(0)                                     # IR material code
497             self.header.fw.write_ushort(0)                                  # transparency 0 = opaque
498             self.header.fw.write_uchar(0)                                   # LOD generation control
499             self.header.fw.write_uchar(0)                                   # line style index
500             self.header.fw.write_int(0x00000000)                            # Flags
501             self.header.fw.write_uchar(2)                                   # Light mode
502             self.header.fw.pad(7)                                           # Reserved
503             self.header.fw.write_uint(-1)                                   # Packed color
504             self.header.fw.write_uint(-1)                                   # Packed alt color
505             self.header.fw.write_short(-1)                                  # Tex map index
506             self.header.fw.write_short(0)                                   # Reserved
507             self.header.fw.write_uint(face_desc.color_index)                # Color index
508             self.header.fw.write_uint(127)                                  # Alt color index
509             self.header.fw.write_short(0)                                   # Reserved
510             self.header.fw.write_short(-1)                                  # Shader index
511
512             self.write_longid(face_name)
513
514             self.write_push()
515
516             # Vertex list record
517             self.header.fw.write_short(72)                        # Vertex list opcode
518             num_verts = len(face_desc.vertex_index_lst)
519             self.header.fw.write_ushort(4*num_verts+4)            # Length of record
520
521             for vert_index in face_desc.vertex_index_lst:
522                 # Offset into vertex palette
523                 self.header.fw.write_int(vert_index*64+8)
524
525             self.write_pop()
526
527     def write(self):
528         if self.open_flight_type == 'Object':
529             self.header.fw.write_short(4)               # Object opcode
530             self.header.fw.write_ushort(28)             # Length of record
531             self.header.fw.write_string(self.name, 8)   # ASCII ID
532             self.header.fw.pad(16)
533     
534             self.write_longid(self.name)
535             
536             self.write_matrix()
537             
538             if self.face_lst != []:
539                 self.write_push()
540         
541                 self.write_faces()
542         
543                 self.write_pop()
544         else:
545             self.header.fw.write_short(2)               # Group opcode
546             self.header.fw.write_ushort(44)             # Length of record
547             self.header.fw.write_string(self.name, 8)   # ASCII ID
548             self.header.fw.pad(32)
549     
550             self.write_longid(self.name)
551             
552             # Because a group can contain faces as well as children.
553             self.write_push() 
554             
555             self.write_faces()
556             
557             for child in self.children:
558                 child.write()
559     
560             self.write_pop()
561             
562
563     def __init__(self, parent, header, object, object_lst):
564         Node.__init__(self, parent, header, object, object_lst)
565         self.face_lst = []
566         
567         if self.children == []:
568             self.open_flight_type = 'Object'
569         else:
570             self.open_flight_type= 'Group'
571
572 class BlenderEmpty(Node):
573     def write(self):
574         self.header.fw.write_short(2)               # Group opcode
575         self.header.fw.write_ushort(44)             # Length of record
576         self.header.fw.write_string(self.name, 8)   # ASCII ID
577         self.header.fw.pad(32)
578
579         self.write_longid(self.name)
580         
581         self.write_matrix()
582         
583         if self.children != []:
584             self.write_push()
585     
586             for child in self.children:
587                 child.write()
588                 
589             self.write_pop()
590
591 class Database(Node):
592     def write_header(self):
593         if options.verbose >= 2:
594             print 'Writing header.'
595         self.fw.write_short(1)          # Header opcode
596         self.fw.write_ushort(324)       # Length of record
597         self.fw.write_string('db', 8)   # ASCII ID
598         self.fw.write_int(1600)         # Revision Number
599         self.fw.pad(44)
600         self.fw.write_short(1)          # Unit multiplier.
601         self.fw.write_char(0)           # Units, 0 = meters
602         self.fw.write_char(0)           # texwhite on new faces 0 = false
603         self.fw.write_uint(0x80000000)  # misc flags set to saving vertex normals
604         self.fw.pad(24)
605         self.fw.write_int(0)            # projection type, 0 = flat earth
606         self.fw.pad(30)
607         self.fw.write_short(1)          # double precision
608         self.fw.pad(140)
609         self.fw.write_int(0)            # ellipsoid model, 0 = WSG 1984
610         self.fw.pad(52)
611
612     def write_vert_pal(self):
613         if options.verbose >= 2:
614             print 'Writing vertex palette.'
615         # Write record for vertex palette
616         self.fw.write_short(67)                             # Vertex palette opcode.
617         self.fw.write_short(8)                              # Length of record
618         self.fw.write_int(self.GRR.vertex_count() * 64 + 8) # Length of everything.
619
620         # Write records for individual vertices.
621         for i in range(self.GRR.vertex_count()):
622             desc = self.GRR.request_vertex_desc(i)
623             self.fw.write_short(70)                         # Vertex with color normal and uv opcode.
624             self.fw.write_ushort(64)                        # Length of record
625             self.fw.write_ushort(0)                         # Color name index
626             self.fw.write_short(0x2000)                     # Flags set to no color
627             self.fw.write_double(desc.x)
628             self.fw.write_double(desc.y)
629             self.fw.write_double(desc.z)
630             self.fw.write_float(desc.nx)
631             self.fw.write_float(desc.ny)
632             self.fw.write_float(desc.nz)
633             self.fw.write_float(desc.u)
634             self.fw.write_float(desc.v)
635             self.fw.pad(12)
636
637     def write_tex_pal(self):
638         if options.verbose >= 2:
639             print 'Writing texture palette.'
640         # Write record for texture palette
641         for i in range(self.GRR.texture_count()):
642             self.fw.write_short(64)                                         # Texture palette opcode.
643             self.fw.write_short(216)                                        # Length of record
644             self.fw.write_string(self.GRR.request_texture_filename(i), 200) # Filename
645             self.fw.write_int(i)                                            # Texture index
646             self.fw.write_int(0)                                            # X
647             self.fw.write_int(0)                                            # Y
648
649     def write_mat_pal(self):
650         if options.verbose >= 2:
651             print 'Writing material palette.'
652         for i in range(self.GRR.material_count()):
653             desc = self.GRR.request_material_desc(i)
654             self.fw.write_short(113)                # Material palette opcode.
655             self.fw.write_short(84)                 # Length of record
656             self.fw.write_int(i)                    # Material index
657             self.fw.write_string(desc.name, 12)     # Material name
658             self.fw.write_uint(0x80000000)          # Flags
659             self.fw.write_float(desc.ambient[0])    # Ambient color.
660             self.fw.write_float(desc.ambient[1])    # Ambient color.
661             self.fw.write_float(desc.ambient[2])    # Ambient color.
662             self.fw.write_float(desc.diffuse[0])    # Diffuse color.
663             self.fw.write_float(desc.diffuse[1])    # Diffuse color.
664             self.fw.write_float(desc.diffuse[2])    # Diffuse color.
665             self.fw.write_float(desc.specular[0])   # Specular color.
666             self.fw.write_float(desc.specular[1])   # Specular color.
667             self.fw.write_float(desc.specular[2])   # Specular color.
668             self.fw.write_float(desc.emissive[0])   # Emissive color.
669             self.fw.write_float(desc.emissive[1])   # Emissive color.
670             self.fw.write_float(desc.emissive[2])   # Emissive color.
671             self.fw.write_float(desc.shininess)
672             self.fw.write_float(desc.alpha)
673             self.fw.write_int(0)                    # Reserved
674
675     def write_col_pal(self):
676         if options.verbose >= 2:
677             print 'Writing color palette.'
678         self.fw.write_short(32)                     # Color palette opcode.
679         self.fw.write_short(4228)                   # Length of record
680         self.fw.pad(128)
681         count = self.GRR.color_count()
682         for i in range(count):
683             col = self.GRR.request_max_color(i)
684             self.fw.write_uchar(255)                  # alpha
685             self.fw.write_uchar(col[2])               # b
686             self.fw.write_uchar(col[1])               # g
687             self.fw.write_uchar(col[0])               # r
688         self.fw.pad(max(4096-count*4, 0))
689
690     def write(self):
691         self.write_header()
692         self.write_vert_pal()
693         self.write_tex_pal()
694         self.write_mat_pal()
695         self.write_col_pal()
696
697         # Wrap everything in a group if it has an object child.
698         if self.has_object_child:
699             self.header.fw.write_short(2)          # Group opcode
700             self.header.fw.write_ushort(44)        # Length of record
701             self.header.fw.write_string('g1', 8)   # ASCII ID
702             self.header.fw.pad(32)
703         
704         self.write_push()
705
706         for child in self.children:
707             child.write()
708
709         self.write_pop()
710
711     def __init__(self, scene, fw):
712         self.fw = fw
713         self.scene = scene
714         self.all_objects = scene.getChildren()
715         self.GRR = GlobalResourceRepository()
716
717         Node.__init__(self, None, self, None, self.all_objects)
718
719 def fs_callback(filename):
720     Blender.Window.WaitCursor(True)
721     
722     if Blender.sys.exists(filename):
723         r = Blender.Draw.PupMenu('Overwrite ' + filename + '?%t|Yes|No')
724         if r != 1:
725             if options.verbose >= 1:
726                 print 'Export cancelled.'
727             return
728     
729     fw = FltOut(filename)
730
731     db = Database(Blender.Scene.GetCurrent(), fw)
732     
733     if options.verbose >= 1:
734         print
735         print 'Pass 1: Exporting from Blender.'
736         print
737     
738     db.blender_export()
739     
740     if options.verbose >= 1:
741         print
742         print 'Pass 2: Writing', filename
743         print
744         
745     db.write()
746
747     fw.close_file()
748     if options.verbose >= 1:
749         print
750         print 'Done.'
751         
752     Blender.Window.WaitCursor(False)
753    
754 if options.verbose >= 1:
755     print
756     print 'OpenFlight Exporter'
757     print 'Version:', __version__
758     print 'Author: Greg MacDonald'
759     print __url__[2]
760     print
761     
762 fname = Blender.sys.makename(ext=".flt")
763 Blender.Window.FileSelector(fs_callback, "Export OpenFlight v16.0", fname)