def save_md5(settings):

	## CoDEmanX: replace frame range export by action export
	for a in settings.md5actions:
		if a.export_action:
			print("Export action '%s'" % a.name)

	print("Exporting selected objects...")
	bpy.ops.object.mode_set(mode='OBJECT')

	global BONES, scale # MC
	scale = settings.scale

	thearmature = 0	   #null to start, will assign in next section

	#first pass on selected data, pull one skeleton
	skeleton = Skeleton(10, "Exported from Blender by io_export_md5.py by Paul Zirkle")
	bpy.context.scene.frame_set(bpy.context.scene.frame_start)
	BONES = {}

	## CoDEmanX: currently selection, make this an option?
	for obj in bpy.context.selected_objects:
		if obj.type == 'ARMATURE':
			#skeleton.name = obj.name
			thearmature = obj
			w_matrix = obj.matrix_world

			#define recursive bone parsing function
			def treat_bone(b, parent = None, reparent = False):
				if not(reparent) and (parent and not b.parent.name == parent.name):
					return #only catch direct children

				mat = mathutils.Matrix(w_matrix) * mathutils.Matrix(b.matrix_local) #reversed order of multiplication from 2.4 to 2.5!!! ARRRGGG
				## CoDEmanX: row-major change in 2.62 / -Z90 correction?

				bone = Bone(skeleton, parent, b.name, mat, b)

				if (b.children):
					for child in b.children:
						if child.Export and not child.ReparentBool:
							treat_bone(child, bone)

				for brp in thearmature.data.bones:
					if brp.Export and brp.ReparentBool and brp.ReparentName == b.name:
						treat_bone(brp, bone, True)

			for b in thearmature.data.bones:
				if (not b.parent): #only treat root bones'
					if b.Export:
						print("root bone: " + b.name)
						treat_bone(b)

			break #only pull one skeleton out
	else:
		print ("No armature! Quitting...")
		return

	#second pass on selected data, pull meshes
	meshes = []
	for obj in bpy.context.selected_objects:
		if ((obj.type == 'MESH') and ( len(obj.data.vertices.values()) > 0 )): ##CoDEmanX: why values()? --> creates copy, same as vertices[:]
			#for each non-empty mesh

			##CoDEmanX: bmesh, replace with to_mesh!
			me = obj.data
			if not me.tessfaces and me.polygons:
				me.calc_tessface()

			mesh = Mesh(obj.name)
			print("Processing mesh: " + obj.name)
			meshes.append(mesh)

			numTris = 0
			numWeights = 0
			for f in me.tessfaces:
				numTris += len(f.vertices) - 2
			for v in me.vertices:
				numWeights += len( v.groups )

			if settings.rotate:
				from mathutils import Vector
				## CoDEmanX: how to?
				w_matrix = Vector((0,1,1)) * obj.matrix_world #fails?

				'''
				for ob in bpy.data.objects:
					if ob.type != 'MESH':
						continue
					me = ob.to_mesh(bpy.context.scene, True, 'PREVIEW')
					me.transform(Matrix.Rotation(radians(90), 4, 'Z') * ob.matrix_world)
				'''

			else:
				pass

			w_matrix = obj.matrix_world
			verts = me.vertices

			uv_textures = me.tessface_uv_textures
			faces = []
			for f in me.tessfaces:
				faces.append( f )

			createVertexA = 0
			createVertexB = 0
			createVertexC = 0

			while faces:
				material_index = faces[0].material_index
				try:
					mat_name = me.materials[0].name
				except IndexError:
					mat_name = "no_material"

				material = Material(mat_name) #call the shader name by the material's name

				submesh = SubMesh(mesh, material)
				vertices = {}
				for face in faces[:]:
					# der_ton: i added this check to make sure a face has at least 3 vertices.
					# (pdz) also checks for and removes duplicate verts
					if len(face.vertices) < 3: # throw away faces that have less than 3 vertices
						faces.remove(face)
					elif face.vertices[0] == face.vertices[1]:	  #throw away degenerate triangles
						faces.remove(face)
					elif face.vertices[0] == face.vertices[2]:
						faces.remove(face)
					elif face.vertices[1] == face.vertices[2]:
						faces.remove(face)
					elif face.material_index == material_index:
						#all faces in each sub-mesh must have the same material applied
						faces.remove(face)

						if not face.use_smooth :
							p1 = verts[ face.vertices[0] ].co
							p2 = verts[ face.vertices[1] ].co
							p3 = verts[ face.vertices[2] ].co
							normal = (w_matrix.to_3x3() * (p3-p2).cross(p1-p2)).normalized()

							#normal = vector_normalize(vector_by_matrix(vector_crossproduct( \
							#	 [p3[0] - p2[0], p3[1] - p2[1], p3[2] - p2[2]], \
							#	 [p1[0] - p2[0], p1[1] - p2[1], p1[2] - p2[2]], \
							#	 ), w_matrix))

						#for each vertex in this face, add unique to vertices dictionary
						face_vertices = []
						for i in range(len(face.vertices)):
							vertex = False
							if face.vertices[i] in vertices:
								vertex = vertices[face.vertices[i]] #type of Vertex
							if not vertex: #found unique vertex, add to list
								coord = w_matrix * verts[face.vertices[i]].co #point_by_matrix( verts[face.vertices[i]].co, w_matrix ) #TODO: fix possible bug here
								if face.use_smooth:
									normal = w_matrix.to_3x3() * verts[face.vertices[i]].normal
									#normal = vector_normalize(vector_by_matrix( verts[face.vertices[i]].normal, w_matrix ))
								vertex = vertices[face.vertices[i]] = Vertex(submesh, coord, normal)
								createVertexA += 1

								influences = []
								for j in range(len(me.vertices[face.vertices[i]].groups)):
									inf = [obj.vertex_groups[me.vertices[face.vertices[i]].groups[j].group].name, me.vertices[face.vertices[i]].groups[j].weight]
									influences.append(inf)

								if not influences:
									print("There is a vertex without attachment to a bone in mesh: " + mesh.name)
									# sum = 0.0
									# for bone_name, weight in influences: sum += weight

									# for bone_name, weight in influences:
									# if sum != 0:
									# try:
									# vertex.influences.append(Influence(BONES[bone_name], weight / sum))
									# except:
									# continue
									# else: # we have a vertex that is probably not skinned. export anyway
									# try:
									# vertex.influences.append(Influence(BONES[bone_name], weight))
									# except:
									# continue
								#dgis: Because faces can share vertices, the weights normalization will be done later (when serializing the vertices)!
								for bone_name, weight in influences:
									try:
										vertex.influences.append(Influence(BONES[bone_name], weight))
									except:
										continue

										#print( "vert " + str( face.vertices[i] ) + " has " + str(len( vertex.influences ) ) + " influences ")

							elif not face.use_smooth:
								# We cannot share vertex for non-smooth faces, since Cal3D does not
								# support vertex sharing for 2 vertices with different normals.
								# => we must clone the vertex.

								old_vertex = vertex
								vertex = Vertex(submesh, vertex.loc, normal)
								createVertexB += 1
								vertex.cloned_from = old_vertex
								vertex.influences = old_vertex.influences
								old_vertex.clones.append(vertex)

							hasFaceUV = len(uv_textures) > 0 #borrowed from export_obj.py

							if hasFaceUV:
								uv = [uv_textures.active.data[face.index].uv[i][0], uv_textures.active.data[face.index].uv[i][1]]
								uv[1] = 1.0 - uv[1] # should we flip Y? yes, new in Blender 2.5x
								if not vertex.maps: vertex.maps.append(Map(*uv))
								elif (vertex.maps[0].u != uv[0]) or (vertex.maps[0].v != uv[1]):
									# This vertex can be shared for Blender, but not for MD5
									# MD5 does not support vertex sharing for 2 vertices with
									# different UV texture coodinates.
									# => we must clone the vertex.

									for clone in vertex.clones:
										if (clone.maps[0].u == uv[0]) and (clone.maps[0].v == uv[1]):
											vertex = clone
											break
									else: # Not yet cloned...	 (PDZ) note: this ELSE belongs attached to the FOR loop.. python can do that apparently
										old_vertex = vertex
										vertex = Vertex(submesh, vertex.loc, vertex.normal)
										createVertexC += 1
										vertex.cloned_from = old_vertex
										vertex.influences = old_vertex.influences
										vertex.maps.append(Map(*uv))
										old_vertex.clones.append(vertex)

							face_vertices.append(vertex)

						# Split faces with more than 3 vertices
						for i in range(1, len(face.vertices) - 1):
							Face(submesh, face_vertices[0], face_vertices[i], face_vertices[i+1])
					else:
						print( "found face with invalid material!!!!" )
			print("created verts at A " + str(createVertexA) + ", B " + str(createVertexB) + ", C " + str(createVertexC))

	# Export animations

	## CoDEmanX: rewrite!

	if not thearmature.animation_data:
		thearmature.animation_data_create()

	orig_action = thearmature.animation_data.action

	for a in settings.md5actions:
		if not a.export_action and settings.sel_only:
			continue

		arm_action = bpy.data.actions.get(a.name, False)
		if not arm_action:
			continue

		if len(arm_action.pose_markers) < 2:
			frame_range = (int(arm_action.frame_range[0]), int(arm_action.frame_range[1]))
		else:
			pm_frames = [pm.frame for pm in arm_action.pose_markers]
			frame_range = (min(pm_frames), max(pm_frames))

		rangestart = frame_range[0]
		rangeend = frame_range[1]


		thearmature.animation_data.action = arm_action
		#arm_action = thearmature.animation_data.action
		#rangestart = 0
		#rangeend = 0
		#if arm_action:
		ANIMATIONS = {}
		animation = ANIMATIONS[arm_action.name] = MD5Animation(skeleton)

		#rangestart = int(bpy.context.scene.frame_start) # int( arm_action.frame_range[0] )
		#rangeend = int(bpy.context.scene.frame_end) #int( arm_action.frame_range[1] )
		currenttime = rangestart
		while currenttime <= rangeend:
			bpy.context.scene.frame_set(currenttime)
			time = (currenttime - 1.0) / 24.0 #(assuming default 24fps for md5 anim)
			pose = thearmature.pose

			for bonename in thearmature.data.bones.keys():
				posebonemat = mathutils.Matrix(pose.bones[bonename].matrix) # @ivar poseMatrix: The total transformation of this PoseBone including constraints. -- different from localMatrix

				try:
					bone = BONES[bonename] #look up md5bone
				except:
					continue
				if bone.parent: # need parentspace-matrix
					parentposemat = mathutils.Matrix(pose.bones[bone.parent.name].matrix) # @ivar poseMatrix: The total transformation of this PoseBone including constraints. -- different from localMatrix
					#posebonemat = parentposemat.invert() * posebonemat #reverse order of multiplication!!!
					parentposemat.invert() # mikshaw
					posebonemat = parentposemat * posebonemat # mikshaw
				else:
					posebonemat = thearmature.matrix_world * posebonemat	#reverse order of multiplication!!!
				loc = [posebonemat.col[3][0],
					   posebonemat.col[3][1],
					   posebonemat.col[3][2],
					   ] ## CoDEmanX: row-major?
				#rot = posebonemat.to_quat().normalize()
				rot = posebonemat.to_quaternion() # changed from to_quat in 2.57 -mikshaw
				rot.normalize() # mikshaw
				rot = [rot.w, rot.x, rot.y, rot.z]
				
				animation.addkeyforbone(bone.id, time, loc, rot)
			currenttime += 1

		# MOVED ANIM EXPORT CODE
		#if(settings.exportMode == "mesh & anim" or settings.exportMode == "anim only"):
		if True:
			#md5anim_filename = settings.savepath + ".md5anim"
			import os.path

			if settings.prefix:
				if settings.name:
					prefix_str = settings.name + "_"
				else:
					prefix_str = os.path.splitext(os.path.split(settings.savepath)[1])[0] + "_"
			else:
				prefix_str = ""

			md5anim_filename = os.path.split(settings.savepath)[0] + os.path.sep + prefix_str + arm_action.name + ".md5anim"

			#save animation file
			if len(ANIMATIONS) > 0:
				anim = ANIMATIONS.popitem()[1] #ANIMATIONS.values()[0]
				#print(str(anim))
				try:
					file = open(md5anim_filename, 'w')
				except IOError:
					errmsg = "IOError " #%s: %s" % (errno, strerror)
				objects = []
				for submesh in meshes[0].submeshes:
					if len(submesh.weights) > 0:
						obj = None
						for sob in bpy.context.selected_objects:
							if sob and sob.type == 'MESH' and sob.name == submesh.name:
								obj = sob
						objects.append (obj)
				generateboundingbox(objects, anim, [rangestart, rangeend])
				buffer = anim.to_md5anim()
				file.write(buffer)
				file.close()
				print( "saved anim to " + md5anim_filename )
			else:
				print( "No md5anim file was generated." )
				# END MOVED

	thearmature.animation_data.action = orig_action

	# here begins md5mesh and anim output
	# this is how it works
	# first the skeleton is output, using the data that was collected by the above code in this export function
	# then the mesh data is output (into the same md5mesh file)

	## CoDEmanX: replace? shall mesh only be supported?
	#if( settings.exportMode == "mesh & anim" or settings.exportMode == "mesh only" ):
	if True:
		md5mesh_filename = settings.savepath #+ ".md5mesh" ## already has extension?!

		#save all submeshes in the first mesh
		if len(meshes) > 1:
			for mesh in range (1, len(meshes)):
				for submesh in meshes[mesh].submeshes:
					submesh.bindtomesh(meshes[0])
		if (md5mesh_filename != ""):
			try:
				file = open(md5mesh_filename, 'w')
			except IOError:
				errmsg = "IOError " #%s: %s" % (errno, strerror)
			buffer = skeleton.to_md5mesh(len(meshes[0].submeshes))
			#for mesh in meshes:
			buffer = buffer + meshes[0].to_md5mesh()
			file.write(buffer)
			file.close()
			print("saved mesh to " + md5mesh_filename)
		else:
			print("No md5mesh file was generated.")