1 #====================== BEGIN 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.
17 #======================= END GPL LICENSE BLOCK =============================
20 "name": "Export Unreal Engine Format(.psk/.psa)",
21 "author": "Darknet/Optimus_P-Fat/Active_Trash/Sinsoft/VendorX/Spoof",
23 "blender": (2, 65, 4),
24 "location": "File > Export > Skeletal Mesh/Animation Data (.psk/.psa)",
25 "description": "Export Skeleletal Mesh/Animation Data",
27 "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
28 "Scripts/Import-Export/Unreal_psk_psa",
29 "category": "Import-Export",
33 -- Unreal Skeletal Mesh and Animation Export (.psk and .psa) export script v0.0.1 --<br>
36 - This script Exports To Unreal's PSK and PSA file formats for Skeletal Meshes and Animations. <br>
37 - This script DOES NOT support vertex animation! These require completely different file formats. <br>
43 - This version adds support for more than one material index!
47 - This will work on UT3 and it is a stable version that work with vehicle for testing.
48 - Main Bone fix no dummy needed to be there.
49 - Just bone issues position, rotation, and offset for psk.
50 - The armature bone position, rotation, and the offset of the bone is fix. It was to deal with skeleton mesh export for psk.
51 - Animation is fix for position, offset, rotation bone support one rotation direction when armature build.
52 - It will convert your mesh into triangular when exporting to psk file.
53 - Did not work with psa export yet.
56 - The animatoin will support different bone rotations when export the animation.
59 - Fixed Action set keys frames when there is no pose keys and it will ignore it.
62 - Fixed multiple objects when exporting to psk. Select one mesh to export to psk.
66 - Blender 2.50 svn (Support)
69 - export_cal3d.py (Position of the Bones Format)
70 - blender2md5.py (Animation Translation Format)
71 - export_obj.py (Blender 2.5/Pyhton 3.x Format)
73 - freenode #blendercoder -> user -> ideasman42
75 - Give Credit to those who work on this script.
81 #===========================================================================
83 NOTES for Jan 2012 refactor (Spoof)
85 * THIS IS A WORK IN PROGRESS. These modifications were originally
86 intended for internal use and are incomplete. Use at your own risk! *
90 - (Blender 2.62) changes to Matrix math
91 - (Blender 2.62) check for long names
92 - option to manually set the root bone for export
96 - new bone parsing to allow advanced rigging
97 - identification of armature and mesh
98 - removed the need to apply an action to the armature
99 - fixed anim rate to work correctly in UDK (no more FPS fudging)
100 - progress reporting while processing smooth groups
101 - more informative logging
102 - code refactor for clarity and modularity
103 - naming conventions unified to use lowercase_with_underscore
104 - C++ datatypes and PSK/PSA classes remain CamelCaseStyle for clarity
105 - names such as 'ut' and 'unreal' unified to 'udk'
106 - simplification of code structure
107 - removed legacy code paths
111 This version of the exporter is more selective over which bones are considered
112 part of the UDK skeletal mesh, and allows greater flexibility for adding
113 control bones to aid in animation.
115 Taking advantage of this script requires the following methodology:
117 * Place all exportable bones into a bone hierarchy extending from a single
118 root. This root bone must have use_deform enabled. All other root bones
119 in the armature must disable use_deform. *
121 The script searches for a root bone with use_deform set true and considers all
122 bones parented to it as part of the UDK skeletal mesh. Thus only these bones
123 are exported and all other bones are ignored.
125 This removes many restrictions on the rigger/animator, who can add control
126 bone hierarchies to the rig, and keyframe any element into actions. With this
127 approach you can build complex animation rigs in a similar vein to the Rigify
128 add-on, by Nathan Vegdahl. However...
130 * Rigify is incompatible with this script *
132 Rigify interlaces deformer bones within a single hierarchy making it difficult
133 to deconstruct for export. It also splits some meta-rig bones into multiple
134 deformer bones (bad for optimising a game character). I had partial success
135 writing a parser for the structure, but it was taking too much time and,
136 considering the other issues with Rigify, it was abandoned.
138 #===========================================================================
149 from bpy
.props
import *
150 from struct
import pack
152 # REFERENCE MATERIAL JUST IN CASE:
154 # U = x / sqrt(x^2 + y^2 + z^2)
155 # V = y / sqrt(x^2 + y^2 + z^2)
157 # Triangles specifed counter clockwise for front face
159 # defines for sizeofs
163 SIZE_ANIMINFOBINARY
= 168
164 SIZE_VCHUNKHEADER
= 32
167 SIZE_FNAMEDBONEBINARY
= 120
168 SIZE_VRAWBONEINFLUENCE
= 12
169 SIZE_VQUATANIMKEY
= 32
176 #===========================================================================
177 # Custom exception class
178 #===========================================================================
179 class Error( Exception ):
181 def __init__(self
, message
):
182 self
.message
= message
184 #===========================================================================
185 # Verbose logging with loop truncation
186 #===========================================================================
187 def verbose( msg
, iteration
=-1, max_iterations
=4, msg_truncated
="..." ):
189 if bpy
.context
.scene
.udk_option_verbose
== True:
190 # limit the number of times a loop can output messages
191 if iteration
> max_iterations
:
193 elif iteration
== max_iterations
:
199 #===========================================================================
200 # Log header/separator
201 #===========================================================================
202 def header( msg
, justify
='LEFT', spacer
='_', cols
=78 ):
204 if justify
== 'LEFT':
205 s
= '{:{spacer}<{cols}}'.format(msg
+" ", spacer
=spacer
, cols
=cols
)
207 elif justify
== 'RIGHT':
208 s
= '{:{spacer}>{cols}}'.format(" "+msg
, spacer
=spacer
, cols
=cols
)
211 s
= '{:{spacer}^{cols}}'.format(" "+msg
+" ", spacer
=spacer
, cols
=cols
)
213 return "\n" + s
+ "\n"
215 #===========================================================================
216 # Generic Object->Integer mapping
217 # the object must be usable as a dictionary key
218 #===========================================================================
227 return self
.dict[obj
]
230 self
.next
= self
.next
+ 1
235 getval
= operator
.itemgetter(0)
236 getkey
= operator
.itemgetter(1)
237 return map(getval
, sorted(self
.dict.items(), key
=getkey
))
239 #===========================================================================
240 # RG - UNREAL DATA STRUCTS - CONVERTED FROM C STRUCTS GIVEN ON UDN SITE
241 # provided here: http://udn.epicgames.com/Two/BinaryFormatSpecifications.html
242 # updated UDK (Unreal Engine 3): http://udn.epicgames.com/Three/BinaryFormatSpecifications.html
243 #===========================================================================
253 return pack('ffff', self
.X
, self
.Y
, self
.Z
, self
.W
)
255 def __cmp__(self
, other
):
256 return cmp(self
.X
, other
.X
) \
257 or cmp(self
.Y
, other
.Y
) \
258 or cmp(self
.Z
, other
.Z
) \
259 or cmp(self
.W
, other
.W
)
262 return hash(self
.X
) ^
hash(self
.Y
) ^
hash(self
.Z
) ^
hash(self
.W
)
265 return "[%f,%f,%f,%f](FQuat)" % (self
.X
, self
.Y
, self
.Z
, self
.W
)
267 class FVector(object):
269 def __init__(self
, X
=0.0, Y
=0.0, Z
=0.0):
275 return pack('fff', self
.X
, self
.Y
, self
.Z
)
277 def __cmp__(self
, other
):
278 return cmp(self
.X
, other
.X
) \
279 or cmp(self
.Y
, other
.Y
) \
280 or cmp(self
.Z
, other
.Z
)
283 return (type(self
).__name
__, self
.X
, self
.Y
, self
.Z
)
286 return hash(self
._key
())
288 def __eq__(self
, other
):
289 if not hasattr(other
, '_key'):
291 return self
._key
() == other
._key
()
293 def dot(self
, other
):
294 return self
.X
* other
.X
+ self
.Y
* other
.Y
+ self
.Z
* other
.Z
296 def cross(self
, other
):
297 return FVector(self
.Y
* other
.Z
- self
.Z
* other
.Y
,
298 self
.Z
* other
.X
- self
.X
* other
.Z
,
299 self
.X
* other
.Y
- self
.Y
* other
.X
)
301 def sub(self
, other
):
302 return FVector(self
.X
- other
.X
,
309 self
.Orientation
= FQuat()
310 self
.Position
= FVector()
317 return self
.Orientation
.dump() + self
.Position
.dump() + pack('4f', self
.Length
, self
.XSize
, self
.YSize
, self
.ZSize
)
319 class AnimInfoBinary
:
322 self
.Name
= "" # length=64
323 self
.Group
= "" # length=64
326 self
.KeyCompressionStyle
= 0
328 self
.KeyPrediction
= 0.0
332 self
.FirstRawFrame
= 0
333 self
.NumRawFrames
= 0
336 return 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
)
340 def __init__(self
, name
, type_size
):
341 self
.ChunkID
= str.encode(name
) # length=20
342 self
.TypeFlag
= 1999801 # special value
343 self
.DataSize
= type_size
347 return pack('20siii', self
.ChunkID
, self
.TypeFlag
, self
.DataSize
, self
.DataCount
)
352 self
.MaterialName
= "" # length=64
353 self
.TextureIndex
= 0
354 self
.PolyFlags
= 0 # DWORD
356 self
.AuxFlags
= 0 # DWORD
361 #print("DATA MATERIAL:",self.MaterialName)
362 return pack('64siLiLii', str.encode(self
.MaterialName
), self
.TextureIndex
, self
.PolyFlags
, self
.AuxMaterial
, self
.AuxFlags
, self
.LodBias
, self
.LodStyle
)
367 self
.Name
= "" # length = 64
368 self
.Flags
= 0 # DWORD
371 self
.BonePos
= VJointPos()
374 return pack('64sLii', str.encode(self
.Name
), self
.Flags
, self
.NumChildren
, self
.ParentIndex
) + self
.BonePos
.dump()
376 #same as above - whatever - this is how Epic does it...
377 class FNamedBoneBinary
:
380 self
.Name
= "" # length = 64
381 self
.Flags
= 0 # DWORD
384 self
.BonePos
= VJointPos()
385 self
.IsRealBone
= 0 # this is set to 1 when the bone is actually a bone in the mesh and not a dummy
388 return pack('64sLii', str.encode(self
.Name
), self
.Flags
, self
.NumChildren
, self
.ParentIndex
) + self
.BonePos
.dump()
390 class VRawBoneInfluence
:
398 return pack('fii', self
.Weight
, self
.PointIndex
, self
.BoneIndex
)
403 self
.Position
= FVector()
404 self
.Orientation
= FQuat()
408 return self
.Position
.dump() + self
.Orientation
.dump() + pack('f', self
.Time
)
410 class VVertex(object):
413 self
.PointIndex
= 0 # WORD
416 self
.MatIndex
= 0 # BYTE
417 self
.Reserved
= 0 # BYTE
421 return pack('HHffBBH', self
.PointIndex
, 0, self
.U
, self
.V
, self
.MatIndex
, self
.Reserved
, 0)
423 def __cmp__(self
, other
):
424 return cmp(self
.PointIndex
, other
.PointIndex
) \
425 or cmp(self
.U
, other
.U
) \
426 or cmp(self
.V
, other
.V
) \
427 or cmp(self
.MatIndex
, other
.MatIndex
) \
428 or cmp(self
.Reserved
, other
.Reserved
) \
429 or cmp(self
.SmoothGroup
, other
.SmoothGroup
)
432 return (type(self
).__name
__, self
.PointIndex
, self
.U
, self
.V
, self
.MatIndex
, self
.Reserved
)
435 return hash(self
._key
())
437 def __eq__(self
, other
):
438 if not hasattr(other
, '_key'):
440 return self
._key
() == other
._key
()
445 self
.Point
= FVector()
447 def __cmp__(self
, other
):
448 return cmp(self
.Point
, other
.Point
)
451 return hash(self
._key
())
454 return (type(self
).__name
__, self
.Point
)
456 def __eq__(self
, other
):
457 if not hasattr(other
, '_key'):
459 return self
._key
() == other
._key
()
461 class VPoint(object):
464 self
.Point
= FVector()
468 return self
.Point
.dump()
470 def __cmp__(self
, other
):
471 return cmp(self
.Point
, other
.Point
) \
472 or cmp(self
.SmoothGroup
, other
.SmoothGroup
)
475 return (type(self
).__name
__, self
.Point
, self
.SmoothGroup
)
478 return hash(self
._key
()) \
479 ^
hash(self
.SmoothGroup
)
481 def __eq__(self
, other
):
482 if not hasattr(other
, '_key'):
484 return self
._key
() == other
._key
()
489 self
.WedgeIndex0
= 0 # WORD
490 self
.WedgeIndex1
= 0 # WORD
491 self
.WedgeIndex2
= 0 # WORD
492 self
.MatIndex
= 0 # BYTE
493 self
.AuxMatIndex
= 0 # BYTE
494 self
.SmoothingGroups
= 0 # DWORD
497 return pack('HHHBBL', self
.WedgeIndex0
, self
.WedgeIndex1
, self
.WedgeIndex2
, self
.MatIndex
, self
.AuxMatIndex
, self
.SmoothingGroups
)
498 #print("smooth",self.SmoothingGroups)
499 #return pack('HHHBBI', self.WedgeIndex0, self.WedgeIndex1, self.WedgeIndex2, self.MatIndex, self.AuxMatIndex, self.SmoothingGroups)
501 # END UNREAL DATA STRUCTS
502 #===========================================================================
504 #===========================================================================
505 # RG - helper class to handle the normal way the UT files are stored
506 # as sections consisting of a header and then a list of data structures
507 #===========================================================================
510 def __init__(self
, name
, type_size
):
511 self
.Header
= VChunkHeader(name
, type_size
)
512 self
.Data
= [] # list of datatypes
515 data
= self
.Header
.dump()
516 for i
in range(len(self
.Data
)):
517 data
= data
+ self
.Data
[i
].dump()
520 def UpdateHeader(self
):
521 self
.Header
.DataCount
= len(self
.Data
)
523 #===========================================================================
525 #===========================================================================
529 self
.GeneralHeader
= VChunkHeader("ACTRHEAD", 0)
530 self
.Points
= FileSection("PNTS0000", SIZE_VPOINT
) # VPoint
531 self
.Wedges
= FileSection("VTXW0000", SIZE_VVERTEX
) # VVertex
532 self
.Faces
= FileSection("FACE0000", SIZE_VTRIANGLE
) # VTriangle
533 self
.Materials
= FileSection("MATT0000", SIZE_VMATERIAL
) # VMaterial
534 self
.Bones
= FileSection("REFSKELT", SIZE_VBONE
) # VBone
535 self
.Influences
= FileSection("RAWWEIGHTS", SIZE_VRAWBONEINFLUENCE
) # VRawBoneInfluence
537 #RG - this mapping is not dumped, but is used internally to store the new point indices
538 # for vertex groups calculated during the mesh dump, so they can be used again
539 # to dump bone influences during the armature dump
541 # the key in this dictionary is the VertexGroup/Bone Name, and the value
542 # is a list of tuples containing the new point index and the weight, in that order
545 # { groupname : [ (index, weight), ... ], ... }
548 # { 'MyVertexGroup' : [ (0, 1.0), (5, 1.0), (3, 0.5) ] , 'OtherGroup' : [(2, 1.0)] }
550 self
.VertexGroups
= {}
552 def AddPoint(self
, p
):
553 self
.Points
.Data
.append(p
)
555 def AddWedge(self
, w
):
556 self
.Wedges
.Data
.append(w
)
558 def AddFace(self
, f
):
559 self
.Faces
.Data
.append(f
)
561 def AddMaterial(self
, m
):
562 self
.Materials
.Data
.append(m
)
564 def AddBone(self
, b
):
565 self
.Bones
.Data
.append(b
)
567 def AddInfluence(self
, i
):
568 self
.Influences
.Data
.append(i
)
570 def UpdateHeaders(self
):
571 self
.Points
.UpdateHeader()
572 self
.Wedges
.UpdateHeader()
573 self
.Faces
.UpdateHeader()
574 self
.Materials
.UpdateHeader()
575 self
.Bones
.UpdateHeader()
576 self
.Influences
.UpdateHeader()
580 data
= self
.GeneralHeader
.dump() + self
.Points
.dump() + self
.Wedges
.dump() + self
.Faces
.dump() + self
.Materials
.dump() + self
.Bones
.dump() + self
.Influences
.dump()
583 def GetMatByIndex(self
, mat_index
):
584 if mat_index
>= 0 and len(self
.Materials
.Data
) > mat_index
:
585 return self
.Materials
.Data
[mat_index
]
588 # modified by VendorX
589 m
.MaterialName
= MaterialName
[mat_index
]
594 print( "{:>16} {:}".format( "Points", len(self
.Points
.Data
) ) )
595 print( "{:>16} {:}".format( "Wedges", len(self
.Wedges
.Data
) ) )
596 print( "{:>16} {:}".format( "Faces", len(self
.Faces
.Data
) ) )
597 print( "{:>16} {:}".format( "Materials", len(self
.Materials
.Data
) ) )
598 print( "{:>16} {:}".format( "Bones", len(self
.Bones
.Data
) ) )
599 print( "{:>16} {:}".format( "Influences", len(self
.Influences
.Data
) ) )
601 #===========================================================================
605 # The raw key array holds all the keys for all the bones in all the specified sequences,
606 # organized as follows:
607 # For each AnimInfoBinary's sequence there are [Number of bones] times [Number of frames keys]
608 # in the VQuatAnimKeys, laid out as tracks of [numframes] keys for each bone in the order of
609 # the bones as defined in the array of FnamedBoneBinary in the PSA.
611 # Once the data from the PSK (now digested into native skeletal mesh) and PSA (digested into
612 # a native animation object containing one or more sequences) are associated together at runtime,
613 # bones are linked up by name. Any bone in a skeleton (from the PSK) that finds no partner in
614 # the animation sequence (from the PSA) will assume its reference pose stance ( as defined in
615 # the offsets & rotations that are in the VBones making up the reference skeleton from the PSK)
616 #===========================================================================
620 self
.GeneralHeader
= VChunkHeader("ANIMHEAD", 0)
621 self
.Bones
= FileSection("BONENAMES", SIZE_FNAMEDBONEBINARY
) #FNamedBoneBinary
622 self
.Animations
= FileSection("ANIMINFO", SIZE_ANIMINFOBINARY
) #AnimInfoBinary
623 self
.RawKeys
= FileSection("ANIMKEYS", SIZE_VQUATANIMKEY
) #VQuatAnimKey
624 # this will take the format of key=Bone Name, value = (BoneIndex, Bone Object)
628 def AddBone(self
, b
):
629 self
.Bones
.Data
.append(b
)
631 def AddAnimation(self
, a
):
632 self
.Animations
.Data
.append(a
)
634 def AddRawKey(self
, k
):
635 self
.RawKeys
.Data
.append(k
)
637 def UpdateHeaders(self
):
638 self
.Bones
.UpdateHeader()
639 self
.Animations
.UpdateHeader()
640 self
.RawKeys
.UpdateHeader()
642 def GetBoneByIndex(self
, bone_index
):
643 if bone_index
>= 0 and len(self
.Bones
.Data
) > bone_index
:
644 return self
.Bones
.Data
[bone_index
]
647 return (len(self
.Bones
.Data
) == 0 or len(self
.Animations
.Data
) == 0)
649 def StoreBone(self
, b
):
650 self
.BoneLookup
[b
.Name
] = [-1, b
]
652 def UseBone(self
, bone_name
):
653 if bone_name
in self
.BoneLookup
:
654 bone_data
= self
.BoneLookup
[bone_name
]
656 if bone_data
[0] == -1:
657 bone_data
[0] = len(self
.Bones
.Data
)
658 self
.AddBone(bone_data
[1])
659 #self.Bones.Data.append(bone_data[1])
663 def GetBoneByName(self
, bone_name
):
664 if bone_name
in self
.BoneLookup
:
665 bone_data
= self
.BoneLookup
[bone_name
]
668 def GetBoneIndex(self
, bone_name
):
669 if bone_name
in self
.BoneLookup
:
670 bone_data
= self
.BoneLookup
[bone_name
]
675 return self
.GeneralHeader
.dump() + self
.Bones
.dump() + self
.Animations
.dump() + self
.RawKeys
.dump()
678 print( "{:>16} {:}".format( "Bones", len(self
.Bones
.Data
) ) )
679 print( "{:>16} {:}".format( "Animations", len(self
.Animations
.Data
) ) )
680 print( "{:>16} {:}".format( "Raw keys", len(self
.RawKeys
.Data
) ) )
682 #===========================================================================
683 # Helpers to create bone structs
684 #===========================================================================
685 def make_vbone( name
, parent_index
, child_count
, orientation_quat
, position_vect
):
688 bone
.ParentIndex
= parent_index
689 bone
.NumChildren
= child_count
690 bone
.BonePos
.Orientation
= orientation_quat
691 bone
.BonePos
.Position
.X
= position_vect
.x
692 bone
.BonePos
.Position
.Y
= position_vect
.y
693 bone
.BonePos
.Position
.Z
= position_vect
.z
694 #these values seem to be ignored?
695 #bone.BonePos.Length = tail.length
696 #bone.BonePos.XSize = tail.x
697 #bone.BonePos.YSize = tail.y
698 #bone.BonePos.ZSize = tail.z
701 def make_namedbonebinary( name
, parent_index
, child_count
, orientation_quat
, position_vect
, is_real
):
702 bone
= FNamedBoneBinary()
704 bone
.ParentIndex
= parent_index
705 bone
.NumChildren
= child_count
706 bone
.BonePos
.Orientation
= orientation_quat
707 bone
.BonePos
.Position
.X
= position_vect
.x
708 bone
.BonePos
.Position
.Y
= position_vect
.y
709 bone
.BonePos
.Position
.Z
= position_vect
.z
710 bone
.IsRealBone
= is_real
713 def make_fquat( bquat
):
715 #flip handedness for UT = set x,y,z to negative (rotate in other direction)
722 def make_fquat_default( bquat
):
731 #===========================================================================
732 #RG - check to make sure face isnt a line
733 #===========================================================================
734 def is_1d_face( face
, mesh
):
735 #ID Vertex of id point
736 v0
= face
.vertices
[0]
737 v1
= face
.vertices
[1]
738 v2
= face
.vertices
[2]
740 return (mesh
.vertices
[v0
].co
== mesh
.vertices
[v1
].co \
741 or mesh
.vertices
[v1
].co
== mesh
.vertices
[v2
].co \
742 or mesh
.vertices
[v2
].co
== mesh
.vertices
[v0
].co
)
745 #===========================================================================
747 # (renamed to seperate it from VVertex.SmoothGroup)
748 #===========================================================================
749 class SmoothingGroup
:
755 self
.neighboring_faces
= []
756 self
.neighboring_groups
= []
758 self
.local_id
= SmoothingGroup
.static_id
759 SmoothingGroup
.static_id
+= 1
761 def __cmp__(self
, other
):
762 if isinstance(other
, SmoothingGroup
):
763 return cmp( self
.local_id
, other
.local_id
)
767 return hash(self
.local_id
)
769 # searches neighboring faces to determine which smoothing group ID can be used
770 def get_valid_smoothgroup_id(self
):
772 for group
in self
.neighboring_groups
:
773 if group
!= None and group
.id == temp_id
:
774 if temp_id
< 0x80000000:
775 temp_id
= temp_id
<< 1
777 raise Error("Smoothing Group ID Overflowed, Smoothing Group evidently has more than 31 neighboring groups")
782 def make_neighbor(self
, new_neighbor
):
783 if new_neighbor
not in self
.neighboring_groups
:
784 self
.neighboring_groups
.append( new_neighbor
)
786 def contains_face(self
, face
):
787 return (face
in self
.faces
)
789 def add_neighbor_face(self
, face
):
790 if not face
in self
.neighboring_faces
:
791 self
.neighboring_faces
.append( face
)
793 def add_face(self
, face
):
794 if not face
in self
.faces
:
795 self
.faces
.append( face
)
797 def determine_edge_sharing( mesh
):
799 edge_sharing_list
= dict()
801 for edge
in mesh
.edges
:
802 edge_sharing_list
[edge
.key
] = []
804 for face
in mesh
.tessfaces
:
805 for key
in face
.edge_keys
:
806 if not face
in edge_sharing_list
[key
]:
807 edge_sharing_list
[key
].append(face
) # mark this face as sharing this edge
809 return edge_sharing_list
811 def find_edges( mesh
, key
):
812 """ Temp replacement for mesh.findEdges().
813 This is painfully slow.
815 for edge
in mesh
.edges
:
817 if key
[0] == v
[0] and key
[1] == v
[1]:
820 def add_face_to_smoothgroup( mesh
, face
, edge_sharing_list
, smoothgroup
):
822 if face
in smoothgroup
.faces
:
825 smoothgroup
.add_face(face
)
827 for key
in face
.edge_keys
:
829 edge_id
= find_edges(mesh
, key
)
834 if not( mesh
.edges
[edge_id
].use_edge_sharp
):
836 for shared_face
in edge_sharing_list
[key
]:
837 if shared_face
!= face
:
839 add_face_to_smoothgroup( mesh
, shared_face
, edge_sharing_list
, smoothgroup
)
842 for shared_face
in edge_sharing_list
[key
]:
843 if shared_face
!= face
:
844 smoothgroup
.add_neighbor_face( shared_face
)
846 def determine_smoothgroup_for_face( mesh
, face
, edge_sharing_list
, smoothgroup_list
):
848 for group
in smoothgroup_list
:
849 if (face
in group
.faces
):
852 smoothgroup
= SmoothingGroup();
853 add_face_to_smoothgroup( mesh
, face
, edge_sharing_list
, smoothgroup
)
855 if not smoothgroup
in smoothgroup_list
:
856 smoothgroup_list
.append( smoothgroup
)
858 def build_neighbors_tree( smoothgroup_list
):
860 for group
in smoothgroup_list
:
861 for face
in group
.neighboring_faces
:
862 for neighbor_group
in smoothgroup_list
:
863 if neighbor_group
.contains_face( face
) and neighbor_group
not in group
.neighboring_groups
:
864 group
.make_neighbor( neighbor_group
)
865 neighbor_group
.make_neighbor( group
)
867 #===========================================================================
868 # parse_smooth_groups
869 #===========================================================================
870 def parse_smooth_groups( mesh
):
872 print("Parsing smooth groups...")
875 smoothgroup_list
= []
876 edge_sharing_list
= determine_edge_sharing(mesh
)
877 #print("faces:",len(mesh.tessfaces))
878 interval
= math
.floor(len(mesh
.tessfaces
) / 100)
879 if interval
== 0: #if the faces are few do this
880 interval
= math
.floor(len(mesh
.tessfaces
) / 10)
881 #print("FACES:",len(mesh.tessfaces),"//100 =" "interval:",interval)
882 for face
in mesh
.tessfaces
:
884 determine_smoothgroup_for_face(mesh
, face
, edge_sharing_list
, smoothgroup_list
)
885 # progress indicator, writes to console without scrolling
886 if face
.index
> 0 and (face
.index
% interval
) == 0:
887 print("Processing... {}%\r".format( int(face
.index
/ len(mesh
.tessfaces
) * 100) ), end
='')
889 print("Completed" , ' '*20)
891 verbose("len(smoothgroup_list)={}".format(len(smoothgroup_list
)))
893 build_neighbors_tree(smoothgroup_list
)
895 for group
in smoothgroup_list
:
896 group
.get_valid_smoothgroup_id()
898 print("Smooth group parsing completed in {:.2f}s".format(time
.clock() - t
))
899 return smoothgroup_list
901 #===========================================================================
902 # http://en.wikibooks.org/wiki/Blender_3D:_Blending_Into_Python/Cookbook#Triangulate_NMesh
903 # blender 2.50 format using the Operators/command convert the mesh to tri mesh
904 #===========================================================================
905 def triangulate_mesh( object ):
907 verbose(header("triangulateNMesh"))
909 scene
= bpy
.context
.scene
911 me_ob
= object.copy()
912 me_ob
.data
= object.to_mesh(bpy
.context
.scene
, True, 'PREVIEW') #write data object
913 bpy
.context
.scene
.objects
.link(me_ob
)
914 bpy
.context
.scene
.update()
915 bpy
.ops
.object.mode_set(mode
='OBJECT')
916 for i
in scene
.objects
:
917 i
.select
= False # deselect all objects
920 scene
.objects
.active
= me_ob
922 print("Copy and Convert mesh just incase any way...")
924 bpy
.ops
.object.mode_set(mode
='EDIT')
925 bpy
.ops
.mesh
.select_all(action
='SELECT')# select all the face/vertex/edge
926 bpy
.ops
.object.mode_set(mode
='EDIT')
927 bpy
.ops
.mesh
.quads_convert_to_tris()
928 bpy
.context
.scene
.update()
930 bpy
.ops
.object.mode_set(mode
='OBJECT')
932 bpy
.context
.scene
.udk_option_triangulate
= True
934 verbose("Triangulated mesh")
936 me_ob
.data
= me_ob
.to_mesh(bpy
.context
.scene
, True, 'PREVIEW') #write data object
937 bpy
.context
.scene
.update()
940 #copy mesh data and then merge them into one object
941 def meshmerge(selectedobjects
):
942 bpy
.ops
.object.mode_set(mode
='OBJECT') #object mode and not edit mode
943 cloneobjects
= [] #object holder for copying object data
944 if len(selectedobjects
) > 1:
945 print("selectedobjects:",len(selectedobjects
)) #print select object
946 count
= 0 #reset count
947 for count
in range(len( selectedobjects
)):
948 #print("Index:",count)
949 if selectedobjects
[count
] != None:
950 me_da
= selectedobjects
[count
].data
.copy() #copy data
951 me_ob
= selectedobjects
[count
].copy() #copy object
952 #note two copy two types else it will use the current data or mesh
953 me_ob
.data
= me_da
#assign the data
954 bpy
.context
.scene
.objects
.link(me_ob
)#link the object to the scene #current object location
955 print("Index:",count
,"clone object",me_ob
.name
) #print clone object
956 cloneobjects
.append(me_ob
) #add object to the array
957 for i
in bpy
.data
.objects
: i
.select
= False #deselect all objects
958 count
= 0 #reset count
959 #begin merging the mesh together as one
960 for count
in range(len( cloneobjects
)):
962 bpy
.context
.scene
.objects
.active
= cloneobjects
[count
]
963 print("Set Active Object:",cloneobjects
[count
].name
)
964 cloneobjects
[count
].select
= True
965 bpy
.ops
.object.join() #join object together
966 if len(cloneobjects
) > 1:
967 bpy
.types
.Scene
.udk_copy_merge
= True
968 return cloneobjects
[0]
970 #sort the mesh center top list and not center at the last array. Base on order while select to merge mesh to make them center.
971 def sortmesh(selectmesh
):
972 print("MESH SORTING...")
975 for countm
in range(len(selectmesh
)):
976 #if object are center add here
977 if selectmesh
[countm
].location
.x
== 0 and selectmesh
[countm
].location
.y
== 0 and selectmesh
[countm
].location
.z
== 0:
978 centermesh
.append(selectmesh
[countm
])
979 else:#if not add here for not center
980 notcentermesh
.append(selectmesh
[countm
])
982 #add mesh object in order for merge object
983 for countm
in range(len(centermesh
)):
984 selectmesh
.append(centermesh
[countm
])
985 for countm
in range(len(notcentermesh
)):
986 selectmesh
.append(notcentermesh
[countm
])
987 if len(selectmesh
) == 1: #if there one mesh just do some here
988 return selectmesh
[0] #return object mesh
990 return meshmerge(selectmesh
) #return merge object mesh
992 #===========================================================================
994 #===========================================================================
995 def parse_mesh( mesh
, psk
):
996 #bpy.ops.object.mode_set(mode='OBJECT')
997 #error ? on commands for select object?
998 print(header("MESH", 'RIGHT'))
999 print("Mesh object:", mesh
.name
)
1000 scene
= bpy
.context
.scene
1001 for i
in scene
.objects
: i
.select
= False # deselect all objects
1002 scene
.objects
.active
= mesh
1004 mesh
= triangulate_mesh(mesh
)
1005 if bpy
.types
.Scene
.udk_copy_merge
== True:
1006 bpy
.context
.scene
.objects
.unlink(setmesh
)
1007 #print("FACES----:",len(mesh.data.tessfaces))
1008 verbose("Working mesh object: {}".format(mesh
.name
))
1010 #collect a list of the material names
1011 print("Materials...")
1015 for slot
in mesh
.material_slots
:
1017 print(" Material {} '{}'".format(mat_slot_index
, slot
.name
))
1018 MaterialName
.append(slot
.name
)
1019 #if slot.material.texture_slots[0] != None:
1020 #if slot.material.texture_slots[0].texture.image.filepath != None:
1021 #print(" Texture path {}".format(slot.material.texture_slots[0].texture.image.filepath))
1022 #create the current material
1023 v_material
= psk
.GetMatByIndex(mat_slot_index
)
1024 v_material
.MaterialName
= slot
.name
1025 v_material
.TextureIndex
= mat_slot_index
1026 v_material
.AuxMaterial
= mat_slot_index
1028 verbose(" PSK index {}".format(v_material
.TextureIndex
))
1030 #END slot in mesh.material_slots
1032 # object_mat = mesh.materials[0]
1033 #object_material_index = mesh.active_material_index
1034 #FIXME ^ this is redundant due to "= face.material_index" in face loop
1037 points
= ObjMap() #vertex
1040 discarded_face_count
= 0
1041 sys
.setrecursionlimit(1000000)
1042 smoothgroup_list
= parse_smooth_groups(mesh
.data
)
1044 print("{} faces".format(len(mesh
.data
.tessfaces
)))
1046 print("Smooth groups active:", bpy
.context
.scene
.udk_option_smoothing_groups
)
1048 for face
in mesh
.data
.tessfaces
:
1050 smoothgroup_id
= 0x80000000
1052 for smooth_group
in smoothgroup_list
:
1053 if smooth_group
.contains_face(face
):
1054 smoothgroup_id
= smooth_group
.id
1057 #print ' -- Dumping UVs -- '
1058 #print current_face.uv_textures
1059 # modified by VendorX
1060 object_material_index
= face
.material_index
1062 if len(face
.vertices
) != 3:
1063 raise Error("Non-triangular face (%i)" % len(face
.vertices
))
1065 #RG - apparently blender sometimes has problems when you do quad to triangle
1066 # conversion, and ends up creating faces that have only TWO points -
1067 # one of the points is simply in the vertex list for the face twice.
1068 # This is bad, since we can't get a real face normal for a LINE, we need
1069 # a plane for this. So, before we add the face to the list of real faces,
1070 # ensure that the face is actually a plane, and not a line. If it is not
1071 # planar, just discard it and notify the user in the console after we're
1072 # done dumping the rest of the faces
1074 if not is_1d_face(face
, mesh
.data
):
1079 #get or create the current material
1080 psk
.GetMatByIndex(object_material_index
)
1082 face_index
= face
.index
1086 if len(mesh
.data
.uv_textures
) > 0:
1088 uv_layer
= mesh
.data
.tessface_uv_textures
.active
1089 face_uv
= uv_layer
.data
[face_index
]
1090 #size(data) is number of texture faces. Each face has UVs
1091 #print("DATA face uv: ",len(faceUV.uv), " >> ",(faceUV.uv[0][0]))
1094 vert_index
= face
.vertices
[i
]
1095 vert
= mesh
.data
.vertices
[vert_index
]
1097 #assumes 3 UVs Per face (for now)
1099 if len(face_uv
.uv
) != 3:
1100 print("WARNING: face has more or less than 3 UV coordinates - writing 0,0...")
1103 uv
= [face_uv
.uv
[i
][0],face_uv
.uv
[i
][1]] #OR bottom works better # 24 for cube
1108 #flip V coordinate because UEd requires it and DOESN'T flip it on its own like it
1109 #does with the mesh Y coordinates. this is otherwise known as MAGIC-2
1112 # clamp UV coords if udk_option_clamp_uv is True
1113 if bpy
.context
.scene
.udk_option_clamp_uv
:
1123 # RE - Append untransformed vector (for normal calc below)
1124 # TODO: convert to Blender.Mathutils
1125 vect_list
.append( FVector(vert
.co
.x
, vert
.co
.y
, vert
.co
.z
) )
1127 # Transform position for export
1128 #vpos = vert.co * object_material_index
1130 #should fixed this!!
1131 vpos
= mesh
.matrix_local
* vert
.co
1132 if bpy
.context
.scene
.udk_option_scale
< 0 or bpy
.context
.scene
.udk_option_scale
> 1:
1134 vpos
.x
= vpos
.x
* bpy
.context
.scene
.udk_option_scale
1135 vpos
.y
= vpos
.y
* bpy
.context
.scene
.udk_option_scale
1136 vpos
.z
= vpos
.z
* bpy
.context
.scene
.udk_option_scale
1137 #print("scale pos:", vpos)
1143 if bpy
.context
.scene
.udk_option_smoothing_groups
:#is this necessary?
1144 p
.SmoothGroup
= smoothgroup_id
1146 lPoint
= VPointSimple()
1147 lPoint
.Point
.X
= vpos
.x
1148 lPoint
.Point
.Y
= vpos
.y
1149 lPoint
.Point
.Z
= vpos
.z
1151 if lPoint
in points_linked
:
1152 if not(p
in points_linked
[lPoint
]):
1153 points_linked
[lPoint
].append(p
)
1155 points_linked
[lPoint
] = [p
]
1159 w
.MatIndex
= object_material_index
1160 w
.PointIndex
= points
.get(p
) # store keys
1163 if bpy
.context
.scene
.udk_option_smoothing_groups
:#is this necessary?
1164 w
.SmoothGroup
= smoothgroup_id
1165 index_wedge
= wedges
.get(w
)
1166 wedge_list
.append(index_wedge
)
1169 #print("result PointIndex={}, U={:.6f}, V={:.6f}, wedge_index={}".format(
1175 #END for i in range(3)
1177 # Determine face vertex order
1179 # TODO: convert to Blender.Mathutils
1180 # get normal from blender
1182 # convert to FVector
1183 norm
= FVector(no
[0], no
[1], no
[2])
1184 # Calculate the normal of the face in blender order
1185 tnorm
= vect_list
[1].sub(vect_list
[0]).cross(vect_list
[2].sub(vect_list
[1]))
1186 # RE - dot the normal from blender order against the blender normal
1187 # this gives the product of the two vectors' lengths along the blender normal axis
1188 # all that matters is the sign
1189 dot
= norm
.dot(tnorm
)
1192 # RE - magic: if the dot product above > 0, order the vertices 2, 1, 0
1193 # if the dot product above < 0, order the vertices 0, 1, 2
1194 # if the dot product is 0, then blender's normal is coplanar with the face
1195 # and we cannot deduce which side of the face is the outside of the mesh
1197 (tri
.WedgeIndex2
, tri
.WedgeIndex1
, tri
.WedgeIndex0
) = wedge_list
1199 (tri
.WedgeIndex0
, tri
.WedgeIndex1
, tri
.WedgeIndex2
) = wedge_list
1201 dindex0
= face
.vertices
[0];
1202 dindex1
= face
.vertices
[1];
1203 dindex2
= face
.vertices
[2];
1205 mesh
.data
.vertices
[dindex0
].select
= True
1206 mesh
.data
.vertices
[dindex1
].select
= True
1207 mesh
.data
.vertices
[dindex2
].select
= True
1209 raise Error("Normal coplanar with face! points: %s, %s, %s" % (str(mesh
.data
.vertices
[dindex0
].co
),
1210 str(mesh
.data
.vertices
[dindex1
].co
),
1211 str(mesh
.data
.vertices
[dindex2
].co
)))
1214 if face
.use_smooth
== True:
1215 tri
.SmoothingGroups
= 1
1217 tri
.SmoothingGroups
= 0
1218 tri
.MatIndex
= object_material_index
1220 if bpy
.context
.scene
.udk_option_smoothing_groups
:
1221 tri
.SmoothingGroups
= smoothgroup_id
1222 print("Bool Smooth")
1226 #END if not is_1d_face(current_face, mesh.data)
1229 discarded_face_count
+= 1
1231 #END face in mesh.data.faces
1233 print("{} points".format(len(points
.dict)))
1235 for point
in points
.items():
1238 if len(points
.dict) > 32767:
1239 raise Error("Mesh vertex limit exceeded! {} > 32767".format(len(points
.dict)))
1241 print("{} wedges".format(len(wedges
.dict)))
1243 for wedge
in wedges
.items():
1246 # alert the user to degenerate face issues
1247 if discarded_face_count
> 0:
1248 print("WARNING: Mesh contained degenerate faces (non-planar)")
1249 print(" Discarded {} faces".format(discarded_face_count
))
1251 #RG - walk through the vertex groups and find the indexes into the PSK points array
1252 #for them, then store that index and the weight as a tuple in a new list of
1253 #verts for the group that we can look up later by bone name, since Blender matches
1254 #verts to bones for influences by having the VertexGroup named the same thing as
1257 #[print(x, len(points_linked[x])) for x in points_linked]
1258 #print("pointsindex length ",len(points_linked))
1261 # all vertex groups of the mesh (obj)...
1262 for obj_vertex_group
in mesh
.vertex_groups
:
1264 #print(" bone group build:",obj_vertex_group.name)#print bone name
1265 #print(dir(obj_vertex_group))
1266 verbose("obj_vertex_group.name={}".format(obj_vertex_group
.name
))
1270 # all vertices in the mesh...
1271 for vertex
in mesh
.data
.vertices
:
1273 # all groups this vertex is a member of...
1274 for vgroup
in vertex
.groups
:
1275 if vgroup
.group
== obj_vertex_group
.index
:
1276 vertex_weight
= vgroup
.weight
1278 vpos
= mesh
.matrix_local
* vertex
.co
1279 if bpy
.context
.scene
.udk_option_scale
< 0 or bpy
.context
.scene
.udk_option_scale
> 1:
1280 vpos
.x
= vpos
.x
* bpy
.context
.scene
.udk_option_scale
1281 vpos
.y
= vpos
.y
* bpy
.context
.scene
.udk_option_scale
1282 vpos
.z
= vpos
.z
* bpy
.context
.scene
.udk_option_scale
1287 #print(len(points_linked[p]))
1288 try: #check if point doesn't give error
1289 for point
in points_linked
[p
]:
1290 point_index
= points
.get(point
) #point index
1291 v_item
= (point_index
, vertex_weight
)
1292 vertex_list
.append(v_item
)
1293 except Exception:#if get error ignore them #not safe I think
1294 print("Error link points!")
1297 #bone name, [point id and wieght]
1298 #print("Add Vertex Group:",obj_vertex_group.name, " No. Points:",len(vertex_list))
1299 psk
.VertexGroups
[obj_vertex_group
.name
] = vertex_list
1301 # remove the temporary triangulated mesh
1302 if bpy
.context
.scene
.udk_option_triangulate
== True:
1303 verbose("Removing temporary triangle mesh: {}".format(mesh
.name
))
1304 bpy
.ops
.object.mode_set(mode
='OBJECT') # OBJECT mode
1305 mesh
.parent
= None # unparent to avoid phantom links
1306 bpy
.context
.scene
.objects
.unlink(mesh
) # unlink
1308 #===========================================================================
1309 # Collate bones that belong to the UDK skeletal mesh
1310 #===========================================================================
1311 def parse_armature( armature
, psk
, psa
):
1313 print(header("ARMATURE", 'RIGHT'))
1314 verbose("Armature object: {} Armature data: {}".format(armature
.name
, armature
.data
.name
))
1316 # generate a list of root bone candidates
1317 root_candidates
= [b
for b
in armature
.data
.bones
if b
.parent
== None and b
.use_deform
== True]
1319 # should be a single, unambiguous result
1320 if len(root_candidates
) == 0:
1321 raise Error("Cannot find root for UDK bones. The root bone must use deform.")
1323 if len(root_candidates
) > 1:
1324 raise Error("Ambiguous root for UDK. More than one root bone is using deform.")
1326 # prep for bone collection
1327 udk_root_bone
= root_candidates
[0]
1329 BoneUtil
.static_bone_id
= 0 # replaces global
1331 # traverse bone chain
1332 print("{: <3} {: <48} {: <20}".format("ID", "Bone", "Status"))
1334 recurse_bone(udk_root_bone
, udk_bones
, psk
, psa
, 0, armature
.matrix_local
)
1337 if len(udk_bones
) < 3:
1338 raise Error("Less than three bones may crash UDK (legacy issue?)")
1340 # return a list of bones making up the entire udk skel
1341 # this is passed to parse_animation instead of working from keyed bones in the action
1344 #===========================================================================
1347 # psk the PSK file object
1348 # psa the PSA file object
1351 # indent text indent for recursive log
1352 #===========================================================================
1353 def recurse_bone( bone
, bones
, psk
, psa
, parent_id
, parent_matrix
, indent
="" ):
1359 if not bone
.use_deform
:
1360 status
= "No effect"
1362 # calc parented bone transform
1363 if bone
.parent
!= None:
1364 quat
= make_fquat(bone
.matrix
.to_quaternion())
1365 quat_parent
= bone
.parent
.matrix
.to_quaternion().inverted()
1366 parent_head
= quat_parent
* bone
.parent
.head
1367 parent_tail
= quat_parent
* bone
.parent
.tail
1368 translation
= (parent_tail
- parent_head
) + bone
.head
1370 # calc root bone transform
1372 translation
= parent_matrix
* bone
.head
# ARMATURE OBJECT Location
1373 rot_matrix
= bone
.matrix
* parent_matrix
.to_3x3() # ARMATURE OBJECT Rotation
1374 quat
= make_fquat_default(rot_matrix
.to_quaternion())
1375 #udk_option_scale bones here?
1376 if bpy
.context
.scene
.udk_option_scale
< 0 or bpy
.context
.scene
.udk_option_scale
> 1:
1377 translation
.x
= translation
.x
* bpy
.context
.scene
.udk_option_scale
1378 translation
.y
= translation
.y
* bpy
.context
.scene
.udk_option_scale
1379 translation
.z
= translation
.z
* bpy
.context
.scene
.udk_option_scale
1380 bone_id
= BoneUtil
.static_bone_id
# ALT VERS
1381 BoneUtil
.static_bone_id
+= 1 # ALT VERS
1383 child_count
= len(bone
.children
)
1385 psk
.AddBone( make_vbone(bone
.name
, parent_id
, child_count
, quat
, translation
) )
1386 psa
.StoreBone( make_namedbonebinary(bone
.name
, parent_id
, child_count
, quat
, translation
, 1) )
1388 #RG - dump influences for this bone - use the data we collected in the mesh dump phase to map our bones to vertex groups
1389 if bone
.name
in psk
.VertexGroups
:
1390 vertex_list
= psk
.VertexGroups
[bone
.name
]
1391 #print("vertex list:", len(vertex_list), " of >" ,bone.name )
1392 for vertex_data
in vertex_list
:
1393 point_index
= vertex_data
[0]
1394 vertex_weight
= vertex_data
[1]
1395 influence
= VRawBoneInfluence()
1396 influence
.Weight
= vertex_weight
1397 influence
.BoneIndex
= bone_id
1398 influence
.PointIndex
= point_index
1399 #print (" AddInfluence to vertex {}, weight={},".format(point_index, vertex_weight))
1400 psk
.AddInfluence(influence
)
1402 status
= "No vertex group"
1403 #FIXME overwriting previous status error?
1405 print("{:<3} {:<48} {:<20}".format(bone_id
, indent
+bone
.name
, status
))
1408 #recursively dump child bones
1410 for child_bone
in bone
.children
:
1411 recurse_bone(child_bone
, bones
, psk
, psa
, bone_id
, parent_matrix
, " "+indent
)
1413 # FIXME rename? remove?
1415 static_bone_id
= 0 # static property to replace global
1417 #===========================================================================
1418 # armature the armature
1419 # udk_bones list of bones to be exported
1420 # actions_to_export list of actions to process for export
1421 # psa the PSA file object
1422 #===========================================================================
1423 def parse_animation( armature
, udk_bones
, actions_to_export
, psa
):
1425 print(header("ANIMATION", 'RIGHT'))
1427 context
= bpy
.context
1428 anim_rate
= context
.scene
.render
.fps
1430 verbose("Armature object: {}".format(armature
.name
))
1431 print("Scene: {} FPS: {} Frames: {} to {}".format(context
.scene
.name
, anim_rate
, context
.scene
.frame_start
, context
.scene
.frame_end
))
1432 print("Processing {} action(s)".format(len(actions_to_export
)))
1434 if armature
.animation_data
== None: #if animation data was not create for the armature it will skip the exporting action set(s)
1435 print("None Actions Set! skipping...")
1437 restoreAction
= armature
.animation_data
.action
# Q: is animation_data always valid?
1439 restoreFrame
= context
.scene
.frame_current
# we already do this in export_proxy, but we'll do it here too for now
1440 raw_frame_index
= 0 # used to set FirstRawFrame, seperating actions in the raw keyframe array
1443 for action
in actions_to_export
:
1445 # removed: check for armature with no animation; all it did was force you to add one
1447 if not len(action
.fcurves
):
1448 print("{} has no keys, skipping".format(action
.name
))
1451 # apply action to armature and update scene
1452 # note if loop all actions that is not armature it will override and will break armature animation.
1453 armature
.animation_data
.action
= action
1454 context
.scene
.update()
1456 # min/max frames define range
1457 framemin
, framemax
= action
.frame_range
1458 start_frame
= int(framemin
)
1459 end_frame
= int(framemax
)
1460 scene_range
= range(start_frame
, end_frame
+ 1)
1461 frame_count
= len(scene_range
)
1463 # create the AnimInfoBinary
1464 anim
= AnimInfoBinary()
1465 anim
.Name
= action
.name
1466 anim
.Group
= "" # unused?
1467 anim
.NumRawFrames
= frame_count
1468 anim
.AnimRate
= anim_rate
1469 anim
.FirstRawFrame
= raw_frame_index
1471 print("{}, frames {} to {} ({} frames)".format(action
.name
, start_frame
, end_frame
, frame_count
))
1473 # removed: bone lookup table
1475 # build a list of pose bones relevant to the collated udk_bones
1476 # fixme: could be done once, prior to loop?
1479 for pb
in armature
.pose
.bones
:
1480 if b
.name
== pb
.name
:
1481 udk_pose_bones
.append(pb
)
1484 # sort in the order the bones appear in the PSA file
1486 ordered_bones
= sorted([(psa
.UseBone(b
.name
), b
) for b
in udk_pose_bones
], key
=operator
.itemgetter(0))
1488 # NOTE: posebone.bone references the obj/edit bone
1489 # REMOVED: unique_bone_indexes is redundant?
1492 for i
in range(frame_count
):
1494 frame
= scene_range
[i
]
1496 #verbose("FRAME {}".format(i), i) # test loop sampling
1498 # advance to frame (automatically updates the pose)
1499 context
.scene
.frame_set(frame
)
1501 # compute the key for each bone
1502 for bone_data
in ordered_bones
:
1504 bone_index
= bone_data
[0]
1505 pose_bone
= bone_data
[1]
1506 pose_bone_matrix
= mathutils
.Matrix(pose_bone
.matrix
)
1508 if pose_bone
.parent
!= None:
1509 pose_bone_parent_matrix
= mathutils
.Matrix(pose_bone
.parent
.matrix
)
1510 pose_bone_matrix
= pose_bone_parent_matrix
.inverted() * pose_bone_matrix
1512 head
= pose_bone_matrix
.to_translation()
1513 quat
= pose_bone_matrix
.to_quaternion().normalized()
1515 if pose_bone
.parent
!= None:
1516 quat
= make_fquat(quat
)
1518 quat
= make_fquat_default(quat
)
1520 #scale animation position here?
1521 if bpy
.context
.scene
.udk_option_scale
< 0 or bpy
.context
.scene
.udk_option_scale
> 1:
1522 head
.x
= head
.x
* bpy
.context
.scene
.udk_option_scale
1523 head
.y
= head
.y
* bpy
.context
.scene
.udk_option_scale
1524 head
.z
= head
.z
* bpy
.context
.scene
.udk_option_scale
1526 vkey
= VQuatAnimKey()
1527 vkey
.Position
.X
= head
.x
1528 vkey
.Position
.Y
= head
.y
1529 vkey
.Position
.Z
= head
.z
1530 vkey
.Orientation
= quat
1532 # frame delta = 1.0 / fps
1533 vkey
.Time
= 1.0 / anim_rate
# according to C++ header this is "disregarded"
1537 # END for bone_data in ordered_bones
1539 raw_frame_index
+= 1
1541 # END for i in range(frame_count)
1543 anim
.TotalBones
= len(ordered_bones
) # REMOVED len(unique_bone_indexes)
1544 anim
.TrackTime
= float(frame_count
) # frame_count/anim.AnimRate makes more sense, but this is what actually works in UDK
1546 verbose("anim.TotalBones={}, anim.TrackTime={}".format(anim
.TotalBones
, anim
.TrackTime
))
1548 psa
.AddAnimation(anim
)
1550 # END for action in actions
1553 armature
.animation_data
.action
= restoreAction
1554 context
.scene
.frame_set(restoreFrame
)
1556 #===========================================================================
1557 # Collate actions to be exported
1558 # Modify this to filter for one, some or all actions. For now use all.
1559 # RETURNS list of actions
1560 #===========================================================================
1561 def collate_actions():
1562 verbose(header("collate_actions"))
1563 actions_to_export
= []
1565 for action
in bpy
.data
.actions
:
1566 if bpy
.context
.scene
.udk_option_selectanimations
: # check if needed to select actions set for exporting it
1567 print("Action Set is selected!")
1569 for actionlist
in bpy
.context
.scene
.udkas_list
: #list the action set from the list
1570 if actionlist
.name
== action
.name
and actionlist
.bmatch
== True and actionlist
.bexport
== True:
1572 print("Added Action Set:",action
.name
)
1574 if bready
== False:#don't export it
1575 print("Skipping Action Set:",action
.name
)
1577 verbose(" + {}".format(action
.name
)) #action set name
1578 actions_to_export
.append(action
) #add to the action array
1580 return actions_to_export
1582 #===========================================================================
1583 # Locate the target armature and mesh for export
1584 # RETURNS armature, mesh
1585 #===========================================================================
1586 def find_armature_and_mesh():
1587 verbose(header("find_armature_and_mesh", 'LEFT', '<', 60))
1589 context
= bpy
.context
1590 active_object
= context
.active_object
1595 # this could be more intuitive
1596 #bpy.ops.object.mode_set(mode='OBJECT')
1598 if bpy
.context
.scene
.udk_option_selectobjects
: #if checked select object true do list object on export
1599 print("select mode:")
1600 if len(bpy
.context
.scene
.udkArm_list
) > 0:
1601 print("Armature Name:",bpy
.context
.scene
.udkArm_list
[bpy
.context
.scene
.udkArm_list_idx
].name
)
1602 for obj
in bpy
.context
.scene
.objects
:
1603 if obj
.name
== bpy
.context
.scene
.udkArm_list
[bpy
.context
.scene
.udkArm_list_idx
].name
:
1607 raise Error("There is no Armature in the list!")
1609 #parented_meshes = [obj for obj in armature.children if obj.type == 'MESH']
1610 meshes
= [obj
for obj
in bpy
.context
.scene
.objects
if obj
.type == 'MESH']
1613 if obj
.type == 'MESH':
1615 #print("PARENT MESH:",obj.name)
1616 for udkmeshlist
in bpy
.context
.scene
.udkmesh_list
:
1617 if obj
.name
== udkmeshlist
.name
and udkmeshlist
.bexport
== True:
1620 if bexportmesh
== True:
1621 print("Mesh Name:",obj
.name
," < SELECT TO EXPORT!")
1622 meshselected
.append(obj
)
1623 print("MESH COUNT:",len(meshselected
))
1624 # try the active object
1625 if active_object
and active_object
.type == 'MESH' and len(meshselected
) == 0:
1626 if active_object
.parent
== armature
:
1627 mesh
= active_object
1629 raise Error("The selected mesh is not parented to the armature")
1631 # otherwise, expect a single mesh parented to the armature (other object types are ignored)
1633 print("Number of meshes:",len(meshes
))
1634 print("Number of meshes (selected):",len(meshes
))
1635 if len(meshes
) == 1:
1638 elif len(meshes
) > 1:
1639 if len(meshselected
) >= 1:
1640 mesh
= sortmesh(meshselected
)
1642 raise Error("More than one mesh(s) parented to armature. Select object(s)!")
1644 raise Error("No mesh parented to armature")
1645 else: #if not check for select function from the list work the code here
1646 print("normal mode:")
1647 # try the active object
1648 if active_object
and active_object
.type == 'ARMATURE':
1649 armature
= active_object
1650 bpy
.ops
.object.mode_set(mode
='OBJECT')
1651 # otherwise, try for a single armature in the scene
1653 #bpy.ops.object.mode_set(mode='OBJECT')
1654 all_armatures
= [obj
for obj
in bpy
.context
.scene
.objects
if obj
.type == 'ARMATURE']
1656 if len(all_armatures
) == 1:#if armature has one scene just assign it
1657 armature
= all_armatures
[0]
1658 elif len(all_armatures
) > 1:#if there more armature then find the select armature
1660 for _armobj
in all_armatures
:
1665 if barmselect
== False:
1666 raise Error("Please select an armatures in the scene")
1668 raise Error("No armatures in scene")
1670 verbose("Found armature: {}".format(armature
.name
))
1673 parented_meshes
= [obj
for obj
in armature
.children
if obj
.type == 'MESH']
1675 if len(armature
.children
) == 0:
1676 raise Error("The selected Armature has no mesh parented to the Armature Object!")
1678 for obj
in armature
.children
:
1680 if obj
.type == 'MESH' and obj
.select
== True:
1681 meshselected
.append(obj
)
1682 # try the active object
1683 if active_object
and active_object
.type == 'MESH' and len(meshselected
) == 0:
1684 if active_object
.parent
== armature
:
1685 mesh
= active_object
1687 raise Error("The selected mesh is not parented to the armature")
1689 # otherwise, expect a single mesh parented to the armature (other object types are ignored)
1691 print("Number of meshes:",len(parented_meshes
))
1692 print("Number of meshes (selected):",len(meshselected
))
1693 if len(parented_meshes
) == 1:
1694 mesh
= parented_meshes
[0]
1696 elif len(parented_meshes
) > 1:
1697 if len(meshselected
) >= 1:
1698 mesh
= sortmesh(meshselected
)
1700 raise Error("More than one mesh(s) parented to armature. Select object(s)!")
1702 raise Error("No mesh parented to armature")
1704 verbose("Found mesh: {}".format(mesh
.name
))
1705 if mesh
== None or armature
== None:
1706 raise Error("Check Mesh and Armature are list!")
1707 #if len(armature.pose.bones) == len(mesh.vertex_groups):
1708 #print("Armature and Mesh Vertex Groups matches Ok!")
1710 #raise Error("Armature bones:" + str(len(armature.pose.bones)) + " Mesh Vertex Groups:" + str(len(mesh.vertex_groups)) +" doesn't match!")
1712 #this will check if object need to be rebuild.
1713 if bpy
.context
.scene
.udk_option_rebuildobjects
:
1714 #print("INIT... REBUILDING...")
1715 print("REBUILDING ARMATURE...")
1717 armature
= rebuildarmature(armature
) #rebuild the armature to raw . If there IK constraint it will ignore it.
1718 print("REBUILDING MESH...")
1719 mesh
= rebuildmesh(mesh
) #rebuild the mesh to raw data format.
1721 return armature
, mesh
1723 #===========================================================================
1724 # Returns a list of vertex groups in the mesh. Can be modified to filter
1725 # groups as necessary.
1727 #===========================================================================
1728 def collate_vertex_groups( mesh
):
1729 verbose("collate_vertex_groups")
1732 for group
in mesh
.vertex_groups
:
1734 groups
.append(group
)
1735 verbose(" " + group
.name
)
1739 #===========================================================================
1741 #===========================================================================
1742 def export(filepath
):
1743 print(header("Export", 'RIGHT'))
1744 bpy
.types
.Scene
.udk_copy_merge
= False #in case fail to export set this to default
1746 context
= bpy
.context
1748 print("Blender Version {}.{}.{}".format(bpy
.app
.version
[0], bpy
.app
.version
[1], bpy
.app
.version
[2]))
1749 print("Filepath: {}".format(filepath
))
1751 verbose("PSK={}, PSA={}".format(context
.scene
.udk_option_export_psk
, context
.scene
.udk_option_export_psa
))
1753 # find armature and mesh
1754 # [change this to implement alternative methods; raise Error() if not found]
1755 udk_armature
, udk_mesh
= find_armature_and_mesh()
1757 # check misc conditions
1758 if not (udk_armature
.scale
.x
== udk_armature
.scale
.y
== udk_armature
.scale
.z
== 1):
1759 raise Error("bad armature scale: armature object should have uniform scale of 1 (ALT-S)")
1761 if not (udk_mesh
.scale
.x
== udk_mesh
.scale
.y
== udk_mesh
.scale
.z
== 1):
1762 raise Error("bad mesh scale: mesh object should have uniform scale of 1 (ALT-S)")
1764 if not (udk_armature
.location
.x
== udk_armature
.location
.y
== udk_armature
.location
.z
== 0):
1765 raise Error("bad armature location: armature should be located at origin (ALT-G)")
1767 if not (udk_mesh
.location
.x
== udk_mesh
.location
.y
== udk_mesh
.location
.z
== 0):
1768 raise Error("bad mesh location: mesh should be located at origin (ALT-G)")
1775 parse_mesh(udk_mesh
, psk
)
1778 udk_bones
= parse_armature(udk_armature
, psk
, psa
)
1781 if context
.scene
.udk_option_export_psa
== True:
1782 actions
= collate_actions()
1783 parse_animation(udk_armature
, udk_bones
, actions
, psa
)
1786 print(header("Exporting", 'CENTER'))
1788 psk_filename
= filepath
+ '.psk'
1789 psa_filename
= filepath
+ '.psa'
1791 if context
.scene
.udk_option_export_psk
== True:
1792 print("Skeletal mesh data...")
1794 file = open(psk_filename
, "wb")
1795 file.write(psk
.dump())
1797 print("Exported: " + psk_filename
)
1800 if context
.scene
.udk_option_export_psa
== True:
1801 print("Animation data...")
1802 if not psa
.IsEmpty():
1804 file = open(psa_filename
, "wb")
1805 file.write(psa
.dump())
1807 print("Exported: " + psa_filename
)
1809 print("No Animation (.psa file) to export")
1813 #if objects are rebuild do the unlink
1814 if bpy
.context
.scene
.udk_option_rebuildobjects
:
1815 print("Unlinking Objects")
1816 print("Armature Object Name:",udk_armature
.name
) #display object name
1817 bpy
.context
.scene
.objects
.unlink(udk_armature
) #remove armature from the scene
1818 print("Mesh Object Name:",udk_mesh
.name
) #display object name
1819 bpy
.context
.scene
.objects
.unlink(udk_mesh
) #remove mesh from the scene
1821 print("Export completed in {:.2f} seconds".format((time
.clock() - t
)))
1823 #===========================================================================
1825 #===========================================================================
1826 class Operator_UDKExport( bpy
.types
.Operator
):
1828 bl_idname
= "object.udk_export"
1829 bl_label
= "Export now"
1831 def execute(self
, context
):
1834 scene
= bpy
.context
.scene
1836 scene
.udk_option_export_psk
= (scene
.udk_option_export
== '0' or scene
.udk_option_export
== '2')
1837 scene
.udk_option_export_psa
= (scene
.udk_option_export
== '1' or scene
.udk_option_export
== '2')
1839 filepath
= get_dst_path()
1842 restore_frame
= scene
.frame_current
1844 message
= "Finish Export!"
1848 except Error
as err
:
1850 message
= err
.message
1853 scene
.frame_set(restore_frame
)
1855 self
.report({'ERROR'}, message
)
1858 scene
.frame_set(restore_frame
)
1862 #===========================================================================
1864 #===========================================================================
1865 class Operator_ToggleConsole( bpy
.types
.Operator
):
1866 """Show or hide the console"""
1867 bl_idname
= "object.toggle_console"
1868 bl_label
= "Toggle console"
1870 #def invoke(self, context, event):
1871 # bpy.ops.wm.console_toggle()
1872 # return{'FINISHED'}
1873 def execute(self
, context
):
1874 bpy
.ops
.wm
.console_toggle()
1877 #===========================================================================
1878 # Get filepath for export
1879 #===========================================================================
1881 if bpy
.context
.scene
.udk_option_filename_src
== '0':
1882 if bpy
.context
.active_object
:
1883 path
= os
.path
.split(bpy
.data
.filepath
)[0] + "\\" + bpy
.context
.active_object
.name
# + ".psk"
1885 #path = os.path.split(bpy.data.filepath)[0] + "\\" + "Unknown";
1886 path
= os
.path
.splitext(bpy
.data
.filepath
)[0]# + ".psk"
1888 path
= os
.path
.splitext(bpy
.data
.filepath
)[0]# + ".psk"
1892 bpy
.types
.Scene
.udk_option_filename_src
= EnumProperty(
1894 description
= "Sets the name for the files",
1895 items
= [ ('0', "From object", "Name will be taken from object name"),
1896 ('1', "From Blend", "Name will be taken from .blend file name") ],
1899 bpy
.types
.Scene
.udk_option_export_psk
= BoolProperty(
1900 name
= "bool export psa",
1901 description
= "Boolean for exporting psk format (Skeleton Mesh)",
1904 bpy
.types
.Scene
.udk_option_export_psa
= BoolProperty(
1905 name
= "bool export psa",
1906 description
= "Boolean for exporting psa format (Animation Data)",
1909 bpy
.types
.Scene
.udk_option_clamp_uv
= BoolProperty(
1911 description
= "True is to limit Clamp UV co-ordinates to [0-1]. False is unrestricted (x,y). ",
1914 bpy
.types
.Scene
.udk_copy_merge
= BoolProperty(
1915 name
= "Merge Mesh",
1916 description
= "This will copy the mesh(s) and merge the object together and unlink the mesh to be remove while exporting the object",
1919 bpy
.types
.Scene
.udk_option_export
= EnumProperty(
1921 description
= "What to export",
1922 items
= [ ('0', "Mesh only", "Exports the PSK file for the Skeletal Mesh"),
1923 ('1', "Animation only", "Export the PSA file for Action Set(s)(Animations Data)"),
1924 ('2', "Mesh & Animation", "Export both PSK and PSA files(Skeletal Mesh/Animation(s) Data)") ],
1927 bpy
.types
.Scene
.udk_option_verbose
= BoolProperty(
1929 description
= "Verbose console output",
1932 bpy
.types
.Scene
.udk_option_smoothing_groups
= BoolProperty(
1933 name
= "Smooth Groups",
1934 description
= "Activate hard edges as smooth groups",
1937 bpy
.types
.Scene
.udk_option_triangulate
= BoolProperty(
1938 name
= "Triangulate Mesh",
1939 description
= "Convert Quads to Triangles",
1942 bpy
.types
.Scene
.udk_option_selectanimations
= BoolProperty(
1943 name
= "Select Animation(s)",
1944 description
= "Select animation(s) for export to psa file",
1947 bpy
.types
.Scene
.udk_option_selectobjects
= BoolProperty(
1948 name
= "Select Object(s)",
1949 description
= "Select Armature and Mesh(s). Just make sure mesh(s) is parent to armature",
1952 bpy
.types
.Scene
.udk_option_rebuildobjects
= BoolProperty(
1953 name
= "Rebuild Objects",
1954 description
= "In case of deform skeleton mesh and animations data - This will rebuild objects from raw format on export when checked",
1957 bpy
.types
.Scene
.udk_option_ignoreactiongroupnames
= BoolProperty(
1958 name
= "Ignore Action Group Names",
1959 description
= "This will Ignore Action Set Group Names Check With Armature Bones. It will override armature to set action set",
1962 bpy
.types
.Scene
.udk_option_scale
= FloatProperty(
1964 description
= "In case you don't want to scale objects manually - This will just scale position when on export for the skeleton mesh and animation data",
1967 #===========================================================================
1969 #===========================================================================
1970 class OBJECT_OT_UTSelectedFaceSmooth(bpy
.types
.Operator
):
1971 """It will only select smooth faces that is select mesh"""
1972 bl_idname
= "object.utselectfacesmooth" # XXX, name???
1973 bl_label
= "Select Smooth Faces"#"Select Smooth faces"
1975 def invoke(self
, context
, event
):
1976 print("----------------------------------------")
1977 print("Init Select Face(s):")
1979 for obj
in bpy
.data
.objects
:
1980 if obj
.type == 'MESH' and obj
.select
== True:
1983 bpy
.ops
.object.mode_set(mode
='OBJECT')#it need to go into object mode to able to select the faces
1984 for i
in bpy
.context
.scene
.objects
: i
.select
= False #deselect all objects
1985 obj
.select
= True #set current object select
1986 bpy
.context
.scene
.objects
.active
= obj
#set active object
1988 mesh
.from_mesh(obj
.data
)
1989 for face
in mesh
.faces
:
1991 for face
in mesh
.faces
:
1992 if face
.smooth
== True:
1998 mesh
.to_mesh(obj
.data
)
1999 bpy
.context
.scene
.update()
2000 bpy
.ops
.object.mode_set(mode
='EDIT')
2001 print("Select Smooth Count(s):",smoothcount
," Flat Count(s):",flatcount
)
2005 print("Selected Face(s) Exectue!")
2006 self
.report({'INFO'}, "Selected Face(s) Exectue!")
2008 print("Didn't select Mesh Object!")
2009 self
.report({'INFO'}, "Didn't Select Mesh Object!")
2010 print("----------------------------------------")
2013 class OBJECT_OT_MeshClearWeights(bpy
.types
.Operator
):
2014 """Remove all mesh vertex groups weights for the bones"""
2015 bl_idname
= "object.meshclearweights" # XXX, name???
2016 bl_label
= "Remove Vertex Weights"#"Remove Mesh vertex weights"
2018 def invoke(self
, context
, event
):
2019 for obj
in bpy
.data
.objects
:
2020 if obj
.type == 'MESH' and obj
.select
== True:
2021 for vg
in obj
.vertex_groups
:
2022 obj
.vertex_groups
.remove(vg
)
2023 self
.report({'INFO'}, "Mesh Vertex Groups Remove!")
2027 def unpack_list(list_of_tuples
):
2029 for t
in list_of_tuples
:
2033 def rebuildmesh(obj
):
2034 #make sure it in object mode
2035 print("Mesh Object Name:",obj
.name
)
2036 bpy
.ops
.object.mode_set(mode
='OBJECT')
2037 for i
in bpy
.context
.scene
.objects
: i
.select
= False #deselect all objects
2039 bpy
.context
.scene
.objects
.active
= obj
2041 me_ob
= bpy
.data
.meshes
.new(("Re_"+obj
.name
))
2047 #print("creating array build mesh...")
2048 mmesh
= obj
.to_mesh(bpy
.context
.scene
,True,'PREVIEW')
2049 uv_layer
= mmesh
.tessface_uv_textures
.active
2050 for face
in mmesh
.tessfaces
:
2051 smoothings
.append(face
.use_smooth
)#smooth or flat in boolean
2052 if uv_layer
!= None:#check if there texture data exist
2053 faceUV
= uv_layer
.data
[face
.index
]
2055 for uv
in faceUV
.uv
:
2056 uvs
.append((uv
[0],uv
[1]))
2058 #print((face.vertices[:]))
2059 if len(face
.vertices
) == 3:
2060 faces
.extend([(face
.vertices
[0],face
.vertices
[1],face
.vertices
[2],0)])
2062 faces
.extend([(face
.vertices
[0],face
.vertices
[1],face
.vertices
[2],face
.vertices
[3])])
2064 for vertex
in mesh
.vertices
:
2065 verts
.append(vertex
.co
.to_tuple())
2066 #vertices weight groups into array
2067 vertGroups
= {} #array in strings
2068 for vgroup
in obj
.vertex_groups
:
2070 for v
in mesh
.vertices
:
2072 if vg
.group
== vgroup
.index
:
2073 vlist
.append((v
.index
,vg
.weight
))
2074 #print((v.index,vg.weight))
2075 vertGroups
[vgroup
.name
] = vlist
2077 #print("creating mesh object...")
2078 #me_ob.from_pydata(verts, [], faces)
2079 me_ob
.vertices
.add(len(verts
))
2080 me_ob
.tessfaces
.add(len(faces
))
2081 me_ob
.vertices
.foreach_set("co", unpack_list(verts
))
2082 me_ob
.tessfaces
.foreach_set("vertices_raw",unpack_list( faces
))
2083 me_ob
.tessfaces
.foreach_set("use_smooth", smoothings
)#smooth array from face
2085 #check if there is uv faces
2086 if len(uvfaces
) > 0:
2087 uvtex
= me_ob
.tessface_uv_textures
.new(name
="retex")
2088 for i
, face
in enumerate(me_ob
.tessfaces
):
2089 blender_tface
= uvtex
.data
[i
] #face
2090 mfaceuv
= uvfaces
[i
]
2091 if len(mfaceuv
) == 3:
2092 blender_tface
.uv1
= mfaceuv
[0];
2093 blender_tface
.uv2
= mfaceuv
[1];
2094 blender_tface
.uv3
= mfaceuv
[2];
2095 if len(mfaceuv
) == 4:
2096 blender_tface
.uv1
= mfaceuv
[0];
2097 blender_tface
.uv2
= mfaceuv
[1];
2098 blender_tface
.uv3
= mfaceuv
[2];
2099 blender_tface
.uv4
= mfaceuv
[3];
2101 me_ob
.update()#need to update the information to able to see into the secne
2102 obmesh
= bpy
.data
.objects
.new(("Re_"+obj
.name
),me_ob
)
2103 bpy
.context
.scene
.update()
2104 #Build tmp materials
2105 materialname
= "ReMaterial"
2106 for matcount
in mesh
.materials
:
2107 matdata
= bpy
.data
.materials
.new(materialname
)
2108 me_ob
.materials
.append(matdata
)
2109 #assign face to material id
2110 for face
in mesh
.tessfaces
:
2111 me_ob
.faces
[face
.index
].material_index
= face
.material_index
2112 #vertices weight groups
2113 for vgroup
in vertGroups
:
2114 group
= obmesh
.vertex_groups
.new(vgroup
)
2115 for v
in vertGroups
[vgroup
]:
2116 group
.add([v
[0]], v
[1], 'ADD')# group.add(array[vertex id],weight,add)
2117 bpy
.context
.scene
.objects
.link(obmesh
)
2118 #print("Mesh Material Count:",len(me_ob.materials))
2120 #print("MATERIAL ID OREDER:")
2121 for mat
in me_ob
.materials
:
2122 #print("-Material:",mat.name,"INDEX:",matcount)
2124 print("Mesh Object Name:",obmesh
.name
)
2125 bpy
.context
.scene
.update()
2128 class OBJECT_OT_UTRebuildMesh(bpy
.types
.Operator
):
2129 """It rebuild the mesh from scrape from the selected mesh object. """ \
2130 """Note the scale will be 1:1 for object mode. To keep from deforming"""
2131 bl_idname
= "object.utrebuildmesh" # XXX, name???
2132 bl_label
= "Rebuild Mesh"#"Rebuild Mesh"
2134 def invoke(self
, context
, event
):
2135 print("----------------------------------------")
2136 print("Init Mesh Bebuild...")
2138 bpy
.ops
.object.mode_set(mode
='OBJECT')
2139 for obj
in bpy
.data
.objects
:
2140 if obj
.type == 'MESH' and obj
.select
== True:
2142 self
.report({'INFO'}, "Rebuild Mesh Finish!")
2143 print("Finish Mesh Build...")
2144 print("----------------------------------------")
2147 def rebuildarmature(obj
):
2148 currentbone
= [] #select armature for roll copy
2149 print("Armature Name:",obj
.name
)
2150 objectname
= "ArmatureDataPSK"
2151 meshname
="ArmatureObjectPSK"
2152 armdata
= bpy
.data
.armatures
.new(objectname
)
2153 ob_new
= bpy
.data
.objects
.new(meshname
, armdata
)
2154 bpy
.context
.scene
.objects
.link(ob_new
)
2155 #bpy.ops.object.mode_set(mode='OBJECT')
2156 for i
in bpy
.context
.scene
.objects
: i
.select
= False #deselect all objects
2157 ob_new
.select
= True
2158 bpy
.context
.scene
.objects
.active
= obj
2160 bpy
.ops
.object.mode_set(mode
='EDIT')
2161 for bone
in obj
.data
.edit_bones
:
2162 if bone
.parent
!= None:
2163 currentbone
.append([bone
.name
,bone
.roll
])
2165 currentbone
.append([bone
.name
,bone
.roll
])
2166 bpy
.ops
.object.mode_set(mode
='OBJECT')
2167 for i
in bpy
.context
.scene
.objects
: i
.select
= False #deselect all objects
2168 bpy
.context
.scene
.objects
.active
= ob_new
2169 bpy
.ops
.object.mode_set(mode
='EDIT')
2171 for bone
in obj
.data
.bones
:
2172 bpy
.ops
.object.mode_set(mode
='EDIT')
2173 newbone
= ob_new
.data
.edit_bones
.new(bone
.name
)
2174 newbone
.head
= bone
.head_local
2175 newbone
.tail
= bone
.tail_local
2176 for bonelist
in currentbone
:
2177 if bone
.name
== bonelist
[0]:
2178 newbone
.roll
= bonelist
[1]
2180 if bone
.parent
!= None:
2181 parentbone
= ob_new
.data
.edit_bones
[bone
.parent
.name
]
2182 newbone
.parent
= parentbone
2184 ob_new
.animation_data_create()#create animation data
2185 if obj
.animation_data
!= None:#check for animation
2186 ob_new
.animation_data
.action
= obj
.animation_data
.action
#just make sure it here to do the animations if exist
2187 print("Armature Object Name:",ob_new
.name
)
2190 class OBJECT_OT_UTRebuildArmature(bpy
.types
.Operator
):
2191 """If mesh is deform when importing to unreal engine try this. """ \
2192 """It rebuild the bones one at the time by select one armature object scrape to raw setup build. """ \
2193 """Note the scale will be 1:1 for object mode. To keep from deforming"""
2194 bl_idname
= "object.utrebuildarmature" # XXX, name???
2195 bl_label
= "Rebuild Armature" #Rebuild Armature
2197 def invoke(self
, context
, event
):
2198 print("----------------------------------------")
2199 print("Init Rebuild Armature...")
2201 for obj
in bpy
.data
.objects
:
2202 if obj
.type == 'ARMATURE' and obj
.select
== True:
2203 rebuildarmature(obj
)
2204 self
.report({'INFO'}, "Rebuild Armature Finish!")
2205 print("End of Rebuild Armature.")
2206 print("----------------------------------------")
2209 class UDKActionSetListPG(bpy
.types
.PropertyGroup
):
2210 bool = BoolProperty(default
=False)
2211 string
= StringProperty()
2212 actionname
= StringProperty()
2213 bmatch
= BoolProperty(default
=False,name
="Match", options
={"HIDDEN"},description
= "This check against bone names and action group names matches and override boolean if true")
2214 bexport
= BoolProperty(default
=False,name
="Export",description
= "Check this to export the animation")
2216 bpy
.utils
.register_class(UDKActionSetListPG
)
2217 bpy
.types
.Scene
.udkas_list
= CollectionProperty(type=UDKActionSetListPG
)
2218 bpy
.types
.Scene
.udkas_list_idx
= IntProperty()
2220 class UL_UDKActionSetList(bpy
.types
.UIList
):
2221 def draw_item(self
, context
, layout
, data
, item
, icon
, active_data
, active_propname
, index
):
2222 layout
.label(item
.name
)
2223 layout
.prop(item
, "bmatch", text
="Match")
2224 layout
.prop(item
, "bexport", text
="Export")
2226 class UDKObjListPG(bpy
.types
.PropertyGroup
):
2227 bool = BoolProperty(default
=False)
2228 string
= StringProperty()
2229 bexport
= BoolProperty(default
=False,name
="Export", options
={"HIDDEN"},description
= "This will be ignore when exported")
2230 bselect
= BoolProperty(default
=False,name
="Select", options
={"HIDDEN"},description
= "This will be ignore when exported")
2231 otype
= StringProperty(name
="Type",description
= "This will be ignore when exported")
2233 bpy
.utils
.register_class(UDKObjListPG
)
2234 bpy
.types
.Scene
.udkobj_list
= CollectionProperty(type=UDKObjListPG
)
2235 bpy
.types
.Scene
.udkobj_list_idx
= IntProperty()
2237 class UL_UDKObjList(bpy
.types
.UIList
):
2238 def draw_item(self
, context
, layout
, data
, item
, icon
, active_data
, active_propname
, index
):
2239 layout
.label(item
.name
)
2240 layout
.prop(item
, "otype", text
="")
2241 layout
.prop(item
, "bselect", text
="")
2243 class UDKMeshListPG(bpy
.types
.PropertyGroup
):
2244 bool = BoolProperty(default
=False)
2245 string
= StringProperty()
2246 bexport
= BoolProperty(default
=False,name
="Export", options
={"HIDDEN"},description
= "This object will be export when true")
2247 bselect
= BoolProperty(default
=False,name
="Select", options
={"HIDDEN"},description
= "Make sure you have Mesh is parent to Armature")
2248 otype
= StringProperty(name
="Type",description
= "This will be ignore when exported")
2250 bpy
.utils
.register_class(UDKMeshListPG
)
2251 bpy
.types
.Scene
.udkmesh_list
= CollectionProperty(type=UDKMeshListPG
)
2252 bpy
.types
.Scene
.udkmesh_list_idx
= IntProperty()
2254 class UL_UDKMeshList(bpy
.types
.UIList
):
2255 def draw_item(self
, context
, layout
, data
, item
, icon
, active_data
, active_propname
, index
):
2256 layout
.label(item
.name
)
2257 #layout.prop(item, "bselect", text="Select")
2258 layout
.prop(item
, "bexport", text
="Export")
2260 class UDKArmListPG(bpy
.types
.PropertyGroup
):
2261 bool = BoolProperty(default
=False)
2262 string
= StringProperty()
2263 bexport
= BoolProperty(default
=False,name
="Export", options
={"HIDDEN"},description
= "This will be ignore when exported")
2264 bselect
= BoolProperty(default
=False,name
="Select", options
={"HIDDEN"},description
= "This will be ignore when exported")
2265 otype
= StringProperty(name
="Type",description
= "This will be ignore when exported")
2267 bpy
.utils
.register_class(UDKArmListPG
)
2268 bpy
.types
.Scene
.udkArm_list
= CollectionProperty(type=UDKArmListPG
)
2269 bpy
.types
.Scene
.udkArm_list_idx
= IntProperty()
2271 class UL_UDKArmList(bpy
.types
.UIList
):
2272 def draw_item(self
, context
, layout
, data
, item
, icon
, active_data
, active_propname
, index
):
2273 layout
.label(item
.name
)
2275 class Panel_UDKExport( bpy
.types
.Panel
):
2277 bl_label
= "UDK Export"
2278 bl_idname
= "OBJECT_PT_udk_tools"
2279 #bl_space_type = "PROPERTIES"
2280 #bl_region_type = "WINDOW"
2281 #bl_context = "object"
2282 bl_space_type
= "VIEW_3D"
2283 bl_region_type
= "TOOLS"
2285 #def draw_header(self, context):
2286 # layout = self.layout
2287 #obj = context.object
2288 #layout.prop(obj, "select", text="")
2291 #def poll(cls, context):
2292 # return context.active_object
2294 def draw(self
, context
):
2295 layout
= self
.layout
2296 path
= get_dst_path()
2300 # object_name = context.object.name
2301 if context
.active_object
:
2302 object_name
= context
.active_object
.name
2303 row10
= layout
.row()
2304 row10
.prop(context
.scene
, "udk_option_smoothing_groups")
2305 row10
.prop(context
.scene
, "udk_option_clamp_uv")
2306 row10
.prop(context
.scene
, "udk_option_verbose")
2308 row
.label(text
="Active object: " + object_name
)
2310 layout
.prop(context
.scene
, "udk_option_filename_src")
2312 row
.label(text
=path
)
2314 layout
.prop(context
.scene
, "udk_option_export")
2315 layout
.prop(context
.scene
, "udk_option_selectobjects")
2317 if context
.scene
.udk_option_selectobjects
:
2318 layout
.operator("object.selobjectpdate")
2319 layout
.label(text
="ARMATURE - Index")
2320 layout
.template_list("UL_UDKArmList", "udk_armatures", context
.scene
, "udkArm_list",
2321 context
.scene
, "udkArm_list_idx", rows
=3)
2322 layout
.label(text
="MESH - Export")
2323 layout
.template_list("UL_UDKMeshList", "", context
.scene
, "udkmesh_list",
2324 context
.scene
, "udkmesh_list_idx", rows
=5)
2325 layout
.prop(context
.scene
, "udk_option_selectanimations")
2326 if context
.scene
.udk_option_selectanimations
:
2327 layout
.operator("action.setanimupdate")
2328 layout
.label(text
="Action Set(s) - Match / Export")
2329 layout
.template_list("UL_UDKActionSetList", "", context
.scene
, "udkas_list",
2330 context
.scene
, "udkas_list_idx", rows
=5)
2331 test
= layout
.separator()
2332 layout
.prop(context
.scene
, "udk_option_scale")
2333 layout
.prop(context
.scene
, "udk_option_rebuildobjects")
2334 #layout.prop(context.scene, "udk_option_ignoreactiongroupnames")
2335 row11
= layout
.row()
2336 row11
.operator("object.udk_export")
2337 row11
.operator("object.toggle_console")
2338 layout
.operator(OBJECT_OT_UTRebuildArmature
.bl_idname
)
2339 layout
.label(text
="Mesh")
2340 layout
.operator(OBJECT_OT_MeshClearWeights
.bl_idname
)
2341 layout
.operator(OBJECT_OT_UTSelectedFaceSmooth
.bl_idname
)
2342 layout
.operator(OBJECT_OT_UTRebuildMesh
.bl_idname
)
2343 layout
.operator(OBJECT_OT_UDKCheckMeshLines
.bl_idname
)
2345 def udkupdateobjects():
2346 my_objlist
= bpy
.context
.scene
.udkArm_list
2348 for objarm
in bpy
.context
.scene
.objects
:#list and filter only mesh and armature
2349 if objarm
.type == 'ARMATURE':
2350 objectl
.append(objarm
)
2351 for _objd
in objectl
:#check if list has in udk list
2353 for _obj
in my_objlist
:
2354 if _obj
.name
== _objd
.name
and _obj
.otype
== _objd
.type:
2355 _obj
.bselect
= _objd
.select
2358 if bfound_obj
== False:
2359 #print("ADD ARMATURE...")
2360 my_item
= my_objlist
.add()
2361 my_item
.name
= _objd
.name
2362 my_item
.bselect
= _objd
.select
2363 my_item
.otype
= _objd
.type
2365 for _udkobj
in my_objlist
:
2367 for _objd
in bpy
.context
.scene
.objects
: #check if there no existing object from sense to remove it
2368 if _udkobj
.name
== _objd
.name
and _udkobj
.otype
== _objd
.type:
2371 if bfound_objv
== False:
2372 removeobject
.append(_udkobj
)
2373 #print("remove check...")
2374 for _item
in removeobject
: #loop remove object from udk list object
2376 for _obj
in my_objlist
:
2377 if _obj
.name
== _item
.name
and _obj
.otype
== _item
.otype
:
2378 my_objlist
.remove(count
)
2382 my_objlist
= bpy
.context
.scene
.udkmesh_list
2384 for objarm
in bpy
.context
.scene
.objects
:#list and filter only mesh and armature
2385 if objarm
.type == 'MESH':
2386 objectl
.append(objarm
)
2387 for _objd
in objectl
:#check if list has in udk list
2389 for _obj
in my_objlist
:
2390 if _obj
.name
== _objd
.name
and _obj
.otype
== _objd
.type:
2391 _obj
.bselect
= _objd
.select
2394 if bfound_obj
== False:
2395 my_item
= my_objlist
.add()
2396 my_item
.name
= _objd
.name
2397 my_item
.bselect
= _objd
.select
2398 my_item
.otype
= _objd
.type
2400 for _udkobj
in my_objlist
:
2402 for _objd
in bpy
.context
.scene
.objects
: #check if there no existing object from sense to remove it
2403 if _udkobj
.name
== _objd
.name
and _udkobj
.otype
== _objd
.type:
2406 if bfound_objv
== False:
2407 removeobject
.append(_udkobj
)
2408 #print("remove check...")
2409 for _item
in removeobject
: #loop remove object from udk list object
2411 for _obj
in my_objlist
:
2412 if _obj
.name
== _item
.name
and _obj
.otype
== _item
.otype
:
2413 my_objlist
.remove(count
)
2417 class OBJECT_OT_UDKObjUpdate(bpy
.types
.Operator
):
2418 """This will update the filter of the mesh and armature"""
2419 bl_idname
= "object.selobjectpdate"
2420 bl_label
= "Update Object(s)"
2422 actionname
= bpy
.props
.StringProperty()
2424 def execute(self
, context
):
2428 def udkcheckmeshline():
2430 for obj
in bpy
.context
.scene
.objects
:
2431 if obj
.type == 'MESH' and obj
.select
== True:
2433 objmesh
= triangulate_mesh(objmesh
) #create a copy of the mesh
2434 bpy
.ops
.object.mode_set(mode
='OBJECT')
2435 for i
in bpy
.context
.scene
.objects
: i
.select
= False # deselect all objects
2436 objmesh
.select
= True
2437 bpy
.context
.scene
.objects
.active
= objmesh
#set active mesh
2440 bpy
.ops
.object.mode_set(mode
='EDIT') #set in edit mode
2441 bpy
.ops
.mesh
.select_all(action
='DESELECT')
2442 bpy
.context
.tool_settings
.mesh_select_mode
= (True, False, False) #select vertices
2447 print(objmesh
.data
.tessfaces
)
2449 for face
in objmesh
.data
.tessfaces
:
2453 vert_index
= face
.vertices
[i
]
2454 vert
= objmesh
.data
.vertices
[vert_index
]
2455 vect_list
.append( FVector(vert
.co
.x
, vert
.co
.y
, vert
.co
.z
) )
2456 vpos
= objmesh
.matrix_local
* vert
.co
2462 w
.PointIndex
= points
.get(p
) # store keys
2463 index_wedge
= wedges
.get(w
)
2464 wedge_list
.append(index_wedge
)
2466 norm
= FVector(no
[0], no
[1], no
[2])
2467 tnorm
= vect_list
[1].sub(vect_list
[0]).cross(vect_list
[2].sub(vect_list
[1]))
2468 dot
= norm
.dot(tnorm
)
2472 (tri
.WedgeIndex2
, tri
.WedgeIndex1
, tri
.WedgeIndex0
) = wedge_list
2474 (tri
.WedgeIndex0
, tri
.WedgeIndex1
, tri
.WedgeIndex2
) = wedge_list
2476 dindex0
= face
.vertices
[0];
2477 dindex1
= face
.vertices
[1];
2478 dindex2
= face
.vertices
[2];
2479 vertex_list
.append(dindex0
)
2480 vertex_list
.append(dindex1
)
2481 vertex_list
.append(dindex2
)
2483 bpy
.ops
.object.mode_set(mode
='OBJECT')
2484 for vertex
in objmesh
.data
.vertices
: #loop all vertex in the mesh list
2485 for vl
in vertex_list
: #loop for error vertex
2486 if vertex
.index
== vl
: #if match set to select
2487 vertex
.select
= True
2489 bpy
.ops
.object.mode_set(mode
='EDIT') #set in edit mode to see the select vertex
2490 objmesh
.data
.update() # update object
2491 bpy
.context
.scene
.update() #update scene
2492 message
= "MESH PASS"
2493 if len(vertex_list
) > 0:
2494 message
= "MESH FAIL"
2497 class OBJECT_OT_UDKCheckMeshLines(bpy
.types
.Operator
):
2498 """Select the mesh for export test. This will create dummy mesh to see which area are broken. """ \
2499 """If the vertices share the same position it will cause a bug"""
2500 bl_idname
= "object.udkcheckmeshline"
2501 bl_label
= "Check Mesh Vertices"
2503 def execute(self
, context
):
2504 message
= udkcheckmeshline()
2505 self
.report({'ERROR'}, message
)
2508 class OBJECT_OT_ActionSetAnimUpdate(bpy
.types
.Operator
):
2509 """Select Armture to match the action set groups. """ \
2510 """All bones keys must be set to match with number of bones"""
2511 bl_idname
= "action.setanimupdate"
2512 bl_label
= "Update Action Set(s)"
2514 actionname
= bpy
.props
.StringProperty()
2516 def execute(self
, context
):
2517 my_sett
= bpy
.context
.scene
.udkas_list
2522 armatureselected
= []
2523 for objarm
in bpy
.context
.scene
.objects
:
2524 if objarm
.type == 'ARMATURE':
2525 #print("ADDED ARMATURE...")
2526 armatures
.append(objarm
)
2527 if objarm
.select
== True:
2528 armatureselected
.append(objarm
)
2530 if len(armatureselected
) == len(armatures
) == 1:
2531 armature
= armatures
[0]
2532 if len(armatures
) == 1:
2533 armature
= armatures
[0]
2534 if len(armatureselected
) == 1:
2535 armature
= armatureselected
[0]
2537 if armature
!= None:
2538 for bone
in armature
.pose
.bones
:
2539 bones
.append(bone
.name
)
2541 for action
in bpy
.data
.actions
:#action list
2544 for actionbone
in action
.groups
:
2545 #print("Pose bone name: ",actionbone.name)
2547 if b
== actionbone
.name
:
2549 #print(b," : ",actionbone.name)
2551 for actionlist
in my_sett
:
2552 if action
.name
== actionlist
.name
:
2554 if len(bones
) == len(action
.groups
) == count
:
2555 actionlist
.bmatch
= True
2557 actionlist
.bmatch
= False
2561 my_item
= my_sett
.add()
2562 #print(dir(my_item.bmatch))
2563 my_item
.name
= action
.name
2564 #my_item.template_list_controls = "bmatch:bexport"
2565 if len(bones
) == len(action
.groups
) == count
:
2566 my_item
.bmatch
= True
2568 my_item
.bmatch
= False
2570 #check action list and data actions
2571 for actionlist
in bpy
.context
.scene
.udkas_list
:
2574 for act
in bpy
.data
.actions
:
2575 if actionlist
.name
== act
.name
:
2579 #print("ACT NAME:",actionlist.name," COUNT",notfound)
2580 if notfound
== len(bpy
.data
.actions
):
2581 #print("remove :",actionlist.name)
2582 removeactions
.append(actionlist
.name
)
2583 #print("Not in the action data list:",len(removeactions))
2584 #remove list or chnages in the name the template list
2585 for actname
in removeactions
:
2587 for actionlist
in my_sett
:
2588 #print("action name:",actionlist.name)
2589 if actionlist
.name
== actname
:
2590 my_sett
.remove(actioncount
);
2595 class ExportUDKAnimData(bpy
.types
.Operator
):
2596 """Export Skeleton Mesh / Animation Data file(s). """ \
2597 """One mesh and one armature else select one mesh or armature to be exported"""
2598 bl_idname
= "export_anim.udk" # this is important since its how bpy.ops.export.udk_anim_data is constructed
2599 bl_label
= "Export PSK/PSA"
2601 # List of operator properties, the attributes will be assigned
2602 # to the class instance from the operator settings before calling.
2604 filepath
= StringProperty(
2605 subtype
='FILE_PATH',
2607 filter_glob
= StringProperty(
2608 default
="*.psk;*.psa",
2611 udk_option_smoothing_groups
= bpy
.types
.Scene
.udk_option_smoothing_groups
2612 udk_option_clamp_uv
= bpy
.types
.Scene
.udk_option_clamp_uv
2613 udk_option_verbose
= bpy
.types
.Scene
.udk_option_verbose
2614 udk_option_filename_src
= bpy
.types
.Scene
.udk_option_filename_src
2615 udk_option_export
= bpy
.types
.Scene
.udk_option_export
2616 udk_option_scale
= bpy
.types
.Scene
.udk_option_scale
2617 udk_option_rebuildobjects
= bpy
.types
.Scene
.udk_option_rebuildobjects
2620 def poll(cls
, context
):
2621 return context
.active_object
!= None
2623 def execute(self
, context
):
2624 scene
= bpy
.context
.scene
2625 scene
.udk_option_export_psk
= (scene
.udk_option_export
== '0' or scene
.udk_option_export
== '2')
2626 scene
.udk_option_export_psa
= (scene
.udk_option_export
== '1' or scene
.udk_option_export
== '2')
2627 bpy
.context
.scene
.udk_option_scale
= self
.udk_option_scale
2628 bpy
.context
.scene
.udk_option_rebuildobjects
= self
.udk_option_rebuildobjects
2630 filepath
= get_dst_path()
2633 restore_frame
= scene
.frame_current
2635 message
= "Finish Export!"
2639 except Error
as err
:
2641 message
= err
.message
2644 scene
.frame_set(restore_frame
)
2646 self
.report({'WARNING', 'INFO'}, message
)
2649 def invoke(self
, context
, event
):
2650 wm
= context
.window_manager
2651 wm
.fileselect_add(self
)
2652 return {'RUNNING_MODAL'}
2654 def menu_func(self
, context
):
2655 default_path
= os
.path
.splitext(bpy
.data
.filepath
)[0] + ".psk"
2656 self
.layout
.operator(ExportUDKAnimData
.bl_idname
, text
="Skeleton Mesh / Animation Data (.psk/.psa)").filepath
= default_path
2658 #===========================================================================
2660 #===========================================================================
2663 bpy
.utils
.register_module(__name__
)
2664 bpy
.types
.INFO_MT_file_export
.append(menu_func
)
2667 #print("UNREGISTER")
2668 bpy
.utils
.unregister_module(__name__
)
2669 bpy
.types
.INFO_MT_file_export
.remove(menu_func
)
2671 if __name__
== "__main__":
2673 print(header("UDK Export PSK/PSA 2.6", 'CENTER'))
2677 #filename = "D:/Projects/BlenderScripts/io_export_udk_psa_psk_alpha.py"
2678 #exec(compile(open(filename).read(), filename, 'exec'))