Skip to content
Snippets Groups Projects
io_export_unreal_psk_psa.py 96.1 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_info = {
    
        "name": "Export Unreal Engine Format(.psk/.psa)",
    
    John Phan's avatar
    John Phan committed
        "author": "Darknet/Optimus_P-Fat/Active_Trash/Sinsoft/VendorX",
    
    John Phan's avatar
    John Phan committed
        "version": (2, 5),
        "blender": (2, 6, 3),
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        "location": "File > Export > Skeletal Mesh/Animation Data (.psk/.psa)",
    
        "description": "Export Skeleletal Mesh/Animation Data",
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        "warning": "",
        "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
            "Scripts/Import-Export/Unreal_psk_psa",
        "tracker_url": "https://projects.blender.org/tracker/index.php?"\
    
            "func=detail&aid=21366",
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        "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 random
    
    John Phan's avatar
    John Phan committed
    import bmesh
    
    Campbell Barton's avatar
    Campbell Barton committed
    from struct import pack
    
    
    # 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
    
    John Phan's avatar
    John Phan committed
    MaterialName = []
    
    # ======================================================================
    # TODO: remove this 1am hack
    nbone = 0
    exportmessage = "Export Finish" 
    
    
    ########################################################################
    # Generic Object->Integer mapping
    # the object must be usable as a dictionary key
    class ObjMap:
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        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:
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        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)
            
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        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)
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        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
                
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        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):
    
    John Phan's avatar
    John Phan committed
            data = pack('64s64siiiifffiii', str.encode(self.Name), str.encode(self.Group), self.TotalBones, self.RootInclude, self.KeyCompressionStyle, self.KeyQuotum, self.KeyPrediction, self.TrackTime, self.AnimRate, self.StartBone, self.FirstRawFrame, self.NumRawFrames)
    
    Luca Bonavita's avatar
    Luca Bonavita committed
            return data
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        def __init__(self, name, type_size):
    
    John Phan's avatar
    John Phan committed
            self.ChunkID = str.encode(name) # length=20
    
    Luca Bonavita's avatar
    Luca Bonavita committed
            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
            
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        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):
    
            print("DATA MATERIAL:",self.MaterialName)
    
    John Phan's avatar
    John Phan committed
            data = pack('64siLiLii', str.encode(self.MaterialName), self.TextureIndex, self.PolyFlags, self.AuxMaterial, self.AuxFlags, self.LodBias, self.LodStyle)
    
    Luca Bonavita's avatar
    Luca Bonavita committed
            return data
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        def __init__(self):
            self.Name = "" # length = 64
            self.Flags = 0 # DWORD
            self.NumChildren = 0
            self.ParentIndex = 0
            self.BonePos = VJointPos()
            
        def dump(self):
    
    John Phan's avatar
    John Phan committed
            data = pack('64sLii', str.encode(self.Name), self.Flags, self.NumChildren, self.ParentIndex) + self.BonePos.dump()
    
    Luca Bonavita's avatar
    Luca Bonavita committed
            return data
    
    Luca Bonavita's avatar
    Luca Bonavita committed
    
    #same as above - whatever - this is how Epic does it...        
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        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):
    
    John Phan's avatar
    John Phan committed
            data = pack('64sLii', str.encode(self.Name), self.Flags, self.NumChildren, self.ParentIndex) + self.BonePos.dump()
    
    Luca Bonavita's avatar
    Luca Bonavita committed
            return data
        
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        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
            
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        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
            
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        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()
            
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        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() 
            
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        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:
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        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)
            
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        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()
    
    John Phan's avatar
    John Phan committed
                # modified by VendorX
                m.MaterialName = MaterialName[mat_index]
    
    Luca Bonavita's avatar
    Luca Bonavita committed
                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)
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        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 = {} 
    
    Campbell Barton's avatar
    Campbell Barton committed
    
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        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):
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        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):
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        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):
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        #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
    
    #blender 2.63 format using the Operators/Commands to merge the meshes into one
    def meshmerge(selectedobjects):
        bpy.ops.object.mode_set(mode='OBJECT')
        cloneobjects = []
        if len(selectedobjects) > 1:
            print("selectedobjects:",len(selectedobjects))
            count = 0 #reset count
            for count in range(len( selectedobjects)):
                #print("Index:",count)
                if selectedobjects[count] != None:
                    cloneobjects.append(selectedobjects[count])
            for i in bpy.data.objects: i.select = False #deselect all objects
            count = 0 #reset count
            for count in range(len( cloneobjects)):
                if count == 0:
                    bpy.context.scene.objects.active = cloneobjects[count]
                    print("Set Active Object:",cloneobjects[count].name)
                cloneobjects[count].select = True
            bpy.ops.object.join()
            return cloneobjects[0]	
    
    
    ##################################################
    # http://en.wikibooks.org/wiki/Blender_3D:_Blending_Into_Python/Cookbook#Triangulate_NMesh
    
    #blender 2.63 format using the Operators/Commands to convert the mesh to tri mesh
    
        print("Converting quad to tri mesh...")
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        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')
    
        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
        scene = bpy.context.scene
        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
        print("Triangulate Mesh Done!")
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        return me_ob
    
    John Phan's avatar
    John Phan committed
    #Texture not working still find a way to get it work
    
    John Phan's avatar
    John Phan committed
    def parse_meshes(blender_meshes, psk_file):	
    
        global exportmessage
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        print("Number of Object Meshes:",len(blender_meshes))
        for current_obj in blender_meshes: #number of mesh that should be one mesh here
    
    John Phan's avatar
    John Phan committed
            #material
    
    John Phan's avatar
    John Phan committed
            if len(current_obj.material_slots) > 0:
                counter = 0
                while counter < len(current_obj.material_slots):
    
                    print("[MATERIAL IDX:",counter,"=]")
    
    John Phan's avatar
    John Phan committed
                    MaterialName.append(current_obj.material_slots[counter].name)
    
    John Phan's avatar
    John Phan committed
                    
                    #if current_obj.material_slots[counter].material.texture_slots[0] != None:
                        #if current_obj.material_slots[counter].material.texture_slots[0].texture.image.filepath != None:
                            #print("TEXTURE PATH:",current_obj.material_slots[counter].material.texture_slots[0].texture.image.filepath)
                    
    
    John Phan's avatar
    John Phan committed
                    #create the current material
    
                    matdata = psk_file.GetMatByIndex(counter)
                    matdata.MaterialName = current_obj.material_slots[counter].name
                    matdata.TextureIndex = counter
                    matdata.AuxMaterial = counter
    
    John Phan's avatar
    John Phan committed
                    #print("materials: ",MaterialName[counter])
                    counter += 1
    
                    print("PSK INDEX:",matdata.TextureIndex)
    
    Luca Bonavita's avatar
    Luca Bonavita committed
            points = ObjMap()
            wedges = ObjMap()
            discarded_face_count = 0
    
    John Phan's avatar
    John Phan committed
            has_UV = True
            faceUV = None
            scene = bpy.context.scene #get current scene
            EXPORT_APPLY_MODIFIERS = True
    
            currentmeshobject = current_obj;
            
            current_obj = triangulateNMesh(currentmeshobject) #convert tri incase
            bpy.context.scene.objects.unlink(currentmeshobject)
    
    John Phan's avatar
    John Phan committed
            me = current_obj.to_mesh(scene, EXPORT_APPLY_MODIFIERS, 'PREVIEW') #apply modified mesh and write mesh
            
    
            #print(dir(me))
            
    
    John Phan's avatar
    John Phan committed
            faceuv = len(me.uv_textures) > 0 #check if has uv texture
            if faceuv:
                uv_layer = me.tessface_uv_textures.active.data[:]
            else:
    
    John Phan's avatar
    John Phan committed
                has_UV = False
            
            for face in me.tessfaces:
    
                #print("Vertices count:",len(face.vertices))
    
    John Phan's avatar
    John Phan committed
                #get or create the current material
                object_material_index = face.material_index
                psk_file.GetMatByIndex(object_material_index)
                #print(dir(face))
                #print(dir(uv_layer[face.index]))
                if len(face.vertices) != 3:
                    #raise RuntimeError("Non-triangular face (%i)" % len(face.vertices))
                    exportmessage = "MESH IS NOT TRIANGLE (Alt + T)"
                    return
    
    Luca Bonavita's avatar
    Luca Bonavita committed
                
    
    John Phan's avatar
    John Phan committed
                if not is_1d_face(face,me):#face , Mesh
    
    Luca Bonavita's avatar
    Luca Bonavita committed
                    wedge_list = []
                    vect_list = []
    
    Luca Bonavita's avatar
    Luca Bonavita committed
                    
    
    John Phan's avatar
    John Phan committed
                    for i in range(3): #UV TEXTURE, VERTICES
                        vert_index = face.vertices[i]
                        vert = me.vertices[vert_index]
    
    Luca Bonavita's avatar
    Luca Bonavita committed
                        uv = []
                        if (has_UV):
    
                            faceUV = uv_layer[face.index]#UV TEXTURE
    
    Luca Bonavita's avatar
    Luca Bonavita committed
                            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 = [faceUV.uv[i][0],faceUV.uv[i][1]] #OR bottom works better # 24 for cube
                        else:
                            uv = [0.0, 0.0]
                        
    
    John Phan's avatar
    John Phan committed
                        uv[1] = 1.0 - uv[1] #flip uv
                        
                        if bpy.context.scene.limituv:#set limit to 0-1 or not
    
                            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
    
    Luca Bonavita's avatar
    Luca Bonavita committed
                        
                        vect_list.append(FVector(vert.co.x, vert.co.y, vert.co.z))
    
                        vpos = current_obj.matrix_local * vert.co
    
    John Phan's avatar
    John Phan committed
                        
    
    Luca Bonavita's avatar
    Luca Bonavita committed
                        # 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)
                    
                    # Determine face vertex order
                    # get normal from blender
    
    John Phan's avatar
    John Phan committed
                    no = face.normal
    
    Luca Bonavita's avatar
    Luca Bonavita committed
                    
                    # 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)
    
    John Phan's avatar
    John Phan committed
                    
    
    Luca Bonavita's avatar
    Luca Bonavita committed
                    # 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:
    
    John Phan's avatar
    John Phan committed
                        dindex0 = face.vertices[0];
                        dindex1 = face.vertices[1];
                        dindex2 = face.vertices[2];
    
    Luca Bonavita's avatar
    Luca Bonavita committed
    
    
    John Phan's avatar
    John Phan committed
                        me.vertices[dindex0].select = True
                        me.vertices[dindex1].select = True
                        me.vertices[dindex2].select = True
    
    Luca Bonavita's avatar
    Luca Bonavita committed
                        
    
    John Phan's avatar
    John Phan committed
                        #raise RuntimeError("normal vector coplanar with face! points:", me.vertices[dindex0].co, current_mesh.vertices[dindex1].co, current_mesh.vertices[dindex2].co)
                        exportmessage = "One of the face is dot or period or line, coplanar with the face"
                        return 
    
    Luca Bonavita's avatar
    Luca Bonavita committed
                    #print(dir(current_face))
    
    John Phan's avatar
    John Phan committed
                    face.select = True
    
                    #print("smooth:",(current_face.use_smooth))
    
    Luca Bonavita's avatar
    Luca Bonavita committed
                    #not sure if this right
                    #tri.SmoothingGroups
    
                    print(face.use_smooth)
    
    John Phan's avatar
    John Phan committed
                    if face.use_smooth == True:
    
    Luca Bonavita's avatar
    Luca Bonavita committed
                        tri.SmoothingGroups = 0
    
                    else:
                        tri.SmoothingGroups = 1
                    #tri.SmoothingGroups = face.use_smooth
                    #tri.SmoothingGroups = hex(face.use_smooth)
                    #print(hex(True))
                    print(hex(face.use_smooth))
    
                    #tri.SmoothingGroups = 1
    
    Luca Bonavita's avatar
    Luca Bonavita committed
                    tri.MatIndex = object_material_index
                    #print(tri)
    
                    psk_file.AddFace(tri)                
    
    Luca Bonavita's avatar
    Luca Bonavita committed
                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)
    
    John Phan's avatar
    John Phan committed
            #if len(points.dict) > 32767:
                #raise RuntimeError("Vertex point reach max limited 32767 in pack data. Your",len(points.dict))
    
    Luca Bonavita's avatar
    Luca Bonavita committed
            print (" -- Dumping Mesh Wedge -- LEN:",len(wedges.dict))
    
    Luca Bonavita's avatar
    Luca Bonavita committed
            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 obvgroup in current_obj.vertex_groups:
                #print("bone gourp build:",obvgroup.name)#print bone name
                #print(dir(obvgroup))
    
    Luca Bonavita's avatar
    Luca Bonavita committed
                vert_list = []
    
    John Phan's avatar
    John Phan committed
                for current_vert in me.vertices:
    
    Luca Bonavita's avatar
    Luca Bonavita committed
                    #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(obvgroup.index == vgroup.group):
    
    Luca Bonavita's avatar
    Luca Bonavita committed
                            p = VPoint()
    
                            vpos = current_obj.matrix_local * current_vert.co
    
    Luca Bonavita's avatar
    Luca Bonavita committed
                            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:",obvgroup.name, " No. Points:",len(vert_list))
                psk_file.VertexGroups[obvgroup.name] = vert_list
    
            #unlink copy of object from scene
            bpy.context.scene.objects.unlink(current_obj)
    
    Luca Bonavita's avatar
    Luca Bonavita committed
            
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        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
        
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        quat = FQuat()
    
    John Phan's avatar
    John Phan committed
        #print(dir(bquat))
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        quat.X = bquat.x
        quat.Y = bquat.y
        quat.Z = bquat.z
        
        quat.W = bquat.w
        return quat
    
    
    def parse_bone(blender_bone, psk_file, psa_file, parent_id, is_root_bone, parent_matrix, parent_root):
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        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_quaternion())
    
    John Phan's avatar
    John Phan committed
            #print("DIR:",dir(child_parent.matrix.to_quaternion()))
            quat_parent = child_parent.matrix.to_quaternion().inverted()
    
            parent_head = quat_parent * child_parent.head
            parent_tail = quat_parent * child_parent.tail
    
    Luca Bonavita's avatar
    Luca Bonavita committed
            
            set_position = (parent_tail - parent_head) + blender_bone.head
        else:
            # ROOT BONE
            #This for root 
    
            set_position = parent_matrix * blender_bone.head #ARMATURE OBJECT Locction
    
            rot_mat = blender_bone.matrix * parent_matrix.to_3x3() #ARMATURE OBJECT Rotation
    
    Luca Bonavita's avatar
    Luca Bonavita committed
            #print(dir(rot_mat))
            
    
            quat = make_fquat_default(rot_mat.to_quaternion())
    
    Luca Bonavita's avatar
    Luca Bonavita committed
            
        #print ("[[======= FINAL POSITION:", set_position)
        final_parent_id = parent_id
        
        #RG/RE -
    
        #if we are not separated by a small distance, create a dummy bone for the displacement
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        #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)
            final_parent_id = nbone
            nbone = nbone + 1
            #tail = tail-head
            
        my_id = nbone
        
        pb = make_vbone(blender_bone.name, final_parent_id, child_count, quat, set_position)
        psk_file.AddBone(pb)
        pbb = make_namedbonebinary(blender_bone.name, final_parent_id, child_count, quat, set_position, 1)
        psa_file.StoreBone(pbb)
    
        nbone = nbone + 1
        
        #RG - dump influences for this bone - use the data we collected in the mesh dump phase
        # to map our bones to vertex groups
        #print("///////////////////////")
        #print("set influence")
        if blender_bone.name in psk_file.VertexGroups:
            vertex_list = psk_file.VertexGroups[blender_bone.name]
            #print("vertex list:", len(vertex_list), " of >" ,blender_bone.name )
            for vertex_data in vertex_list:
                #print("set influence vettex")
                point_index = vertex_data[0]
                vertex_weight = vertex_data[1]
                influence = VRawBoneInfluence()
                influence.Weight = vertex_weight
                influence.BoneIndex = my_id
                influence.PointIndex = point_index
                #print ('Adding Bone Influence for [%s] = Point Index=%i, Weight=%f' % (blender_bone.name, point_index, vertex_weight))
                #print("adding influence")
                psk_file.AddInfluence(influence)
        
        #blender_bone.matrix_local
        #recursively dump child bones
        mainparent = parent_matrix
        #if len(blender_bone.children) > 0:
        for current_child_bone in blender_bone.children:
            parse_bone(current_child_bone, psk_file, psa_file, my_id, 0, mainparent, parent_root)
    
    
    def parse_armature(blender_armature, psk_file, psa_file):
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        print ("----- parsing armature -----")
        print ('blender_armature length: %i' % (len(blender_armature)))
        
        #magic 0 sized root bone for UT - this is where all armature dummy bones will attach
        #dont increment nbone here because we initialize it to 1 (hackity hackity hack)
    
        #count top level bones first. NOT EFFICIENT.
        child_count = 0
        for current_obj in blender_armature: