diff --git a/release/scripts/3ds_export.py b/release/scripts/3ds_export.py new file mode 100644 index 00000000000..ebe06c3d4eb --- /dev/null +++ b/release/scripts/3ds_export.py @@ -0,0 +1,673 @@ +#!BPY + +""" +Name: '3D Studio (.3ds)...' +Blender: 237 +Group: 'Export' +Tooltip: 'Export to 3DS file format (.3ds).' +""" + +__author__ = ["Campbell Barton", "Bob Holcomb", "Richard Lärkäng", "Damien McGinnes"] +__url__ = ("blender", "elysiun", "http://www.gametutorials.com") +__version__ = "0.82" +__bpydoc__ = """\ + +3ds Exporter + +This script Exports a 3ds file and the materials into blender for editing. + +Exporting is based on 3ds loader from www.gametutorials.com(Thanks DigiBen). +""" + +# ***** BEGIN GPL LICENSE BLOCK ***** +# +# Script copyright (C) Bob Holcomb +# +# 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 ***** +# -------------------------------------------------------------------------- + + +###################################################### +# Importing modules +###################################################### + +import Blender +from Blender import NMesh, Scene, Object, Material +import struct + + +###################################################### +# Data Structures +###################################################### + +#Some of the chunks that we will export +#----- Primary Chunk, at the beginning of each file +PRIMARY= long("0x4D4D",16) + +#------ Main Chunks +OBJECTINFO = long("0x3D3D",16); #This gives the version of the mesh and is found right before the material and object information +VERSION = long("0x0002",16); #This gives the version of the .3ds file +EDITKEYFRAME= long("0xB000",16); #This is the header for all of the key frame info + +#------ sub defines of OBJECTINFO +MATERIAL=45055 #0xAFFF // This stored the texture info +OBJECT=16384 #0x4000 // This stores the faces, vertices, etc... + +#>------ sub defines of MATERIAL +MATNAME = long("0xA000",16); # This holds the material name +MATAMBIENT = long("0xA010",16); # Ambient color of the object/material +MATDIFFUSE = long("0xA020",16); # This holds the color of the object/material +MATSPECULAR = long("0xA030",16); # SPecular color of the object/material +MATSHINESS = long("0xA040",16); # ?? +MATMAP = long("0xA200",16); # This is a header for a new material +MATMAPFILE = long("0xA300",16); # This holds the file name of the texture + +RGB1= long("0x0011",16) +RGB2= long("0x0012",16) + +#>------ sub defines of OBJECT +OBJECT_MESH = long("0x4100",16); # This lets us know that we are reading a new object +OBJECT_LIGHT = long("0x4600",16); # This lets un know we are reading a light object +OBJECT_CAMERA= long("0x4700",16); # This lets un know we are reading a camera object + +#>------ sub defines of CAMERA +OBJECT_CAM_RANGES= long("0x4720",16); # The camera range values + +#>------ sub defines of OBJECT_MESH +OBJECT_VERTICES = long("0x4110",16); # The objects vertices +OBJECT_FACES = long("0x4120",16); # The objects faces +OBJECT_MATERIAL = long("0x4130",16); # This is found if the object has a material, either texture map or color +OBJECT_UV = long("0x4140",16); # The UV texture coordinates +OBJECT_TRANS_MATRIX = long("0x4160",16); # The Object Matrix + +#==============================================# +# Strips the slashes from the back of a string # +#==============================================# +def stripPath(path): + return path.split('/')[-1].split('\\')[-1] + +#==================================================# +# New name based on old with a different extension # +#==================================================# +def newFName(ext): + return Blender.Get('filename')[: -len(Blender.Get('filename').split('.', -1)[-1]) ] + ext + + +#the chunk class +class chunk: + ID=0 + size=0 + + def __init__(self): + self.ID=0 + self.size=0 + + def get_size(self): + self.size=6 + + def write(self, file): + #write header + data=struct.pack(\ + " 2] + facenr=0 + #fill in faces + for face in valid_faces: + + #is this a tri or a quad + num_fv=len(face.v) + + + #it's a tri + if num_fv==3: + mesh.f_chunk.faces.append((face[0].index, face[1].index, face[2].index)) + if (face.materialIndex < len(mesh.f_chunk.m_chunks)): + mesh.f_chunk.m_chunks[face.materialIndex].faces.append(facenr) + facenr+=1 + + else: #it's a quad + mesh.f_chunk.faces.append((face[0].index, face[1].index, face[2].index)) # 0,1,2 + mesh.f_chunk.faces.append((face[2].index, face[3].index, face[0].index)) # 2,3,0 + #first tri + if (face.materialIndex < len(mesh.f_chunk.m_chunks)): + mesh.f_chunk.m_chunks[face.materialIndex].faces.append(facenr) + facenr+=1 + #other tri + if (face.materialIndex < len(mesh.f_chunk.m_chunks)): + mesh.f_chunk.m_chunks[face.materialIndex].faces.append(facenr) + facenr+=1 + + + #fill in the UV info + if blender_mesh.hasVertexUV(): + for vert in blender_mesh.verts: + mesh.uv_chunk.uv.append((vert.uvco[0], vert.uvco[1])) + + elif blender_mesh.hasFaceUV(): + for face in valid_faces: + # Tri or quad. + for uv_coord in face.uv: + mesh.uv_chunk.uv.append((uv_coord[0], uv_coord[1])) + + #filled in our mesh, lets add it to the file + primary.obj_info.obj_chunks[len(primary.obj_info.obj_chunks)-1].mesh_chunks.append(mesh) + + #check the size + primary.get_size() + #open the files up for writing + file = open( filename, "wb" ) + #recursively write the stuff to file + primary.write(file) + file.close() + print "3ds export time: %.2f" % (Blender.sys.time() - time1) + + +Blender.Window.FileSelector(save_3ds, "Export 3DS", newFName('3ds')) diff --git a/release/scripts/3ds_import.py b/release/scripts/3ds_import.py new file mode 100644 index 00000000000..6bd81c2606d --- /dev/null +++ b/release/scripts/3ds_import.py @@ -0,0 +1,548 @@ +#!BPY + +""" +Name: '3D Studio (.3ds)...' +Blender: 237 +Group: 'Import' +Tooltip: 'Import from 3DS file format (.3ds).' +""" + +__author__ = ["Bob Holcomb", "Richard Lärkäng", "Damien McGinnes", "Campbell Barton"] +__url__ = ("blender", "elysiun", "http://www.gametutorials.com") +__version__ = "0.82" +__bpydoc__ = """\ + +3ds Importer + +This script imports a 3ds file and the materials into blender for editing. + +Loader is based on 3ds loader from www.gametutorials.com(Thanks DigiBen). + +Changes:
+0.81a (fork- not 0.9) Campbell Barton 2005-06-08
+- Simplified import code
+- Never overwrite data
+- Faster list handling
+- Leaves import selected
+ +0.81 Damien McGinnes 2005-01-09
+- handle missing images better
+ +0.8 Damien McGinnes 2005-01-08
+- copies sticky UV coords to face ones
+- handles images better
+- Recommend that you run 'RemoveDoubles' on each imported mesh after using this script + +""" + +# ***** BEGIN GPL LICENSE BLOCK ***** +# +# Script copyright (C) Bob Holcomb +# +# 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 ***** +# -------------------------------------------------------------------------- + +# Importing modules + +import Blender +from Blender import NMesh, Scene, Object, Material, Image + +import sys, struct, string + +import os + +#this script imports uvcoords as sticky vertex coords +#this parameter enables copying these to face uv coords +#which shold be more useful. + + +#===========================================================================# +# Returns unique name of object/mesh (stops overwriting existing meshes) # +#===========================================================================# +def getUniqueName(name): + newName = name + uniqueInt = 0 + while 1: + try: + ob = Object.Get(newName) + # Okay, this is working, so lets make a new name + newName = '%s.%d' % (name, uniqueInt) + uniqueInt +=1 + except AttributeError: + if newName not in NMesh.GetNames(): + return newName + else: + newName = '%s.%d' % (name, uniqueInt) + uniqueInt +=1 + + +###################################################### +# Data Structures +###################################################### + +#Some of the chunks that we will see +#----- Primary Chunk, at the beginning of each file +PRIMARY= long("0x4D4D",16) + +#------ Main Chunks +OBJECTINFO = long("0x3D3D",16); #This gives the version of the mesh and is found right before the material and object information +VERSION = long("0x0002",16); #This gives the version of the .3ds file +EDITKEYFRAME= long("0xB000",16); #This is the header for all of the key frame info + +#------ sub defines of OBJECTINFO +MATERIAL=45055 #0xAFFF // This stored the texture info +OBJECT=16384 #0x4000 // This stores the faces, vertices, etc... + +#>------ sub defines of MATERIAL +MATNAME = long("0xA000",16); # This holds the material name +MATAMBIENT = long("0xA010",16); # Ambient color of the object/material +MATDIFFUSE = long("0xA020",16); # This holds the color of the object/material +MATSPECULAR = long("0xA030",16); # SPecular color of the object/material +MATSHINESS = long("0xA040",16); # ?? +MATMAP = long("0xA200",16); # This is a header for a new material +MATMAPFILE = long("0xA300",16); # This holds the file name of the texture + +#>------ sub defines of OBJECT +OBJECT_MESH = long("0x4100",16); # This lets us know that we are reading a new object +OBJECT_LIGHT = long("0x4600",16); # This lets un know we are reading a light object +OBJECT_CAMERA= long("0x4700",16); # This lets un know we are reading a camera object + +#>------ sub defines of CAMERA +OBJECT_CAM_RANGES= long("0x4720",16); # The camera range values + +#>------ sub defines of OBJECT_MESH +OBJECT_VERTICES = long("0x4110",16); # The objects vertices +OBJECT_FACES = long("0x4120",16); # The objects faces +OBJECT_MATERIAL = long("0x4130",16); # This is found if the object has a material, either texture map or color +OBJECT_UV = long("0x4140",16); # The UV texture coordinates +OBJECT_TRANS_MATRIX = long("0x4160",16); # The Object Matrix + +#the chunk class +class chunk: + ID=0 + length=0 + bytes_read=0 + + #we don't read in the bytes_read, we compute that + binary_format="3): + print "\tNon-Fatal Error: Version greater than 3, may not load correctly: ", version + + #is it an object info chunk? + elif (new_chunk.ID==OBJECTINFO): + # print "found an OBJECTINFO chunk" + process_next_chunk(file, new_chunk, new_object_list) + + #keep track of how much we read in the main chunk + new_chunk.bytes_read+=temp_chunk.bytes_read + + #is it an object chunk? + elif (new_chunk.ID==OBJECT): + # print "found an OBJECT chunk" + tempName = str(read_string(file)) + contextObName = getUniqueName( tempName ) + new_chunk.bytes_read += (len(tempName)+1) + + #is it a material chunk? + elif (new_chunk.ID==MATERIAL): + # print "found a MATERIAL chunk" + contextMaterial = Material.New() + + elif (new_chunk.ID==MATNAME): + # print "Found a MATNAME chunk" + material_name="" + material_name=str(read_string(file)) + + #plus one for the null character that ended the string + new_chunk.bytes_read+=(len(material_name)+1) + + contextMaterial.setName(material_name) + MATDICT[material_name] = contextMaterial.name + + elif (new_chunk.ID==MATAMBIENT): + # print "Found a MATAMBIENT chunk" + + read_chunk(file, temp_chunk) + temp_data=file.read(struct.calcsize("3B")) + data=struct.unpack("3B", temp_data) + temp_chunk.bytes_read+=3 + contextMaterial.mirCol = [float(col)/255 for col in data] # data [0,1,2] == rgb + new_chunk.bytes_read+=temp_chunk.bytes_read + + elif (new_chunk.ID==MATDIFFUSE): + # print "Found a MATDIFFUSE chunk" + + read_chunk(file, temp_chunk) + temp_data=file.read(struct.calcsize("3B")) + data=struct.unpack("3B", temp_data) + temp_chunk.bytes_read+=3 + contextMaterial.rgbCol = [float(col)/255 for col in data] # data [0,1,2] == rgb + new_chunk.bytes_read+=temp_chunk.bytes_read + + elif (new_chunk.ID==MATSPECULAR): + # print "Found a MATSPECULAR chunk" + + read_chunk(file, temp_chunk) + temp_data=file.read(struct.calcsize("3B")) + data=struct.unpack("3B", temp_data) + temp_chunk.bytes_read+=3 + + contextMaterial.specCol = [float(col)/255 for col in data] # data [0,1,2] == rgb + new_chunk.bytes_read+=temp_chunk.bytes_read + + elif (new_chunk.ID==MATMAP): + # print "Found a MATMAP chunk" + pass # This chunk has no data + + elif (new_chunk.ID==MATMAPFILE): + # print "Found a MATMAPFILE chunk" + texture_name="" + texture_name=str(read_string(file)) + try: + img = Image.Load(texture_name) + TEXDICT[contextMaterial.name]=img + except IOError: + fname = os.path.join( os.path.dirname(FILENAME), texture_name) + try: + img = Image.Load(fname) + TEXDICT[contextMaterial.name]=img + except IOError: + print "\tERROR: failed to load image ",texture_name + TEXDICT[contextMaterial.name] = None # Dummy + + #plus one for the null character that gets removed + new_chunk.bytes_read += (len(texture_name)+1) + + + elif (new_chunk.ID==OBJECT_MESH): + # print "Found an OBJECT_MESH chunk" + if contextMesh != None: # Write context mesh if we have one. + putContextMesh(contextMesh) + + contextMesh = NMesh.New() + + # Reset matrix + contextMatrix = Blender.Mathutils.Matrix(); contextMatrix.identity() + + elif (new_chunk.ID==OBJECT_VERTICES): + # print "Found an OBJECT_VERTICES chunk" + #print "object_verts: length: ", new_chunk.length + temp_data=file.read(STRUCT_SIZE_UNSIGNED_SHORT) + data=struct.unpack("H", temp_data) + new_chunk.bytes_read+=2 + num_verts=data[0] + # print "number of verts: ", num_verts + for counter in range (num_verts): + temp_data=file.read(STRUCT_SIZE_3FLOAT) + new_chunk.bytes_read += STRUCT_SIZE_3FLOAT #12: 3 floats x 4 bytes each + data=struct.unpack("3f", temp_data) + v=NMesh.Vert(data[0],data[1],data[2]) + contextMesh.verts.append(v) + #print "object verts: bytes read: ", new_chunk.bytes_read + + elif (new_chunk.ID==OBJECT_FACES): + # print "Found an OBJECT_FACES chunk" + #print "object faces: length: ", new_chunk.length + temp_data=file.read(STRUCT_SIZE_UNSIGNED_SHORT) + data=struct.unpack("H", temp_data) + new_chunk.bytes_read+=2 + num_faces=data[0] + #print "number of faces: ", num_faces + + for counter in range(num_faces): + temp_data=file.read(STRUCT_SIZE_4UNSIGNED_SHORT) + new_chunk.bytes_read += STRUCT_SIZE_4UNSIGNED_SHORT #4 short ints x 2 bytes each + data=struct.unpack("4H", temp_data) + + #insert the mesh info into the faces, don't worry about data[3] it is a 3D studio thing + f = NMesh.Face( [contextMesh.verts[data[i]] for i in xrange(3) ] ) + f.uv = [ tuple(contextMesh.verts[data[i]].uvco[:2]) for i in xrange(3) ] + contextMesh.faces.append(f) + #print "object faces: bytes read: ", new_chunk.bytes_read + + elif (new_chunk.ID==OBJECT_MATERIAL): + # print "Found an OBJECT_MATERIAL chunk" + material_name="" + material_name=str(read_string(file)) + new_chunk.bytes_read += len(material_name)+1 # remove 1 null character. + + #look up the material in all the materials + material_found=0 + for mat in Material.Get(): + + #found it, add it to the mesh + if(mat.name==material_name): + if len(contextMesh.materials) >= 15: + print "\tCant assign more than 16 materials per mesh, keep going..." + break + else: + meshHasMat = 0 + for myMat in contextMesh.materials: + if myMat.name == mat.name: + meshHasMat = 1 + + if meshHasMat == 0: + contextMesh.addMaterial(mat) + material_found=1 + + #figure out what material index this is for the mesh + for mat_counter in range(len(contextMesh.materials)): + if contextMesh.materials[mat_counter].name == material_name: + mat_index=mat_counter + #print "material index: ",mat_index + + + break # get out of this for loop so we don't accidentally set material_found back to 0 + else: + material_found=0 + # print "Not matching: ", mat.name, " and ", material_name + + if material_found == 1: + contextMaterial = mat + #read the number of faces using this material + temp_data=file.read(STRUCT_SIZE_UNSIGNED_SHORT) + data=struct.unpack("H", temp_data) + new_chunk.bytes_read += STRUCT_SIZE_UNSIGNED_SHORT + num_faces_using_mat=data[0] + + #list of faces using mat + for face_counter in range(num_faces_using_mat): + temp_data=file.read(STRUCT_SIZE_UNSIGNED_SHORT) + new_chunk.bytes_read += STRUCT_SIZE_UNSIGNED_SHORT + data=struct.unpack("H", temp_data) + contextMesh.faces[data[0]].materialIndex = mat_index + + try: + mname = MATDICT[contextMaterial.name] + contextMesh.faces[data[0]].image = TEXDICT[mname] + except: + continue + else: + #read past the information about the material you couldn't find + #print "Couldn't find material. Reading past face material info" + buffer_size=new_chunk.length-new_chunk.bytes_read + binary_format=str(buffer_size)+"c" + temp_data=file.read(struct.calcsize(binary_format)) + new_chunk.bytes_read+=buffer_size + + #print "object mat: bytes read: ", new_chunk.bytes_read + + elif (new_chunk.ID == OBJECT_UV): + # print "Found an OBJECT_UV chunk" + temp_data=file.read(STRUCT_SIZE_UNSIGNED_SHORT) + data=struct.unpack("H", temp_data) + new_chunk.bytes_read+=2 + num_uv=data[0] + + for counter in range(num_uv): + temp_data=file.read(STRUCT_SIZE_2FLOAT) + new_chunk.bytes_read += STRUCT_SIZE_2FLOAT #2 float x 4 bytes each + data=struct.unpack("2f", temp_data) + + #insert the insert the UV coords in the vertex data + contextMesh.verts[counter].uvco = data + + elif (new_chunk.ID == OBJECT_TRANS_MATRIX): + # print "Found an OBJECT_TRANS_MATRIX chunk" + + temp_data=file.read(STRUCT_SIZE_4x3MAT) + data = list( struct.unpack("ffffffffffff", temp_data) ) + new_chunk.bytes_read += STRUCT_SIZE_4x3MAT + + contextMatrix = Blender.Mathutils.Matrix(\ + data[:3] + [0],\ + data[3:6] + [0],\ + data[6:9] + [0],\ + data[9:] + [1]) + + + + else: #(new_chunk.ID!=VERSION or new_chunk.ID!=OBJECTINFO or new_chunk.ID!=OBJECT or new_chunk.ID!=MATERIAL): + # print "skipping to end of this chunk" + buffer_size=new_chunk.length-new_chunk.bytes_read + binary_format=str(buffer_size)+"c" + temp_data=file.read(struct.calcsize(binary_format)) + new_chunk.bytes_read+=buffer_size + + + #update the previous chunk bytes read + previous_chunk.bytes_read += new_chunk.bytes_read + #print "Bytes left in this chunk: ", previous_chunk.length-previous_chunk.bytes_read + + # FINISHED LOOP + # There will be a number of objects still not added + if contextMesh != None: + putContextMesh(contextMesh) + + for ob in objectList: + ob.sel = 1 + +def load_3ds (filename): + print 'Importing "%s"' % filename + + time1 = Blender.sys.time() + + global FILENAME + FILENAME=filename + current_chunk=chunk() + + file=open(filename,"rb") + + #here we go! + # print "reading the first chunk" + new_object_list = [] + read_chunk(file, current_chunk) + if (current_chunk.ID!=PRIMARY): + print "\tFatal Error: Not a valid 3ds file: ", filename + file.close() + return + + process_next_chunk(file, current_chunk, new_object_list) + + # Select all new objects. + for ob in new_object_list: ob.sel = 1 + + print 'finished importing: "%s" in %.4f sec.' % (filename, (Blender.sys.time()-time1)) + file.close() + +#*********************************************** +# MAIN +#*********************************************** +def my_callback(filename): + load_3ds(filename) + +Blender.Window.FileSelector(my_callback, "Import 3DS", '*.3ds') + +# For testing compatibility +''' +TIME = Blender.sys.time() +import os +for _3ds in os.listdir('/3ds/'): + if _3ds.lower().endswith('3ds'): + print _3ds + newScn = Scene.New(_3ds) + newScn.makeCurrent() + my_callback('/3ds/' + _3ds) + +print "TOTAL TIME: ", Blender.sys.time() - TIME +''' diff --git a/release/scripts/ac3d_export.py b/release/scripts/ac3d_export.py index b9b7b8e5ae6..ea9ba239003 100644 --- a/release/scripts/ac3d_export.py +++ b/release/scripts/ac3d_export.py @@ -202,7 +202,8 @@ def transform_verts(verts, m): vecs = [] for v in verts: vec = Mathutils.Vector([v[0],v[1],v[2], 1]) - vecs.append(Mathutils.VecMultMat(vec, m)) + #vecs.append(Mathutils.VecMultMat(vec, m)) + vecs.append(vec*m) return vecs # --- diff --git a/release/scripts/ac3d_import.py b/release/scripts/ac3d_import.py index d2505022adf..4dcde65fb4a 100644 --- a/release/scripts/ac3d_import.py +++ b/release/scripts/ac3d_import.py @@ -10,7 +10,7 @@ Tip: 'Import an AC3D (.ac) file.' __author__ = "Willian P. Germano" __url__ = ("blender", "elysiun", "AC3D's homepage, http://www.ac3d.org", "PLib 3d gaming lib, http://plib.sf.net") -__version__ = "2.36 2005-04-14" +__version__ = "2.36a 2005-12-04" __bpydoc__ = """\ This script imports AC3D models into Blender. @@ -43,9 +43,9 @@ users can configure (see config options above). # $Id$ # # -------------------------------------------------------------------------- -# AC3DImport version 2.36 Apr 14, 2005 +# AC3DImport version 2.36a Dec 04, 2005 # Program versions: Blender 2.36+ and AC3Db files (means version 0xb) -# changed: updated to use the Scripts Config Editor facilities +# changed: fixed a bug: error on 1 vertex "closed" polylines # -------------------------------------------------------------------------- # ***** BEGIN GPL LICENSE BLOCK ***** # @@ -366,7 +366,7 @@ class AC3DImport: faces.append(cut) face = face[1:] - if flaglow == 1: + if flaglow == 1 and faces: face = [faces[-1][-1], faces[0][0]] faces.append(face) @@ -498,7 +498,9 @@ class AC3DImport: for vi in range(len(f)): bface.v.append(mesh.verts[f[vi][0]]) bface.uv.append((f[vi][1][0], f[vi][1][1])) - mesh.faces.append(bface) + #mesh.faces.append(bface) + # quick hack, will switch from NMesh to Mesh later: + if len(bface.v) > 1: mesh.addFace(bface) mesh.mode = 0 object = Blender.NMesh.PutRaw(mesh) diff --git a/release/scripts/md2_export.py b/release/scripts/md2_export.py new file mode 100644 index 00000000000..d02f1101d9a --- /dev/null +++ b/release/scripts/md2_export.py @@ -0,0 +1,1016 @@ +#!BPY + +""" +Name: 'MD2 (.md2)' +Blender: 239 +Group: 'Export' +Tooltip: 'Export to Quake file format (.md2).' +""" + +__author__ = 'Bob Holcomb' +__version__ = '0.16' +__url__ = ["Bob's site, http://bane.servebeer.com", + "Support forum, http://scourage.servebeer.com/phpbb/", "blender", "elysiun"] +__email__ = ["Bob Holcomb, bob_holcomb:hotmail*com", "scripts"] +__bpydoc__ = """\ +This script Exports a Quake 2 file (MD2). + + Additional help from: Shadwolf, Skandal, Rojo, Cambo
+ Thanks Guys! +""" + +# ***** BEGIN GPL LICENSE BLOCK ***** +# +# Script copyright (C): Bob Holcomb +# +# 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 ***** +# -------------------------------------------------------------------------- + + +import Blender +from Blender import * +from Blender.Draw import * +from Blender.BGL import * +from Blender.Window import * + +import struct, string +from types import * + + + +###################################################### +# GUI Loader +###################################################### + +# Export globals +g_filename=Create("/home/bob/work/blender_scripts/md2/test-export.md2") +g_frame_filename=Create("default") + +g_filename_search=Create("model") +g_frame_search=Create("default") + +user_frame_list=[] + +#Globals +g_scale=Create(1.0) + +# Events +EVENT_NOEVENT=1 +EVENT_SAVE_MD2=2 +EVENT_CHOOSE_FILENAME=3 +EVENT_CHOOSE_FRAME=4 +EVENT_EXIT=100 + +###################################################### +# Callbacks for Window functions +###################################################### +def filename_callback(input_filename): + global g_filename + g_filename.val=input_filename + +def frame_callback(input_frame): + global g_frame_filename + g_frame_filename.val=input_frame + +def draw_gui(): + global g_scale + global g_filename + global g_frame_filename + global EVENT_NOEVENT,EVENT_SAVE_MD2,EVENT_CHOOSE_FILENAME,EVENT_CHOOSE_FRAME,EVENT_EXIT + + ########## Titles + glClear(GL_COLOR_BUFFER_BIT) + glRasterPos2d(8, 103) + Text("MD2 Export") + + ######### Parameters GUI Buttons + g_filename = String("MD2 file to save: ", EVENT_NOEVENT, 10, 55, 210, 18, + g_filename.val, 255, "MD2 file to save") + ########## MD2 File Search Button + Button("Search",EVENT_CHOOSE_FILENAME,220,55,80,18) + + g_frame_filename = String("Frame List file to load: ", EVENT_NOEVENT, 10, 35, 210, 18, + g_frame_filename.val, 255, "Frame List to load-overrides MD2 defaults") + ########## Texture Search Button + Button("Search",EVENT_CHOOSE_FRAME,220,35,80,18) + + ########## Scale slider-default is 1/8 which is a good scale for md2->blender + g_scale= Slider("Scale Factor: ", EVENT_NOEVENT, 10, 75, 210, 18, + 1.0, 0.001, 10.0, 1, "Scale factor for obj Model"); + + ######### Draw and Exit Buttons + Button("Export",EVENT_SAVE_MD2 , 10, 10, 80, 18) + Button("Exit",EVENT_EXIT , 170, 10, 80, 18) + +def event(evt, val): + if (evt == QKEY and not val): + Exit() + +def bevent(evt): + global g_filename + global g_frame_filename + global EVENT_NOEVENT,EVENT_SAVE_MD2,EVENT_EXIT + + ######### Manages GUI events + if (evt==EVENT_EXIT): + Blender.Draw.Exit() + elif (evt==EVENT_CHOOSE_FILENAME): + FileSelector(filename_callback, "MD2 File Selection") + elif (evt==EVENT_CHOOSE_FRAME): + FileSelector(frame_callback, "Frame Selection") + elif (evt==EVENT_SAVE_MD2): + if (g_filename.val == "model"): + save_md2("blender.md2") + Blender.Draw.Exit() + return + else: + save_md2(g_filename.val) + Blender.Draw.Exit() + return + +Register(draw_gui, event, bevent) + +###################################################### +# MD2 Model Constants +###################################################### +MD2_MAX_TRIANGLES=4096 +MD2_MAX_VERTICES=2048 +MD2_MAX_TEXCOORDS=2048 +MD2_MAX_FRAMES=512 +MD2_MAX_SKINS=32 +MD2_MAX_FRAMESIZE=(MD2_MAX_VERTICES * 4 + 128) + +MD2_FRAME_NAME_LIST=(("stand",1,40), + ("run",41,46), + ("attack",47,54), + ("pain1",55,58), + ("pain2",59,62), + ("pain3",63,66), + ("jump",67,72), + ("flip",73,84), + ("salute", 85,95), + ("taunt",96,112), + ("wave",113,123), + ("point",124,135), + ("crstnd",136,154), + ("crwalk",155,160), + ("crattack",161,169), + ("crpain",170,173), + ("crdeath",174,178), + ("death1",179,184), + ("death2",185,190), + ("death3",191,198)) + #198 frames + +###################################################### +# MD2 data structures +###################################################### +class md2_point: + vertices=[] + lightnormalindex=0 + binary_format="<3BB" + def __init__(self): + self.vertices=[0]*3 + self.lightnormalindex=0 + def save(self, file): + temp_data=[0]*4 + temp_data[0]=self.vertices[0] + temp_data[1]=self.vertices[1] + temp_data[2]=self.vertices[2] + temp_data[3]=self.lightnormalindex + data=struct.pack(self.binary_format, temp_data[0], temp_data[1], temp_data[2], temp_data[3]) + file.write(data) + def dump(self): + print "MD2 Point Structure" + print "vertex X: ", self.vertices[0] + print "vertex Y: ", self.vertices[1] + print "vertex Z: ", self.vertices[2] + print "lightnormalindex: ",self.lightnormalindex + print "" + +class md2_face: + vertex_index=[] + texture_index=[] + binary_format="<3h3h" + def __init__(self): + self.vertex_index = [ 0, 0, 0 ] + self.texture_index = [ 0, 0, 0] + def save(self, file): + temp_data=[0]*6 + #swap vertices around so they draw right + temp_data[0]=self.vertex_index[0] + temp_data[1]=self.vertex_index[2] + temp_data[2]=self.vertex_index[1] + #swap texture vertices around so they draw right + temp_data[3]=self.texture_index[0] + temp_data[4]=self.texture_index[2] + temp_data[5]=self.texture_index[1] + data=struct.pack(self.binary_format,temp_data[0],temp_data[1],temp_data[2],temp_data[3],temp_data[4],temp_data[5]) + file.write(data) + def dump (self): + print "MD2 Face Structure" + print "vertex 1 index: ", self.vertex_index[0] + print "vertex 2 index: ", self.vertex_index[1] + print "vertex 3 index: ", self.vertex_index[2] + print "texture 1 index: ", self.texture_index[0] + print "texture 2 index: ", self.texture_index[1] + print "texture 3 index: ", self.texture_index[2] + print "" + +class md2_tex_coord: + u=0 + v=0 + binary_format="<2h" + def __init__(self): + self.u=0 + self.v=0 + def save(self, file): + temp_data=[0]*2 + temp_data[0]=self.u + temp_data[1]=self.v + data=struct.pack(self.binary_format, temp_data[0], temp_data[1]) + file.write(data) + def dump (self): + print "MD2 Texture Coordinate Structure" + print "texture coordinate u: ",self.u + print "texture coordinate v: ",self.v + print "" + +class md2_GL_command: + s=0.0 + t=0.0 + vert_index=0 + binary_format="<2fi" + + def __init__(self): + self.s=0.0 + self.t=0.0 + vert_index=0 + def save(self,file): + temp_data=[0]*3 + temp_data[0]=float(self.s) + temp_data[1]=float(self.t) + temp_data[2]=self.vert_index + data=struct.pack(self.binary_format, temp_data[0],temp_data[1],temp_data[2]) + file.write(data) + def dump (self): + print "MD2 OpenGL Command" + print "s: ", self.s + print "t: ", self.t + print "Vertex Index: ", self.vert_index + print "" + +class md2_GL_cmd_list: + num=0 + cmd_list=[] + binary_format="MD2_MAX_TRIANGLES: + print "Number of triangles exceeds MD2 standard: ", face_count,">",MD2_MAX_TRIANGLES + result=Blender.Draw.PupMenu("Number of triangles exceeds MD2 standard: Continue?%t|YES|NO") + if(result==2): + return False + if vert_count>MD2_MAX_VERTICES: + print "Number of verticies exceeds MD2 standard",vert_count,">",MD2_MAX_VERTICES + result=Blender.Draw.PupMenu("Number of verticies exceeds MD2 standard: Continue?%t|YES|NO") + if(result==2): + return False + if frame_count>MD2_MAX_FRAMES: + print "Number of frames exceeds MD2 standard of",frame_count,">",MD2_MAX_FRAMES + result=Blender.Draw.PupMenu("Number of frames exceeds MD2 standard: Continue?%t|YES|NO") + if(result==2): + return False + #model is OK + return True + +###################################################### +# Fill MD2 data structure +###################################################### +def fill_md2(md2, object): + global user_frame_list + #get a Mesh, not NMesh + mesh=object.getData(False, True) + + #load up some intermediate data structures + tex_list={} + tex_count=0 + #create the vertex list from the first frame + Blender.Set("curframe", 1) + + #header information + md2.ident=844121161 + md2.version=8 + md2.num_vertices=len(mesh.verts) + md2.num_faces=len(mesh.faces) + + #get the skin information + #use the first faces' image for the texture information + mesh_image=mesh.faces[0].image + size=mesh_image.getSize() + md2.skin_width=size[0] + md2.skin_height=size[1] + md2.num_skins=1 + #add a skin node to the md2 data structure + md2.skins.append(md2_skin()) + md2.skins[0].name=Blender.sys.basename(mesh_image.getFilename()) + + #put texture information in the md2 structure + #build UV coord dictionary (prevents double entries-saves space) + for face in mesh.faces: + for i in range(0,3): + t=(face.uv[i]) + tex_key=(t[0],t[1]) + if not tex_list.has_key(tex_key): + tex_list[tex_key]=tex_count + tex_count+=1 + md2.num_tex_coords=tex_count #each vert has its own UV coord + + for this_tex in range (0, md2.num_tex_coords): + md2.tex_coords.append(md2_tex_coord()) + for coord, index in tex_list.iteritems(): + #md2.tex_coords.append(md2_tex_coord()) + md2.tex_coords[index].u=int(coord[0]*md2.skin_width) + md2.tex_coords[index].v=int((1-coord[1])*md2.skin_height) + + #put faces in the md2 structure + #for each face in the model + for this_face in range(0, md2.num_faces): + md2.faces.append(md2_face()) + for i in range(0,3): + #blender uses indexed vertexes so this works very well + md2.faces[this_face].vertex_index[i]=mesh.faces[this_face].verts[i].index + #lookup texture index in dictionary + uv_coord=(mesh.faces[this_face].uv[i]) + tex_key=(uv_coord[0],uv_coord[1]) + tex_index=tex_list[tex_key] + md2.faces[this_face].texture_index[i]=tex_index + + #compute GL commands + md2.num_GL_commands=build_GL_commands(md2) + + #get the frame data + #calculate 1 frame size + (1 vert size*num_verts) + md2.frame_size=40+(md2.num_vertices*4) #in bytes + + #get the frame list + user_frame_list=get_frame_list() + if user_frame_list=="default": + md2.num_frames=198 + else: + temp=user_frame_list[len(user_frame_list)-1] #last item + md2.num_frames=temp[2] #last frame number + + #fill in each frame with frame info and all the vertex data for that frame + for frame_counter in range(0,md2.num_frames): + #add a frame + md2.frames.append(md2_frame()) + #update the mesh objects vertex positions for the animation + Blender.Set("curframe", frame_counter) #set blender to the correct frame + mesh.getFromObject(object.name) #update the mesh to make verts current + +#each frame has a scale and transform value that gets the vertex value between 0-255 +#since the scale and transform are the same for the all the verts in the frame, we only need +#to figure this out once per frame + + #we need to start with the bounding box + bounding_box=object.getBoundBox() #uses the object, not the mesh data + #initialize with the first vertex for both min and max. X and Y are swapped for MD2 format + point=bounding_box[0] + frame_min_x=point[1] + frame_max_x=point[1] + frame_min_y=point[0] + frame_max_y=point[0] + frame_min_z=point[2] + frame_max_z=point[2] + #find min/max values + for point in bounding_box: + if frame_min_x>point[1]: frame_min_x=point[1] + if frame_max_xpoint[0]: frame_min_y=point[0] + if frame_max_ypoint[2]: frame_min_z=point[2] + if frame_max_z"# MD2 Frame Name List\n": + print "its not a valid file" + result=Blender.Draw.PupMenu("This is not a valid frame definition file-using default%t|OK") + return MD2_FRAME_NAME_LIST + else: + #read in the data + num_frames=0 + for counter in range(1, len(lines)): + current_line=lines[counter] + if current_line[0]=="#": + #found a comment + pass + else: + data=current_line.split() + frame_list.append([data[0],num_frames+1, num_frames+int(data[1])]) + num_frames+=int(data[1]) + return frame_list + else: + print "Cannot find file" + result=Blender.Draw.PupMenu("Cannot find frame definion file-using default%t|OK") + return MD2_FRAME_NAME_LIST + +###################################################### +# Tri-Strip/Tri-Fan functions +###################################################### +def find_strip_length(md2, start_tri, start_vert): + #variables shared between fan and strip functions + global used + global strip_vert + global strip_st + global strip_tris + global strip_count + + m1=m2=0 + st1=st2=0 + + used[start_tri]=2 + + last=start_tri + + strip_vert[0]=md2.faces[last].vertex_index[start_vert%3] + strip_vert[1]=md2.faces[last].vertex_index[(start_vert+1)%3] + strip_vert[2]=md2.faces[last].vertex_index[(start_vert+2)%3] + + strip_st[0]=md2.faces[last].texture_index[start_vert%3] + strip_st[1]=md2.faces[last].texture_index[(start_vert+1)%3] + strip_st[2]=md2.faces[last].texture_index[(start_vert+2)%3] + + strip_tris[0]=start_tri + strip_count=1 + + m1=md2.faces[last].vertex_index[(start_vert+2)%3] + st1=md2.faces[last].texture_index[(start_vert+2)%3] + m2=md2.faces[last].vertex_index[(start_vert+1)%3] + st2=md2.faces[last].texture_index[(start_vert+1)%3] + + #look for matching triangle + check=start_tri+1 + + for tri_counter in range(start_tri+1, md2.num_faces): + + for k in range(0,3): + if md2.faces[check].vertex_index[k]!=m1: + continue + if md2.faces[check].texture_index[k]!=st1: + continue + if md2.faces[check].vertex_index[(k+1)%3]!=m2: + continue + if md2.faces[check].texture_index[(k+1)%3]!=st2: + continue + + #if we can't use this triangle, this tri_strip is done + if (used[tri_counter]!=0): + for clear_counter in range(start_tri+1, md2.num_faces): + if used[clear_counter]==2: + used[clear_counter]=0 + return strip_count + + #new edge + if (strip_count & 1): + m2=md2.faces[check].vertex_index[(k+2)%3] + st2=md2.faces[check].texture_index[(k+2)%3] + else: + m1=md2.faces[check].vertex_index[(k+2)%3] + st1=md2.faces[check].texture_index[(k+2)%3] + + strip_vert[strip_count+2]=md2.faces[tri_counter].vertex_index[(k+2)%3] + strip_st[strip_count+2]=md2.faces[tri_counter].texture_index[(k+2)%3] + strip_tris[strip_count]=tri_counter + strip_count+=1 + + used[tri_counter]=2 + check+=1 + return strip_count + +def find_fan_length(md2, start_tri, start_vert): + #variables shared between fan and strip functions + global used + global strip_vert + global strip_st + global strip_tris + global strip_count + + m1=m2=0 + st1=st2=0 + + used[start_tri]=2 + + last=start_tri + + strip_vert[0]=md2.faces[last].vertex_index[start_vert%3] + strip_vert[1]=md2.faces[last].vertex_index[(start_vert+1)%3] + strip_vert[2]=md2.faces[last].vertex_index[(start_vert+2)%3] + + strip_st[0]=md2.faces[last].texture_index[start_vert%3] + strip_st[1]=md2.faces[last].texture_index[(start_vert+1)%3] + strip_st[2]=md2.faces[last].texture_index[(start_vert+2)%3] + + strip_tris[0]=start_tri + strip_count=1 + + m1=md2.faces[last].vertex_index[(start_vert+0)%3] + st1=md2.faces[last].texture_index[(start_vert+0)%3] + m2=md2.faces[last].vertex_index[(start_vert+2)%3] + st2=md2.faces[last].texture_index[(start_vert+2)%3] + + #look for matching triangle + check=start_tri+1 + for tri_counter in range(start_tri+1, md2.num_faces): + for k in range(0,3): + if md2.faces[check].vertex_index[k]!=m1: + continue + if md2.faces[check].texture_index[k]!=st1: + continue + if md2.faces[check].vertex_index[(k+1)%3]!=m2: + continue + if md2.faces[check].texture_index[(k+1)%3]!=st2: + continue + + #if we can't use this triangle, this tri_strip is done + if (used[tri_counter]!=0): + for clear_counter in range(start_tri+1, md2.num_faces): + if used[clear_counter]==2: + used[clear_counter]=0 + return strip_count + + #new edge + m2=md2.faces[check].vertex_index[(k+2)%3] + st2=md2.faces[check].texture_index[(k+2)%3] + + strip_vert[strip_count+2]=m2 + strip_st[strip_count+2]=st2 + strip_tris[strip_count]=tri_counter + strip_count+=1 + + used[tri_counter]=2 + check+=1 + return strip_count + + +###################################################### +# Globals for GL command list calculations +###################################################### +used=[] +strip_vert=0 +strip_st=0 +strip_tris=0 +strip_count=0 + +###################################################### +# Build GL command List +###################################################### +def build_GL_commands(md2): + #variables shared between fan and strip functions + global used + used=[0]*md2.num_faces + global strip_vert + strip_vert=[0]*128 + global strip_st + strip_st=[0]*128 + global strip_tris + strip_tris=[0]*128 + global strip_count + strip_count=0 + + #variables + num_commands=0 + start_vert=0 + fan_length=strip_length=0 + length=best_length=0 + best_type=0 + best_vert=[0]*1024 + best_st=[0]*1024 + best_tris=[0]*1024 + s=0.0 + t=0.0 + + for face_counter in range(0,md2.num_faces): + if used[face_counter]!=0: #don't evaluate a tri that's been used + #print "found a used triangle: ", face_counter + pass + else: + best_length=0 #restart the counter + #for each vertex index in this face + for start_vert in range(0,3): + fan_length=find_fan_length(md2, face_counter, start_vert) + if (fan_length>best_length): + best_type=1 + best_length=fan_length + for index in range (0, best_length+2): + best_st[index]=strip_st[index] + best_vert[index]=strip_vert[index] + for index in range(0, best_length): + best_tris[index]=strip_tris[index] + + strip_length=find_strip_length(md2, face_counter, start_vert) + if (strip_length>best_length): + best_type=0 + best_length=strip_length + for index in range (0, best_length+2): + best_st[index]=strip_st[index] + best_vert[index]=strip_vert[index] + for index in range(0, best_length): + best_tris[index]=strip_tris[index] + + #mark the tris on the best strip/fan as used + for used_counter in range (0, best_length): + used[best_tris[used_counter]]=1 + + temp_cmdlist=md2_GL_cmd_list() + #push the number of commands into the command stream + if best_type==1: + temp_cmdlist.num=best_length+2 + num_commands+=1 + else: + temp_cmdlist.num=(-(best_length+2)) + num_commands+=1 + for command_counter in range (0, best_length+2): + #emit a vertex into the reorder buffer + cmd=md2_GL_command() + index=best_st[command_counter] + #calc and put S/T coords in the structure + s=md2.tex_coords[index].u + t=md2.tex_coords[index].v + s=(s+0.5)/md2.skin_width + t=(t+0.5)/md2.skin_height + cmd.s=s + cmd.t=t + cmd.vert_index=best_vert[command_counter] + temp_cmdlist.cmd_list.append(cmd) + num_commands+=3 + md2.GL_commands.append(temp_cmdlist) + + #end of list + temp_cmdlist=md2_GL_cmd_list() + temp_cmdlist.num=0 + md2.GL_commands.append(temp_cmdlist) + num_commands+=1 + + #cleanup and return + used=best_vert=best_st=best_tris=strip_vert=strip_st=strip_tris=0 + return num_commands + +###################################################### +# Save MD2 Format +###################################################### +def save_md2(filename): + md2=md2_obj() #blank md2 object to save + + #get the object + mesh_objs = Blender.Object.GetSelected() + + #check there is a blender object selected + if len(mesh_objs)==0: + print "Fatal Error: Must select a mesh to output as MD2" + print "Found nothing" + result=Blender.Draw.PupMenu("Must select an object to export%t|OK") + return + + mesh_obj=mesh_objs[0] #this gets the first object (should be only one) + + #check if it's a mesh object + if mesh_obj.getType()!="Mesh": + print "Fatal Error: Must select a mesh to output as MD2" + print "Found: ", mesh_obj.getType() + result=Blender.Draw.PupMenu("Selected Object must be a mesh to output as MD2%t|OK") + return + + ok=validation(mesh_obj) + if ok==False: + return + + fill_md2(md2, mesh_obj) + md2.dump() + + #actually write it to disk + file=open(filename,"wb") + md2.save(file) + file.close() + + #cleanup + md2=0 + + print "Closed the file" diff --git a/release/scripts/md2_import.py b/release/scripts/md2_import.py new file mode 100644 index 00000000000..82b76d40c94 --- /dev/null +++ b/release/scripts/md2_import.py @@ -0,0 +1,571 @@ +#!BPY + +""" +Name: 'MD2 (.md2)' +Blender: 239 +Group: 'Import' +Tooltip: 'Import from Quake file format (.md2).' +""" + +__author__ = 'Bob Holcomb' +__version__ = '0.15' +__url__ = ["Bob's site, http://bane.servebeer.com", + "Support forum, http://scourage.servebeer.com/phpbb/", "blender", "elysiun"] +__email__ = ["Bob Holcomb, bob_holcomb:hotmail*com", "scripts"] +__bpydoc__ = """\ +This script imports a Quake 2 file (MD2), textures, +and animations into blender for editing. Loader is based on MD2 loader from www.gametutorials.com-Thanks DigiBen! and the md3 blender loader by PhaethonH
+ + Additional help from: Shadwolf, Skandal, Rojo, Cambo
+ Thanks Guys! +""" + +# ***** BEGIN GPL LICENSE BLOCK ***** +# +# Script copyright (C) Bob Holcomb +# +# 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 ***** +# -------------------------------------------------------------------------- + + +import Blender +from Blender import NMesh, Object, sys +from Blender.BGL import * +from Blender.Draw import * +from Blender.Window import * +from Blender.Image import * + +import struct, string +from types import * + + + + +###################################################### +# Main Body +###################################################### + +#returns the string from a null terminated string +def asciiz (s): + n = 0 + while (ord(s[n]) != 0): + n = n + 1 + return s[0:n] + + +###################################################### +# MD2 Model Constants +###################################################### +MD2_MAX_TRIANGLES=4096 +MD2_MAX_VERTICES=2048 +MD2_MAX_TEXCOORDS=2048 +MD2_MAX_FRAMES=512 +MD2_MAX_SKINS=32 +MD2_MAX_FRAMESIZE=(MD2_MAX_VERTICES * 4 + 128) + +###################################################### +# MD2 data structures +###################################################### +class md2_alias_triangle: + vertices=[] + lightnormalindex=0 + + binary_format="<3BB" #little-endian (<), 3 Unsigned char + + def __init__(self): + self.vertices=[0]*3 + self.lightnormalindex=0 + + def load(self, file): + temp_data = file.read(struct.calcsize(self.binary_format)) + data = struct.unpack(self.binary_format, temp_data) + self.vertices[0]=data[0] + self.vertices[1]=data[1] + self.vertices[2]=data[2] + self.lightnormalindex=data[3] + return self + + def dump(self): + print "MD2 Alias_Triangle Structure" + print "vertex: ", self.vertices[0] + print "vertex: ", self.vertices[1] + print "vertex: ", self.vertices[2] + print "lightnormalindex: ",self.lightnormalindex + print "" + +class md2_face: + vertex_index=[] + texture_index=[] + + binary_format="<3h3h" #little-endian (<), 3 short, 3 short + + def __init__(self): + self.vertex_index = [ 0, 0, 0 ] + self.texture_index = [ 0, 0, 0] + + def load (self, file): + temp_data=file.read(struct.calcsize(self.binary_format)) + data=struct.unpack(self.binary_format, temp_data) + self.vertex_index[0]=data[0] + self.vertex_index[1]=data[1] + self.vertex_index[2]=data[2] + self.texture_index[0]=data[3] + self.texture_index[1]=data[4] + self.texture_index[2]=data[5] + return self + + def dump (self): + print "MD2 Face Structure" + print "vertex index: ", self.vertex_index[0] + print "vertex index: ", self.vertex_index[1] + print "vertex index: ", self.vertex_index[2] + print "texture index: ", self.texture_index[0] + print "texture index: ", self.texture_index[1] + print "texture index: ", self.texture_index[2] + print "" + +class md2_tex_coord: + u=0 + v=0 + + binary_format="<2h" #little-endian (<), 2 unsigned short + + def __init__(self): + self.u=0 + self.v=0 + + def load (self, file): + temp_data=file.read(struct.calcsize(self.binary_format)) + data=struct.unpack(self.binary_format, temp_data) + self.u=data[0] + self.v=data[1] + return self + + def dump (self): + print "MD2 Texture Coordinate Structure" + print "texture coordinate u: ",self.u + print "texture coordinate v: ",self.v + print "" + + +class md2_skin: + name="" + + binary_format="<64s" #little-endian (<), char[64] + + def __init__(self): + self.name="" + + def load (self, file): + temp_data=file.read(struct.calcsize(self.binary_format)) + data=struct.unpack(self.binary_format, temp_data) + self.name=asciiz(data[0]) + return self + + def dump (self): + print "MD2 Skin" + print "skin name: ",self.name + print "" + +class md2_alias_frame: + scale=[] + translate=[] + name=[] + vertices=[] + + binary_format="<3f3f16s" #little-endian (<), 3 float, 3 float char[16] + #did not add the "3bb" to the end of the binary format + #because the alias_vertices will be read in through + #thier own loader + + def __init__(self): + self.scale=[0.0]*3 + self.translate=[0.0]*3 + self.name="" + self.vertices=[] + + + def load (self, file): + temp_data=file.read(struct.calcsize(self.binary_format)) + data=struct.unpack(self.binary_format, temp_data) + self.scale[0]=data[0] + self.scale[1]=data[1] + self.scale[2]=data[2] + self.translate[0]=data[3] + self.translate[1]=data[4] + self.translate[2]=data[5] + self.name=asciiz(data[6]) + return self + + def dump (self): + print "MD2 Alias Frame" + print "scale x: ",self.scale[0] + print "scale y: ",self.scale[1] + print "scale z: ",self.scale[2] + print "translate x: ",self.translate[0] + print "translate y: ",self.translate[1] + print "translate z: ",self.translate[2] + print "name: ",self.name + print "" + +class md2_obj: + #Header Structure + ident=0 #int 0 This is used to identify the file + version=0 #int 1 The version number of the file (Must be 8) + skin_width=0 #int 2 The skin width in pixels + skin_height=0 #int 3 The skin height in pixels + frame_size=0 #int 4 The size in bytes the frames are + num_skins=0 #int 5 The number of skins associated with the model + num_vertices=0 #int 6 The number of vertices (constant for each frame) + num_tex_coords=0 #int 7 The number of texture coordinates + num_faces=0 #int 8 The number of faces (polygons) + num_GL_commands=0 #int 9 The number of gl commands + num_frames=0 #int 10 The number of animation frames + offset_skins=0 #int 11 The offset in the file for the skin data + offset_tex_coords=0 #int 12 The offset in the file for the texture data + offset_faces=0 #int 13 The offset in the file for the face data + offset_frames=0 #int 14 The offset in the file for the frames data + offset_GL_commands=0#int 15 The offset in the file for the gl commands data + offset_end=0 #int 16 The end of the file offset + + binary_format="<17i" #little-endian (<), 17 integers (17i) + + #md2 data objects + tex_coords=[] + faces=[] + frames=[] + skins=[] + + def __init__ (self): + self.tex_coords=[] + self.faces=[] + self.frames=[] + self.skins=[] + + + def load (self, file): + temp_data = file.read(struct.calcsize(self.binary_format)) + data = struct.unpack(self.binary_format, temp_data) + + self.ident=data[0] + self.version=data[1] + + if (self.ident!=844121161 or self.version!=8): + print "Not a valid MD2 file" + Exit() + + self.skin_width=data[2] + self.skin_height=data[3] + self.frame_size=data[4] + + #make the # of skin objects for model + self.num_skins=data[5] + for i in xrange(0,self.num_skins): + self.skins.append(md2_skin()) + + self.num_vertices=data[6] + + #make the # of texture coordinates for model + self.num_tex_coords=data[7] + for i in xrange(0,self.num_tex_coords): + self.tex_coords.append(md2_tex_coord()) + + #make the # of triangle faces for model + self.num_faces=data[8] + for i in xrange(0,self.num_faces): + self.faces.append(md2_face()) + + self.num_GL_commands=data[9] + + #make the # of frames for the model + self.num_frames=data[10] + for i in xrange(0,self.num_frames): + self.frames.append(md2_alias_frame()) + #make the # of vertices for each frame + for j in xrange(0,self.num_vertices): + self.frames[i].vertices.append(md2_alias_triangle()) + + self.offset_skins=data[11] + self.offset_tex_coords=data[12] + self.offset_faces=data[13] + self.offset_frames=data[14] + self.offset_GL_commands=data[15] + + #load the skin info + file.seek(self.offset_skins,0) + for i in xrange(0, self.num_skins): + self.skins[i].load(file) + #self.skins[i].dump() + + #load the texture coordinates + file.seek(self.offset_tex_coords,0) + for i in xrange(0, self.num_tex_coords): + self.tex_coords[i].load(file) + #self.tex_coords[i].dump() + + #load the face info + file.seek(self.offset_faces,0) + for i in xrange(0, self.num_faces): + self.faces[i].load(file) + #self.faces[i].dump() + + #load the frames + file.seek(self.offset_frames,0) + for i in xrange(0, self.num_frames): + self.frames[i].load(file) + #self.frames[i].dump() + for j in xrange(0,self.num_vertices): + self.frames[i].vertices[j].load(file) + #self.frames[i].vertices[j].dump() + return self + + def dump (self): + print "Header Information" + print "ident: ", self.ident + print "version: ", self.version + print "skin width: ", self.skin_width + print "skin height: ", self.skin_height + print "frame size: ", self.frame_size + print "number of skins: ", self.num_skins + print "number of texture coordinates: ", self.num_tex_coords + print "number of faces: ", self.num_faces + print "number of frames: ", self.num_frames + print "number of vertices: ", self.num_vertices + print "offset skins: ", self.offset_skins + print "offset texture coordinates: ", self.offset_tex_coords + print "offset faces: ", self.offset_faces + print "offset frames: ",self.offset_frames + print "" + +###################################################### +# Import functions +###################################################### +def load_textures(md2, texture_filename): + #did the user specify a texture they wanted to use? + if (texture_filename!="texture"): + if (Blender.sys.exists(texture_filename)): + mesh_image=Blender.Image.Load(texture_filename) + return mesh_image + else: + result=Blender.Draw.PupMenu("Cannot find texture: "+texture_filename+"-Continue?%t|OK") + if(result==1): + return -1 + #does the model have textures specified with it? + if int(md2.num_skins) > 0: + for i in xrange(0,md2.num_skins): + #md2.skins[i].dump() + if (Blender.sys.exists(md2.skins[i].name)): + mesh_image=Blender.Image.Load(md2.skins[i].name) + else: + result=Blender.Draw.PupMenu("Cannot find texture: "+md2.skins[i].name+"-Continue?%t|OK") + if(result==1): + return -1 + return mesh_image + else: + result=Blender.Draw.PupMenu("There will be no Texutre"+"-Continue?%t|OK") + if(result==1): + return -1 + + +def animate_md2(md2, mesh_obj): + ######### Animate the verts through keyframe animation + mesh=mesh_obj.getData() + for i in xrange(1, md2.num_frames): + #update the vertices + for j in xrange(0,md2.num_vertices): + x=(md2.frames[i].scale[0]*md2.frames[i].vertices[j].vertices[0]+md2.frames[i].translate[0])*g_scale.val + y=(md2.frames[i].scale[1]*md2.frames[i].vertices[j].vertices[1]+md2.frames[i].translate[1])*g_scale.val + z=(md2.frames[i].scale[2]*md2.frames[i].vertices[j].vertices[2]+md2.frames[i].translate[2])*g_scale.val + + #put the vertex in the right spot + mesh.verts[j].co[0]=y + mesh.verts[j].co[1]=-x + mesh.verts[j].co[2]=z + + mesh.update() + NMesh.PutRaw(mesh, mesh_obj.name) + #absolute keys, need to figure out how to get them working around the 100 frame limitation + mesh.insertKey(i,"absolute") + + #not really necissary, but I like playing with the frame counter + Blender.Set("curframe", i) + + +def load_md2 (md2_filename, texture_filename): + #read the file in + file=open(md2_filename,"rb") + md2=md2_obj() + md2.load(file) + #md2.dump() + file.close() + + ######### Creates a new mesh + mesh = NMesh.New() + + uv_coord=[] + uv_list=[] + + #load the textures to use later + #-1 if there is no texture to load + mesh_image=load_textures(md2, texture_filename) + + ######### Make the verts + DrawProgressBar(0.25,"Loading Vertex Data") + for i in xrange(0,md2.num_vertices): + #use the first frame for the mesh vertices + x=(md2.frames[0].scale[0]*md2.frames[0].vertices[i].vertices[0]+md2.frames[0].translate[0])*g_scale.val + y=(md2.frames[0].scale[1]*md2.frames[0].vertices[i].vertices[1]+md2.frames[0].translate[1])*g_scale.val + z=(md2.frames[0].scale[2]*md2.frames[0].vertices[i].vertices[2]+md2.frames[0].translate[2])*g_scale.val + vertex=NMesh.Vert(y,-x,z) + mesh.verts.append(vertex) + + ######## Make the UV list + DrawProgressBar(0.50,"Loading UV Data") + mesh.hasFaceUV(1) #turn on face UV coordinates for this mesh + for i in xrange(0, md2.num_tex_coords): + u=(float(md2.tex_coords[i].u)/float(md2.skin_width)) + v=(float(md2.tex_coords[i].v)/float(md2.skin_height)) + #for some reason quake2 texture maps are upside down, flip that + uv_coord=(u,1-v) + uv_list.append(uv_coord) + + ######### Make the faces + DrawProgressBar(0.75,"Loading Face Data") + for i in xrange(0,md2.num_faces): + face = NMesh.Face() + #draw the triangles in reverse order so they show up + face.v.append(mesh.verts[md2.faces[i].vertex_index[0]]) + face.v.append(mesh.verts[md2.faces[i].vertex_index[2]]) + face.v.append(mesh.verts[md2.faces[i].vertex_index[1]]) + #append the list of UV + #ditto in reverse order with the texture verts + face.uv.append(uv_list[md2.faces[i].texture_index[0]]) + face.uv.append(uv_list[md2.faces[i].texture_index[2]]) + face.uv.append(uv_list[md2.faces[i].texture_index[1]]) + + #set the texture that this face uses if it has one + if (mesh_image!=-1): + face.image=mesh_image + + #add the face + mesh.faces.append(face) + + mesh_obj=NMesh.PutRaw(mesh) + animate_md2(md2, mesh_obj) + DrawProgressBar(0.999,"Loading Animation Data") + + #locate the Object containing the mesh at the cursor location + cursor_pos=Blender.Window.GetCursorPos() + mesh_obj.setLocation(float(cursor_pos[0]),float(cursor_pos[1]),float(cursor_pos[2])) + DrawProgressBar (1.0, "Finished") + +#*********************************************** +# MAIN +#*********************************************** + +# Import globals +g_md2_filename=Create("model") +g_texture_filename=Create("texture") + +g_filename_search=Create("model") +g_texture_search=Create("texture") + +#Globals +g_scale=Create(1.0) + +# Events +EVENT_NOEVENT=1 +EVENT_LOAD_MD2=2 +EVENT_CHOOSE_FILENAME=3 +EVENT_CHOOSE_TEXTURE=4 +EVENT_SAVE_MD2=5 +EVENT_EXIT=100 + +###################################################### +# Callbacks for Window functions +###################################################### +def filename_callback(input_filename): + global g_md2_filename + g_md2_filename.val=input_filename + +def texture_callback(input_texture): + global g_texture_filename + g_texture_filename.val=input_texture + +###################################################### +# GUI Loader +###################################################### + + +def draw_gui(): + global g_scale + global g_md2_filename + global g_texture_filename + global EVENT_NOEVENT,EVENT_LOAD_MD2,EVENT_CHOOSE_FILENAME,EVENT_CHOOSE_TEXTURE,EVENT_EXIT + + ########## Titles + glClear(GL_COLOR_BUFFER_BIT) + glRasterPos2d(8, 125) + Text("MD2 loader") + + ######### Parameters GUI Buttons + g_md2_filename = String("MD2 file to load: ", EVENT_NOEVENT, 10, 55, 210, 18, + g_md2_filename.val, 255, "MD2 file to load") + ########## MD2 File Search Button + Button("Search",EVENT_CHOOSE_FILENAME,220,55,80,18) + + g_texture_filename = String("Texture file to load: ", EVENT_NOEVENT, 10, 35, 210, 18, + g_texture_filename.val, 255, "Texture file to load-overrides MD2 file") + ########## Texture Search Button + Button("Search",EVENT_CHOOSE_TEXTURE,220,35,80,18) + + ########## Scale slider-default is 1/8 which is a good scale for md2->blender + g_scale= Slider("Scale Factor: ", EVENT_NOEVENT, 10, 75, 210, 18, + 1.0, 0.001, 10.0, 1, "Scale factor for obj Model"); + + ######### Draw and Exit Buttons + Button("Load",EVENT_LOAD_MD2 , 10, 10, 80, 18) + Button("Exit",EVENT_EXIT , 170, 10, 80, 18) + +def event(evt, val): + if (evt == QKEY and not val): + Blender.Draw.Exit() + +def bevent(evt): + global g_md2_filename + global g_texture_filename + global EVENT_NOEVENT,EVENT_LOAD_MD2,EVENT_SAVE_MD2,EVENT_EXIT + + ######### Manages GUI events + if (evt==EVENT_EXIT): + Blender.Draw.Exit() + elif (evt==EVENT_CHOOSE_FILENAME): + FileSelector(filename_callback, "MD2 File Selection") + elif (evt==EVENT_CHOOSE_TEXTURE): + FileSelector(texture_callback, "Texture Selection") + elif (evt==EVENT_LOAD_MD2): + if (g_md2_filename.val == "model"): + Blender.Draw.Exit() + return + else: + load_md2(g_md2_filename.val, g_texture_filename.val) + Blender.Redraw() + Blender.Draw.Exit() + return + + +Register(draw_gui, event, bevent)