#!BPY #HL2 SMD Importer """ Name: 'Half-Life 2 (.smd) Importer' Blender: 242 Group: 'Import' Tooltip: 'Import mesh/animation from Half-Life 2 File' """ __author__ = 'Joseph Gilbert' __url__ = ('blender', 'elysiun','My script homepage, http://www.blender.org',) __version__ = '1.0' __bpydoc__ = """This is a Half-Life 2 Importer (.smd). Usage: Alt+P to run Notes: Designed to import either the skeletal mesh/rest frame file or an animation sequence """ # -------------------------------------------------------------------------- # HL2 SMD Importer # -------------------------------------------------------------------------- # ***** BEGIN GPL LICENSE BLOCK ***** # # 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 Mathutils from Blender import Armature from Blender import Object from Blender import Scene from Blender import Mesh import math print "--------------- Script Starting ---------------" FILE = 'C:/Documents and Settings/jgilbert/Desktop/H2exporter/SMD_mesh.smd' MODEL_NAME = 'SMD_model' #constants Header_file = 'version 1' Header_armature = 'nodes' Header_animation = 'skeleton' Header_mesh = 'triangles' Block_end = 'end' def parse_file(file): '''Parse the file''' myfile = open(FILE, 'r') try: file_data = {} parse_header(myfile) for x in range(3): data, type = check_next_block(myfile) file_data[type] = data finally: myfile.close() return file_data def check_next_block(myfile): '''check which block comes next in the file and parse it''' type = None line = myfile.readline() if line.strip() == Header_armature: data = parse_armature(myfile) type = 'armature' elif line.strip() == Header_animation: data = parse_animation(myfile) type = 'animation' elif line.strip() == Header_mesh: data = parse_mesh(myfile) type = 'mesh' else: raise AttributeError, 'File format is not correct' return data, type def parse_header(myfile): '''Parse the header line''' line = myfile.readline() if line.strip() != Header_file: raise AttributeError, 'This is not a valid .smd file' def parse_armature(myfile): '''Parse armature data''' armature_data = {} line = myfile.readline() while line.strip() != Block_end: data = line.strip().split('\"') armature_data[int(data[0].strip())] = {'name': data[1], \ 'parent': int(data[2].strip())} line = myfile.readline() return armature_data def parse_mesh(myfile): '''Parse the mesh data''' mesh_data = [] triangle = {} line = myfile.readline() while line.strip() != Block_end: data = line.strip().split() if len(data) == 1: if triangle != {}: mesh_data.append(triangle.copy()) triangle['texture'] = data[0] triangle['verts'] = [] else: triangle['texture'] = data[0] triangle['verts'] = [] else: vertex = {} vertex['bparent'] = data[0] vertex['loc'] = Mathutils.Vector(float(data[1]), \ float(data[2]), float(data[3])) vertex['norm'] = Mathutils.Vector(float(data[4]), \ float(data[5]), float(data[6])) vertex['tex'] = Mathutils.Vector(float(data[7]), \ float(data[8])) num_weights = int(data[9]) vertex['weights'] = {} for x in range(num_weights): bone = int(data[10+(x*2)]) weight = float(data[11+(x*2)]) vertex['weights'][bone] = weight triangle['verts'].append(vertex) line = myfile.readline() mesh_data.append(triangle) #add last return mesh_data def parse_animation(myfile): '''Parse the animation data''' animation_data = {} frame_data = {} frame_number = None line = myfile.readline() while line.strip() != Block_end: data = line.strip().split() if data[0] == 'time': if frame_data != {}: animation_data[frame_number] = frame_data frame_data = {} frame_number = int(data[1]) else: frame_number = int(data[1]) else: #reverse y and z loc = Mathutils.Vector(float(data[1]), float(data[2]), \ float(data[3])) rot = Mathutils.Euler(float(data[4]), float(data[5]), \ float(data[6])) frame_data[int(data[0])] = {'Loc':loc, 'Rot':rot} line = myfile.readline() animation_data[frame_number] = frame_data #add final return animation_data def load_file_data(data): '''load the file data into blender''' create_resting_armature(file_data['armature'], file_data['animation'][0]) create_mesh(file_data['mesh']) def create_resting_armature(armature_data, frame_0_data): '''creates the armature in it's rest pose''' myscene = Scene.GetCurrent() myobj = Object.New('Armature', MODEL_NAME) myobj.setDrawMode(64) #xray bones myarm = Armature.Armature(MODEL_NAME) myobj.link(myarm) myscene.link(myobj) myarm.makeEditable() #create bones for bone in armature_data.keys(): mybone = Armature.Editbone() mybone.name = armature_data[bone]['name'] myarm.bones[mybone.name] = mybone #now fix the parenting for bone in armature_data.keys(): ebone = myarm.bones[armature_data[bone]['name']] parent = armature_data[bone]['parent'] if parent != -1: parent_name = armature_data[parent]['name'] ebone.parent = myarm.bones[parent_name] bone_mapping = {} for key, value in armature_data.items(): bone_mapping[value['name']] = {'key':key, 'matrix': None, 'head': None, 'tail': None} #now fix the position data #calculate all matrices for bone in armature_data.keys(): parent = armature_data[bone]['parent'] if parent == -1: ebone = myarm.bones[armature_data[bone]['name']] bone_mapping[ebone.name]['matrix'] = generate_local_bone_matrix( \ frame_0_data[bone]['Loc'], frame_0_data[bone]['Rot']) calculate_matrices(myarm, ebone, frame_0_data, bone_mapping) #update them in the ebones for bone_name, data in bone_mapping.items(): myarm.bones[bone_name].matrix = data['matrix'] bone_mapping_w_headtail = bone_mapping.copy() for bone_name, data in bone_mapping.items(): bone_mapping_w_headtail[bone_name]['head'] = myarm.bones[bone_name].head bone_mapping_w_headtail[bone_name]['tail'] = myarm.bones[bone_name].tail for bone_name, data in bone_mapping.items(): parent = myarm.bones[bone_name].parent if parent: myarm.bones[bone_name].head = bone_mapping_w_headtail[parent.name]['tail'] myarm.update() def generate_local_bone_matrix(loc, rot): '''generate a localspace matrix from file data''' new_rot = convert_Euler(rot) new_loc = Mathutils.Vector(loc).resize4D() transform = new_rot.toMatrix().resize4x4() transform[3] = new_loc return transform def convert_Euler(euler): '''convert a euler to the appropriate type''' x = (180/math.pi)*euler[0] y = (180/math.pi)*euler[1] z = (180/math.pi)*euler[2] return Mathutils.Euler(x,y,z) def calculate_matrices(myarm, ebone, frame_0_data, bone_mapping): children = [] if ebone.parent: bone_id = bone_mapping[ebone.name]['key'] local_matrix = generate_local_bone_matrix(frame_0_data[bone_id]['Loc'] , \ rot = frame_0_data[bone_id]['Rot']) parent_matrix = bone_mapping[ebone.parent.name]['matrix'] bone_mapping[ebone.name]['matrix'] = local_matrix * parent_matrix for bone in myarm.bones.values(): if bone.parent: if bone.parent.name == ebone.name: children.append(bone) for child in children: calculate_matrices(myarm, child, frame_0_data, bone_mapping) def create_mesh(mesh_data): '''create the mesh from the imported data''' myscene = Scene.GetCurrent() myobj = Object.New('Mesh', MODEL_NAME) myobj.setDrawType(2) #draw wire mesh mymesh= Mesh.New(MODEL_NAME) myobj.link(mymesh) myscene.link(myobj) verts = [] faces = [] for tris in mesh_data: for vert in tris['verts']: verts.append(vert['loc']) #get unique vertices unique_verts = [] for v in verts: if v not in unique_verts: unique_verts.append(v) mymesh.verts.extend(unique_verts) #build vert data map unique_vert_mapping = {} for x in range(len(unique_verts)): unique_vert_mapping[x] = {'loc':unique_verts[x]} #this is a little slow - we need unique vert data by vert index in the mesh unique_vert_map = unique_vert_mapping.copy() for key, value in unique_vert_mapping.items(): found = 0 for tris in mesh_data: for vert in tris['verts']: if value['loc'] == vert['loc']: unique_vert_map[key]['norm'] = vert['norm'] unique_vert_map[key]['tex'] = vert['tex'] unique_vert_map[key]['weights'] = vert['weights'] unique_vert_map[key]['bparent'] = vert['bparent'] unique_vert_map[key]['texture'] = tris['texture'] found = 1 break if found: break unique_vert_mapping = unique_vert_map.copy() del unique_vert_map #set normals per vertex for x in range(len(mymesh.verts)): mymesh.verts[x].no = unique_vert_mapping[x]['norm'] #build faces for tris in mesh_data: face = [] for vert in tris['verts']: index = unique_verts.index(vert['loc']) face.append(index) faces.append(face[:]) mymesh.faces.extend(faces) if __name__=='__main__': file_data = parse_file(FILE) load_file_data(file_data)