Added openflight import/export- Blight v1.2
[blender.git] / release / scripts / flt_import.py
1 #!BPY
2
3 # flt_import.py is an OpenFlight importer 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: 238
23 Group: 'Import'
24 Tip: 'Import OpenFlight (.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 imports OpenFlight files into Blender. OpenFlight is a
32 registered trademark of MultiGen-Paradigm, Inc.
33
34 Run from "File->Import" 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 All options are toggle switches that let the user choose what is imported. Most
40 are straight-forward, but one option could be a source of confusion. The 
41 "Diffuse Color From Face" option when set pulls the diffuse color from the face
42 colors. Otherwise the diffuse color comes from the material. What may be
43 confusing is that this options only works if the "Diffuse Color" option is set.
44
45 New Features:<br>
46 * Importer is 14 times faster.<br>
47 * External triangle module is no longer required, but make sure the importer
48 has a 3d View screen open while its running or triangulation won't work.<br>
49 * Should be able to import all versions of flight files.
50
51 Features:<br>
52 * Heirarchy retained.<br>
53 * First texture imported.<br>
54 * Colors imported from face or material.<br>
55 * LOD seperated out into different layers.<br>
56 * Asks for location of unfound textures or external references.<br>
57 * Searches Blender's texture directory in the user preferences panel.<br>
58 * Triangles with more than 4 verts are triangulated if the Triangle python
59 module is installed.<br>
60 * Matrix transforms imported.<br>
61 * External references to whole files are imported.
62
63 Things To Be Aware Of:<br>
64 * Each new color and face attribute creates a new material and there are only a maximum of 16
65 materials per object.<br>
66 * For triangulated faces, normals must be recomputed outward manually by typing
67 CTRL+N in edit mode.<br>
68 * You can change options only after an initial import.<br>
69 * External references are imported as geometry and will be exported that way.<br>
70 * A work around for not using the Triangle python module is to simply to 
71 triangulate in Creator before importing. This is only necessary if your
72 model contains 5 or more vertices.<br>
73 * You have to manually blend the material color with the texture color.
74
75 What's Not Handled:<br>
76 * Special texture repeating modes.<br>
77 * Replications and instancing.<br>
78 * Comment and attribute fields.<br>
79 * Light points.<br>
80 * Animations.<br>
81 * External references to a node within a file.<br>
82 * Multitexturing.<br>
83 * Vetex colors.<br>
84 """
85
86 import Blender
87 import os
88
89 from flt_filewalker import FltIn, FileFinder
90
91 def col_to_gray(c):
92     return 0.3*c[0] + 0.59*c[1] + 0.11*c[2]
93
94 class ImporterOptions:
95     def __init__(self):
96         self.defaults = { 'Texture': True,
97                           'Diffuse Color': True,
98                           'Specular Color': False,
99                           'Emissive Intensity': False,
100                           'Alpha': True,
101                           'Ambient Intensity': False,
102                           'Shininess': True,
103                           'Diffuse Color From Face': True}
104
105         d = Blender.Registry.GetKey('flt_import', True)
106
107         if d == None or d.keys() != self.defaults.keys():
108             d = self.defaults
109             Blender.Registry.SetKey('flt_import', d, True)
110
111         self.verbose = 1
112         self.get_texture = d['Texture']
113         self.get_diffuse = d['Diffuse Color']
114         self.get_specular = d['Specular Color']
115         self.get_emissive = d['Emissive Intensity']
116         self.get_alpha = d['Alpha']
117         self.get_ambient = d['Ambient Intensity']
118         self.get_shininess = d['Shininess']
119         self.color_from_face = d['Diffuse Color From Face']
120
121 options = ImporterOptions()
122
123 # Contributed by Campbell Barton:
124 # http://en.wikibooks.org/wiki/Blending_Into_Python:_Cookbook#Expanded_Scanfill_function
125 msg_once = False
126 def scanFillPoints(pointList):
127     global msg_once
128     
129     screen_info = Blender.Window.GetScreenInfo(Blender.Window.Types.VIEW3D)
130     if screen_info == []:
131         if not msg_once:
132             msg = 'Error: A 3D View window must be open while the script is running for triangulation to occur.'
133             Blender.Draw.PupMenu(msg)
134             print msg
135             print
136             msg_once = True
137         return None
138         
139     Blender.Window.EditMode(0)
140  
141     nme = Blender.NMesh.GetRaw('.scanfill')
142     if nme:
143         nme.verts = []
144         nme.edges = []
145         nme. faces = []
146     else:
147         nme = Blender.NMesh.New('.scanfill')
148         
149     for p in pointList:
150         v = Blender.NMesh.Vert( p[0], p[1], p[2] )
151         nme.verts.append(v)
152         v.sel = 1
153
154         if len(nme.verts) >= 2:
155             nme.addEdge(nme.verts[-2], nme.verts[-1])
156
157     nme.addEdge(nme.verts[0], nme.verts[-1])
158
159     nme.update()
160     
161     scn = Blender.Scene.GetCurrent()
162
163     actOb = scn.getActiveObject()
164     if actOb:
165         actSel = actOb.sel
166     else:
167         actSel = 0
168
169     try:
170         ob = Blender.Object.Get('.scanfill')
171     except AttributeError:
172         ob = Blender.Object.New('Mesh')
173         ob.setName('.scanfill')
174         ob.link(nme)
175     
176     scn.link(ob)
177     scn.layers = range(1,20)
178     ob.sel = 1
179     Blender.Window.EditMode(1)
180
181     winid = screen_info[0]['id']
182     Blender.Window.SetKeyQualifiers(Blender.Window.Qual.SHIFT)
183     Blender.Window.QAdd(winid, Blender.Draw.FKEY,1)
184     Blender.Window.QHandle(winid)
185     Blender.Window.SetKeyQualifiers(0)
186
187     Blender.Window.EditMode(0)
188     scn.unlink(ob)
189
190     # Select the old active object.
191     if actOb:
192         actOb.sel = actSel
193
194     # Return the scanfilled faces.
195     return ob.getData()
196
197 class MaterialDesc:
198     # Was going to use int(f*1000.0) instead of round(f,3), but for some reason
199     # round produces better results, as in less dups.
200     def make_key(self):
201         key = []
202         if options.get_texture:
203             if self.tex0:
204                 key.append(self.tex0.getName())
205             else:
206                 key.append(None)
207         
208         if options.get_alpha:
209             key.append(round(self.alpha, 3))
210         else:
211             key.append(None)
212             
213         if options.get_shininess:
214             key.append(round(self.shininess, 3))
215         else:
216             key.append(None)
217         
218         if options.get_emissive:
219             key.append(round(self.emissive, 3))
220         else:
221             key.append(None)
222         
223         if options.get_ambient:
224             key.append(round(self.ambient, 3))
225         else:
226             key.append(None)
227         
228         if options.get_specular:
229             for n in self.specular:
230                 key.append(round(n, 3))
231         else:
232             key.extend([None, None, None])
233         
234         if options.get_diffuse:
235             for n in self.diffuse:
236                 key.append(round(n, 3))
237         else:
238             key.extend([None, None, None])
239         
240 #        key.extend(self.face_props.values())
241         
242         return tuple(key)
243
244     def __init__(self):
245         self.name = 'Material'
246         # Colors, List of 3 floats.
247         self.diffuse = [1.0, 1.0, 1.0]
248         self.specular = [1.0, 1.0, 1.0]
249
250         # Scalars
251         self.ambient = 0.0 # [0.0, 1.0]
252         self.emissive = 0.0 # [0.0, 1.0]
253         self.shininess = 0.5 # Range is [0.0, 2.0]
254         self.alpha = 1.0 # Range is [0.0, 1.0]
255
256         self.tex0 = None
257         
258         # OpenFlight Face attributes
259         self.face_props = dict.fromkeys(['comment', 'ir color', 'priority', 
260                             'draw type', 'texture white', 'template billboard',
261                             'smc', 'fid', 'ir material', 'lod generation control',
262                             'flags', 'light mode'])
263
264 class VertexDesc:
265     def make_key(self):
266         return (round(self.x,3), round(self.y, 3), round(self.z, 3))
267         
268     def __init__(self):
269         self.x = 0.0
270         self.y = 0.0
271         self.z = 0.0
272         self.nx = 0.0
273         self.ny = 1.0
274         self.nz = 0.0
275         self.u = 0.0
276         self.v = 0.0
277         self.r = 1.0
278         self.g = 1.0
279         self.b = 1.0
280         self.a = 1.0        
281
282 class LightPointAppDesc:
283     def make_key(self):
284         d = dict(self.props)
285         del d['id']
286         del d['type']
287         
288         if d['directionality'] != 0: # not omni
289             d['nx'] = 0.0
290             d['ny'] = 0.0
291             d['nz'] = 0.0
292         
293         return tuple(d.values())
294         
295     def __init__(self):
296         self.props = dict()
297         self.props.update({'type': 'LPA'})
298         self.props.update({'id': 'ap'})
299         # Attribs not found in inline lightpoint.
300         self.props.update({'visibility range': 0.0})
301         self.props.update({'fade range ratio': 0.0})
302         self.props.update({'fade in duration': 0.0})
303         self.props.update({'fade out duration': 0.0})
304         self.props.update({'LOD range ratio': 0.0})
305         self.props.update({'LOD scale': 0.0})
306
307 class GlobalResourceRepository:
308     def request_lightpoint_app(self, desc):
309         match = self.light_point_app.get(desc.make_key())
310         
311         if match:
312             return match.getName()
313         else:
314             # Create empty and fill with properties.
315             name = desc.props['type'] + ': ' + desc.props['id']
316             object = Blender.Object.New('Empty', name)
317             
318             scene.link(object)
319     
320             # Attach properties
321             for name, value in desc.props.items():
322                 object.addProperty(name, value)
323             
324             self.light_point_app.update({desc.make_key(): object})
325             
326             return object.getName()
327             
328     def request_vert(self, desc):
329         match = self.vert_dict.get(desc.make_key())
330
331         if match:
332             return match
333         else:
334             vert = Blender.NMesh.Vert(desc.x, desc.y, desc.z)
335
336             vert.no[0] = desc.nx
337             vert.no[1] = desc.ny
338             vert.no[2] = desc.nz
339
340             self.vert_dict.update({desc.make_key(): vert})
341             return vert
342
343     def request_mat(self, mat_desc):
344         match = self.mat_dict.get(mat_desc.make_key())
345         if match: return match
346         
347         mat = Blender.Material.New(mat_desc.name)
348
349         if mat_desc.tex0 != None:
350             mat.setTexture(0, mat_desc.tex0, Blender.Texture.TexCo.UV)
351
352         mat.setAlpha(mat_desc.alpha)
353         mat.setSpec(mat_desc.shininess)
354         mat.setHardness(255)
355         mat.setEmit(mat_desc.emissive)
356         mat.setAmb(mat_desc.ambient)
357         mat.setSpecCol(mat_desc.specular)
358         mat.setRGBCol(mat_desc.diffuse)
359         
360         # Create a text object to store openflight face attribs until
361         # user properties can be set on materials.
362 #        t = Blender.Text.New('FACE: ' + mat.getName())
363 #
364 #        for name, value in mat_desc.face_props.items():
365 #            t.write(name + '\n' + str(value) + '\n\n')    
366                 
367         self.mat_dict.update({mat_desc.make_key(): mat})
368
369         return mat
370         
371     def request_image(self, filename_with_path):
372         if not options.get_texture: return None
373
374         img = self.img_dict.get(filename_with_path)
375         if img: return img
376             
377         img =  Blender.Image.Load(filename_with_path)
378         self.img_dict.update({filename_with_path: img})
379         return img
380         
381     def request_texture(self, image):
382         if not options.get_texture:
383             return None
384
385         tex = self.tex_dict.get(image.filename)
386         if tex: return tex
387         
388         tex = Blender.Texture.New(Blender.sys.basename(image.filename))
389         tex.setImage(image)
390         tex.setType('Image')
391         self.tex_dict.update({image.filename: tex})
392         return tex
393         
394     def __init__(self):
395         # material
396         self.mat_dict = dict()
397         mat_lst = Blender.Material.Get()
398         for mat in mat_lst:
399             mat_desc = MaterialDesc()
400             mapto_lst = mat.getTextures()
401             if mapto_lst[0]:
402                 mat_desc.tex0 = mapto_lst[0].tex
403             else:
404                 mat_desc.tex0 = None
405             mat_desc.alpha = mat.getAlpha()
406             mat_desc.shininess = mat.getSpec()
407             mat_desc.emissive = mat.getEmit()
408             mat_desc.ambient = mat.getAmb()
409             mat_desc.specular = mat.getSpecCol()
410             mat_desc.diffuse = mat.getRGBCol()
411             
412             self.mat_dict.update({mat_desc.make_key(): mat})
413             
414         # texture
415         self.tex_dict = dict()
416         tex_lst = Blender.Texture.Get()
417         
418         for tex in tex_lst:
419             img = tex.getImage()
420             # Only interested in textures with images.
421             if img:
422                 self.tex_dict.update({img.filename: tex})
423                 
424         # image
425         img_lst = Blender.Image.Get()
426         self.img_dict = dict()
427         for img in img_lst:
428             self.img_dict.update({img.filename: img})
429             
430         # vertex
431         self.vert_dict = dict()
432         
433         # light point
434         self.light_point_app = dict()
435         
436 # Globals
437 GRR = GlobalResourceRepository()
438 FF = FileFinder()
439 current_layer = 0x1
440 scene = Blender.Scene.getCurrent()
441
442 # Opcodes that indicate its time to return control to parent.
443 throw_back_opcodes = [2, 73, 4, 11, 96, 14, 91, 98, 63]
444 do_not_report_opcodes = [76, 78, 79, 80, 81, 82, 94, 83, 33, 112, 100, 101, 102, 97, 31, 103, 104, 117, 118, 120, 121, 124, 125]
445
446 opcode_name = { 0: 'db',
447                 1: 'head',
448                 2: 'grp',
449                 4: 'obj',
450                 5: 'face',
451                 10: 'push',
452                 11: 'pop',
453                 14: 'dof',
454                 19: 'push sub',
455                 20: 'pop sub',
456                 21: 'push ext',
457                 22: 'pop ext',
458                 23: 'cont',
459                 31: 'comment',
460                 32: 'color pal',
461                 33: 'long id',
462                 49: 'matrix',
463                 50: 'vector',
464                 52: 'multi-tex',
465                 53: 'uv lst',
466                 55: 'bsp',
467                 60: 'rep',
468                 61: 'inst ref',
469                 62: 'inst def',
470                 63: 'ext ref',
471                 64: 'tex pal',
472                 67: 'vert pal',
473                 68: 'vert w col',
474                 69: 'vert w col & norm',
475                 70: 'vert w col, norm & uv',
476                 71: 'vert w col & uv',
477                 72: 'vert lst',
478                 73: 'lod',
479                 74: 'bndin box',
480                 76: 'rot edge',
481                 78: 'trans',
482                 79: 'scl',
483                 80: 'rot pnt',
484                 81: 'rot and/or scale pnt',
485                 82: 'put',
486                 83: 'eyepoint & trackplane pal',
487                 84: 'mesh',
488                 85: 'local vert pool',
489                 86: 'mesh prim',
490                 87: 'road seg',
491                 88: 'road zone',
492                 89: 'morph vert lst',
493                 90: 'link pal',
494                 91: 'snd',
495                 92: 'rd path',
496                 93: 'snd pal',
497                 94: 'gen matrix',
498                 95: 'txt',
499                 96: 'sw',
500                 97: 'line styl pal',
501                 98: 'clip reg',
502                 100: 'ext',
503                 101: 'light src',
504                 102: 'light src pal',
505                 103: 'reserved',
506                 104: 'reserved',
507                 105: 'bndin sph',
508                 106: 'bndin cyl',
509                 107: 'bndin hull',
510                 108: 'bndin vol cntr',
511                 109: 'bndin vol orient',
512                 110: 'rsrvd',
513                 111: 'light pnt',
514                 112: 'tex map pal',
515                 113: 'mat pal',
516                 114: 'name tab',
517                 115: 'cat',
518                 116: 'cat dat',
519                 117: 'rsrvd',
520                 118: 'rsrvd',
521                 119: 'bounding hist',
522                 120: 'rsrvd',
523                 121: 'rsrvd',
524                 122: 'push attrib',
525                 123: 'pop attrib',
526                 124: 'rsrvd',
527                 125: 'rsrvd',
528                 126: 'curv',
529                 127: 'road const',
530                 128: 'light pnt appear pal',
531                 129: 'light pnt anim pal',
532                 130: 'indexed lp',
533                 131: 'lp sys',
534                 132: 'indx str',
535                 133: 'shdr pal'}
536
537 class Handler:
538     def in_throw_back_lst(self, opcode):
539         return opcode in self.throw_back_lst
540         
541     def handle(self, opcode):
542         return self.handler[opcode]()
543     
544     def handles(self, opcode):
545         return opcode in self.handler.keys()
546     
547     def throws_back_all_unhandled(self):
548         return self.throw_back_unhandled
549         
550     def set_throw_back_lst(self, a):
551         self.throw_back_lst = a
552         
553     def set_throw_back_all_unhandled(self):
554         self.throw_back_unhandled = True
555         
556     def set_only_throw_back_specified(self):
557         self.throw_back_unhandled = False
558         
559     def set_handler(self, d):
560         self.handler = d
561         
562     def __init__(self):
563         # Dictionary of opcodes to handler methods.
564         self.handler = dict()
565         # Send all opcodes not handled to the parent node.
566         self.throw_back_unhandled = False
567         # If throw_back_unhandled is False then only throw back
568         # if the opcodes in throw_back are encountered.
569         self.throw_back_lst = []
570         
571 class Node:
572     def blender_import(self):
573         if self.opcode in opcode_name and options.verbose >= 2:
574             for i in range(self.get_level()):
575                 print ' ',
576             print opcode_name[self.opcode],
577             print '-', self.props['id'],
578             print '-', self.props['comment'],
579
580             print
581
582         for child in self.children:
583             child.blender_import()
584         
585         # Import comment.
586 #        if self.props['comment'] != '':
587 #            name = 'COMMENT: ' + self.props['id']
588 #            t = Blender.Text.New(name)
589 #            t.write(self.props['comment'])
590 #            self.props['comment'] = name
591         
592     # Always ignore extensions and anything in between them.
593     def parse_push_extension(self):
594         self.saved_handler = self.active_handler
595         self.active_handler = self.extension_handler
596         return True
597     
598     def parse_pop_extension(self):
599         self.active_handler = self.saved_handler
600         return True
601     
602     def parse_push(self):
603         self.header.fw.up_level()
604         # Ignore unknown children.
605         self.ignore_unhandled = True
606         # Don't do child records that might overwrite parent info. ex: longid
607         self.active_handler = self.child_handler
608         return True
609         
610     def parse_pop(self):
611         self.header.fw.down_level()
612         
613         if self.header.fw.get_level() == self.level:
614             return False
615         
616         return True
617     
618     def parse(self):
619         while self.header.fw.begin_record():
620             opcode = self.header.fw.get_opcode()
621
622             # Print out info on opcode and tree level.
623             if options.verbose >= 3:
624                 p = ''
625                 for i in range(self.header.fw.get_level()):
626                     p = p + '  '
627                 if opcode in opcode_name:
628                     p = p + opcode_name[opcode]
629                 else:
630                     if options.verbose >= 1:
631                         print 'undocumented opcode', opcode
632                     continue
633                             
634             if self.global_handler.handles(opcode):
635                 if options.verbose >= 3:
636                     print p + ' handled globally'
637                 if self.global_handler.handle(opcode) == False:
638                     break
639                     
640             elif self.active_handler.handles(opcode):
641                 if options.verbose >= 4:
642                     print p + ' handled'
643                 if self.active_handler.handle(opcode) == False:
644                     break
645                     
646             else:
647                 if self.active_handler.throws_back_all_unhandled():
648                     if options.verbose >= 3:
649                         print p + ' handled elsewhere'              
650                     self.header.fw.repeat_record()
651                     break
652
653                 elif self.active_handler.in_throw_back_lst(opcode):
654                     if options.verbose >= 3:
655                         print p + ' handled elsewhere'              
656                     self.header.fw.repeat_record()
657                     break
658
659                 else:
660                     if options.verbose >= 3:
661                         print p + ' ignored'
662                     elif options.verbose >= 1 and not opcode in do_not_report_opcodes and opcode in opcode_name:
663                         print opcode_name[opcode], 'not handled'                        
664                     
665     def get_level(self):
666         return self.level
667         
668     def parse_long_id(self):
669         self.props['id'] = self.header.fw.read_string(self.header.fw.get_length()-4)
670         return True
671
672     def parse_comment(self):
673         self.props['comment'] = self.header.fw.read_string(self.header.fw.get_length()-4)
674         return True
675
676     def __init__(self, parent, header):
677         self.root_handler = Handler()
678         self.child_handler = Handler()
679         self.extension_handler = Handler()
680         self.global_handler = Handler()
681         
682         self.global_handler.set_handler({21: self.parse_push_extension})
683         self.active_handler = self.root_handler
684         
685         # used by parse_*_extension
686         self.extension_handler.set_handler({22: self.parse_pop_extension})
687         self.saved_handler = None
688         
689         self.header = header
690         self.children = []
691
692         self.parent = parent
693
694         if parent:
695             parent.children.append(self)
696
697         self.level = self.header.fw.get_level()
698         self.opcode = self.header.fw.get_opcode()
699
700         self.props = {'id': 'unnamed', 'comment': '', 'type': 'untyped'}
701
702 class VertexPalette(Node):
703     def blender_import(self):
704         for vert_desc in self.vert_desc_lst:
705             vert = GRR.request_vert(vert_desc)
706             self.blender_verts.append(vert)
707
708     def parse_vertex_common(self):
709         # Add this vertex to an offset to index dictionary.
710         self.index_lst.append( (self.offset, self.next_index) )
711         self.next_index += 1
712         # Get ready for next record.
713         self.offset += self.header.fw.get_length()
714
715         v = VertexDesc()
716
717         self.header.fw.read_ahead(2)
718         v.flags = self.header.fw.read_short()
719
720         v.x = self.header.fw.read_double()
721         v.y = self.header.fw.read_double()
722         v.z = self.header.fw.read_double()
723
724         return v
725
726     def parse_vertex_post_common(self, v):
727         if not v.flags & 0x2000: # 0x2000 = no color
728             if v.flags & 0x1000: # 0x1000 = packed color
729                 v.a = self.header.fw.read_uchar()
730                 v.b = self.header.fw.read_uchar()
731                 v.g = self.header.fw.read_uchar()
732                 v.r = self.header.fw.read_uchar()
733             else:
734                 self.header.fw.read_ahead(4)
735
736             color_index = self.header.fw.read_uint()
737             color = self.header.get_color(color_index)
738             v.r = color[0]
739             v.g = color[1]
740             v.b = color[2]
741             v.a = color[3]
742
743         self.vert_desc_lst.append(v)
744         
745         return True
746
747     def parse_vertex_c(self):
748         v = self.parse_vertex_common()
749
750         self.parse_vertex_post_common(v)
751         
752         return True
753
754     def parse_vertex_cn(self):
755         v = self.parse_vertex_common()
756
757         v.nx = self.header.fw.read_float()
758         v.ny = self.header.fw.read_float()
759         v.nz = self.header.fw.read_float()
760
761         self.parse_vertex_post_common(v)
762         
763         return True
764
765     def parse_vertex_cuv(self):
766         v = self.parse_vertex_common()
767
768         v.u = self.header.fw.read_float()
769         v.v = self.header.fw.read_float()
770
771         self.parse_vertex_post_common(v)
772         
773         return True
774
775     def parse_vertex_cnuv(self):
776         v = self.parse_vertex_common()
777
778         v.nx = self.header.fw.read_float()
779         v.ny = self.header.fw.read_float()
780         v.nz = self.header.fw.read_float()
781
782         v.u = self.header.fw.read_float()
783         v.v = self.header.fw.read_float()
784
785         self.parse_vertex_post_common(v)
786         
787         return True
788
789     def parse(self):
790         Node.parse(self)
791
792         self.index = dict(self.index_lst)
793         del self.index_lst
794
795     def __init__(self, parent):
796         Node.__init__(self, parent, parent.header)
797         self.root_handler.set_handler({68: self.parse_vertex_c,
798                                        69: self.parse_vertex_cn,
799                                        70: self.parse_vertex_cnuv,
800                                        71: self.parse_vertex_cuv})
801         self.root_handler.set_throw_back_all_unhandled()
802
803         self.vert_desc_lst = []
804         self.blender_verts = []
805         self.offset = 8
806         # Used to create a map from byte offset to vertex index.
807         self.index = dict()
808         self.index_lst = []
809         self.next_index = 0
810
811 class InterNode(Node):
812     def blender_import(self):
813 #        name = self.props['type'] + ': ' + self.props['id']
814         name = self.props['id']
815         if self.isMesh:
816             self.object = Blender.Object.New('Mesh', name)
817             self.mesh = self.object.getData()
818         else:
819             self.object = Blender.Object.New('Empty', name)
820
821         if self.parent:
822             self.parent.object.makeParent([self.object])
823
824         scene.link(self.object)
825
826         self.object.Layer = current_layer
827
828         Node.blender_import(self)
829
830         if self.isMesh:
831             self.mesh.update(recalc_normals=1)
832             
833         if self.matrix:
834             self.object.setMatrix(self.matrix)
835         
836         # Attach properties
837         #for name, value in self.props.items():
838         #    self.object.addProperty(name, value)
839         
840     def parse_face(self):
841         child = Face(self)
842         child.parse()
843         return True
844
845     def parse_group(self):
846         child = Group(self)
847         child.parse()
848         return True
849
850     def move_to_next_layer(self):
851         global current_layer
852         current_layer = current_layer << 1
853         if current_layer > 0x80000:
854             current_layer = 1
855
856     def parse_lod(self):
857         child = LOD(self)
858         child.parse()
859         return True
860
861     def parse_unhandled(self):
862         child = Unhandled(self)
863         child.parse()
864         return True
865
866     def parse_object(self):
867         child = Object(self)
868         child.parse()
869         return True
870     
871     def parse_xref(self):
872         child = XRef(self)
873         child.parse()
874         return True
875
876     def parse_indexed_light_point(self):
877         child = IndexedLightPoint(self)
878         child.parse()
879         return True
880         
881     def parse_inline_light_point(self):
882         child = InlineLightPoint(self)
883         child.parse()
884         return True
885         
886     def parse_matrix(self):
887         m = []
888         for i in range(4):
889             m.append([])
890             for j in range(4):
891                 f = self.header.fw.read_float()
892                 m[i].append(f)
893         self.matrix = Blender.Mathutils.Matrix(m[0], m[1], m[2], m[3])
894         
895     def __init__(self):
896         self.object = None
897         self.mesh = None
898         self.isMesh = False
899         self.matrix = None
900         
901 class Face(Node):
902     def blender_import_face(self, indices, material_index, image):
903         mesh = self.parent.mesh
904         face = Blender.NMesh.Face()
905         
906         # Add vertices to face.
907         for i in indices:
908             # Add uv info to face.
909             vert_desc = self.header.vert_pal.vert_desc_lst[i]
910             if vert_desc.u != None and vert_desc.v != None:
911                 mesh.hasFaceUV(True)
912                 face.uv.append((vert_desc.u, vert_desc.v))
913             
914             # Add vert to face.
915             vert = self.header.vert_pal.blender_verts[i]
916             face.v.append(vert)
917             
918             # Add vert to mesh.
919             if not vert in mesh.verts:
920                 mesh.verts.append(vert)
921             
922         if image:
923             face.image = image           
924         face.materialIndex = material_index
925         face.smooth = True
926         
927         mesh.addFace(face)
928         
929     def parse_comment(self):
930         self.comment = self.header.fw.read_string(self.header.fw.get_length()-4)
931         return True
932         
933     # returns a tuple (material, image) where material is the blender material and
934     # image is the blender image or None.
935     def create_blender_material(self):
936          # Create face material.
937         mat_desc = MaterialDesc()
938         
939         if self.mat_index != -1:
940             if not self.mat_index in self.header.mat_desc_pal:
941                 if options.verbose >= 1:
942                     print 'Warning: Material index', self.mat_index, 'not in material palette.'
943             else:
944                 mat_pal_desc = self.header.mat_desc_pal[self.mat_index]
945                 mat_desc.alpha = mat_pal_desc.alpha * self.alpha # combine face and mat alphas
946                 mat_desc.ambient = mat_pal_desc.ambient
947                 mat_desc.diffuse = mat_pal_desc.diffuse
948                 mat_desc.specular = mat_pal_desc.specular
949                 mat_desc.emissive = mat_pal_desc.emissive
950                 mat_desc.shininess = mat_pal_desc.shininess
951         else:
952             # if no material get alpha from just face.
953             mat_desc.alpha = self.alpha
954
955         # Color.
956         if options.color_from_face:
957             color = None
958             if not self.props['flags'] & 0x40000000:
959                 if self.props['flags'] & 0x10000000: # packed color
960                     color = self.packed_color
961                 else:
962                     color = self.header.get_color(self.color_index)
963
964             if color:
965                 r = float(color[0])/255.0
966                 g = float(color[1])/255.0
967                 b = float(color[2])/255.0
968                 mat_desc.diffuse = [r, g, b]
969
970         # Texture
971         image = None
972         if self.tex_index != -1 and self.tex_index in self.header.bl_tex_pal:
973             mat_desc.tex0 = self.header.bl_tex_pal[self.tex_index]
974             if mat_desc.tex0:
975                 mat_desc.name = FF.strip_path(self.header.tex_pal[self.tex_index])
976                 image = mat_desc.tex0.image
977
978         # OpenFlight Face Attributes
979         mat_desc.face_props = self.props
980         
981         # Get material.
982         mat = GRR.request_mat(mat_desc)
983         
984         # Add material to mesh.
985         mesh = self.parent.mesh
986         try:
987             mesh.addMaterial(mat)
988         except AttributeError:
989             pass
990         except RuntimeError:
991             if options.verbose >= 1:
992                 print 'Warning: Too many materials per mesh object. Only a maximum of 16 ' + \
993                       'allowed. Using 16th material instead.'
994             mat = mesh.materials[-1]
995
996         # Return where it is in the mesh for faces.
997         material_index = mesh.materials.index(mat)
998         
999         return (material_index, image)
1000         
1001     def triangulate(self):
1002         point_lst = []
1003         for i in self.indices:
1004             vert_desc = self.header.vert_pal.vert_desc_lst[i]
1005             point_lst.append(self.header.vert_pal.blender_verts[i].co)
1006         
1007         mesh = scanFillPoints(point_lst)
1008         if not mesh:
1009             return []
1010             
1011         # mesh.verts and vert_lst should be in the same order unless blender rearranged them during triangulation, unlikely.
1012         tri_lst = []
1013         for f in mesh.faces:
1014             tri = []
1015             for vert in f.v:
1016                 i = mesh.verts.index(vert)
1017                 tri.append(self.indices[i])
1018             tri_lst.append(tri)
1019             
1020         return tri_lst
1021         
1022     def blender_import(self):
1023         vert_count = len(self.indices)
1024         if vert_count == 0:
1025             if options.verbose >= 2:
1026                 print 'Warning: Ignoring face with no vertices.'
1027             return
1028
1029         material = self.create_blender_material()
1030         
1031         if vert_count > 4:
1032             tri_lst = self.triangulate()
1033         else:
1034             tri_lst = [self.indices]
1035             
1036         for tri in tri_lst:
1037             self.blender_import_face(tri, material[0], material[1])
1038         
1039         # Store comment info in parent.
1040         if self.comment != '':
1041             if self.parent.props['comment'] != '':
1042                 self.parent.props['comment'] += '\n\nFrom Face:\n' + self.comment
1043             else:
1044                 self.parent.props['comment'] = self.comment
1045         
1046     def parse_vertex_list(self):
1047         length = self.header.fw.get_length()
1048         fw = self.header.fw
1049         vert_pal = self.header.vert_pal
1050
1051         count = (length-4)/4
1052
1053         for i in range(count):
1054             byte_offset = fw.read_int()
1055             if byte_offset in vert_pal.index:
1056                 index = vert_pal.index[byte_offset]
1057                 self.indices.append(index)
1058             elif options.verbose >= 1:
1059                 print 'Warning: Unable to map byte offset %s' + \
1060                       ' to vertex index.' % byte_offset
1061       
1062         return True
1063
1064     def __init__(self, parent):
1065         Node.__init__(self, parent, parent.header)
1066         self.root_handler.set_handler({31: self.parse_comment,
1067                                        10: self.parse_push})
1068         self.root_handler.set_throw_back_lst(throw_back_opcodes)
1069         
1070         self.child_handler.set_handler({72: self.parse_vertex_list,
1071                                         10: self.parse_push,
1072                                         11: self.parse_pop})
1073         
1074         if parent:
1075             parent.isMesh = True
1076
1077         self.indices = []
1078         
1079         self.comment = ''
1080         self.props = dict.fromkeys(['ir color', 'priority', 
1081                                     'draw type', 'texture white', 'template billboard',
1082                                     'smc', 'fid', 'ir material', 'lod generation control',
1083                                     'flags', 'light mode'])
1084         
1085         self.header.fw.read_ahead(8) # face id
1086         # Load face.
1087         self.props['ir color'] = self.header.fw.read_int()
1088         self.props['priority'] = self.header.fw.read_short()
1089         self.props['draw type'] = self.header.fw.read_char()
1090         self.props['texture white'] = self.header.fw.read_char()
1091         self.header.fw.read_ahead(4) # color name indices
1092         self.header.fw.read_ahead(1) # reserved
1093         self.props['template billboard'] = self.header.fw.read_uchar()
1094         self.detail_tex_index = self.header.fw.read_short()
1095         self.tex_index = self.header.fw.read_short()
1096         self.mat_index = self.header.fw.read_short()
1097         self.props['smc'] = self.header.fw.read_short()
1098         self.props['fid'] = self.header.fw.read_short()
1099         self.props['ir material'] = self.header.fw.read_int()
1100         self.alpha = 1.0 - float(self.header.fw.read_ushort()) / 65535.0
1101         self.props['lod generation control'] = self.header.fw.read_uchar()
1102         self.header.fw.read_ahead(1) # line style index
1103         self.props['flags'] = self.header.fw.read_int()
1104         self.props['light mode'] = self.header.fw.read_uchar()
1105         self.header.fw.read_ahead(7)
1106         a = self.header.fw.read_uchar()
1107         b = self.header.fw.read_uchar()
1108         g = self.header.fw.read_uchar()
1109         r = self.header.fw.read_uchar()
1110         self.packed_color = [r, g, b, a]
1111         a = self.header.fw.read_uchar()
1112         b = self.header.fw.read_uchar()
1113         g = self.header.fw.read_uchar()
1114         r = self.header.fw.read_uchar()
1115         self.alt_packed_color = [r, g, b, a]
1116         self.tex_map_index = self.header.fw.read_short()
1117         self.header.fw.read_ahead(2)
1118         self.color_index = self.header.fw.read_uint()
1119         self.alt_color_index = self.header.fw.read_uint()
1120         #self.header.fw.read_ahead(2)
1121         #self.shader_index = self.header.fw.read_short()
1122
1123 class Object(InterNode):
1124     def __init__(self, parent):
1125         Node.__init__(self, parent, parent.header)
1126         InterNode.__init__(self)
1127         
1128         self.root_handler.set_handler({33: self.parse_long_id,
1129                                        31: self.parse_comment,
1130                                        10: self.parse_push,
1131                                        49: self.parse_matrix})
1132         self.root_handler.set_throw_back_lst(throw_back_opcodes)
1133        
1134         self.child_handler.set_handler({5: self.parse_face,
1135                                         #130: self.parse_indexed_light_point,
1136                                         #111: self.parse_inline_light_point,
1137                                         10: self.parse_push,
1138                                         11: self.parse_pop})
1139
1140         self.props['type'] = 'Object'
1141         self.props['id'] = self.header.fw.read_string(8)
1142
1143 class Group(InterNode):
1144     def __init__(self, parent):
1145         Node.__init__(self, parent, parent.header)
1146         InterNode.__init__(self)
1147         
1148         self.root_handler.set_handler({33: self.parse_long_id,
1149                                        31: self.parse_comment,
1150                                        10: self.parse_push,
1151                                        49: self.parse_matrix})
1152         self.root_handler.set_throw_back_lst(throw_back_opcodes)
1153         
1154         self.child_handler.set_handler({5: self.parse_face,
1155                                         #130: self.parse_indexed_light_point,
1156                                         #111: self.parse_inline_light_point,
1157                                         2: self.parse_group,
1158                                         73: self.parse_lod,
1159                                         4: self.parse_object,
1160                                         10: self.parse_push,
1161                                         11: self.parse_pop,
1162                                         96: self.parse_unhandled,
1163                                         14: self.parse_unhandled,
1164                                         91: self.parse_unhandled,
1165                                         98: self.parse_unhandled,
1166                                         63: self.parse_xref})
1167         self.props = dict.fromkeys(['type', 'id', 'comment', 'priority', 'flags', 'special1',
1168                                     'special2', 'significance', 'layer code', 'loop count',
1169                                     'loop duration', 'last frame duration'])
1170         
1171         self.props['type'] = 'Group'
1172         self.props['comment'] = ''
1173         self.props['id'] = self.header.fw.read_string(8)
1174         self.props['priority'] = self.header.fw.read_short()
1175         self.header.fw.read_ahead(2)
1176         self.props['flags'] = self.header.fw.read_int()
1177         self.props['special1'] = self.header.fw.read_short()
1178         self.props['special2'] = self.header.fw.read_short()
1179         self.props['significance'] = self.header.fw.read_short()
1180         self.props['layer code'] = self.header.fw.read_char()
1181         self.header.fw.read_ahead(5)
1182         self.props['loop count'] = self.header.fw.read_int()
1183         self.props['loop duration'] = self.header.fw.read_float()
1184         self.props['last frame duration'] = self.header.fw.read_float()               
1185
1186 class XRef(InterNode):
1187     def parse(self):
1188         if self.xref:
1189             self.xref.parse()
1190         Node.parse(self)
1191
1192     def __init__(self, parent):
1193         Node.__init__(self, parent, parent.header)
1194         InterNode.__init__(self)
1195
1196         self.root_handler.set_handler({49: self.parse_matrix})
1197         self.root_handler.set_throw_back_lst(throw_back_opcodes)
1198         
1199         xref_filename = self.header.fw.read_string(200)
1200         filename = FF.find(xref_filename)
1201
1202         self.props['type'] = 'XRef'
1203         
1204         if filename != None:
1205             self.xref = Database(filename, self)
1206             self.props['id'] = 'X: ' + Blender.sys.splitext(Blender.sys.basename(filename))[0]
1207         else:
1208             self.xref = None
1209             self.props['id'] = 'X: broken'
1210
1211 class LOD(InterNode):
1212     def blender_import(self):
1213         self.move_to_next_layer()
1214         InterNode.blender_import(self)
1215
1216     def __init__(self, parent):
1217         Node.__init__(self, parent, parent.header)
1218         InterNode.__init__(self)
1219         
1220         self.root_handler.set_handler({33: self.parse_long_id,
1221                                        31: self.parse_comment,
1222                                        10: self.parse_push,
1223                                        49: self.parse_matrix})
1224         self.root_handler.set_throw_back_lst(throw_back_opcodes)
1225         
1226         self.child_handler.set_handler({2: self.parse_group,
1227                                         73: self.parse_lod,
1228                                         4: self.parse_object,
1229                                         10: self.parse_push,
1230                                         11: self.parse_pop,
1231                                         96: self.parse_unhandled, # switch
1232                                         14: self.parse_unhandled, # DOF
1233                                         91: self.parse_unhandled, # sound
1234                                         98: self.parse_unhandled, # clip
1235                                         63: self.parse_xref})
1236
1237         self.props['type'] = 'LOD'
1238         self.props['id'] = self.header.fw.read_string(8)
1239
1240 class InlineLightPoint(InterNode):
1241     # return dictionary: lp_app name => index list
1242     def group_points(self, props):
1243         
1244         name_to_indices = {}
1245         
1246         for i in self.indices:
1247             vert_desc = self.header.vert_pal.vert_desc_lst[i]
1248             app_desc = LightPointAppDesc()
1249             app_desc.props.update(props)
1250             # add vertex normal and color
1251             app_desc.props.update({'nx': vert_desc.nx})
1252             app_desc.props.update({'ny': vert_desc.ny})
1253             app_desc.props.update({'nz': vert_desc.nz})
1254             
1255             app_desc.props.update({'r': vert_desc.r})
1256             app_desc.props.update({'g': vert_desc.g})
1257             app_desc.props.update({'b': vert_desc.b})
1258             app_desc.props.update({'a': vert_desc.a})
1259             
1260             app_name = GRR.request_lightpoint_app(app_desc)
1261
1262             if name_to_indices.get(app_name):
1263                 name_to_indices[app_name].append(i)
1264             else:
1265                 name_to_indices.update({app_name: [i]})
1266             
1267         return name_to_indices
1268         
1269     def blender_import(self):
1270         name = self.props['type'] + ': ' + self.props['id']
1271
1272         name_to_indices = self.group_points(self.app_props)
1273
1274         for app_name, indices in name_to_indices.items():
1275             self.object = Blender.Object.New('Mesh', name)
1276             self.mesh = self.object.getData()
1277
1278             if self.parent:
1279                 self.parent.object.makeParent([self.object])
1280                 
1281             for i in indices:
1282                 vert = self.header.vert_pal.blender_verts[i]
1283                 self.mesh.verts.append(vert)
1284             
1285             scene.link(self.object)
1286     
1287             self.object.Layer = current_layer
1288             
1289             if self.matrix:
1290                 self.object.setMatrix(self.matrix)
1291                 
1292             # Import comment.
1293             if self.props['comment'] != '':
1294                 name = 'COMMENT: ' + self.props['id']
1295                 t = Blender.Text.New(name)
1296                 t.write(self.props['comment'])
1297                 self.props['comment'] = name
1298                 
1299             # Attach properties.
1300             self.props.update({'appearance': app_name})
1301             for name, value in self.props.items():
1302                 self.object.addProperty(name, value)
1303             
1304             self.mesh.update()
1305             
1306     def parse_vertex_list(self):
1307         length = self.header.fw.get_length()
1308         fw = self.header.fw
1309         vert_pal = self.header.vert_pal
1310
1311         count = (length-4)/4
1312
1313         for i in range(count):
1314             byte_offset = fw.read_int()
1315             if byte_offset in vert_pal.index:
1316                 index = vert_pal.index[byte_offset]
1317                 self.indices.append(index)
1318             elif options.verbose >= 1:
1319                 print 'Warning: Unable to map byte offset %s' + \
1320                       ' to vertex index.' % byte_offset
1321       
1322         return True
1323         
1324     def __init__(self, parent):
1325         Node.__init__(self, parent, parent.header)
1326         InterNode.__init__(self)
1327         self.root_handler.set_handler({33: self.parse_long_id,
1328                                        31: self.parse_comment,
1329                                        10: self.parse_push,
1330                                        49: self.parse_matrix})
1331         self.root_handler.set_throw_back_lst(throw_back_opcodes)
1332         
1333         self.child_handler.set_handler({72: self.parse_vertex_list,
1334                                         10: self.parse_push,
1335                                         11: self.parse_pop})
1336
1337         self.indices = []
1338                 
1339         self.props = dict.fromkeys(['id', 'type', 'comment', 'draw order', 'appearance'])
1340         self.app_props = dict()
1341         
1342         self.props['comment'] = ''
1343         self.props['type'] = 'Light Point'
1344         self.props['id'] = self.header.fw.read_string(8)
1345         
1346         self.app_props.update({'smc': self.header.fw.read_short()})
1347         self.app_props.update({'fid': self.header.fw.read_short()})
1348         self.app_props.update({'back color: a': self.header.fw.read_uchar()})
1349         self.app_props.update({'back color: b': self.header.fw.read_uchar()})
1350         self.app_props.update({'back color: g': self.header.fw.read_uchar()})
1351         self.app_props.update({'back color: r': self.header.fw.read_uchar()})
1352         self.app_props.update({'display mode': self.header.fw.read_int()})
1353         self.app_props.update({'intensity': self.header.fw.read_float()})
1354         self.app_props.update({'back intensity': self.header.fw.read_float()})
1355         self.app_props.update({'minimum defocus': self.header.fw.read_float()})
1356         self.app_props.update({'maximum defocus': self.header.fw.read_float()})
1357         self.app_props.update({'fading mode': self.header.fw.read_int()})
1358         self.app_props.update({'fog punch mode': self.header.fw.read_int()})
1359         self.app_props.update({'directional mode': self.header.fw.read_int()})
1360         self.app_props.update({'range mode': self.header.fw.read_int()})
1361         self.app_props.update({'min pixel size': self.header.fw.read_float()})
1362         self.app_props.update({'max pixel size': self.header.fw.read_float()})
1363         self.app_props.update({'actual size': self.header.fw.read_float()})
1364         self.app_props.update({'trans falloff pixel size': self.header.fw.read_float()})
1365         self.app_props.update({'trans falloff exponent': self.header.fw.read_float()})
1366         self.app_props.update({'trans falloff scalar': self.header.fw.read_float()})
1367         self.app_props.update({'trans falloff clamp': self.header.fw.read_float()})
1368         self.app_props.update({'fog scalar': self.header.fw.read_float()})
1369         self.app_props.update({'fog intensity': self.header.fw.read_float()})
1370         self.app_props.update({'size threshold': self.header.fw.read_float()})
1371         self.app_props.update({'directionality': self.header.fw.read_int()})
1372         self.app_props.update({'horizontal lobe angle': self.header.fw.read_float()})
1373         self.app_props.update({'vertical lobe angle': self.header.fw.read_float()})
1374         self.app_props.update({'lobe roll angle': self.header.fw.read_float()})
1375         self.app_props.update({'dir falloff exponent': self.header.fw.read_float()})
1376         self.app_props.update({'dir ambient intensity': self.header.fw.read_float()})
1377         self.header.fw.read_ahead(12) # Animation settings.        
1378         self.app_props.update({'significance': self.header.fw.read_float()})
1379         self.props['draw order'] = self.header.fw.read_int()
1380         self.app_props.update({'flags': self.header.fw.read_int()})
1381         #self.fw.read_ahead(12) # More animation settings.                
1382         
1383 class IndexedLightPoint(InterNode):
1384     # return dictionary: lp_app name => index list
1385     def group_points(self, props):
1386         
1387         name_to_indices = {}
1388         
1389         for i in self.indices:
1390             vert_desc = self.header.vert_pal.vert_desc_lst[i]
1391             app_desc = LightPointAppDesc()
1392             app_desc.props.update(props)
1393             # add vertex normal and color
1394             app_desc.props.update({'nx': vert_desc.nx})
1395             app_desc.props.update({'ny': vert_desc.ny})
1396             app_desc.props.update({'nz': vert_desc.nz})
1397             
1398             app_desc.props.update({'r': vert_desc.r})
1399             app_desc.props.update({'g': vert_desc.g})
1400             app_desc.props.update({'b': vert_desc.b})
1401             app_desc.props.update({'a': vert_desc.a})
1402             
1403             app_name = GRR.request_lightpoint_app(app_desc)
1404
1405             if name_to_indices.get(app_name):
1406                 name_to_indices[app_name].append(i)
1407             else:
1408                 name_to_indices.update({app_name: [i]})
1409             
1410         return name_to_indices
1411         
1412     def blender_import(self):
1413         name = self.props['type'] + ': ' + self.props['id']
1414         
1415         name_to_indices = self.group_points(self.header.lightpoint_appearance_pal[self.index])
1416         
1417         for app_name, indices in name_to_indices.items():        
1418             self.object = Blender.Object.New('Mesh', name)
1419             self.mesh = self.object.getData()
1420             
1421             if self.parent:
1422                 self.parent.object.makeParent([self.object])
1423                 
1424             for i in indices:
1425                 vert = self.header.vert_pal.blender_verts[i]
1426                 self.mesh.verts.append(vert)
1427             
1428             scene.link(self.object)
1429     
1430             self.object.Layer = current_layer
1431             
1432             if self.matrix:
1433                 self.object.setMatrix(self.matrix)
1434                 
1435             # Import comment.
1436             if self.props['comment'] != '':
1437                 name = 'COMMENT: ' + self.props['id']
1438                 t = Blender.Text.New(name)
1439                 t.write(self.props['comment'])
1440                 self.props['comment'] = name
1441                 
1442             # Attach properties.
1443             self.props.update({'appearance': app_name})
1444             for name, value in self.props.items():
1445                 self.object.addProperty(name, value)
1446             
1447             self.mesh.update()
1448             
1449     def parse_vertex_list(self):
1450         length = self.header.fw.get_length()
1451         fw = self.header.fw
1452         vert_pal = self.header.vert_pal
1453
1454         count = (length-4)/4
1455
1456         for i in range(count):
1457             byte_offset = fw.read_int()
1458             if byte_offset in vert_pal.index:
1459                 index = vert_pal.index[byte_offset]
1460                 self.indices.append(index)
1461             elif options.verbose >= 1:
1462                 print 'Warning: Unable to map byte offset %s' + \
1463                       ' to vertex index.' % byte_offset
1464       
1465         return True
1466         
1467     def __init__(self, parent):
1468         Node.__init__(self, parent, parent.header)
1469         InterNode.__init__(self)
1470         self.root_handler.set_handler({33: self.parse_long_id,
1471                                        31: self.parse_comment,
1472                                        10: self.parse_push,
1473                                        49: self.parse_matrix})
1474         self.root_handler.set_throw_back_lst(throw_back_opcodes)
1475         
1476         self.child_handler.set_handler({72: self.parse_vertex_list,
1477                                         10: self.parse_push,
1478                                         11: self.parse_pop})
1479
1480         self.indices = []
1481         
1482         self.props = dict.fromkeys(['id', 'type', 'comment', 'draw order', 'appearance'])
1483         self.props['comment'] = ''
1484         self.props['type'] = 'Light Point'
1485         self.props['id'] = self.header.fw.read_string(8)
1486         self.index = self.header.fw.read_int()
1487         self.header.fw.read_ahead(4) # animation index
1488         self.props['draw order'] = self.header.fw.read_int()        
1489
1490 class Unhandled(InterNode):
1491     def __init__(self, parent):
1492         Node.__init__(self, parent, parent.header)
1493         InterNode.__init__(self)
1494         
1495         self.root_handler.set_handler({33: self.parse_long_id,
1496                                        31: self.parse_comment,
1497                                        10: self.parse_push,
1498                                        49: self.parse_matrix})
1499         self.root_handler.set_throw_back_lst(throw_back_opcodes)
1500         
1501         self.child_handler.set_handler({2: self.parse_group,
1502                                         73: self.parse_lod,
1503                                         4: self.parse_object,
1504                                         10: self.parse_push,
1505                                         11: self.parse_pop,
1506                                         96: self.parse_unhandled, # switch
1507                                         14: self.parse_unhandled, # DOF
1508                                         91: self.parse_unhandled, # sound
1509                                         98: self.parse_unhandled, # clip
1510                                         63: self.parse_xref})
1511
1512         self.props['id'] = self.header.fw.read_string(8)
1513
1514 class Database(InterNode):
1515     def blender_import(self):
1516         self.tex_pal = dict(self.tex_pal_lst)
1517         del self.tex_pal_lst
1518
1519         # Setup Textures
1520         bl_tex_pal_lst = []
1521         for i in self.tex_pal.keys():
1522             path_filename = FF.find(self.tex_pal[i])
1523             if path_filename != None:
1524                 img = GRR.request_image(path_filename)
1525                 if img:
1526                     tex = GRR.request_texture(img)
1527                     tex.setName(FF.strip_path(self.tex_pal[i]))
1528                     bl_tex_pal_lst.append( (i, tex) )
1529                 else:
1530                     bl_tex_pal_lst.append( (i, None) )
1531             elif options.verbose >= 1:
1532                 print 'Warning: Unable to find', self.tex_pal[i]
1533
1534         self.bl_tex_pal = dict(bl_tex_pal_lst)
1535
1536         # Setup Materials
1537         self.mat_desc_pal = dict(self.mat_desc_pal_lst)
1538
1539         InterNode.blender_import(self)
1540
1541     def parse_appearance_palette(self):
1542         props = dict()
1543         self.fw.read_ahead(4) # reserved
1544         props.update({'id': self.fw.read_string(256)})
1545         index = self.fw.read_int()
1546         props.update({'smc': self.fw.read_short()})
1547         props.update({'fid': self.fw.read_short()})
1548         props.update({'back color: a': self.fw.read_uchar()})
1549         props.update({'back color: b': self.fw.read_uchar()})
1550         props.update({'back color: g': self.fw.read_uchar()})
1551         props.update({'back color: r': self.fw.read_uchar()})
1552         props.update({'display mode': self.fw.read_int()})
1553         props.update({'intensity': self.fw.read_float()})
1554         props.update({'back intensity': self.fw.read_float()})
1555         props.update({'minimum defocus': self.fw.read_float()})
1556         props.update({'maximum defocus': self.fw.read_float()})
1557         props.update({'fading mode': self.fw.read_int()})
1558         props.update({'fog punch mode': self.fw.read_int()})
1559         props.update({'directional mode': self.fw.read_int()})
1560         props.update({'range mode': self.fw.read_int()})
1561         props.update({'min pixel size': self.fw.read_float()})
1562         props.update({'max pixel size': self.fw.read_float()})
1563         props.update({'actual size': self.fw.read_float()})
1564         props.update({'trans falloff pixel size': self.fw.read_float()})
1565         props.update({'trans falloff exponent': self.fw.read_float()})
1566         props.update({'trans falloff scalar': self.fw.read_float()})
1567         props.update({'trans falloff clamp': self.fw.read_float()})
1568         props.update({'fog scalar': self.fw.read_float()})
1569         props.update({'fog intensity': self.fw.read_float()})
1570         props.update({'size threshold': self.fw.read_float()})
1571         props.update({'directionality': self.fw.read_int()})
1572         props.update({'horizontal lobe angle': self.fw.read_float()})
1573         props.update({'vertical lobe angle': self.fw.read_float()})
1574         props.update({'lobe roll angle': self.fw.read_float()})
1575         props.update({'dir falloff exponent': self.fw.read_float()})
1576         props.update({'dir ambient intensity': self.fw.read_float()})
1577         props.update({'significance': self.fw.read_float()})
1578         props.update({'flags': self.fw.read_int()})
1579         props.update({'visibility range': self.fw.read_float()})
1580         props.update({'fade range ratio': self.fw.read_float()})
1581         props.update({'fade in duration': self.fw.read_float()})
1582         props.update({'fade out duration': self.fw.read_float()})
1583         props.update({'LOD range ratio': self.fw.read_float()})
1584         props.update({'LOD scale': self.fw.read_float()})
1585         
1586         self.lightpoint_appearance_pal.update({index: props})
1587         
1588     def parse_header(self):
1589         self.props['type'] = 'Header'
1590         self.props['comment'] = ''
1591         self.props['id'] = self.fw.read_string(8)
1592         self.props['version'] = self.fw.read_int()
1593         self.fw.read_ahead(46)
1594         self.props['units'] = self.fw.read_char()
1595         self.props['set white'] = bool(self.fw.read_char())
1596         self.props['flags'] = self.fw.read_int()
1597         self.fw.read_ahead(24)
1598         self.props['projection type'] = self.fw.read_int()
1599         self.fw.read_ahead(36)
1600         self.props['sw x'] = self.fw.read_double()
1601         self.props['sw y'] = self.fw.read_double()
1602         self.props['dx'] = self.fw.read_double()
1603         self.props['dy'] = self.fw.read_double()
1604         self.fw.read_ahead(24)
1605         self.props['sw lat'] = self.fw.read_double()
1606         self.props['sw lon'] = self.fw.read_double()
1607         self.props['ne lat'] = self.fw.read_double()
1608         self.props['ne lon'] = self.fw.read_double()
1609         self.props['origin lat'] = self.fw.read_double()
1610         self.props['origin lon'] = self.fw.read_double()
1611         self.props['lambert lat1'] = self.fw.read_double()
1612         self.props['lambert lat2'] = self.fw.read_double()
1613         self.fw.read_ahead(16)
1614         self.props['ellipsoid model'] = self.fw.read_int()
1615         self.fw.read_ahead(4)
1616         self.props['utm zone'] = self.fw.read_short()
1617         self.fw.read_ahead(6)
1618         self.props['dz'] = self.fw.read_double()
1619         self.props['radius'] = self.fw.read_double()
1620         self.fw.read_ahead(8)
1621         self.props['major axis'] = self.fw.read_double()
1622         self.props['minor axis'] = self.fw.read_double()
1623         
1624         if options.verbose >= 1:
1625             print 'OpenFlight Version:', float(self.props['version']) / 100.0
1626             print
1627             
1628         return True
1629
1630     def parse_mat_palette(self):
1631         mat_desc = MaterialDesc()
1632         index = self.fw.read_int()
1633
1634         name = self.fw.read_string(12)
1635         if len(mat_desc.name) > 0:
1636             mat_desc.name = name
1637
1638         flag = self.fw.read_int()
1639         # skip material if not used
1640         if not flag & 0x80000000:
1641             return True
1642
1643         ambient_col = [self.fw.read_float(), self.fw.read_float(), self.fw.read_float()]
1644         mat_desc.diffuse = [self.fw.read_float(), self.fw.read_float(), self.fw.read_float()]
1645         mat_desc.specular = [self.fw.read_float(), self.fw.read_float(), self.fw.read_float()]
1646         emissive_col = [self.fw.read_float(), self.fw.read_float(), self.fw.read_float()]
1647
1648         mat_desc.shininess = self.fw.read_float() / 64.0 # [0.0, 128.0] => [0.0, 2.0]
1649         mat_desc.alpha = self.fw.read_float()
1650
1651         # Convert ambient and emissive colors into intensitities.
1652         mat_desc.ambient = col_to_gray(ambient_col)
1653         mat_desc.emissive = col_to_gray(emissive_col)
1654
1655         self.mat_desc_pal_lst.append( (index, mat_desc) )
1656         
1657         return True
1658     
1659     def get_color(self, color_index):
1660         index = color_index / 128
1661         intensity = float(color_index - 128.0 * index) / 127.0
1662
1663         if index >= 0 and index <= 1023:
1664             brightest = self.col_pal[index]
1665             r = int(brightest[0] * intensity)
1666             g = int(brightest[1] * intensity)
1667             b = int(brightest[2] * intensity)
1668             #a = int(brightest[3] * intensity)
1669             a = int(brightest[3])
1670             
1671             color = [r, g, b, a]
1672             
1673         return color
1674     
1675     def parse_color_palette(self):
1676         self.header.fw.read_ahead(128)
1677         for i in range(1024):
1678             a = self.header.fw.read_uchar()
1679             b = self.header.fw.read_uchar()
1680             g = self.header.fw.read_uchar()
1681             r = self.header.fw.read_uchar()
1682             self.col_pal.append((r, g, b, a))
1683         return True
1684         
1685     def parse_vertex_palette(self):
1686         self.vert_pal = VertexPalette(self)
1687         self.vert_pal.parse()
1688         return True
1689         
1690     def parse_texture_palette(self):
1691         name = self.fw.read_string(200)
1692         index = self.fw.read_int()
1693         self.tex_pal_lst.append( (index, name) )
1694         return True
1695         
1696     def __init__(self, filename, parent=None):
1697         if options.verbose >= 1:
1698             print 'Parsing:', filename
1699             print
1700         
1701         self.fw = FltIn(filename)
1702         Node.__init__(self, parent, self)
1703         InterNode.__init__(self)
1704         
1705         self.root_handler.set_handler({1: self.parse_header,
1706                                        67: self.parse_vertex_palette,
1707                                        33: self.parse_long_id,
1708                                        31: self.parse_comment,
1709                                        64: self.parse_texture_palette,
1710                                        32: self.parse_color_palette,
1711                                        113: self.parse_mat_palette,
1712                                        128: self.parse_appearance_palette,
1713                                        10: self.parse_push})
1714         if parent:
1715             self.root_handler.set_throw_back_lst(throw_back_opcodes)
1716
1717         self.child_handler.set_handler({#130: self.parse_indexed_light_point,
1718                                         #111: self.parse_inline_light_point,
1719                                         2: self.parse_group,
1720                                         73: self.parse_lod,
1721                                         4: self.parse_object,
1722                                         10: self.parse_push,
1723                                         11: self.parse_pop,
1724                                         96: self.parse_unhandled,
1725                                         14: self.parse_unhandled,
1726                                         91: self.parse_unhandled,
1727                                         98: self.parse_unhandled,
1728                                         63: self.parse_xref})
1729         
1730         self.vert_pal = None
1731         self.lightpoint_appearance_pal = dict()
1732         self.tex_pal = dict()
1733         self.tex_pal_lst = []
1734         self.bl_tex_pal = dict()
1735         self.col_pal = []
1736         self.mat_desc_pal_lst = []
1737         self.mat_desc_pal = dict()
1738         self.props = dict.fromkeys(['id', 'type', 'comment', 'version', 'units', 'set white',
1739             'flags', 'projection type', 'sw x', 'sw y', 'dx', 'dy', 'dz', 'sw lat',
1740             'sw lon', 'ne lat', 'ne lon', 'origin lat', 'origin lon', 'lambert lat1',
1741             'lambert lat2', 'ellipsoid model', 'utm zone', 'radius', 'major axis', 'minor axis'])
1742
1743 def select_file(filename):
1744     Blender.Window.WaitCursor(True)
1745
1746     if filename[-4:] != '.flt':
1747         msg = 'Error: Not a flight file.'
1748         Blender.Draw.PupMenu(msg)
1749         print msg
1750         print
1751         return
1752
1753     if not Blender.sys.exists(filename):
1754         msg = 'Error: File ' + filename + ' does not exist.'
1755         Blender.Draw.PupMenu(msg)
1756         return
1757
1758     FF.add_file_to_search_path(filename)
1759     
1760     if options.verbose >= 1:
1761         print 'Pass 1: Loading.'
1762         print
1763
1764     load_time = Blender.sys.time()    
1765     db = Database(filename)
1766     db.parse()
1767     load_time = Blender.sys.time() - load_time
1768
1769     if options.verbose >= 1:
1770         print
1771         print 'Pass 2: Importing to Blender.'
1772         print
1773
1774     import_time = Blender.sys.time()
1775     db.blender_import()
1776     import_time = Blender.sys.time() - import_time
1777     
1778     Blender.Window.ViewLayer(range(1,21))
1779     Blender.Window.RedrawAll()
1780         
1781     if options.verbose >= 1:
1782         print 'Done.'
1783         print
1784         print 'Time to parse file: %.3f seconds' % load_time
1785         print 'Time to import to blender: %.3f seconds' % import_time
1786         print 'Total time: %.3f seconds' % (load_time + import_time)
1787     
1788     Blender.Window.WaitCursor(False)
1789
1790
1791 if options.verbose >= 1:
1792     print
1793     print 'OpenFlight Importer'
1794     print 'Version:', __version__
1795     print 'Author: Greg MacDonald'
1796     print __url__[2]
1797     print
1798
1799 Blender.Window.EditMode(0)
1800
1801 winid = Blender.Window.GetScreenInfo(Blender.Window.Types.VIEW3D)[0]['id']
1802
1803 Blender.Window.FileSelector(select_file, "Import OpenFlight", "*.flt")