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": "https://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.
51 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 # ===========================================================================
149 from bpy
.props
import (
157 from bpy
.types
import (
164 from struct
import pack
167 # REFERENCE MATERIAL JUST IN CASE:
169 # U = x / sqrt(x^2 + y^2 + z^2)
170 # V = y / sqrt(x^2 + y^2 + z^2)
172 # Triangles specifed counter clockwise for front face
174 # defines for sizeofs
178 SIZE_ANIMINFOBINARY
= 168
179 SIZE_VCHUNKHEADER
= 32
182 SIZE_FNAMEDBONEBINARY
= 120
183 SIZE_VRAWBONEINFLUENCE
= 12
184 SIZE_VQUATANIMKEY
= 32
192 # Python 3 doesn't have cmp anymore
194 return (a
> b
) - (a
< b
)
197 # ===========================================================================
198 # Custom exception class
199 # ===========================================================================
200 class Error(Exception):
202 def __init__(self
, message
):
203 self
.message
= message
206 # ===========================================================================
207 # Verbose logging with loop truncation
208 # ===========================================================================
209 def verbose(msg
, iteration
=-1, max_iterations
=4, msg_truncated
="..."):
211 if bpy
.context
.scene
.udk_option_verbose
is True:
212 # limit the number of times a loop can output messages
213 if iteration
> max_iterations
:
215 elif iteration
== max_iterations
:
222 # ===========================================================================
223 # Log header/separator
224 # ===========================================================================
225 def header(msg
, justify
='LEFT', spacer
='_', cols
=78):
227 if justify
== 'LEFT':
228 s
= '{:{spacer}<{cols}}'.format(msg
+ " ", spacer
=spacer
, cols
=cols
)
230 elif justify
== 'RIGHT':
231 s
= '{:{spacer}>{cols}}'.format(" " + msg
, spacer
=spacer
, cols
=cols
)
234 s
= '{:{spacer}^{cols}}'.format(" " + msg
+ " ", spacer
=spacer
, cols
=cols
)
236 return "\n" + s
+ "\n"
239 # ===========================================================================
240 # Generic Object->Integer mapping
241 # the object must be usable as a dictionary key
242 # ===========================================================================
251 return self
.dict[obj
]
254 self
.next
= self
.next
+ 1
259 getval
= operator
.itemgetter(0)
260 getkey
= operator
.itemgetter(1)
261 return map(getval
, sorted(self
.dict.items(), key
=getkey
))
264 # ===========================================================================
265 # RG - UNREAL DATA STRUCTS - CONVERTED FROM C STRUCTS GIVEN ON UDN SITE
266 # provided here: http://udn.epicgames.com/Two/BinaryFormatSpecifications.html
267 # updated UDK (Unreal Engine 3): http://udn.epicgames.com/Three/BinaryFormatSpecifications.html
268 # ===========================================================================
278 return pack('ffff', self
.X
, self
.Y
, self
.Z
, self
.W
)
280 def __cmp__(self
, other
):
281 return cmp(self
.X
, other
.X
) or \
282 cmp(self
.Y
, other
.Y
) or \
283 cmp(self
.Z
, other
.Z
) or \
287 return hash(self
.X
) ^
hash(self
.Y
) ^
hash(self
.Z
) ^
hash(self
.W
)
290 return "[%f,%f,%f,%f](FQuat)" % (self
.X
, self
.Y
, self
.Z
, self
.W
)
293 class FVector(object):
295 def __init__(self
, X
=0.0, Y
=0.0, Z
=0.0):
301 return pack('fff', self
.X
, self
.Y
, self
.Z
)
303 def __cmp__(self
, other
):
304 return cmp(self
.X
, other
.X
) or \
305 cmp(self
.Y
, other
.Y
) or \
309 return (type(self
).__name
__, self
.X
, self
.Y
, self
.Z
)
312 return hash(self
._key
())
314 def __eq__(self
, other
):
315 if not hasattr(other
, '_key'):
317 return self
._key
() == other
._key
()
319 def dot(self
, other
):
320 return self
.X
* other
.X
+ self
.Y
* other
.Y
+ self
.Z
* other
.Z
322 def cross(self
, other
):
323 return FVector(self
.Y
* other
.Z
- self
.Z
* other
.Y
,
324 self
.Z
* other
.X
- self
.X
* other
.Z
,
325 self
.X
* other
.Y
- self
.Y
* other
.X
)
327 def sub(self
, other
):
328 return FVector(self
.X
- other
.X
,
336 self
.Orientation
= FQuat()
337 self
.Position
= FVector()
344 return self
.Orientation
.dump() + self
.Position
.dump() + \
345 pack('4f', self
.Length
, self
.XSize
, self
.YSize
, self
.ZSize
)
348 class AnimInfoBinary
:
351 self
.Name
= "" # length=64
352 self
.Group
= "" # length=64
355 self
.KeyCompressionStyle
= 0
357 self
.KeyPrediction
= 0.0
361 self
.FirstRawFrame
= 0
362 self
.NumRawFrames
= 0
365 return pack('64s64siiiifffiii', str.encode(self
.Name
), str.encode(self
.Group
),
366 self
.TotalBones
, self
.RootInclude
, self
.KeyCompressionStyle
, self
.KeyQuotum
,
367 self
.KeyPrediction
, self
.TrackTime
, self
.AnimRate
, self
.StartBone
,
368 self
.FirstRawFrame
, self
.NumRawFrames
)
373 def __init__(self
, name
, type_size
):
374 self
.ChunkID
= str.encode(name
) # length=20
375 self
.TypeFlag
= 1999801 # special value
376 self
.DataSize
= type_size
380 return pack('20siii', self
.ChunkID
, self
.TypeFlag
, self
.DataSize
, self
.DataCount
)
386 self
.MaterialName
= "" # length=64
387 self
.TextureIndex
= 0
388 self
.PolyFlags
= 0 # DWORD
390 self
.AuxFlags
= 0 # DWORD
395 # print("DATA MATERIAL:",self.MaterialName)
396 return pack('64siLiLii', str.encode(self
.MaterialName
), self
.TextureIndex
,
397 self
.PolyFlags
, self
.AuxMaterial
, self
.AuxFlags
, self
.LodBias
, self
.LodStyle
)
403 self
.Name
= "" # length = 64
404 self
.Flags
= 0 # DWORD
407 self
.BonePos
= VJointPos()
410 return pack('64sLii', str.encode(self
.Name
), self
.Flags
,
411 self
.NumChildren
, self
.ParentIndex
) + self
.BonePos
.dump()
414 # same as above - whatever - this is how Epic does it...
415 class FNamedBoneBinary
:
418 self
.Name
= "" # length = 64
419 self
.Flags
= 0 # DWORD
422 self
.BonePos
= VJointPos()
423 self
.IsRealBone
= 0 # this is set to 1 when the bone is actually a bone in the mesh and not a dummy
426 return pack('64sLii', str.encode(self
.Name
), self
.Flags
,
427 self
.NumChildren
, self
.ParentIndex
) + self
.BonePos
.dump()
430 class VRawBoneInfluence
:
438 return pack('fii', self
.Weight
, self
.PointIndex
, self
.BoneIndex
)
444 self
.Position
= FVector()
445 self
.Orientation
= FQuat()
449 return self
.Position
.dump() + self
.Orientation
.dump() + pack('f', self
.Time
)
452 class VVertex(object):
455 self
.PointIndex
= 0 # WORD
458 self
.MatIndex
= 0 # BYTE
459 self
.Reserved
= 0 # BYTE
463 return pack('HHffBBH', self
.PointIndex
, 0, self
.U
, self
.V
, self
.MatIndex
, self
.Reserved
, 0)
465 def __cmp__(self
, other
):
466 return cmp(self
.PointIndex
, other
.PointIndex
) or\
467 cmp(self
.U
, other
.U
) or \
468 cmp(self
.V
, other
.V
) or \
469 cmp(self
.MatIndex
, other
.MatIndex
) or \
470 cmp(self
.Reserved
, other
.Reserved
) or \
471 cmp(self
.SmoothGroup
, other
.SmoothGroup
)
474 return (type(self
).__name
__, self
.PointIndex
, self
.U
, self
.V
, self
.MatIndex
, self
.Reserved
)
477 return hash(self
._key
())
479 def __eq__(self
, other
):
480 if not hasattr(other
, '_key'):
482 return self
._key
() == other
._key
()
488 self
.Point
= FVector()
490 def __cmp__(self
, other
):
491 return cmp(self
.Point
, other
.Point
)
494 return hash(self
._key
())
497 return (type(self
).__name
__, self
.Point
)
499 def __eq__(self
, other
):
500 if not hasattr(other
, '_key'):
502 return self
._key
() == other
._key
()
505 class VPoint(object):
508 self
.Point
= FVector()
512 return self
.Point
.dump()
514 def __cmp__(self
, other
):
515 return cmp(self
.Point
, other
.Point
) \
516 or cmp(self
.SmoothGroup
, other
.SmoothGroup
)
519 return (type(self
).__name
__, self
.Point
, self
.SmoothGroup
)
522 return hash(self
._key
()) \
523 ^
hash(self
.SmoothGroup
)
525 def __eq__(self
, other
):
526 if not hasattr(other
, '_key'):
528 return self
._key
() == other
._key
()
534 self
.WedgeIndex0
= 0 # WORD
535 self
.WedgeIndex1
= 0 # WORD
536 self
.WedgeIndex2
= 0 # WORD
537 self
.MatIndex
= 0 # BYTE
538 self
.AuxMatIndex
= 0 # BYTE
539 self
.SmoothingGroups
= 0 # DWORD
542 return pack('HHHBBL', self
.WedgeIndex0
, self
.WedgeIndex1
, self
.WedgeIndex2
,
543 self
.MatIndex
, self
.AuxMatIndex
, self
.SmoothingGroups
)
545 print("smooth",self.SmoothingGroups)
546 return pack('HHHBBI', self.WedgeIndex0, self.WedgeIndex1, self.WedgeIndex2,
547 self.MatIndex, self.AuxMatIndex, self.SmoothingGroups)
549 # END UNREAL DATA STRUCTS
550 # ===========================================================================
553 # ===========================================================================
554 # RG - helper class to handle the normal way the UT files are stored
555 # as sections consisting of a header and then a list of data structures
556 # ===========================================================================
559 def __init__(self
, name
, type_size
):
560 self
.Header
= VChunkHeader(name
, type_size
)
561 self
.Data
= [] # list of datatypes
564 data
= self
.Header
.dump()
565 for i
in range(len(self
.Data
)):
566 data
= data
+ self
.Data
[i
].dump()
569 def UpdateHeader(self
):
570 self
.Header
.DataCount
= len(self
.Data
)
573 # ===========================================================================
575 # ===========================================================================
579 self
.GeneralHeader
= VChunkHeader("ACTRHEAD", 0)
580 self
.Points
= FileSection("PNTS0000", SIZE_VPOINT
) # VPoint
581 self
.Wedges
= FileSection("VTXW0000", SIZE_VVERTEX
) # VVertex
582 self
.Faces
= FileSection("FACE0000", SIZE_VTRIANGLE
) # VTriangle
583 self
.Materials
= FileSection("MATT0000", SIZE_VMATERIAL
) # VMaterial
584 self
.Bones
= FileSection("REFSKELT", SIZE_VBONE
) # VBone
585 self
.Influences
= FileSection("RAWWEIGHTS", SIZE_VRAWBONEINFLUENCE
) # VRawBoneInfluence
587 # RG - this mapping is not dumped, but is used internally to store the new point indices
588 # for vertex groups calculated during the mesh dump, so they can be used again
589 # to dump bone influences during the armature dump
591 # the key in this dictionary is the VertexGroup/Bone Name, and the value
592 # is a list of tuples containing the new point index and the weight, in that order
595 # { groupname : [ (index, weight), ... ], ... }
598 # {'MyVertexGroup' : [ (0, 1.0), (5, 1.0), (3, 0.5) ] , 'OtherGroup' : [(2, 1.0)]}
600 self
.VertexGroups
= {}
602 def AddPoint(self
, p
):
603 self
.Points
.Data
.append(p
)
605 def AddWedge(self
, w
):
606 self
.Wedges
.Data
.append(w
)
608 def AddFace(self
, f
):
609 self
.Faces
.Data
.append(f
)
611 def AddMaterial(self
, m
):
612 self
.Materials
.Data
.append(m
)
614 def AddBone(self
, b
):
615 self
.Bones
.Data
.append(b
)
617 def AddInfluence(self
, i
):
618 self
.Influences
.Data
.append(i
)
620 def UpdateHeaders(self
):
621 self
.Points
.UpdateHeader()
622 self
.Wedges
.UpdateHeader()
623 self
.Faces
.UpdateHeader()
624 self
.Materials
.UpdateHeader()
625 self
.Bones
.UpdateHeader()
626 self
.Influences
.UpdateHeader()
630 data
= self
.GeneralHeader
.dump() + self
.Points
.dump() + self
.Wedges
.dump() + \
631 self
.Faces
.dump() + self
.Materials
.dump() + self
.Bones
.dump() + self
.Influences
.dump()
634 def GetMatByIndex(self
, mat_index
):
635 if mat_index
>= 0 and len(self
.Materials
.Data
) > mat_index
:
636 return self
.Materials
.Data
[mat_index
]
639 # modified by VendorX
640 m
.MaterialName
= MaterialName
[mat_index
]
645 print("{:>16} {:}".format("Points", len(self
.Points
.Data
)))
646 print("{:>16} {:}".format("Wedges", len(self
.Wedges
.Data
)))
647 print("{:>16} {:}".format("Faces", len(self
.Faces
.Data
)))
648 print("{:>16} {:}".format("Materials", len(self
.Materials
.Data
)))
649 print("{:>16} {:}".format("Bones", len(self
.Bones
.Data
)))
650 print("{:>16} {:}".format("Influences", len(self
.Influences
.Data
)))
653 # ===========================================================================
657 # The raw key array holds all the keys for all the bones in all the specified sequences,
658 # organized as follows:
659 # For each AnimInfoBinary's sequence there are [Number of bones] times [Number of frames keys]
660 # in the VQuatAnimKeys, laid out as tracks of [numframes] keys for each bone in the order of
661 # the bones as defined in the array of FnamedBoneBinary in the PSA.
663 # Once the data from the PSK (now digested into native skeletal mesh) and PSA (digested into
664 # a native animation object containing one or more sequences) are associated together at runtime,
665 # bones are linked up by name. Any bone in a skeleton (from the PSK) that finds no partner in
666 # the animation sequence (from the PSA) will assume its reference pose stance ( as defined in
667 # the offsets & rotations that are in the VBones making up the reference skeleton from the PSK)
668 # ===========================================================================
672 self
.GeneralHeader
= VChunkHeader("ANIMHEAD", 0)
673 self
.Bones
= FileSection("BONENAMES", SIZE_FNAMEDBONEBINARY
) # FNamedBoneBinary
674 self
.Animations
= FileSection("ANIMINFO", SIZE_ANIMINFOBINARY
) # AnimInfoBinary
675 self
.RawKeys
= FileSection("ANIMKEYS", SIZE_VQUATANIMKEY
) # VQuatAnimKey
676 # this will take the format of key=Bone Name, value = (BoneIndex, Bone Object)
680 def AddBone(self
, b
):
681 self
.Bones
.Data
.append(b
)
683 def AddAnimation(self
, a
):
684 self
.Animations
.Data
.append(a
)
686 def AddRawKey(self
, k
):
687 self
.RawKeys
.Data
.append(k
)
689 def UpdateHeaders(self
):
690 self
.Bones
.UpdateHeader()
691 self
.Animations
.UpdateHeader()
692 self
.RawKeys
.UpdateHeader()
694 def GetBoneByIndex(self
, bone_index
):
695 if bone_index
>= 0 and len(self
.Bones
.Data
) > bone_index
:
696 return self
.Bones
.Data
[bone_index
]
699 return (len(self
.Bones
.Data
) == 0 or len(self
.Animations
.Data
) == 0)
701 def StoreBone(self
, b
):
702 self
.BoneLookup
[b
.Name
] = [-1, b
]
704 def UseBone(self
, bone_name
):
705 if bone_name
in self
.BoneLookup
:
706 bone_data
= self
.BoneLookup
[bone_name
]
708 if bone_data
[0] == -1:
709 bone_data
[0] = len(self
.Bones
.Data
)
710 self
.AddBone(bone_data
[1])
711 # self.Bones.Data.append(bone_data[1])
715 def GetBoneByName(self
, bone_name
):
716 if bone_name
in self
.BoneLookup
:
717 bone_data
= self
.BoneLookup
[bone_name
]
720 def GetBoneIndex(self
, bone_name
):
721 if bone_name
in self
.BoneLookup
:
722 bone_data
= self
.BoneLookup
[bone_name
]
727 return self
.GeneralHeader
.dump() + self
.Bones
.dump() + self
.Animations
.dump() + self
.RawKeys
.dump()
730 print("{:>16} {:}".format("Bones", len(self
.Bones
.Data
)))
731 print("{:>16} {:}".format("Animations", len(self
.Animations
.Data
)))
732 print("{:>16} {:}".format("Raw keys", len(self
.RawKeys
.Data
)))
735 # ===========================================================================
736 # Helpers to create bone structs
737 # ===========================================================================
738 def make_vbone(name
, parent_index
, child_count
, orientation_quat
, position_vect
):
741 bone
.ParentIndex
= parent_index
742 bone
.NumChildren
= child_count
743 bone
.BonePos
.Orientation
= orientation_quat
744 bone
.BonePos
.Position
.X
= position_vect
.x
745 bone
.BonePos
.Position
.Y
= position_vect
.y
746 bone
.BonePos
.Position
.Z
= position_vect
.z
747 # these values seem to be ignored?
748 # bone.BonePos.Length = tail.length
749 # bone.BonePos.XSize = tail.x
750 # bone.BonePos.YSize = tail.y
751 # bone.BonePos.ZSize = tail.z
755 def make_namedbonebinary(name
, parent_index
, child_count
, orientation_quat
, position_vect
, is_real
):
756 bone
= FNamedBoneBinary()
758 bone
.ParentIndex
= parent_index
759 bone
.NumChildren
= child_count
760 bone
.BonePos
.Orientation
= orientation_quat
761 bone
.BonePos
.Position
.X
= position_vect
.x
762 bone
.BonePos
.Position
.Y
= position_vect
.y
763 bone
.BonePos
.Position
.Z
= position_vect
.z
764 bone
.IsRealBone
= is_real
768 def make_fquat(bquat
):
770 # flip handedness for UT = set x,y,z to negative (rotate in other direction)
779 def make_fquat_default(bquat
):
790 # ===========================================================================
791 # RG - check to make sure face isnt a line
792 # ===========================================================================
793 def is_1d_face(face
, mesh
):
794 # ID Vertex of id point
795 v0
= face
.vertices
[0]
796 v1
= face
.vertices
[1]
797 v2
= face
.vertices
[2]
799 return (mesh
.vertices
[v0
].co
== mesh
.vertices
[v1
].co
or
800 mesh
.vertices
[v1
].co
== mesh
.vertices
[v2
].co
or
801 mesh
.vertices
[v2
].co
== mesh
.vertices
[v0
].co
)
805 # ===========================================================================
807 # (renamed to seperate it from VVertex.SmoothGroup)
808 # ===========================================================================
809 class SmoothingGroup
:
815 self
.neighboring_faces
= []
816 self
.neighboring_groups
= []
818 self
.local_id
= SmoothingGroup
.static_id
819 SmoothingGroup
.static_id
+= 1
821 def __cmp__(self
, other
):
822 if isinstance(other
, SmoothingGroup
):
823 return cmp(self
.local_id
, other
.local_id
)
827 return hash(self
.local_id
)
829 # searches neighboring faces to determine which smoothing group ID can be used
830 def get_valid_smoothgroup_id(self
):
832 for group
in self
.neighboring_groups
:
833 if group
is not None and group
.id == temp_id
:
834 if temp_id
< 0x80000000:
835 temp_id
= temp_id
<< 1
837 raise Error("Smoothing Group ID Overflowed, "
838 "Smoothing Group evidently has more than 31 neighboring groups")
843 def make_neighbor(self
, new_neighbor
):
844 if new_neighbor
not in self
.neighboring_groups
:
845 self
.neighboring_groups
.append(new_neighbor
)
847 def contains_face(self
, face
):
848 return (face
in self
.faces
)
850 def add_neighbor_face(self
, face
):
851 if face
not in self
.neighboring_faces
:
852 self
.neighboring_faces
.append(face
)
854 def add_face(self
, face
):
855 if face
not in self
.faces
:
856 self
.faces
.append(face
)
859 def determine_edge_sharing(mesh
):
861 edge_sharing_list
= dict()
863 for edge
in mesh
.edges
:
864 edge_sharing_list
[edge
.key
] = []
866 for face
in mesh
.tessfaces
:
867 for key
in face
.edge_keys
:
868 if face
not in edge_sharing_list
[key
]:
869 edge_sharing_list
[key
].append(face
) # mark this face as sharing this edge
871 return edge_sharing_list
874 def find_edges(mesh
, key
):
875 """ Temp replacement for mesh.findEdges().
876 This is painfully slow.
878 for edge
in mesh
.edges
:
880 if key
[0] == v
[0] and key
[1] == v
[1]:
884 def add_face_to_smoothgroup(mesh
, face
, edge_sharing_list
, smoothgroup
):
886 if face
in smoothgroup
.faces
:
889 smoothgroup
.add_face(face
)
891 for key
in face
.edge_keys
:
893 edge_id
= find_edges(mesh
, key
)
895 if edge_id
is not None:
898 if not (mesh
.edges
[edge_id
].use_edge_sharp
):
900 for shared_face
in edge_sharing_list
[key
]:
901 if shared_face
!= face
:
903 add_face_to_smoothgroup(mesh
, shared_face
, edge_sharing_list
, smoothgroup
)
906 for shared_face
in edge_sharing_list
[key
]:
907 if shared_face
!= face
:
908 smoothgroup
.add_neighbor_face(shared_face
)
911 def determine_smoothgroup_for_face(mesh
, face
, edge_sharing_list
, smoothgroup_list
):
913 for group
in smoothgroup_list
:
914 if (face
in group
.faces
):
917 smoothgroup
= SmoothingGroup()
918 add_face_to_smoothgroup(mesh
, face
, edge_sharing_list
, smoothgroup
)
920 if smoothgroup
not in smoothgroup_list
:
921 smoothgroup_list
.append(smoothgroup
)
924 def build_neighbors_tree(smoothgroup_list
):
926 for group
in smoothgroup_list
:
927 for face
in group
.neighboring_faces
:
928 for neighbor_group
in smoothgroup_list
:
929 if neighbor_group
.contains_face(face
) and neighbor_group
not in group
.neighboring_groups
:
930 group
.make_neighbor(neighbor_group
)
931 neighbor_group
.make_neighbor(group
)
934 # ===========================================================================
935 # parse_smooth_groups
936 # ===========================================================================
937 def parse_smooth_groups(mesh
):
939 print("Parsing smooth groups...")
942 smoothgroup_list
= []
943 edge_sharing_list
= determine_edge_sharing(mesh
)
945 # print("faces:",len(mesh.tessfaces))
946 interval
= math
.floor(len(mesh
.tessfaces
) / 100)
948 if interval
== 0: # if the faces are few do this
949 interval
= math
.floor(len(mesh
.tessfaces
) / 10)
950 # print("FACES:",len(mesh.tessfaces),"//100 =" "interval:",interval)
952 for face
in mesh
.tessfaces
:
954 determine_smoothgroup_for_face(mesh
, face
, edge_sharing_list
, smoothgroup_list
)
955 # progress indicator, writes to console without scrolling
956 if face
.index
> 0 and (face
.index
% interval
) == 0:
957 print("Processing... {}%\r".format(int(face
.index
/ len(mesh
.tessfaces
) * 100)), end
='')
959 print("Completed", ' ' * 20)
961 verbose("len(smoothgroup_list)={}".format(len(smoothgroup_list
)))
963 build_neighbors_tree(smoothgroup_list
)
965 for group
in smoothgroup_list
:
966 group
.get_valid_smoothgroup_id()
968 print("Smooth group parsing completed in {:.2f}s".format(time
.clock() - t
))
969 return smoothgroup_list
972 # ===========================================================================
973 # http://en.wikibooks.org/wiki/Blender_3D:_Blending_Into_Python/Cookbook#Triangulate_NMesh
974 # blender 2.50 format using the Operators/command convert the mesh to tri mesh
975 # ===========================================================================
976 def triangulate_mesh(object):
978 verbose(header("triangulateNMesh"))
979 # print(type(object))
980 scene
= bpy
.context
.scene
982 me_ob
= object.copy()
983 me_ob
.data
= object.to_mesh(bpy
.context
.scene
, True, 'PREVIEW') # write data object
984 bpy
.context
.scene
.objects
.link(me_ob
)
985 bpy
.context
.scene
.update()
986 bpy
.ops
.object.mode_set(mode
='OBJECT')
988 for i
in scene
.objects
:
989 i
.select
= False # deselect all objects
992 scene
.objects
.active
= me_ob
994 print("Copy and Convert mesh just incase any way...")
996 bpy
.ops
.object.mode_set(mode
='EDIT')
997 bpy
.ops
.mesh
.select_all(action
='SELECT') # select all the face/vertex/edge
998 bpy
.ops
.object.mode_set(mode
='EDIT')
999 bpy
.ops
.mesh
.quads_convert_to_tris()
1000 bpy
.context
.scene
.update()
1002 bpy
.ops
.object.mode_set(mode
='OBJECT')
1004 bpy
.context
.scene
.udk_option_triangulate
= True
1006 verbose("Triangulated mesh")
1008 me_ob
.data
= me_ob
.to_mesh(bpy
.context
.scene
, True, 'PREVIEW') # write data object
1009 bpy
.context
.scene
.update()
1013 # copy mesh data and then merge them into one object
1014 def meshmerge(selectedobjects
):
1015 bpy
.ops
.object.mode_set(mode
='OBJECT') # object mode and not edit mode
1016 cloneobjects
= [] # object holder for copying object data
1018 if len(selectedobjects
) > 1:
1019 print("selectedobjects:", len(selectedobjects
)) # print select object
1020 count
= 0 # reset count
1022 for count
in range(len(selectedobjects
)):
1023 # print("Index:",count)
1024 if selectedobjects
[count
] is not None:
1025 me_da
= selectedobjects
[count
].data
.copy() # copy data
1026 me_ob
= selectedobjects
[count
].copy() # copy object
1027 # note two copy two types else it will use the current data or mesh
1028 me_ob
.data
= me_da
# assign the data
1029 bpy
.context
.scene
.objects
.link(me_ob
) # link the object to the scene #current object location
1030 print("Index:", count
, "clone object", me_ob
.name
) # print clone object
1031 cloneobjects
.append(me_ob
) # add object to the array
1033 for i
in bpy
.data
.objects
:
1034 i
.select
= False # deselect all objects
1035 count
= 0 # reset count
1036 # begin merging the mesh together as one
1037 for count
in range(len(cloneobjects
)):
1039 bpy
.context
.scene
.objects
.active
= cloneobjects
[count
]
1040 print("Set Active Object:", cloneobjects
[count
].name
)
1041 cloneobjects
[count
].select
= True
1042 bpy
.ops
.object.join() # join object together
1043 if len(cloneobjects
) > 1:
1044 bpy
.types
.Scene
.udk_copy_merge
= True
1045 return cloneobjects
[0]
1048 # sort the mesh center top list and not center at the last array.
1049 # Base on order while select to merge mesh to make them center.
1050 def sortmesh(selectmesh
):
1051 print("MESH SORTING...")
1054 for countm
in range(len(selectmesh
)):
1055 # if object are center add here
1056 if selectmesh
[countm
].location
.x
== 0 and \
1057 selectmesh
[countm
].location
.y
== 0 and \
1058 selectmesh
[countm
].location
.z
== 0:
1059 centermesh
.append(selectmesh
[countm
])
1060 else: # if not add here for not center
1061 notcentermesh
.append(selectmesh
[countm
])
1063 # add mesh object in order for merge object
1064 for countm
in range(len(centermesh
)):
1065 selectmesh
.append(centermesh
[countm
])
1066 for countm
in range(len(notcentermesh
)):
1067 selectmesh
.append(notcentermesh
[countm
])
1068 if len(selectmesh
) == 1: # if there one mesh just do some here
1069 return selectmesh
[0] # return object mesh
1071 return meshmerge(selectmesh
) # return merge object mesh
1077 # ===========================================================================
1079 # ===========================================================================
1080 def parse_mesh(mesh
, psk
):
1081 # bpy.ops.object.mode_set(mode='OBJECT')
1082 # error ? on commands for select object?
1083 print(header("MESH", 'RIGHT'))
1084 print("Mesh object:", mesh
.name
)
1085 scene
= bpy
.context
.scene
1087 for i
in scene
.objects
:
1088 i
.select
= False # deselect all objects
1090 scene
.objects
.active
= mesh
1092 mesh
= triangulate_mesh(mesh
)
1094 if bpy
.types
.Scene
.udk_copy_merge
is True:
1095 bpy
.context
.scene
.objects
.unlink(setmesh
)
1097 # print("FACES----:",len(mesh.data.tessfaces))
1098 verbose("Working mesh object: {}".format(mesh
.name
))
1100 # collect a list of the material names
1101 print("Materials...")
1105 for slot
in mesh
.material_slots
:
1107 print(" Material {} '{}'".format(mat_slot_index
, slot
.name
))
1108 MaterialName
.append(slot
.name
)
1111 if slot.material.texture_slots[0] is not None:
1112 if slot.material.texture_slots[0].texture.image.filepath is not None:
1113 print(" Texture path {}".format(slot.material.texture_slots[0].texture.image.filepath))
1116 # create the current material
1117 v_material
= psk
.GetMatByIndex(mat_slot_index
)
1118 v_material
.MaterialName
= slot
.name
1119 v_material
.TextureIndex
= mat_slot_index
1120 v_material
.AuxMaterial
= mat_slot_index
1122 verbose(" PSK index {}".format(v_material
.TextureIndex
))
1124 # END slot in mesh.material_slots
1126 # object_mat = mesh.materials[0]
1127 # object_material_index = mesh.active_material_index
1128 # FIXME ^ this is redundant due to "= face.material_index" in face loop
1131 points
= ObjMap() # vertex
1134 discarded_face_count
= 0
1135 sys
.setrecursionlimit(1000000)
1136 smoothgroup_list
= parse_smooth_groups(mesh
.data
)
1138 print("{} faces".format(len(mesh
.data
.tessfaces
)))
1140 print("Smooth groups active:", bpy
.context
.scene
.udk_option_smoothing_groups
)
1142 for face
in mesh
.data
.tessfaces
:
1144 smoothgroup_id
= 0x80000000
1146 for smooth_group
in smoothgroup_list
:
1147 if smooth_group
.contains_face(face
):
1148 smoothgroup_id
= smooth_group
.id
1151 # modified by VendorX
1152 object_material_index
= face
.material_index
1154 if len(face
.vertices
) != 3:
1155 raise Error("Non-triangular face (%i)" % len(face
.vertices
))
1157 # RG - apparently blender sometimes has problems when you do quad to triangle
1158 # conversion, and ends up creating faces that have only TWO points -
1159 # one of the points is simply in the vertex list for the face twice.
1160 # This is bad, since we can't get a real face normal for a LINE, we need
1161 # a plane for this. So, before we add the face to the list of real faces,
1162 # ensure that the face is actually a plane, and not a line. If it is not
1163 # planar, just discard it and notify the user in the console after we're
1164 # done dumping the rest of the faces
1166 if not is_1d_face(face
, mesh
.data
):
1171 # get or create the current material
1172 psk
.GetMatByIndex(object_material_index
)
1174 face_index
= face
.index
1178 if len(mesh
.data
.uv_textures
) > 0:
1180 uv_layer
= mesh
.data
.tessface_uv_textures
.active
1181 face_uv
= uv_layer
.data
[face_index
]
1182 # size(data) is number of texture faces. Each face has UVs
1183 # print("DATA face uv: ",len(faceUV.uv), " >> ",(faceUV.uv[0][0]))
1186 vert_index
= face
.vertices
[i
]
1187 vert
= mesh
.data
.vertices
[vert_index
]
1189 # assumes 3 UVs Per face (for now)
1191 if len(face_uv
.uv
) != 3:
1192 print("WARNING: face has more or less than 3 UV coordinates - writing 0,0...")
1195 uv
= [face_uv
.uv
[i
][0], face_uv
.uv
[i
][1]] # OR bottom works better # 24 for cube
1200 # flip V coordinate because UEd requires it and DOESN'T flip it on its own like it
1201 # does with the mesh Y coordinates. this is otherwise known as MAGIC-2
1204 # clamp UV coords if udk_option_clamp_uv is True
1205 if bpy
.context
.scene
.udk_option_clamp_uv
:
1215 # RE - Append untransformed vector (for normal calc below)
1216 # TODO: convert to Blender.Mathutils
1217 vect_list
.append(FVector(vert
.co
.x
, vert
.co
.y
, vert
.co
.z
))
1219 # Transform position for export
1220 # vpos = vert.co * object_material_index
1222 # should fixed this!!
1223 vpos
= mesh
.matrix_local
* vert
.co
1224 if bpy
.context
.scene
.udk_option_scale
< 0 or bpy
.context
.scene
.udk_option_scale
> 1:
1226 vpos
.x
= vpos
.x
* bpy
.context
.scene
.udk_option_scale
1227 vpos
.y
= vpos
.y
* bpy
.context
.scene
.udk_option_scale
1228 vpos
.z
= vpos
.z
* bpy
.context
.scene
.udk_option_scale
1230 # print("scale pos:", vpos)
1236 if bpy
.context
.scene
.udk_option_smoothing_groups
: # is this necessary?
1237 p
.SmoothGroup
= smoothgroup_id
1239 lPoint
= VPointSimple()
1240 lPoint
.Point
.X
= vpos
.x
1241 lPoint
.Point
.Y
= vpos
.y
1242 lPoint
.Point
.Z
= vpos
.z
1244 if lPoint
in points_linked
:
1245 if not(p
in points_linked
[lPoint
]):
1246 points_linked
[lPoint
].append(p
)
1248 points_linked
[lPoint
] = [p
]
1252 w
.MatIndex
= object_material_index
1253 w
.PointIndex
= points
.get(p
) # store keys
1256 if bpy
.context
.scene
.udk_option_smoothing_groups
: # is this necessary?
1257 w
.SmoothGroup
= smoothgroup_id
1258 index_wedge
= wedges
.get(w
)
1259 wedge_list
.append(index_wedge
)
1262 # print("result PointIndex={}, U={:.6f}, V={:.6f}, wedge_index={}".format(
1268 # END for i in range(3)
1270 # Determine face vertex order
1272 # TODO: convert to Blender.Mathutils
1273 # get normal from blender
1275 # convert to FVector
1276 norm
= FVector(no
[0], no
[1], no
[2])
1277 # Calculate the normal of the face in blender order
1278 tnorm
= vect_list
[1].sub(vect_list
[0]).cross(vect_list
[2].sub(vect_list
[1]))
1279 # RE - dot the normal from blender order against the blender normal
1280 # this gives the product of the two vectors' lengths along the blender normal axis
1281 # all that matters is the sign
1282 dot
= norm
.dot(tnorm
)
1285 # RE - magic: if the dot product above > 0, order the vertices 2, 1, 0
1286 # if the dot product above < 0, order the vertices 0, 1, 2
1287 # if the dot product is 0, then blender's normal is coplanar with the face
1288 # and we cannot deduce which side of the face is the outside of the mesh
1290 (tri
.WedgeIndex2
, tri
.WedgeIndex1
, tri
.WedgeIndex0
) = wedge_list
1292 (tri
.WedgeIndex0
, tri
.WedgeIndex1
, tri
.WedgeIndex2
) = wedge_list
1294 dindex0
= face
.vertices
[0]
1295 dindex1
= face
.vertices
[1]
1296 dindex2
= face
.vertices
[2]
1298 mesh
.data
.vertices
[dindex0
].select
= True
1299 mesh
.data
.vertices
[dindex1
].select
= True
1300 mesh
.data
.vertices
[dindex2
].select
= True
1302 raise Error("Normal coplanar with face! points: %s, %s, %s" % (str(mesh
.data
.vertices
[dindex0
].co
),
1303 str(mesh
.data
.vertices
[dindex1
].co
),
1304 str(mesh
.data
.vertices
[dindex2
].co
)))
1307 if face
.use_smooth
is True:
1308 tri
.SmoothingGroups
= 1
1310 tri
.SmoothingGroups
= 0
1311 tri
.MatIndex
= object_material_index
1313 if bpy
.context
.scene
.udk_option_smoothing_groups
:
1314 tri
.SmoothingGroups
= smoothgroup_id
1315 print("Bool Smooth")
1319 # END if not is_1d_face(current_face, mesh.data)
1322 discarded_face_count
+= 1
1324 # END face in mesh.data.faces
1326 print("{} points".format(len(points
.dict)))
1328 for point
in points
.items():
1331 if len(points
.dict) > 32767:
1332 raise Error("Mesh vertex limit exceeded! {} > 32767".format(len(points
.dict)))
1334 print("{} wedges".format(len(wedges
.dict)))
1336 for wedge
in wedges
.items():
1339 # alert the user to degenerate face issues
1340 if discarded_face_count
> 0:
1341 print("WARNING: Mesh contained degenerate faces (non-planar)")
1342 print(" Discarded {} faces".format(discarded_face_count
))
1344 # RG - walk through the vertex groups and find the indexes into the PSK points array
1345 # for them, then store that index and the weight as a tuple in a new list of
1346 # verts for the group that we can look up later by bone name, since Blender matches
1347 # verts to bones for influences by having the VertexGroup named the same thing as
1350 # [print(x, len(points_linked[x])) for x in points_linked]
1351 # print("pointsindex length ",len(points_linked))
1354 # all vertex groups of the mesh (obj)...
1355 for obj_vertex_group
in mesh
.vertex_groups
:
1357 # print(" bone group build:",obj_vertex_group.name)#print bone name
1358 # print(dir(obj_vertex_group))
1359 verbose("obj_vertex_group.name={}".format(obj_vertex_group
.name
))
1363 # all vertices in the mesh...
1364 for vertex
in mesh
.data
.vertices
:
1365 # print(dir(vertex))
1366 # all groups this vertex is a member of...
1367 for vgroup
in vertex
.groups
:
1368 if vgroup
.group
== obj_vertex_group
.index
:
1369 vertex_weight
= vgroup
.weight
1371 vpos
= mesh
.matrix_local
* vertex
.co
1373 if bpy
.context
.scene
.udk_option_scale
< 0 or bpy
.context
.scene
.udk_option_scale
> 1:
1374 vpos
.x
= vpos
.x
* bpy
.context
.scene
.udk_option_scale
1375 vpos
.y
= vpos
.y
* bpy
.context
.scene
.udk_option_scale
1376 vpos
.z
= vpos
.z
* bpy
.context
.scene
.udk_option_scale
1381 # print(len(points_linked[p]))
1382 try: # check if point doesn't give error
1383 for point
in points_linked
[p
]:
1384 point_index
= points
.get(point
) # point index
1385 v_item
= (point_index
, vertex_weight
)
1386 vertex_list
.append(v_item
)
1387 except Exception: # if get error ignore them # not safe I think
1388 print("Error link points!")
1391 # bone name, [point id and wieght]
1392 # print("Add Vertex Group:",obj_vertex_group.name, " No. Points:",len(vertex_list))
1393 psk
.VertexGroups
[obj_vertex_group
.name
] = vertex_list
1395 # remove the temporary triangulated mesh
1396 if bpy
.context
.scene
.udk_option_triangulate
is True:
1397 verbose("Removing temporary triangle mesh: {}".format(mesh
.name
))
1398 bpy
.ops
.object.mode_set(mode
='OBJECT') # OBJECT mode
1399 mesh
.parent
= None # unparent to avoid phantom links
1400 bpy
.context
.scene
.objects
.unlink(mesh
) # unlink
1403 # ===========================================================================
1404 # Collate bones that belong to the UDK skeletal mesh
1405 # ===========================================================================
1406 def parse_armature(armature
, psk
, psa
):
1408 print(header("ARMATURE", 'RIGHT'))
1409 verbose("Armature object: {} Armature data: {}".format(armature
.name
, armature
.data
.name
))
1411 # generate a list of root bone candidates
1412 root_candidates
= [b
for b
in armature
.data
.bones
if b
.parent
is None and b
.use_deform
is True]
1414 # should be a single, unambiguous result
1415 if len(root_candidates
) == 0:
1416 raise Error("Cannot find root for UDK bones. The root bone must use deform.")
1418 if len(root_candidates
) > 1:
1419 raise Error("Ambiguous root for UDK. More than one root bone is using deform.")
1421 # prep for bone collection
1422 udk_root_bone
= root_candidates
[0]
1424 BoneUtil
.static_bone_id
= 0 # replaces global
1426 # traverse bone chain
1427 print("{: <3} {: <48} {: <20}".format("ID", "Bone", "Status"))
1429 recurse_bone(udk_root_bone
, udk_bones
, psk
, psa
, 0, armature
.matrix_local
)
1432 if len(udk_bones
) < 3:
1433 raise Error("Less than three bones may crash UDK (legacy issue?)")
1435 # return a list of bones making up the entire udk skel
1436 # this is passed to parse_animation instead of working from keyed bones in the action
1440 # ===========================================================================
1443 # psk the PSK file object
1444 # psa the PSA file object
1447 # indent text indent for recursive log
1448 # ===========================================================================
1449 def recurse_bone(bone
, bones
, psk
, psa
, parent_id
, parent_matrix
, indent
=""):
1454 if not bone
.use_deform
:
1455 status
= "No effect"
1457 # calc parented bone transform
1458 if bone
.parent
is not None:
1459 quat
= make_fquat(bone
.matrix
.to_quaternion())
1460 quat_parent
= bone
.parent
.matrix
.to_quaternion().inverted()
1461 parent_head
= quat_parent
* bone
.parent
.head
1462 parent_tail
= quat_parent
* bone
.parent
.tail
1463 translation
= (parent_tail
- parent_head
) + bone
.head
1465 # calc root bone transform
1467 translation
= parent_matrix
* bone
.head
# ARMATURE OBJECT Location
1468 rot_matrix
= bone
.matrix
* parent_matrix
.to_3x3() # ARMATURE OBJECT Rotation
1469 quat
= make_fquat_default(rot_matrix
.to_quaternion())
1471 # udk_option_scale bones here?
1472 if bpy
.context
.scene
.udk_option_scale
< 0 or bpy
.context
.scene
.udk_option_scale
> 1:
1473 translation
.x
= translation
.x
* bpy
.context
.scene
.udk_option_scale
1474 translation
.y
= translation
.y
* bpy
.context
.scene
.udk_option_scale
1475 translation
.z
= translation
.z
* bpy
.context
.scene
.udk_option_scale
1476 bone_id
= BoneUtil
.static_bone_id
# ALT VERS
1477 BoneUtil
.static_bone_id
+= 1 # ALT VERS
1479 child_count
= len(bone
.children
)
1481 psk
.AddBone(make_vbone(bone
.name
, parent_id
, child_count
, quat
, translation
))
1482 psa
.StoreBone(make_namedbonebinary(bone
.name
, parent_id
, child_count
, quat
, translation
, 1))
1484 # RG - dump influences for this bone - use the data we collected
1485 # in the mesh dump phase to map our bones to vertex groups
1486 if bone
.name
in psk
.VertexGroups
:
1487 vertex_list
= psk
.VertexGroups
[bone
.name
]
1488 # print("vertex list:", len(vertex_list), " of >" ,bone.name)
1490 for vertex_data
in vertex_list
:
1491 point_index
= vertex_data
[0]
1492 vertex_weight
= vertex_data
[1]
1493 influence
= VRawBoneInfluence()
1494 influence
.Weight
= vertex_weight
1495 influence
.BoneIndex
= bone_id
1496 influence
.PointIndex
= point_index
1497 # print (" AddInfluence to vertex {}, weight={},".format(point_index, vertex_weight))
1498 psk
.AddInfluence(influence
)
1500 status
= "No vertex group"
1501 # FIXME overwriting previous status error?
1503 print("{:<3} {:<48} {:<20}".format(bone_id
, indent
+ bone
.name
, status
))
1506 # recursively dump child bones
1508 for child_bone
in bone
.children
:
1509 recurse_bone(child_bone
, bones
, psk
, psa
, bone_id
, parent_matrix
, " " + indent
)
1512 # FIXME rename? remove?
1514 static_bone_id
= 0 # static property to replace global
1517 # ===========================================================================
1518 # armature the armature
1519 # udk_bones list of bones to be exported
1520 # actions_to_export list of actions to process for export
1521 # psa the PSA file object
1522 # ===========================================================================
1523 def parse_animation(armature
, udk_bones
, actions_to_export
, psa
):
1525 print(header("ANIMATION", 'RIGHT'))
1527 context
= bpy
.context
1528 anim_rate
= context
.scene
.render
.fps
1530 verbose("Armature object: {}".format(armature
.name
))
1531 print("Scene: {} FPS: {} Frames: {} to {}".format(context
.scene
.name
, anim_rate
,
1532 context
.scene
.frame_start
, context
.scene
.frame_end
)
1534 print("Processing {} action(s)\n".format(len(actions_to_export
)))
1536 # if animation data was not create for the armature it will skip the exporting action set(s)
1537 if armature
.animation_data
is None:
1538 print("None Actions Set! skipping...")
1540 restoreAction
= armature
.animation_data
.action
# Q: is animation_data always valid?
1541 # we already do this in export_proxy, but we'll do it here too for now
1542 restoreFrame
= context
.scene
.frame_current
1543 raw_frame_index
= 0 # used to set FirstRawFrame, seperating actions in the raw keyframe array
1546 for action
in actions_to_export
:
1548 # removed: check for armature with no animation; all it did was force you to add one
1550 if not len(action
.fcurves
):
1551 print("{} has no keys, skipping".format(action
.name
))
1554 # apply action to armature and update scene
1555 # note if loop all actions that is not armature it will override and will break armature animation
1556 armature
.animation_data
.action
= action
1557 context
.scene
.update()
1559 # min/max frames define range
1560 framemin
, framemax
= action
.frame_range
1561 start_frame
= int(framemin
)
1562 end_frame
= int(framemax
)
1563 scene_range
= range(start_frame
, end_frame
+ 1)
1564 frame_count
= len(scene_range
)
1566 # create the AnimInfoBinary
1567 anim
= AnimInfoBinary()
1568 anim
.Name
= action
.name
1569 anim
.Group
= "" # unused?
1570 anim
.NumRawFrames
= frame_count
1571 anim
.AnimRate
= anim_rate
1572 anim
.FirstRawFrame
= raw_frame_index
1574 print("{}, frames {} to {} ({} frames)".format(action
.name
, start_frame
, end_frame
, frame_count
))
1576 # removed: bone lookup table
1578 # build a list of pose bones relevant to the collated udk_bones
1579 # fixme: could be done once, prior to loop?
1582 for pb
in armature
.pose
.bones
:
1583 if b
.name
== pb
.name
:
1584 udk_pose_bones
.append(pb
)
1587 # sort in the order the bones appear in the PSA file
1589 ordered_bones
= sorted([(psa
.UseBone(b
.name
), b
) for b
in udk_pose_bones
], key
=operator
.itemgetter(0))
1591 # NOTE: posebone.bone references the obj/edit bone
1592 # REMOVED: unique_bone_indexes is redundant?
1595 for i
in range(frame_count
):
1597 frame
= scene_range
[i
]
1599 # verbose("FRAME {}".format(i), i) # test loop sampling
1601 # advance to frame (automatically updates the pose)
1602 context
.scene
.frame_set(frame
)
1604 # compute the key for each bone
1605 for bone_data
in ordered_bones
:
1607 bone_index
= bone_data
[0]
1608 pose_bone
= bone_data
[1]
1609 pose_bone_matrix
= mathutils
.Matrix(pose_bone
.matrix
)
1611 if pose_bone
.parent
is not None:
1612 pose_bone_parent_matrix
= mathutils
.Matrix(pose_bone
.parent
.matrix
)
1613 pose_bone_matrix
= pose_bone_parent_matrix
.inverted() * pose_bone_matrix
1615 head
= pose_bone_matrix
.to_translation()
1616 quat
= pose_bone_matrix
.to_quaternion().normalized()
1618 if pose_bone
.parent
is not None:
1619 quat
= make_fquat(quat
)
1621 quat
= make_fquat_default(quat
)
1623 # scale animation position here?
1624 if bpy
.context
.scene
.udk_option_scale
< 0 or bpy
.context
.scene
.udk_option_scale
> 1:
1625 head
.x
= head
.x
* bpy
.context
.scene
.udk_option_scale
1626 head
.y
= head
.y
* bpy
.context
.scene
.udk_option_scale
1627 head
.z
= head
.z
* bpy
.context
.scene
.udk_option_scale
1629 vkey
= VQuatAnimKey()
1630 vkey
.Position
.X
= head
.x
1631 vkey
.Position
.Y
= head
.y
1632 vkey
.Position
.Z
= head
.z
1633 vkey
.Orientation
= quat
1635 # frame delta = 1.0 / fps
1636 vkey
.Time
= 1.0 / anim_rate
# according to C++ header this is "disregarded"
1640 # END for bone_data in ordered_bones
1642 raw_frame_index
+= 1
1644 # END for i in range(frame_count)
1646 # REMOVED len(unique_bone_indexes)
1647 anim
.TotalBones
= len(ordered_bones
)
1648 # frame_count/anim.AnimRate makes more sense, but this is what actually works in UDK
1649 anim
.TrackTime
= float(frame_count
)
1651 verbose("anim.TotalBones={}, anim.TrackTime={}".format(anim
.TotalBones
, anim
.TrackTime
))
1653 psa
.AddAnimation(anim
)
1655 # END for action in actions
1658 armature
.animation_data
.action
= restoreAction
1659 context
.scene
.frame_set(restoreFrame
)
1662 # ===========================================================================
1663 # Collate actions to be exported
1664 # Modify this to filter for one, some or all actions. For now use all.
1665 # RETURNS list of actions
1666 # ===========================================================================
1667 def collate_actions():
1668 verbose(header("collate_actions"))
1669 actions_to_export
= []
1671 for action
in bpy
.data
.actions
:
1672 if bpy
.context
.scene
.udk_option_selectanimations
: # check if needed to select actions set for exporting it
1673 print("Action Set is selected!")
1675 for actionlist
in bpy
.context
.scene
.udkas_list
: # list the action set from the list
1676 if actionlist
.name
== action
.name
and actionlist
.bmatch
is True and actionlist
.bexport
is True:
1678 print("Added Action Set:", action
.name
)
1680 if bready
is False: # don't export it
1681 print("Skipping Action Set:", action
.name
)
1683 verbose(" + {}".format(action
.name
)) # action set name
1684 actions_to_export
.append(action
) # add to the action array
1686 return actions_to_export
1689 # ===========================================================================
1690 # Locate the target armature and mesh for export
1691 # RETURNS armature, mesh
1692 # ===========================================================================
1693 def find_armature_and_mesh():
1694 verbose(header("find_armature_and_mesh", 'LEFT', '<', 60))
1696 context
= bpy
.context
1697 active_object
= context
.active_object
1702 # this could be more intuitive
1703 # bpy.ops.object.mode_set(mode='OBJECT')
1705 if bpy
.context
.scene
.udk_option_selectobjects
: # if checked select object true do list object on export
1706 print("select mode:")
1707 if len(bpy
.context
.scene
.udkArm_list
) > 0:
1708 print("Armature Name:", bpy
.context
.scene
.udkArm_list
[bpy
.context
.scene
.udkArm_list_idx
].name
)
1709 for obj
in bpy
.context
.scene
.objects
:
1710 if obj
.name
== bpy
.context
.scene
.udkArm_list
[bpy
.context
.scene
.udkArm_list_idx
].name
:
1714 raise Error("There is no Armature in the list!")
1716 # parented_meshes = [obj for obj in armature.children if obj.type == 'MESH']
1717 meshes
= [obj
for obj
in bpy
.context
.scene
.objects
if obj
.type == 'MESH']
1720 if obj
.type == 'MESH':
1722 # print("PARENT MESH:",obj.name)
1723 for udkmeshlist
in bpy
.context
.scene
.udkmesh_list
:
1724 if obj
.name
== udkmeshlist
.name
and udkmeshlist
.bexport
is True:
1727 if bexportmesh
is True:
1728 print("Mesh Name:", obj
.name
, " < SELECT TO EXPORT!")
1729 meshselected
.append(obj
)
1731 print("MESH COUNT:", len(meshselected
))
1732 # try the active object
1733 if active_object
and active_object
.type == 'MESH' and len(meshselected
) == 0:
1734 if active_object
.parent
== armature
:
1735 mesh
= active_object
1737 raise Error("The selected mesh is not parented to the armature")
1739 # otherwise, expect a single mesh parented to the armature (other object types are ignored)
1741 print("Number of meshes:", len(meshes
))
1742 print("Number of meshes (selected):", len(meshes
))
1743 if len(meshes
) == 1:
1746 elif len(meshes
) > 1:
1747 if len(meshselected
) >= 1:
1748 mesh
= sortmesh(meshselected
)
1750 raise Error("More than one mesh(s) parented to armature. Select object(s)!")
1752 raise Error("No mesh parented to armature")
1753 else: # if not check for select function from the list work the code here
1754 print("normal mode:")
1755 # try the active object
1756 if active_object
and active_object
.type == 'ARMATURE':
1757 armature
= active_object
1758 bpy
.ops
.object.mode_set(mode
='OBJECT')
1759 # otherwise, try for a single armature in the scene
1761 # bpy.ops.object.mode_set(mode='OBJECT')
1762 all_armatures
= [obj
for obj
in bpy
.context
.scene
.objects
if obj
.type == 'ARMATURE']
1764 if len(all_armatures
) == 1: # if armature has one scene just assign it
1765 armature
= all_armatures
[0]
1766 elif len(all_armatures
) > 1: # if there more armature then find the select armature
1768 for _armobj
in all_armatures
:
1773 if barmselect
is False:
1774 raise Error("Please select an armatures in the scene")
1776 raise Error("No armatures in scene")
1778 verbose("Found armature: {}".format(armature
.name
))
1781 parented_meshes
= [obj
for obj
in armature
.children
if obj
.type == 'MESH']
1783 if len(armature
.children
) == 0:
1784 raise Error("The selected Armature has no mesh parented to the Armature Object!")
1786 for obj
in armature
.children
:
1788 if obj
.type == 'MESH' and obj
.select
is True:
1789 meshselected
.append(obj
)
1790 # try the active object
1791 if active_object
and active_object
.type == 'MESH' and len(meshselected
) == 0:
1792 if active_object
.parent
== armature
:
1793 mesh
= active_object
1795 raise Error("The selected mesh is not parented to the armature")
1797 # otherwise, expect a single mesh parented to the armature (other object types are ignored)
1799 print("Number of meshes:", len(parented_meshes
))
1800 print("Number of meshes (selected):", len(meshselected
))
1801 if len(parented_meshes
) == 1:
1802 mesh
= parented_meshes
[0]
1804 elif len(parented_meshes
) > 1:
1805 if len(meshselected
) >= 1:
1806 mesh
= sortmesh(meshselected
)
1808 raise Error("More than one mesh(s) parented to armature. Select object(s)!")
1810 raise Error("No mesh parented to armature")
1812 verbose("Found mesh: {}".format(mesh
.name
))
1813 if mesh
is None or armature
is None:
1814 raise Error("Check Mesh and Armature are list!")
1817 if len(armature.pose.bones) == len(mesh.vertex_groups):
1818 print("Armature and Mesh Vertex Groups matches Ok!")
1820 raise Error("Armature bones:" + str(len(armature.pose.bones)) +
1821 " Mesh Vertex Groups:" + str(len(mesh.vertex_groups)) +" doesn't match!")
1823 # this will check if object need to be rebuild
1824 if bpy
.context
.scene
.udk_option_rebuildobjects
:
1825 # print("INIT... REBUILDING...")
1826 print("REBUILDING ARMATURE...")
1828 # rebuild the armature to raw. If there IK constraint it will ignore it
1829 armature
= rebuildarmature(armature
)
1830 print("REBUILDING MESH...")
1831 mesh
= rebuildmesh(mesh
) # rebuild the mesh to raw data format.
1833 return armature
, mesh
1836 # ===========================================================================
1837 # Returns a list of vertex groups in the mesh. Can be modified to filter
1838 # groups as necessary.
1840 # ===========================================================================
1841 def collate_vertex_groups(mesh
):
1842 verbose("collate_vertex_groups")
1845 for group
in mesh
.vertex_groups
:
1847 groups
.append(group
)
1848 verbose(" " + group
.name
)
1853 # ===========================================================================
1855 # ===========================================================================
1856 def export(filepath
):
1857 print(header("Export", 'RIGHT'))
1858 bpy
.types
.Scene
.udk_copy_merge
= False # in case fail to export set this to default
1860 context
= bpy
.context
1862 print("Blender Version {}.{}.{}".format(bpy
.app
.version
[0], bpy
.app
.version
[1], bpy
.app
.version
[2]))
1863 print("Filepath: {}".format(filepath
))
1865 verbose("PSK={}, PSA={}".format(context
.scene
.udk_option_export_psk
, context
.scene
.udk_option_export_psa
))
1867 # find armature and mesh
1868 # [change this to implement alternative methods; raise Error() if not found]
1869 udk_armature
, udk_mesh
= find_armature_and_mesh()
1871 # check misc conditions
1872 if not (udk_armature
.scale
.x
== udk_armature
.scale
.y
== udk_armature
.scale
.z
== 1):
1873 raise Error("bad armature scale: armature object should have uniform scale of 1 (ALT-S)")
1875 if not (udk_mesh
.scale
.x
== udk_mesh
.scale
.y
== udk_mesh
.scale
.z
== 1):
1876 raise Error("bad mesh scale: mesh object should have uniform scale of 1 (ALT-S)")
1878 if not (udk_armature
.location
.x
== udk_armature
.location
.y
== udk_armature
.location
.z
== 0):
1879 raise Error("bad armature location: armature should be located at origin (ALT-G)")
1881 if not (udk_mesh
.location
.x
== udk_mesh
.location
.y
== udk_mesh
.location
.z
== 0):
1882 raise Error("bad mesh location: mesh should be located at origin (ALT-G)")
1889 parse_mesh(udk_mesh
, psk
)
1892 udk_bones
= parse_armature(udk_armature
, psk
, psa
)
1895 if context
.scene
.udk_option_export_psa
is True:
1896 actions
= collate_actions()
1897 parse_animation(udk_armature
, udk_bones
, actions
, psa
)
1900 print(header("Exporting", 'CENTER'))
1902 psk_filename
= filepath
+ '.psk'
1903 psa_filename
= filepath
+ '.psa'
1905 if context
.scene
.udk_option_export_psk
is True:
1906 print("Skeletal mesh data...")
1908 file = open(psk_filename
, "wb")
1909 file.write(psk
.dump())
1911 print("Exported: " + psk_filename
)
1914 if context
.scene
.udk_option_export_psa
is True:
1915 print("Animation data...")
1916 if not psa
.IsEmpty():
1918 file = open(psa_filename
, "wb")
1919 file.write(psa
.dump())
1921 print("Exported: " + psa_filename
)
1923 print("No Animation (.psa file) to export")
1927 # if objects are rebuild do the unlink
1928 if bpy
.context
.scene
.udk_option_rebuildobjects
:
1929 print("Unlinking Objects")
1930 print("Armature Object Name:", udk_armature
.name
) # display object name
1931 bpy
.context
.scene
.objects
.unlink(udk_armature
) # remove armature from the scene
1932 print("Mesh Object Name:", udk_mesh
.name
) # display object name
1933 bpy
.context
.scene
.objects
.unlink(udk_mesh
) # remove mesh from the scene
1935 print("Export completed in {:.2f} seconds".format((time
.clock() - t
)))
1938 # ===========================================================================
1940 # ===========================================================================
1941 class Operator_UDKExport(Operator
):
1943 bl_idname
= "object.udk_export"
1944 bl_label
= "Export now"
1946 def execute(self
, context
):
1948 scene
= bpy
.context
.scene
1950 scene
.udk_option_export_psk
= (scene
.udk_option_export
== '0' or scene
.udk_option_export
== '2')
1951 scene
.udk_option_export_psa
= (scene
.udk_option_export
== '1' or scene
.udk_option_export
== '2')
1953 filepath
= get_dst_path()
1956 restore_frame
= scene
.frame_current
1957 message
= "Object(s) exported to: {}".format(filepath
)
1961 except Error
as err
:
1963 message
= err
.message
1966 scene
.frame_set(restore_frame
)
1968 def draw(self
, context
):
1969 self
.layout
.label(text
="Export Finished")
1971 context
.window_manager
.popup_menu(draw
, title
=message
, icon
="INFO")
1975 self
.report({'INFO'}, message
)
1978 scene
.frame_set(restore_frame
)
1983 # ===========================================================================
1985 # ===========================================================================
1986 class Operator_ToggleConsole(Operator
):
1987 """Show or hide the console"""
1988 bl_idname
= "object.toggle_console"
1989 bl_label
= "Toggle console"
1991 def execute(self
, context
):
1992 bpy
.ops
.wm
.console_toggle()
1996 # ===========================================================================
1997 # Get filepath for export
1998 # ===========================================================================
2000 if bpy
.context
.scene
.udk_option_filename_src
== '0':
2001 if bpy
.context
.active_object
:
2002 path
= os
.path
.split(bpy
.data
.filepath
)[0] + "\\" + bpy
.context
.active_object
.name
# + ".psk"
2004 # path = os.path.split(bpy.data.filepath)[0] + "\\" + "Unknown";
2005 path
= os
.path
.splitext(bpy
.data
.filepath
)[0] # + ".psk"
2007 path
= os
.path
.splitext(bpy
.data
.filepath
)[0] # + ".psk"
2011 # ===========================================================================
2013 # ===========================================================================
2014 class OBJECT_OT_UTSelectedFaceSmooth(Operator
):
2015 """It will only select smooth faces that is select mesh"""
2016 bl_idname
= "object.utselectfacesmooth" # XXX, name???
2017 bl_label
= "Select Smooth Faces" # "Select Smooth faces"
2019 def invoke(self
, context
, event
):
2020 print("----------------------------------------")
2021 print("Init Select Face(s):")
2023 for obj
in bpy
.data
.objects
:
2024 if obj
.type == 'MESH' and obj
.select
is True:
2027 bpy
.ops
.object.mode_set(mode
='OBJECT') # it need to go into object mode to able to select the faces
2029 for i
in bpy
.context
.scene
.objects
:
2030 i
.select
= False # deselect all objects
2032 obj
.select
= True # set current object select
2033 bpy
.context
.scene
.objects
.active
= obj
# set active object
2035 mesh
.from_mesh(obj
.data
)
2037 for face
in mesh
.faces
:
2040 for face
in mesh
.faces
:
2041 if face
.smooth
is True:
2047 mesh
.to_mesh(obj
.data
)
2048 bpy
.context
.scene
.update()
2049 bpy
.ops
.object.mode_set(mode
='EDIT')
2050 print("Select Smooth Count(s):", smoothcount
, " Flat Count(s):", flatcount
)
2054 self
.report({'INFO'}, "Selected Face(s) Executed")
2056 self
.report({'INFO'}, "Mesh Object is not selected")
2057 print("----------------------------------------")
2062 class OBJECT_OT_MeshClearWeights(Operator
):
2063 """Remove all mesh vertex groups weights for the bones"""
2064 bl_idname
= "object.meshclearweights" # XXX, name???
2065 bl_label
= "Remove Vertex Weights" # "Remove Mesh vertex weights"
2067 def invoke(self
, context
, event
):
2068 for obj
in bpy
.data
.objects
:
2069 if obj
.type == 'MESH' and obj
.select
is True:
2070 for vg
in obj
.vertex_groups
:
2071 obj
.vertex_groups
.remove(vg
)
2072 self
.report({'INFO'}, "Mesh Vertex Groups Removed")
2077 def unpack_list(list_of_tuples
):
2079 for t
in list_of_tuples
:
2084 def rebuildmesh(obj
):
2085 # make sure it in object mode
2086 print("Mesh Object Name:", obj
.name
)
2087 bpy
.ops
.object.mode_set(mode
='OBJECT')
2089 for i
in bpy
.context
.scene
.objects
:
2090 i
.select
= False # deselect all objects
2092 bpy
.context
.scene
.objects
.active
= obj
2094 me_ob
= bpy
.data
.meshes
.new(("Re_" + obj
.name
))
2100 # print("creating array build mesh...")
2101 mmesh
= obj
.to_mesh(bpy
.context
.scene
, True, 'PREVIEW')
2102 uv_layer
= mmesh
.tessface_uv_textures
.active
2104 for face
in mmesh
.tessfaces
:
2105 smoothings
.append(face
.use_smooth
) # smooth or flat in boolean
2106 if uv_layer
is not None: # check if there texture data exist
2107 faceUV
= uv_layer
.data
[face
.index
]
2109 for uv
in faceUV
.uv
:
2110 uvs
.append((uv
[0], uv
[1]))
2112 # print((face.vertices[:]))
2113 if len(face
.vertices
) == 3:
2114 faces
.extend([(face
.vertices
[0], face
.vertices
[1], face
.vertices
[2], 0)])
2116 faces
.extend([(face
.vertices
[0], face
.vertices
[1], face
.vertices
[2], face
.vertices
[3])])
2119 for vertex
in mesh
.vertices
:
2120 verts
.append(vertex
.co
.to_tuple())
2121 # vertices weight groups into array
2122 vertGroups
= {} # array in strings
2124 for vgroup
in obj
.vertex_groups
:
2126 for v
in mesh
.vertices
:
2128 if vg
.group
== vgroup
.index
:
2129 vlist
.append((v
.index
, vg
.weight
))
2130 # print((v.index,vg.weight))
2131 vertGroups
[vgroup
.name
] = vlist
2133 # print("creating mesh object...")
2134 # me_ob.from_pydata(verts, [], faces)
2135 me_ob
.vertices
.add(len(verts
))
2136 me_ob
.tessfaces
.add(len(faces
))
2137 me_ob
.vertices
.foreach_set("co", unpack_list(verts
))
2138 me_ob
.tessfaces
.foreach_set("vertices_raw", unpack_list(faces
))
2139 me_ob
.tessfaces
.foreach_set("use_smooth", smoothings
) # smooth array from face
2141 # check if there is uv faces
2142 if len(uvfaces
) > 0:
2143 uvtex
= me_ob
.tessface_uv_textures
.new(name
="retex")
2144 for i
, face
in enumerate(me_ob
.tessfaces
):
2145 blender_tface
= uvtex
.data
[i
] # face
2146 mfaceuv
= uvfaces
[i
]
2147 if len(mfaceuv
) == 3:
2148 blender_tface
.uv1
= mfaceuv
[0]
2149 blender_tface
.uv2
= mfaceuv
[1]
2150 blender_tface
.uv3
= mfaceuv
[2]
2151 if len(mfaceuv
) == 4:
2152 blender_tface
.uv1
= mfaceuv
[0]
2153 blender_tface
.uv2
= mfaceuv
[1]
2154 blender_tface
.uv3
= mfaceuv
[2]
2155 blender_tface
.uv4
= mfaceuv
[3]
2157 me_ob
.update() # need to update the information to able to see into the secne
2158 obmesh
= bpy
.data
.objects
.new(("Re_" + obj
.name
), me_ob
)
2159 bpy
.context
.scene
.update()
2161 # Build tmp materials
2162 materialname
= "ReMaterial"
2163 for matcount
in mesh
.materials
:
2164 matdata
= bpy
.data
.materials
.new(materialname
)
2165 me_ob
.materials
.append(matdata
)
2167 # assign face to material id
2168 for face
in mesh
.tessfaces
:
2169 me_ob
.faces
[face
.index
].material_index
= face
.material_index
2171 # vertices weight groups
2172 for vgroup
in vertGroups
:
2173 group
= obmesh
.vertex_groups
.new(vgroup
)
2174 for v
in vertGroups
[vgroup
]:
2175 group
.add([v
[0]], v
[1], 'ADD') # group.add(array[vertex id],weight,add)
2176 bpy
.context
.scene
.objects
.link(obmesh
)
2177 # print("Mesh Material Count:",len(me_ob.materials))
2179 # print("MATERIAL ID OREDER:")
2180 for mat
in me_ob
.materials
:
2181 # print("-Material:",mat.name,"INDEX:",matcount)
2184 print("Mesh Object Name:", obmesh
.name
)
2185 bpy
.context
.scene
.update()
2190 class OBJECT_OT_UTRebuildMesh(Operator
):
2191 """It rebuild the mesh from scrape from the selected mesh object. """ \
2192 """Note the scale will be 1:1 for object mode. To keep from deforming"""
2193 bl_idname
= "object.utrebuildmesh" # XXX, name???
2194 bl_label
= "Rebuild Mesh" # "Rebuild Mesh"
2196 def invoke(self
, context
, event
):
2197 print("----------------------------------------")
2198 print("Init Mesh Bebuild...")
2200 bpy
.ops
.object.mode_set(mode
='OBJECT')
2202 for obj
in bpy
.data
.objects
:
2203 if obj
.type == 'MESH' and obj
.select
is True:
2206 self
.report({'INFO'}, "Rebuild Mesh Finished!")
2207 print("Finish Mesh Build...")
2208 print("----------------------------------------")
2212 def rebuildarmature(obj
):
2213 currentbone
= [] # select armature for roll copy
2214 print("Armature Name:", obj
.name
)
2215 objectname
= "ArmatureDataPSK"
2216 meshname
= "ArmatureObjectPSK"
2217 armdata
= bpy
.data
.armatures
.new(objectname
)
2218 ob_new
= bpy
.data
.objects
.new(meshname
, armdata
)
2219 bpy
.context
.scene
.objects
.link(ob_new
)
2220 # bpy.ops.object.mode_set(mode='OBJECT')
2222 for i
in bpy
.context
.scene
.objects
:
2223 i
.select
= False # deselect all objects
2225 ob_new
.select
= True
2226 bpy
.context
.scene
.objects
.active
= obj
2228 bpy
.ops
.object.mode_set(mode
='EDIT')
2229 for bone
in obj
.data
.edit_bones
:
2230 if bone
.parent
is not None:
2231 currentbone
.append([bone
.name
, bone
.roll
])
2233 currentbone
.append([bone
.name
, bone
.roll
])
2234 bpy
.ops
.object.mode_set(mode
='OBJECT')
2236 for i
in bpy
.context
.scene
.objects
:
2237 i
.select
= False # deselect all objects
2239 bpy
.context
.scene
.objects
.active
= ob_new
2240 bpy
.ops
.object.mode_set(mode
='EDIT')
2242 for bone
in obj
.data
.bones
:
2243 bpy
.ops
.object.mode_set(mode
='EDIT')
2244 newbone
= ob_new
.data
.edit_bones
.new(bone
.name
)
2245 newbone
.head
= bone
.head_local
2246 newbone
.tail
= bone
.tail_local
2247 for bonelist
in currentbone
:
2248 if bone
.name
== bonelist
[0]:
2249 newbone
.roll
= bonelist
[1]
2251 if bone
.parent
is not None:
2252 parentbone
= ob_new
.data
.edit_bones
[bone
.parent
.name
]
2253 newbone
.parent
= parentbone
2255 ob_new
.animation_data_create() # create animation data
2256 if obj
.animation_data
is not None: # check for animation
2257 # just make sure it here to do the animations if exist
2258 ob_new
.animation_data
.action
= obj
.animation_data
.action
2260 print("Armature Object Name:", ob_new
.name
)
2264 class OBJECT_OT_UTRebuildArmature(Operator
):
2265 """If mesh is deform when importing to unreal engine try this. """ \
2266 """It rebuild the bones one at the time by select one armature object scrape to raw setup build. """ \
2267 """Note the scale will be 1:1 for object mode. To keep from deforming"""
2268 bl_idname
= "object.utrebuildarmature" # XXX, name???
2269 bl_label
= "Rebuild Armature" # Rebuild Armature
2271 def invoke(self
, context
, event
):
2272 print("----------------------------------------")
2273 print("Init Rebuild Armature...")
2275 for obj
in bpy
.data
.objects
:
2276 if obj
.type == 'ARMATURE' and obj
.select
is True:
2277 rebuildarmature(obj
)
2278 self
.report({'INFO'}, "Rebuild Armature Finish!")
2279 print("End of Rebuild Armature.")
2280 print("----------------------------------------")
2284 class UDKActionSetListPG(PropertyGroup
):
2285 bool = BoolProperty(default
=False)
2286 string
= StringProperty()
2287 actionname
= StringProperty()
2288 bmatch
= BoolProperty(
2292 description
="This check against bone names and action group "
2293 "names matches and override boolean if true"
2295 bexport
= BoolProperty(
2298 description
="Check this to export the animation"
2302 class UL_UDKActionSetList(UIList
):
2303 def draw_item(self
, context
, layout
, data
, item
, icon
, active_data
, active_propname
, index
):
2304 layout
.label(item
.name
)
2305 layout
.prop(item
, "bmatch", text
="Match")
2306 layout
.prop(item
, "bexport", text
="Export")
2309 class UDKObjListPG(PropertyGroup
):
2310 bool = BoolProperty(default
=False)
2311 string
= StringProperty()
2312 bexport
= BoolProperty(
2316 description
="This will be ignore when exported"
2318 bselect
= BoolProperty(
2322 description
="This will be ignore when exported"
2324 otype
= StringProperty(
2326 description
="This will be ignore when exported"
2330 class UL_UDKObjList(UIList
):
2331 def draw_item(self
, context
, layout
, data
, item
, icon
, active_data
, active_propname
, index
):
2332 layout
.label(item
.name
)
2333 layout
.prop(item
, "otype", text
="")
2334 layout
.prop(item
, "bselect", text
="")
2337 class UDKMeshListPG(PropertyGroup
):
2338 bool = BoolProperty(
2341 string
= StringProperty()
2342 bexport
= BoolProperty(
2346 description
="This object will be export when true"
2348 bselect
= BoolProperty(
2352 description
="Make sure you have Mesh is parent to Armature"
2354 otype
= StringProperty(
2356 description
="This will be ignore when exported"
2360 class UL_UDKMeshList(UIList
):
2361 def draw_item(self
, context
, layout
, data
, item
, icon
, active_data
, active_propname
, index
):
2362 layout
.label(item
.name
)
2363 # layout.prop(item, "bselect", text="Select")
2364 layout
.prop(item
, "bexport", text
="Export")
2367 class UDKArmListPG(PropertyGroup
):
2368 bool = BoolProperty(default
=False)
2369 string
= StringProperty()
2370 bexport
= BoolProperty(
2374 description
="This will be ignore when exported"
2376 bselect
= BoolProperty(
2380 description
="This will be ignore when exported"
2382 otype
= StringProperty(
2384 description
="This will be ignore when exported"
2388 class UL_UDKArmList(UIList
):
2389 def draw_item(self
, context
, layout
, data
, item
, icon
, active_data
, active_propname
, index
):
2390 layout
.label(item
.name
)
2393 class Panel_UDKExport(Panel
):
2394 bl_label
= "UDK Export"
2395 bl_idname
= "OBJECT_PT_udk_tools"
2396 bl_category
= "File I/O"
2397 bl_space_type
= "VIEW_3D"
2398 bl_region_type
= "TOOLS"
2399 bl_context
= "objectmode"
2402 def draw_header(self, context):
2403 layout = self.layout
2404 obj = context.object
2405 layout.prop(obj, "select", text="")
2408 def poll(cls, context):
2409 return context.active_object
2412 def draw(self
, context
):
2413 layout
= self
.layout
2414 path
= get_dst_path()
2419 object_name = context.object.name
2421 if context
.active_object
:
2422 object_name
= context
.active_object
.name
2423 row10
= layout
.row()
2424 row10
.prop(context
.scene
, "udk_option_smoothing_groups")
2425 row10
.prop(context
.scene
, "udk_option_clamp_uv")
2426 row10
.prop(context
.scene
, "udk_option_verbose")
2429 row
.label(text
="Active object: " + object_name
)
2430 layout
.prop(context
.scene
, "udk_option_filename_src")
2432 row
.label(text
=path
)
2434 layout
.prop(context
.scene
, "udk_option_export")
2435 layout
.prop(context
.scene
, "udk_option_selectobjects")
2437 if context
.scene
.udk_option_selectobjects
:
2438 layout
.operator("object.selobjectpdate")
2439 layout
.label(text
="ARMATURE - Index")
2440 layout
.template_list("UL_UDKArmList", "udk_armatures", context
.scene
, "udkArm_list",
2441 context
.scene
, "udkArm_list_idx", rows
=3)
2442 layout
.label(text
="MESH - Export")
2443 layout
.template_list("UL_UDKMeshList", "", context
.scene
, "udkmesh_list",
2444 context
.scene
, "udkmesh_list_idx", rows
=5)
2445 layout
.prop(context
.scene
, "udk_option_selectanimations")
2447 if context
.scene
.udk_option_selectanimations
:
2448 layout
.operator("action.setanimupdate")
2449 layout
.label(text
="Action Set(s) - Match / Export")
2450 layout
.template_list("UL_UDKActionSetList", "", context
.scene
, "udkas_list",
2451 context
.scene
, "udkas_list_idx", rows
=5)
2453 layout
.prop(context
.scene
, "udk_option_scale")
2454 layout
.prop(context
.scene
, "udk_option_rebuildobjects")
2455 # layout.prop(context.scene, "udk_option_ignoreactiongroupnames")
2457 row11
= layout
.row()
2458 row11
.operator("object.udk_export")
2459 row11
.operator("object.toggle_console")
2460 layout
.operator(OBJECT_OT_UTRebuildArmature
.bl_idname
)
2461 layout
.label(text
="Mesh")
2462 layout
.operator(OBJECT_OT_MeshClearWeights
.bl_idname
)
2463 layout
.operator(OBJECT_OT_UTSelectedFaceSmooth
.bl_idname
)
2464 layout
.operator(OBJECT_OT_UTRebuildMesh
.bl_idname
)
2465 layout
.operator(OBJECT_OT_UDKCheckMeshLines
.bl_idname
)
2468 def udkupdateobjects():
2469 my_objlist
= bpy
.context
.scene
.udkArm_list
2471 for objarm
in bpy
.context
.scene
.objects
: # list and filter only mesh and armature
2472 if objarm
.type == 'ARMATURE':
2473 objectl
.append(objarm
)
2475 for _objd
in objectl
: # check if list has in udk list
2477 for _obj
in my_objlist
:
2478 if _obj
.name
== _objd
.name
and _obj
.otype
== _objd
.type:
2479 _obj
.bselect
= _objd
.select
2483 if bfound_obj
is False:
2484 # print("ADD ARMATURE...")
2485 my_item
= my_objlist
.add()
2486 my_item
.name
= _objd
.name
2487 my_item
.bselect
= _objd
.select
2488 my_item
.otype
= _objd
.type
2490 for _udkobj
in my_objlist
:
2493 for _objd
in bpy
.context
.scene
.objects
: # check if there no existing object from sense to remove it
2494 if _udkobj
.name
== _objd
.name
and _udkobj
.otype
== _objd
.type:
2498 if bfound_objv
is False:
2499 removeobject
.append(_udkobj
)
2500 # print("remove check...")
2501 for _item
in removeobject
: # loop remove object from udk list object
2503 for _obj
in my_objlist
:
2504 if _obj
.name
== _item
.name
and _obj
.otype
== _item
.otype
:
2505 my_objlist
.remove(count
)
2509 my_objlist
= bpy
.context
.scene
.udkmesh_list
2511 for objarm
in bpy
.context
.scene
.objects
: # list and filter only mesh and armature
2512 if objarm
.type == 'MESH':
2513 objectl
.append(objarm
)
2514 for _objd
in objectl
: # check if list has in udk list
2516 for _obj
in my_objlist
:
2517 if _obj
.name
== _objd
.name
and _obj
.otype
== _objd
.type:
2518 _obj
.bselect
= _objd
.select
2521 if bfound_obj
is False:
2522 my_item
= my_objlist
.add()
2523 my_item
.name
= _objd
.name
2524 my_item
.bselect
= _objd
.select
2525 my_item
.otype
= _objd
.type
2527 for _udkobj
in my_objlist
:
2529 for _objd
in bpy
.context
.scene
.objects
: # check if there no existing object from sense to remove it
2530 if _udkobj
.name
== _objd
.name
and _udkobj
.otype
== _objd
.type:
2533 if bfound_objv
is False:
2534 removeobject
.append(_udkobj
)
2535 # print("remove check...")
2536 for _item
in removeobject
: # loop remove object from udk list object
2538 for _obj
in my_objlist
:
2539 if _obj
.name
== _item
.name
and _obj
.otype
== _item
.otype
:
2540 my_objlist
.remove(count
)
2545 class OBJECT_OT_UDKObjUpdate(Operator
):
2546 """This will update the filter of the mesh and armature"""
2547 bl_idname
= "object.selobjectpdate"
2548 bl_label
= "Update Object(s)"
2550 actionname
= bpy
.props
.StringProperty()
2552 def execute(self
, context
):
2557 def udkcheckmeshline():
2559 for obj
in bpy
.context
.scene
.objects
:
2560 if obj
.type == 'MESH' and obj
.select
is True:
2563 objmesh
= triangulate_mesh(objmesh
) # create a copy of the mesh
2564 bpy
.ops
.object.mode_set(mode
='OBJECT')
2566 for i
in bpy
.context
.scene
.objects
:
2567 i
.select
= False # deselect all objects
2569 objmesh
.select
= True
2570 bpy
.context
.scene
.objects
.active
= objmesh
# set active mesh
2573 bpy
.ops
.object.mode_set(mode
='EDIT') # set in edit mode
2574 bpy
.ops
.mesh
.select_all(action
='DESELECT')
2575 bpy
.context
.tool_settings
.mesh_select_mode
= (True, False, False) # select vertices
2577 if objmesh
is not None:
2580 print(objmesh
.data
.tessfaces
)
2582 for face
in objmesh
.data
.tessfaces
:
2586 vert_index
= face
.vertices
[i
]
2587 vert
= objmesh
.data
.vertices
[vert_index
]
2588 vect_list
.append(FVector(vert
.co
.x
, vert
.co
.y
, vert
.co
.z
))
2589 vpos
= objmesh
.matrix_local
* vert
.co
2595 w
.PointIndex
= points
.get(p
) # store keys
2596 index_wedge
= wedges
.get(w
)
2597 wedge_list
.append(index_wedge
)
2599 norm
= FVector(no
[0], no
[1], no
[2])
2600 tnorm
= vect_list
[1].sub(vect_list
[0]).cross(vect_list
[2].sub(vect_list
[1]))
2601 dot
= norm
.dot(tnorm
)
2605 (tri
.WedgeIndex2
, tri
.WedgeIndex1
, tri
.WedgeIndex0
) = wedge_list
2607 (tri
.WedgeIndex0
, tri
.WedgeIndex1
, tri
.WedgeIndex2
) = wedge_list
2609 dindex0
= face
.vertices
[0]
2610 dindex1
= face
.vertices
[1]
2611 dindex2
= face
.vertices
[2]
2612 vertex_list
.append(dindex0
)
2613 vertex_list
.append(dindex1
)
2614 vertex_list
.append(dindex2
)
2616 bpy
.ops
.object.mode_set(mode
='OBJECT')
2617 for vertex
in objmesh
.data
.vertices
: # loop all vertex in the mesh list
2618 for vl
in vertex_list
: # loop for error vertex
2619 if vertex
.index
== vl
: # if match set to select
2620 vertex
.select
= True
2622 bpy
.ops
.object.mode_set(mode
='EDIT') # set in edit mode to see the select vertex
2623 objmesh
.data
.update() # update object
2624 bpy
.context
.scene
.update() # update scene
2625 message
= "MESH PASS"
2626 if len(vertex_list
) > 0:
2627 message
= "MESH FAIL"
2631 class OBJECT_OT_UDKCheckMeshLines(Operator
):
2632 """Select the mesh for export test. This will create dummy mesh to see which area are broken. """ \
2633 """If the vertices share the same position it will cause a bug"""
2634 bl_idname
= "object.udkcheckmeshline"
2635 bl_label
= "Check Mesh Vertices"
2637 def execute(self
, context
):
2638 message
= udkcheckmeshline()
2639 self
.report({'ERROR'}, message
)
2643 class OBJECT_OT_ActionSetAnimUpdate(Operator
):
2644 """Select Armture to match the action set groups. """ \
2645 """All bones keys must be set to match with number of bones"""
2646 bl_idname
= "action.setanimupdate"
2647 bl_label
= "Update Action Set(s)"
2649 actionname
= bpy
.props
.StringProperty()
2651 def execute(self
, context
):
2652 my_sett
= bpy
.context
.scene
.udkas_list
2657 armatureselected
= []
2658 for objarm
in bpy
.context
.scene
.objects
:
2659 if objarm
.type == 'ARMATURE':
2660 # print("ADDED ARMATURE...")
2661 armatures
.append(objarm
)
2662 if objarm
.select
is True:
2663 armatureselected
.append(objarm
)
2665 if len(armatureselected
) == len(armatures
) == 1:
2666 armature
= armatures
[0]
2667 if len(armatures
) == 1:
2668 armature
= armatures
[0]
2669 if len(armatureselected
) == 1:
2670 armature
= armatureselected
[0]
2672 if armature
is not None:
2673 for bone
in armature
.pose
.bones
:
2674 bones
.append(bone
.name
)
2676 for action
in bpy
.data
.actions
: # action list
2679 for actionbone
in action
.groups
:
2680 # print("Pose bone name: ",actionbone.name)
2682 if b
== actionbone
.name
:
2684 # print(b," : ",actionbone.name)
2686 for actionlist
in my_sett
:
2687 if action
.name
== actionlist
.name
:
2689 if len(bones
) == len(action
.groups
) == count
:
2690 actionlist
.bmatch
= True
2692 actionlist
.bmatch
= False
2695 if bfound
is not True:
2696 my_item
= my_sett
.add()
2697 # print(dir(my_item.bmatch))
2698 my_item
.name
= action
.name
2699 # my_item.template_list_controls = "bmatch:bexport"
2700 if len(bones
) == len(action
.groups
) == count
:
2701 my_item
.bmatch
= True
2703 my_item
.bmatch
= False
2705 # check action list and data actions
2706 for actionlist
in bpy
.context
.scene
.udkas_list
:
2709 for act
in bpy
.data
.actions
:
2710 if actionlist
.name
== act
.name
:
2714 # print("ACT NAME:",actionlist.name," COUNT",notfound)
2715 if notfound
== len(bpy
.data
.actions
):
2716 # print("remove :",actionlist.name)
2717 removeactions
.append(actionlist
.name
)
2718 # print("Not in the action data list:",len(removeactions))
2719 # remove list or chnages in the name the template list
2720 for actname
in removeactions
:
2722 for actionlist
in my_sett
:
2723 # print("action name:",actionlist.name)
2724 if actionlist
.name
== actname
:
2725 my_sett
.remove(actioncount
)
2731 class ExportUDKAnimData(Operator
):
2732 """Export Skeleton Mesh / Animation Data file(s). """ \
2733 """One mesh and one armature else select one mesh or armature to be exported"""
2734 bl_idname
= "export_anim.udk" # this is important since its how bpy.ops.export.udk_anim_data is constructed
2735 bl_label
= "Export PSK/PSA"
2737 # List of operator properties, the attributes will be assigned
2738 # to the class instance from the operator settings before calling.
2740 filepath
= StringProperty(
2741 subtype
='FILE_PATH',
2743 filter_glob
= StringProperty(
2744 default
="*.psk;*.psa",
2747 udk_option_scale
= FloatProperty(
2749 description
="In case you don't want to scale objects manually - "
2750 "This will just scale position when on export for the skeleton mesh and animation data",
2753 udk_option_rebuildobjects
= BoolProperty(
2754 name
="Rebuild Objects",
2755 description
="In case of deform skeleton mesh and animations data - "
2756 "This will rebuild objects from raw format on export when checked",
2761 def poll(cls
, context
):
2762 return context
.active_object
is not None
2764 def execute(self
, context
):
2765 scene
= bpy
.context
.scene
2766 scene
.udk_option_export_psk
= (scene
.udk_option_export
== '0' or scene
.udk_option_export
== '2')
2767 scene
.udk_option_export_psa
= (scene
.udk_option_export
== '1' or scene
.udk_option_export
== '2')
2768 bpy
.context
.scene
.udk_option_scale
= self
.udk_option_scale
2769 bpy
.context
.scene
.udk_option_rebuildobjects
= self
.udk_option_rebuildobjects
2771 filepath
= get_dst_path()
2774 restore_frame
= scene
.frame_current
2776 message
= "Finish Export!"
2780 except Error
as err
:
2782 message
= err
.message
2785 scene
.frame_set(restore_frame
)
2787 self
.report({'WARNING', 'INFO'}, message
)
2790 def draw(self
, context
):
2791 layout
= self
.layout
2792 scene
= context
.scene
2794 layout
.prop(scene
, "udk_option_smoothing_groups")
2795 layout
.prop(scene
, "udk_option_clamp_uv")
2796 layout
.prop(scene
, "udk_option_verbose")
2797 layout
.prop(scene
, "udk_option_filename_src")
2798 layout
.prop(scene
, "udk_option_export")
2799 layout
.prop(self
, "udk_option_scale")
2800 layout
.prop(self
, "udk_option_rebuildobjects")
2802 def invoke(self
, context
, event
):
2803 self
.udk_option_scale
= bpy
.context
.scene
.udk_option_scale
2804 self
.udk_option_rebuildobjects
= bpy
.context
.scene
.udk_option_rebuildobjects
2806 wm
= context
.window_manager
2807 wm
.fileselect_add(self
)
2808 return {'RUNNING_MODAL'}
2811 def menu_func(self
, context
):
2812 default_path
= os
.path
.splitext(bpy
.data
.filepath
)[0] + ".psk"
2813 self
.layout
.operator(ExportUDKAnimData
.bl_idname
,
2814 text
="Skeleton Mesh / Animation Data (.psk/.psa)").filepath
= default_path
2817 # Add-ons Preferences Update Panel
2819 # Define Panel classes for updating
2825 def update_panel(self
, context
):
2826 message
= "Export Unreal Engine Format(.psk/.psa): Updating Panel locations has failed"
2828 for panel
in panels
:
2829 if "bl_rna" in panel
.__dict
__:
2830 bpy
.utils
.unregister_class(panel
)
2832 for panel
in panels
:
2833 panel
.bl_category
= context
.user_preferences
.addons
[__name__
].preferences
.category
2834 bpy
.utils
.register_class(panel
)
2836 except Exception as e
:
2837 print("\n[{}]\n{}\n\nError:\n{}".format(__name__
, message
, e
))
2841 class PskAddonPreferences(AddonPreferences
):
2842 # this must match the addon name, use '__package__'
2843 # when defining this in a submodule of a python package.
2844 bl_idname
= __name__
2846 category
= StringProperty(
2847 name
="Tab Category",
2848 description
="Choose a name for the category of the panel",
2853 def draw(self
, context
):
2854 layout
= self
.layout
2858 col
.label(text
="Tab Category:")
2859 col
.prop(self
, "category", text
="")
2862 # ===========================================================================
2864 # ===========================================================================
2867 bpy
.utils
.register_module(__name__
)
2868 bpy
.types
.INFO_MT_file_export
.append(menu_func
)
2869 update_panel(None, bpy
.context
)
2872 bpy
.types
.Scene
.udk_option_filename_src
= EnumProperty(
2874 description
="Sets the name for the files",
2876 ('0', "From object", "Name will be taken from object name"),
2877 ('1', "From Blend", "Name will be taken from .blend file name")
2881 bpy
.types
.Scene
.udk_option_export_psk
= BoolProperty(
2882 name
="bool export psa",
2883 description
="Boolean for exporting psk format (Skeleton Mesh)",
2886 bpy
.types
.Scene
.udk_option_export_psa
= BoolProperty(
2887 name
="bool export psa",
2888 description
="Boolean for exporting psa format (Animation Data)",
2891 bpy
.types
.Scene
.udk_option_clamp_uv
= BoolProperty(
2893 description
="True is to limit Clamp UV co-ordinates to [0-1]. False is unrestricted (x,y)",
2896 bpy
.types
.Scene
.udk_copy_merge
= BoolProperty(
2898 description
="This will copy the mesh(s) and merge the object together "
2899 "and unlink the mesh to be remove while exporting the object",
2902 bpy
.types
.Scene
.udk_option_export
= EnumProperty(
2904 description
="What to export",
2906 ('0', "Mesh only", "Exports the PSK file for the Skeletal Mesh"),
2907 ('1', "Animation only", "Export the PSA file for Action Set(s)(Animations Data)"),
2908 ('2', "Mesh & Animation", "Export both PSK and PSA files(Skeletal Mesh/Animation(s) Data)")
2912 bpy
.types
.Scene
.udk_option_verbose
= BoolProperty(
2914 description
="Verbose console output",
2917 bpy
.types
.Scene
.udk_option_smoothing_groups
= BoolProperty(
2918 name
="Smooth Groups",
2919 description
="Activate hard edges as smooth groups",
2922 bpy
.types
.Scene
.udk_option_triangulate
= BoolProperty(
2923 name
="Triangulate Mesh",
2924 description
="Convert Quads to Triangles",
2927 bpy
.types
.Scene
.udk_option_selectanimations
= BoolProperty(
2928 name
="Select Animation(s)",
2929 description
="Select animation(s) for export to psa file",
2932 bpy
.types
.Scene
.udk_option_selectobjects
= BoolProperty(
2933 name
="Select Object(s)",
2934 description
="Select Armature and Mesh(s). Just make sure mesh(s) is parent to armature",
2937 bpy
.types
.Scene
.udk_option_rebuildobjects
= BoolProperty(
2938 name
="Rebuild Objects",
2939 description
="In case of deform skeleton mesh and animations data - "
2940 "This will rebuild objects from raw format on export when checked",
2943 bpy
.types
.Scene
.udk_option_ignoreactiongroupnames
= BoolProperty(
2944 name
="Ignore Action Group Names",
2945 description
="This will Ignore Action Set Group Names Check With Armature Bones. "
2946 "It will override armature to set action set",
2949 bpy
.types
.Scene
.udk_option_scale
= FloatProperty(
2951 description
="In case you don't want to scale objects manually - "
2952 "This will just scale position when on export for the skeleton mesh and animation data",
2955 bpy
.types
.Scene
.udkas_list
= CollectionProperty(
2956 type=UDKActionSetListPG
2958 bpy
.types
.Scene
.udkas_list_idx
= IntProperty()
2959 bpy
.types
.Scene
.udkobj_list
= CollectionProperty(
2962 bpy
.types
.Scene
.udkobj_list_idx
= IntProperty()
2963 bpy
.types
.Scene
.udkmesh_list
= CollectionProperty(
2966 bpy
.types
.Scene
.udkmesh_list_idx
= IntProperty()
2967 bpy
.types
.Scene
.udkArm_list
= CollectionProperty(
2970 bpy
.types
.Scene
.udkArm_list_idx
= IntProperty()
2974 bpy
.utils
.unregister_module(__name__
)
2975 bpy
.types
.INFO_MT_file_export
.remove(menu_func
)
2977 del bpy
.types
.Scene
.udk_option_filename_src
2978 del bpy
.types
.Scene
.udk_option_export_psk
2979 del bpy
.types
.Scene
.udk_option_export_psa
2980 del bpy
.types
.Scene
.udk_option_clamp_uv
2981 del bpy
.types
.Scene
.udk_copy_merge
2982 del bpy
.types
.Scene
.udk_option_export
2983 del bpy
.types
.Scene
.udk_option_verbose
2984 del bpy
.types
.Scene
.udk_option_smoothing_groups
2985 del bpy
.types
.Scene
.udk_option_triangulate
2986 del bpy
.types
.Scene
.udk_option_selectanimations
2987 del bpy
.types
.Scene
.udk_option_selectobjects
2988 del bpy
.types
.Scene
.udk_option_rebuildobjects
2989 del bpy
.types
.Scene
.udk_option_ignoreactiongroupnames
2990 del bpy
.types
.Scene
.udk_option_scale
2991 del bpy
.types
.Scene
.udkas_list
2992 del bpy
.types
.Scene
.udkas_list_idx
2993 del bpy
.types
.Scene
.udkobj_list
2994 del bpy
.types
.Scene
.udkobj_list_idx
2995 del bpy
.types
.Scene
.udkmesh_list
2996 del bpy
.types
.Scene
.udkmesh_list_idx
2997 del bpy
.types
.Scene
.udkArm_list
2998 del bpy
.types
.Scene
.udkArm_list_idx
3001 if __name__
== "__main__":
3002 print(header("UDK Export PSK/PSA 2.6", 'CENTER'))