Skip to content
Snippets Groups Projects
io_export_unreal_psk_psa.py 58.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • #  ***** 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)