1 # ***** GPL LICENSE BLOCK *****
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation, either version 3 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # All rights reserved.
16 # ***** GPL LICENSE BLOCK *****
19 "name": "Export Skeleletal Mesh/Animation Data",
20 "author": "Darknet/Optimus_P-Fat/Active_Trash/Sinsoft",
24 "location": "File > Export > Skeletal Mesh/Animation Data (.psk/.psa)",
25 "description": "Export Unreal Engine (.psk)",
27 "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
28 "Scripts/Import-Export/Unreal_psk_psa",
29 "tracker_url": "https://projects.blender.org/tracker/index.php?"\
30 "func=detail&aid=21366&group_id=153&atid=469",
31 "category": "Import-Export"}
34 -- Unreal Skeletal Mesh and Animation Export (.psk and .psa) export script v0.0.1 --<br>
37 - This script Exports To Unreal's PSK and PSA file formats for Skeletal Meshes and Animations. <br>
38 - This script DOES NOT support vertex animation! These require completely different file formats. <br>
44 - This version adds support for more than one material index!
48 - This will work on UT3 and it is a stable version that work with vehicle for testing.
49 - Main Bone fix no dummy needed to be there.
50 - Just bone issues position, rotation, and offset for psk.
51 - The armature bone position, rotation, and the offset of the bone is fix. It was to deal with skeleton mesh export for psk.
52 - Animation is fix for position, offset, rotation bone support one rotation direction when armature build.
53 - It will convert your mesh into triangular when exporting to psk file.
54 - Did not work with psa export yet.
57 - The animatoin will support different bone rotations when export the animation.
60 - Fixed Action set keys frames when there is no pose keys and it will ignore it.
63 - Fixed multiple objects when exporting to psk. Select one mesh to export to psk.
67 - Blender 2.50 svn (Support)
70 - export_cal3d.py (Position of the Bones Format)
71 - blender2md5.py (Animation Translation Format)
72 - export_obj.py (Blender 2.5/Pyhton 3.x Format)
74 - freenode #blendercoder -> user -> ideasman42
76 - Give Credit to those who work on this script.
88 from struct
import pack
, calcsize
91 # REFERENCE MATERIAL JUST IN CASE:
93 # U = x / sqrt(x^2 + y^2 + z^2)
94 # V = y / sqrt(x^2 + y^2 + z^2)
96 # Triangles specifed counter clockwise for front face
102 SIZE_ANIMINFOBINARY
= 168
103 SIZE_VCHUNKHEADER
= 32
106 SIZE_FNAMEDBONEBINARY
= 120
107 SIZE_VRAWBONEINFLUENCE
= 12
108 SIZE_VQUATANIMKEY
= 32
113 ########################################################################
114 # Generic Object->Integer mapping
115 # the object must be usable as a dictionary key
122 return self
.dict[obj
]
125 self
.next
= self
.next
+ 1
130 getval
= operator
.itemgetter(0)
131 getkey
= operator
.itemgetter(1)
132 return map(getval
, sorted(self
.dict.items(), key
=getkey
))
134 ########################################################################
135 # RG - UNREAL DATA STRUCTS - CONVERTED FROM C STRUCTS GIVEN ON UDN SITE
136 # provided here: http://udn.epicgames.com/Two/BinaryFormatSpecifications.html
137 # updated UDK (Unreal Engine 3): http://udn.epicgames.com/Three/BinaryFormatSpecifications.html
146 data
= pack('ffff', self
.X
, self
.Y
, self
.Z
, self
.W
)
149 def __cmp__(self
, other
):
150 return cmp(self
.X
, other
.X
) \
151 or cmp(self
.Y
, other
.Y
) \
152 or cmp(self
.Z
, other
.Z
) \
153 or cmp(self
.W
, other
.W
)
156 return hash(self
.X
) ^
hash(self
.Y
) ^
hash(self
.Z
) ^
hash(self
.W
)
159 return "[%f,%f,%f,%f](FQuat)" % (self
.X
, self
.Y
, self
.Z
, self
.W
)
161 class FVector(object):
162 def __init__(self
, X
=0.0, Y
=0.0, Z
=0.0):
168 data
= pack('fff', self
.X
, self
.Y
, self
.Z
)
171 def __cmp__(self
, other
):
172 return cmp(self
.X
, other
.X
) \
173 or cmp(self
.Y
, other
.Y
) \
174 or cmp(self
.Z
, other
.Z
)
177 return (type(self
).__name
__, self
.X
, self
.Y
, self
.Z
)
180 return hash(self
._key
())
182 def __eq__(self
, other
):
183 if not hasattr(other
, '_key'):
185 return self
._key
() == other
._key
()
187 def dot(self
, other
):
188 return self
.X
* other
.X
+ self
.Y
* other
.Y
+ self
.Z
* other
.Z
190 def cross(self
, other
):
191 return FVector(self
.Y
* other
.Z
- self
.Z
* other
.Y
,
192 self
.Z
* other
.X
- self
.X
* other
.Z
,
193 self
.X
* other
.Y
- self
.Y
* other
.X
)
195 def sub(self
, other
):
196 return FVector(self
.X
- other
.X
,
202 self
.Orientation
= FQuat()
203 self
.Position
= FVector()
210 data
= self
.Orientation
.dump() + self
.Position
.dump() + pack('4f', self
.Length
, self
.XSize
, self
.YSize
, self
.ZSize
)
213 class AnimInfoBinary
:
215 self
.Name
= "" # length=64
216 self
.Group
= "" # length=64
219 self
.KeyCompressionStyle
= 0
221 self
.KeyPrediction
= 0.0
225 self
.FirstRawFrame
= 0
226 self
.NumRawFrames
= 0
229 data
= pack('64s64siiiifffiii', self
.Name
, self
.Group
, self
.TotalBones
, self
.RootInclude
, self
.KeyCompressionStyle
, self
.KeyQuotum
, self
.KeyPrediction
, self
.TrackTime
, self
.AnimRate
, self
.StartBone
, self
.FirstRawFrame
, self
.NumRawFrames
)
233 def __init__(self
, name
, type_size
):
234 self
.ChunkID
= name
# length=20
235 self
.TypeFlag
= 1999801 # special value
236 self
.DataSize
= type_size
240 data
= pack('20siii', self
.ChunkID
, self
.TypeFlag
, self
.DataSize
, self
.DataCount
)
245 self
.MaterialName
= "" # length=64
246 self
.TextureIndex
= 0
247 self
.PolyFlags
= 0 # DWORD
249 self
.AuxFlags
= 0 # DWORD
254 data
= pack('64siLiLii', self
.MaterialName
, self
.TextureIndex
, self
.PolyFlags
, self
.AuxMaterial
, self
.AuxFlags
, self
.LodBias
, self
.LodStyle
)
259 self
.Name
= "" # length = 64
260 self
.Flags
= 0 # DWORD
263 self
.BonePos
= VJointPos()
266 data
= pack('64sLii', self
.Name
, self
.Flags
, self
.NumChildren
, self
.ParentIndex
) + self
.BonePos
.dump()
269 #same as above - whatever - this is how Epic does it...
270 class FNamedBoneBinary
:
272 self
.Name
= "" # length = 64
273 self
.Flags
= 0 # DWORD
276 self
.BonePos
= VJointPos()
278 self
.IsRealBone
= 0 # this is set to 1 when the bone is actually a bone in the mesh and not a dummy
281 data
= pack('64sLii', self
.Name
, self
.Flags
, self
.NumChildren
, self
.ParentIndex
) + self
.BonePos
.dump()
284 class VRawBoneInfluence
:
291 data
= pack('fii', self
.Weight
, self
.PointIndex
, self
.BoneIndex
)
296 self
.Position
= FVector()
297 self
.Orientation
= FQuat()
301 data
= self
.Position
.dump() + self
.Orientation
.dump() + pack('f', self
.Time
)
304 class VVertex(object):
306 self
.PointIndex
= 0 # WORD
309 self
.MatIndex
= 0 #BYTE
310 self
.Reserved
= 0 #BYTE
313 data
= pack('HHffBBH', self
.PointIndex
, 0, self
.U
, self
.V
, self
.MatIndex
, self
.Reserved
, 0)
316 def __cmp__(self
, other
):
317 return cmp(self
.PointIndex
, other
.PointIndex
) \
318 or cmp(self
.U
, other
.U
) \
319 or cmp(self
.V
, other
.V
) \
320 or cmp(self
.MatIndex
, other
.MatIndex
) \
321 or cmp(self
.Reserved
, other
.Reserved
)
324 return (type(self
).__name
__,self
.PointIndex
, self
.U
, self
.V
,self
.MatIndex
,self
.Reserved
)
327 return hash(self
._key
())
329 def __eq__(self
, other
):
330 if not hasattr(other
, '_key'):
332 return self
._key
() == other
._key
()
334 class VPoint(object):
336 self
.Point
= FVector()
339 return self
.Point
.dump()
341 def __cmp__(self
, other
):
342 return cmp(self
.Point
, other
.Point
)
345 return (type(self
).__name
__, self
.Point
)
348 return hash(self
._key
())
350 def __eq__(self
, other
):
351 if not hasattr(other
, '_key'):
353 return self
._key
() == other
._key
()
357 self
.WedgeIndex0
= 0 # WORD
358 self
.WedgeIndex1
= 0 # WORD
359 self
.WedgeIndex2
= 0 # WORD
360 self
.MatIndex
= 0 # BYTE
361 self
.AuxMatIndex
= 0 # BYTE
362 self
.SmoothingGroups
= 0 # DWORD
365 data
= pack('HHHBBL', self
.WedgeIndex0
, self
.WedgeIndex1
, self
.WedgeIndex2
, self
.MatIndex
, self
.AuxMatIndex
, self
.SmoothingGroups
)
368 # END UNREAL DATA STRUCTS
369 ########################################################################
371 ########################################################################
372 #RG - helper class to handle the normal way the UT files are stored
373 #as sections consisting of a header and then a list of data structures
375 def __init__(self
, name
, type_size
):
376 self
.Header
= VChunkHeader(name
, type_size
)
377 self
.Data
= [] # list of datatypes
380 data
= self
.Header
.dump()
381 for i
in range(len(self
.Data
)):
382 data
= data
+ self
.Data
[i
].dump()
385 def UpdateHeader(self
):
386 self
.Header
.DataCount
= len(self
.Data
)
390 self
.GeneralHeader
= VChunkHeader("ACTRHEAD", 0)
391 self
.Points
= FileSection("PNTS0000", SIZE_VPOINT
) #VPoint
392 self
.Wedges
= FileSection("VTXW0000", SIZE_VVERTEX
) #VVertex
393 self
.Faces
= FileSection("FACE0000", SIZE_VTRIANGLE
) #VTriangle
394 self
.Materials
= FileSection("MATT0000", SIZE_VMATERIAL
) #VMaterial
395 self
.Bones
= FileSection("REFSKELT", SIZE_VBONE
) #VBone
396 self
.Influences
= FileSection("RAWWEIGHTS", SIZE_VRAWBONEINFLUENCE
) #VRawBoneInfluence
398 #RG - this mapping is not dumped, but is used internally to store the new point indices
399 # for vertex groups calculated during the mesh dump, so they can be used again
400 # to dump bone influences during the armature dump
402 # the key in this dictionary is the VertexGroup/Bone Name, and the value
403 # is a list of tuples containing the new point index and the weight, in that order
406 # { groupname : [ (index, weight), ... ], ... }
409 # { 'MyVertexGroup' : [ (0, 1.0), (5, 1.0), (3, 0.5) ] , 'OtherGroup' : [(2, 1.0)] }
411 self
.VertexGroups
= {}
413 def AddPoint(self
, p
):
415 self
.Points
.Data
.append(p
)
417 def AddWedge(self
, w
):
419 self
.Wedges
.Data
.append(w
)
421 def AddFace(self
, f
):
423 self
.Faces
.Data
.append(f
)
425 def AddMaterial(self
, m
):
426 #print ('AddMaterial')
427 self
.Materials
.Data
.append(m
)
429 def AddBone(self
, b
):
430 #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))
431 self
.Bones
.Data
.append(b
)
433 def AddInfluence(self
, i
):
434 #print ('AddInfluence')
435 self
.Influences
.Data
.append(i
)
437 def UpdateHeaders(self
):
438 self
.Points
.UpdateHeader()
439 self
.Wedges
.UpdateHeader()
440 self
.Faces
.UpdateHeader()
441 self
.Materials
.UpdateHeader()
442 self
.Bones
.UpdateHeader()
443 self
.Influences
.UpdateHeader()
447 data
= self
.GeneralHeader
.dump() + self
.Points
.dump() + self
.Wedges
.dump() + self
.Faces
.dump() + self
.Materials
.dump() + self
.Bones
.dump() + self
.Influences
.dump()
450 def GetMatByIndex(self
, mat_index
):
451 if mat_index
>= 0 and len(self
.Materials
.Data
) > mat_index
:
452 return self
.Materials
.Data
[mat_index
]
455 m
.MaterialName
= "Mat%i" % mat_index
460 print ("--- PSK FILE EXPORTED ---")
461 print ('point count: %i' % len(self
.Points
.Data
))
462 print ('wedge count: %i' % len(self
.Wedges
.Data
))
463 print ('face count: %i' % len(self
.Faces
.Data
))
464 print ('material count: %i' % len(self
.Materials
.Data
))
465 print ('bone count: %i' % len(self
.Bones
.Data
))
466 print ('inlfuence count: %i' % len(self
.Influences
.Data
))
467 print ('-------------------------')
469 # PSA FILE NOTES FROM UDN:
471 # The raw key array holds all the keys for all the bones in all the specified sequences,
472 # organized as follows:
473 # For each AnimInfoBinary's sequence there are [Number of bones] times [Number of frames keys]
474 # in the VQuatAnimKeys, laid out as tracks of [numframes] keys for each bone in the order of
475 # the bones as defined in the array of FnamedBoneBinary in the PSA.
477 # Once the data from the PSK (now digested into native skeletal mesh) and PSA (digested into
478 # a native animation object containing one or more sequences) are associated together at runtime,
479 # bones are linked up by name. Any bone in a skeleton (from the PSK) that finds no partner in
480 # the animation sequence (from the PSA) will assume its reference pose stance ( as defined in
481 # the offsets & rotations that are in the VBones making up the reference skeleton from the PSK)
485 self
.GeneralHeader
= VChunkHeader("ANIMHEAD", 0)
486 self
.Bones
= FileSection("BONENAMES", SIZE_FNAMEDBONEBINARY
) #FNamedBoneBinary
487 self
.Animations
= FileSection("ANIMINFO", SIZE_ANIMINFOBINARY
) #AnimInfoBinary
488 self
.RawKeys
= FileSection("ANIMKEYS", SIZE_VQUATANIMKEY
) #VQuatAnimKey
490 # this will take the format of key=Bone Name, value = (BoneIndex, Bone Object)
495 data
= self
.Generalheader
.dump() + self
.Bones
.dump() + self
.Animations
.dump() + self
.RawKeys
.dump()
498 def AddBone(self
, b
):
500 #print "AddBone: " + b.Name
501 self
.Bones
.Data
.append(b
)
503 def AddAnimation(self
, a
):
505 #print "AddAnimation: %s, TotalBones: %i, AnimRate: %f, NumRawFrames: %i, TrackTime: %f" % (a.Name, a.TotalBones, a.AnimRate, a.NumRawFrames, a.TrackTime)
506 self
.Animations
.Data
.append(a
)
508 def AddRawKey(self
, k
):
510 #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)
511 self
.RawKeys
.Data
.append(k
)
513 def UpdateHeaders(self
):
514 self
.Bones
.UpdateHeader()
515 self
.Animations
.UpdateHeader()
516 self
.RawKeys
.UpdateHeader()
518 def GetBoneByIndex(self
, bone_index
):
519 if bone_index
>= 0 and len(self
.Bones
.Data
) > bone_index
:
520 return self
.Bones
.Data
[bone_index
]
523 return (len(self
.Bones
.Data
) == 0 or len(self
.Animations
.Data
) == 0)
525 def StoreBone(self
, b
):
526 self
.BoneLookup
[b
.Name
] = [-1, b
]
528 def UseBone(self
, bone_name
):
529 if bone_name
in self
.BoneLookup
:
530 bone_data
= self
.BoneLookup
[bone_name
]
532 if bone_data
[0] == -1:
533 bone_data
[0] = len(self
.Bones
.Data
)
534 self
.AddBone(bone_data
[1])
535 #self.Bones.Data.append(bone_data[1])
539 def GetBoneByName(self
, bone_name
):
540 if bone_name
in self
.BoneLookup
:
541 bone_data
= self
.BoneLookup
[bone_name
]
544 def GetBoneIndex(self
, bone_name
):
545 if bone_name
in self
.BoneLookup
:
546 bone_data
= self
.BoneLookup
[bone_name
]
551 data
= self
.GeneralHeader
.dump() + self
.Bones
.dump() + self
.Animations
.dump() + self
.RawKeys
.dump()
555 print ('--- PSA FILE EXPORTED ---')
556 print ('bone count: %i' % len(self
.Bones
.Data
))
557 print ('animation count: %i' % len(self
.Animations
.Data
))
558 print ('rawkey count: %i' % len(self
.RawKeys
.Data
))
559 print ('-------------------------')
561 ####################################
562 # helpers to create bone structs
563 def make_vbone(name
, parent_index
, child_count
, orientation_quat
, position_vect
):
566 bone
.ParentIndex
= parent_index
567 bone
.NumChildren
= child_count
568 bone
.BonePos
.Orientation
= orientation_quat
569 bone
.BonePos
.Position
.X
= position_vect
.x
570 bone
.BonePos
.Position
.Y
= position_vect
.y
571 bone
.BonePos
.Position
.Z
= position_vect
.z
573 #these values seem to be ignored?
574 #bone.BonePos.Length = tail.length
575 #bone.BonePos.XSize = tail.x
576 #bone.BonePos.YSize = tail.y
577 #bone.BonePos.ZSize = tail.z
581 def make_namedbonebinary(name
, parent_index
, child_count
, orientation_quat
, position_vect
, is_real
):
582 bone
= FNamedBoneBinary()
584 bone
.ParentIndex
= parent_index
585 bone
.NumChildren
= child_count
586 bone
.BonePos
.Orientation
= orientation_quat
587 bone
.BonePos
.Position
.X
= position_vect
.x
588 bone
.BonePos
.Position
.Y
= position_vect
.y
589 bone
.BonePos
.Position
.Z
= position_vect
.z
590 bone
.IsRealBone
= is_real
593 ##################################################
594 #RG - check to make sure face isnt a line
595 #The face has to be triangle not a line
596 def is_1d_face(blender_face
,mesh
):
597 #ID Vertex of id point
598 v0
= blender_face
.vertices
[0]
599 v1
= blender_face
.vertices
[1]
600 v2
= blender_face
.vertices
[2]
602 return (mesh
.vertices
[v0
].co
== mesh
.vertices
[v1
].co
or \
603 mesh
.vertices
[v1
].co
== mesh
.vertices
[v2
].co
or \
604 mesh
.vertices
[v2
].co
== mesh
.vertices
[v0
].co
)
607 ##################################################
608 # http://en.wikibooks.org/wiki/Blender_3D:_Blending_Into_Python/Cookbook#Triangulate_NMesh
610 #blender 2.50 format using the Operators/command convert the mesh to tri mesh
611 def triangulateNMesh(object):
613 scene
= bpy
.context
.scene
614 bpy
.ops
.object.mode_set(mode
='OBJECT')
615 for i
in scene
.objects
: i
.select
= False #deselect all objects
617 scene
.objects
.active
= object #set the mesh object to current
618 bpy
.ops
.object.mode_set(mode
='OBJECT')
619 print("Checking mesh if needs to convert quad to Tri...")
620 for face
in object.data
.faces
:
621 if (len(face
.vertices
) > 3):
625 bpy
.ops
.object.mode_set(mode
='OBJECT')
627 print("Converting quad to tri mesh...")
628 me_da
= object.data
.copy() #copy data
629 me_ob
= object.copy() #copy object
630 #note two copy two types else it will use the current data or mesh
632 bpy
.context
.scene
.objects
.link(me_ob
)#link the object to the scene #current object location
633 for i
in scene
.objects
: i
.select
= False #deselect all objects
635 scene
.objects
.active
= me_ob
#set the mesh object to current
636 bpy
.ops
.object.mode_set(mode
='EDIT') #Operators
637 bpy
.ops
.mesh
.select_all(action
='SELECT')#select all the face/vertex/edge
638 bpy
.ops
.mesh
.quads_convert_to_tris() #Operators
639 bpy
.context
.scene
.update()
640 bpy
.ops
.object.mode_set(mode
='OBJECT') # set it in object
641 bpy
.context
.scene
.unrealtriangulatebool
= True
642 print("Triangulate Mesh Done!")
644 bpy
.context
.scene
.unrealtriangulatebool
= False
645 print("No need to convert tri mesh.")
656 #deal with mesh bones groups vertex point
658 global BBCount
, bonedata
659 #print("//==============")
660 #print(bone.name , "ID:",BBCount)
666 for current_child_bone
in bone
.children
:
667 BoneIndex(current_child_bone
)
669 def BoneIndexArmature(blender_armature
):
671 #print("\n Buildng bone before mesh \n")
672 #objectbone = blender_armature.pose #Armature bone
673 #print(blender_armature)
674 objectbone
= blender_armature
[0].pose
675 #print(dir(ArmatureData))
677 for bone
in objectbone
.bones
:
678 if(bone
.parent
is None):
683 # Actual object parsing functions
684 def parse_meshes(blender_meshes
, psk_file
):
685 #this is use to call the bone name and the index array for group index matches
687 #print("BONE DATA",len(bonedata))
688 print ("----- parsing meshes -----")
689 print("Number of Object Meshes:",len(blender_meshes
))
690 for current_obj
in blender_meshes
: #number of mesh that should be one mesh here
691 bpy
.ops
.object.mode_set(mode
='EDIT')
692 current_obj
= triangulateNMesh(current_obj
)
693 #print(dir(current_obj))
694 print("Mesh Name:",current_obj
.name
)
695 current_mesh
= current_obj
.data
697 #if len(current_obj.materials) > 0:
698 # object_mat = current_obj.materials[0]
699 object_material_index
= current_obj
.active_material_index
704 discarded_face_count
= 0
705 print (" -- Dumping Mesh Faces -- LEN:", len(current_mesh
.faces
))
706 for current_face
in current_mesh
.faces
:
707 #print ' -- Dumping UVs -- '
708 #print current_face.uv_textures
710 if len(current_face
.vertices
) != 3:
711 raise RuntimeError("Non-triangular face (%i)" % len(current_face
.vertices
))
714 # if len(current_face.vertices) != 3:
715 # raise RuntimeError("Non-triangular face (%i)" % len(current_face.vertices))
716 # #TODO: add two fake faces made of triangles?
718 #RG - apparently blender sometimes has problems when you do quad to triangle
719 # conversion, and ends up creating faces that have only TWO points -
720 # one of the points is simply in the vertex list for the face twice.
721 # This is bad, since we can't get a real face normal for a LINE, we need
722 # a plane for this. So, before we add the face to the list of real faces,
723 # ensure that the face is actually a plane, and not a line. If it is not
724 # planar, just discard it and notify the user in the console after we're
725 # done dumping the rest of the faces
727 if not is_1d_face(current_face
,current_mesh
):
732 #get or create the current material
733 m
= psk_file
.GetMatByIndex(object_material_index
)
735 face_index
= current_face
.index
739 if len(current_mesh
.uv_textures
) > 0:
741 #print("face index: ",face_index)
742 #faceUV = current_mesh.uv_textures.active.data[face_index]#UVs for current face
743 #faceUV = current_mesh.uv_textures.active.data[0]#UVs for current face
744 #print(face_index,"<[FACE NUMBER")
745 uv_layer
= current_mesh
.uv_textures
.active
746 faceUV
= uv_layer
.data
[face_index
]
747 #print("============================")
748 #size(data) is number of texture faces. Each face has UVs
749 #print("DATA face uv: ",len(faceUV.uv), " >> ",(faceUV.uv[0][0]))
752 vert_index
= current_face
.vertices
[i
]
753 vert
= current_mesh
.vertices
[vert_index
]
755 #assumes 3 UVs Per face (for now).
757 if len(faceUV
.uv
) != 3:
758 print ("WARNING: Current face is missing UV coordinates - writing 0,0...")
759 print ("WARNING: Face has more than 3 UVs - writing 0,0...")
762 #uv.append(faceUV.uv[i][0])
763 #uv.append(faceUV.uv[i][1])
764 uv
= [faceUV
.uv
[i
][0],faceUV
.uv
[i
][1]] #OR bottom works better # 24 for cube
765 #uv = list(faceUV.uv[i]) #30 just cube
770 #uv = [0.0, 0.0] #over ride uv that is not fixed
772 #flip V coordinate because UEd requires it and DOESN'T flip it on its own like it
773 #does with the mesh Y coordinates.
774 #this is otherwise known as MAGIC-2
777 #deal with the min and max value
778 #if value is over the set limit it will null the uv texture
789 # RE - Append untransformed vector (for normal calc below)
790 # TODO: convert to Blender.Mathutils
791 vect_list
.append(FVector(vert
.co
.x
, vert
.co
.y
, vert
.co
.z
))
793 # Transform position for export
794 #vpos = vert.co * object_material_index
795 vpos
= vert
.co
* current_obj
.matrix_local
804 w
.MatIndex
= object_material_index
805 w
.PointIndex
= points
.get(p
) # get index from map
809 index_wedge
= wedges
.get(w
)
810 wedge_list
.append(index_wedge
)
813 #print 'result PointIndex=%i, U=%f, V=%f, wedge_index=%i' % (
819 # Determine face vertex order
820 # get normal from blender
821 no
= current_face
.normal
823 # TODO: convert to Blender.Mathutils
825 norm
= FVector(no
[0], no
[1], no
[2])
827 # Calculate the normal of the face in blender order
828 tnorm
= vect_list
[1].sub(vect_list
[0]).cross(vect_list
[2].sub(vect_list
[1]))
830 # RE - dot the normal from blender order against the blender normal
831 # this gives the product of the two vectors' lengths along the blender normal axis
832 # all that matters is the sign
833 dot
= norm
.dot(tnorm
)
836 #print 'face norm: (%f,%f,%f), tnorm=(%f,%f,%f), dot=%f' % (
837 # norm.X, norm.Y, norm.Z,
838 # tnorm.X, tnorm.Y, tnorm.Z,
842 # RE - magic: if the dot product above > 0, order the vertices 2, 1, 0
843 # if the dot product above < 0, order the vertices 0, 1, 2
844 # if the dot product is 0, then blender's normal is coplanar with the face
845 # and we cannot deduce which side of the face is the outside of the mesh
847 (tri
.WedgeIndex2
, tri
.WedgeIndex1
, tri
.WedgeIndex0
) = wedge_list
849 (tri
.WedgeIndex0
, tri
.WedgeIndex1
, tri
.WedgeIndex2
) = wedge_list
851 dindex0
= current_face
.vertices
[0];
852 dindex1
= current_face
.vertices
[1];
853 dindex2
= current_face
.vertices
[2];
855 current_mesh
.vertices
[dindex0
].select
= True
856 current_mesh
.vertices
[dindex1
].select
= True
857 current_mesh
.vertices
[dindex2
].select
= True
859 raise RuntimeError("normal vector coplanar with face! points:", current_mesh
.vertices
[dindex0
].co
, current_mesh
.vertices
[dindex1
].co
, current_mesh
.vertices
[dindex2
].co
)
860 #print(dir(current_face))
861 current_face
.select
= True
862 #print((current_face.use_smooth))
863 #not sure if this right
865 if current_face
.use_smooth
== True:
866 tri
.SmoothingGroups
= 1
868 tri
.SmoothingGroups
= 0
870 tri
.MatIndex
= object_material_index
874 psk_file
.AddFace(tri
)
877 discarded_face_count
= discarded_face_count
+ 1
879 print (" -- Dumping Mesh Points -- LEN:",len(points
.dict))
880 for point
in points
.items():
881 psk_file
.AddPoint(point
)
882 print (" -- Dumping Mesh Wedge -- LEN:",len(wedges
.dict))
883 for wedge
in wedges
.items():
884 psk_file
.AddWedge(wedge
)
886 #RG - if we happend upon any non-planar faces above that we've discarded,
887 # just let the user know we discarded them here in case they want
890 if discarded_face_count
> 0:
891 print ("INFO: Discarded %i non-planar faces." % (discarded_face_count
))
893 #RG - walk through the vertex groups and find the indexes into the PSK points array
894 #for them, then store that index and the weight as a tuple in a new list of
895 #verts for the group that we can look up later by bone name, since Blender matches
896 #verts to bones for influences by having the VertexGroup named the same thing as
900 for bonegroup
in bonedata
:
901 #print("bone gourp build:",bonegroup.bone)
903 for current_vert
in current_mesh
.vertices
:
904 #print("INDEX V:",current_vert.index)
905 vert_index
= current_vert
.index
906 for vgroup
in current_vert
.groups
:#vertex groupd id
907 vert_weight
= vgroup
.weight
908 if(bonegroup
.index
== vgroup
.group
):
910 vpos
= current_vert
.co
* current_obj
.matrix_local
914 #print(current_vert.co)
915 point_index
= points
.get(p
) #point index
916 v_item
= (point_index
, vert_weight
)
917 vert_list
.append(v_item
)
918 #bone name, [point id and wieght]
919 #print("Add Vertex Group:",bonegroup.bone, " No. Points:",len(vert_list))
920 psk_file
.VertexGroups
[bonegroup
.bone
] = vert_list
922 #unrealtriangulatebool #this will remove the mesh from the scene
923 if (bpy
.context
.scene
.unrealtriangulatebool
== True):
924 print("Remove tmp Mesh [ " ,current_obj
.name
, " ] from scene >" ,(bpy
.context
.scene
.unrealtriangulatebool
))
925 bpy
.ops
.object.mode_set(mode
='OBJECT') # set it in object
926 bpy
.context
.scene
.objects
.unlink(current_obj
)
928 def make_fquat(bquat
):
931 #flip handedness for UT = set x,y,z to negative (rotate in other direction)
939 def make_fquat_default(bquat
):
949 # =================================================================================================
950 # TODO: remove this 1am hack
952 def parse_bone(blender_bone
, psk_file
, psa_file
, parent_id
, is_root_bone
, parent_matrix
, parent_root
):
953 global nbone
# look it's evil!
954 #print '-------------------- Dumping Bone ---------------------- '
956 #If bone does not have parent that mean it the root bone
957 if blender_bone
.parent
is None:
958 parent_root
= blender_bone
961 child_count
= len(blender_bone
.children
)
963 child_parent
= blender_bone
.parent
965 if child_parent
!= None:
966 print ("--Bone Name:",blender_bone
.name
," parent:" , blender_bone
.parent
.name
, "ID:", nbone
)
968 print ("--Bone Name:",blender_bone
.name
," parent: None" , "ID:", nbone
)
970 if child_parent
!= None:
971 quat_root
= blender_bone
.matrix
972 quat
= make_fquat(quat_root
.to_quat())
974 quat_parent
= child_parent
.matrix
.to_quat().inverse()
975 parent_head
= child_parent
.head
* quat_parent
976 parent_tail
= child_parent
.tail
* quat_parent
978 set_position
= (parent_tail
- parent_head
) + blender_bone
.head
982 set_position
= blender_bone
.head
* parent_matrix
#ARMATURE OBJECT Locction
983 rot_mat
= blender_bone
.matrix
* parent_matrix
.rotation_part() #ARMATURE OBJECT Rotation
986 quat
= make_fquat_default(rot_mat
.to_quat())
988 #print ("[[======= FINAL POSITION:", set_position)
989 final_parent_id
= parent_id
992 #if we are not seperated by a small distance, create a dummy bone for the displacement
993 #this is only needed for root bones, since UT assumes a connected skeleton, and from here
994 #down the chain we just use "tail" as an endpoint
995 #if(head.length > 0.001 and is_root_bone == 1):
997 pb
= make_vbone("dummy_" + blender_bone
.name
, parent_id
, 1, FQuat(), tail
)
999 pbb
= make_namedbonebinary("dummy_" + blender_bone
.name
, parent_id
, 1, FQuat(), tail
, 0)
1000 psa_file
.StoreBone(pbb
)
1001 final_parent_id
= nbone
1007 pb
= make_vbone(blender_bone
.name
, final_parent_id
, child_count
, quat
, set_position
)
1008 psk_file
.AddBone(pb
)
1009 pbb
= make_namedbonebinary(blender_bone
.name
, final_parent_id
, child_count
, quat
, set_position
, 1)
1010 psa_file
.StoreBone(pbb
)
1014 #RG - dump influences for this bone - use the data we collected in the mesh dump phase
1015 # to map our bones to vertex groups
1016 #print("///////////////////////")
1017 #print("set influence")
1018 if blender_bone
.name
in psk_file
.VertexGroups
:
1019 vertex_list
= psk_file
.VertexGroups
[blender_bone
.name
]
1020 #print("vertex list:", len(vertex_list), " of >" ,blender_bone.name )
1021 for vertex_data
in vertex_list
:
1022 #print("set influence vettex")
1023 point_index
= vertex_data
[0]
1024 vertex_weight
= vertex_data
[1]
1025 influence
= VRawBoneInfluence()
1026 influence
.Weight
= vertex_weight
1027 influence
.BoneIndex
= my_id
1028 influence
.PointIndex
= point_index
1029 #print ('Adding Bone Influence for [%s] = Point Index=%i, Weight=%f' % (blender_bone.name, point_index, vertex_weight))
1030 #print("adding influence")
1031 psk_file
.AddInfluence(influence
)
1033 #blender_bone.matrix_local
1034 #recursively dump child bones
1035 mainparent
= parent_matrix
1036 #if len(blender_bone.children) > 0:
1037 for current_child_bone
in blender_bone
.children
:
1038 parse_bone(current_child_bone
, psk_file
, psa_file
, my_id
, 0, mainparent
, parent_root
)
1040 def parse_armature(blender_armature
, psk_file
, psa_file
):
1041 print ("----- parsing armature -----")
1042 print ('blender_armature length: %i' % (len(blender_armature
)))
1044 #magic 0 sized root bone for UT - this is where all armature dummy bones will attach
1045 #dont increment nbone here because we initialize it to 1 (hackity hackity hack)
1047 #count top level bones first. NOT EFFICIENT.
1049 for current_obj
in blender_armature
:
1050 current_armature
= current_obj
.data
1051 bones
= [x
for x
in current_armature
.bones
if not x
.parent
is None]
1052 child_count
+= len(bones
)
1054 for current_obj
in blender_armature
:
1055 print ("Current Armature Name: " + current_obj
.name
)
1056 current_armature
= current_obj
.data
1057 #armature_id = make_armature_bone(current_obj, psk_file, psa_file)
1059 #we dont want children here - only the top level bones of the armature itself
1060 #we will recursively dump the child bones as we dump these bones
1062 bones = [x for x in current_armature.bones if not x.parent is None]
1063 #will ingore this part of the ocde
1065 for current_bone
in current_armature
.bones
: #list the bone. #note this will list all the bones.
1066 if(current_bone
.parent
is None):
1067 parse_bone(current_bone
, psk_file
, psa_file
, 0, 0, current_obj
.matrix_local
, None)
1070 # get blender objects by type
1071 def get_blender_objects(objects
, intype
):
1072 return [x
for x
in objects
if x
.type == intype
]
1074 #strips current extension (if any) from filename and replaces it with extension passed in
1075 def make_filename_ext(filename
, extension
):
1077 extension_index
= filename
.find('.')
1079 if extension_index
== -1:
1080 new_filename
= filename
+ extension
1082 new_filename
= filename
[0:extension_index
] + extension
1086 # returns the quaternion Grassman product a*b
1087 # this is the same as the rotation a(b(x))
1088 # (ie. the same as B*A if A and B are matrices representing
1089 # the rotations described by quaternions a and b)
1091 return mathutils
.Quaternion(
1092 a
.w
*b
.w
- a
.x
*b
.x
- a
.y
*b
.y
- a
.z
*b
.z
,
1093 a
.w
*b
.x
+ a
.x
*b
.w
+ a
.y
*b
.z
- a
.z
*b
.y
,
1094 a
.w
*b
.y
- a
.x
*b
.z
+ a
.y
*b
.w
+ a
.z
*b
.x
,
1095 a
.w
*b
.z
+ a
.x
*b
.y
- a
.y
*b
.x
+ a
.z
*b
.w
)
1097 def parse_animation(blender_scene
, blender_armatures
, psa_file
):
1099 #need to list the action sets
1100 #need to check if there animation
1101 #need to check if animation is has one frame then exit it
1102 print ('\n----- parsing animation -----')
1103 render_data
= blender_scene
.render
1106 anim_rate
= render_data
.fps
1108 print("==== Blender Settings ====")
1109 print ('Scene: %s Start Frame: %i, End Frame: %i' % (blender_scene
.name
, blender_scene
.frame_start
, blender_scene
.frame_end
))
1110 print ('Frames Per Sec: %i' % anim_rate
)
1111 print ("Default FPS: 24" )
1115 if bpy
.context
.scene
.unrealactionexportall
:#if exporting all actions is ture then go do some work.
1116 print("Exporting all action:",bpy
.context
.scene
.unrealactionexportall
)
1117 print("[==== Action list Start====]")
1119 print("Number of Action set(s):",len(bpy
.data
.actions
))
1121 for action
in bpy
.data
.actions
:#current number action sets
1122 print("========>>>>>")
1123 print("Action Name:",action
.name
)
1125 #for bone in action.groups:
1126 #print("> Name: ",bone.name)
1129 print("[==== Action list End ====]")
1131 amatureobject
= None #this is the armature set to none
1132 bonenames
= [] #bone name of the armature bones list
1134 for arm
in blender_armatures
:
1137 print("\n[==== Armature Object ====]")
1138 if amatureobject
!= None:
1139 print("Name:",amatureobject
.name
)
1140 print("Number of bones:", len(amatureobject
.pose
.bones
))
1141 for bone
in amatureobject
.pose
.bones
:
1142 bonenames
.append(bone
.name
)
1143 print("[=========================]")
1145 for ActionNLA
in bpy
.data
.actions
:
1146 print("\n==== Action Set ====")
1149 #print("\nChecking actions matching groups with bone names...")
1150 #Check if the bone names matches the action groups names
1151 for group
in ActionNLA
.groups
:
1152 for abone
in bonenames
:
1153 #print("name:>>",abone)
1154 if abone
== group
.name
:
1157 #if action groups matches the bones length and names matching the gourps do something
1158 if (len(ActionNLA
.groups
) == len(bonenames
)) and (nobone
== len(ActionNLA
.groups
)):
1159 print("Action Set match: Pass")
1162 print("Action Set match: Fail")
1163 print("Action Name:",ActionNLA
.name
)
1167 arm
= amatureobject
#set armature object
1168 if not arm
.animation_data
:
1169 print("======================================")
1170 print("Check Animation Data: None")
1171 print("Armature has no animation, skipping...")
1172 print("======================================")
1175 if not arm
.animation_data
.action
:
1176 print("======================================")
1177 print("Check Action: None")
1178 print("Armature has no animation, skipping...")
1179 print("======================================")
1181 arm
.animation_data
.action
= ActionNLA
1182 act
= arm
.animation_data
.action
1183 action_name
= act
.name
1185 if not len(act
.fcurves
):
1186 print("//===========================================================")
1187 print("// None bone pose set keys for this action set... skipping...")
1188 print("//===========================================================")
1191 #this deal with action export control
1192 if bHaveAction
== True:
1194 print("Action Name:",action_name
)
1195 #look for min and max frame that current set keys
1196 framemin
, framemax
= act
.frame_range
1197 #print("max frame:",framemax)
1198 start_frame
= int(framemin
)
1199 end_frame
= int(framemax
)
1200 scene_frames
= range(start_frame
, end_frame
+1)
1201 frame_count
= len(scene_frames
)
1202 #===================================================
1203 anim
= AnimInfoBinary()
1204 anim
.Name
= action_name
1205 anim
.Group
= "" #what is group?
1206 anim
.NumRawFrames
= frame_count
1207 anim
.AnimRate
= anim_rate
1208 anim
.FirstRawFrame
= cur_frame_index
1209 #===================================================
1210 count_previous_keys
= len(psa_file
.RawKeys
.Data
)
1211 print("Frame Key Set Count:",frame_count
, "Total Frame:",frame_count
)
1212 #print("init action bones...")
1213 unique_bone_indexes
= {}
1217 #build bone node for animation keys needed to be set
1218 for bone
in arm
.data
.bones
:
1219 bones_lookup
[bone
.name
] = bone
1220 #print("bone name:",bone.name)
1221 frame_count
= len(scene_frames
)
1222 #print ('Frame Count: %i' % frame_count)
1223 pose_data
= arm
.pose
1225 #these must be ordered in the order the bones will show up in the PSA file!
1227 ordered_bones
= sorted([(psa_file
.UseBone(x
.name
), x
) for x
in pose_data
.bones
], key
=operator
.itemgetter(0))
1229 #############################
1230 # ORDERED FRAME, BONE
1231 #for frame in scene_frames:
1233 for i
in range(frame_count
):
1234 frame
= scene_frames
[i
]
1236 #print ("==== outputting frame %i ===" % frame)
1238 if frame_count
> i
+1:
1239 next_frame
= scene_frames
[i
+1]
1240 #print "This Frame: %i, Next Frame: %i" % (frame, next_frame)
1243 #print "This Frame: %i, Next Frame: NONE" % frame
1245 #frame start from 1 as number one from blender
1246 blender_scene
.frame_set(frame
)
1248 cur_frame_index
= cur_frame_index
+ 1
1249 for bone_data
in ordered_bones
:
1250 bone_index
= bone_data
[0]
1251 pose_bone
= bone_data
[1]
1252 #print("[=====POSE NAME:",pose_bone.name)
1254 #print("LENG >>.",len(bones_lookup))
1255 blender_bone
= bones_lookup
[pose_bone
.name
]
1257 #just need the total unique bones used, later for this AnimInfoBinary
1258 unique_bone_indexes
[bone_index
] = bone_index
1260 #print ("-------------------", pose_bone.name)
1261 head
= pose_bone
.head
1263 posebonemat
= mathutils
.Matrix(pose_bone
.matrix
)
1264 parent_pose
= pose_bone
.parent
1265 if parent_pose
!= None:
1266 parentposemat
= mathutils
.Matrix(parent_pose
.matrix
)
1267 #blender 2.4X it been flip around with new 2.50 (mat1 * mat2) should now be (mat2 * mat1)
1268 posebonemat
= parentposemat
.invert() * posebonemat
1269 head
= posebonemat
.translation_part()
1270 quat
= posebonemat
.to_quat().normalize()
1271 vkey
= VQuatAnimKey()
1272 vkey
.Position
.X
= head
.x
1273 vkey
.Position
.Y
= head
.y
1274 vkey
.Position
.Z
= head
.z
1276 if parent_pose
!= None:
1277 quat
= make_fquat(quat
)
1279 quat
= make_fquat_default(quat
)
1281 vkey
.Orientation
= quat
1282 #print("Head:",head)
1283 #print("Orientation",quat)
1285 #time from now till next frame = diff / framesPerSec
1287 diff
= next_frame
- frame
1291 #print ("Diff = ", diff)
1292 vkey
.Time
= float(diff
)/float(anim_rate
)
1294 psa_file
.AddRawKey(vkey
)
1296 #done looping frames
1297 #done looping armatures
1298 #continue adding animInfoBinary counts here
1300 anim
.TotalBones
= len(unique_bone_indexes
)
1301 print("Bones Count:",anim
.TotalBones
)
1302 anim
.TrackTime
= float(frame_count
) / anim
.AnimRate
1303 print("Time Track Frame:",anim
.TrackTime
)
1304 psa_file
.AddAnimation(anim
)
1305 print("==== Finish Action Build(s) ====")
1307 print("Exporting one action:",bpy
.context
.scene
.unrealactionexportall
)
1308 #list of armature objects
1309 for arm
in blender_armatures
:
1310 #check if there animation data from armature or something
1312 if not arm
.animation_data
:
1313 print("======================================")
1314 print("Check Animation Data: None")
1315 print("Armature has no animation, skipping...")
1316 print("======================================")
1319 if not arm
.animation_data
.action
:
1320 print("======================================")
1321 print("Check Action: None")
1322 print("Armature has no animation, skipping...")
1323 print("======================================")
1325 act
= arm
.animation_data
.action
1327 action_name
= act
.name
1329 if not len(act
.fcurves
):
1330 print("//===========================================================")
1331 print("// None bone pose set keys for this action set... skipping...")
1332 print("//===========================================================")
1335 #this deal with action export control
1336 if bHaveAction
== True:
1338 print("==== Action Set ====")
1339 print("Action Name:",action_name
)
1340 #look for min and max frame that current set keys
1341 framemin
, framemax
= act
.frame_range
1342 #print("max frame:",framemax)
1343 start_frame
= int(framemin
)
1344 end_frame
= int(framemax
)
1345 scene_frames
= range(start_frame
, end_frame
+1)
1346 frame_count
= len(scene_frames
)
1347 #===================================================
1348 anim
= AnimInfoBinary()
1349 anim
.Name
= action_name
1350 anim
.Group
= "" #what is group?
1351 anim
.NumRawFrames
= frame_count
1352 anim
.AnimRate
= anim_rate
1353 anim
.FirstRawFrame
= cur_frame_index
1354 #===================================================
1355 count_previous_keys
= len(psa_file
.RawKeys
.Data
)
1356 print("Frame Key Set Count:",frame_count
, "Total Frame:",frame_count
)
1357 #print("init action bones...")
1358 unique_bone_indexes
= {}
1362 #build bone node for animation keys needed to be set
1363 for bone
in arm
.data
.bones
:
1364 bones_lookup
[bone
.name
] = bone
1365 #print("bone name:",bone.name)
1366 frame_count
= len(scene_frames
)
1367 #print ('Frame Count: %i' % frame_count)
1368 pose_data
= arm
.pose
1370 #these must be ordered in the order the bones will show up in the PSA file!
1372 ordered_bones
= sorted([(psa_file
.UseBone(x
.name
), x
) for x
in pose_data
.bones
], key
=operator
.itemgetter(0))
1374 #############################
1375 # ORDERED FRAME, BONE
1376 #for frame in scene_frames:
1378 for i
in range(frame_count
):
1379 frame
= scene_frames
[i
]
1381 #print ("==== outputting frame %i ===" % frame)
1383 if frame_count
> i
+1:
1384 next_frame
= scene_frames
[i
+1]
1385 #print "This Frame: %i, Next Frame: %i" % (frame, next_frame)
1388 #print "This Frame: %i, Next Frame: NONE" % frame
1390 #frame start from 1 as number one from blender
1391 blender_scene
.frame_set(frame
)
1393 cur_frame_index
= cur_frame_index
+ 1
1394 for bone_data
in ordered_bones
:
1395 bone_index
= bone_data
[0]
1396 pose_bone
= bone_data
[1]
1397 #print("[=====POSE NAME:",pose_bone.name)
1399 #print("LENG >>.",len(bones_lookup))
1400 blender_bone
= bones_lookup
[pose_bone
.name
]
1402 #just need the total unique bones used, later for this AnimInfoBinary
1403 unique_bone_indexes
[bone_index
] = bone_index
1405 #print ("-------------------", pose_bone.name)
1406 head
= pose_bone
.head
1408 posebonemat
= mathutils
.Matrix(pose_bone
.matrix
)
1409 parent_pose
= pose_bone
.parent
1410 if parent_pose
!= None:
1411 parentposemat
= mathutils
.Matrix(parent_pose
.matrix
)
1412 #blender 2.4X it been flip around with new 2.50 (mat1 * mat2) should now be (mat2 * mat1)
1413 posebonemat
= parentposemat
.invert() * posebonemat
1414 head
= posebonemat
.translation_part()
1415 quat
= posebonemat
.to_quat().normalize()
1416 vkey
= VQuatAnimKey()
1417 vkey
.Position
.X
= head
.x
1418 vkey
.Position
.Y
= head
.y
1419 vkey
.Position
.Z
= head
.z
1421 if parent_pose
!= None:
1422 quat
= make_fquat(quat
)
1424 quat
= make_fquat_default(quat
)
1426 vkey
.Orientation
= quat
1427 #print("Head:",head)
1428 #print("Orientation",quat)
1430 #time from now till next frame = diff / framesPerSec
1432 diff
= next_frame
- frame
1436 #print ("Diff = ", diff)
1437 vkey
.Time
= float(diff
)/float(anim_rate
)
1439 psa_file
.AddRawKey(vkey
)
1441 #done looping frames
1442 #done looping armatures
1443 #continue adding animInfoBinary counts here
1445 anim
.TotalBones
= len(unique_bone_indexes
)
1446 print("Bones Count:",anim
.TotalBones
)
1447 anim
.TrackTime
= float(frame_count
) / anim
.AnimRate
1448 print("Time Track Frame:",anim
.TrackTime
)
1449 psa_file
.AddAnimation(anim
)
1450 print("==== Finish Action Build(s) ====")
1452 exportmessage
= "Export Finish"
1454 def fs_callback(filename
, context
):
1455 #this deal with repeat export and the reset settings
1456 global bonedata
, BBCount
, nbone
, exportmessage
1457 bonedata
= []#clear array
1461 start_time
= time
.clock()
1463 print ("========EXPORTING TO UNREAL SKELETAL MESH FORMATS========\r\n")
1464 print("Blender Version:", bpy
.app
.version_string
)
1469 #sanity check - this should already have the extension, but just in case, we'll give it one if it doesn't
1470 psk_filename
= make_filename_ext(filename
, '.psk')
1472 #make the psa filename
1473 psa_filename
= make_filename_ext(filename
, '.psa')
1475 print ('PSK File: ' + psk_filename
)
1476 print ('PSA File: ' + psa_filename
)
1481 blender_armature
= []
1485 current_scene
= context
.scene
1486 cur_frame
= current_scene
.frame_current
#store current frame before we start walking them during animation parse
1487 objects
= current_scene
.objects
1489 print("Checking object count...")
1490 for next_obj
in objects
:
1491 if next_obj
.type == 'MESH':
1492 blender_meshes
.append(next_obj
)
1493 if (next_obj
.select
):
1494 #print("mesh object select")
1495 selectmesh
.append(next_obj
)
1496 if next_obj
.type == 'ARMATURE':
1497 blender_armature
.append(next_obj
)
1498 if (next_obj
.select
):
1499 #print("armature object select")
1500 selectarmature
.append(next_obj
)
1502 print("Mesh Count:",len(blender_meshes
)," Armature Count:",len(blender_armature
))
1503 print("====================================")
1504 print("Checking Mesh Condtion(s):")
1505 if len(blender_meshes
) == 1:
1506 print(" - One Mesh Scene")
1507 elif (len(blender_meshes
) > 1) and (len(selectmesh
) == 1):
1508 print(" - One Mesh [Select]")
1510 print(" - Too Many Meshes!")
1511 print(" - Select One Mesh Object!")
1513 print("====================================")
1514 print("Checking Armature Condtion(s):")
1515 if len(blender_armature
) == 1:
1516 print(" - One Armature Scene")
1517 elif (len(blender_armature
) > 1) and (len(selectarmature
) == 1):
1518 print(" - One Armature [Select]")
1520 print(" - Too Armature Meshes!")
1521 print(" - Select One Armature Object Only!")
1524 if (bmesh
== False) or (barmature
== False):
1525 exportmessage
= "Export Fail! Check Log."
1526 print("=================================")
1527 print("= Export Fail! =")
1528 print("=================================")
1530 exportmessage
= "Export Finish!"
1531 #need to build a temp bone index for mesh group vertex
1532 BoneIndexArmature(blender_armature
)
1535 #######################
1537 # we build the vertexes, wedges, and faces in here, as well as a vertexgroup lookup table
1538 # for the armature parse
1539 print("//===============================")
1541 print("//===============================")
1542 parse_meshes(blender_meshes
, psk
)
1544 context
.scene
.frame_set(cur_frame
) #set frame back to original frame
1545 print ("Exception during Mesh Parse")
1549 #######################
1550 # STEP 2: ARMATURE DUMP
1551 # IMPORTANT: do this AFTER parsing meshes - we need to use the vertex group data from
1552 # the mesh parse in here to generate bone influences
1553 print("//===============================")
1555 print("//===============================")
1556 parse_armature(blender_armature
, psk
, psa
)
1559 context
.scene
.frame_set(cur_frame
) #set frame back to original frame
1560 print ("Exception during Armature Parse")
1564 #######################
1565 # STEP 3: ANIMATION DUMP
1566 # IMPORTANT: do AFTER parsing bones - we need to do bone lookups in here during animation frames
1567 print("//===============================")
1569 print("//===============================")
1570 parse_animation(current_scene
, blender_armature
, psa
)
1573 context
.scene
.frame_set(cur_frame
) #set frame back to original frame
1574 print ("Exception during Animation Parse")
1577 # reset current frame
1579 context
.scene
.frame_set(cur_frame
) #set frame back to original frame
1581 ##########################
1583 print("//===========================================")
1584 print("// bExportPsk:",bpy
.context
.scene
.unrealexportpsk
," bExportPsa:",bpy
.context
.scene
.unrealexportpsa
)
1585 print("//===========================================")
1586 if bpy
.context
.scene
.unrealexportpsk
== True:
1587 print("Writing Skeleton Mesh Data...")
1590 file = open(psk_filename
, "wb")
1591 file.write(psk
.dump())
1593 print ("Successfully Exported File: " + psk_filename
)
1594 if bpy
.context
.scene
.unrealexportpsa
== True:
1595 print("Writing Animaiton Data...")
1597 if not psa
.IsEmpty():
1599 file = open(psa_filename
, "wb")
1600 file.write(psa
.dump())
1602 print ("Successfully Exported File: " + psa_filename
)
1604 print ("No Animations (.psa file) to Export")
1606 print ('PSK/PSA Export Script finished in %.2f seconds' % (time
.clock() - start_time
))
1607 print( "Current Script version: ",bl_addon_info
['version'])
1608 #MSG BOX EXPORT COMPLETE
1612 print ("PSK/PSA Export Complete")
1614 def write_data(path
, context
):
1615 print("//============================")
1616 print("// running psk/psa export...")
1617 print("//============================")
1618 fs_callback(path
, context
)
1621 from bpy
.props
import *
1625 # [index,text field,0] #or something like that
1626 exporttypedata
.append(("0","PSK","Export PSK"))
1627 exporttypedata
.append(("1","PSA","Export PSA"))
1628 exporttypedata
.append(("2","ALL","Export ALL"))
1630 bpy
.types
.Scene
.unrealfpsrate
= IntProperty(
1632 description
="Set the frame per second (fps) for unreal.",
1633 default
=24,min=1,max=100)
1635 bpy
.types
.Scene
.unrealexport_settings
= EnumProperty(
1637 description
="Select a export settings (psk/psa/all)...",
1638 items
= exporttypedata
, default
= '0')
1640 bpy
.types
.Scene
.unrealtriangulatebool
= BoolProperty(
1641 name
="Triangulate Mesh",
1642 description
="Convert Quad to Tri Mesh Boolean...",
1645 bpy
.types
.Scene
.unrealactionexportall
= BoolProperty(
1647 description
="This let you export all the actions from current armature that matches bone name in action groups names.",
1650 bpy
.types
.Scene
.unrealexportpsk
= BoolProperty(
1651 name
="bool export psa",
1652 description
="bool for exporting this psk format",
1655 bpy
.types
.Scene
.unrealexportpsa
= BoolProperty(
1656 name
="bool export psa",
1657 description
="bool for exporting this psa format",
1660 class ExportUDKAnimData(bpy
.types
.Operator
):
1661 global exportmessage
1662 '''Export Skeleton Mesh / Animation Data file(s)'''
1663 bl_idname
= "export.udk_anim_data" # this is important since its how bpy.ops.export.udk_anim_data is constructed
1664 bl_label
= "Export PSK/PSA"
1665 __doc__
= "One mesh and one armature else select one mesh or armature to be exported."
1667 # List of operator properties, the attributes will be assigned
1668 # to the class instance from the operator settings before calling.
1670 filepath
= StringProperty(name
="File Path", description
="Filepath used for exporting the PSA file", maxlen
= 1024, default
= "", subtype
='FILE_PATH')
1671 pskexportbool
= BoolProperty(name
="Export PSK", description
="Export Skeletal Mesh", default
= True)
1672 psaexportbool
= BoolProperty(name
="Export PSA", description
="Export Action Set (Animation Data)", default
= True)
1673 actionexportall
= BoolProperty(name
="All Actions", description
="This will export all the actions that matches the current armature.", default
=False)
1676 def poll(cls
, context
):
1677 return context
.active_object
!= None
1679 def execute(self
, context
):
1680 #check if skeleton mesh is needed to be exported
1681 if (self
.pskexportbool
):
1682 bpy
.context
.scene
.unrealexportpsk
= True
1684 bpy
.context
.scene
.unrealexportpsk
= False
1685 #check if animation data is needed to be exported
1686 if (self
.psaexportbool
):
1687 bpy
.context
.scene
.unrealexportpsa
= True
1689 bpy
.context
.scene
.unrealexportpsa
= False
1691 if (self
.actionexportall
):
1692 bpy
.context
.scene
.unrealactionexportall
= True
1694 bpy
.context
.scene
.unrealactionexportall
= False
1696 write_data(self
.filepath
, context
)
1698 self
.report({'WARNING', 'INFO'}, exportmessage
)
1701 def invoke(self
, context
, event
):
1702 wm
= context
.window_manager
1703 wm
.fileselect_add(self
)
1704 return {'RUNNING_MODAL'}
1706 class VIEW3D_PT_unrealtools_objectmode(bpy
.types
.Panel
):
1707 bl_space_type
= "VIEW_3D"
1708 bl_region_type
= "TOOLS"
1709 bl_label
= "Unreal Tools"
1712 def poll(cls
, context
):
1713 return context
.active_object
1715 def draw(self
, context
):
1716 layout
= self
.layout
1718 layout
.prop(rd
, "unrealexport_settings",expand
=True)
1719 layout
.operator("object.UnrealExport")#button
1720 #FPS #it use the real data from your scene
1721 layout
.prop(rd
.render
, "fps")
1723 layout
.prop(rd
, "unrealactionexportall")
1725 #row.label(text="Action Set(s)(not build)")
1726 #for action in bpy.data.actions:
1727 #print(dir( action))
1728 #print(action.frame_range)
1730 #row.prop(action, "name")
1732 #print(dir(action.groups[0]))
1733 #for g in action.groups:#those are bones
1736 #print("////////////")
1738 #print("////////////")
1740 #row.label(text="Active:" + action.select)
1743 class OBJECT_OT_UnrealExport(bpy
.types
.Operator
):
1744 global exportmessage
1745 bl_idname
= "OBJECT_OT_UnrealExport"
1746 bl_label
= "Unreal Export"
1747 __doc__
= "Select export setting for .psk/.psa or both."
1749 def invoke(self
, context
, event
):
1750 print("Init Export Script:")
1751 if(int(bpy
.context
.scene
.unrealexport_settings
) == 0):
1752 bpy
.context
.scene
.unrealexportpsk
= True
1753 bpy
.context
.scene
.unrealexportpsa
= False
1754 print("Exporting PSK...")
1755 if(int(bpy
.context
.scene
.unrealexport_settings
) == 1):
1756 bpy
.context
.scene
.unrealexportpsk
= False
1757 bpy
.context
.scene
.unrealexportpsa
= True
1758 print("Exporting PSA...")
1759 if(int(bpy
.context
.scene
.unrealexport_settings
) == 2):
1760 bpy
.context
.scene
.unrealexportpsk
= True
1761 bpy
.context
.scene
.unrealexportpsa
= True
1762 print("Exporting ALL...")
1764 default_path
= os
.path
.splitext(bpy
.data
.filepath
)[0] + ".psk"
1765 fs_callback(default_path
, bpy
.context
)
1767 #self.report({'WARNING', 'INFO'}, exportmessage)
1768 self
.report({'INFO'}, exportmessage
)
1771 def menu_func(self
, context
):
1772 #bpy.context.scene.unrealexportpsk = True
1773 #bpy.context.scene.unrealexportpsa = True
1774 default_path
= os
.path
.splitext(bpy
.data
.filepath
)[0] + ".psk"
1775 self
.layout
.operator("export.udk_anim_data", text
="Skeleton Mesh / Animation Data (.psk/.psa)").filepath
= default_path
1778 bpy
.types
.INFO_MT_file_export
.append(menu_func
)
1781 bpy
.types
.INFO_MT_file_export
.remove(menu_func
)
1783 if __name__
== "__main__":