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