Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'io_export_unreal_psk_psa.py')
-rw-r--r--io_export_unreal_psk_psa.py2287
1 files changed, 2287 insertions, 0 deletions
diff --git a/io_export_unreal_psk_psa.py b/io_export_unreal_psk_psa.py
new file mode 100644
index 00000000..48aeb29b
--- /dev/null
+++ b/io_export_unreal_psk_psa.py
@@ -0,0 +1,2287 @@
+# ***** 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)",
+ "author": "Darknet/Optimus_P-Fat/Active_Trash/Sinsoft/VendorX",
+ "version": (2, 2),
+ "blender": (2, 5, 7),
+ "api": 36079,
+ "location": "File > Export > Skeletal Mesh/Animation Data (.psk/.psa)",
+ "description": "Export Skeleletal Mesh/Animation Data",
+ "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",
+ "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 random
+import operator
+import sys
+
+
+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
+MaterialName = []
+
+# ======================================================================
+# TODO: remove this 1am hack
+nbone = 0
+bDeleteMergeMesh = False
+exportmessage = "Export Finish"
+
+########################################################################
+# 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)
+
+class FVector(object):
+ 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)
+
+class VJointPos:
+ 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
+
+class AnimInfoBinary:
+ 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', 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)
+ return data
+
+class VChunkHeader:
+ def __init__(self, name, type_size):
+ self.ChunkID = str.encode(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
+
+class VMaterial:
+ 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', str.encode(self.MaterialName), self.TextureIndex, self.PolyFlags, self.AuxMaterial, self.AuxFlags, self.LodBias, self.LodStyle)
+ return data
+
+class VBone:
+ 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', str.encode(self.Name), self.Flags, self.NumChildren, self.ParentIndex) + self.BonePos.dump()
+ return data
+
+#same as above - whatever - this is how Epic does it...
+class FNamedBoneBinary:
+ 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', str.encode(self.Name), self.Flags, self.NumChildren, self.ParentIndex) + self.BonePos.dump()
+ return data
+
+class VRawBoneInfluence:
+ 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
+
+class VQuatAnimKey:
+ 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
+
+class VVertex(object):
+ 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()
+
+class VPoint(object):
+ 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()
+
+class VTriangle:
+ 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)
+
+class PSKFile:
+ 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()
+ # modified by VendorX
+ m.MaterialName = MaterialName[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 ('-------------------------')
+
+# PSA FILE NOTES FROM UDN:
+#
+# 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.
+#
+# 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)
+
+class PSAFile:
+ 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 ('-------------------------')
+
+####################################
+# 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):
+ global bDeleteMergeMesh
+ 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!")
+ if bDeleteMergeMesh == True:
+ print("Remove Merge tmp Mesh [ " ,object.name, " ] from scene!" )
+ bpy.ops.object.mode_set(mode='OBJECT') # set it in object
+ bpy.context.scene.objects.unlink(object)
+ else:
+ bpy.context.scene.unrealtriangulatebool = False
+ print("No need to convert tri mesh.")
+ me_ob = object
+ return me_ob
+
+# 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 bDeleteMergeMesh
+ 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
+ #bpy.ops.object.mode_set(mode='EDIT')
+ current_obj = triangulateNMesh(current_obj)
+ #print(dir(current_obj))
+ print("Mesh Name:",current_obj.name)
+ current_mesh = current_obj.data
+
+ #collect a list of the material names
+ if len(current_obj.material_slots) > 0:
+ counter = 0
+ while counter < len(current_obj.material_slots):
+ MaterialName.append(current_obj.material_slots[counter].name)
+ print("Material Name:",current_obj.material_slots[counter].name)
+ #create the current material
+ psk_file.GetMatByIndex(counter)
+ #print("materials: ",MaterialName[counter])
+ counter += 1
+ # 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
+ # modified by VendorX
+ object_material_index = current_face.material_index
+
+ 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:
+ #print ("No UVs?")
+ 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
+ #print("smooth:",(current_face.use_smooth))
+ #not sure if this right
+ #tri.SmoothingGroups
+ if current_face.use_smooth == True:
+ tri.SmoothingGroups = 1
+ else:
+ tri.SmoothingGroups = 0
+
+ tri.MatIndex = object_material_index
+ #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)
+ if len(points.dict) > 32767:
+ raise RuntimeError("Vertex point reach max limited 32767 in pack data. Your",len(points.dict))
+ 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 obvgroup in current_obj.vertex_groups:
+ #print("bone gourp build:",obvgroup.name)#print bone name
+ #print(dir(obvgroup))
+ 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(obvgroup.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:",obvgroup.name, " No. Points:",len(vert_list))
+ psk_file.VertexGroups[obvgroup.name] = vert_list
+
+ #unrealtriangulatebool #this will remove the mesh from the scene
+ '''
+ if (bpy.context.scene.unrealtriangulatebool == True) and (bDeleteMergeMesh == True):
+ #if bDeleteMergeMesh == True:
+ # print("Removing merge mesh.")
+ 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)
+ el
+ '''
+ if bDeleteMergeMesh == True:
+ print("Remove Merge 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)
+ elif bpy.context.scene.unrealtriangulatebool == True:
+ print("Remove tri 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)
+ #if bDeleteMergeMesh == True:
+ #print("Remove merge Mesh [ " ,current_obj.name, " ] from scene")
+ #bpy.ops.object.mode_set(mode='OBJECT') # set it in object
+ #bpy.context.scene.objects.unlink(current_obj)
+
+def make_fquat(bquat):
+ 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
+
+def make_fquat_default(bquat):
+ quat = FQuat()
+ #print(dir(bquat))
+ 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):
+ 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())
+ #print("DIR:",dir(child_parent.matrix.to_quaternion()))
+ quat_parent = child_parent.matrix.to_quaternion().inverted()
+ 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.to_3x3() #ARMATURE OBJECT Rotation
+ #print(dir(rot_mat))
+
+ quat = make_fquat_default(rot_mat.to_quaternion())
+
+ #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
+ #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):
+ 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:
+ current_armature = current_obj.data
+ bones = [x for x in current_armature.bones if not x.parent is None]
+ child_count += len(bones)
+
+ for current_obj in blender_armature:
+ print ("Current Armature Name: " + current_obj.name)
+ current_armature = current_obj.data
+ #armature_id = make_armature_bone(current_obj, psk_file, psa_file)
+
+ #we dont want children here - only the top level bones of the armature itself
+ #we will recursively dump the child bones as we dump these bones
+ """
+ bones = [x for x in current_armature.bones if not x.parent is None]
+ #will ingore this part of the ocde
+ """
+ if len(current_armature.bones) == 0:
+ raise RuntimeError("Warning add two bones else it will crash the unreal editor.")
+ if len(current_armature.bones) == 1:
+ raise RuntimeError("Warning add one more bone else it will crash the unreal editor.")
+
+ mainbonecount = 0;
+ for current_bone in current_armature.bones: #list the bone. #note this will list all the bones.
+ if(current_bone.parent is None):
+ mainbonecount += 1
+ print("Main Bone",mainbonecount)
+ if mainbonecount > 1:
+ #print("Warning there no main bone.")
+ raise RuntimeError("There too many Main bones. Number main bones:",mainbonecount)
+ for current_bone in current_armature.bones: #list the bone. #note this will list all the bones.
+ if(current_bone.parent is None):
+ parse_bone(current_bone, psk_file, psa_file, 0, 0, current_obj.matrix_local, None)
+ break
+
+# get blender objects by type
+def get_blender_objects(objects, intype):
+ return [x for x in objects if x.type == intype]
+
+#strips current extension (if any) from filename and replaces it with extension passed in
+def make_filename_ext(filename, extension):
+ new_filename = ''
+ extension_index = filename.find('.')
+
+ if extension_index == -1:
+ new_filename = filename + extension
+ else:
+ new_filename = filename[0:extension_index] + extension
+
+ return new_filename
+
+# returns the quaternion Grassman product a*b
+# this is the same as the rotation a(b(x))
+# (ie. the same as B*A if A and B are matrices representing
+# the rotations described by quaternions a and b)
+def grassman(a, b):
+ return mathutils.Quaternion(
+ a.w*b.w - a.x*b.x - a.y*b.y - a.z*b.z,
+ a.w*b.x + a.x*b.w + a.y*b.z - a.z*b.y,
+ a.w*b.y - a.x*b.z + a.y*b.w + a.z*b.x,
+ a.w*b.z + a.x*b.y - a.y*b.x + a.z*b.w)
+
+def parse_animation(blender_scene, blender_armatures, psa_file):
+ #to do list:
+ #need to list the action sets
+ #need to check if there animation
+ #need to check if animation is has one frame then exit it
+ print ('\n----- parsing animation -----')
+ render_data = blender_scene.render
+ bHaveAction = True
+
+ anim_rate = render_data.fps
+
+ print("==== Blender Settings ====")
+ print ('Scene: %s Start Frame: %i, End Frame: %i' % (blender_scene.name, blender_scene.frame_start, blender_scene.frame_end))
+ print ('Frames Per Sec: %i' % anim_rate)
+ print ("Default FPS: 24" )
+
+ cur_frame_index = 0
+ if (bpy.context.scene.UEActionSetSettings == '1') or (bpy.context.scene.UEActionSetSettings == '2'):
+ print("Action Set(s) Settings Idx:",bpy.context.scene.UEActionSetSettings)
+ print("[==== Action list Start====]")
+
+ print("Number of Action set(s):",len(bpy.data.actions))
+
+ for action in bpy.data.actions:#current number action sets
+ print("========>>>>>")
+ print("Action Name:",action.name)
+ #print("Groups:")
+ #for bone in action.groups:
+ #print("> Name: ",bone.name)
+ #print(dir(bone))
+
+ print("[==== Action list End ====]")
+
+ amatureobject = None #this is the armature set to none
+ bonenames = [] #bone name of the armature bones list
+
+ for arm in blender_armatures:
+ amatureobject = arm
+ #print(dir(amatureobject))
+ collection = amatureobject.myCollectionUEA #collection of the object
+ print("\n[==== Armature Object ====]")
+ if amatureobject != None:
+ print("Name:",amatureobject.name)
+ print("Number of bones:", len(amatureobject.pose.bones))
+ for bone in amatureobject.pose.bones:
+ bonenames.append(bone.name)
+ print("[=========================]")
+
+ for ActionNLA in bpy.data.actions:
+ FoundAction = True
+ if bpy.context.scene.UEActionSetSettings == '2':
+ for c in collection:
+ if c.name == ActionNLA.name:
+ if c.mybool == True:
+ FoundAction = True
+ else:
+ FoundAction = False
+ break
+ if FoundAction == False:
+ print("========================================")
+ print("Skipping Action Set!",ActionNLA.name)
+ print("========================================")
+ #break
+ print("\n==== Action Set ====")
+ nobone = 0
+ baction = True
+ #print("\nChecking actions matching groups with bone names...")
+ #Check if the bone names matches the action groups names
+ for group in ActionNLA.groups:
+ for abone in bonenames:
+ #print("name:>>",abone)
+ if abone == group.name:
+ nobone += 1
+ break
+ #if action groups matches the bones length and names matching the gourps do something
+ if (len(ActionNLA.groups) == len(bonenames)) and (nobone == len(ActionNLA.groups)):
+ #print("Action Set match: Pass")
+ baction = True
+ else:
+ #print("Action Set match: Fail")
+ #print("Action Name:",ActionNLA.name)
+ baction = False
+
+ if (baction == True) and (FoundAction == True):
+ arm = amatureobject #set armature object
+ if not arm.animation_data:
+ print("======================================")
+ print("Check Animation Data: None")
+ print("Armature has no animation, skipping...")
+ print("======================================")
+ break
+
+ if not arm.animation_data.action:
+ print("======================================")
+ print("Check Action: None")
+ print("Armature has no animation, skipping...")
+ print("======================================")
+ break
+ print("Last Action Name:",arm.animation_data.action.name)
+ arm.animation_data.action = ActionNLA
+ print("Set Action Name:",arm.animation_data.action.name)
+ bpy.context.scene.update()
+ act = arm.animation_data.action
+ action_name = act.name
+
+ if not len(act.fcurves):
+ print("//===========================================================")
+ print("// None bone pose set keys for this action set... skipping...")
+ print("//===========================================================")
+ bHaveAction = False
+
+ #this deal with action export control
+ if bHaveAction == True:
+ print("------------------------------------")
+ print("Action Name:",action_name)
+ #look for min and max frame that current set keys
+ framemin, framemax = act.frame_range
+ #print("max frame:",framemax)
+ start_frame = int(framemin)
+ end_frame = int(framemax)
+ scene_frames = range(start_frame, end_frame+1)
+ frame_count = len(scene_frames)
+ #===================================================
+ anim = AnimInfoBinary()
+ anim.Name = action_name
+ anim.Group = "" #what is group?
+ anim.NumRawFrames = frame_count
+ anim.AnimRate = anim_rate
+ anim.FirstRawFrame = cur_frame_index
+ #===================================================
+ count_previous_keys = len(psa_file.RawKeys.Data)
+ print("Frame Key Set Count:",frame_count, "Total Frame:",frame_count)
+ #print("init action bones...")
+ unique_bone_indexes = {}
+ # bone lookup table
+ bones_lookup = {}
+
+ #build bone node for animation keys needed to be set
+ for bone in arm.data.bones:
+ bones_lookup[bone.name] = bone
+ #print("bone name:",bone.name)
+ frame_count = len(scene_frames)
+ #print ('Frame Count: %i' % frame_count)
+ pose_data = arm.pose
+
+ #these must be ordered in the order the bones will show up in the PSA file!
+ ordered_bones = {}
+ ordered_bones = sorted([(psa_file.UseBone(x.name), x) for x in pose_data.bones], key=operator.itemgetter(0))
+
+ #############################
+ # ORDERED FRAME, BONE
+ #for frame in scene_frames:
+
+ for i in range(frame_count):
+ frame = scene_frames[i]
+ #LOUD
+ #print ("==== outputting frame %i ===" % frame)
+
+ if frame_count > i+1:
+ next_frame = scene_frames[i+1]
+ #print "This Frame: %i, Next Frame: %i" % (frame, next_frame)
+ else:
+ next_frame = -1
+ #print "This Frame: %i, Next Frame: NONE" % frame
+
+ #frame start from 1 as number one from blender
+ blender_scene.frame_set(frame)
+
+ cur_frame_index = cur_frame_index + 1
+ for bone_data in ordered_bones:
+ bone_index = bone_data[0]
+ pose_bone = bone_data[1]
+ #print("[=====POSE NAME:",pose_bone.name)
+
+ #print("LENG >>.",len(bones_lookup))
+ blender_bone = bones_lookup[pose_bone.name]
+
+ #just need the total unique bones used, later for this AnimInfoBinary
+ unique_bone_indexes[bone_index] = bone_index
+ #LOUD
+ #print ("-------------------", pose_bone.name)
+ head = pose_bone.head
+
+ posebonemat = mathutils.Matrix(pose_bone.matrix)
+ #print("quat",posebonemat)
+ #
+ # Error looop action get None in matrix
+ # looping on each armature give invert and normalize for None
+ #
+ parent_pose = pose_bone.parent
+ if parent_pose != None:
+ parentposemat = mathutils.Matrix(parent_pose.matrix)
+ #blender 2.4X it been flip around with new 2.50 (mat1 * mat2) should now be (mat2 * mat1)
+ #print("posebonemat",posebonemat)
+ #print("parentposemat",parentposemat.inverted())
+ #print("parentposemat",dir(parentposemat))
+ '''
+ parentposematinvert = parentposemat.invert()
+ if parentposematinvert == None:
+ posebonemat = parentposemat.inverted() * posebonemat
+ else:
+ posebonemat = parentposemat.invert() * posebonemat
+ '''
+ posebonemat = parentposemat.invert() * posebonemat
+
+ head = posebonemat.to_translation()
+ quat = posebonemat.to_quaternion().normalize()
+ #print("to_quaternion:",posebonemat.to_quaternion())
+ quat2 = mathutils.Quaternion(posebonemat.to_quaternion())
+ #print("to_quaternion2:",dir(quat2))
+ #if quat == None:
+ #quat = posebonemat.to_quaternion().normalized()
+
+
+ #print("quat",(posebonemat.to_quaternion().normalized()))
+ #print("quat",dir(posebonemat.to_quaternion().normalized()))
+ #print("pose_bone name:",pose_bone.name)
+ #print("head",head)
+ #print("quat",quat)
+ vkey = VQuatAnimKey()
+ vkey.Position.X = head.x
+ vkey.Position.Y = head.y
+ vkey.Position.Z = head.z
+
+ if parent_pose != None:
+ quat = make_fquat(quat)
+ else:
+ quat = make_fquat_default(quat)
+
+ vkey.Orientation = quat
+ #print("Head:",head)
+ #print("Orientation",quat)
+
+ #time from now till next frame = diff / framesPerSec
+ if next_frame >= 0:
+ diff = next_frame - frame
+ else:
+ diff = 1.0
+
+ #print ("Diff = ", diff)
+ vkey.Time = float(diff)/float(anim_rate)
+ psa_file.AddRawKey(vkey)
+
+ #done looping frames
+ #done looping armatures
+ #continue adding animInfoBinary counts here
+
+ anim.TotalBones = len(unique_bone_indexes)
+ print("Bones Count:",anim.TotalBones)
+ anim.TrackTime = float(frame_count) / anim.AnimRate
+ print("Time Track Frame:",anim.TrackTime)
+ psa_file.AddAnimation(anim)
+ print("==== Finish Action Build(s) ====")
+ else:
+ print("[==== Action Set Single Export====]")
+ #list of armature objects
+ for arm in blender_armatures:
+ #check if there animation data from armature or something
+
+ if not arm.animation_data:
+ print("======================================")
+ print("Check Animation Data: None")
+ print("Armature has no animation, skipping...")
+ print("======================================")
+ break
+
+ if not arm.animation_data.action:
+ print("======================================")
+ print("Check Action: None")
+ print("Armature has no animation, skipping...")
+ print("======================================")
+ break
+ act = arm.animation_data.action
+ #print(dir(act))
+ action_name = act.name
+
+ if not len(act.fcurves):
+ print("//===========================================================")
+ print("// None bone pose set keys for this action set... skipping...")
+ print("//===========================================================")
+ bHaveAction = False
+
+ #this deal with action export control
+ if bHaveAction == True:
+ print("---- Action Start ----")
+ print("Action Name:",action_name)
+ #look for min and max frame that current set keys
+ framemin, framemax = act.frame_range
+ #print("max frame:",framemax)
+ start_frame = int(framemin)
+ end_frame = int(framemax)
+ scene_frames = range(start_frame, end_frame+1)
+ frame_count = len(scene_frames)
+ #===================================================
+ anim = AnimInfoBinary()
+ anim.Name = action_name
+ anim.Group = "" #what is group?
+ anim.NumRawFrames = frame_count
+ anim.AnimRate = anim_rate
+ anim.FirstRawFrame = cur_frame_index
+ #===================================================
+ count_previous_keys = len(psa_file.RawKeys.Data)
+ print("Frame Key Set Count:",frame_count, "Total Frame:",frame_count)
+ #print("init action bones...")
+ unique_bone_indexes = {}
+ # bone lookup table
+ bones_lookup = {}
+
+ #build bone node for animation keys needed to be set
+ for bone in arm.data.bones:
+ bones_lookup[bone.name] = bone
+ #print("bone name:",bone.name)
+ frame_count = len(scene_frames)
+ #print ('Frame Count: %i' % frame_count)
+ pose_data = arm.pose
+
+ #these must be ordered in the order the bones will show up in the PSA file!
+ ordered_bones = {}
+ ordered_bones = sorted([(psa_file.UseBone(x.name), x) for x in pose_data.bones], key=operator.itemgetter(0))
+
+ #############################
+ # ORDERED FRAME, BONE
+ #for frame in scene_frames:
+
+ for i in range(frame_count):
+ frame = scene_frames[i]
+ #LOUD
+ #print ("==== outputting frame %i ===" % frame)
+
+ if frame_count > i+1:
+ next_frame = scene_frames[i+1]
+ #print "This Frame: %i, Next Frame: %i" % (frame, next_frame)
+ else:
+ next_frame = -1
+ #print "This Frame: %i, Next Frame: NONE" % frame
+
+ #frame start from 1 as number one from blender
+ blender_scene.frame_set(frame)
+
+ cur_frame_index = cur_frame_index + 1
+ for bone_data in ordered_bones:
+ bone_index = bone_data[0]
+ pose_bone = bone_data[1]
+ #print("[=====POSE NAME:",pose_bone.name)
+
+ #print("LENG >>.",len(bones_lookup))
+ blender_bone = bones_lookup[pose_bone.name]
+
+ #just need the total unique bones used, later for this AnimInfoBinary
+ unique_bone_indexes[bone_index] = bone_index
+ #LOUD
+ #print ("-------------------", pose_bone.name)
+ head = pose_bone.head
+
+ posebonemat = mathutils.Matrix(pose_bone.matrix)
+ parent_pose = pose_bone.parent
+ if parent_pose != None:
+ parentposemat = mathutils.Matrix(parent_pose.matrix)
+ #blender 2.4X it been flip around with new 2.50 (mat1 * mat2) should now be (mat2 * mat1)
+ posebonemat = parentposemat.inverted() * posebonemat
+ #print("parentposemat ::::",parentposemat)
+ #print("parentposemat ::::",dir(posebonemat.to_quaternion()))
+ head = posebonemat.to_translation()
+ quat = posebonemat.to_quaternion().normalized()
+ #print("position :",posebonemat.to_translation(),"quat",posebonemat.to_quaternion().normalized())
+ #print("posebonemat",posebonemat)
+ vkey = VQuatAnimKey()
+ vkey.Position.X = head.x
+ vkey.Position.Y = head.y
+ vkey.Position.Z = head.z
+ #print("quat:",quat)
+ if parent_pose != None:
+ quat = make_fquat(quat)
+ else:
+ quat = make_fquat_default(quat)
+
+ vkey.Orientation = quat
+ #print("Head:",head)
+ #print("Orientation",quat)
+
+ #time from now till next frame = diff / framesPerSec
+ if next_frame >= 0:
+ diff = next_frame - frame
+ else:
+ diff = 1.0
+
+ #print ("Diff = ", diff)
+ vkey.Time = float(diff)/float(anim_rate)
+ psa_file.AddRawKey(vkey)
+
+ #done looping frames
+ #done looping armatures
+ #continue adding animInfoBinary counts here
+
+ anim.TotalBones = len(unique_bone_indexes)
+ print("Bones Count:",anim.TotalBones)
+ anim.TrackTime = float(frame_count) / anim.AnimRate
+ print("Time Track Frame:",anim.TrackTime)
+ psa_file.AddAnimation(anim)
+ print("---- Action End ----")
+ print("==== Finish Action Build ====")
+
+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:
+ me_da = selectedobjects[count].data.copy() #copy data
+ me_ob = selectedobjects[count].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
+ print("Index:",count,"clone object",me_ob.name)
+ cloneobjects.append(me_ob)
+ #bpy.ops.object.mode_set(mode='OBJECT')
+ for i in bpy.data.objects: i.select = False #deselect all objects
+ count = 0 #reset count
+ #bpy.ops.object.mode_set(mode='OBJECT')
+ 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]
+
+def fs_callback(filename, context):
+ #this deal with repeat export and the reset settings
+ global nbone, exportmessage, bDeleteMergeMesh
+ nbone = 0
+
+ start_time = time.clock()
+
+ print ("========EXPORTING TO UNREAL SKELETAL MESH FORMATS========\r\n")
+ print("Blender Version:", bpy.app.version_string)
+
+ psk = PSKFile()
+ psa = PSAFile()
+
+ #sanity check - this should already have the extension, but just in case, we'll give it one if it doesn't
+ psk_filename = make_filename_ext(filename, '.psk')
+
+ #make the psa filename
+ psa_filename = make_filename_ext(filename, '.psa')
+
+ print ('PSK File: ' + psk_filename)
+ print ('PSA File: ' + psa_filename)
+
+ barmature = True
+ bmesh = True
+ blender_meshes = []
+ blender_armature = []
+ selectmesh = []
+ selectarmature = []
+
+ current_scene = context.scene
+ cur_frame = current_scene.frame_current #store current frame before we start walking them during animation parse
+ objects = current_scene.objects
+
+ print("Checking object count...")
+ for next_obj in objects:
+ if next_obj.type == 'MESH':
+ blender_meshes.append(next_obj)
+ if (next_obj.select):
+ #print("mesh object select")
+ selectmesh.append(next_obj)
+ if next_obj.type == 'ARMATURE':
+ blender_armature.append(next_obj)
+ if (next_obj.select):
+ #print("armature object select")
+ selectarmature.append(next_obj)
+
+ print("Mesh Count:",len(blender_meshes)," Armature Count:",len(blender_armature))
+ print("====================================")
+ print("Checking Mesh Condtion(s):")
+ #if there 1 mesh in scene add to the array
+ if len(blender_meshes) == 1:
+ print(" - One Mesh Scene")
+ #if there more than one mesh and one mesh select add to array
+ elif (len(blender_meshes) > 1) and (len(selectmesh) == 1):
+ blender_meshes = []
+ blender_meshes.append(selectmesh[0])
+ print(" - One Mesh [Select]")
+ elif (len(blender_meshes) > 1) and (len(selectmesh) >= 1):
+ #code build check for merge mesh before ops
+ print("More than one mesh is selected!")
+ centermesh = []
+ notcentermesh = []
+ countm = 0
+ for countm in range(len(selectmesh)):
+ #selectmesh[]
+ if selectmesh[countm].location.x == 0 and selectmesh[countm].location.y == 0 and selectmesh[countm].location.z == 0:
+ centermesh.append(selectmesh[countm])
+ else:
+ notcentermesh.append(selectmesh[countm])
+ if len(centermesh) > 0:
+ print("Center Object Found!")
+ blender_meshes = []
+ selectmesh = []
+ countm = 0
+ for countm in range(len(centermesh)):
+ selectmesh.append(centermesh[countm])
+ for countm in range(len(notcentermesh)):
+ selectmesh.append(notcentermesh[countm])
+ blender_meshes.append(meshmerge(selectmesh))
+ bDeleteMergeMesh = True
+ else:
+ bDeleteMergeMesh = False
+ bmesh = False
+ print("Center Object Not Found")
+ else:
+ print(" - Too Many Meshes!")
+ print(" - Select One Mesh Object!")
+ bmesh = False
+ bDeleteMergeMesh = False
+
+ print("====================================")
+ print("Checking Armature Condtion(s):")
+ if len(blender_armature) == 1:
+ print(" - One Armature Scene")
+ elif (len(blender_armature) > 1) and (len(selectarmature) == 1):
+ print(" - One Armature [Select]")
+ else:
+ print(" - Too Armature Meshes!")
+ print(" - Select One Armature Object Only!")
+ barmature = False
+ bMeshScale = True
+ bMeshCenter = True
+ if len(blender_meshes) > 0:
+ if blender_meshes[0].scale.x == 1 and blender_meshes[0].scale.y == 1 and blender_meshes[0].scale.z == 1:
+ #print("Okay")
+ bMeshScale = True
+ else:
+ print("Error, Mesh Object not scale right should be (1,1,1).")
+ bMeshScale = False
+ if blender_meshes[0].location.x == 0 and blender_meshes[0].location.y == 0 and blender_meshes[0].location.z == 0:
+ #print("Okay")
+ bMeshCenter = True
+ else:
+ print("Error, Mesh Object not center.",blender_meshes[0].location)
+ bMeshCenter = False
+ else:
+ bmesh = False
+ bArmatureScale = True
+ bArmatureCenter = True
+ if blender_armature[0] !=None:
+ if blender_armature[0].scale.x == 1 and blender_armature[0].scale.y == 1 and blender_armature[0].scale.z == 1:
+ #print("Okay")
+ bArmatureScale = True
+ else:
+ print("Error, Armature Object not scale right should be (1,1,1).")
+ bArmatureScale = False
+ if blender_armature[0].location.x == 0 and blender_armature[0].location.y == 0 and blender_armature[0].location.z == 0:
+ #print("Okay")
+ bArmatureCenter = True
+ else:
+ print("Error, Armature Object not center.",blender_armature[0].location)
+ bArmatureCenter = False
+
+
+
+ #print("location:",blender_armature[0].location.x)
+
+ if (bmesh == False) or (barmature == False) or (bArmatureCenter == False) or (bArmatureScale == False)or (bMeshScale == False) or (bMeshCenter == False):
+ exportmessage = "Export Fail! Check Log."
+ print("=================================")
+ print("= Export Fail! =")
+ print("=================================")
+ else:
+ exportmessage = "Export Finish!"
+ #print("blender_armature:",dir(blender_armature[0]))
+ #print(blender_armature[0].scale)
+
+ try:
+ #######################
+ # STEP 1: MESH DUMP
+ # we build the vertexes, wedges, and faces in here, as well as a vertexgroup lookup table
+ # for the armature parse
+ print("//===============================")
+ print("// STEP 1")
+ print("//===============================")
+ parse_meshes(blender_meshes, psk)
+ except:
+ context.scene.frame_set(cur_frame) #set frame back to original frame
+ print ("Exception during Mesh Parse")
+ raise
+
+ try:
+ #######################
+ # STEP 2: ARMATURE DUMP
+ # IMPORTANT: do this AFTER parsing meshes - we need to use the vertex group data from
+ # the mesh parse in here to generate bone influences
+ print("//===============================")
+ print("// STEP 2")
+ print("//===============================")
+ parse_armature(blender_armature, psk, psa)
+
+ except:
+ context.scene.frame_set(cur_frame) #set frame back to original frame
+ print ("Exception during Armature Parse")
+ raise
+
+ try:
+ #######################
+ # STEP 3: ANIMATION DUMP
+ # IMPORTANT: do AFTER parsing bones - we need to do bone lookups in here during animation frames
+ print("//===============================")
+ print("// STEP 3")
+ print("//===============================")
+ parse_animation(current_scene, blender_armature, psa)
+
+ except:
+ context.scene.frame_set(cur_frame) #set frame back to original frame
+ print ("Exception during Animation Parse")
+ raise
+
+ # reset current frame
+
+ context.scene.frame_set(cur_frame) #set frame back to original frame
+
+ ##########################
+ # FILE WRITE
+ print("//===========================================")
+ print("// bExportPsk:",bpy.context.scene.unrealexportpsk," bExportPsa:",bpy.context.scene.unrealexportpsa)
+ print("//===========================================")
+ if bpy.context.scene.unrealexportpsk == True:
+ print("Writing Skeleton Mesh Data...")
+ #RG - dump psk file
+ psk.PrintOut()
+ file = open(psk_filename, "wb")
+ file.write(psk.dump())
+ file.close()
+ print ("Successfully Exported File: " + psk_filename)
+ if bpy.context.scene.unrealexportpsa == True:
+ print("Writing Animaiton Data...")
+ #RG - dump psa file
+ if not psa.IsEmpty():
+ psa.PrintOut()
+ file = open(psa_filename, "wb")
+ file.write(psa.dump())
+ file.close()
+ print ("Successfully Exported File: " + psa_filename)
+ else:
+ print ("No Animations (.psa file) to Export")
+
+ print ('PSK/PSA Export Script finished in %.2f seconds' % (time.clock() - start_time))
+ print( "Current Script version: ",bl_info['version'])
+ #MSG BOX EXPORT COMPLETE
+ #...
+
+ #DONE
+ print ("PSK/PSA Export Complete")
+
+def write_data(path, context):
+ print("//============================")
+ print("// running psk/psa export...")
+ print("//============================")
+ fs_callback(path, context)
+ pass
+
+from bpy.props import *
+
+exporttypedata = []
+
+# [index,text field,0] #or something like that
+exporttypedata.append(("0","PSK","Export PSK"))
+exporttypedata.append(("1","PSA","Export PSA"))
+exporttypedata.append(("2","ALL","Export ALL"))
+
+bpy.types.Scene.unrealfpsrate = IntProperty(
+ name="fps rate",
+ description="Set the frame per second (fps) for unreal.",
+ default=24,min=1,max=100)
+
+bpy.types.Scene.unrealexport_settings = EnumProperty(
+ name="Export:",
+ description="Select a export settings (psk/psa/all)...",
+ items = exporttypedata, default = '0')
+
+bpy.types.Scene.UEActionSetSettings = EnumProperty(
+ name="Action Set(s) Export Type",
+ description="For Exporting Single, All, and Select Action Set(s).",
+ items = [("0","Single","Single Action Set Export"),("1","All","All Action Sets Export"),("2","Select","Select Action Set(s) Export")], default = '0')
+
+bpy.types.Scene.unrealtriangulatebool = BoolProperty(
+ name="Triangulate Mesh",
+ description="Convert Quad to Tri Mesh Boolean...",
+ default=False)
+
+bpy.types.Scene.unrealdisplayactionsets = BoolProperty(
+ name="Show Action Set(s)",
+ description="Display Action Sets Information.",
+ default=False)
+
+bpy.types.Scene.unrealexportpsk = BoolProperty(
+ name="bool export psa",
+ description="bool for exporting this psk format",
+ default=True)
+
+bpy.types.Scene.unrealexportpsa = BoolProperty(
+ name="bool export psa",
+ description="bool for exporting this psa format",
+ default=True)
+
+class UEAPropertyGroup(bpy.types.PropertyGroup):
+ ## create Properties for the collection entries:
+ mystring = bpy.props.StringProperty()
+ mybool = bpy.props.BoolProperty(name="Export",description="Check if you want to export the action set.",default = False)
+
+bpy.utils.register_class(UEAPropertyGroup)
+
+## create CollectionProperty and link it to the property class
+bpy.types.Object.myCollectionUEA = bpy.props.CollectionProperty(type = UEAPropertyGroup)
+bpy.types.Object.myCollectionUEA_index = bpy.props.IntProperty(min = -1, default = -1)
+
+## create operator to add or remove entries to/from the Collection
+class OBJECT_OT_add_remove_Collection_Items_UE(bpy.types.Operator):
+ bl_label = "Add or Remove"
+ bl_idname = "collection.add_remove_ueactions"
+ __doc__ = """Button for Add, Remove, Refresh Action Set(s) list."""
+ set = bpy.props.StringProperty()
+
+ def invoke(self, context, event):
+ obj = context.object
+ collection = obj.myCollectionUEA
+ if self.set == "remove":
+ print("remove")
+ index = obj.myCollectionUEA_index
+ collection.remove(index) # This remove on item in the collection list function of index value
+ if self.set == "add":
+ print("add")
+ added = collection.add() # This add at the end of the collection list
+ added.name = "Action"+ str(random.randrange(0, 101, 2))
+ if self.set == "refresh":
+ print("refresh")
+ ArmatureSelect = None
+ ActionNames = []
+ BoneNames = []
+ for obj in bpy.data.objects:
+ if obj.type == 'ARMATURE' and obj.select == True:
+ print("Armature Name:",obj.name)
+ ArmatureSelect = obj
+ for bone in obj.pose.bones:
+ BoneNames.append(bone.name)
+ break
+ actionsetmatchcount = 0
+ for ActionNLA in bpy.data.actions:
+ nobone = 0
+ for group in ActionNLA.groups:
+ for abone in BoneNames:
+ if abone == group.name:
+ nobone += 1
+ break
+ if (len(ActionNLA.groups) == len(BoneNames)) and (nobone == len(ActionNLA.groups)):
+ actionsetmatchcount += 1
+ ActionNames.append(ActionNLA.name)
+ #print(dir(collection))
+ #print("collection:",len(collection))
+ print("action list check")
+ for action in ActionNames:
+ BfoundAction = False
+ #print("action:",action)
+ for c in collection:
+ #print(c.name)
+ if c.name == action:
+ BfoundAction = True
+ break
+ if BfoundAction == False:
+ added = collection.add() # This add at the end of the collection list
+ added.name = action
+ #print("finish...")
+ return {'FINISHED'}
+
+class ExportUDKAnimData(bpy.types.Operator):
+ global exportmessage
+ '''Export Skeleton Mesh / Animation Data file(s)'''
+ bl_idname = "export_anim.udk" # this is important since its how bpy.ops.export.udk_anim_data is constructed
+ bl_label = "Export PSK/PSA"
+ __doc__ = """One mesh and one armature else select one mesh or armature to be exported."""
+
+ # List of operator properties, the attributes will be assigned
+ # to the class instance from the operator settings before calling.
+
+ filepath = StringProperty(name="File Path", description="Filepath used for exporting the PSA file", maxlen= 1024, default= "", subtype='FILE_PATH')
+ pskexportbool = BoolProperty(name="Export PSK", description="Export Skeletal Mesh", default= True)
+ psaexportbool = BoolProperty(name="Export PSA", description="Export Action Set (Animation Data)", default= True)
+ actionexportall = BoolProperty(name="All Actions", description="This will export all the actions that matches the current armature.", default=False)
+
+ @classmethod
+ def poll(cls, context):
+ return context.active_object != None
+
+ def execute(self, context):
+ #check if skeleton mesh is needed to be exported
+ if (self.pskexportbool):
+ bpy.context.scene.unrealexportpsk = True
+ else:
+ bpy.context.scene.unrealexportpsk = False
+ #check if animation data is needed to be exported
+ if (self.psaexportbool):
+ bpy.context.scene.unrealexportpsa = True
+ else:
+ bpy.context.scene.unrealexportpsa = False
+
+ if (self.actionexportall):
+ bpy.context.scene.unrealactionexportall = True
+ else:
+ bpy.context.scene.unrealactionexportall = False
+
+ write_data(self.filepath, context)
+
+ self.report({'WARNING', 'INFO'}, exportmessage)
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ wm = context.window_manager
+ wm.fileselect_add(self)
+ return {'RUNNING_MODAL'}
+
+class VIEW3D_PT_unrealtools_objectmode(bpy.types.Panel):
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "TOOLS"
+ bl_label = "Unreal Tools"
+
+ @classmethod
+ def poll(cls, context):
+ return context.active_object
+
+ def draw(self, context):
+ layout = self.layout
+ rd = context.scene
+ layout.prop(rd, "unrealexport_settings",expand=True)
+ layout.operator(OBJECT_OT_UnrealExport.bl_idname)
+ #FPS #it use the real data from your scene
+ layout.prop(rd.render, "fps")
+
+ layout.prop(rd, "UEActionSetSettings")
+ layout.prop(rd, "unrealdisplayactionsets")
+
+ ArmatureSelect = None
+ for obj in bpy.data.objects:
+ if obj.type == 'ARMATURE' and obj.select == True:
+ #print("Armature Name:",obj.name)
+ ArmatureSelect = obj
+ break
+ #display armature actions list
+ if ArmatureSelect != None and rd.unrealdisplayactionsets == True:
+ layout.label(("Selected: "+ArmatureSelect.name))
+ row = layout.row()
+ row.template_list(obj, "myCollectionUEA", obj, "myCollectionUEA_index") # This show list for the collection
+ col = row.column(align=True)
+ col.operator("collection.add_remove_ueactions", icon="ZOOMIN", text="").set = "add" # This show a plus sign button
+ col.operator("collection.add_remove_ueactions", icon="ZOOMOUT", text="").set = "remove" # This show a minus sign button
+ col.operator("collection.add_remove_ueactions", icon="FILE_REFRESH", text="").set = "refresh" # This show a refresh sign button
+
+ ##change name of Entry:
+ if obj.myCollectionUEA:
+ entry = obj.myCollectionUEA[obj.myCollectionUEA_index]
+ layout.prop(entry, "name")
+ layout.prop(entry, "mybool")
+ layout.operator(OBJECT_OT_UTSelectedFaceSmooth.bl_idname)
+ layout.operator(OBJECT_OT_UTRebuildArmature.bl_idname)
+ layout.operator(OBJECT_OT_UTRebuildMesh.bl_idname)
+ layout.operator(OBJECT_OT_ToggleConsle.bl_idname)
+
+class OBJECT_OT_UnrealExport(bpy.types.Operator):
+ global exportmessage
+ bl_idname = "export_mesh.udk" # XXX, name???
+ bl_label = "Unreal Export"
+ __doc__ = """Select export setting for .psk/.psa or both."""
+
+ def invoke(self, context, event):
+ print("Init Export Script:")
+ if(int(bpy.context.scene.unrealexport_settings) == 0):
+ bpy.context.scene.unrealexportpsk = True
+ bpy.context.scene.unrealexportpsa = False
+ print("Exporting PSK...")
+ if(int(bpy.context.scene.unrealexport_settings) == 1):
+ bpy.context.scene.unrealexportpsk = False
+ bpy.context.scene.unrealexportpsa = True
+ print("Exporting PSA...")
+ if(int(bpy.context.scene.unrealexport_settings) == 2):
+ bpy.context.scene.unrealexportpsk = True
+ bpy.context.scene.unrealexportpsa = True
+ print("Exporting ALL...")
+
+ default_path = os.path.splitext(bpy.data.filepath)[0] + ".psk"
+ fs_callback(default_path, bpy.context)
+ #self.report({'WARNING', 'INFO'}, exportmessage)
+ self.report({'INFO'}, exportmessage)
+ return{'FINISHED'}
+
+class OBJECT_OT_ToggleConsle(bpy.types.Operator):
+ global exportmessage
+ bl_idname = "object.toggleconsle" # XXX, name???
+ bl_label = "Toggle Console"
+ __doc__ = "Show or Hide Console."
+
+ def invoke(self, context, event):
+ print("Init Export Script:")
+ bpy.ops.wm.console_toggle()
+ return{'FINISHED'}
+
+class OBJECT_OT_UTSelectedFaceSmooth(bpy.types.Operator):
+ bl_idname = "object.utselectfacesmooth" # XXX, name???
+ bl_label = "Select Smooth faces"
+ __doc__ = """It will only select smooth faces that is select mesh."""
+
+ def invoke(self, context, event):
+ print("----------------------------------------")
+ print("Init Select Face(s):")
+ bselected = False
+ for obj in bpy.data.objects:
+ if obj.type == 'MESH' and obj.select == True:
+ smoothcount = 0
+ flatcount = 0
+ bpy.ops.object.mode_set(mode='OBJECT')#it need to go into object mode to able to select the faces
+ for i in bpy.context.scene.objects: i.select = False #deselect all objects
+ obj.select = True #set current object select
+ bpy.context.scene.objects.active = obj #set active object
+ for face in obj.data.faces:
+ if face.use_smooth == True:
+ face.select = True
+ smoothcount += 1
+ else:
+ flatcount += 1
+ face.select = False
+ #print("selected:",face.select)
+ #print(("smooth:",face.use_smooth))
+ bpy.context.scene.update()
+ bpy.ops.object.mode_set(mode='EDIT')
+ print("Select Smooth Count(s):",smoothcount," Flat Count(s):",flatcount)
+ bselected = True
+ break
+ if bselected:
+ print("Selected Face(s) Exectue!")
+ self.report({'INFO'}, "Selected Face(s) Exectue!")
+ else:
+ print("Didn't select Mesh Object!")
+ self.report({'INFO'}, "Didn't Select Mesh Object!")
+ print("----------------------------------------")
+ return{'FINISHED'}
+
+class OBJECT_OT_UTRebuildArmature(bpy.types.Operator):
+ bl_idname = "object.utrebuildarmature" # XXX, name???
+ bl_label = "Rebuild Armature"
+ __doc__ = """If mesh is deform when importing to unreal engine try this. It rebuild the bones one at the time by select one armature object scrape to raw setup build."""
+
+ def invoke(self, context, event):
+ print("----------------------------------------")
+ print("Init Rebuild Armature...")
+ bselected = False
+ for obj in bpy.data.objects:
+ if obj.type == 'ARMATURE' and obj.select == True:
+ currentbone = [] #select armature for roll copy
+ print("Armature Name:",obj.name)
+ objectname = "ArmatureDataPSK"
+ meshname ="ArmatureObjectPSK"
+ armdata = bpy.data.armatures.new(objectname)
+ ob_new = bpy.data.objects.new(meshname, armdata)
+ bpy.context.scene.objects.link(ob_new)
+ bpy.ops.object.mode_set(mode='OBJECT')
+ for i in bpy.context.scene.objects: i.select = False #deselect all objects
+ ob_new.select = True
+ bpy.context.scene.objects.active = obj
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ for bone in obj.data.edit_bones:
+ if bone.parent != None:
+ currentbone.append([bone.name,bone.roll])
+ else:
+ currentbone.append([bone.name,bone.roll])
+ bpy.ops.object.mode_set(mode='OBJECT')
+ for i in bpy.context.scene.objects: i.select = False #deselect all objects
+ bpy.context.scene.objects.active = ob_new
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ for bone in obj.data.bones:
+ bpy.ops.object.mode_set(mode='EDIT')
+ newbone = ob_new.data.edit_bones.new(bone.name)
+ newbone.head = bone.head_local
+ newbone.tail = bone.tail_local
+ for bonelist in currentbone:
+ if bone.name == bonelist[0]:
+ newbone.roll = bonelist[1]
+ break
+ if bone.parent != None:
+ parentbone = ob_new.data.edit_bones[bone.parent.name]
+ newbone.parent = parentbone
+ print("Bone Count:",len(obj.data.bones))
+ print("Hold Bone Count",len(currentbone))
+ print("New Bone Count",len(ob_new.data.edit_bones))
+ print("Rebuild Armture Finish:",ob_new.name)
+ bpy.context.scene.update()
+ bselected = True
+ break
+ if bselected:
+ self.report({'INFO'}, "Rebuild Armature Finish!")
+ else:
+ self.report({'INFO'}, "Didn't Select Armature Object!")
+ print("End of Rebuild Armature.")
+ print("----------------------------------------")
+ return{'FINISHED'}
+
+# rounded the vert locations to save a bit of blurb.. change the round value or remove for accuracy i suppose
+def rounded_tuple(tup):
+ return tuple(round(value,4) for value in tup)
+
+def unpack_list(list_of_tuples):
+ l = []
+ for t in list_of_tuples:
+ l.extend(t)
+ return l
+
+class OBJECT_OT_UTRebuildMesh(bpy.types.Operator):
+ bl_idname = "object.utrebuildmesh" # XXX, name???
+ bl_label = "Rebuild Mesh"
+ __doc__ = """It rebuild the mesh from scrape from the selected mesh object."""
+
+ def invoke(self, context, event):
+ print("----------------------------------------")
+ print("Init Mesh Bebuild...")
+ bselected = False
+ for obj in bpy.data.objects:
+ if obj.type == 'MESH' and obj.select == True:
+ for i in bpy.context.scene.objects: i.select = False #deselect all objects
+ obj.select = True
+ bpy.context.scene.objects.active = obj
+ bpy.ops.object.mode_set(mode='OBJECT')
+ me_ob = bpy.data.meshes.new(("Re_"+obj.name))
+ mesh = obj.data
+ faces = []
+ verts = []
+ smoothings = []
+ uvfaces = []
+ #print(dir(mesh))
+ print("creating array build mesh...")
+ uv_layer = mesh.uv_textures.active
+ for face in mesh.faces:
+ v = []
+ smoothings.append(face.use_smooth)#smooth or flat in boolean
+ if uv_layer != None:#check if there texture data exist
+ faceUV = uv_layer.data[face.index]
+ #print(len(faceUV.uv))
+ uvs = []
+ for uv in faceUV.uv:
+ #vert = mesh.vertices[videx]
+ #print("UV:",uv[0],":",uv[1])
+ uvs.append((uv[0],uv[1]))
+ #print(uvs)
+ uvfaces.append(uvs)
+ for videx in face.vertices:
+ vert = mesh.vertices[videx]
+ v.append(videx)
+ faces.append(v)
+ #vertex positions
+ for vertex in mesh.vertices:
+ verts.append(vertex.co.to_tuple())
+ #vertices weight groups into array
+ vertGroups = {} #array in strings
+ for vgroup in obj.vertex_groups:
+ #print(dir(vgroup))
+ #print("name:",(vgroup.name),"index:",vgroup.index)
+ #vertex in index and weight
+ vlist = []
+ for v in mesh.vertices:
+ for vg in v.groups:
+ if vg.group == vgroup.index:
+ vlist.append((v.index,vg.weight))
+ #print((v.index,vg.weight))
+ vertGroups[vgroup.name] = vlist
+ '''
+ #Fail for this method
+ #can't covert the tri face plogyon
+ for face in mesh.faces:
+ x = [f for f in face.vertices]
+ faces.extend(x)
+ smoothings.append(face.use_smooth)
+ for vertex in mesh.vertices:
+ verts.append(vertex.co.to_tuple())
+ me_ob.vertices.add(len(verts))
+ me_ob.faces.add(len(faces)//4)
+ me_ob.vertices.foreach_set("co", unpack_list(verts))
+ me_ob.faces.foreach_set("vertices_raw", faces)
+ me_ob.faces.foreach_set("use_smooth", smoothings)
+ '''
+ #test dummy mesh
+ #verts = [(-1,1,0),(1,1,0),(1,-1,0),(-1,-1,0),(0,1,1),(0,-1,1)]
+ #faces = [(0,1,2,3),(1,2,5,4),(0,3,5,4),(0,1,4),(2,3,5)]
+ #for f in faces:
+ #print("face",f)
+ #for v in verts:
+ #print("vertex",v)
+ #me_ob = bpy.data.objects.new("ReBuildMesh",me_ob)
+ print("creating mesh object...")
+ me_ob.from_pydata(verts, [], faces)
+ me_ob.faces.foreach_set("use_smooth", smoothings)#smooth array from face
+ me_ob.update()
+ #check if there is uv faces
+ if len(uvfaces) > 0:
+ uvtex = me_ob.uv_textures.new(name="retex")
+ for i, face in enumerate(me_ob.faces):
+ blender_tface = uvtex.data[i] #face
+ mfaceuv = uvfaces[i]
+ if len(mfaceuv) == 3:
+ blender_tface.uv1 = mfaceuv[0];
+ blender_tface.uv2 = mfaceuv[1];
+ blender_tface.uv3 = mfaceuv[2];
+ if len(mfaceuv) == 4:
+ blender_tface.uv1 = mfaceuv[0];
+ blender_tface.uv2 = mfaceuv[1];
+ blender_tface.uv3 = mfaceuv[2];
+ blender_tface.uv4 = mfaceuv[3];
+
+ obmesh = bpy.data.objects.new(("Re_"+obj.name),me_ob)
+ bpy.context.scene.update()
+ #Build tmp materials
+ materialname = "ReMaterial"
+ for matcount in mesh.materials:
+ matdata = bpy.data.materials.new(materialname)
+ me_ob.materials.append(matdata)
+ #assign face to material id
+ for face in mesh.faces:
+ #print(dir(face))
+ me_ob.faces[face.index].material_index = face.material_index
+ #vertices weight groups
+ for vgroup in vertGroups:
+ #print("vgroup",vgroup)#name of group
+ #print(dir(vgroup))
+ #print(vertGroups[vgroup])
+ group = obmesh.vertex_groups.new(vgroup)
+ #print("group index",group.index)
+ for v in vertGroups[vgroup]:
+ group.add([v[0]], v[1], 'ADD')# group.add(array[vertex id],weight,add)
+ #print("[vertex id, weight]",v) #array (0,0)
+ #print("[vertex id, weight]",v[0],":",v[1]) #array (0,0)
+ bpy.context.scene.objects.link(obmesh)
+ print("Mesh Material Count:",len(me_ob.materials))
+ for mat in me_ob.materials:
+ print("-Material:",mat.name)
+ print("Object Name:",obmesh.name)
+ bpy.context.scene.update()
+ #bpy.ops.wm.console_toggle()
+ bselected = True
+ break
+ if bselected:
+ self.report({'INFO'}, "Rebuild Mesh Finish!")
+ print("Finish Mesh Build...")
+ else:
+ self.report({'INFO'}, "Didn't Select Mesh Object!")
+ print("Didn't Select Mesh Object!")
+ print("----------------------------------------")
+
+ return{'FINISHED'}
+
+def menu_func(self, context):
+ #bpy.context.scene.unrealexportpsk = True
+ #bpy.context.scene.unrealexportpsa = True
+ default_path = os.path.splitext(bpy.data.filepath)[0] + ".psk"
+ self.layout.operator(ExportUDKAnimData.bl_idname, text="Skeleton Mesh / Animation Data (.psk/.psa)").filepath = default_path
+
+def register():
+ bpy.utils.register_module(__name__)
+ bpy.types.INFO_MT_file_export.append(menu_func)
+
+def unregister():
+ bpy.utils.unregister_module(__name__)
+ bpy.types.INFO_MT_file_export.remove(menu_func)
+
+if __name__ == "__main__":
+ register()