#!BPY """ Name: 'MD3 Import 234' Blender: 234 Group: 'Import' Submenu: 'All' all Submenu: 'Selected' sel Submenu: 'Configure (gui)' gui Tooltip: 'Import ID Software MD3 format.' """ """md3import.py Import MD3 into Blender Copyright 2004 PhaethonH Permission granted to use, copy, distribute, and modify, provided this copyright notice remains intact. Software is provided as-is, with absolutely no warranty of any kind. """ # md3import.py version 0009 (2004.10.23) """ Overhauled for 2.34. Development platform: CPU: AMD Athlon 1200MHz kernel: linux-x86 2.4.26 libc: GNU glibc-2.3.2 OS: Debian GNU/Linux sid (2002.08.21) python libs: python-2.3.4 Blender: blender-2.34-linux-glibc2.2.5-i386 """ """ TODO: * use current frame as initial frame, instead of overwriting 1 """ #Set this to where the base directory of unpacked data starts. pk3path="." #Modify this if needed (e.g. FileSelector doesn't work) md3file="blends/railgun.md3" import string import os.path from os import F_OK, R_OK, W_OK, X_OK # The MD3 parsing module. import md3 # Targeted for Blender 2.22. Plan for later Python API mess. import Blender from Blender import Window, Scene, Object, NMesh, Material, Image, Texture #Default scale factor is 1/64 MD3BLENDER_SCALE = md3.MD3_XYZ_SCALE def make_bounds_box (md3obj): """Create bounds box in the current scene and layer. This is the box used in Q3A for determining collision. Return value is undefined.""" ourscene = Scene.GetCurrent () # thisobj = Object.New('Mesh') # thismesh = Blender210.Mesh("bounds") # Blender210.connect(thisobj, thismesh) # Blender210.connect(ourscene, thisobj) # Blender210.setCurrentFrame(1) #Create the box (first frame). minx = md3obj.frames[0].bounds[0][0] miny = md3obj.frames[0].bounds[0][1] minz = md3obj.frames[0].bounds[0][2] maxx = md3obj.frames[0].bounds[1][0] maxy = md3obj.frames[0].bounds[1][1] maxz = md3obj.frames[0].bounds[1][2] """ 8 ________ 6 / /| / / | 4 /______2/ | | z| | | 7 . | .| x | . | / 5 |_______|/ y 3 1 """ #We have eight vertices. vertices = () vertices = vertices + ((minx, miny, minz),) vertices = vertices + ((minx, miny, maxz),) vertices = vertices + ((minx, maxy, minz),) vertices = vertices + ((minx, maxy, maxz),) vertices = vertices + ((maxx, miny, minz),) vertices = vertices + ((maxx, miny, maxz),) vertices = vertices + ((maxx, maxy, minz),) vertices = vertices + ((maxx, maxy, maxz),) #six faces. faces = () faces = faces + ((0, 1, 3, 2),) faces = faces + ((4, 5, 7, 6),) faces = faces + ((0, 1, 5, 4),) faces = faces + ((2, 3, 7, 6),) faces = faces + ((0, 2, 6, 4),) faces = faces + ((1, 3, 7, 5),) thismesh = NMesh.New() bvertices = [ NMesh.Vert(*x) for x in vertices ] z = [ [bvertices[x[0]], bvertices[x[1]], bvertices[x[2]], bvertices[x[3]]] for x in faces ] bfaces = [] for i in xrange(0, len(z)): bfaces.append(NMesh.Face(z[i])) # bfaces = [ NMesh.Face(*y) for y in [ [bvertices[x[0]], bvertices[x[1]], bvertices[x[2]]] for x in faces ] ] thismesh.verts = bvertices thismesh.faces = bfaces NMesh.PutRaw(thismesh, "bounds1", True) # thisobj.link(thismesh) # thisobj = thismesh # ourscene.link(thisobj) ### Animation for i in xrange(0, len(md3obj.frames)): Blender.Set('curframe', i+1) #Move the vertices around #Insert VertexKey IPO key Blender.Set('curframe', 1) #Restore frame number. def make_tags (md3obj): """Tags are named points in the model space. These are naturally represented by Empty objects. Return value is undefined.""" ourscene = Scene.GetCurrent() for i in xrange(0, len(md3obj.tags)): # print " tag %d (%s) @ " % (i, md3obj.tags[i].name), md3obj.tags[i].origin, md3obj.tags[i].axis print " tag %d:" % i, md3obj.tags[i].name #This should be Empty object. thistag = Object.New("Empty", md3obj.tags[i].name); ourscene.link(thistag) #Note: Quake3 uses left-hand geometry. x = md3obj.tags[i].origin[0] y = md3obj.tags[i].origin[1] z = md3obj.tags[i].origin[2] thistag.setLocation([x, y, z]) rx = md3obj.tags[i].axis[2][0] ry = md3obj.tags[i].axis[2][1] rz = md3obj.tags[i].axis[2][2] # thistag.setRotation([rx, ry, rz]) # thistag.RotX = rx # thistag.RotY = ry # thistag.RotZ = rz def find_texfile (tname): """Find texture file named `tname' Search through paths if needed. Return value is undefined.""" retval = tname if os.path.exists(retval): print "TEX =", retval return retval p = pk3path + os.pathsep + os.getenv("PK3PATH", ".") paths = string.split(p, os.pathsep) for p in paths: retval = os.path.normpath(p + os.sep + tname) if os.path.exists(retval): print "TEX =", retval return retval print "TEX not found:", tname return tname def make_surfaces (md3obj): """What MD3 calls a "surface", Blender calls a "mesh". Return value is undefined.""" ourscene = Scene.GetCurrent() for i in xrange(0, len(md3obj.surfaces)): print " mesh %d," % i, md3obj.surfaces[i].name thisname = md3obj.surfaces[i].name thisobj = Object.New("Mesh", thisname) thismesh = NMesh.New(thisname) thisobj.link(thismesh) ourscene.link(thisobj) ### Vertices vertices = () normals = () for j in xrange(0, md3obj.surfaces[i].numVerts): # print " vertex %d = " % j, md3obj.surfaces[i].verts[0][j].xyz, md3obj.surfaces[i].verts[0][j].normal #Note: Quake3 uses left-hand geometry. vx = md3obj.surfaces[i].verts[0][j].xyz[0] * MD3BLENDER_SCALE vy = md3obj.surfaces[i].verts[0][j].xyz[1] * MD3BLENDER_SCALE vz = md3obj.surfaces[i].verts[0][j].xyz[2] * MD3BLENDER_SCALE nx = md3obj.surfaces[i].verts[0][j].normal[0] ny = md3obj.surfaces[i].verts[0][j].normal[1] nz = md3obj.surfaces[i].verts[0][j].normal[2] vertices = vertices + ((vx, vy, vz),) normals = normals + ((nx, ny, nz),) ### Polygon faces (triangles) faces = () for j in xrange(0, len(md3obj.surfaces[i].triangles)): # print " tri %d = " % j, md3obj.surfaces[i].triangles[j].indexes #Ensures vertex 0 won't occur, so addFace won't mess up. a = md3obj.surfaces[i].triangles[j].indexes[0] b = md3obj.surfaces[i].triangles[j].indexes[1] c = md3obj.surfaces[i].triangles[j].indexes[2] d = 0 # thismesh.addFace(a, b, c, d, 0, 0) faces = faces + ((c, b, a, 0),) bvertices = [ NMesh.Vert(*x) for x in vertices ] bnormals = () for i in xrange(0, len(normals)): bvertices[i].no[0] = normals[i][0] bvertices[i].no[1] = normals[i][1] bvertices[i].no[2] = normals[i][2] z = [ [bvertices[x[0]], bvertices[x[1]], bvertices[x[2]]] for x in faces ] # print "processed1[0] =", z[0] # bfaces = [ NMesh.Face(y) for y in z ] bfaces = [] for i in xrange(0, len(z)): # print "Face(%s)" % (z[i],) # bfaces = bfaces.append(NMesh.Face(faces[i])) bfaces.append(NMesh.Face(z[i])) ###Remove doubles. # thismesh.removeDoubles() thismesh.verts = bvertices thismesh.faces = bfaces NMesh.PutRaw(thismesh, thisname, False) # Blender210.setCurrentFrame(1) #Reset animation frame to 1. # print "thismesh %d = " % i, dir(thismesh) return 1 def make_textures (md3obj): """Set up textures and texture coordinates. """ ### Textures for i in xrange(0, len(md3obj.surfaces)): thismesh = NMesh.get(md3obj.surfaces[i].name) #Blender210 only allows 1 texture per mesh. for j in xrange(0, len(md3obj.surfaces[i].shaders)): pass tname = find_texfile(md3obj.surfaces[i].shaders[0].name) # thismesh.addTexture(tname) ### Texture coordinates for j in xrange(0, len(md3obj.surfaces[i].triangles)): # print " tri %d" % j a = md3obj.surfaces[i].triangles[j].indexes[0] b = md3obj.surfaces[i].triangles[j].indexes[1] c = md3obj.surfaces[i].triangles[j].indexes[2] d = 0 u1, v1 = md3obj.surfaces[i].texcoords[a-1].st u2, v2 = md3obj.surfaces[i].texcoords[b-1].st u3, v3 = md3obj.surfaces[i].texcoords[c-1].st # thismesh.addTexCoords(u1, v1, u2, v2, u3, v3, 0, 0) # thismesh.addTexCoords(u3, v3, u2, v2, u1, v1, 0, 0) # thismesh.texcoords[j] = [[u3, v3, u2, v2, u1, v1, 0, 0]] # Blender210.setCurrentFrame(1) def make_animation (md3obj): """Set up animation of object. Doable, but requires extensive use of module `Blender' """ ### Animate for i in xrange(0, len(md3obj.surfaces)): for framenum in xrange(0, md3obj.surfaces[i].numFrames): Blender.Set('curframe', framenum+1) #Move around all the vertices. for j in xrange(0, md3obj.surfaces[i].numVerts): vx = md3obj.surfaces[i].verts[0][j].xyz[0] * MD3BLENDER_SCALE vy = md3obj.surfaces[i].verts[0][j].xyz[1] * MD3BLENDER_SCALE vz = md3obj.surfaces[i].verts[0][j].xyz[2] * MD3BLENDER_SCALE nx = md3obj.surfaces[i].verts[0][j].normal[0] ny = md3obj.surfaces[i].verts[0][j].normal[1] nz = md3obj.surfaces[i].verts[0][j].normal[2] # thismesh.vertices[j] = tuple([[vx, vy, vz]]) # thismesh.normals[j] = tuple([[nx, ny, nz]]) #Insert VertexKey IPO key # Blender210.setCurrentFrame(1) def md3_import (md3obj): """Import MD3 object into Blender. Parameter is a parsed MD3 object populated with data (from md3obj.parse()). Return value is undefined.""" print "importing MD3", md3obj.name, "..." # print "MODEL:" # print " NAME:", md3obj.name # print " #FRAMES:", len(md3obj.frames) # print " #EMPTIES:", len(md3obj.tags) # print " #MESHES:", len(md3obj.surfaces) #Warning: Quake3 uses left-hand geometry. Blender uses right-hand. # - MD3 surfaces, meshes per frame make_surfaces(md3obj) # - bounds box make_bounds_box(md3obj) # - MD3 tags make_tags(md3obj) # - Textures # make_textures(md3obj) # - Animation # make_animation(md3obj) print "...MD3 finished" def md3import (fname): """Import MD3 by filename into Blender.""" md3obj = 0 md3obj = md3.qmodel() f = open(fname, "rb") if f: f.seek(0) if md3obj.parse(f): md3_import(md3obj) f.close() def FScallback (filename): """Callback handler for FileSelector widget.""" fname = filename # print "Filename", fname md3import(fname) if __name__ == '__main__': FS = Window.FileSelector(FScallback, "Import MD3", md3file) # md3import(md3file)