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 "tracker_url": "https://projects.blender.org/tracker/index.php?"\
30 "func=detail&aid=21366",
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.
82 #===========================================================================
84 NOTES for Jan 2012 refactor (Spoof)
86 * THIS IS A WORK IN PROGRESS. These modifications were originally
87 intended for internal use and are incomplete. Use at your own risk! *
91 - (Blender 2.62) changes to Matrix math
92 - (Blender 2.62) check for long names
93 - option to manually set the root bone for export
97 - new bone parsing to allow advanced rigging
98 - identification of armature and mesh
99 - removed the need to apply an action to the armature
100 - fixed anim rate to work correctly in UDK (no more FPS fudging)
101 - progress reporting while processing smooth groups
102 - more informative logging
103 - code refactor for clarity and modularity
104 - naming conventions unified to use lowercase_with_underscore
105 - C++ datatypes and PSK/PSA classes remain CamelCaseStyle for clarity
106 - names such as 'ut' and 'unreal' unified to 'udk'
107 - simplification of code structure
108 - removed legacy code paths
112 This version of the exporter is more selective over which bones are considered
113 part of the UDK skeletal mesh, and allows greater flexibility for adding
114 control bones to aid in animation.
116 Taking advantage of this script requires the following methodology:
118 * Place all exportable bones into a bone hierarchy extending from a single
119 root. This root bone must have use_deform enabled. All other root bones
120 in the armature must disable use_deform. *
122 The script searches for a root bone with use_deform set true and considers all
123 bones parented to it as part of the UDK skeletal mesh. Thus only these bones
124 are exported and all other bones are ignored.
126 This removes many restrictions on the rigger/animator, who can add control
127 bone hierarchies to the rig, and keyframe any element into actions. With this
128 approach you can build complex animation rigs in a similar vein to the Rigify
129 add-on, by Nathan Vegdahl. However...
131 * Rigify is incompatible with this script *
133 Rigify interlaces deformer bones within a single hierarchy making it difficult
134 to deconstruct for export. It also splits some meta-rig bones into multiple
135 deformer bones (bad for optimising a game character). I had partial success
136 writing a parser for the structure, but it was taking too much time and,
137 considering the other issues with Rigify, it was abandoned.
139 #===========================================================================
150 from bpy
.props
import *
151 from struct
import pack
153 # REFERENCE MATERIAL JUST IN CASE:
155 # U = x / sqrt(x^2 + y^2 + z^2)
156 # V = y / sqrt(x^2 + y^2 + z^2)
158 # Triangles specifed counter clockwise for front face
160 # defines for sizeofs
164 SIZE_ANIMINFOBINARY
= 168
165 SIZE_VCHUNKHEADER
= 32
168 SIZE_FNAMEDBONEBINARY
= 120
169 SIZE_VRAWBONEINFLUENCE
= 12
170 SIZE_VQUATANIMKEY
= 32
177 #===========================================================================
178 # Custom exception class
179 #===========================================================================
180 class Error( Exception ):
182 def __init__(self
, message
):
183 self
.message
= message
185 #===========================================================================
186 # Verbose logging with loop truncation
187 #===========================================================================
188 def verbose( msg
, iteration
=-1, max_iterations
=4, msg_truncated
="..." ):
190 if bpy
.context
.scene
.udk_option_verbose
== True:
191 # limit the number of times a loop can output messages
192 if iteration
> max_iterations
:
194 elif iteration
== max_iterations
:
200 #===========================================================================
201 # Log header/separator
202 #===========================================================================
203 def header( msg
, justify
='LEFT', spacer
='_', cols
=78 ):
205 if justify
== 'LEFT':
206 s
= '{:{spacer}<{cols}}'.format(msg
+" ", spacer
=spacer
, cols
=cols
)
208 elif justify
== 'RIGHT':
209 s
= '{:{spacer}>{cols}}'.format(" "+msg
, spacer
=spacer
, cols
=cols
)
212 s
= '{:{spacer}^{cols}}'.format(" "+msg
+" ", spacer
=spacer
, cols
=cols
)
214 return "\n" + s
+ "\n"
216 #===========================================================================
217 # Generic Object->Integer mapping
218 # the object must be usable as a dictionary key
219 #===========================================================================
228 return self
.dict[obj
]
231 self
.next
= self
.next
+ 1
236 getval
= operator
.itemgetter(0)
237 getkey
= operator
.itemgetter(1)
238 return map(getval
, sorted(self
.dict.items(), key
=getkey
))
240 #===========================================================================
241 # RG - UNREAL DATA STRUCTS - CONVERTED FROM C STRUCTS GIVEN ON UDN SITE
242 # provided here: http://udn.epicgames.com/Two/BinaryFormatSpecifications.html
243 # updated UDK (Unreal Engine 3): http://udn.epicgames.com/Three/BinaryFormatSpecifications.html
244 #===========================================================================
254 return pack('ffff', self
.X
, self
.Y
, self
.Z
, self
.W
)
256 def __cmp__(self
, other
):
257 return cmp(self
.X
, other
.X
) \
258 or cmp(self
.Y
, other
.Y
) \
259 or cmp(self
.Z
, other
.Z
) \
260 or cmp(self
.W
, other
.W
)
263 return hash(self
.X
) ^
hash(self
.Y
) ^
hash(self
.Z
) ^
hash(self
.W
)
266 return "[%f,%f,%f,%f](FQuat)" % (self
.X
, self
.Y
, self
.Z
, self
.W
)
268 class FVector(object):
270 def __init__(self
, X
=0.0, Y
=0.0, Z
=0.0):
276 return pack('fff', self
.X
, self
.Y
, self
.Z
)
278 def __cmp__(self
, other
):
279 return cmp(self
.X
, other
.X
) \
280 or cmp(self
.Y
, other
.Y
) \
281 or cmp(self
.Z
, other
.Z
)
284 return (type(self
).__name
__, self
.X
, self
.Y
, self
.Z
)
287 return hash(self
._key
())
289 def __eq__(self
, other
):
290 if not hasattr(other
, '_key'):
292 return self
._key
() == other
._key
()
294 def dot(self
, other
):
295 return self
.X
* other
.X
+ self
.Y
* other
.Y
+ self
.Z
* other
.Z
297 def cross(self
, other
):
298 return FVector(self
.Y
* other
.Z
- self
.Z
* other
.Y
,
299 self
.Z
* other
.X
- self
.X
* other
.Z
,
300 self
.X
* other
.Y
- self
.Y
* other
.X
)
302 def sub(self
, other
):
303 return FVector(self
.X
- other
.X
,
310 self
.Orientation
= FQuat()
311 self
.Position
= FVector()
318 return self
.Orientation
.dump() + self
.Position
.dump() + pack('4f', self
.Length
, self
.XSize
, self
.YSize
, self
.ZSize
)
320 class AnimInfoBinary
:
323 self
.Name
= "" # length=64
324 self
.Group
= "" # length=64
327 self
.KeyCompressionStyle
= 0
329 self
.KeyPrediction
= 0.0
333 self
.FirstRawFrame
= 0
334 self
.NumRawFrames
= 0
337 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
)
341 def __init__(self
, name
, type_size
):
342 self
.ChunkID
= str.encode(name
) # length=20
343 self
.TypeFlag
= 1999801 # special value
344 self
.DataSize
= type_size
348 return pack('20siii', self
.ChunkID
, self
.TypeFlag
, self
.DataSize
, self
.DataCount
)
353 self
.MaterialName
= "" # length=64
354 self
.TextureIndex
= 0
355 self
.PolyFlags
= 0 # DWORD
357 self
.AuxFlags
= 0 # DWORD
362 #print("DATA MATERIAL:",self.MaterialName)
363 return pack('64siLiLii', str.encode(self
.MaterialName
), self
.TextureIndex
, self
.PolyFlags
, self
.AuxMaterial
, self
.AuxFlags
, self
.LodBias
, self
.LodStyle
)
368 self
.Name
= "" # length = 64
369 self
.Flags
= 0 # DWORD
372 self
.BonePos
= VJointPos()
375 return pack('64sLii', str.encode(self
.Name
), self
.Flags
, self
.NumChildren
, self
.ParentIndex
) + self
.BonePos
.dump()
377 #same as above - whatever - this is how Epic does it...
378 class FNamedBoneBinary
:
381 self
.Name
= "" # length = 64
382 self
.Flags
= 0 # DWORD
385 self
.BonePos
= VJointPos()
386 self
.IsRealBone
= 0 # this is set to 1 when the bone is actually a bone in the mesh and not a dummy
389 return pack('64sLii', str.encode(self
.Name
), self
.Flags
, self
.NumChildren
, self
.ParentIndex
) + self
.BonePos
.dump()
391 class VRawBoneInfluence
:
399 return pack('fii', self
.Weight
, self
.PointIndex
, self
.BoneIndex
)
404 self
.Position
= FVector()
405 self
.Orientation
= FQuat()
409 return self
.Position
.dump() + self
.Orientation
.dump() + pack('f', self
.Time
)
411 class VVertex(object):
414 self
.PointIndex
= 0 # WORD
417 self
.MatIndex
= 0 # BYTE
418 self
.Reserved
= 0 # BYTE
422 return pack('HHffBBH', self
.PointIndex
, 0, self
.U
, self
.V
, self
.MatIndex
, self
.Reserved
, 0)
424 def __cmp__(self
, other
):
425 return cmp(self
.PointIndex
, other
.PointIndex
) \
426 or cmp(self
.U
, other
.U
) \
427 or cmp(self
.V
, other
.V
) \
428 or cmp(self
.MatIndex
, other
.MatIndex
) \
429 or cmp(self
.Reserved
, other
.Reserved
) \
430 or cmp(self
.SmoothGroup
, other
.SmoothGroup
)
433 return (type(self
).__name
__, self
.PointIndex
, self
.U
, self
.V
, self
.MatIndex
, self
.Reserved
)
436 return hash(self
._key
())
438 def __eq__(self
, other
):
439 if not hasattr(other
, '_key'):
441 return self
._key
() == other
._key
()
446 self
.Point
= FVector()
448 def __cmp__(self
, other
):
449 return cmp(self
.Point
, other
.Point
)
452 return hash(self
._key
())
455 return (type(self
).__name
__, self
.Point
)
457 def __eq__(self
, other
):
458 if not hasattr(other
, '_key'):
460 return self
._key
() == other
._key
()
462 class VPoint(object):
465 self
.Point
= FVector()
469 return self
.Point
.dump()
471 def __cmp__(self
, other
):
472 return cmp(self
.Point
, other
.Point
) \
473 or cmp(self
.SmoothGroup
, other
.SmoothGroup
)
476 return (type(self
).__name
__, self
.Point
, self
.SmoothGroup
)
479 return hash(self
._key
()) \
480 ^
hash(self
.SmoothGroup
)
482 def __eq__(self
, other
):
483 if not hasattr(other
, '_key'):
485 return self
._key
() == other
._key
()
490 self
.WedgeIndex0
= 0 # WORD
491 self
.WedgeIndex1
= 0 # WORD
492 self
.WedgeIndex2
= 0 # WORD
493 self
.MatIndex
= 0 # BYTE
494 self
.AuxMatIndex
= 0 # BYTE
495 self
.SmoothingGroups
= 0 # DWORD
498 return pack('HHHBBL', self
.WedgeIndex0
, self
.WedgeIndex1
, self
.WedgeIndex2
, self
.MatIndex
, self
.AuxMatIndex
, self
.SmoothingGroups
)
499 #print("smooth",self.SmoothingGroups)
500 #return pack('HHHBBI', self.WedgeIndex0, self.WedgeIndex1, self.WedgeIndex2, self.MatIndex, self.AuxMatIndex, self.SmoothingGroups)
502 # END UNREAL DATA STRUCTS
503 #===========================================================================
505 #===========================================================================
506 # RG - helper class to handle the normal way the UT files are stored
507 # as sections consisting of a header and then a list of data structures
508 #===========================================================================
511 def __init__(self
, name
, type_size
):
512 self
.Header
= VChunkHeader(name
, type_size
)
513 self
.Data
= [] # list of datatypes
516 data
= self
.Header
.dump()
517 for i
in range(len(self
.Data
)):
518 data
= data
+ self
.Data
[i
].dump()
521 def UpdateHeader(self
):
522 self
.Header
.DataCount
= len(self
.Data
)
524 #===========================================================================
526 #===========================================================================
530 self
.GeneralHeader
= VChunkHeader("ACTRHEAD", 0)
531 self
.Points
= FileSection("PNTS0000", SIZE_VPOINT
) # VPoint
532 self
.Wedges
= FileSection("VTXW0000", SIZE_VVERTEX
) # VVertex
533 self
.Faces
= FileSection("FACE0000", SIZE_VTRIANGLE
) # VTriangle
534 self
.Materials
= FileSection("MATT0000", SIZE_VMATERIAL
) # VMaterial
535 self
.Bones
= FileSection("REFSKELT", SIZE_VBONE
) # VBone
536 self
.Influences
= FileSection("RAWWEIGHTS", SIZE_VRAWBONEINFLUENCE
) # VRawBoneInfluence
538 #RG - this mapping is not dumped, but is used internally to store the new point indices
539 # for vertex groups calculated during the mesh dump, so they can be used again
540 # to dump bone influences during the armature dump
542 # the key in this dictionary is the VertexGroup/Bone Name, and the value
543 # is a list of tuples containing the new point index and the weight, in that order
546 # { groupname : [ (index, weight), ... ], ... }
549 # { 'MyVertexGroup' : [ (0, 1.0), (5, 1.0), (3, 0.5) ] , 'OtherGroup' : [(2, 1.0)] }
551 self
.VertexGroups
= {}
553 def AddPoint(self
, p
):
554 self
.Points
.Data
.append(p
)
556 def AddWedge(self
, w
):
557 self
.Wedges
.Data
.append(w
)
559 def AddFace(self
, f
):
560 self
.Faces
.Data
.append(f
)
562 def AddMaterial(self
, m
):
563 self
.Materials
.Data
.append(m
)
565 def AddBone(self
, b
):
566 self
.Bones
.Data
.append(b
)
568 def AddInfluence(self
, i
):
569 self
.Influences
.Data
.append(i
)
571 def UpdateHeaders(self
):
572 self
.Points
.UpdateHeader()
573 self
.Wedges
.UpdateHeader()
574 self
.Faces
.UpdateHeader()
575 self
.Materials
.UpdateHeader()
576 self
.Bones
.UpdateHeader()
577 self
.Influences
.UpdateHeader()
581 data
= self
.GeneralHeader
.dump() + self
.Points
.dump() + self
.Wedges
.dump() + self
.Faces
.dump() + self
.Materials
.dump() + self
.Bones
.dump() + self
.Influences
.dump()
584 def GetMatByIndex(self
, mat_index
):
585 if mat_index
>= 0 and len(self
.Materials
.Data
) > mat_index
:
586 return self
.Materials
.Data
[mat_index
]
589 # modified by VendorX
590 m
.MaterialName
= MaterialName
[mat_index
]
595 print( "{:>16} {:}".format( "Points", len(self
.Points
.Data
) ) )
596 print( "{:>16} {:}".format( "Wedges", len(self
.Wedges
.Data
) ) )
597 print( "{:>16} {:}".format( "Faces", len(self
.Faces
.Data
) ) )
598 print( "{:>16} {:}".format( "Materials", len(self
.Materials
.Data
) ) )
599 print( "{:>16} {:}".format( "Bones", len(self
.Bones
.Data
) ) )
600 print( "{:>16} {:}".format( "Influences", len(self
.Influences
.Data
) ) )
602 #===========================================================================
606 # The raw key array holds all the keys for all the bones in all the specified sequences,
607 # organized as follows:
608 # For each AnimInfoBinary's sequence there are [Number of bones] times [Number of frames keys]
609 # in the VQuatAnimKeys, laid out as tracks of [numframes] keys for each bone in the order of
610 # the bones as defined in the array of FnamedBoneBinary in the PSA.
612 # Once the data from the PSK (now digested into native skeletal mesh) and PSA (digested into
613 # a native animation object containing one or more sequences) are associated together at runtime,
614 # bones are linked up by name. Any bone in a skeleton (from the PSK) that finds no partner in
615 # the animation sequence (from the PSA) will assume its reference pose stance ( as defined in
616 # the offsets & rotations that are in the VBones making up the reference skeleton from the PSK)
617 #===========================================================================
621 self
.GeneralHeader
= VChunkHeader("ANIMHEAD", 0)
622 self
.Bones
= FileSection("BONENAMES", SIZE_FNAMEDBONEBINARY
) #FNamedBoneBinary
623 self
.Animations
= FileSection("ANIMINFO", SIZE_ANIMINFOBINARY
) #AnimInfoBinary
624 self
.RawKeys
= FileSection("ANIMKEYS", SIZE_VQUATANIMKEY
) #VQuatAnimKey
625 # this will take the format of key=Bone Name, value = (BoneIndex, Bone Object)
629 def AddBone(self
, b
):
630 self
.Bones
.Data
.append(b
)
632 def AddAnimation(self
, a
):
633 self
.Animations
.Data
.append(a
)
635 def AddRawKey(self
, k
):
636 self
.RawKeys
.Data
.append(k
)
638 def UpdateHeaders(self
):
639 self
.Bones
.UpdateHeader()
640 self
.Animations
.UpdateHeader()
641 self
.RawKeys
.UpdateHeader()
643 def GetBoneByIndex(self
, bone_index
):
644 if bone_index
>= 0 and len(self
.Bones
.Data
) > bone_index
:
645 return self
.Bones
.Data
[bone_index
]
648 return (len(self
.Bones
.Data
) == 0 or len(self
.Animations
.Data
) == 0)
650 def StoreBone(self
, b
):
651 self
.BoneLookup
[b
.Name
] = [-1, b
]
653 def UseBone(self
, bone_name
):
654 if bone_name
in self
.BoneLookup
:
655 bone_data
= self
.BoneLookup
[bone_name
]
657 if bone_data
[0] == -1:
658 bone_data
[0] = len(self
.Bones
.Data
)
659 self
.AddBone(bone_data
[1])
660 #self.Bones.Data.append(bone_data[1])
664 def GetBoneByName(self
, bone_name
):
665 if bone_name
in self
.BoneLookup
:
666 bone_data
= self
.BoneLookup
[bone_name
]
669 def GetBoneIndex(self
, bone_name
):
670 if bone_name
in self
.BoneLookup
:
671 bone_data
= self
.BoneLookup
[bone_name
]
676 return self
.GeneralHeader
.dump() + self
.Bones
.dump() + self
.Animations
.dump() + self
.RawKeys
.dump()
679 print( "{:>16} {:}".format( "Bones", len(self
.Bones
.Data
) ) )
680 print( "{:>16} {:}".format( "Animations", len(self
.Animations
.Data
) ) )
681 print( "{:>16} {:}".format( "Raw keys", len(self
.RawKeys
.Data
) ) )
683 #===========================================================================
684 # Helpers to create bone structs
685 #===========================================================================
686 def make_vbone( name
, parent_index
, child_count
, orientation_quat
, position_vect
):
689 bone
.ParentIndex
= parent_index
690 bone
.NumChildren
= child_count
691 bone
.BonePos
.Orientation
= orientation_quat
692 bone
.BonePos
.Position
.X
= position_vect
.x
693 bone
.BonePos
.Position
.Y
= position_vect
.y
694 bone
.BonePos
.Position
.Z
= position_vect
.z
695 #these values seem to be ignored?
696 #bone.BonePos.Length = tail.length
697 #bone.BonePos.XSize = tail.x
698 #bone.BonePos.YSize = tail.y
699 #bone.BonePos.ZSize = tail.z
702 def make_namedbonebinary( name
, parent_index
, child_count
, orientation_quat
, position_vect
, is_real
):
703 bone
= FNamedBoneBinary()
705 bone
.ParentIndex
= parent_index
706 bone
.NumChildren
= child_count
707 bone
.BonePos
.Orientation
= orientation_quat
708 bone
.BonePos
.Position
.X
= position_vect
.x
709 bone
.BonePos
.Position
.Y
= position_vect
.y
710 bone
.BonePos
.Position
.Z
= position_vect
.z
711 bone
.IsRealBone
= is_real
714 def make_fquat( bquat
):
716 #flip handedness for UT = set x,y,z to negative (rotate in other direction)
723 def make_fquat_default( bquat
):
732 #===========================================================================
733 #RG - check to make sure face isnt a line
734 #===========================================================================
735 def is_1d_face( face
, mesh
):
736 #ID Vertex of id point
737 v0
= face
.vertices
[0]
738 v1
= face
.vertices
[1]
739 v2
= face
.vertices
[2]
741 return (mesh
.vertices
[v0
].co
== mesh
.vertices
[v1
].co \
742 or mesh
.vertices
[v1
].co
== mesh
.vertices
[v2
].co \
743 or mesh
.vertices
[v2
].co
== mesh
.vertices
[v0
].co
)
746 #===========================================================================
748 # (renamed to seperate it from VVertex.SmoothGroup)
749 #===========================================================================
750 class SmoothingGroup
:
756 self
.neighboring_faces
= []
757 self
.neighboring_groups
= []
759 self
.local_id
= SmoothingGroup
.static_id
760 SmoothingGroup
.static_id
+= 1
762 def __cmp__(self
, other
):
763 if isinstance(other
, SmoothingGroup
):
764 return cmp( self
.local_id
, other
.local_id
)
768 return hash(self
.local_id
)
770 # searches neighboring faces to determine which smoothing group ID can be used
771 def get_valid_smoothgroup_id(self
):
773 for group
in self
.neighboring_groups
:
774 if group
!= None and group
.id == temp_id
:
775 if temp_id
< 0x80000000:
776 temp_id
= temp_id
<< 1
778 raise Error("Smoothing Group ID Overflowed, Smoothing Group evidently has more than 31 neighboring groups")
783 def make_neighbor(self
, new_neighbor
):
784 if new_neighbor
not in self
.neighboring_groups
:
785 self
.neighboring_groups
.append( new_neighbor
)
787 def contains_face(self
, face
):
788 return (face
in self
.faces
)
790 def add_neighbor_face(self
, face
):
791 if not face
in self
.neighboring_faces
:
792 self
.neighboring_faces
.append( face
)
794 def add_face(self
, face
):
795 if not face
in self
.faces
:
796 self
.faces
.append( face
)
798 def determine_edge_sharing( mesh
):
800 edge_sharing_list
= dict()
802 for edge
in mesh
.edges
:
803 edge_sharing_list
[edge
.key
] = []
805 for face
in mesh
.tessfaces
:
806 for key
in face
.edge_keys
:
807 if not face
in edge_sharing_list
[key
]:
808 edge_sharing_list
[key
].append(face
) # mark this face as sharing this edge
810 return edge_sharing_list
812 def find_edges( mesh
, key
):
813 """ Temp replacement for mesh.findEdges().
814 This is painfully slow.
816 for edge
in mesh
.edges
:
818 if key
[0] == v
[0] and key
[1] == v
[1]:
821 def add_face_to_smoothgroup( mesh
, face
, edge_sharing_list
, smoothgroup
):
823 if face
in smoothgroup
.faces
:
826 smoothgroup
.add_face(face
)
828 for key
in face
.edge_keys
:
830 edge_id
= find_edges(mesh
, key
)
835 if not( mesh
.edges
[edge_id
].use_edge_sharp
):
837 for shared_face
in edge_sharing_list
[key
]:
838 if shared_face
!= face
:
840 add_face_to_smoothgroup( mesh
, shared_face
, edge_sharing_list
, smoothgroup
)
843 for shared_face
in edge_sharing_list
[key
]:
844 if shared_face
!= face
:
845 smoothgroup
.add_neighbor_face( shared_face
)
847 def determine_smoothgroup_for_face( mesh
, face
, edge_sharing_list
, smoothgroup_list
):
849 for group
in smoothgroup_list
:
850 if (face
in group
.faces
):
853 smoothgroup
= SmoothingGroup();
854 add_face_to_smoothgroup( mesh
, face
, edge_sharing_list
, smoothgroup
)
856 if not smoothgroup
in smoothgroup_list
:
857 smoothgroup_list
.append( smoothgroup
)
859 def build_neighbors_tree( smoothgroup_list
):
861 for group
in smoothgroup_list
:
862 for face
in group
.neighboring_faces
:
863 for neighbor_group
in smoothgroup_list
:
864 if neighbor_group
.contains_face( face
) and neighbor_group
not in group
.neighboring_groups
:
865 group
.make_neighbor( neighbor_group
)
866 neighbor_group
.make_neighbor( group
)
868 #===========================================================================
869 # parse_smooth_groups
870 #===========================================================================
871 def parse_smooth_groups( mesh
):
873 print("Parsing smooth groups...")
876 smoothgroup_list
= []
877 edge_sharing_list
= determine_edge_sharing(mesh
)
878 #print("faces:",len(mesh.tessfaces))
879 interval
= math
.floor(len(mesh
.tessfaces
) / 100)
880 if interval
== 0: #if the faces are few do this
881 interval
= math
.floor(len(mesh
.tessfaces
) / 10)
882 #print("FACES:",len(mesh.tessfaces),"//100 =" "interval:",interval)
883 for face
in mesh
.tessfaces
:
885 determine_smoothgroup_for_face(mesh
, face
, edge_sharing_list
, smoothgroup_list
)
886 # progress indicator, writes to console without scrolling
887 if face
.index
> 0 and (face
.index
% interval
) == 0:
888 print("Processing... {}%\r".format( int(face
.index
/ len(mesh
.tessfaces
) * 100) ), end
='')
890 print("Completed" , ' '*20)
892 verbose("len(smoothgroup_list)={}".format(len(smoothgroup_list
)))
894 build_neighbors_tree(smoothgroup_list
)
896 for group
in smoothgroup_list
:
897 group
.get_valid_smoothgroup_id()
899 print("Smooth group parsing completed in {:.2f}s".format(time
.clock() - t
))
900 return smoothgroup_list
902 #===========================================================================
903 # http://en.wikibooks.org/wiki/Blender_3D:_Blending_Into_Python/Cookbook#Triangulate_NMesh
904 # blender 2.50 format using the Operators/command convert the mesh to tri mesh
905 #===========================================================================
906 def triangulate_mesh( object ):
908 verbose(header("triangulateNMesh"))
910 scene
= bpy
.context
.scene
912 me_ob
= object.copy()
913 me_ob
.data
= object.to_mesh(bpy
.context
.scene
, True, 'PREVIEW') #write data object
914 bpy
.context
.scene
.objects
.link(me_ob
)
915 bpy
.context
.scene
.update()
916 bpy
.ops
.object.mode_set(mode
='OBJECT')
917 for i
in scene
.objects
:
918 i
.select
= False # deselect all objects
921 scene
.objects
.active
= me_ob
923 print("Copy and Convert mesh just incase any way...")
925 bpy
.ops
.object.mode_set(mode
='EDIT')
926 bpy
.ops
.mesh
.select_all(action
='SELECT')# select all the face/vertex/edge
927 bpy
.ops
.object.mode_set(mode
='EDIT')
928 bpy
.ops
.mesh
.quads_convert_to_tris()
929 bpy
.context
.scene
.update()
931 bpy
.ops
.object.mode_set(mode
='OBJECT')
933 bpy
.context
.scene
.udk_option_triangulate
= True
935 verbose("Triangulated mesh")
937 me_ob
.data
= me_ob
.to_mesh(bpy
.context
.scene
, True, 'PREVIEW') #write data object
938 bpy
.context
.scene
.update()
941 #copy mesh data and then merge them into one object
942 def meshmerge(selectedobjects
):
943 bpy
.ops
.object.mode_set(mode
='OBJECT') #object mode and not edit mode
944 cloneobjects
= [] #object holder for copying object data
945 if len(selectedobjects
) > 1:
946 print("selectedobjects:",len(selectedobjects
)) #print select object
947 count
= 0 #reset count
948 for count
in range(len( selectedobjects
)):
949 #print("Index:",count)
950 if selectedobjects
[count
] != None:
951 me_da
= selectedobjects
[count
].data
.copy() #copy data
952 me_ob
= selectedobjects
[count
].copy() #copy object
953 #note two copy two types else it will use the current data or mesh
954 me_ob
.data
= me_da
#assign the data
955 bpy
.context
.scene
.objects
.link(me_ob
)#link the object to the scene #current object location
956 print("Index:",count
,"clone object",me_ob
.name
) #print clone object
957 cloneobjects
.append(me_ob
) #add object to the array
958 for i
in bpy
.data
.objects
: i
.select
= False #deselect all objects
959 count
= 0 #reset count
960 #begin merging the mesh together as one
961 for count
in range(len( cloneobjects
)):
963 bpy
.context
.scene
.objects
.active
= cloneobjects
[count
]
964 print("Set Active Object:",cloneobjects
[count
].name
)
965 cloneobjects
[count
].select
= True
966 bpy
.ops
.object.join() #join object together
967 if len(cloneobjects
) > 1:
968 bpy
.types
.Scene
.udk_copy_merge
= True
969 return cloneobjects
[0]
971 #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.
972 def sortmesh(selectmesh
):
973 print("MESH SORTING...")
976 for countm
in range(len(selectmesh
)):
977 #if object are center add here
978 if selectmesh
[countm
].location
.x
== 0 and selectmesh
[countm
].location
.y
== 0 and selectmesh
[countm
].location
.z
== 0:
979 centermesh
.append(selectmesh
[countm
])
980 else:#if not add here for not center
981 notcentermesh
.append(selectmesh
[countm
])
983 #add mesh object in order for merge object
984 for countm
in range(len(centermesh
)):
985 selectmesh
.append(centermesh
[countm
])
986 for countm
in range(len(notcentermesh
)):
987 selectmesh
.append(notcentermesh
[countm
])
988 if len(selectmesh
) == 1: #if there one mesh just do some here
989 return selectmesh
[0] #return object mesh
991 return meshmerge(selectmesh
) #return merge object mesh
993 #===========================================================================
995 #===========================================================================
996 def parse_mesh( mesh
, psk
):
997 #bpy.ops.object.mode_set(mode='OBJECT')
998 #error ? on commands for select object?
999 print(header("MESH", 'RIGHT'))
1000 print("Mesh object:", mesh
.name
)
1001 scene
= bpy
.context
.scene
1002 for i
in scene
.objects
: i
.select
= False # deselect all objects
1003 scene
.objects
.active
= mesh
1005 mesh
= triangulate_mesh(mesh
)
1006 if bpy
.types
.Scene
.udk_copy_merge
== True:
1007 bpy
.context
.scene
.objects
.unlink(setmesh
)
1008 #print("FACES----:",len(mesh.data.tessfaces))
1009 verbose("Working mesh object: {}".format(mesh
.name
))
1011 #collect a list of the material names
1012 print("Materials...")
1016 for slot
in mesh
.material_slots
:
1018 print(" Material {} '{}'".format(mat_slot_index
, slot
.name
))
1019 MaterialName
.append(slot
.name
)
1020 #if slot.material.texture_slots[0] != None:
1021 #if slot.material.texture_slots[0].texture.image.filepath != None:
1022 #print(" Texture path {}".format(slot.material.texture_slots[0].texture.image.filepath))
1023 #create the current material
1024 v_material
= psk
.GetMatByIndex(mat_slot_index
)
1025 v_material
.MaterialName
= slot
.name
1026 v_material
.TextureIndex
= mat_slot_index
1027 v_material
.AuxMaterial
= mat_slot_index
1029 verbose(" PSK index {}".format(v_material
.TextureIndex
))
1031 #END slot in mesh.material_slots
1033 # object_mat = mesh.materials[0]
1034 #object_material_index = mesh.active_material_index
1035 #FIXME ^ this is redundant due to "= face.material_index" in face loop
1038 points
= ObjMap() #vertex
1041 discarded_face_count
= 0
1042 sys
.setrecursionlimit(1000000)
1043 smoothgroup_list
= parse_smooth_groups(mesh
.data
)
1045 print("{} faces".format(len(mesh
.data
.tessfaces
)))
1047 print("Smooth groups active:", bpy
.context
.scene
.udk_option_smoothing_groups
)
1049 for face
in mesh
.data
.tessfaces
:
1051 smoothgroup_id
= 0x80000000
1053 for smooth_group
in smoothgroup_list
:
1054 if smooth_group
.contains_face(face
):
1055 smoothgroup_id
= smooth_group
.id
1058 #print ' -- Dumping UVs -- '
1059 #print current_face.uv_textures
1060 # modified by VendorX
1061 object_material_index
= face
.material_index
1063 if len(face
.vertices
) != 3:
1064 raise Error("Non-triangular face (%i)" % len(face
.vertices
))
1066 #RG - apparently blender sometimes has problems when you do quad to triangle
1067 # conversion, and ends up creating faces that have only TWO points -
1068 # one of the points is simply in the vertex list for the face twice.
1069 # This is bad, since we can't get a real face normal for a LINE, we need
1070 # a plane for this. So, before we add the face to the list of real faces,
1071 # ensure that the face is actually a plane, and not a line. If it is not
1072 # planar, just discard it and notify the user in the console after we're
1073 # done dumping the rest of the faces
1075 if not is_1d_face(face
, mesh
.data
):
1080 #get or create the current material
1081 psk
.GetMatByIndex(object_material_index
)
1083 face_index
= face
.index
1087 if len(mesh
.data
.uv_textures
) > 0:
1089 uv_layer
= mesh
.data
.tessface_uv_textures
.active
1090 face_uv
= uv_layer
.data
[face_index
]
1091 #size(data) is number of texture faces. Each face has UVs
1092 #print("DATA face uv: ",len(faceUV.uv), " >> ",(faceUV.uv[0][0]))
1095 vert_index
= face
.vertices
[i
]
1096 vert
= mesh
.data
.vertices
[vert_index
]
1098 #assumes 3 UVs Per face (for now)
1100 if len(face_uv
.uv
) != 3:
1101 print("WARNING: face has more or less than 3 UV coordinates - writing 0,0...")
1104 uv
= [face_uv
.uv
[i
][0],face_uv
.uv
[i
][1]] #OR bottom works better # 24 for cube
1109 #flip V coordinate because UEd requires it and DOESN'T flip it on its own like it
1110 #does with the mesh Y coordinates. this is otherwise known as MAGIC-2
1113 # clamp UV coords if udk_option_clamp_uv is True
1114 if bpy
.context
.scene
.udk_option_clamp_uv
:
1124 # RE - Append untransformed vector (for normal calc below)
1125 # TODO: convert to Blender.Mathutils
1126 vect_list
.append( FVector(vert
.co
.x
, vert
.co
.y
, vert
.co
.z
) )
1128 # Transform position for export
1129 #vpos = vert.co * object_material_index
1131 #should fixed this!!
1132 vpos
= mesh
.matrix_local
* vert
.co
1133 if bpy
.context
.scene
.udk_option_scale
< 0 or bpy
.context
.scene
.udk_option_scale
> 1:
1135 vpos
.x
= vpos
.x
* bpy
.context
.scene
.udk_option_scale
1136 vpos
.y
= vpos
.y
* bpy
.context
.scene
.udk_option_scale
1137 vpos
.z
= vpos
.z
* bpy
.context
.scene
.udk_option_scale
1138 #print("scale pos:", vpos)
1144 if bpy
.context
.scene
.udk_option_smoothing_groups
:#is this necessary?
1145 p
.SmoothGroup
= smoothgroup_id
1147 lPoint
= VPointSimple()
1148 lPoint
.Point
.X
= vpos
.x
1149 lPoint
.Point
.Y
= vpos
.y
1150 lPoint
.Point
.Z
= vpos
.z
1152 if lPoint
in points_linked
:
1153 if not(p
in points_linked
[lPoint
]):
1154 points_linked
[lPoint
].append(p
)
1156 points_linked
[lPoint
] = [p
]
1160 w
.MatIndex
= object_material_index
1161 w
.PointIndex
= points
.get(p
) # store keys
1164 if bpy
.context
.scene
.udk_option_smoothing_groups
:#is this necessary?
1165 w
.SmoothGroup
= smoothgroup_id
1166 index_wedge
= wedges
.get(w
)
1167 wedge_list
.append(index_wedge
)
1170 #print("result PointIndex={}, U={:.6f}, V={:.6f}, wedge_index={}".format(
1176 #END for i in range(3)
1178 # Determine face vertex order
1180 # TODO: convert to Blender.Mathutils
1181 # get normal from blender
1183 # convert to FVector
1184 norm
= FVector(no
[0], no
[1], no
[2])
1185 # Calculate the normal of the face in blender order
1186 tnorm
= vect_list
[1].sub(vect_list
[0]).cross(vect_list
[2].sub(vect_list
[1]))
1187 # RE - dot the normal from blender order against the blender normal
1188 # this gives the product of the two vectors' lengths along the blender normal axis
1189 # all that matters is the sign
1190 dot
= norm
.dot(tnorm
)
1193 # RE - magic: if the dot product above > 0, order the vertices 2, 1, 0
1194 # if the dot product above < 0, order the vertices 0, 1, 2
1195 # if the dot product is 0, then blender's normal is coplanar with the face
1196 # and we cannot deduce which side of the face is the outside of the mesh
1198 (tri
.WedgeIndex2
, tri
.WedgeIndex1
, tri
.WedgeIndex0
) = wedge_list
1200 (tri
.WedgeIndex0
, tri
.WedgeIndex1
, tri
.WedgeIndex2
) = wedge_list
1202 dindex0
= face
.vertices
[0];
1203 dindex1
= face
.vertices
[1];
1204 dindex2
= face
.vertices
[2];
1206 mesh
.data
.vertices
[dindex0
].select
= True
1207 mesh
.data
.vertices
[dindex1
].select
= True
1208 mesh
.data
.vertices
[dindex2
].select
= True
1210 raise Error("Normal coplanar with face! points:", mesh
.data
.vertices
[dindex0
].co
, mesh
.data
.vertices
[dindex1
].co
, mesh
.data
.vertices
[dindex2
].co
)
1213 if face
.use_smooth
== True:
1214 tri
.SmoothingGroups
= 1
1216 tri
.SmoothingGroups
= 0
1217 tri
.MatIndex
= object_material_index
1219 if bpy
.context
.scene
.udk_option_smoothing_groups
:
1220 tri
.SmoothingGroups
= smoothgroup_id
1221 print("Bool Smooth")
1225 #END if not is_1d_face(current_face, mesh.data)
1228 discarded_face_count
+= 1
1230 #END face in mesh.data.faces
1232 print("{} points".format(len(points
.dict)))
1234 for point
in points
.items():
1237 if len(points
.dict) > 32767:
1238 raise Error("Mesh vertex limit exceeded! {} > 32767".format(len(points
.dict)))
1240 print("{} wedges".format(len(wedges
.dict)))
1242 for wedge
in wedges
.items():
1245 # alert the user to degenerate face issues
1246 if discarded_face_count
> 0:
1247 print("WARNING: Mesh contained degenerate faces (non-planar)")
1248 print(" Discarded {} faces".format(discarded_face_count
))
1250 #RG - walk through the vertex groups and find the indexes into the PSK points array
1251 #for them, then store that index and the weight as a tuple in a new list of
1252 #verts for the group that we can look up later by bone name, since Blender matches
1253 #verts to bones for influences by having the VertexGroup named the same thing as
1256 #[print(x, len(points_linked[x])) for x in points_linked]
1257 #print("pointsindex length ",len(points_linked))
1260 # all vertex groups of the mesh (obj)...
1261 for obj_vertex_group
in mesh
.vertex_groups
:
1263 #print(" bone group build:",obj_vertex_group.name)#print bone name
1264 #print(dir(obj_vertex_group))
1265 verbose("obj_vertex_group.name={}".format(obj_vertex_group
.name
))
1269 # all vertices in the mesh...
1270 for vertex
in mesh
.data
.vertices
:
1272 # all groups this vertex is a member of...
1273 for vgroup
in vertex
.groups
:
1274 if vgroup
.group
== obj_vertex_group
.index
:
1275 vertex_weight
= vgroup
.weight
1277 vpos
= mesh
.matrix_local
* vertex
.co
1278 if bpy
.context
.scene
.udk_option_scale
< 0 or bpy
.context
.scene
.udk_option_scale
> 1:
1279 vpos
.x
= vpos
.x
* bpy
.context
.scene
.udk_option_scale
1280 vpos
.y
= vpos
.y
* bpy
.context
.scene
.udk_option_scale
1281 vpos
.z
= vpos
.z
* bpy
.context
.scene
.udk_option_scale
1286 #print(len(points_linked[p]))
1287 try: #check if point doesn't give error
1288 for point
in points_linked
[p
]:
1289 point_index
= points
.get(point
) #point index
1290 v_item
= (point_index
, vertex_weight
)
1291 vertex_list
.append(v_item
)
1292 except Exception:#if get error ignore them #not safe I think
1293 print("Error link points!")
1296 #bone name, [point id and wieght]
1297 #print("Add Vertex Group:",obj_vertex_group.name, " No. Points:",len(vertex_list))
1298 psk
.VertexGroups
[obj_vertex_group
.name
] = vertex_list
1300 # remove the temporary triangulated mesh
1301 if bpy
.context
.scene
.udk_option_triangulate
== True:
1302 verbose("Removing temporary triangle mesh: {}".format(mesh
.name
))
1303 bpy
.ops
.object.mode_set(mode
='OBJECT') # OBJECT mode
1304 mesh
.parent
= None # unparent to avoid phantom links
1305 bpy
.context
.scene
.objects
.unlink(mesh
) # unlink
1307 #===========================================================================
1308 # Collate bones that belong to the UDK skeletal mesh
1309 #===========================================================================
1310 def parse_armature( armature
, psk
, psa
):
1312 print(header("ARMATURE", 'RIGHT'))
1313 verbose("Armature object: {} Armature data: {}".format(armature
.name
, armature
.data
.name
))
1315 # generate a list of root bone candidates
1316 root_candidates
= [b
for b
in armature
.data
.bones
if b
.parent
== None and b
.use_deform
== True]
1318 # should be a single, unambiguous result
1319 if len(root_candidates
) == 0:
1320 raise Error("Cannot find root for UDK bones. The root bone must use deform.")
1322 if len(root_candidates
) > 1:
1323 raise Error("Ambiguous root for UDK. More than one root bone is using deform.")
1325 # prep for bone collection
1326 udk_root_bone
= root_candidates
[0]
1328 BoneUtil
.static_bone_id
= 0 # replaces global
1330 # traverse bone chain
1331 print("{: <3} {: <48} {: <20}".format("ID", "Bone", "Status"))
1333 recurse_bone(udk_root_bone
, udk_bones
, psk
, psa
, 0, armature
.matrix_local
)
1336 if len(udk_bones
) < 3:
1337 raise Error("Less than three bones may crash UDK (legacy issue?)")
1339 # return a list of bones making up the entire udk skel
1340 # this is passed to parse_animation instead of working from keyed bones in the action
1343 #===========================================================================
1346 # psk the PSK file object
1347 # psa the PSA file object
1350 # indent text indent for recursive log
1351 #===========================================================================
1352 def recurse_bone( bone
, bones
, psk
, psa
, parent_id
, parent_matrix
, indent
="" ):
1358 if not bone
.use_deform
:
1359 status
= "No effect"
1361 # calc parented bone transform
1362 if bone
.parent
!= None:
1363 quat
= make_fquat(bone
.matrix
.to_quaternion())
1364 quat_parent
= bone
.parent
.matrix
.to_quaternion().inverted()
1365 parent_head
= quat_parent
* bone
.parent
.head
1366 parent_tail
= quat_parent
* bone
.parent
.tail
1367 translation
= (parent_tail
- parent_head
) + bone
.head
1369 # calc root bone transform
1371 translation
= parent_matrix
* bone
.head
# ARMATURE OBJECT Location
1372 rot_matrix
= bone
.matrix
* parent_matrix
.to_3x3() # ARMATURE OBJECT Rotation
1373 quat
= make_fquat_default(rot_matrix
.to_quaternion())
1374 #udk_option_scale bones here?
1375 if bpy
.context
.scene
.udk_option_scale
< 0 or bpy
.context
.scene
.udk_option_scale
> 1:
1376 translation
.x
= translation
.x
* bpy
.context
.scene
.udk_option_scale
1377 translation
.y
= translation
.y
* bpy
.context
.scene
.udk_option_scale
1378 translation
.z
= translation
.z
* bpy
.context
.scene
.udk_option_scale
1379 bone_id
= BoneUtil
.static_bone_id
# ALT VERS
1380 BoneUtil
.static_bone_id
+= 1 # ALT VERS
1382 child_count
= len(bone
.children
)
1384 psk
.AddBone( make_vbone(bone
.name
, parent_id
, child_count
, quat
, translation
) )
1385 psa
.StoreBone( make_namedbonebinary(bone
.name
, parent_id
, child_count
, quat
, translation
, 1) )
1387 #RG - dump influences for this bone - use the data we collected in the mesh dump phase to map our bones to vertex groups
1388 if bone
.name
in psk
.VertexGroups
:
1389 vertex_list
= psk
.VertexGroups
[bone
.name
]
1390 #print("vertex list:", len(vertex_list), " of >" ,bone.name )
1391 for vertex_data
in vertex_list
:
1392 point_index
= vertex_data
[0]
1393 vertex_weight
= vertex_data
[1]
1394 influence
= VRawBoneInfluence()
1395 influence
.Weight
= vertex_weight
1396 influence
.BoneIndex
= bone_id
1397 influence
.PointIndex
= point_index
1398 #print (" AddInfluence to vertex {}, weight={},".format(point_index, vertex_weight))
1399 psk
.AddInfluence(influence
)
1401 status
= "No vertex group"
1402 #FIXME overwriting previous status error?
1404 print("{:<3} {:<48} {:<20}".format(bone_id
, indent
+bone
.name
, status
))
1407 #recursively dump child bones
1409 for child_bone
in bone
.children
:
1410 recurse_bone(child_bone
, bones
, psk
, psa
, bone_id
, parent_matrix
, " "+indent
)
1412 # FIXME rename? remove?
1414 static_bone_id
= 0 # static property to replace global
1416 #===========================================================================
1417 # armature the armature
1418 # udk_bones list of bones to be exported
1419 # actions_to_export list of actions to process for export
1420 # psa the PSA file object
1421 #===========================================================================
1422 def parse_animation( armature
, udk_bones
, actions_to_export
, psa
):
1424 print(header("ANIMATION", 'RIGHT'))
1426 context
= bpy
.context
1427 anim_rate
= context
.scene
.render
.fps
1429 verbose("Armature object: {}".format(armature
.name
))
1430 print("Scene: {} FPS: {} Frames: {} to {}".format(context
.scene
.name
, anim_rate
, context
.scene
.frame_start
, context
.scene
.frame_end
))
1431 print("Processing {} action(s)".format(len(actions_to_export
)))
1433 if armature
.animation_data
== None: #if animation data was not create for the armature it will skip the exporting action set(s)
1434 print("None Actions Set! skipping...")
1436 restoreAction
= armature
.animation_data
.action
# Q: is animation_data always valid?
1438 restoreFrame
= context
.scene
.frame_current
# we already do this in export_proxy, but we'll do it here too for now
1439 raw_frame_index
= 0 # used to set FirstRawFrame, seperating actions in the raw keyframe array
1442 for action
in actions_to_export
:
1444 # removed: check for armature with no animation; all it did was force you to add one
1446 if not len(action
.fcurves
):
1447 print("{} has no keys, skipping".format(action
.name
))
1450 # apply action to armature and update scene
1451 # note if loop all actions that is not armature it will override and will break armature animation.
1452 armature
.animation_data
.action
= action
1453 context
.scene
.update()
1455 # min/max frames define range
1456 framemin
, framemax
= action
.frame_range
1457 start_frame
= int(framemin
)
1458 end_frame
= int(framemax
)
1459 scene_range
= range(start_frame
, end_frame
+ 1)
1460 frame_count
= len(scene_range
)
1462 # create the AnimInfoBinary
1463 anim
= AnimInfoBinary()
1464 anim
.Name
= action
.name
1465 anim
.Group
= "" # unused?
1466 anim
.NumRawFrames
= frame_count
1467 anim
.AnimRate
= anim_rate
1468 anim
.FirstRawFrame
= raw_frame_index
1470 print("{}, frames {} to {} ({} frames)".format(action
.name
, start_frame
, end_frame
, frame_count
))
1472 # removed: bone lookup table
1474 # build a list of pose bones relevant to the collated udk_bones
1475 # fixme: could be done once, prior to loop?
1478 for pb
in armature
.pose
.bones
:
1479 if b
.name
== pb
.name
:
1480 udk_pose_bones
.append(pb
)
1483 # sort in the order the bones appear in the PSA file
1485 ordered_bones
= sorted([(psa
.UseBone(b
.name
), b
) for b
in udk_pose_bones
], key
=operator
.itemgetter(0))
1487 # NOTE: posebone.bone references the obj/edit bone
1488 # REMOVED: unique_bone_indexes is redundant?
1491 for i
in range(frame_count
):
1493 frame
= scene_range
[i
]
1495 #verbose("FRAME {}".format(i), i) # test loop sampling
1497 # advance to frame (automatically updates the pose)
1498 context
.scene
.frame_set(frame
)
1500 # compute the key for each bone
1501 for bone_data
in ordered_bones
:
1503 bone_index
= bone_data
[0]
1504 pose_bone
= bone_data
[1]
1505 pose_bone_matrix
= mathutils
.Matrix(pose_bone
.matrix
)
1507 if pose_bone
.parent
!= None:
1508 pose_bone_parent_matrix
= mathutils
.Matrix(pose_bone
.parent
.matrix
)
1509 pose_bone_matrix
= pose_bone_parent_matrix
.inverted() * pose_bone_matrix
1511 head
= pose_bone_matrix
.to_translation()
1512 quat
= pose_bone_matrix
.to_quaternion().normalized()
1514 if pose_bone
.parent
!= None:
1515 quat
= make_fquat(quat
)
1517 quat
= make_fquat_default(quat
)
1519 #scale animation position here?
1520 if bpy
.context
.scene
.udk_option_scale
< 0 or bpy
.context
.scene
.udk_option_scale
> 1:
1521 head
.x
= head
.x
* bpy
.context
.scene
.udk_option_scale
1522 head
.y
= head
.y
* bpy
.context
.scene
.udk_option_scale
1523 head
.z
= head
.z
* bpy
.context
.scene
.udk_option_scale
1525 vkey
= VQuatAnimKey()
1526 vkey
.Position
.X
= head
.x
1527 vkey
.Position
.Y
= head
.y
1528 vkey
.Position
.Z
= head
.z
1529 vkey
.Orientation
= quat
1531 # frame delta = 1.0 / fps
1532 vkey
.Time
= 1.0 / float(anim_rate
) # according to C++ header this is "disregarded"
1536 # END for bone_data in ordered_bones
1538 raw_frame_index
+= 1
1540 # END for i in range(frame_count)
1542 anim
.TotalBones
= len(ordered_bones
) # REMOVED len(unique_bone_indexes)
1543 anim
.TrackTime
= float(frame_count
) # frame_count/anim.AnimRate makes more sense, but this is what actually works in UDK
1545 verbose("anim.TotalBones={}, anim.TrackTime={}".format(anim
.TotalBones
, anim
.TrackTime
))
1547 psa
.AddAnimation(anim
)
1549 # END for action in actions
1552 armature
.animation_data
.action
= restoreAction
1553 context
.scene
.frame_set(restoreFrame
)
1555 #===========================================================================
1556 # Collate actions to be exported
1557 # Modify this to filter for one, some or all actions. For now use all.
1558 # RETURNS list of actions
1559 #===========================================================================
1560 def collate_actions():
1561 verbose(header("collate_actions"))
1562 actions_to_export
= []
1564 for action
in bpy
.data
.actions
:
1565 if bpy
.context
.scene
.udk_option_selectanimations
: # check if needed to select actions set for exporting it
1566 print("Action Set is selected!")
1568 for actionlist
in bpy
.context
.scene
.udkas_list
: #list the action set from the list
1569 if actionlist
.name
== action
.name
and actionlist
.bmatch
== True and actionlist
.bexport
== True:
1571 print("Added Action Set:",action
.name
)
1573 if bready
== False:#don't export it
1574 print("Skipping Action Set:",action
.name
)
1576 verbose(" + {}".format(action
.name
)) #action set name
1577 actions_to_export
.append(action
) #add to the action array
1579 return actions_to_export
1581 #===========================================================================
1582 # Locate the target armature and mesh for export
1583 # RETURNS armature, mesh
1584 #===========================================================================
1585 def find_armature_and_mesh():
1586 verbose(header("find_armature_and_mesh", 'LEFT', '<', 60))
1588 context
= bpy
.context
1589 active_object
= context
.active_object
1594 # this could be more intuitive
1595 #bpy.ops.object.mode_set(mode='OBJECT')
1597 if bpy
.context
.scene
.udk_option_selectobjects
: #if checked select object true do list object on export
1598 print("select mode:")
1599 if len(bpy
.context
.scene
.udkArm_list
) > 0:
1600 print("Armature Name:",bpy
.context
.scene
.udkArm_list
[bpy
.context
.scene
.udkArm_list_idx
].name
)
1601 for obj
in bpy
.context
.scene
.objects
:
1602 if obj
.name
== bpy
.context
.scene
.udkArm_list
[bpy
.context
.scene
.udkArm_list_idx
].name
:
1606 raise Error("There is no Armature in the list!")
1608 #parented_meshes = [obj for obj in armature.children if obj.type == 'MESH']
1609 meshes
= [obj
for obj
in bpy
.context
.scene
.objects
if obj
.type == 'MESH']
1612 if obj
.type == 'MESH':
1614 #print("PARENT MESH:",obj.name)
1615 for udkmeshlist
in bpy
.context
.scene
.udkmesh_list
:
1616 if obj
.name
== udkmeshlist
.name
and udkmeshlist
.bexport
== True:
1619 if bexportmesh
== True:
1620 print("Mesh Name:",obj
.name
," < SELECT TO EXPORT!")
1621 meshselected
.append(obj
)
1622 print("MESH COUNT:",len(meshselected
))
1623 # try the active object
1624 if active_object
and active_object
.type == 'MESH' and len(meshselected
) == 0:
1625 if active_object
.parent
== armature
:
1626 mesh
= active_object
1628 raise Error("The selected mesh is not parented to the armature")
1630 # otherwise, expect a single mesh parented to the armature (other object types are ignored)
1632 print("Number of meshes:",len(meshes
))
1633 print("Number of meshes (selected):",len(meshes
))
1634 if len(meshes
) == 1:
1637 elif len(meshes
) > 1:
1638 if len(meshselected
) >= 1:
1639 mesh
= sortmesh(meshselected
)
1641 raise Error("More than one mesh(s) parented to armature. Select object(s)!")
1643 raise Error("No mesh parented to armature")
1644 else: #if not check for select function from the list work the code here
1645 print("normal mode:")
1646 # try the active object
1647 if active_object
and active_object
.type == 'ARMATURE':
1648 armature
= active_object
1649 bpy
.ops
.object.mode_set(mode
='OBJECT')
1650 # otherwise, try for a single armature in the scene
1652 #bpy.ops.object.mode_set(mode='OBJECT')
1653 all_armatures
= [obj
for obj
in bpy
.context
.scene
.objects
if obj
.type == 'ARMATURE']
1655 if len(all_armatures
) == 1:#if armature has one scene just assign it
1656 armature
= all_armatures
[0]
1657 elif len(all_armatures
) > 1:#if there more armature then find the select armature
1659 for _armobj
in all_armatures
:
1664 if barmselect
== False:
1665 raise Error("Please select an armatures in the scene")
1667 raise Error("No armatures in scene")
1669 verbose("Found armature: {}".format(armature
.name
))
1672 parented_meshes
= [obj
for obj
in armature
.children
if obj
.type == 'MESH']
1674 if len(armature
.children
) == 0:
1675 raise Error("The selected Armature has no mesh parented to the Armature Object!")
1677 for obj
in armature
.children
:
1679 if obj
.type == 'MESH' and obj
.select
== True:
1680 meshselected
.append(obj
)
1681 # try the active object
1682 if active_object
and active_object
.type == 'MESH' and len(meshselected
) == 0:
1683 if active_object
.parent
== armature
:
1684 mesh
= active_object
1686 raise Error("The selected mesh is not parented to the armature")
1688 # otherwise, expect a single mesh parented to the armature (other object types are ignored)
1690 print("Number of meshes:",len(parented_meshes
))
1691 print("Number of meshes (selected):",len(meshselected
))
1692 if len(parented_meshes
) == 1:
1693 mesh
= parented_meshes
[0]
1695 elif len(parented_meshes
) > 1:
1696 if len(meshselected
) >= 1:
1697 mesh
= sortmesh(meshselected
)
1699 raise Error("More than one mesh(s) parented to armature. Select object(s)!")
1701 raise Error("No mesh parented to armature")
1703 verbose("Found mesh: {}".format(mesh
.name
))
1704 if mesh
== None or armature
== None:
1705 raise Error("Check Mesh and Armature are list!")
1706 #if len(armature.pose.bones) == len(mesh.vertex_groups):
1707 #print("Armature and Mesh Vertex Groups matches Ok!")
1709 #raise Error("Armature bones:" + str(len(armature.pose.bones)) + " Mesh Vertex Groups:" + str(len(mesh.vertex_groups)) +" doesn't match!")
1711 #this will check if object need to be rebuild.
1712 if bpy
.context
.scene
.udk_option_rebuildobjects
:
1713 #print("INIT... REBUILDING...")
1714 print("REBUILDING ARMATURE...")
1716 armature
= rebuildarmature(armature
) #rebuild the armature to raw . If there IK constraint it will ignore it.
1717 print("REBUILDING MESH...")
1718 mesh
= rebuildmesh(mesh
) #rebuild the mesh to raw data format.
1720 return armature
, mesh
1722 #===========================================================================
1723 # Returns a list of vertex groups in the mesh. Can be modified to filter
1724 # groups as necessary.
1726 #===========================================================================
1727 def collate_vertex_groups( mesh
):
1728 verbose("collate_vertex_groups")
1731 for group
in mesh
.vertex_groups
:
1733 groups
.append(group
)
1734 verbose(" " + group
.name
)
1738 #===========================================================================
1740 #===========================================================================
1741 def export(filepath
):
1742 print(header("Export", 'RIGHT'))
1743 bpy
.types
.Scene
.udk_copy_merge
= False #in case fail to export set this to default
1745 context
= bpy
.context
1747 print("Blender Version {}.{}.{}".format(bpy
.app
.version
[0], bpy
.app
.version
[1], bpy
.app
.version
[2]))
1748 print("Filepath: {}".format(filepath
))
1750 verbose("PSK={}, PSA={}".format(context
.scene
.udk_option_export_psk
, context
.scene
.udk_option_export_psa
))
1752 # find armature and mesh
1753 # [change this to implement alternative methods; raise Error() if not found]
1754 udk_armature
, udk_mesh
= find_armature_and_mesh()
1756 # check misc conditions
1757 if not (udk_armature
.scale
.x
== udk_armature
.scale
.y
== udk_armature
.scale
.z
== 1):
1758 raise Error("bad armature scale: armature object should have uniform scale of 1 (ALT-S)")
1760 if not (udk_mesh
.scale
.x
== udk_mesh
.scale
.y
== udk_mesh
.scale
.z
== 1):
1761 raise Error("bad mesh scale: mesh object should have uniform scale of 1 (ALT-S)")
1763 if not (udk_armature
.location
.x
== udk_armature
.location
.y
== udk_armature
.location
.z
== 0):
1764 raise Error("bad armature location: armature should be located at origin (ALT-G)")
1766 if not (udk_mesh
.location
.x
== udk_mesh
.location
.y
== udk_mesh
.location
.z
== 0):
1767 raise Error("bad mesh location: mesh should be located at origin (ALT-G)")
1774 parse_mesh(udk_mesh
, psk
)
1777 udk_bones
= parse_armature(udk_armature
, psk
, psa
)
1780 if context
.scene
.udk_option_export_psa
== True:
1781 actions
= collate_actions()
1782 parse_animation(udk_armature
, udk_bones
, actions
, psa
)
1785 print(header("Exporting", 'CENTER'))
1787 psk_filename
= filepath
+ '.psk'
1788 psa_filename
= filepath
+ '.psa'
1790 if context
.scene
.udk_option_export_psk
== True:
1791 print("Skeletal mesh data...")
1793 file = open(psk_filename
, "wb")
1794 file.write(psk
.dump())
1796 print("Exported: " + psk_filename
)
1799 if context
.scene
.udk_option_export_psa
== True:
1800 print("Animation data...")
1801 if not psa
.IsEmpty():
1803 file = open(psa_filename
, "wb")
1804 file.write(psa
.dump())
1806 print("Exported: " + psa_filename
)
1808 print("No Animation (.psa file) to export")
1812 #if objects are rebuild do the unlink
1813 if bpy
.context
.scene
.udk_option_rebuildobjects
:
1814 print("Unlinking Objects")
1815 print("Armature Object Name:",udk_armature
.name
) #display object name
1816 bpy
.context
.scene
.objects
.unlink(udk_armature
) #remove armature from the scene
1817 print("Mesh Object Name:",udk_mesh
.name
) #display object name
1818 bpy
.context
.scene
.objects
.unlink(udk_mesh
) #remove mesh from the scene
1820 print("Export completed in {:.2f} seconds".format((time
.clock() - t
)))
1822 #===========================================================================
1824 #===========================================================================
1825 class Operator_UDKExport( bpy
.types
.Operator
):
1827 bl_idname
= "object.udk_export"
1828 bl_label
= "Export now"
1830 def execute(self
, context
):
1833 scene
= bpy
.context
.scene
1835 scene
.udk_option_export_psk
= (scene
.udk_option_export
== '0' or scene
.udk_option_export
== '2')
1836 scene
.udk_option_export_psa
= (scene
.udk_option_export
== '1' or scene
.udk_option_export
== '2')
1838 filepath
= get_dst_path()
1841 restore_frame
= scene
.frame_current
1843 message
= "Finish Export!"
1847 except Error
as err
:
1849 message
= err
.message
1852 scene
.frame_set(restore_frame
)
1854 self
.report({'ERROR'}, message
)
1857 scene
.frame_set(restore_frame
)
1861 #===========================================================================
1863 #===========================================================================
1864 class Operator_ToggleConsole( bpy
.types
.Operator
):
1865 """Show or hide the console"""
1866 bl_idname
= "object.toggle_console"
1867 bl_label
= "Toggle console"
1869 #def invoke(self, context, event):
1870 # bpy.ops.wm.console_toggle()
1871 # return{'FINISHED'}
1872 def execute(self
, context
):
1873 bpy
.ops
.wm
.console_toggle()
1876 #===========================================================================
1877 # Get filepath for export
1878 #===========================================================================
1880 if bpy
.context
.scene
.udk_option_filename_src
== '0':
1881 if bpy
.context
.active_object
:
1882 path
= os
.path
.split(bpy
.data
.filepath
)[0] + "\\" + bpy
.context
.active_object
.name
# + ".psk"
1884 #path = os.path.split(bpy.data.filepath)[0] + "\\" + "Unknown";
1885 path
= os
.path
.splitext(bpy
.data
.filepath
)[0]# + ".psk"
1887 path
= os
.path
.splitext(bpy
.data
.filepath
)[0]# + ".psk"
1891 bpy
.types
.Scene
.udk_option_filename_src
= EnumProperty(
1893 description
= "Sets the name for the files",
1894 items
= [ ('0', "From object", "Name will be taken from object name"),
1895 ('1', "From Blend", "Name will be taken from .blend file name") ],
1898 bpy
.types
.Scene
.udk_option_export_psk
= BoolProperty(
1899 name
= "bool export psa",
1900 description
= "Boolean for exporting psk format (Skeleton Mesh)",
1903 bpy
.types
.Scene
.udk_option_export_psa
= BoolProperty(
1904 name
= "bool export psa",
1905 description
= "Boolean for exporting psa format (Animation Data)",
1908 bpy
.types
.Scene
.udk_option_clamp_uv
= BoolProperty(
1910 description
= "True is to limit Clamp UV co-ordinates to [0-1]. False is unrestricted (x,y). ",
1913 bpy
.types
.Scene
.udk_copy_merge
= BoolProperty(
1914 name
= "Merge Mesh",
1915 description
= "This will copy the mesh(s) and merge the object together and unlink the mesh to be remove while exporting the object.",
1918 bpy
.types
.Scene
.udk_option_export
= EnumProperty(
1920 description
= "What to export",
1921 items
= [ ('0', "Mesh only", "Exports the PSK file for the Skeletal Mesh"),
1922 ('1', "Animation only", "Export the PSA file for Action Set(s)(Animations Data)"),
1923 ('2', "Mesh & Animation", "Export both PSK and PSA files(Skeletal Mesh/Animation(s) Data)") ],
1926 bpy
.types
.Scene
.udk_option_verbose
= BoolProperty(
1928 description
= "Verbose console output",
1931 bpy
.types
.Scene
.udk_option_smoothing_groups
= BoolProperty(
1932 name
= "Smooth Groups",
1933 description
= "Activate hard edges as smooth groups",
1936 bpy
.types
.Scene
.udk_option_triangulate
= BoolProperty(
1937 name
= "Triangulate Mesh",
1938 description
= "Convert Quads to Triangles",
1941 bpy
.types
.Scene
.udk_option_selectanimations
= BoolProperty(
1942 name
= "Select Animation(s)",
1943 description
= "Select animation(s) for export to psa file.",
1946 bpy
.types
.Scene
.udk_option_selectobjects
= BoolProperty(
1947 name
= "Select Object(s)",
1948 description
= "Select Armature and Mesh(s). Just make sure mesh(s) is parent to armature.",
1951 bpy
.types
.Scene
.udk_option_rebuildobjects
= BoolProperty(
1952 name
= "Rebuild Objects",
1953 description
= "In case of deform skeleton mesh and animations data. This will rebuild objects from raw format on export when checked.",
1956 bpy
.types
.Scene
.udk_option_ignoreactiongroupnames
= BoolProperty(
1957 name
= "Ignore Action Group Names",
1958 description
= "This will Ignore Action Set Group Names Check With Armature Bones. It will override armature to set action set.",
1961 bpy
.types
.Scene
.udk_option_scale
= FloatProperty(
1963 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.",
1966 #===========================================================================
1968 #===========================================================================
1969 class OBJECT_OT_UTSelectedFaceSmooth(bpy
.types
.Operator
):
1970 """It will only select smooth faces that is select mesh"""
1971 bl_idname
= "object.utselectfacesmooth" # XXX, name???
1972 bl_label
= "Select Smooth Faces"#"Select Smooth faces"
1974 def invoke(self
, context
, event
):
1975 print("----------------------------------------")
1976 print("Init Select Face(s):")
1978 for obj
in bpy
.data
.objects
:
1979 if obj
.type == 'MESH' and obj
.select
== True:
1982 bpy
.ops
.object.mode_set(mode
='OBJECT')#it need to go into object mode to able to select the faces
1983 for i
in bpy
.context
.scene
.objects
: i
.select
= False #deselect all objects
1984 obj
.select
= True #set current object select
1985 bpy
.context
.scene
.objects
.active
= obj
#set active object
1987 mesh
.from_mesh(obj
.data
)
1988 for face
in mesh
.faces
:
1990 for face
in mesh
.faces
:
1991 if face
.smooth
== True:
1997 mesh
.to_mesh(obj
.data
)
1998 bpy
.context
.scene
.update()
1999 bpy
.ops
.object.mode_set(mode
='EDIT')
2000 print("Select Smooth Count(s):",smoothcount
," Flat Count(s):",flatcount
)
2004 print("Selected Face(s) Exectue!")
2005 self
.report({'INFO'}, "Selected Face(s) Exectue!")
2007 print("Didn't select Mesh Object!")
2008 self
.report({'INFO'}, "Didn't Select Mesh Object!")
2009 print("----------------------------------------")
2012 class OBJECT_OT_MeshClearWeights(bpy
.types
.Operator
):
2013 """Remove all mesh vertex groups weights for the bones."""
2014 bl_idname
= "object.meshclearweights" # XXX, name???
2015 bl_label
= "Remove Vertex Weights"#"Remove Mesh vertex weights"
2017 def invoke(self
, context
, event
):
2018 for obj
in bpy
.data
.objects
:
2019 if obj
.type == 'MESH' and obj
.select
== True:
2020 for vg
in obj
.vertex_groups
:
2021 obj
.vertex_groups
.remove(vg
)
2022 self
.report({'INFO'}, "Mesh Vertex Groups Remove!")
2026 def unpack_list(list_of_tuples
):
2028 for t
in list_of_tuples
:
2032 def rebuildmesh(obj
):
2033 #make sure it in object mode
2034 print("Mesh Object Name:",obj
.name
)
2035 bpy
.ops
.object.mode_set(mode
='OBJECT')
2036 for i
in bpy
.context
.scene
.objects
: i
.select
= False #deselect all objects
2038 bpy
.context
.scene
.objects
.active
= obj
2040 me_ob
= bpy
.data
.meshes
.new(("Re_"+obj
.name
))
2046 #print("creating array build mesh...")
2047 mmesh
= obj
.to_mesh(bpy
.context
.scene
,True,'PREVIEW')
2048 uv_layer
= mmesh
.tessface_uv_textures
.active
2049 for face
in mmesh
.tessfaces
:
2050 smoothings
.append(face
.use_smooth
)#smooth or flat in boolean
2051 if uv_layer
!= None:#check if there texture data exist
2052 faceUV
= uv_layer
.data
[face
.index
]
2054 for uv
in faceUV
.uv
:
2055 uvs
.append((uv
[0],uv
[1]))
2057 #print((face.vertices[:]))
2058 if len(face
.vertices
) == 3:
2059 faces
.extend([(face
.vertices
[0],face
.vertices
[1],face
.vertices
[2],0)])
2061 faces
.extend([(face
.vertices
[0],face
.vertices
[1],face
.vertices
[2],face
.vertices
[3])])
2063 for vertex
in mesh
.vertices
:
2064 verts
.append(vertex
.co
.to_tuple())
2065 #vertices weight groups into array
2066 vertGroups
= {} #array in strings
2067 for vgroup
in obj
.vertex_groups
:
2069 for v
in mesh
.vertices
:
2071 if vg
.group
== vgroup
.index
:
2072 vlist
.append((v
.index
,vg
.weight
))
2073 #print((v.index,vg.weight))
2074 vertGroups
[vgroup
.name
] = vlist
2076 #print("creating mesh object...")
2077 #me_ob.from_pydata(verts, [], faces)
2078 me_ob
.vertices
.add(len(verts
))
2079 me_ob
.tessfaces
.add(len(faces
))
2080 me_ob
.vertices
.foreach_set("co", unpack_list(verts
))
2081 me_ob
.tessfaces
.foreach_set("vertices_raw",unpack_list( faces
))
2082 me_ob
.tessfaces
.foreach_set("use_smooth", smoothings
)#smooth array from face
2084 #check if there is uv faces
2085 if len(uvfaces
) > 0:
2086 uvtex
= me_ob
.tessface_uv_textures
.new(name
="retex")
2087 for i
, face
in enumerate(me_ob
.tessfaces
):
2088 blender_tface
= uvtex
.data
[i
] #face
2089 mfaceuv
= uvfaces
[i
]
2090 if len(mfaceuv
) == 3:
2091 blender_tface
.uv1
= mfaceuv
[0];
2092 blender_tface
.uv2
= mfaceuv
[1];
2093 blender_tface
.uv3
= mfaceuv
[2];
2094 if len(mfaceuv
) == 4:
2095 blender_tface
.uv1
= mfaceuv
[0];
2096 blender_tface
.uv2
= mfaceuv
[1];
2097 blender_tface
.uv3
= mfaceuv
[2];
2098 blender_tface
.uv4
= mfaceuv
[3];
2100 me_ob
.update()#need to update the information to able to see into the secne
2101 obmesh
= bpy
.data
.objects
.new(("Re_"+obj
.name
),me_ob
)
2102 bpy
.context
.scene
.update()
2103 #Build tmp materials
2104 materialname
= "ReMaterial"
2105 for matcount
in mesh
.materials
:
2106 matdata
= bpy
.data
.materials
.new(materialname
)
2107 me_ob
.materials
.append(matdata
)
2108 #assign face to material id
2109 for face
in mesh
.tessfaces
:
2110 me_ob
.faces
[face
.index
].material_index
= face
.material_index
2111 #vertices weight groups
2112 for vgroup
in vertGroups
:
2113 group
= obmesh
.vertex_groups
.new(vgroup
)
2114 for v
in vertGroups
[vgroup
]:
2115 group
.add([v
[0]], v
[1], 'ADD')# group.add(array[vertex id],weight,add)
2116 bpy
.context
.scene
.objects
.link(obmesh
)
2117 #print("Mesh Material Count:",len(me_ob.materials))
2119 #print("MATERIAL ID OREDER:")
2120 for mat
in me_ob
.materials
:
2121 #print("-Material:",mat.name,"INDEX:",matcount)
2123 print("Mesh Object Name:",obmesh
.name
)
2124 bpy
.context
.scene
.update()
2127 class OBJECT_OT_UTRebuildMesh(bpy
.types
.Operator
):
2128 """It rebuild the mesh from scrape from the selected mesh object. """ \
2129 """Note the scale will be 1:1 for object mode. To keep from deforming"""
2130 bl_idname
= "object.utrebuildmesh" # XXX, name???
2131 bl_label
= "Rebuild Mesh"#"Rebuild Mesh"
2133 def invoke(self
, context
, event
):
2134 print("----------------------------------------")
2135 print("Init Mesh Bebuild...")
2137 bpy
.ops
.object.mode_set(mode
='OBJECT')
2138 for obj
in bpy
.data
.objects
:
2139 if obj
.type == 'MESH' and obj
.select
== True:
2141 self
.report({'INFO'}, "Rebuild Mesh Finish!")
2142 print("Finish Mesh Build...")
2143 print("----------------------------------------")
2146 def rebuildarmature(obj
):
2147 currentbone
= [] #select armature for roll copy
2148 print("Armature Name:",obj
.name
)
2149 objectname
= "ArmatureDataPSK"
2150 meshname
="ArmatureObjectPSK"
2151 armdata
= bpy
.data
.armatures
.new(objectname
)
2152 ob_new
= bpy
.data
.objects
.new(meshname
, armdata
)
2153 bpy
.context
.scene
.objects
.link(ob_new
)
2154 #bpy.ops.object.mode_set(mode='OBJECT')
2155 for i
in bpy
.context
.scene
.objects
: i
.select
= False #deselect all objects
2156 ob_new
.select
= True
2157 bpy
.context
.scene
.objects
.active
= obj
2159 bpy
.ops
.object.mode_set(mode
='EDIT')
2160 for bone
in obj
.data
.edit_bones
:
2161 if bone
.parent
!= None:
2162 currentbone
.append([bone
.name
,bone
.roll
])
2164 currentbone
.append([bone
.name
,bone
.roll
])
2165 bpy
.ops
.object.mode_set(mode
='OBJECT')
2166 for i
in bpy
.context
.scene
.objects
: i
.select
= False #deselect all objects
2167 bpy
.context
.scene
.objects
.active
= ob_new
2168 bpy
.ops
.object.mode_set(mode
='EDIT')
2170 for bone
in obj
.data
.bones
:
2171 bpy
.ops
.object.mode_set(mode
='EDIT')
2172 newbone
= ob_new
.data
.edit_bones
.new(bone
.name
)
2173 newbone
.head
= bone
.head_local
2174 newbone
.tail
= bone
.tail_local
2175 for bonelist
in currentbone
:
2176 if bone
.name
== bonelist
[0]:
2177 newbone
.roll
= bonelist
[1]
2179 if bone
.parent
!= None:
2180 parentbone
= ob_new
.data
.edit_bones
[bone
.parent
.name
]
2181 newbone
.parent
= parentbone
2183 ob_new
.animation_data_create()#create animation data
2184 if obj
.animation_data
!= None:#check for animation
2185 ob_new
.animation_data
.action
= obj
.animation_data
.action
#just make sure it here to do the animations if exist
2186 print("Armature Object Name:",ob_new
.name
)
2189 class OBJECT_OT_UTRebuildArmature(bpy
.types
.Operator
):
2190 """If mesh is deform when importing to unreal engine try this. """ \
2191 """It rebuild the bones one at the time by select one armature object scrape to raw setup build. """ \
2192 """Note the scale will be 1:1 for object mode. To keep from deforming"""
2193 bl_idname
= "object.utrebuildarmature" # XXX, name???
2194 bl_label
= "Rebuild Armature" #Rebuild Armature
2196 def invoke(self
, context
, event
):
2197 print("----------------------------------------")
2198 print("Init Rebuild Armature...")
2200 for obj
in bpy
.data
.objects
:
2201 if obj
.type == 'ARMATURE' and obj
.select
== True:
2202 rebuildarmature(obj
)
2203 self
.report({'INFO'}, "Rebuild Armature Finish!")
2204 print("End of Rebuild Armature.")
2205 print("----------------------------------------")
2208 class UDKActionSetListPG(bpy
.types
.PropertyGroup
):
2209 bool = BoolProperty(default
=False)
2210 string
= StringProperty()
2211 actionname
= StringProperty()
2212 bmatch
= BoolProperty(default
=False,name
="Match", options
={"HIDDEN"},description
= "This check against bone names and action group names matches and override boolean if true.")
2213 bexport
= BoolProperty(default
=False,name
="Export",description
= "Check this to export the animation")
2215 bpy
.utils
.register_class(UDKActionSetListPG
)
2216 bpy
.types
.Scene
.udkas_list
= CollectionProperty(type=UDKActionSetListPG
)
2217 bpy
.types
.Scene
.udkas_list_idx
= IntProperty()
2219 class UL_UDKActionSetList(bpy
.types
.UIList
):
2220 def draw_item(self
, context
, layout
, data
, item
, icon
, active_data
, active_propname
, index
):
2221 layout
.label(item
.name
)
2222 layout
.prop(item
, "bmatch", text
="Match")
2223 layout
.prop(item
, "bexport", text
="Export")
2225 class UDKObjListPG(bpy
.types
.PropertyGroup
):
2226 bool = BoolProperty(default
=False)
2227 string
= StringProperty()
2228 bexport
= BoolProperty(default
=False,name
="Export", options
={"HIDDEN"},description
= "This will be ignore when exported")
2229 bselect
= BoolProperty(default
=False,name
="Select", options
={"HIDDEN"},description
= "This will be ignore when exported")
2230 otype
= StringProperty(name
="Type",description
= "This will be ignore when exported")
2232 bpy
.utils
.register_class(UDKObjListPG
)
2233 bpy
.types
.Scene
.udkobj_list
= CollectionProperty(type=UDKObjListPG
)
2234 bpy
.types
.Scene
.udkobj_list_idx
= IntProperty()
2236 class UL_UDKObjList(bpy
.types
.UIList
):
2237 def draw_item(self
, context
, layout
, data
, item
, icon
, active_data
, active_propname
, index
):
2238 layout
.label(item
.name
)
2239 layout
.prop(item
, "otype", text
="")
2240 layout
.prop(item
, "bselect", text
="")
2242 class UDKMeshListPG(bpy
.types
.PropertyGroup
):
2243 bool = BoolProperty(default
=False)
2244 string
= StringProperty()
2245 bexport
= BoolProperty(default
=False,name
="Export", options
={"HIDDEN"},description
= "This object will be export when true.")
2246 bselect
= BoolProperty(default
=False,name
="Select", options
={"HIDDEN"},description
= "Make sure you have Mesh is parent to Armature.")
2247 otype
= StringProperty(name
="Type",description
= "This will be ignore when exported")
2249 bpy
.utils
.register_class(UDKMeshListPG
)
2250 bpy
.types
.Scene
.udkmesh_list
= CollectionProperty(type=UDKMeshListPG
)
2251 bpy
.types
.Scene
.udkmesh_list_idx
= IntProperty()
2253 class UL_UDKMeshList(bpy
.types
.UIList
):
2254 def draw_item(self
, context
, layout
, data
, item
, icon
, active_data
, active_propname
, index
):
2255 layout
.label(item
.name
)
2256 #layout.prop(item, "bselect", text="Select")
2257 layout
.prop(item
, "bexport", text
="Export")
2259 class UDKArmListPG(bpy
.types
.PropertyGroup
):
2260 bool = BoolProperty(default
=False)
2261 string
= StringProperty()
2262 bexport
= BoolProperty(default
=False,name
="Export", options
={"HIDDEN"},description
= "This will be ignore when exported")
2263 bselect
= BoolProperty(default
=False,name
="Select", options
={"HIDDEN"},description
= "This will be ignore when exported")
2264 otype
= StringProperty(name
="Type",description
= "This will be ignore when exported")
2266 bpy
.utils
.register_class(UDKArmListPG
)
2267 bpy
.types
.Scene
.udkArm_list
= CollectionProperty(type=UDKArmListPG
)
2268 bpy
.types
.Scene
.udkArm_list_idx
= IntProperty()
2270 class UL_UDKArmList(bpy
.types
.UIList
):
2271 def draw_item(self
, context
, layout
, data
, item
, icon
, active_data
, active_propname
, index
):
2272 layout
.label(item
.name
)
2274 class Panel_UDKExport( bpy
.types
.Panel
):
2276 bl_label
= "UDK Export"
2277 bl_idname
= "OBJECT_PT_udk_tools"
2278 #bl_space_type = "PROPERTIES"
2279 #bl_region_type = "WINDOW"
2280 #bl_context = "object"
2281 bl_space_type
= "VIEW_3D"
2282 bl_region_type
= "TOOLS"
2284 #def draw_header(self, context):
2285 # layout = self.layout
2286 #obj = context.object
2287 #layout.prop(obj, "select", text="")
2290 #def poll(cls, context):
2291 # return context.active_object
2293 def draw(self
, context
):
2294 layout
= self
.layout
2295 path
= get_dst_path()
2299 # object_name = context.object.name
2300 if context
.active_object
:
2301 object_name
= context
.active_object
.name
2302 row10
= layout
.row()
2303 row10
.prop(context
.scene
, "udk_option_smoothing_groups")
2304 row10
.prop(context
.scene
, "udk_option_clamp_uv")
2305 row10
.prop(context
.scene
, "udk_option_verbose")
2307 row
.label(text
="Active object: " + object_name
)
2309 layout
.prop(context
.scene
, "udk_option_filename_src")
2311 row
.label(text
=path
)
2313 layout
.prop(context
.scene
, "udk_option_export")
2314 layout
.prop(context
.scene
, "udk_option_selectobjects")
2316 if context
.scene
.udk_option_selectobjects
:
2317 layout
.operator("object.selobjectpdate")
2318 layout
.label(text
="ARMATURE - Index")
2319 layout
.template_list("UL_UDKArmList", "udk_armatures", context
.scene
, "udkArm_list",
2320 context
.scene
, "udkArm_list_idx", rows
=3)
2321 layout
.label(text
="MESH - Export")
2322 layout
.template_list("UL_UDKMeshList", "", context
.scene
, "udkmesh_list",
2323 context
.scene
, "udkmesh_list_idx", rows
=5)
2324 layout
.prop(context
.scene
, "udk_option_selectanimations")
2325 if context
.scene
.udk_option_selectanimations
:
2326 layout
.operator("action.setanimupdate")
2327 layout
.label(text
="Action Set(s) - Match / Export")
2328 layout
.template_list("UL_UDKActionSetList", "", context
.scene
, "udkas_list",
2329 context
.scene
, "udkas_list_idx", rows
=5)
2330 test
= layout
.separator()
2331 layout
.prop(context
.scene
, "udk_option_scale")
2332 layout
.prop(context
.scene
, "udk_option_rebuildobjects")
2333 #layout.prop(context.scene, "udk_option_ignoreactiongroupnames")
2334 row11
= layout
.row()
2335 row11
.operator("object.udk_export")
2336 row11
.operator("object.toggle_console")
2337 layout
.operator(OBJECT_OT_UTRebuildArmature
.bl_idname
)
2338 layout
.label(text
="Mesh")
2339 layout
.operator(OBJECT_OT_MeshClearWeights
.bl_idname
)
2340 layout
.operator(OBJECT_OT_UTSelectedFaceSmooth
.bl_idname
)
2341 layout
.operator(OBJECT_OT_UTRebuildMesh
.bl_idname
)
2342 layout
.operator(OBJECT_OT_UDKCheckMeshLines
.bl_idname
)
2344 def udkupdateobjects():
2345 my_objlist
= bpy
.context
.scene
.udkArm_list
2347 for objarm
in bpy
.context
.scene
.objects
:#list and filter only mesh and armature
2348 if objarm
.type == 'ARMATURE':
2349 objectl
.append(objarm
)
2350 for _objd
in objectl
:#check if list has in udk list
2352 for _obj
in my_objlist
:
2353 if _obj
.name
== _objd
.name
and _obj
.otype
== _objd
.type:
2354 _obj
.bselect
= _objd
.select
2357 if bfound_obj
== False:
2358 #print("ADD ARMATURE...")
2359 my_item
= my_objlist
.add()
2360 my_item
.name
= _objd
.name
2361 my_item
.bselect
= _objd
.select
2362 my_item
.otype
= _objd
.type
2364 for _udkobj
in my_objlist
:
2366 for _objd
in bpy
.context
.scene
.objects
: #check if there no existing object from sense to remove it
2367 if _udkobj
.name
== _objd
.name
and _udkobj
.otype
== _objd
.type:
2370 if bfound_objv
== False:
2371 removeobject
.append(_udkobj
)
2372 #print("remove check...")
2373 for _item
in removeobject
: #loop remove object from udk list object
2375 for _obj
in my_objlist
:
2376 if _obj
.name
== _item
.name
and _obj
.otype
== _item
.otype
:
2377 my_objlist
.remove(count
)
2381 my_objlist
= bpy
.context
.scene
.udkmesh_list
2383 for objarm
in bpy
.context
.scene
.objects
:#list and filter only mesh and armature
2384 if objarm
.type == 'MESH':
2385 objectl
.append(objarm
)
2386 for _objd
in objectl
:#check if list has in udk list
2388 for _obj
in my_objlist
:
2389 if _obj
.name
== _objd
.name
and _obj
.otype
== _objd
.type:
2390 _obj
.bselect
= _objd
.select
2393 if bfound_obj
== False:
2394 my_item
= my_objlist
.add()
2395 my_item
.name
= _objd
.name
2396 my_item
.bselect
= _objd
.select
2397 my_item
.otype
= _objd
.type
2399 for _udkobj
in my_objlist
:
2401 for _objd
in bpy
.context
.scene
.objects
: #check if there no existing object from sense to remove it
2402 if _udkobj
.name
== _objd
.name
and _udkobj
.otype
== _objd
.type:
2405 if bfound_objv
== False:
2406 removeobject
.append(_udkobj
)
2407 #print("remove check...")
2408 for _item
in removeobject
: #loop remove object from udk list object
2410 for _obj
in my_objlist
:
2411 if _obj
.name
== _item
.name
and _obj
.otype
== _item
.otype
:
2412 my_objlist
.remove(count
)
2416 class OBJECT_OT_UDKObjUpdate(bpy
.types
.Operator
):
2417 """This will update the filter of the mesh and armature."""
2418 bl_idname
= "object.selobjectpdate"
2419 bl_label
= "Update Object(s)"
2421 actionname
= bpy
.props
.StringProperty()
2423 def execute(self
, context
):
2427 def udkcheckmeshline():
2429 for obj
in bpy
.context
.scene
.objects
:
2430 if obj
.type == 'MESH' and obj
.select
== True:
2432 objmesh
= triangulate_mesh(objmesh
) #create a copy of the mesh
2433 bpy
.ops
.object.mode_set(mode
='OBJECT')
2434 for i
in bpy
.context
.scene
.objects
: i
.select
= False # deselect all objects
2435 objmesh
.select
= True
2436 bpy
.context
.scene
.objects
.active
= objmesh
#set active mesh
2439 bpy
.ops
.object.mode_set(mode
='EDIT') #set in edit mode
2440 bpy
.ops
.mesh
.select_all(action
='DESELECT')
2441 bpy
.context
.tool_settings
.mesh_select_mode
= (True, False, False) #select vertices
2446 print(objmesh
.data
.tessfaces
)
2448 for face
in objmesh
.data
.tessfaces
:
2452 vert_index
= face
.vertices
[i
]
2453 vert
= objmesh
.data
.vertices
[vert_index
]
2454 vect_list
.append( FVector(vert
.co
.x
, vert
.co
.y
, vert
.co
.z
) )
2455 vpos
= objmesh
.matrix_local
* vert
.co
2461 w
.PointIndex
= points
.get(p
) # store keys
2462 index_wedge
= wedges
.get(w
)
2463 wedge_list
.append(index_wedge
)
2465 norm
= FVector(no
[0], no
[1], no
[2])
2466 tnorm
= vect_list
[1].sub(vect_list
[0]).cross(vect_list
[2].sub(vect_list
[1]))
2467 dot
= norm
.dot(tnorm
)
2471 (tri
.WedgeIndex2
, tri
.WedgeIndex1
, tri
.WedgeIndex0
) = wedge_list
2473 (tri
.WedgeIndex0
, tri
.WedgeIndex1
, tri
.WedgeIndex2
) = wedge_list
2475 dindex0
= face
.vertices
[0];
2476 dindex1
= face
.vertices
[1];
2477 dindex2
= face
.vertices
[2];
2478 vertex_list
.append(dindex0
)
2479 vertex_list
.append(dindex1
)
2480 vertex_list
.append(dindex2
)
2482 bpy
.ops
.object.mode_set(mode
='OBJECT')
2483 for vertex
in objmesh
.data
.vertices
: #loop all vertex in the mesh list
2484 for vl
in vertex_list
: #loop for error vertex
2485 if vertex
.index
== vl
: #if match set to select
2486 vertex
.select
= True
2488 bpy
.ops
.object.mode_set(mode
='EDIT') #set in edit mode to see the select vertex
2489 objmesh
.data
.update() # update object
2490 bpy
.context
.scene
.update() #update scene
2491 message
= "MESH PASS"
2492 if len(vertex_list
) > 0:
2493 message
= "MESH FAIL"
2496 class OBJECT_OT_UDKCheckMeshLines(bpy
.types
.Operator
):
2497 """Select the mesh for export test. This will create dummy mesh to see which area are broken. """ \
2498 """If the vertices share the same position it will causes an bug."""
2499 bl_idname
= "object.udkcheckmeshline"
2500 bl_label
= "Check Mesh Vertices"
2502 def execute(self
, context
):
2503 message
= udkcheckmeshline()
2504 self
.report({'ERROR'}, message
)
2507 class OBJECT_OT_ActionSetAnimUpdate(bpy
.types
.Operator
):
2508 """Select Armture to match the action set groups. """ \
2509 """All bones keys must be set to match with number of bones."""
2510 bl_idname
= "action.setanimupdate"
2511 bl_label
= "Update Action Set(s)"
2513 actionname
= bpy
.props
.StringProperty()
2515 def execute(self
, context
):
2516 my_sett
= bpy
.context
.scene
.udkas_list
2521 armatureselected
= []
2522 for objarm
in bpy
.context
.scene
.objects
:
2523 if objarm
.type == 'ARMATURE':
2524 #print("ADDED ARMATURE...")
2525 armatures
.append(objarm
)
2526 if objarm
.select
== True:
2527 armatureselected
.append(objarm
)
2529 if len(armatureselected
) == len(armatures
) == 1:
2530 armature
= armatures
[0]
2531 if len(armatures
) == 1:
2532 armature
= armatures
[0]
2533 if len(armatureselected
) == 1:
2534 armature
= armatureselected
[0]
2536 if armature
!= None:
2537 for bone
in armature
.pose
.bones
:
2538 bones
.append(bone
.name
)
2540 for action
in bpy
.data
.actions
:#action list
2543 for actionbone
in action
.groups
:
2544 #print("Pose bone name: ",actionbone.name)
2546 if b
== actionbone
.name
:
2548 #print(b," : ",actionbone.name)
2550 for actionlist
in my_sett
:
2551 if action
.name
== actionlist
.name
:
2553 if len(bones
) == len(action
.groups
) == count
:
2554 actionlist
.bmatch
= True
2556 actionlist
.bmatch
= False
2560 my_item
= my_sett
.add()
2561 #print(dir(my_item.bmatch))
2562 my_item
.name
= action
.name
2563 #my_item.template_list_controls = "bmatch:bexport"
2564 if len(bones
) == len(action
.groups
) == count
:
2565 my_item
.bmatch
= True
2567 my_item
.bmatch
= False
2569 #check action list and data actions
2570 for actionlist
in bpy
.context
.scene
.udkas_list
:
2573 for act
in bpy
.data
.actions
:
2574 if actionlist
.name
== act
.name
:
2578 #print("ACT NAME:",actionlist.name," COUNT",notfound)
2579 if notfound
== len(bpy
.data
.actions
):
2580 #print("remove :",actionlist.name)
2581 removeactions
.append(actionlist
.name
)
2582 #print("Not in the action data list:",len(removeactions))
2583 #remove list or chnages in the name the template list
2584 for actname
in removeactions
:
2586 for actionlist
in my_sett
:
2587 #print("action name:",actionlist.name)
2588 if actionlist
.name
== actname
:
2589 my_sett
.remove(actioncount
);
2594 class ExportUDKAnimData(bpy
.types
.Operator
):
2595 """Export Skeleton Mesh / Animation Data file(s). """ \
2596 """One mesh and one armature else select one mesh or armature to be exported"""
2597 bl_idname
= "export_anim.udk" # this is important since its how bpy.ops.export.udk_anim_data is constructed
2598 bl_label
= "Export PSK/PSA"
2600 # List of operator properties, the attributes will be assigned
2601 # to the class instance from the operator settings before calling.
2603 filepath
= StringProperty(
2604 subtype
='FILE_PATH',
2606 filter_glob
= StringProperty(
2607 default
="*.psk;*.psa",
2610 udk_option_smoothing_groups
= bpy
.types
.Scene
.udk_option_smoothing_groups
2611 udk_option_clamp_uv
= bpy
.types
.Scene
.udk_option_clamp_uv
2612 udk_option_verbose
= bpy
.types
.Scene
.udk_option_verbose
2613 udk_option_filename_src
= bpy
.types
.Scene
.udk_option_filename_src
2614 udk_option_export
= bpy
.types
.Scene
.udk_option_export
2615 udk_option_scale
= bpy
.types
.Scene
.udk_option_scale
2616 udk_option_rebuildobjects
= bpy
.types
.Scene
.udk_option_rebuildobjects
2619 def poll(cls
, context
):
2620 return context
.active_object
!= None
2622 def execute(self
, context
):
2623 scene
= bpy
.context
.scene
2624 scene
.udk_option_export_psk
= (scene
.udk_option_export
== '0' or scene
.udk_option_export
== '2')
2625 scene
.udk_option_export_psa
= (scene
.udk_option_export
== '1' or scene
.udk_option_export
== '2')
2626 bpy
.context
.scene
.udk_option_scale
= self
.udk_option_scale
2627 bpy
.context
.scene
.udk_option_rebuildobjects
= self
.udk_option_rebuildobjects
2629 filepath
= get_dst_path()
2632 restore_frame
= scene
.frame_current
2634 message
= "Finish Export!"
2638 except Error
as err
:
2640 message
= err
.message
2643 scene
.frame_set(restore_frame
)
2645 self
.report({'WARNING', 'INFO'}, message
)
2648 def invoke(self
, context
, event
):
2649 wm
= context
.window_manager
2650 wm
.fileselect_add(self
)
2651 return {'RUNNING_MODAL'}
2653 def menu_func(self
, context
):
2654 default_path
= os
.path
.splitext(bpy
.data
.filepath
)[0] + ".psk"
2655 self
.layout
.operator(ExportUDKAnimData
.bl_idname
, text
="Skeleton Mesh / Animation Data (.psk/.psa)").filepath
= default_path
2657 #===========================================================================
2659 #===========================================================================
2662 bpy
.utils
.register_module(__name__
)
2663 bpy
.types
.INFO_MT_file_export
.append(menu_func
)
2666 #print("UNREGISTER")
2667 bpy
.utils
.unregister_module(__name__
)
2668 bpy
.types
.INFO_MT_file_export
.remove(menu_func
)
2670 if __name__
== "__main__":
2672 print(header("UDK Export PSK/PSA 2.6", 'CENTER'))
2676 #filename = "D:/Projects/BlenderScripts/io_export_udk_psa_psk_alpha.py"
2677 #exec(compile(open(filename).read(), filename, 'exec'))