All pastes #345416 Raw Edit

ascotan

public text v1 · immutable
·published 2007-02-08 14:36 UTC
rendered paste body
#!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)