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.")