Skip to content
Snippets Groups Projects
io_export_unreal_psk_psa.py 58.2 KiB
Newer Older
#  ***** 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 3 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, see <http://www.gnu.org/licenses/>.
#  All rights reserved.
#  ***** GPL LICENSE BLOCK *****
bl_addon_info = {
	"name": "Export Skeleletal Mesh/Animation Data",
	"author": "Darknet/Optimus_P-Fat/Active_Trash/Sinsoft",
	"version": (2,0),
	"blender": (2, 5, 3),
	"api": 31847,
	"location": "File > Export > Skeletal Mesh/Animation Data (.psk/.psa)",
	"description": "Export Unreal Engine (.psk)",
	"warning": "",
	"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
		"Scripts/File_I-O/Unreal_psk_psa",
	"tracker_url": "https://projects.blender.org/tracker/index.php?"\
		"func=detail&aid=21366&group_id=153&atid=469",
	"category": "Import/Export"}
-- Unreal Skeletal Mesh and Animation Export (.psk  and .psa) export script v0.0.1 --<br> 

- NOTES:
- This script Exports To Unreal's PSK and PSA file formats for Skeletal Meshes and Animations. <br>
- This script DOES NOT support vertex animation! These require completely different file formats. <br>

- v0.0.1
- Initial version

- v0.0.2
- This version adds support for more than one material index!

[ - Edit by: Darknet
- v0.0.3 - v0.0.12
- This will work on UT3 and it is a stable version that work with vehicle for testing. 
- Main Bone fix no dummy needed to be there.
- Just bone issues position, rotation, and offset for psk.
- The armature bone position, rotation, and the offset of the bone is fix. It was to deal with skeleton mesh export for psk.
- Animation is fix for position, offset, rotation bone support one rotation direction when armature build. 
- It will convert your mesh into triangular when exporting to psk file.
- Did not work with psa export yet.

- v0.0.13
- The animatoin will support different bone rotations when export the animation.

- v0.0.14
- Fixed Action set keys frames when there is no pose keys and it will ignore it.

- v0.0.15
- Fixed multiple objects when exporting to psk. Select one mesh to export to psk.
- ]

- v0.1.1
- Blender 2.50 svn (Support)

Credit to:
- export_cal3d.py (Position of the Bones Format)
- blender2md5.py (Animation Translation Format)
- export_obj.py (Blender 2.5/Pyhton 3.x Format)

- freenode #blendercoder -> user -> ideasman42

- Give Credit to those who work on this script.

- http://sinsoft.com
"""

import os
import time
import datetime
import bpy
import mathutils
import operator

from struct import pack, calcsize


# REFERENCE MATERIAL JUST IN CASE:
# 
# U = x / sqrt(x^2 + y^2 + z^2)
# V = y / sqrt(x^2 + y^2 + z^2)
#
# Triangles specifed counter clockwise for front face
#
#defines for sizeofs
SIZE_FQUAT = 16
SIZE_FVECTOR = 12
SIZE_VJOINTPOS = 44
SIZE_ANIMINFOBINARY = 168
SIZE_VCHUNKHEADER = 32
SIZE_VMATERIAL = 88
SIZE_VBONE = 120
SIZE_FNAMEDBONEBINARY = 120
SIZE_VRAWBONEINFLUENCE = 12
SIZE_VQUATANIMKEY = 32
SIZE_VVERTEX = 16
SIZE_VPOINT = 12
SIZE_VTRIANGLE = 12

########################################################################
# Generic Object->Integer mapping
# the object must be usable as a dictionary key
class ObjMap:
	def __init__(self):
		self.dict = {}
		self.next = 0
	def get(self, obj):
		if obj in self.dict:
			return self.dict[obj]
		else:
			id = self.next
			self.next = self.next + 1
			self.dict[obj] = id
			return id
		
	def items(self):
		getval = operator.itemgetter(0)
		getkey = operator.itemgetter(1)
		return map(getval, sorted(self.dict.items(), key=getkey))

########################################################################
# RG - UNREAL DATA STRUCTS - CONVERTED FROM C STRUCTS GIVEN ON UDN SITE 
# provided here: http://udn.epicgames.com/Two/BinaryFormatSpecifications.html
# updated UDK (Unreal Engine 3): http://udn.epicgames.com/Three/BinaryFormatSpecifications.html
class FQuat:
	def __init__(self): 
		self.X = 0.0
		self.Y = 0.0
		self.Z = 0.0
		self.W = 1.0
		
	def dump(self):
		data = pack('ffff', self.X, self.Y, self.Z, self.W)
		return data
		
	def __cmp__(self, other):
		return cmp(self.X, other.X) \
			or cmp(self.Y, other.Y) \
			or cmp(self.Z, other.Z) \
			or cmp(self.W, other.W)
		
	def __hash__(self):
		return hash(self.X) ^ hash(self.Y) ^ hash(self.Z) ^ hash(self.W)
		
	def __str__(self):
		return "[%f,%f,%f,%f](FQuat)" % (self.X, self.Y, self.Z, self.W)
		
	def __init__(self, X=0.0, Y=0.0, Z=0.0):
		self.X = X
		self.Y = Y
		self.Z = Z
		
	def dump(self):
		data = pack('fff', self.X, self.Y, self.Z)
		return data
		
	def __cmp__(self, other):
		return cmp(self.X, other.X) \
			or cmp(self.Y, other.Y) \
			or cmp(self.Z, other.Z)
		
	def _key(self):
		return (type(self).__name__, self.X, self.Y, self.Z)
		
	def __hash__(self):
		return hash(self._key())
		
	def __eq__(self, other):
		if not hasattr(other, '_key'):
			return False
		return self._key() == other._key() 
		
	def dot(self, other):
		return self.X * other.X + self.Y * other.Y + self.Z * other.Z
	
	def cross(self, other):
		return FVector(self.Y * other.Z - self.Z * other.Y,
				self.Z * other.X - self.X * other.Z,
				self.X * other.Y - self.Y * other.X)
				
	def sub(self, other):
		return FVector(self.X - other.X,
			self.Y - other.Y,
			self.Z - other.Z)
	def __init__(self):
		self.Orientation = FQuat()
		self.Position = FVector()
		self.Length = 0.0
		self.XSize = 0.0
		self.YSize = 0.0
		self.ZSize = 0.0
		
	def dump(self):
		data = self.Orientation.dump() + self.Position.dump() + pack('4f', self.Length, self.XSize, self.YSize, self.ZSize)
		return data
			
	def __init__(self):
		self.Name = "" # length=64
		self.Group = ""    # length=64
		self.TotalBones = 0
		self.RootInclude = 0
		self.KeyCompressionStyle = 0
		self.KeyQuotum = 0
		self.KeyPrediction = 0.0
		self.TrackTime = 0.0
		self.AnimRate = 0.0
		self.StartBone = 0
		self.FirstRawFrame = 0
		self.NumRawFrames = 0
		
	def dump(self):
		data = pack('64s64siiiifffiii', self.Name, self.Group, self.TotalBones, self.RootInclude, self.KeyCompressionStyle, self.KeyQuotum, self.KeyPrediction, self.TrackTime, self.AnimRate, self.StartBone, self.FirstRawFrame, self.NumRawFrames)
		return data
	def __init__(self, name, type_size):
		self.ChunkID = name # length=20
		self.TypeFlag = 1999801 # special value
		self.DataSize = type_size
		self.DataCount = 0
		
	def dump(self):
		data = pack('20siii', self.ChunkID, self.TypeFlag, self.DataSize, self.DataCount)
		return data
		
	def __init__(self):
		self.MaterialName = "" # length=64
		self.TextureIndex = 0
		self.PolyFlags = 0 # DWORD
		self.AuxMaterial = 0
		self.AuxFlags = 0 # DWORD
		self.LodBias = 0
		self.LodStyle = 0
		
	def dump(self):
		data = pack('64siLiLii', self.MaterialName, self.TextureIndex, self.PolyFlags, self.AuxMaterial, self.AuxFlags, self.LodBias, self.LodStyle)
		return data
	def __init__(self):
		self.Name = "" # length = 64
		self.Flags = 0 # DWORD
		self.NumChildren = 0
		self.ParentIndex = 0
		self.BonePos = VJointPos()
		
	def dump(self):
		data = pack('64sLii', self.Name, self.Flags, self.NumChildren, self.ParentIndex) + self.BonePos.dump()
		return data
Luca Bonavita's avatar
Luca Bonavita committed

#same as above - whatever - this is how Epic does it...        
	def __init__(self):
		self.Name = "" # length = 64
		self.Flags = 0 # DWORD
		self.NumChildren = 0
		self.ParentIndex = 0
		self.BonePos = VJointPos()
		
		self.IsRealBone = 0  # this is set to 1 when the bone is actually a bone in the mesh and not a dummy
		
	def dump(self):
		data = pack('64sLii', self.Name, self.Flags, self.NumChildren, self.ParentIndex) + self.BonePos.dump()
		return data
	
	def __init__(self):
		self.Weight = 0.0
		self.PointIndex = 0
		self.BoneIndex = 0
		
	def dump(self):
		data = pack('fii', self.Weight, self.PointIndex, self.BoneIndex)
		return data
		
	def __init__(self):
		self.Position = FVector()
		self.Orientation = FQuat()
		self.Time = 0.0
		
	def dump(self):
		data = self.Position.dump() + self.Orientation.dump() + pack('f', self.Time)
		return data
		
	def __init__(self):
		self.PointIndex = 0 # WORD
		self.U = 0.0
		self.V = 0.0
		self.MatIndex = 0 #BYTE
		self.Reserved = 0 #BYTE
		
	def dump(self):
		data = pack('HHffBBH', self.PointIndex, 0, self.U, self.V, self.MatIndex, self.Reserved, 0)
		return data
		
	def __cmp__(self, other):
		return cmp(self.PointIndex, other.PointIndex) \
			or cmp(self.U, other.U) \
			or cmp(self.V, other.V) \
			or cmp(self.MatIndex, other.MatIndex) \
			or cmp(self.Reserved, other.Reserved)
	
	def _key(self):
		return (type(self).__name__,self.PointIndex, self.U, self.V,self.MatIndex,self.Reserved)
		
	def __hash__(self):
		return hash(self._key())
		
	def __eq__(self, other):
		if not hasattr(other, '_key'):
			return False
		return self._key() == other._key()
		
	def __init__(self):
		self.Point = FVector()
		
	def dump(self):
		return self.Point.dump()
		
	def __cmp__(self, other):
		return cmp(self.Point, other.Point)
	
	def _key(self):
		return (type(self).__name__, self.Point)
	
	def __hash__(self):
		return hash(self._key())
		
	def __eq__(self, other):
		if not hasattr(other, '_key'):
			return False
		return self._key() == other._key() 
		
	def __init__(self):
		self.WedgeIndex0 = 0 # WORD
		self.WedgeIndex1 = 0 # WORD
		self.WedgeIndex2 = 0 # WORD
		self.MatIndex = 0 # BYTE
		self.AuxMatIndex = 0 # BYTE
		self.SmoothingGroups = 0 # DWORD
		
	def dump(self):
		data = pack('HHHBBL', self.WedgeIndex0, self.WedgeIndex1, self.WedgeIndex2, self.MatIndex, self.AuxMatIndex, self.SmoothingGroups)
		return data

# END UNREAL DATA STRUCTS
########################################################################

########################################################################
#RG - helper class to handle the normal way the UT files are stored 
#as sections consisting of a header and then a list of data structures
class FileSection:
	def __init__(self, name, type_size):
		self.Header = VChunkHeader(name, type_size)
		self.Data = [] # list of datatypes
		
	def dump(self):
		data = self.Header.dump()
		for i in range(len(self.Data)):
			data = data + self.Data[i].dump()
		return data
		
	def UpdateHeader(self):
		self.Header.DataCount = len(self.Data)
		
	def __init__(self):
		self.GeneralHeader = VChunkHeader("ACTRHEAD", 0)
		self.Points = FileSection("PNTS0000", SIZE_VPOINT)        #VPoint
		self.Wedges = FileSection("VTXW0000", SIZE_VVERTEX)        #VVertex
		self.Faces = FileSection("FACE0000", SIZE_VTRIANGLE)        #VTriangle
		self.Materials = FileSection("MATT0000", SIZE_VMATERIAL)    #VMaterial
		self.Bones = FileSection("REFSKELT", SIZE_VBONE)        #VBone
		self.Influences = FileSection("RAWWEIGHTS", SIZE_VRAWBONEINFLUENCE)    #VRawBoneInfluence
		
		#RG - this mapping is not dumped, but is used internally to store the new point indices 
		# for vertex groups calculated during the mesh dump, so they can be used again
		# to dump bone influences during the armature dump
		#
		# the key in this dictionary is the VertexGroup/Bone Name, and the value
		# is a list of tuples containing the new point index and the weight, in that order
		#
		# Layout:
		# { groupname : [ (index, weight), ... ], ... }
		#
		# example: 
		# { 'MyVertexGroup' : [ (0, 1.0), (5, 1.0), (3, 0.5) ] , 'OtherGroup' : [(2, 1.0)] }
		
		self.VertexGroups = {} 
		
	def AddPoint(self, p):
		#print ('AddPoint')
		self.Points.Data.append(p)
		
	def AddWedge(self, w):
		#print ('AddWedge')
		self.Wedges.Data.append(w)
	
	def AddFace(self, f):
		#print ('AddFace')
		self.Faces.Data.append(f)
		
	def AddMaterial(self, m):
		#print ('AddMaterial')
		self.Materials.Data.append(m)
		
	def AddBone(self, b):
		#print ('AddBone [%s]: Position: (x=%f, y=%f, z=%f) Rotation=(%f,%f,%f,%f)'  % (b.Name, b.BonePos.Position.X, b.BonePos.Position.Y, b.BonePos.Position.Z, b.BonePos.Orientation.X,b.BonePos.Orientation.Y,b.BonePos.Orientation.Z,b.BonePos.Orientation.W))
		self.Bones.Data.append(b)
		
	def AddInfluence(self, i):
		#print ('AddInfluence')
		self.Influences.Data.append(i)
		
	def UpdateHeaders(self):
		self.Points.UpdateHeader()
		self.Wedges.UpdateHeader()
		self.Faces.UpdateHeader()
		self.Materials.UpdateHeader()
		self.Bones.UpdateHeader()
		self.Influences.UpdateHeader()
		
	def dump(self):
		self.UpdateHeaders()
		data = self.GeneralHeader.dump() + self.Points.dump() + self.Wedges.dump() + self.Faces.dump() + self.Materials.dump() + self.Bones.dump() + self.Influences.dump()
		return data
		
	def GetMatByIndex(self, mat_index):
		if mat_index >= 0 and len(self.Materials.Data) > mat_index:
			return self.Materials.Data[mat_index]
		else:
			m = VMaterial()
			m.MaterialName = "Mat%i" % mat_index
			self.AddMaterial(m)
			return m
		
	def PrintOut(self):
		print ("--- PSK FILE EXPORTED ---")
		print ('point count: %i' % len(self.Points.Data))
		print ('wedge count: %i' % len(self.Wedges.Data))
		print ('face count: %i' % len(self.Faces.Data))
		print ('material count: %i' % len(self.Materials.Data))
		print ('bone count: %i' % len(self.Bones.Data))
		print ('inlfuence count: %i' % len(self.Influences.Data))
		print ('-------------------------')
Luca Bonavita's avatar
Luca Bonavita committed
#    The raw key array holds all the keys for all the bones in all the specified sequences, 
#    organized as follows:
#    For each AnimInfoBinary's sequence there are [Number of bones] times [Number of frames keys] 
#    in the VQuatAnimKeys, laid out as tracks of [numframes] keys for each bone in the order of 
#    the bones as defined in the array of FnamedBoneBinary in the PSA. 
Luca Bonavita's avatar
Luca Bonavita committed
#    Once the data from the PSK (now digested into native skeletal mesh) and PSA (digested into 
#    a native animation object containing one or more sequences) are associated together at runtime, 
#    bones are linked up by name. Any bone in a skeleton (from the PSK) that finds no partner in 
#    the animation sequence (from the PSA) will assume its reference pose stance ( as defined in 
#    the offsets & rotations that are in the VBones making up the reference skeleton from the PSK)
	def __init__(self):
		self.GeneralHeader = VChunkHeader("ANIMHEAD", 0)
		self.Bones = FileSection("BONENAMES", SIZE_FNAMEDBONEBINARY)    #FNamedBoneBinary
		self.Animations = FileSection("ANIMINFO", SIZE_ANIMINFOBINARY)    #AnimInfoBinary
		self.RawKeys = FileSection("ANIMKEYS", SIZE_VQUATANIMKEY)    #VQuatAnimKey
		
		# this will take the format of key=Bone Name, value = (BoneIndex, Bone Object)
		# THIS IS NOT DUMPED
		self.BoneLookup = {} 
		
	def dump(self):
		data = self.Generalheader.dump() + self.Bones.dump() + self.Animations.dump() + self.RawKeys.dump()
		return data
	
	def AddBone(self, b):
		#LOUD
		#print "AddBone: " + b.Name
		self.Bones.Data.append(b)
		
	def AddAnimation(self, a):
		#LOUD
		#print "AddAnimation: %s, TotalBones: %i, AnimRate: %f, NumRawFrames: %i, TrackTime: %f" % (a.Name, a.TotalBones, a.AnimRate, a.NumRawFrames, a.TrackTime)
		self.Animations.Data.append(a)
		
	def AddRawKey(self, k):
		#LOUD
		#print "AddRawKey [%i]: Time: %f, Quat: x=%f, y=%f, z=%f, w=%f, Position: x=%f, y=%f, z=%f" % (len(self.RawKeys.Data), k.Time, k.Orientation.X, k.Orientation.Y, k.Orientation.Z, k.Orientation.W, k.Position.X, k.Position.Y, k.Position.Z)
		self.RawKeys.Data.append(k)
		
	def UpdateHeaders(self):
		self.Bones.UpdateHeader()
		self.Animations.UpdateHeader()
		self.RawKeys.UpdateHeader()
		
	def GetBoneByIndex(self, bone_index):
		if bone_index >= 0 and len(self.Bones.Data) > bone_index:
			return self.Bones.Data[bone_index]
	
	def IsEmpty(self):
		return (len(self.Bones.Data) == 0 or len(self.Animations.Data) == 0)
	
	def StoreBone(self, b):
		self.BoneLookup[b.Name] = [-1, b]
					
	def UseBone(self, bone_name):
		if bone_name in self.BoneLookup:
			bone_data = self.BoneLookup[bone_name]
			
			if bone_data[0] == -1:
				bone_data[0] = len(self.Bones.Data)
				self.AddBone(bone_data[1])
				#self.Bones.Data.append(bone_data[1])
			
			return bone_data[0]
			
	def GetBoneByName(self, bone_name):
		if bone_name in self.BoneLookup:
			bone_data = self.BoneLookup[bone_name]
			return bone_data[1]
		
	def GetBoneIndex(self, bone_name):
		if bone_name in self.BoneLookup:
			bone_data = self.BoneLookup[bone_name]
			return bone_data[0]
		
	def dump(self):
		self.UpdateHeaders()
		data = self.GeneralHeader.dump() + self.Bones.dump() + self.Animations.dump() + self.RawKeys.dump()
		return data
		
	def PrintOut(self):
		print ('--- PSA FILE EXPORTED ---')
		print ('bone count: %i' % len(self.Bones.Data))
		print ('animation count: %i' % len(self.Animations.Data))
		print ('rawkey count: %i' % len(self.RawKeys.Data))
		print ('-------------------------')
		
Luca Bonavita's avatar
Luca Bonavita committed
####################################    
# helpers to create bone structs
def make_vbone(name, parent_index, child_count, orientation_quat, position_vect):
	bone = VBone()
	bone.Name = name
	bone.ParentIndex = parent_index
	bone.NumChildren = child_count
	bone.BonePos.Orientation = orientation_quat
	bone.BonePos.Position.X = position_vect.x
	bone.BonePos.Position.Y = position_vect.y
	bone.BonePos.Position.Z = position_vect.z
	
	#these values seem to be ignored?
	#bone.BonePos.Length = tail.length
	#bone.BonePos.XSize = tail.x
	#bone.BonePos.YSize = tail.y
	#bone.BonePos.ZSize = tail.z

	return bone

def make_namedbonebinary(name, parent_index, child_count, orientation_quat, position_vect, is_real):
	bone = FNamedBoneBinary()
	bone.Name = name
	bone.ParentIndex = parent_index
	bone.NumChildren = child_count
	bone.BonePos.Orientation = orientation_quat
	bone.BonePos.Position.X = position_vect.x
	bone.BonePos.Position.Y = position_vect.y
	bone.BonePos.Position.Z = position_vect.z
	bone.IsRealBone = is_real
	return bone    
	
##################################################
#RG - check to make sure face isnt a line
#The face has to be triangle not a line
def is_1d_face(blender_face,mesh):
	#ID Vertex of id point
	v0 = blender_face.vertices[0]
	v1 = blender_face.vertices[1]
	v2 = blender_face.vertices[2]
	
	return (mesh.vertices[v0].co == mesh.vertices[v1].co or \
	mesh.vertices[v1].co == mesh.vertices[v2].co or \
	mesh.vertices[v2].co == mesh.vertices[v0].co)
	return False

##################################################
# http://en.wikibooks.org/wiki/Blender_3D:_Blending_Into_Python/Cookbook#Triangulate_NMesh

#blender 2.50 format using the Operators/command convert the mesh to tri mesh
def triangulateNMesh(object):
	bneedtri = False
	scene = bpy.context.scene
	bpy.ops.object.mode_set(mode='OBJECT')
	for i in scene.objects: i.select = False #deselect all objects
	object.select = True
	scene.objects.active = object #set the mesh object to current
	bpy.ops.object.mode_set(mode='OBJECT')
	print("Checking mesh if needs to convert quad to Tri...")
	for face in object.data.faces:
		if (len(face.vertices) > 3):
			bneedtri = True
			break
	
	bpy.ops.object.mode_set(mode='OBJECT')
	if bneedtri == True:
		print("Converting quad to tri mesh...")
		me_da = object.data.copy() #copy data
		me_ob = object.copy() #copy object
		#note two copy two types else it will use the current data or mesh
		me_ob.data = me_da
		bpy.context.scene.objects.link(me_ob)#link the object to the scene #current object location
		for i in scene.objects: i.select = False #deselect all objects
		me_ob.select = True
		scene.objects.active = me_ob #set the mesh object to current
		bpy.ops.object.mode_set(mode='EDIT') #Operators
		bpy.ops.mesh.select_all(action='SELECT')#select all the face/vertex/edge
		bpy.ops.mesh.quads_convert_to_tris() #Operators
		bpy.context.scene.update()
		bpy.ops.object.mode_set(mode='OBJECT') # set it in object
		bpy.context.scene.unrealtriangulatebool = True
		print("Triangulate Mesh Done!")
	else:
		bpy.context.scene.unrealtriangulatebool = False
		print("No need to convert tri mesh.")
		me_ob = object
	return me_ob
	def __init__(self):
		self.bone = ""
		self.index = 0
Luca Bonavita's avatar
Luca Bonavita committed
BBCount = 0    
#deal with mesh bones groups vertex point
def BoneIndex(bone):
	global BBCount, bonedata
	#print("//==============")
	#print(bone.name , "ID:",BBCount)
	BB = BBone()
	BB.bone = bone.name
	BB.index = BBCount
	bonedata.append(BB)
	BBCount += 1
	for current_child_bone in bone.children:
		BoneIndex(current_child_bone)
	global BBCount
	#print("\n Buildng bone before mesh \n")
	#objectbone = blender_armature.pose #Armature bone
	#print(blender_armature)
	objectbone = blender_armature[0].pose 
	#print(dir(ArmatureData))
	
	for bone in objectbone.bones:
		if(bone.parent is None):
			BoneIndex(bone)
			#BBCount += 1
			break
	
# Actual object parsing functions
def parse_meshes(blender_meshes, psk_file):
	#this is use to call the bone name and the index array for group index matches
	global bonedata
	#print("BONE DATA",len(bonedata))
	print ("----- parsing meshes -----")
	print("Number of Object Meshes:",len(blender_meshes))
	for current_obj in blender_meshes: #number of mesh that should be one mesh here
		current_obj = triangulateNMesh(current_obj)
		#print(dir(current_obj))
		print("Mesh Name:",current_obj.name)
		current_mesh = current_obj.data
		
		#if len(current_obj.materials) > 0:
		#    object_mat = current_obj.materials[0]
		object_material_index = current_obj.active_material_index
	
		points = ObjMap()
		wedges = ObjMap()
		
		discarded_face_count = 0
		print (" -- Dumping Mesh Faces -- LEN:", len(current_mesh.faces))
		for current_face in current_mesh.faces:
			#print ' -- Dumping UVs -- '
			#print current_face.uv_textures
			
			if len(current_face.vertices) != 3:
				raise RuntimeError("Non-triangular face (%i)" % len(current_face.vertices))
			
			#No Triangulate Yet
			#            if len(current_face.vertices) != 3:
			#                raise RuntimeError("Non-triangular face (%i)" % len(current_face.vertices))
			#                #TODO: add two fake faces made of triangles?
			
			#RG - apparently blender sometimes has problems when you do quad to triangle 
			#    conversion, and ends up creating faces that have only TWO points -
			#     one of the points is simply in the vertex list for the face twice. 
			#    This is bad, since we can't get a real face normal for a LINE, we need 
			#    a plane for this. So, before we add the face to the list of real faces, 
			#    ensure that the face is actually a plane, and not a line. If it is not 
			#    planar, just discard it and notify the user in the console after we're
			#    done dumping the rest of the faces
			
			if not is_1d_face(current_face,current_mesh):
				#print("faces")
				wedge_list = []
				vect_list = []
				
				#get or create the current material
				m = psk_file.GetMatByIndex(object_material_index)

				face_index = current_face.index
				has_UV = False
				faceUV = None
				
				if len(current_mesh.uv_textures) > 0:
					has_UV = True    
					#print("face index: ",face_index)
					#faceUV = current_mesh.uv_textures.active.data[face_index]#UVs for current face
					#faceUV = current_mesh.uv_textures.active.data[0]#UVs for current face
					#print(face_index,"<[FACE NUMBER")
					uv_layer = current_mesh.uv_textures.active
					faceUV = uv_layer.data[face_index]
					#print("============================")
					#size(data) is number of texture faces. Each face has UVs
					#print("DATA face uv: ",len(faceUV.uv), " >> ",(faceUV.uv[0][0]))
				
				for i in range(3):
					vert_index = current_face.vertices[i]
					vert = current_mesh.vertices[vert_index]
					uv = []
					#assumes 3 UVs Per face (for now).
					if (has_UV):
						if len(faceUV.uv) != 3:
							print ("WARNING: Current face is missing UV coordinates - writing 0,0...")
							print ("WARNING: Face has more than 3 UVs - writing 0,0...")
							uv = [0.0, 0.0]
						else:
							#uv.append(faceUV.uv[i][0])
							#uv.append(faceUV.uv[i][1])
							uv = [faceUV.uv[i][0],faceUV.uv[i][1]] #OR bottom works better # 24 for cube
							#uv = list(faceUV.uv[i]) #30 just cube    
					else:
						uv = [0.0, 0.0]
					#print("UV >",uv)
					#uv = [0.0, 0.0] #over ride uv that is not fixed
					#print(uv)
					#flip V coordinate because UEd requires it and DOESN'T flip it on its own like it
					#does with the mesh Y coordinates.
					#this is otherwise known as MAGIC-2
					uv[1] = 1.0 - uv[1]
					
					#deal with the min and max value
					#if value is over the set limit it will null the uv texture
					if (uv[0] > 1):
						uv[0] = 1
					if (uv[0] < 0):
						uv[0] = 0
					if (uv[1] > 1):
						uv[1] = 1
					if (uv[1] < 0):
						uv[1] = 0

					
					# RE - Append untransformed vector (for normal calc below)
					# TODO: convert to Blender.Mathutils
					vect_list.append(FVector(vert.co.x, vert.co.y, vert.co.z))
					
					# Transform position for export
					#vpos = vert.co * object_material_index
					vpos = vert.co * current_obj.matrix_local
					# Create the point
					p = VPoint()
					p.Point.X = vpos.x
					p.Point.Y = vpos.y
					p.Point.Z = vpos.z
					
					# Create the wedge
					w = VVertex()
					w.MatIndex = object_material_index
					w.PointIndex = points.get(p) # get index from map
					#Set UV TEXTURE
					w.U = uv[0]
					w.V = uv[1]
					index_wedge = wedges.get(w)
					wedge_list.append(index_wedge)
					
					#print results
					#print 'result PointIndex=%i, U=%f, V=%f, wedge_index=%i' % (
					#    w.PointIndex,
					#    w.U,
					#    w.V,
					#    wedge_index)
				
				# Determine face vertex order
				# get normal from blender
				no = current_face.normal
				
				# TODO: convert to Blender.Mathutils
				# convert to FVector
				norm = FVector(no[0], no[1], no[2])
				
				# Calculate the normal of the face in blender order
				tnorm = vect_list[1].sub(vect_list[0]).cross(vect_list[2].sub(vect_list[1]))
				
				# RE - dot the normal from blender order against the blender normal
				# this gives the product of the two vectors' lengths along the blender normal axis
				# all that matters is the sign
				dot = norm.dot(tnorm)

				# print results
				#print 'face norm: (%f,%f,%f), tnorm=(%f,%f,%f), dot=%f' % (
				#    norm.X, norm.Y, norm.Z,
				#    tnorm.X, tnorm.Y, tnorm.Z,
				#    dot)

				tri = VTriangle()
				# RE - magic: if the dot product above > 0, order the vertices 2, 1, 0
				#        if the dot product above < 0, order the vertices 0, 1, 2
				#        if the dot product is 0, then blender's normal is coplanar with the face
				#        and we cannot deduce which side of the face is the outside of the mesh
				if (dot > 0):
					(tri.WedgeIndex2, tri.WedgeIndex1, tri.WedgeIndex0) = wedge_list
				elif (dot < 0):
					(tri.WedgeIndex0, tri.WedgeIndex1, tri.WedgeIndex2) = wedge_list
				else:
					dindex0 = current_face.vertices[0];
					dindex1 = current_face.vertices[1];
					dindex2 = current_face.vertices[2];
					current_mesh.vertices[dindex0].select = True
					current_mesh.vertices[dindex1].select = True
					current_mesh.vertices[dindex2].select = True
					
					raise RuntimeError("normal vector coplanar with face! points:", current_mesh.vertices[dindex0].co, current_mesh.vertices[dindex1].co, current_mesh.vertices[dindex2].co)
				#print(dir(current_face))
				current_face.select = True
John Phan's avatar
John Phan committed
				#print((current_face.use_smooth))
				#not sure if this right
				#tri.SmoothingGroups
				if current_face.use_smooth == True:
					tri.SmoothingGroups = 1
				else:
					tri.SmoothingGroups = 0
				#print(tri)
				psk_file.AddFace(tri)
				
			else:
				discarded_face_count = discarded_face_count + 1
				
		print (" -- Dumping Mesh Points -- LEN:",len(points.dict))
		for point in points.items():
			psk_file.AddPoint(point)
		print (" -- Dumping Mesh Wedge -- LEN:",len(wedges.dict))
		for wedge in wedges.items():
			psk_file.AddWedge(wedge)
			
		#RG - if we happend upon any non-planar faces above that we've discarded, 
		#    just let the user know we discarded them here in case they want 
		#    to investigate
	
		if discarded_face_count > 0: 
			print ("INFO: Discarded %i non-planar faces." % (discarded_face_count))
		
		#RG - walk through the vertex groups and find the indexes into the PSK points array 
		#for them, then store that index and the weight as a tuple in a new list of 
		#verts for the group that we can look up later by bone name, since Blender matches
		#verts to bones for influences by having the VertexGroup named the same thing as
		#the bone

		#vertex group.
		for bonegroup in bonedata:
			#print("bone gourp build:",bonegroup.bone)
			vert_list = []
			for current_vert in current_mesh.vertices:
				#print("INDEX V:",current_vert.index)
				vert_index = current_vert.index
				for vgroup in current_vert.groups:#vertex groupd id
					vert_weight = vgroup.weight
					if(bonegroup.index == vgroup.group):
						p = VPoint()
						vpos = current_vert.co * current_obj.matrix_local
						p.Point.X = vpos.x
						p.Point.Y = vpos.y 
						p.Point.Z = vpos.z
						#print(current_vert.co)
						point_index = points.get(p) #point index
						v_item = (point_index, vert_weight)
						vert_list.append(v_item)
			#bone name, [point id and wieght]
			#print("Add Vertex Group:",bonegroup.bone, " No. Points:",len(vert_list))
			psk_file.VertexGroups[bonegroup.bone] = vert_list
		
		#unrealtriangulatebool #this will remove the mesh from the scene
		if (bpy.context.scene.unrealtriangulatebool == True):
			print("Remove tmp Mesh [ " ,current_obj.name, " ] from scene >"  ,(bpy.context.scene.unrealtriangulatebool ))
			bpy.ops.object.mode_set(mode='OBJECT') # set it in object
			bpy.context.scene.objects.unlink(current_obj)
		
	quat = FQuat()
	
	#flip handedness for UT = set x,y,z to negative (rotate in other direction)
	quat.X = -bquat.x
	quat.Y = -bquat.y
	quat.Z = -bquat.z

	quat.W = bquat.w
	return quat
	
	quat = FQuat()
	
	quat.X = bquat.x
	quat.Y = bquat.y
	quat.Z = bquat.z
	
	quat.W = bquat.w
	return quat

# =================================================================================================
# TODO: remove this 1am hack
nbone = 0
def parse_bone(blender_bone, psk_file, psa_file, parent_id, is_root_bone, parent_matrix, parent_root):
	global nbone     # look it's evil!
	#print '-------------------- Dumping Bone ---------------------- '

	#If bone does not have parent that mean it the root bone
	if blender_bone.parent is None:
		parent_root = blender_bone
	
	
	child_count = len(blender_bone.children)
	#child of parent
	child_parent = blender_bone.parent
	
	if child_parent != None:
		print ("--Bone Name:",blender_bone.name ," parent:" , blender_bone.parent.name, "ID:", nbone)
	else:
		print ("--Bone Name:",blender_bone.name ," parent: None" , "ID:", nbone)
	
	if child_parent != None:
		quat_root = blender_bone.matrix
		quat = make_fquat(quat_root.to_quat())
		
		quat_parent = child_parent.matrix.to_quat().inverse()
		parent_head = child_parent.head * quat_parent
		parent_tail = child_parent.tail * quat_parent
		
		set_position = (parent_tail - parent_head) + blender_bone.head
	else:
		# ROOT BONE
		#This for root 
		set_position = blender_bone.head * parent_matrix #ARMATURE OBJECT Locction
		rot_mat = blender_bone.matrix * parent_matrix.rotation_part() #ARMATURE OBJECT Rotation
		#print(dir(rot_mat))
		
		quat = make_fquat_default(rot_mat.to_quat())
		
	#print ("[[======= FINAL POSITION:", set_position)
	final_parent_id = parent_id
	
	#RG/RE -
	#if we are not seperated by a small distance, create a dummy bone for the displacement
	#this is only needed for root bones, since UT assumes a connected skeleton, and from here
	#down the chain we just use "tail" as an endpoint
	#if(head.length > 0.001 and is_root_bone == 1):
	if(0):    
		pb = make_vbone("dummy_" + blender_bone.name, parent_id, 1, FQuat(), tail)
		psk_file.AddBone(pb)
		pbb = make_namedbonebinary("dummy_" + blender_bone.name, parent_id, 1, FQuat(), tail, 0)
		psa_file.StoreBone(pbb)