diff options
author | Willian Padovani Germano <wpgermano@gmail.com> | 2005-12-15 04:42:45 +0300 |
---|---|---|
committer | Willian Padovani Germano <wpgermano@gmail.com> | 2005-12-15 04:42:45 +0300 |
commit | b662fcb694ecdf1312c45b5d5cd8c0f07d955d13 (patch) | |
tree | a469e3a9b444f496e855e9701d76782ac80d9613 /release | |
parent | e64008e2c7b780db1c8e8a2fabc2f5cc1a0f8481 (diff) |
Scripts:
- added import and export scripts for 3ds and md2 (Quake 2) models
contributed by author Bob Holcomb. Thanks!
- updated ac3d importer to be more forgiving and exporter to get rid of
deprecated call.
More additions and possibly fixes to come.
Note: we're forming a group to take care of importers and exporters and
improve the situation as a whole in Blender. Discussions should happen
at the bf-scripts-dev mailing list:
http://projects.blender.org/mailman/listinfo/bf-scripts-dev
Thanks Tom Musgrove (LetterRip) for helping a lot with scripts.
Diffstat (limited to 'release')
-rw-r--r-- | release/scripts/3ds_export.py | 673 | ||||
-rw-r--r-- | release/scripts/3ds_import.py | 548 | ||||
-rw-r--r-- | release/scripts/ac3d_export.py | 3 | ||||
-rw-r--r-- | release/scripts/ac3d_import.py | 12 | ||||
-rw-r--r-- | release/scripts/md2_export.py | 1016 | ||||
-rw-r--r-- | release/scripts/md2_import.py | 571 |
6 files changed, 2817 insertions, 6 deletions
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(\ + "<HI",\ + self.ID,\ + self.size) + file.write(data) + + def dump(self): + print "ID: ", self.ID + print "ID in hex: ", hex(self.ID) + print "size: ", self.size + + + +#may want to add light, camera, keyframe chunks. +class vert_chunk(chunk): + verts=[] + + def __init__(self): + self.verts=[] + self.ID=OBJECT_VERTICES + + def get_size(self): + chunk.get_size(self) + temp_size=2 #for the number of verts short + temp_size += 12 * len(self.verts) #3 floats x 4 bytes each + self.size+=temp_size + #~ print "vert_chunk size: ", self.size + return self.size + + def write(self, file): + chunk.write(self, file) + #write header + data=struct.pack("<H", len(self.verts)) + file.write(data) + #write verts + for vert in self.verts: + data=struct.pack("<3f",vert[0],vert[1], vert[2]) + file.write(data) + +class obj_material_chunk(chunk): + name="" + faces=[] + + def __init__(self): + self.name="" + self.faces=[] + self.ID=OBJECT_MATERIAL + + def get_size(self): + chunk.get_size(self) + temp_size=(len(self.name)+1) + temp_size+=2 + for face in self.faces: + temp_size+=2 + self.size+=temp_size + #~ print "obj material chunk size: ", self.size + return self.size + + def write(self, file): + chunk.write(self, file) + #write name + name_length=len(self.name)+1 + binary_format="<"+str(name_length)+"s" + data=struct.pack(binary_format, self.name) + file.write(data) + binary_format="<H" + #~ print "Nr of faces: ", len(self.faces) + data=struct.pack(binary_format, len(self.faces)) + file.write(data) + for face in self.faces: + data=struct.pack(binary_format, face) + file.write(data) + +class face_chunk(chunk): + faces=[] + num_faces=0 + m_chunks=[] + + def __init__(self): + self.faces=[] + self.ID=OBJECT_FACES + self.num_faces=0 + self.m_chunks=[] + + def get_size(self): + chunk.get_size(self) + temp_size = 2 #num faces info + temp_size += 8 * len(self.faces) #4 short ints x 2 bytes each + for m in self.m_chunks: + temp_size+=m.get_size() + self.size += temp_size + #~ print "face_chunk size: ", self.size + return self.size + + def write(self, file): + chunk.write(self, file) + data=struct.pack("<H", len(self.faces)) + file.write(data) + #write faces + for face in self.faces: + data=struct.pack("<4H", face[0],face[1], face[2], 0) # The last zero is only used by 3d studio + file.write(data) + #write materials + for m in self.m_chunks: + m.write(file) + +class uv_chunk(chunk): + uv=[] + num_uv=0 + + def __init__(self): + self.uv=[] + self.ID=OBJECT_UV + self.num_uv=0 + + def get_size(self): + chunk.get_size(self) + temp_size=2 #for num UV + for this_uv in self.uv: + temp_size+=8 #2 floats at 4 bytes each + self.size+=temp_size + #~ print "uv chunk size: ", self.size + return self.size + + def write(self, file): + chunk.write(self, file) + + #write header + data=struct.pack("<H", len(self.uv)) + file.write(data) + + #write verts + for this_uv in self.uv: + data=struct.pack("<2f", this_uv[0], this_uv[1]) + file.write(data) + +class mesh_chunk(chunk): + v_chunk=vert_chunk() + f_chunk=face_chunk() + uv_chunk=uv_chunk() + + def __init__(self): + self.v_chunk=vert_chunk() + self.f_chunk=face_chunk() + self.uv_chunk=uv_chunk() + self.ID=OBJECT_MESH + + def get_size(self): + chunk.get_size(self) + temp_size=self.v_chunk.get_size() + temp_size+=self.f_chunk.get_size() + temp_size+=self.uv_chunk.get_size() + self.size+=temp_size + #~ print "object mesh chunk size: ", self.size + return self.size + + def write(self, file): + chunk.write(self, file) + #write stuff + self.v_chunk.write(file) + self.f_chunk.write(file) + self.uv_chunk.write(file) + +class object_chunk(chunk): + name="" + mesh_chunks=[] + + def __init__(self): + self.name="" + self.mesh_chunks=[] + self.ID=OBJECT + + def get_size(self): + chunk.get_size(self) + temp_size=len(self.name)+1 #+1 for null character + for mesh in self.mesh_chunks: + temp_size+=mesh.get_size() + self.size+=temp_size + #~ print "object chunk size: ", self.size + return self.size + + def write(self, file): + chunk.write(self, file) + #write name + + binary_format = "<%ds" % (len(self.name)+1) + data=struct.pack(binary_format, self.name) + file.write(data) + #write stuff + for mesh in self.mesh_chunks: + mesh.write(file) + +class object_info_chunk(chunk): + obj_chunks=[] + mat_chunks=[] + + def __init__(self): + self.obj_chunks=[] + self.mat_chunks=[] + self.ID=OBJECTINFO + + def get_size(self): + chunk.get_size(self) + temp_size=0 + for mat in self.mat_chunks: + temp_size+=mat.get_size() + for obj in self.obj_chunks: + temp_size+=obj.get_size() + self.size+=temp_size + #~ print "object info size: ", self.size + return self.size + + def write(self, file): + chunk.write(self, file) + #write all the materials + for mat in self.mat_chunks: + mat.write(file) + #write all the objects + for obj in self.obj_chunks: + obj.write(file) + + + +class version_chunk(chunk): + version=3 + + def __init__(self): + self.ID=VERSION + self.version=3 #that the document that I'm using + + def get_size(self): + chunk.get_size(self) + self.size += 4 #bytes for the version info + #~ print "version chunk size: ", self.size + return self.size + + def write(self, file): + chunk.write(self, file) + #write header and version + data=struct.pack("<I", self.version) + file.write(data) + +class rgb_chunk(chunk): + col=[] + + def __init__(self): + self.col=[] + + def get_size(self): + chunk.get_size(self) + self.size+=3 #color size + #~ print "rgb chunk size: ", self.size + return self.size + + def write(self, file): + chunk.write(self, file) + #write colors + for c in self.col: + file.write( struct.pack("<c", chr(int(255*c))) ) + + +class rgb1_chunk(rgb_chunk): + + def __init__(self): + self.ID=RGB1 + +class rgb2_chunk(rgb_chunk): + + def __init__(self): + self.ID=RGB2 + +class material_ambient_chunk(chunk): + col1=None + col2=None + + def __init__(self): + self.ID=MATAMBIENT + self.col1=rgb1_chunk() + self.col2=rgb2_chunk() + + def get_size(self): + chunk.get_size(self) + temp_size=self.col1.get_size() + temp_size+=self.col2.get_size() + self.size+=temp_size + #~ print "material ambient size: ", self.size + return self.size + + def write(self, file): + chunk.write(self, file) + #write colors + self.col1.write(file) + self.col2.write(file) + +class material_diffuse_chunk(chunk): + col1=None + col2=None + + def __init__(self): + self.ID=MATDIFFUSE + self.col1=rgb1_chunk() + self.col2=rgb2_chunk() + + def get_size(self): + chunk.get_size(self) + temp_size=self.col1.get_size() + temp_size+=self.col2.get_size() + self.size+=temp_size + #~ print "material diffuse size: ", self.size + return self.size + + def write(self, file): + chunk.write(self, file) + #write colors + self.col1.write(file) + self.col2.write(file) + +class material_specular_chunk(chunk): + col1=None + col2=None + + def __init__(self): + self.ID=MATSPECULAR + self.col1=rgb1_chunk() + self.col2=rgb2_chunk() + + def get_size(self): + chunk.get_size(self) + temp_size=self.col1.get_size() + temp_size+=self.col2.get_size() + self.size+=temp_size + #~ print "material specular size: ", self.size + return self.size + + def write(self, file): + chunk.write(self, file) + #write colors + self.col1.write(file) + self.col2.write(file) + +class material_name_chunk(chunk): + name="" + + def __init__(self): + self.ID=MATNAME + self.name="" + + def get_size(self): + chunk.get_size(self) + temp_size=(len(self.name)+1) + self.size+=temp_size + #~ print "material name size: ", self.size + return self.size + + def write(self, file): + chunk.write(self, file) + #write name + name_length=len(self.name)+1 + binary_format="<"+str(name_length)+"s" + data=struct.pack(binary_format, self.name) + file.write(data) + +class material_chunk(chunk): + matname_chunk=None + matambient_chunk=None + matdiffuse_chunk=None + matspecular_chunk=None + + def __init__(self): + self.ID=MATERIAL + self.matname_chunk=material_name_chunk() + self.matambient_chunk=material_ambient_chunk() + self.matdiffuse_chunk=material_diffuse_chunk() + self.matspecular_chunk=material_specular_chunk() + + def get_size(self): + chunk.get_size(self) + temp_size=self.matname_chunk.get_size() + temp_size+=self.matambient_chunk.get_size() + temp_size+=self.matdiffuse_chunk.get_size() + temp_size+=self.matspecular_chunk.get_size() + self.size+=temp_size + #~ print "material chunk size: ", self.size + return self.size + + def write(self, file): + chunk.write(self, file) + #write name chunk + self.matname_chunk.write(file) + #write material colors + self.matambient_chunk.write(file) + self.matdiffuse_chunk.write(file) + self.matspecular_chunk.write(file) + +class primary_chunk(chunk): + version=None + obj_info=None + + def __init__(self): + self.version=version_chunk() + self.obj_info=object_info_chunk() + self.ID=PRIMARY + + def get_size(self): + chunk.get_size(self) + temp_size=self.version.get_size() + temp_size+=self.obj_info.get_size() + self.size+=temp_size + #~ print "primary chunk size: ", self.size + return self.size + + def write(self, file): + chunk.write(self, file) + #write version chunk + self.version.write(file) + #write object_info chunk + self.obj_info.write(file) + +def read_chunk(file, chunk): + chunk.ID, chunk.size = \ + struct.unpack(\ + chunk.binary_format, \ + file.read(struct.calcsize(chunk.binary_format)) ) + +def read_string(file): + s="" + index=0 + + #read in the characters till we get a null character + data=struct.unpack("c", file.read(struct.calcsize("c"))) + s=s+(data[0]) + #print "string: ",s + while(ord(s[index])!=0): + index+=1 + data=struct.unpack("c", file.read(struct.calcsize("c"))) + s=s+(data[0]) + #print "string: ",s + return str(s) + +###################################################### +# EXPORT +###################################################### +def save_3ds(filename): + # Time the export + time1 = Blender.sys.time() + + exported_materials = {} + + #fill the chunks full of data + primary=primary_chunk() + #get all the objects in this scene + object_list = [ ob for ob in Blender.Object.GetSelected() if ob.getType() == 'Mesh' ] + #fill up the data structures with objects + for obj in object_list: + #create a new object chunk + primary.obj_info.obj_chunks.append(object_chunk()) + #get the mesh data + blender_mesh = obj.getData() + blender_mesh.transform(obj.getMatrix()) + #set the object name + primary.obj_info.obj_chunks[len(primary.obj_info.obj_chunks)-1].name=obj.getName() + + matrix = obj.getMatrix() + + #make a new mesh chunk object + mesh=mesh_chunk() + + mesh.v_chunk.verts = blender_mesh.verts + + dummy = None # just incase... + + for m in blender_mesh.materials: + mesh.f_chunk.m_chunks.append(obj_material_chunk()) + mesh.f_chunk.m_chunks[len(mesh.f_chunk.m_chunks)-1].name = m.name + + # materials should only be exported once + try: + dummy = exported_materials[m.name] + + + except KeyError: + material = material_chunk() + material.matname_chunk.name=m.name + material.matambient_chunk.col1.col = m.mirCol + material.matambient_chunk.col2.col = m.mirCol + material.matdiffuse_chunk.col1.col = m.rgbCol + material.matdiffuse_chunk.col2.col = m.rgbCol + material.matspecular_chunk.col1.col = m.specCol + material.matspecular_chunk.col2.col = m.specCol + + primary.obj_info.mat_chunks.append(material) + + exported_materials[m.name] = None + + del dummy # unpolute the namespace + + valid_faces = [f for f in blender_mesh.faces if len(f) > 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:<br> +0.81a (fork- not 0.9) Campbell Barton 2005-06-08<br> +- Simplified import code<br> +- Never overwrite data<br> +- Faster list handling<br> +- Leaves import selected<br> + +0.81 Damien McGinnes 2005-01-09<br> +- handle missing images better<br> + +0.8 Damien McGinnes 2005-01-08<br> +- copies sticky UV coords to face ones<br> +- handles images better<br> +- 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="<HI" + + def __init__(self): + self.ID=0 + self.length=0 + self.bytes_read=0 + + def dump(self): + print "ID: ", self.ID + print "ID in hex: ", hex(self.ID) + print "length: ", self.length + print "bytes_read: ", self.bytes_read + + +def read_chunk(file, chunk): + temp_data=file.read(struct.calcsize(chunk.binary_format)) + data=struct.unpack(chunk.binary_format, temp_data) + chunk.ID=data[0] + chunk.length=data[1] + #update the bytes read function + chunk.bytes_read=6 + + #if debugging + #chunk.dump() + +def read_string(file): + s="" + index=0 + #print "reading a string" + #read in the characters till we get a null character + temp_data=file.read(1) + data=struct.unpack("c", temp_data) + s=s+(data[0]) + #print "string: ",s + while(ord(s[index])!=0): + index+=1 + temp_data=file.read(1) + data=struct.unpack("c", temp_data) + s=s+(data[0]) + #print "string: ",s + + #remove the null character from the string + the_string=s[:-1] + return str(the_string) + +###################################################### +# IMPORT +###################################################### +def process_next_object_chunk(file, previous_chunk): + new_chunk=chunk() + temp_chunk=chunk() + + while (previous_chunk.bytes_read<previous_chunk.length): + #read the next chunk + read_chunk(file, new_chunk) + + +def process_next_chunk(file, previous_chunk, new_object_list): + contextObName = None + #contextLamp = None + contextMaterial = None + contextMatrix = Blender.Mathutils.Matrix(); contextMatrix.identity() + contextMesh = None + + TEXDICT={} + MATDICT={} + + objectList = [] # Keep a list of imported objects. + + # Localspace variable names, faster. + STRUCT_SIZE_1CHAR = struct.calcsize("c") + STRUCT_SIZE_2FLOAT = struct.calcsize("2f") + STRUCT_SIZE_3FLOAT = struct.calcsize("3f") + STRUCT_SIZE_UNSIGNED_SHORT = struct.calcsize("H") + STRUCT_SIZE_4UNSIGNED_SHORT = struct.calcsize("4H") + STRUCT_SIZE_4x3MAT = struct.calcsize("ffffffffffff") + + + def putContextMesh(myContextMesh): + INV_MAT = Blender.Mathutils.CopyMat(contextMatrix) + INV_MAT.invert() + contextMesh.transform(INV_MAT) + objectList.append(NMesh.PutRaw(contextMesh)) + objectList[-1].name = contextObName + objectList[-1].setMatrix(contextMatrix) + + + #a spare chunk + new_chunk=chunk() + temp_chunk=chunk() + + #loop through all the data for this chunk (previous chunk) and see what it is + while (previous_chunk.bytes_read<previous_chunk.length): + #read the next chunk + #print "reading a chunk" + read_chunk(file, new_chunk) + + #is it a Version chunk? + if (new_chunk.ID==VERSION): + #print "found a VERSION chunk" + #read in the version of the file + #it's an unsigned short (H) + temp_data=file.read(struct.calcsize("I")) + data=struct.unpack("I", temp_data) + version=data[0] + new_chunk.bytes_read+=4 #read the 4 bytes for the version number + #this loader works with version 3 and below, but may not with 4 and above + if (version>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<br> + 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="<i" + + def __init__(self): + self.num=0 + self.cmd_list=[] + + def save(self,file): + data=struct.pack(self.binary_format, self.num) + file.write(data) + for cmd in self.cmd_list: + cmd.save(file) + def dump(self): + print "MD2 OpenGL Command List" + print "number: ", self.num + for cmd in self.cmd_list: + cmd.dump() + print "" + +class md2_skin: + name="" + binary_format="<64s" + def __init__(self): + self.name="" + def save(self, file): + temp_data=self.name + data=struct.pack(self.binary_format, temp_data) + file.write(data) + def dump (self): + print "MD2 Skin" + print "skin name: ",self.name + print "" + +class md2_frame: + scale=[] + translate=[] + name=[] + vertices=[] + binary_format="<3f3f16s" + + def __init__(self): + self.scale=[0.0]*3 + self.translate=[0.0]*3 + self.name="" + self.vertices=[] + def save(self, file): + temp_data=[0]*7 + temp_data[0]=float(self.scale[0]) + temp_data[1]=float(self.scale[1]) + temp_data[2]=float(self.scale[2]) + temp_data[3]=float(self.translate[0]) + temp_data[4]=float(self.translate[1]) + temp_data[5]=float(self.translate[2]) + temp_data[6]=self.name + data=struct.pack(self.binary_format, temp_data[0],temp_data[1],temp_data[2],temp_data[3],temp_data[4],temp_data[5],temp_data[6]) + file.write(data) + def dump (self): + print "MD2 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=[] + GL_commands=[] + + def __init__ (self): + self.tex_coords=[] + self.faces=[] + self.frames=[] + self.skins=[] + def save(self, file): + temp_data=[0]*17 + temp_data[0]=self.ident + temp_data[1]=self.version + temp_data[2]=self.skin_width + temp_data[3]=self.skin_height + temp_data[4]=self.frame_size + temp_data[5]=self.num_skins + temp_data[6]=self.num_vertices + temp_data[7]=self.num_tex_coords + temp_data[8]=self.num_faces + temp_data[9]=self.num_GL_commands + temp_data[10]=self.num_frames + temp_data[11]=self.offset_skins + temp_data[12]=self.offset_tex_coords + temp_data[13]=self.offset_faces + temp_data[14]=self.offset_frames + temp_data[15]=self.offset_GL_commands + temp_data[16]=self.offset_end + data=struct.pack(self.binary_format, temp_data[0],temp_data[1],temp_data[2],temp_data[3],temp_data[4],temp_data[5],temp_data[6],temp_data[7],temp_data[8],temp_data[9],temp_data[10],temp_data[11],temp_data[12],temp_data[13],temp_data[14],temp_data[15],temp_data[16]) + file.write(data) + #write the skin data + for skin in self.skins: + skin.save(file) + #save the texture coordinates + for tex_coord in self.tex_coords: + tex_coord.save(file) + #save the face info + for face in self.faces: + face.save(file) + #save the frames + for frame in self.frames: + frame.save(file) + for vert in frame.vertices: + vert.save(file) + #save the GL command List + for cmd in self.GL_commands: + cmd.save(file) + 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 "number of GL commands: ",self.num_GL_commands + 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 "offset GL Commands: ",self.offset_GL_commands + print "offset end: ",self.offset_end + print "" + +###################################################### +# Validation +###################################################### +def validation(object): + global user_frame_list + + #get access to the mesh data + mesh=object.getData(False, True) #get the object (not just name) and the Mesh, not NMesh + + #check it's composed of only tri's + result=0 + for face in mesh.faces: + if len(face.verts)!=3: + #select the face for future triangulation + face.sel=1 + if result==0: #first time we have this problem, don't pop-up a window every time it finds a quad + print "Model not made entirely of triangles" + result=Blender.Draw.PupMenu("Model not made entirely out of Triangles-Convert?%t|YES|NO") + if result==1: + mesh.quadToTriangle(0) #use closest verticies in breaking a quad + elif result==2: + return False #user will fix (I guess) + + #check it has UV coordinates + if mesh.vertexUV==True: + print "Vertex UV not supported" + result=Blender.Draw.PupMenu("Vertex UV not suppored-Use Sticky UV%t|OK") + return False + + elif mesh.faceUV==True: + for face in mesh.faces: + if(len(face.uv)==3): + pass + else: + print "Models vertices do not all have UV" + result=Blender.Draw.PupMenu("Models vertices do not all have UV%t|OK") + return False + + else: + print "Model does not have UV (face or vertex)" + result=Blender.Draw.PupMenu("Model does not have UV (face or vertex)%t|OK") + return False + + #check it has only 1 associated texture map + last_face="" + last_face=mesh.faces[0].image + if last_face=="": + print "Model does not have a texture Map" + result=Blender.Draw.PupMenu("Model does not have a texture Map%t|OK") + return False + + for face in mesh.faces: + mesh_image=face.image + if not mesh_image: + print "Model has a face without a texture Map" + result=Blender.Draw.PupMenu("Model has a face without a texture Map%t|OK") + return False + if mesh_image!=last_face: + print "Model has more than 1 texture map assigned" + result=Blender.Draw.PupMenu("Model has more than 1 texture map assigned%t|OK") + return False + + size=mesh_image.getSize() + #is this really what the user wants + if (size[0]!=256 or size[1]!=256): + print "Texture map size is non-standard (not 256x256), it is: ",size[0],"x",size[1] + result=Blender.Draw.PupMenu("Texture map size is non-standard (not 256x256), it is: "+size[0]+"x"+size[1]+": Continue?%t|YES|NO") + if(result==2): + return False + + #verify frame list data + user_frame_list=get_frame_list() + temp=user_frame_list[len(user_frame_list)-1] + temp_num_frames=temp[2] + + #verify tri/vert/frame counts are within MD2 standard + face_count=len(mesh.faces) + vert_count=len(mesh.verts) + frame_count=temp_num_frames + + if face_count>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_x<point[1]: frame_max_x=point[1] + if frame_min_y>point[0]: frame_min_y=point[0] + if frame_max_y<point[0]: frame_max_y=point[0] + if frame_min_z>point[2]: frame_min_z=point[2] + if frame_max_z<point[2]: frame_max_z=point[2] + + #the scale is the difference between the min and max (on that axis) / 255 + frame_scale_x=(frame_max_x-frame_min_x)/255 + frame_scale_y=(frame_max_y-frame_min_y)/255 + frame_scale_z=(frame_max_z-frame_min_z)/255 + + #translate value of the mesh to center it on the origin + frame_trans_x=frame_min_x + frame_trans_y=frame_min_y + frame_trans_z=frame_min_z + + #fill in the data + md2.frames[frame_counter].scale=(-frame_scale_x, frame_scale_y, frame_scale_z) + md2.frames[frame_counter].translate=(-frame_trans_x, frame_trans_y, frame_trans_z) + + #now for the vertices + for vert_counter in range(0, md2.num_vertices): + #add a vertex to the md2 structure + md2.frames[frame_counter].vertices.append(md2_point()) + #figure out the new coords based on scale and transform + #then translates the point so it's not less than 0 + #then scale it so it's between 0..255 + new_x=int((mesh.verts[vert_counter].co[1]-frame_trans_x)/frame_scale_x) + new_y=int((mesh.verts[vert_counter].co[0]-frame_trans_y)/frame_scale_y) + new_z=int((mesh.verts[vert_counter].co[2]-frame_trans_z)/frame_scale_z) + #put them in the structure + md2.frames[frame_counter].vertices[vert_counter].vertices=(new_x, new_y, new_z) + + #need to add the lookup table check here + md2.frames[frame_counter].vertices[vert_counter].lightnormalindex=0 + + #output all the frame names-user_frame_list is loaded during the validation + for frame_set in user_frame_list: + for counter in range(frame_set[1]-1, frame_set[2]): + md2.frames[counter].name=frame_set[0]+"_"+str(counter-frame_set[1]+2) + + #compute these after everthing is loaded into a md2 structure + header_size=17*4 #17 integers, and each integer is 4 bytes + skin_size=64*md2.num_skins #64 char per skin * number of skins + tex_coord_size=4*md2.num_tex_coords #2 short * number of texture coords + face_size=12*md2.num_faces #3 shorts for vertex index, 3 shorts for tex index + frames_size=(((12+12+16)+(4*md2.num_vertices)) * md2.num_frames) #frame info+verts per frame*num frames + GL_command_size=md2.num_GL_commands*4 #each is an int or float, so 4 bytes per + + #fill in the info about offsets + md2.offset_skins=0+header_size + md2.offset_tex_coords=md2.offset_skins+skin_size + md2.offset_faces=md2.offset_tex_coords+tex_coord_size + md2.offset_frames=md2.offset_faces+face_size + md2.offset_GL_commands=md2.offset_frames+frames_size + md2.offset_end=md2.offset_GL_commands+GL_command_size + +###################################################### +# Get Frame List +###################################################### +def get_frame_list(): + global g_frame_filename + frame_list=[] + + if g_frame_filename.val=="default": + return MD2_FRAME_NAME_LIST + + else: + #check for file + if (Blender.sys.exists(g_frame_filename.val)==1): + #open file and read it in + file=open(g_frame_filename.val,"r") + lines=file.readlines() + file.close() + + #check header (first line) + if lines[0]<>"# 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 <phaethon@linux.ucla.edu><br> + + Additional help from: Shadwolf, Skandal, Rojo, Cambo<br> + 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) |