24dcfd350543b5743ef621a59f571f052404f94e
[blender-addons-contrib.git] / io_export_md3.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 bl_addon_info = {
20     'name': 'Quake Model 3 (.md3)',
21     'author': 'Xembie',
22     'version': (0, 7),
23     'blender': (2, 5, 3),
24     'api': 31667,
25     'location': 'File > Export',
26     'description': 'Save a Quake Model 3 File)',
27     'warning': '', # used for warning icon and text in addons panel
28     'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/'\
29         'Scripts/',
30     'tracker_url': 'http://projects.blender.org/tracker/index.php?'\
31         'func=detail&aid=23160',
32     'category': 'Import-Export'}
33
34
35 import bpy,struct,math,os
36
37 MAX_QPATH = 64
38
39 MD3_IDENT = "IDP3"
40 MD3_VERSION = 15
41 MD3_MAX_TAGS = 16
42 MD3_MAX_SURFACES = 32
43 MD3_MAX_FRAMES = 1024
44 MD3_MAX_SHADERS = 256
45 MD3_MAX_VERTICES = 4096
46 MD3_MAX_TRIANGLES = 8192
47 MD3_XYZ_SCALE = 64.0
48
49 class md3Vert:
50     xyz = []
51     normal = 0
52     binaryFormat = "<3hH"
53     
54     def __init__(self):
55         self.xyz = [0.0, 0.0, 0.0]
56         self.normal = 0
57         
58     def GetSize(self):
59         return struct.calcsize(self.binaryFormat)
60     
61     # copied from PhaethonH <phaethon@linux.ucla.edu> md3.py
62     def Decode(self, latlng):
63         lat = (latlng >> 8) & 0xFF;
64         lng = (latlng) & 0xFF;
65         lat *= math.pi / 128;
66         lng *= math.pi / 128;
67         x = math.cos(lat) * math.sin(lng)
68         y = math.sin(lat) * math.sin(lng)
69         z =                 math.cos(lng)
70         retval = [ x, y, z ]
71         return retval
72     
73     # copied from PhaethonH <phaethon@linux.ucla.edu> md3.py
74     def Encode(self, normal):
75         x = normal[0]
76         y = normal[1]
77         z = normal[2]
78         # normalize
79         l = math.sqrt((x*x) + (y*y) + (z*z))
80         if l == 0:
81             return 0
82         x = x/l
83         y = y/l
84         z = z/l
85         
86         if (x == 0.0) & (y == 0.0) :
87             if z > 0.0:
88                 return 0
89             else:
90                 return (128 << 8)
91         
92         lng = math.acos(z) * 255 / (2 * math.pi)
93         lat = math.atan2(y, x) * 255 / (2 * math.pi)
94         retval = ((int(lat) & 0xFF) << 8) | (int(lng) & 0xFF)
95         return retval
96         
97     def Save(self, file):
98         tmpData = [0] * 4
99         tmpData[0] = int(self.xyz[0] * MD3_XYZ_SCALE)
100         tmpData[1] = int(self.xyz[1] * MD3_XYZ_SCALE)
101         tmpData[2] = int(self.xyz[2] * MD3_XYZ_SCALE)
102         tmpData[3] = self.normal
103         data = struct.pack(self.binaryFormat, tmpData[0], tmpData[1], tmpData[2], tmpData[3])
104         file.write(data)
105         
106 class md3TexCoord:
107     u = 0.0
108     v = 0.0
109
110     binaryFormat = "<2f"
111
112     def __init__(self):
113         self.u = 0.0
114         self.v = 0.0
115         
116     def GetSize(self):
117         return struct.calcsize(self.binaryFormat)
118
119     def Save(self, file):
120         tmpData = [0] * 2
121         tmpData[0] = self.u
122         tmpData[1] = 1.0 - self.v
123         data = struct.pack(self.binaryFormat, tmpData[0], tmpData[1])
124         file.write(data)
125
126 class md3Triangle:
127     indexes = []
128
129     binaryFormat = "<3i"
130
131     def __init__(self):
132         self.indexes = [ 0, 0, 0 ]
133         
134     def GetSize(self):
135         return struct.calcsize(self.binaryFormat)
136
137     def Save(self, file):
138         tmpData = [0] * 3
139         tmpData[0] = self.indexes[0]
140         tmpData[1] = self.indexes[2] # reverse
141         tmpData[2] = self.indexes[1] # reverse
142         data = struct.pack(self.binaryFormat,tmpData[0], tmpData[1], tmpData[2])
143         file.write(data)
144
145 class md3Shader:
146     name = ""
147     index = 0
148     
149     binaryFormat = "<%dsi" % MAX_QPATH
150
151     def __init__(self):
152         self.name = ""
153         self.index = 0
154         
155     def GetSize(self):
156         return struct.calcsize(self.binaryFormat)
157
158     def Save(self, file):
159         tmpData = [0] * 2
160         tmpData[0] = self.name
161         tmpData[1] = self.index
162         data = struct.pack(self.binaryFormat, tmpData[0], tmpData[1])
163         file.write(data)
164
165 class md3Surface:
166     ident = ""
167     name = ""
168     flags = 0
169     numFrames = 0
170     numShaders = 0
171     numVerts = 0
172     numTriangles = 0
173     ofsTriangles = 0
174     ofsShaders = 0
175     ofsUV = 0
176     ofsVerts = 0
177     ofsEnd = 0
178     shaders = []
179     triangles = []
180     uv = []
181     verts = []
182     
183     binaryFormat = "<4s%ds10i" % MAX_QPATH  # 1 int, name, then 10 ints
184     
185     def __init__(self):
186         self.ident = ""
187         self.name = ""
188         self.flags = 0
189         self.numFrames = 0
190         self.numShaders = 0
191         self.numVerts = 0
192         self.numTriangles = 0
193         self.ofsTriangles = 0
194         self.ofsShaders = 0
195         self.ofsUV = 0
196         self.ofsVerts = 0
197         self.ofsEnd
198         self.shaders = []
199         self.triangles = []
200         self.uv = []
201         self.verts = []
202         
203     def GetSize(self):
204         sz = struct.calcsize(self.binaryFormat)
205         self.ofsTriangles = sz
206         for t in self.triangles:
207             sz += t.GetSize()
208         self.ofsShaders = sz
209         for s in self.shaders:
210             sz += s.GetSize()
211         self.ofsUV = sz
212         for u in self.uv:
213             sz += u.GetSize()
214         self.ofsVerts = sz
215         for v in self.verts:
216             sz += v.GetSize()
217         self.ofsEnd = sz
218         return self.ofsEnd
219     
220     def Save(self, file):
221         self.GetSize()
222         tmpData = [0] * 12
223         tmpData[0] = self.ident
224         tmpData[1] = self.name
225         tmpData[2] = self.flags
226         tmpData[3] = self.numFrames
227         tmpData[4] = self.numShaders
228         tmpData[5] = self.numVerts
229         tmpData[6] = self.numTriangles
230         tmpData[7] = self.ofsTriangles
231         tmpData[8] = self.ofsShaders
232         tmpData[9] = self.ofsUV
233         tmpData[10] = self.ofsVerts
234         tmpData[11] = self.ofsEnd
235         data = struct.pack(self.binaryFormat, tmpData[0],tmpData[1],tmpData[2],tmpData[3],tmpData[4],tmpData[5],tmpData[6],tmpData[7],tmpData[8],tmpData[9],tmpData[10],tmpData[11])
236         file.write(data)
237
238         # write the tri data
239         for t in self.triangles:
240             t.Save(file)
241
242         # save the shader coordinates
243         for s in self.shaders:
244             s.Save(file)
245
246         # save the uv info
247         for u in self.uv:
248             u.Save(file)
249
250         # save the verts
251         for v in self.verts:
252             v.Save(file)
253
254 class md3Tag:
255     name = ""
256     origin = []
257     axis = []
258     
259     binaryFormat="<%ds3f9f" % MAX_QPATH
260     
261     def __init__(self):
262         self.name = ""
263         self.origin = [0, 0, 0]
264         self.axis = [0, 0, 0, 0, 0, 0, 0, 0, 0]
265         
266     def GetSize(self):
267         return struct.calcsize(self.binaryFormat)
268         
269     def Save(self, file):
270         tmpData = [0] * 13
271         tmpData[0] = self.name
272         tmpData[1] = float(self.origin[0])
273         tmpData[2] = float(self.origin[1])
274         tmpData[3] = float(self.origin[2])
275         tmpData[4] = float(self.axis[0])
276         tmpData[5] = float(self.axis[1])
277         tmpData[6] = float(self.axis[2])
278         tmpData[7] = float(self.axis[3])
279         tmpData[8] = float(self.axis[4])
280         tmpData[9] = float(self.axis[5])
281         tmpData[10] = float(self.axis[6])
282         tmpData[11] = float(self.axis[7])
283         tmpData[12] = float(self.axis[8])
284         data = struct.pack(self.binaryFormat, tmpData[0],tmpData[1],tmpData[2],tmpData[3],tmpData[4],tmpData[5],tmpData[6], tmpData[7], tmpData[8], tmpData[9], tmpData[10], tmpData[11], tmpData[12])
285         file.write(data)
286     
287 class md3Frame:
288     mins = 0
289     maxs = 0
290     localOrigin = 0
291     radius = 0.0
292     name = ""
293     
294     binaryFormat="<3f3f3ff16s"
295     
296     def __init__(self):
297         self.mins = [0, 0, 0]
298         self.maxs = [0, 0, 0]
299         self.localOrigin = [0, 0, 0]
300         self.radius = 0.0
301         self.name = ""
302         
303     def GetSize(self):
304         return struct.calcsize(self.binaryFormat)
305
306     def Save(self, file):
307         tmpData = [0] * 11
308         tmpData[0] = self.mins[0]
309         tmpData[1] = self.mins[1]
310         tmpData[2] = self.mins[2]
311         tmpData[3] = self.maxs[0]
312         tmpData[4] = self.maxs[1]
313         tmpData[5] = self.maxs[2]
314         tmpData[6] = self.localOrigin[0]
315         tmpData[7] = self.localOrigin[1]
316         tmpData[8] = self.localOrigin[2]
317         tmpData[9] = self.radius
318         tmpData[10] = self.name
319         data = struct.pack(self.binaryFormat, tmpData[0],tmpData[1],tmpData[2],tmpData[3],tmpData[4],tmpData[5],tmpData[6],tmpData[7], tmpData[8], tmpData[9], tmpData[10])
320         file.write(data)
321
322 class md3Object:
323     # header structure
324     ident = ""            # this is used to identify the file (must be IDP3)
325     version = 0            # the version number of the file (Must be 15)
326     name = ""
327     flags = 0
328     numFrames = 0
329     numTags = 0
330     numSurfaces = 0
331     numSkins = 0
332     ofsFrames = 0
333     ofsTags = 0
334     ofsSurfaces = 0
335     ofsEnd = 0
336     frames = []
337     tags = []
338     surfaces = []
339
340     binaryFormat="<4si%ds9i" % MAX_QPATH  # little-endian (<), 17 integers (17i)
341
342     def __init__(self):
343         self.ident = 0
344         self.version = 0
345         self.name = ""
346         self.flags = 0
347         self.numFrames = 0
348         self.numTags = 0
349         self.numSurfaces = 0
350         self.numSkins = 0
351         self.ofsFrames = 0
352         self.ofsTags = 0
353         self.ofsSurfaces = 0
354         self.ofsEnd = 0
355         self.frames = []
356         self.tags = []
357         self.surfaces = []
358
359     def GetSize(self):
360         self.ofsFrames = struct.calcsize(self.binaryFormat)
361         self.ofsTags = self.ofsFrames
362         for f in self.frames:
363             self.ofsTags += f.GetSize()
364         self.ofsSurfaces += self.ofsTags
365         for t in self.tags:
366             self.ofsSurfaces += t.GetSize()
367         self.ofsEnd = self.ofsSurfaces
368         for s in self.surfaces:
369             self.ofsEnd += s.GetSize()
370         return self.ofsEnd
371         
372     def Save(self, file):
373         self.GetSize()
374         tmpData = [0] * 12
375         tmpData[0] = self.ident
376         tmpData[1] = self.version
377         tmpData[2] = self.name
378         tmpData[3] = self.flags
379         tmpData[4] = self.numFrames
380         tmpData[5] = self.numTags
381         tmpData[6] = self.numSurfaces
382         tmpData[7] = self.numSkins
383         tmpData[8] = self.ofsFrames
384         tmpData[9] = self.ofsTags
385         tmpData[10] = self.ofsSurfaces
386         tmpData[11] = self.ofsEnd
387
388         data = struct.pack(self.binaryFormat, tmpData[0],tmpData[1],tmpData[2],tmpData[3],tmpData[4],tmpData[5],tmpData[6],tmpData[7], tmpData[8], tmpData[9], tmpData[10], tmpData[11])
389         file.write(data)
390
391         for f in self.frames:
392             f.Save(file)
393             
394         for t in self.tags:
395             t.Save(file)
396             
397         for s in self.surfaces:
398             s.Save(file)
399
400
401 def message(log,msg):
402   if log:
403     log.write(msg + "\n")
404   else:
405     print(msg)
406
407 class md3Settings:
408   def __init__(self,
409                savepath,
410                name,
411                logpath,
412                overwrite=True,
413                dumpall=False,
414                ignoreuvs=False,
415                scale=1.0,
416                offsetx=0.0,
417                offsety=0.0,
418                offsetz=0.0):
419     self.savepath = savepath
420     self.name = name
421     self.logpath = logpath
422     self.overwrite = overwrite
423     self.dumpall = dumpall
424     self.ignoreuvs = ignoreuvs
425     self.scale = scale
426     self.offsetx = offsetx
427     self.offsety = offsety
428     self.offsetz = offsetz
429
430 def print_md3(log,md3,dumpall):
431   message(log,"Header Information")
432   message(log,"Ident: " + str(md3.ident))
433   message(log,"Version: " + str(md3.version))
434   message(log,"Name: " + md3.name)
435   message(log,"Flags: " + str(md3.flags))
436   message(log,"Number of Frames: " + str(md3.numFrames))
437   message(log,"Number of Tags: " + str(md3.numTags))
438   message(log,"Number of Surfaces: " + str(md3.numSurfaces))
439   message(log,"Number of Skins: " + str(md3.numSkins))
440   message(log,"Offset Frames: " + str(md3.ofsFrames))
441   message(log,"Offset Tags: " + str(md3.ofsTags))
442   message(log,"Offset Surfaces: " + str(md3.ofsSurfaces))
443   message(log,"Offset end: " + str(md3.ofsEnd))
444   if dumpall:
445     message(log,"Frames:")
446     for f in md3.frames:
447       message(log," Mins: " + str(f.mins[0]) + " " + str(f.mins[1]) + " " + str(f.mins[2]))
448       message(log," Maxs: " + str(f.maxs[0]) + " " + str(f.maxs[1]) + " " + str(f.maxs[2]))
449       message(log," Origin(local): " + str(f.localOrigin[0]) + " " + str(f.localOrigin[1]) + " " + str(f.localOrigin[2]))
450       message(log," Radius: " + str(f.radius))
451       message(log," Name: " + f.name)
452
453     message(log,"Tags:")
454     for t in md3.tags:
455       message(log," Name: " + t.name)
456       message(log," Origin: " + str(t.origin[0]) + " " + str(t.origin[1]) + " " + str(t.origin[2]))
457       message(log," Axis[0]: " + str(t.axis[0]) + " " + str(t.axis[1]) + " " + str(t.axis[2]))
458       message(log," Axis[1]: " + str(t.axis[3]) + " " + str(t.axis[4]) + " " + str(t.axis[5]))
459       message(log," Axis[2]: " + str(t.axis[6]) + " " + str(t.axis[7]) + " " + str(t.axis[8]))
460
461     message(log,"Surfaces:")
462     for s in md3.surfaces:
463       message(log," Ident: " + s.ident)
464       message(log," Name: " + s.name)
465       message(log," Flags: " + str(s.flags))
466       message(log," # of Frames: " + str(s.numFrames))
467       message(log," # of Shaders: " + str(s.numShaders))
468       message(log," # of Verts: " + str(s.numVerts))
469       message(log," # of Triangles: " + str(s.numTriangles))
470       message(log," Offset Triangles: " + str(s.ofsTriangles))
471       message(log," Offset UVs: " + str(s.ofsUV))
472       message(log," Offset Verts: " + str(s.ofsVerts))
473       message(log," Offset End: " + str(s.ofsEnd))
474       message(log," Shaders:")
475       for shader in s.shaders:
476         message(log,"  Name: " + shader.name)
477         message(log,"  Index: " + str(shader.index))
478       message(log," Triangles:")
479       for tri in s.triangles:
480         message(log,"  Indexes: " + str(tri.indexes[0]) + " " + str(tri.indexes[1]) + " " + str(tri.indexes[2]))
481       message(log," UVs:")
482       for uv in s.uv:
483         message(log,"  U: " + str(uv.u))
484         message(log,"  V: " + str(uv.v)) 
485       message(log," Verts:")
486       for vert in s.verts:
487         message(log,"  XYZ: " + str(vert.xyz[0]) + " " + str(vert.xyz[1]) + " " + str(vert.xyz[2]))
488         message(log,"  Normal: " + str(vert.normal))
489
490   shader_count = 0
491   vert_count = 0
492   tri_count = 0
493   for surface in md3.surfaces:
494     shader_count += surface.numShaders
495     tri_count += surface.numTriangles
496     vert_count += surface.numVerts
497
498   if md3.numTags >= MD3_MAX_TAGS:
499     message(log,"!Warning: Tag limit reached! " + str(md3.numTags))
500   if md3.numSurfaces >= MD3_MAX_SURFACES:
501     message(log,"!Warning: Surface limit reached! " + str(md3.numSurfaces))
502   if md3.numFrames >= MD3_MAX_FRAMES:
503     message(log,"!Warning: Frame limit reached! " + str(md3.numFrames))
504   if shader_count >= MD3_MAX_SHADERS:
505     message(log,"!Warning: Shader limit reached! " + str(shader_count))
506   if vert_count >= MD3_MAX_VERTICES:
507     message(log,"!Warning: Vertex limit reached! " + str(vert_count))
508   if tri_count >= MD3_MAX_TRIANGLES:
509     message(log,"!Warning: Triangle limit reached! " + str(tri_count))
510
511 def save_md3(settings):
512   if settings.logpath:
513     if settings.overwrite:
514       log = open(settings.logpath,"w")
515     else:
516       log = open(settings.logpath,"a")
517   else:
518     log = 0
519   message(log,"##########Exporting MD3##########")
520   bpy.ops.object.mode_set(mode='OBJECT')
521   md3 = md3Object()
522   md3.ident = MD3_IDENT
523   md3.version = MD3_VERSION
524   md3.name = settings.name
525   md3.numFrames = (bpy.context.scene.frame_end + 1) - bpy.context.scene.frame_start
526
527   for obj in bpy.context.selected_objects:
528     if obj.type == 'MESH':
529       nsurface = md3Surface()
530       nsurface.name = obj.name
531       nsurface.ident = MD3_IDENT
532  
533       vertlist = []
534
535       for f,face in enumerate(obj.data.faces):
536         ntri = md3Triangle()
537         if len(face.verts) != 3:
538           message(log,"Found a nontriangle face in object " + obj.name)
539           continue
540
541         for v,vert_index in enumerate(face.verts):
542           uv_u = round(obj.data.active_uv_texture.data[f].uv[v][0],5)
543           uv_v = round(obj.data.active_uv_texture.data[f].uv[v][1],5)
544
545           match = 0
546           match_index = 0
547           for i,vi in enumerate(vertlist):
548             if vi == vert_index:
549               if settings.ignoreuvs:
550                 match = 1#there is a uv match for all
551                 match_index = i
552               else:
553                 if nsurface.uv[i].u == uv_u and nsurface.uv[i].v == uv_v:
554                   match = 1
555                   match_index = i
556
557           if match == 0:
558             vertlist.append(vert_index)
559             ntri.indexes[v] = nsurface.numVerts
560             ntex = md3TexCoord()
561             ntex.u = uv_u
562             ntex.v = uv_v
563             nsurface.uv.append(ntex)
564             nsurface.numVerts += 1
565           else:
566             ntri.indexes[v] = match_index
567         nsurface.triangles.append(ntri)
568         nsurface.numTriangles += 1
569
570       if obj.data.active_uv_texture:
571         nshader = md3Shader()
572         nshader.name = obj.data.active_uv_texture.name
573         nshader.index = nsurface.numShaders
574         nsurface.shaders.append(nshader)
575         nsurface.numShaders += 1
576       if nsurface.numShaders < 1: #we should add a blank as a placeholder
577         nshader = md3Shader()
578         nshader.name = "NULL"
579         nsurface.shaders.append(nshader)
580         nsurface.numShaders += 1
581
582       for frame in range(bpy.context.scene.frame_start,bpy.context.scene.frame_end + 1):
583         bpy.context.scene.set_frame(frame)
584         fobj = obj.create_mesh(bpy.context.scene,True,'PREVIEW')
585         fobj.calc_normals()
586         nframe = md3Frame()
587         nframe.name = str(frame)
588         for vi in vertlist:
589             vert = fobj.verts[vi]
590             nvert = md3Vert()
591             nvert.xyz = vert.co * obj.matrix_world
592             nvert.xyz[0] = (round(nvert.xyz[0] + obj.matrix_world[3][0],5) * settings.scale) + settings.offsetx
593             nvert.xyz[1] = (round(nvert.xyz[1] + obj.matrix_world[3][1],5) * settings.scale) + settings.offsety
594             nvert.xyz[2] = (round(nvert.xyz[2] + obj.matrix_world[3][2],5) * settings.scale) + settings.offsetz
595             nvert.normal = nvert.Encode(vert.normal)
596             for i in range(0,3):
597               nframe.mins[i] = min(nframe.mins[i],nvert.xyz[i])
598               nframe.maxs[i] = max(nframe.maxs[i],nvert.xyz[i])
599             minlength = math.sqrt(math.pow(nframe.mins[0],2) + math.pow(nframe.mins[1],2) + math.pow(nframe.mins[2],2))
600             maxlength = math.sqrt(math.pow(nframe.maxs[0],2) + math.pow(nframe.maxs[1],2) + math.pow(nframe.maxs[2],2))
601             nframe.radius = round(max(minlength,maxlength),5)
602             nsurface.verts.append(nvert) 
603         md3.frames.append(nframe)
604         nsurface.numFrames += 1
605         bpy.data.meshes.remove(fobj)
606       md3.surfaces.append(nsurface)
607       md3.numSurfaces += 1
608
609     elif obj.type == 'EMPTY':
610       md3.numTags += 1
611       for frame in range(bpy.context.scene.frame_start,bpy.context.scene.frame_end + 1):
612         bpy.context.scene.set_frame(frame)
613         ntag = md3Tag()
614         ntag.origin[0] = (round(obj.matrix_world[3][0] * settings.scale,5)) + settings.offsetx
615         ntag.origin[1] = (round(obj.matrix_world[3][1] * settings.scale,5)) + settings.offsety
616         ntag.origin[2] = (round(obj.matrix_world[3][2] * settings.scale,5)) + settings.offsetz
617         ntag.axis[0] = obj.matrix_world[0][0]
618         ntag.axis[1] = obj.matrix_world[0][1]
619         ntag.axis[2] = obj.matrix_world[0][2]
620         ntag.axis[3] = obj.matrix_world[1][0]
621         ntag.axis[4] = obj.matrix_world[1][1]
622         ntag.axis[5] = obj.matrix_world[1][2]
623         ntag.axis[6] = obj.matrix_world[2][0]
624         ntag.axis[7] = obj.matrix_world[2][1]
625         ntag.axis[8] = obj.matrix_world[2][2]
626         md3.tags.append(ntag)
627   
628   if md3.numSurfaces < 1:
629     message(log,"Select a mesh to export!")
630     if log:
631       log.close()
632     return
633
634   file = open(settings.savepath, "wb")
635   md3.Save(file)
636   print_md3(log,md3,settings.dumpall)
637   file.close()
638
639   message(log,"MD3: " + settings.name + " saved to " + settings.savepath)
640   if log:
641     print("Logged to",settings.logpath)
642     log.close()
643
644 from bpy.props import *
645 class ExportMD3(bpy.types.Operator):
646   '''Export to Quake Model 3 (.md3)'''
647   bl_idname = "export.md3"
648   bl_label = 'Export MD3'
649
650   filepath = StringProperty(subtype = 'FILE_PATH',name="File Path", description="Filepath for exporting", maxlen= 1024, default= "")
651   md3name = StringProperty(name="MD3 Name", description="MD3 header name / skin path (64 bytes)",maxlen=64,default="")
652   md3log = StringProperty(name="MD3 Log", description="MD3 log file path",maxlen=1024,default="export_md3.log")
653   md3overwritelog = BoolProperty(name="Overwrite log", description="Overwrite log (off == append)", default=True)
654   md3dumpall = BoolProperty(name="Dump all", description="Dump all data for md3 to log",default=False)
655   md3ignoreuvs = BoolProperty(name="Ignore UVs", description="Ignores uv influence on mesh generation. Use if uv map not made.",default=False)
656   md3scale = FloatProperty(name="Scale", description="Scale all objects from world origin (0,0,0)",default=1.0,precision=5)
657   md3offsetx = FloatProperty(name="Offset X", description="Transition scene along x axis",default=0.0,precision=5)
658   md3offsety = FloatProperty(name="Offset Y", description="Transition scene along y axis",default=0.0,precision=5)
659   md3offsetz = FloatProperty(name="Offset Z", description="Transition scene along z axis",default=0.0,precision=5)
660
661   def execute(self, context):
662    settings = md3Settings(savepath = self.properties.filepath,
663                           name = self.properties.md3name,
664                           logpath = self.properties.md3log,
665                           overwrite = self.properties.md3overwritelog,
666                           dumpall = self.properties.md3dumpall,
667                           ignoreuvs = self.properties.md3ignoreuvs,
668                           scale = self.properties.md3scale,
669                           offsetx = self.properties.md3offsetx,
670                           offsety = self.properties.md3offsety,
671                           offsetz = self.properties.md3offsetz)
672    save_md3(settings)
673    return {'FINISHED'}
674
675   def invoke(self, context, event):
676     wm = context.window_manager
677     wm.fileselect_add(self)
678     return {'RUNNING_MODAL'}
679
680   @classmethod
681   def poll(cls, context):
682     return context.active_object is not None
683
684 def menu_func(self, context):
685   newpath = os.path.splitext(bpy.context.blend_data.filepath)[0] + ".md3"
686   self.layout.operator(ExportMD3.bl_idname, text="Quake Model 3 (.md3)").filepath = newpath 
687
688 def register():
689   bpy.types.INFO_MT_file_export.append(menu_func)
690
691 def unregister():
692   bpy.types.INFO_MT_file_export.remove(menu_func)
693
694 if __name__ == "__main__":
695   register()