Small MD2 Viewer

I know, people probably use this model format less and less. I fixed an old export script for Blender to it since JPCT has that format available for loading and I like it. So, I wrote a simple and basic MD2 viewer that’s not all that complex. Oddly it’s only two classes. Anyhow, I wrote it really quick to test my models when I design them, make sure they look good in the graphics engine, if anyone is interested I can post it here.

I know how difficult it is to find a decent tool for something as simple as this.

Hi

I still use it.

It did something similar for the MD3 file format and David Henry did the same for MD2. Please can you share your findings about the old export script for Blender? I had to modify a few things to prevent Blender from skipping the very first frame. I would be happy if we could collaborate so that this file format works anew as good as before the version 2.50 of Blender.

Oh, there was a new script for 2.63 but the API for Blender changed so the UV coordinates were not stored. All I did to the new script was correct the location it retrieved those from.

Here’s that script, with my corrections made but still giving the original author full credit:


# Dao Nguyen and Bernd Meyer, metaio GmbH

# ***** BEGIN GPL LICENSE BLOCK *****

# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

# ***** END GPL LICENCE BLOCK *****

"""
Name: 'MD2 -new- (.md2)...'
Blender: 245
Group: 'Export'
Tooltip: 'Export to Quake2 file format (.md2) -new-'
"""

__version__ = '0.1'
__author__  = [ 'Damien Thebault', 'Erwan Mathieu' ]
__url__     = [ 'https://metabolsim.no-ip.org/trac/metabolsim' ]
__email__   = [ 'Damien Thebault <damien!thebault#laposte!net>', 'Erwan Mathieu <cyberwan#laposte!net>' ]

__bpydoc__ = """\
This script exports a mesh object to the Quake2 "MD2" file format.

If you want to choose the name of the animations, you have to go to the timeline, choose the starting frame, choose 'Frame', 'Add Marker' (M) and then set a name with 'Frame', 'Name Marker' (Ctrl M).

The frames are named <frameName><N> with :

 - <N> the frame number

 - <frameName> the name choosen at the last marker (or 'frame' if the last marker has no name or if there is no last marker)

Skins are set using image textures in materials. If a skin path is too long (>63 chars), the basename will be used.

Usually, importers expect that the name don't have any digit (to be able to detect where the frame name starts and where the frame number starts.

Thanks to Bob Holcomb for MD2_NORMALS taken from his exporter.

Thanks to David Henry for the documentation about the MD2 file format.
"""

#New Header Data
bl_info = {
    "name": "Quake2 MD2 format",
    "description": "Export to Quake2 file format (.md2)",
    "author": "Dao Nguyen and Bernd Meyer, metaio GmbH (based on the initial exporter of Damien Thebault and Erwan Mathieu)",
    "version": (1, 0),
    "blender": (2, 5, 8),
    "api": 31236,
    "location": "File > Export > md2",
    "warning": '', # used for warning icon and text in addons panel
    "wiki_url": "http://metaio.com", 
    "tracker_url": "http://metaio.com",
    "support": 'OFFICIAL',
    "category": "Import-Export"}


import bpy
from bpy.props import *

from bpy_extras.io_utils import ExportHelper
from datetime import datetime

import math
from math import pi
import mathutils

import struct
import random
import os


MD2_NORMALS=((-0.525731, 0.000000, 0.850651),
             (-0.442863, 0.238856, 0.864188),
             (-0.295242, 0.000000, 0.955423),
             (-0.309017, 0.500000, 0.809017),
             (-0.162460, 0.262866, 0.951056),
             ( 0.000000, 0.000000, 1.000000),
             ( 0.000000, 0.850651, 0.525731),
             (-0.147621, 0.716567, 0.681718),
             ( 0.147621, 0.716567, 0.681718),
             ( 0.000000, 0.525731, 0.850651),
             ( 0.309017, 0.500000, 0.809017),
             ( 0.525731, 0.000000, 0.850651),
             ( 0.295242, 0.000000, 0.955423),
             ( 0.442863, 0.238856, 0.864188),
             ( 0.162460, 0.262866, 0.951056),
             (-0.681718, 0.147621, 0.716567),
             (-0.809017, 0.309017, 0.500000),
             (-0.587785, 0.425325, 0.688191),
             (-0.850651, 0.525731, 0.000000),
             (-0.864188, 0.442863, 0.238856),
             (-0.716567, 0.681718, 0.147621),
             (-0.688191, 0.587785, 0.425325),
             (-0.500000, 0.809017, 0.309017),
             (-0.238856, 0.864188, 0.442863),
             (-0.425325, 0.688191, 0.587785),
             (-0.716567, 0.681718,-0.147621),
             (-0.500000, 0.809017,-0.309017),
             (-0.525731, 0.850651, 0.000000),
             ( 0.000000, 0.850651,-0.525731),
             (-0.238856, 0.864188,-0.442863),
             ( 0.000000, 0.955423,-0.295242),
             (-0.262866, 0.951056,-0.162460),
             ( 0.000000, 1.000000, 0.000000),
             ( 0.000000, 0.955423, 0.295242),
             (-0.262866, 0.951056, 0.162460),
             ( 0.238856, 0.864188, 0.442863),
             ( 0.262866, 0.951056, 0.162460),
             ( 0.500000, 0.809017, 0.309017),
             ( 0.238856, 0.864188,-0.442863),
             ( 0.262866, 0.951056,-0.162460),
             ( 0.500000, 0.809017,-0.309017),
             ( 0.850651, 0.525731, 0.000000),
             ( 0.716567, 0.681718, 0.147621),
             ( 0.716567, 0.681718,-0.147621),
             ( 0.525731, 0.850651, 0.000000),
             ( 0.425325, 0.688191, 0.587785),
             ( 0.864188, 0.442863, 0.238856),
             ( 0.688191, 0.587785, 0.425325),
             ( 0.809017, 0.309017, 0.500000),
             ( 0.681718, 0.147621, 0.716567),
             ( 0.587785, 0.425325, 0.688191),
             ( 0.955423, 0.295242, 0.000000),
             ( 1.000000, 0.000000, 0.000000),
             ( 0.951056, 0.162460, 0.262866),
             ( 0.850651,-0.525731, 0.000000),
             ( 0.955423,-0.295242, 0.000000),
             ( 0.864188,-0.442863, 0.238856),
             ( 0.951056,-0.162460, 0.262866),
             ( 0.809017,-0.309017, 0.500000),
             ( 0.681718,-0.147621, 0.716567),
             ( 0.850651, 0.000000, 0.525731),
             ( 0.864188, 0.442863,-0.238856),
             ( 0.809017, 0.309017,-0.500000),
             ( 0.951056, 0.162460,-0.262866),
             ( 0.525731, 0.000000,-0.850651),
             ( 0.681718, 0.147621,-0.716567),
             ( 0.681718,-0.147621,-0.716567),
             ( 0.850651, 0.000000,-0.525731),
             ( 0.809017,-0.309017,-0.500000),
             ( 0.864188,-0.442863,-0.238856),
             ( 0.951056,-0.162460,-0.262866),
             ( 0.147621, 0.716567,-0.681718),
             ( 0.309017, 0.500000,-0.809017),
             ( 0.425325, 0.688191,-0.587785),
             ( 0.442863, 0.238856,-0.864188),
             ( 0.587785, 0.425325,-0.688191),
             ( 0.688191, 0.587785,-0.425325),
             (-0.147621, 0.716567,-0.681718),
             (-0.309017, 0.500000,-0.809017),
             ( 0.000000, 0.525731,-0.850651),
             (-0.525731, 0.000000,-0.850651),
             (-0.442863, 0.238856,-0.864188),
             (-0.295242, 0.000000,-0.955423),
             (-0.162460, 0.262866,-0.951056),
             ( 0.000000, 0.000000,-1.000000),
             ( 0.295242, 0.000000,-0.955423),
             ( 0.162460, 0.262866,-0.951056),
             (-0.442863,-0.238856,-0.864188),
             (-0.309017,-0.500000,-0.809017),
             (-0.162460,-0.262866,-0.951056),
             ( 0.000000,-0.850651,-0.525731),
             (-0.147621,-0.716567,-0.681718),
             ( 0.147621,-0.716567,-0.681718),
             ( 0.000000,-0.525731,-0.850651),
             ( 0.309017,-0.500000,-0.809017),
             ( 0.442863,-0.238856,-0.864188),
             ( 0.162460,-0.262866,-0.951056),
             ( 0.238856,-0.864188,-0.442863),
             ( 0.500000,-0.809017,-0.309017),
             ( 0.425325,-0.688191,-0.587785),
             ( 0.716567,-0.681718,-0.147621),
             ( 0.688191,-0.587785,-0.425325),
             ( 0.587785,-0.425325,-0.688191),
             ( 0.000000,-0.955423,-0.295242),
             ( 0.000000,-1.000000, 0.000000),
             ( 0.262866,-0.951056,-0.162460),
             ( 0.000000,-0.850651, 0.525731),
             ( 0.000000,-0.955423, 0.295242),
             ( 0.238856,-0.864188, 0.442863),
             ( 0.262866,-0.951056, 0.162460),
             ( 0.500000,-0.809017, 0.309017),
             ( 0.716567,-0.681718, 0.147621),
             ( 0.525731,-0.850651, 0.000000),
             (-0.238856,-0.864188,-0.442863),
             (-0.500000,-0.809017,-0.309017),
             (-0.262866,-0.951056,-0.162460),
             (-0.850651,-0.525731, 0.000000),
             (-0.716567,-0.681718,-0.147621),
             (-0.716567,-0.681718, 0.147621),
             (-0.525731,-0.850651, 0.000000),
             (-0.500000,-0.809017, 0.309017),
             (-0.238856,-0.864188, 0.442863),
             (-0.262866,-0.951056, 0.162460),
             (-0.864188,-0.442863, 0.238856),
             (-0.809017,-0.309017, 0.500000),
             (-0.688191,-0.587785, 0.425325),
             (-0.681718,-0.147621, 0.716567),
             (-0.442863,-0.238856, 0.864188),
             (-0.587785,-0.425325, 0.688191),
             (-0.309017,-0.500000, 0.809017),
             (-0.147621,-0.716567, 0.681718),
             (-0.425325,-0.688191, 0.587785),
             (-0.162460,-0.262866, 0.951056),
             ( 0.442863,-0.238856, 0.864188),
             ( 0.162460,-0.262866, 0.951056),
             ( 0.309017,-0.500000, 0.809017),
             ( 0.147621,-0.716567, 0.681718),
             ( 0.000000,-0.525731, 0.850651),
             ( 0.425325,-0.688191, 0.587785),
             ( 0.587785,-0.425325, 0.688191),
             ( 0.688191,-0.587785, 0.425325),
             (-0.955423, 0.295242, 0.000000),
             (-0.951056, 0.162460, 0.262866),
             (-1.000000, 0.000000, 0.000000),
             (-0.850651, 0.000000, 0.525731),
             (-0.955423,-0.295242, 0.000000),
             (-0.951056,-0.162460, 0.262866),
             (-0.864188, 0.442863,-0.238856),
             (-0.951056, 0.162460,-0.262866),
             (-0.809017, 0.309017,-0.500000),
             (-0.864188,-0.442863,-0.238856),
             (-0.951056,-0.162460,-0.262866),
             (-0.809017,-0.309017,-0.500000),
             (-0.681718, 0.147621,-0.716567),
             (-0.681718,-0.147621,-0.716567),
             (-0.850651, 0.000000,-0.525731),
             (-0.688191, 0.587785,-0.425325),
             (-0.587785, 0.425325,-0.688191),
             (-0.425325, 0.688191,-0.587785),
             (-0.425325,-0.688191,-0.587785),
             (-0.587785,-0.425325,-0.688191),
             (-0.688191,-0.587785,-0.425325))

			 
class MD2:
	def __init__(self, anim, basename):
		self.anim = anim
		self.basename = basename
		self.object = None
		self.progressBarDisplayed = -10
		return

	def setObject(self, object):
		self.object = object
		
	def write(self, filename):
		self.ident = 'IDP2'
		self.version = 8

		self.skinwidth = 2**10-1 #1023
		self.skinheight = 2**10-1 #1023

		# self.framesize : see below

		mesh = self.object.data

		skins = Util.getSkins(mesh)

		self.num_skins = len(skins)
		self.num_xyz = len(mesh.vertices)
		self.num_st = len(mesh.polygons)*3
		self.num_tris = len(mesh.polygons)
		self.num_glcmds = self.num_tris * (1+3*3) + 1
		if self.anim:
			self.num_frames = 1 + bpy.context.scene.frame_end - bpy.context.scene.frame_start

		else:
			self.num_frames = 1

		self.framesize = 40+4*self.num_xyz

		self.ofs_skins = 68 # size of the header
		self.ofs_st = self.ofs_skins + 64*self.num_skins
		self.ofs_tris = self.ofs_st + 4*self.num_st
		self.ofs_frames = self.ofs_tris + 12*self.num_tris
		self.ofs_glcmds = self.ofs_frames + self.framesize*self.num_frames
		self.ofs_end = self.ofs_glcmds + 4*self.num_glcmds

		file = open(filename, 'wb')
		try:
			# write header
			bin = struct.pack('<4B16i', #bin = struct.pack('<4s16i',
			                  ord('I'), #self.ident,
			                  ord('D'),
			                  ord('P'),
			                  ord('2'),
			                  self.version,
			                  self.skinwidth,
			                  self.skinheight,
			                  self.framesize,
			                  self.num_skins,
			                  self.num_xyz,
			                  self.num_st, #  number of texture coordinates
			                  self.num_tris,
			                  self.num_glcmds,
			                  self.num_frames,
			                  self.ofs_skins,
			                  self.ofs_st,
			                  self.ofs_tris,
			                  self.ofs_frames,
			                  self.ofs_glcmds,
			                  self.ofs_end)
			file.write(bin)
			
			# write skin file names
			for skin in skins:
				if len(skin) > 63 and self.basename == False:
					print("WARNING: The texture path '"+skin+"' is too long. It is automatically truncated to the file basename.")
					
				if len(skin) > 63 or self.basename:
					skin = os.path.basename(skin)
				
				bin = struct.pack('<64s', bytes(skin[0:63], encoding='utf8'))
				file.write(bin) # skin name
			
			
			#define meshTextureFaces
			if len(mesh.uv_textures) != 0:
				#meshTextureFaces = mesh.uv_textures[0].data
				meshTextureFaces = self.object.to_mesh(bpy.context.scene, True, 'PREVIEW').tessface_uv_textures.active
			else:
				meshTextureFaces = mesh.polygons
				
			#for meshTextureFace in meshTextureFaces: # for face in mesh.polygons:
			for meshTextureFace in meshTextureFaces.data:
				try:
					#uvs = meshTextureFace.uv
					uvs = (meshTextureFace.uv1, meshTextureFace.uv2, meshTextureFace.uv3)
				except:
					uvs = ([0,0],[0,0],[0,0])
				
				# (u,v) in blender -> (u,1-v)
				bin = struct.pack('<6h',
								  int(uvs[0][0]*self.skinwidth),
								  int((1-uvs[0][1])*self.skinheight),
								  int(uvs[1][0]*self.skinwidth),
								  int((1-uvs[1][1])*self.skinheight),
								  int(uvs[2][0]*self.skinwidth),
								  int((1-uvs[2][1])*self.skinheight),
								  )
				file.write(bin) # uv
				# (uv index is : face.index*3+i)
			
			for face in mesh.polygons:
				# 0,2,1 for good cw/ccw
				bin = struct.pack('<3h',
				                  face.vertices[0],
				                  face.vertices[2],
				                  face.vertices[1]
				                )
				file.write(bin) # vert index
				bin = struct.pack('<3h',
				                  face.index*3 + 0,
				                  face.index*3 + 2,
				                  face.index*3 + 1,
				                  )
				
				file.write(bin) # uv index
			
			if self.anim:
				min = None
				max = None
				
				timeLineMarkers =[]
				for marker in bpy.context.scene.timeline_markers:
					timeLineMarkers.append(marker)
					
				# sort the markers. The marker with the frame number closest to 0 will be the first marker in the list. 
				# The marker with the biggest frame number will be the last marker in the list
				timeLineMarkers.sort(key=lambda marker: marker.frame)
				markerIdx = 0
				
				# delete markers at same frame positions
				if len(timeLineMarkers) > 1:
					markerFrame = timeLineMarkers[len(timeLineMarkers)-1].frame
					for i in range(len(timeLineMarkers)-2, -1, -1):
						if timeLineMarkers[i].frame == markerFrame:
							del timeLineMarkers[i]
						else:
							markerFrame = timeLineMarkers[i].frame
				
				for frame in range(1, self.num_frames+1):
					percent = (frame - bpy.context.scene.frame_start) / ( 1. + self.num_frames)
					
					#Display the progress status of the exportation in the console
					#problem: "Carriage return" doesn't function properly with python 3.x
					progressStatus = math.floor((frame)/(self.num_frames+1)*100)
					progressStatusString = ("Exportation progress: "+str((progressStatus//10)*10)+"%")					
					if progressStatus - self.progressBarDisplayed >= 10:
						print(progressStatusString)
					self.progressBarDisplayed = (progressStatus//10)*10
					if frame == self.num_frames:
						print("Exportation progress: 100% - model exported")
					# --Blender.Window.DrawProgressBar(percent,
						# str(int(percent*100)) + '%' + \
						# ' : frame ' + str(frame))

					bpy.context.scene.frame_set(frame)
					(min, max) = self.findMinMax()
				
					if len(timeLineMarkers) != 0:
						if markerIdx + 1 != len(timeLineMarkers):
							if frame >= timeLineMarkers[markerIdx + 1].frame:
								markerIdx += 1
						name = timeLineMarkers[markerIdx].name
					else:
						name = 'frame'
							
					self.outFrame(file, min, max, name + str(frame))
			else:
				(min, max) = self.findMinMax()
				self.outFrame(file, min, max)

			# gl commands
			#for meshTextureFace in meshTextureFaces: # for face in mesh.polygons:
			for meshTextureFace in meshTextureFaces.data:
				try:
					uvs = (meshTextureFace.uv1, meshTextureFace.uv2, meshTextureFace.uv3)
					#uvs = meshTextureFace.uv
				except:
					uvs = ([0,0],[0,0],[0,0])
				bin = struct.pack('<i', 3)
				file.write(bin)
				# 0,2,1 for good cw/ccw (also flips/inverts normal)
				for vert in [0,2,1]:
					# (u,v) in blender -> (u,1-v)
					bin = struct.pack('<ffI',
						uvs[vert][0],
						(1.0 - uvs[vert][1]),
						face.vertices[vert])
					
					file.write(bin)
			# NULL command
			bin = struct.pack('<I', 0)
			file.write(bin)
		finally:
			file.close()

	def findMinMax(self, min=None, max=None):
		# mesh = Blender.Mesh.New()
		# mesh.getFromObject(self.object.name)
		# mesh = self.object.create_mesh(bpy.context.scene ,True, self.object.name)
		mesh = self.object.to_mesh(bpy.context.scene, True, 'PREVIEW')
		
		mesh.transform(self.object.matrix_world)
		mesh.transform(mathutils.Matrix.Rotation(pi/2, 4, 'Z')) # Hoehrer: rotate 90 degrees

		if min == None:
			min = [mesh.vertices[0].co[0],
			       mesh.vertices[0].co[1],
			       mesh.vertices[0].co[2]]
		if max == None:
			max = [mesh.vertices[0].co[0],
			       mesh.vertices[0].co[1],
			       mesh.vertices[0].co[2]]

		for vert in mesh.vertices:
			for i in range(3):
				if vert.co[i] < min[i]:
					min[i] = vert.co[i]
				if vert.co[i] > max[i]:
					max[i] = vert.co[i]

		return (min, max)

	def outFrame(self, file, min, max, frameName = 'frame'):
		mesh = self.object.to_mesh(bpy.context.scene, True, 'PREVIEW')
		
		mesh.transform(self.object.matrix_world)
		mesh.transform(mathutils.Matrix.Rotation(pi/2, 4, 'Z')) # Hoehrer: rotate 90 degrees
		
		bin = struct.pack('<6f16s', #bin = struct.pack('<6f16s', 
			self.object.scale[0]*(max[0]-min[0])/255., #self.object.getSize()[0]*(max[0]-min[0])/255.
			self.object.scale[1]*(max[1]-min[1])/255., 
			self.object.scale[2]*(max[2]-min[2])/255., 
			min[0],
			min[1],
			min[2],
			bytes(frameName, encoding='utf8'))
			
		file.write(bin) # frame header
		for vert in mesh.vertices:
			for i in range(162):
				dot =  vert.normal[1]*MD2_NORMALS[i][0] + \
				      -vert.normal[0]*MD2_NORMALS[i][1] + \
				       vert.normal[2]*MD2_NORMALS[i][2]
				if (i==0) or (dot > maxDot):
					maxDot = dot
					bestNormalIndex = i

			bin = struct.pack('<4B',
			                  int((vert.co[0]-min[0])/(max[0]-min[0])*255.),
			                  int((vert.co[1]-min[1])/(max[1]-min[1])*255.),
			                  int((vert.co[2]-min[2])/(max[2]-min[2])*255.),
			                  bestNormalIndex)
			
			file.write(bin) # vertex

class Util:
	@staticmethod
	def pickName():
		name = '_MD2Obj_'+str(random.random())
		return name[0:20]

	# deletes an object from Blender (remove + unlink)
	@staticmethod
	def deleteObject(object):
		bpy.context.scene.objects.unlink(object)
		bpy.data.objects.remove(object)
		
	# duplicates the given object and returns it
	@staticmethod
	def duplicateObject(object, name):
		# backup the current object selection and current active object
		selObjects = bpy.context.selected_objects[:]
		actObject = bpy.context.active_object
		
		# deselect all selected objects
		bpy.ops.object.select_all(action='DESELECT')
		# select the object which we want to duplicate
		object.select = True
		
		# duplicate the selected object		
		bpy.ops.object.duplicate()
		
		# the duplicated object is automatically selected
		copyObj = bpy.context.selected_objects[0]
		
		# rename the object with the given name
		copyObj.name = name
		
		# select all objects which have been previously selected and make active the previous active object
		bpy.context.scene.objects.active = actObject
		for obj in selObjects:
			obj.select = True
		
		return copyObj

	# returns the mesh of the object and return object.data (mesh)
	@staticmethod
	def triangulateMesh(object):
		mesh = object.data
		
		# make the object the active object!
		# object.select = True
		bpy.context.scene.objects.active = object
		
		bpy.ops.object.mode_set( mode="EDIT" , toggle = False )
		bpy.ops.mesh.select_all(action="SELECT")
		
		#for face in mesh.polygons:
			#face.select = True	
		
		# mesh.quadToTriangle()
		# bpy.ops.object.mode_set( mode="OBJECT" , toggle = False )
		
		bpy.ops.mesh.quads_convert_to_tris()
		bpy.ops.object.mode_set( mode="OBJECT" , toggle = False )
		return mesh

	@staticmethod
	def getSkins(mesh):
		skins = []
		for material in mesh.materials:
			for texSlot in material.texture_slots:
				if texSlot != None:
					if texSlot.texture.type == "IMAGE": # if texture.tex.getType() == "Image":
						# get the image path and convert it to a relative one (if it is not already a relative path)
						imgfile = bpy.path.relpath(texSlot.texture.image.filepath)[2:]
						skins.append(imgfile)
		return skins
			
		
class ObjectInfo:
	def __init__(self, object):
		self.triang = False
		self.vertices = -1
		self.faces = -1
		self.status = ('','')

		self.ismesh = object and object.type == 'MESH'

		if self.ismesh:
			originalObject = object
			mesh = object.data

			self.skins = Util.getSkins(mesh)

			for face in mesh.polygons:
				if len(face.vertices) == 4:
					self.triang = True
					break

			tmpObjectName = Util.pickName()
			try:
				if self.triang:
					object = Util.duplicateObject(object, tmpObjectName)
					mesh = Util.triangulateMesh(object)

				self.status = (str(len(mesh.vertices)) + ' vertices', str(len(mesh.polygons)) + ' faces')
				
			finally:
				if object.name == tmpObjectName:
					originalObject.select = True
					bpy.context.scene.objects.active = originalObject
					# bpy.context.scene.objects.unlink(object)
					Util.deleteObject(object)
		
class Export_MD2(bpy.types.Operator, ExportHelper):
	"""Export to Quake2 file format (.md2)"""
	bl_idname = "export_quake.md2"
	bl_label = "Export to Quake2 file format (.md2)"

	filename = StringProperty(name="File Path",
		description="Filepath used for processing the script",
		maxlen= 1024,default= "")	
	
	filename_ext = ".md2"

	val_anim = BoolProperty(name="Export animation",
							description="default: True",
							default=True)

	val_basename = BoolProperty(name="Export only basenames (skin)",
							description="default: True",
							default=True)

	# id_export   = 1
	# id_cancel   = 2
	# id_anim     = 3
	# id_update   = 4
	# id_help     = 5
	# id_basename = 6

	def __init__(self):
		try:
			self.object = bpy.context.selected_objects[0]
		except:
			self.object = None

		# go into object mode before we start the actual export procedure
		bpy.ops.object.mode_set( mode="OBJECT" , toggle = False )
		
		self.info = ObjectInfo(self.object)
	
	def execute(self, context):
		
		# if Blender.sys.exists(filename) == 1: #if Blender.sys.exists(filename) == 1:
		    # overwrite = Blender.Draw.PupMenu('File already exists, overwrite?%t|Yes%x1|No%x0') #overwrite = Blender.Draw.PupMenu('File already exists, overwrite?%t|Yes%x1|No%x0')
		    # if overwrite == 0:
			    # return
		
		props = self.properties
		filepath = self.filepath
		filepath = bpy.path.ensure_ext(filepath, self.filename_ext)

		if len(bpy.context.selected_objects[:]) == 0:
			raise NameError('Please, select one object!')
		
		if len(bpy.context.selected_objects[:]) > 1:
			#self.report({'ERROR'}, "Please, select one and only one object!")
			raise NameError('Please, select one and only one object!')

			
		object = self.object
		originalObject = object
		
		if object.type != 'MESH':
			raise NameError('Selected object must be a mesh!')

		# different name each time or we can't unlink it later
		tmpObjectName = Util.pickName()
		mesh = object.data	
		
		'''
		print(str(object)+" "+str(mesh)+" "+str(originalObject))
		object = Util.duplicateObject(object, tmpObjectName)
		print(str(object))
		mesh = Util.triangulateMesh(object)
		print(str(mesh))
		
		if object.name == tmpObjectName:
			originalObject.select = True
			print("unlinkt tmpObject")
			# bpy.data.objects.remove(object)
		'''
		
		if self.info.triang:
			object = Util.duplicateObject(object, tmpObjectName)
			mesh = Util.triangulateMesh(object)

		if self.val_anim:
			frame =bpy.context.scene.frame_current
		
		try:
			md2 = MD2(self.val_anim, self.val_basename)
			md2.setObject(object)
			md2.write(filepath) #md2.write(filename)
		finally:
			if object.name == tmpObjectName:
				originalObject.select = True
				bpy.context.scene.objects.active = originalObject
				Util.deleteObject(object);
			if self.val_anim:
				bpy.context.scene.frame_set(frame)
						
			# Blender.Window.DrawProgressBar(1.0, '')
			# Blender.Window.WaitCursor(False)
			# Blender.Draw.Exit()
			self.report({'INFO'},  "Model '"+originalObject.name+"' exported")
		return {'FINISHED'}
	
	def invoke(self, context, event):
		wm = context.window_manager
		wm.fileselect_add(self)
		return {'RUNNING_MODAL'}


def menuCB(self, context):
	self.layout.operator(Export_MD2.bl_idname, text="Quake2 (.md2)")
 
def register():
	# initialize()
	# bpy.utils.register_class(Export_MD2)
	bpy.utils.register_module(__name__)
	bpy.types.INFO_MT_file_export.append(menuCB)
 
def unregister():
	bpy.utils.unregister_module(__name__)
	bpy.types.INFO_MT_file_export.remove(menuCB)
 
if __name__ == "__main__":
	register()

Oh, figured I should explain how it works, huh?

The animation names are markers on the timeline, you just put the name you want for the animation sequence at the first frame of that animation. It exports only the first to last frame you set in the Blender scene, Start and End points. It also exports the vertices from the end result, so it doesn’t matter how you do the actual Blender animation all alterations to the mesh get exported for the frame. The authors really wrote it well, if not for their one small error.

Thank you very much. You should suggest the maintainers to accept your script, there is a real lack of build-in importers and exporters for some file formats still largely used in gaming (especially in mods).

You’re welcome. Since it’s not officially my script I probably shouldn’t do that, I’ll let the original authors decide since it is mostly their work.

The reason there is a current lack of scripts for Blender since 2.6 is because they have completely changed the API and are working to refine it, eliminating the old redundant left overs. Once they’ve finished the revamp there shouldn’t be much lag after they get all the old scripts replaced. Personally I don’t mind having to write my own or hunt for some for a short time, I’m impressed with 2.6 enough to consider it a tiny price to pay. It hasn’t crashed on me once since 2.63 and runs smoother and even a bit faster. My machine’s old by today’s standards, and was lurching on 2.5.

Yes you should speak about that to the original author but as long as you keep the same license and you don’t claim you wrote the whole script, you can contribute.

Yes the API has changed a lot since 2.50. I see what you mean but almost no centralized effort has been done to repair these scripts, separate people repaired some of them, each one in his side. Keep in mind some Blender users can’t program in Python, it would be fine for them to have these scripts working anew in Blender.