add support for reading lamp shadow-color, distance, cast-shadow settings.
[blender-addons.git] / io_export_unreal_psk_psa.py
blob787ca24dcc11fed293bcda24f52858d7cbdc7f5e
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 =============================
19 bl_info = {
20 "name": "Export Unreal Engine Format(.psk/.psa)",
21 "author": "Darknet/Optimus_P-Fat/Active_Trash/Sinsoft/VendorX/Spoof",
22 "version": (2, 7),
23 "blender": (2, 65, 4),
24 "location": "File > Export > Skeletal Mesh/Animation Data (.psk/.psa)",
25 "description": "Export Skeleletal Mesh/Animation Data",
26 "warning": "",
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"}
33 """
34 -- Unreal Skeletal Mesh and Animation Export (.psk and .psa) export script v0.0.1 --<br>
36 - NOTES:
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>
40 - v0.0.1
41 - Initial version
43 - v0.0.2
44 - This version adds support for more than one material index!
46 [ - Edit by: Darknet
47 - v0.0.3 - v0.0.12
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.
56 - v0.0.13
57 - The animatoin will support different bone rotations when export the animation.
59 - v0.0.14
60 - Fixed Action set keys frames when there is no pose keys and it will ignore it.
62 - v0.0.15
63 - Fixed multiple objects when exporting to psk. Select one mesh to export to psk.
64 - ]
66 - v0.1.1
67 - Blender 2.50 svn (Support)
69 Credit to:
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.
78 - http://sinsoft.com
79 """
82 #===========================================================================
83 """
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! *
89 TODO
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
95 CHANGES
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
110 USAGE
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 #===========================================================================
141 import bmesh
142 import os
143 import time
144 import bpy
145 import mathutils
146 import math
147 import random
148 import operator
149 import sys
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
161 SIZE_FQUAT = 16
162 SIZE_FVECTOR = 12
163 SIZE_VJOINTPOS = 44
164 SIZE_ANIMINFOBINARY = 168
165 SIZE_VCHUNKHEADER = 32
166 SIZE_VMATERIAL = 88
167 SIZE_VBONE = 120
168 SIZE_FNAMEDBONEBINARY = 120
169 SIZE_VRAWBONEINFLUENCE = 12
170 SIZE_VQUATANIMKEY = 32
171 SIZE_VVERTEX = 16
172 SIZE_VPOINT = 12
173 SIZE_VTRIANGLE = 12
175 MaterialName = []
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:
193 return
194 elif iteration == max_iterations:
195 print(msg_truncated)
196 return
198 print(msg)
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)
211 else:
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 #===========================================================================
220 class ObjMap:
222 def __init__(self):
223 self.dict = {}
224 self.next = 0
226 def get(self, obj):
227 if obj in self.dict:
228 return self.dict[obj]
229 else:
230 id = self.next
231 self.next = self.next + 1
232 self.dict[obj] = id
233 return id
235 def items(self):
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 #===========================================================================
245 class FQuat:
247 def __init__(self):
248 self.X = 0.0
249 self.Y = 0.0
250 self.Z = 0.0
251 self.W = 1.0
253 def dump(self):
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)
262 def __hash__(self):
263 return hash(self.X) ^ hash(self.Y) ^ hash(self.Z) ^ hash(self.W)
265 def __str__(self):
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):
271 self.X = X
272 self.Y = Y
273 self.Z = Z
275 def dump(self):
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)
283 def _key(self):
284 return (type(self).__name__, self.X, self.Y, self.Z)
286 def __hash__(self):
287 return hash(self._key())
289 def __eq__(self, other):
290 if not hasattr(other, '_key'):
291 return False
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,
304 self.Y - other.Y,
305 self.Z - other.Z)
307 class VJointPos:
309 def __init__(self):
310 self.Orientation = FQuat()
311 self.Position = FVector()
312 self.Length = 0.0
313 self.XSize = 0.0
314 self.YSize = 0.0
315 self.ZSize = 0.0
317 def dump(self):
318 return self.Orientation.dump() + self.Position.dump() + pack('4f', self.Length, self.XSize, self.YSize, self.ZSize)
320 class AnimInfoBinary:
322 def __init__(self):
323 self.Name = "" # length=64
324 self.Group = "" # length=64
325 self.TotalBones = 0
326 self.RootInclude = 0
327 self.KeyCompressionStyle = 0
328 self.KeyQuotum = 0
329 self.KeyPrediction = 0.0
330 self.TrackTime = 0.0
331 self.AnimRate = 0.0
332 self.StartBone = 0
333 self.FirstRawFrame = 0
334 self.NumRawFrames = 0
336 def dump(self):
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)
339 class VChunkHeader:
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
345 self.DataCount = 0
347 def dump(self):
348 return pack('20siii', self.ChunkID, self.TypeFlag, self.DataSize, self.DataCount)
350 class VMaterial:
352 def __init__(self):
353 self.MaterialName = "" # length=64
354 self.TextureIndex = 0
355 self.PolyFlags = 0 # DWORD
356 self.AuxMaterial = 0
357 self.AuxFlags = 0 # DWORD
358 self.LodBias = 0
359 self.LodStyle = 0
361 def dump(self):
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)
365 class VBone:
367 def __init__(self):
368 self.Name = "" # length = 64
369 self.Flags = 0 # DWORD
370 self.NumChildren = 0
371 self.ParentIndex = 0
372 self.BonePos = VJointPos()
374 def dump(self):
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:
380 def __init__(self):
381 self.Name = "" # length = 64
382 self.Flags = 0 # DWORD
383 self.NumChildren = 0
384 self.ParentIndex = 0
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
388 def dump(self):
389 return pack('64sLii', str.encode(self.Name), self.Flags, self.NumChildren, self.ParentIndex) + self.BonePos.dump()
391 class VRawBoneInfluence:
393 def __init__(self):
394 self.Weight = 0.0
395 self.PointIndex = 0
396 self.BoneIndex = 0
398 def dump(self):
399 return pack('fii', self.Weight, self.PointIndex, self.BoneIndex)
401 class VQuatAnimKey:
403 def __init__(self):
404 self.Position = FVector()
405 self.Orientation = FQuat()
406 self.Time = 0.0
408 def dump(self):
409 return self.Position.dump() + self.Orientation.dump() + pack('f', self.Time)
411 class VVertex(object):
413 def __init__(self):
414 self.PointIndex = 0 # WORD
415 self.U = 0.0
416 self.V = 0.0
417 self.MatIndex = 0 # BYTE
418 self.Reserved = 0 # BYTE
419 self.SmoothGroup = 0
421 def dump(self):
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 )
432 def _key(self):
433 return (type(self).__name__, self.PointIndex, self.U, self.V, self.MatIndex, self.Reserved)
435 def __hash__(self):
436 return hash(self._key())
438 def __eq__(self, other):
439 if not hasattr(other, '_key'):
440 return False
441 return self._key() == other._key()
443 class VPointSimple:
445 def __init__(self):
446 self.Point = FVector()
448 def __cmp__(self, other):
449 return cmp(self.Point, other.Point)
451 def __hash__(self):
452 return hash(self._key())
454 def _key(self):
455 return (type(self).__name__, self.Point)
457 def __eq__(self, other):
458 if not hasattr(other, '_key'):
459 return False
460 return self._key() == other._key()
462 class VPoint(object):
464 def __init__(self):
465 self.Point = FVector()
466 self.SmoothGroup = 0
468 def dump(self):
469 return self.Point.dump()
471 def __cmp__(self, other):
472 return cmp(self.Point, other.Point) \
473 or cmp(self.SmoothGroup, other.SmoothGroup)
475 def _key(self):
476 return (type(self).__name__, self.Point, self.SmoothGroup)
478 def __hash__(self):
479 return hash(self._key()) \
480 ^ hash(self.SmoothGroup)
482 def __eq__(self, other):
483 if not hasattr(other, '_key'):
484 return False
485 return self._key() == other._key()
487 class VTriangle:
489 def __init__(self):
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
497 def dump(self):
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 #===========================================================================
509 class FileSection:
511 def __init__(self, name, type_size):
512 self.Header = VChunkHeader(name, type_size)
513 self.Data = [] # list of datatypes
515 def dump(self):
516 data = self.Header.dump()
517 for i in range(len(self.Data)):
518 data = data + self.Data[i].dump()
519 return data
521 def UpdateHeader(self):
522 self.Header.DataCount = len(self.Data)
524 #===========================================================================
525 # PSK
526 #===========================================================================
527 class PSKFile:
529 def __init__(self):
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
545 # Layout:
546 # { groupname : [ (index, weight), ... ], ... }
548 # example:
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()
579 def dump(self):
580 self.UpdateHeaders()
581 data = self.GeneralHeader.dump() + self.Points.dump() + self.Wedges.dump() + self.Faces.dump() + self.Materials.dump() + self.Bones.dump() + self.Influences.dump()
582 return data
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]
587 else:
588 m = VMaterial()
589 # modified by VendorX
590 m.MaterialName = MaterialName[mat_index]
591 self.AddMaterial(m)
592 return m
594 def PrintOut(self):
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 #===========================================================================
603 # PSA
605 # Notes from UDN:
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 #===========================================================================
618 class PSAFile:
620 def __init__(self):
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)
626 # THIS IS NOT DUMPED
627 self.BoneLookup = {}
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]
647 def IsEmpty(self):
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])
662 return bone_data[0]
664 def GetBoneByName(self, bone_name):
665 if bone_name in self.BoneLookup:
666 bone_data = self.BoneLookup[bone_name]
667 return bone_data[1]
669 def GetBoneIndex(self, bone_name):
670 if bone_name in self.BoneLookup:
671 bone_data = self.BoneLookup[bone_name]
672 return bone_data[0]
674 def dump(self):
675 self.UpdateHeaders()
676 return self.GeneralHeader.dump() + self.Bones.dump() + self.Animations.dump() + self.RawKeys.dump()
678 def PrintOut(self):
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 ):
687 bone = VBone()
688 bone.Name = name
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
700 return bone
702 def make_namedbonebinary( name, parent_index, child_count, orientation_quat, position_vect, is_real ):
703 bone = FNamedBoneBinary()
704 bone.Name = name
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
712 return bone
714 def make_fquat( bquat ):
715 quat = FQuat()
716 #flip handedness for UT = set x,y,z to negative (rotate in other direction)
717 quat.X = -bquat.x
718 quat.Y = -bquat.y
719 quat.Z = -bquat.z
720 quat.W = bquat.w
721 return quat
723 def make_fquat_default( bquat ):
724 quat = FQuat()
725 #print(dir(bquat))
726 quat.X = bquat.x
727 quat.Y = bquat.y
728 quat.Z = bquat.z
729 quat.W = bquat.w
730 return quat
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)
744 return False
746 #===========================================================================
747 # Smoothing group
748 # (renamed to seperate it from VVertex.SmoothGroup)
749 #===========================================================================
750 class SmoothingGroup:
752 static_id = 1
754 def __init__(self):
755 self.faces = []
756 self.neighboring_faces = []
757 self.neighboring_groups = []
758 self.id = -1
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 )
765 return -1
767 def __hash__(self):
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):
772 temp_id = 1
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
777 else:
778 raise Error("Smoothing Group ID Overflowed, Smoothing Group evidently has more than 31 neighboring groups")
780 self.id = temp_id
781 return self.id
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:
817 v = edge.vertices
818 if key[0] == v[0] and key[1] == v[1]:
819 return edge.index
821 def add_face_to_smoothgroup( mesh, face, edge_sharing_list, smoothgroup ):
823 if face in smoothgroup.faces:
824 return
826 smoothgroup.add_face(face)
828 for key in face.edge_keys:
830 edge_id = find_edges(mesh, key)
832 if edge_id != None:
834 # not sharp
835 if not( mesh.edges[edge_id].use_edge_sharp):
837 for shared_face in edge_sharing_list[key]:
838 if shared_face != face:
839 # recursive
840 add_face_to_smoothgroup( mesh, shared_face, edge_sharing_list, smoothgroup )
841 # sharp
842 else:
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):
851 return
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...")
875 t = time.clock()
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:
884 #print(dir(face))
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='')
889 sys.stdout.flush()
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"))
909 #print(type(object))
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
920 me_ob.select = True
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()
939 return me_ob
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)):
962 if count == 0:
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...")
974 centermesh = []
975 notcentermesh = []
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])
982 selectmesh = []
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
990 else:
991 return meshmerge(selectmesh) #return merge object mesh
992 import binascii
993 #===========================================================================
994 # parse_mesh
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
1004 setmesh = 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...")
1014 mat_slot_index = 0
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
1028 mat_slot_index += 1
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
1037 wedges = ObjMap()
1038 points = ObjMap() #vertex
1039 points_linked = {}
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
1056 break
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):
1077 wedge_list = []
1078 vect_list = []
1080 #get or create the current material
1081 psk.GetMatByIndex(object_material_index)
1083 face_index = face.index
1084 has_uv = False
1085 face_uv = None
1087 if len(mesh.data.uv_textures) > 0:
1088 has_uv = True
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]))
1094 for i in range(3):
1095 vert_index = face.vertices[i]
1096 vert = mesh.data.vertices[vert_index]
1097 uv = []
1098 #assumes 3 UVs Per face (for now)
1099 if (has_uv):
1100 if len(face_uv.uv) != 3:
1101 print("WARNING: face has more or less than 3 UV coordinates - writing 0,0...")
1102 uv = [0.0, 0.0]
1103 else:
1104 uv = [face_uv.uv[i][0],face_uv.uv[i][1]] #OR bottom works better # 24 for cube
1105 else:
1106 #print ("No UVs?")
1107 uv = [0.0, 0.0]
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
1111 uv[1] = 1.0 - uv[1]
1113 # clamp UV coords if udk_option_clamp_uv is True
1114 if bpy.context.scene.udk_option_clamp_uv:
1115 if (uv[0] > 1):
1116 uv[0] = 1
1117 if (uv[0] < 0):
1118 uv[0] = 0
1119 if (uv[1] > 1):
1120 uv[1] = 1
1121 if (uv[1] < 0):
1122 uv[1] = 0
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:
1134 #print("OK!")
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)
1139 # Create the point
1140 p = VPoint()
1141 p.Point.X = vpos.x
1142 p.Point.Y = vpos.y
1143 p.Point.Z = vpos.z
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)
1155 else:
1156 points_linked[lPoint] = [p]
1158 # Create the wedge
1159 w = VVertex()
1160 w.MatIndex = object_material_index
1161 w.PointIndex = points.get(p) # store keys
1162 w.U = uv[0]
1163 w.V = uv[1]
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)
1169 #print results
1170 #print("result PointIndex={}, U={:.6f}, V={:.6f}, wedge_index={}".format(
1171 # w.PointIndex,
1172 # w.U,
1173 # w.V,
1174 # index_wedge))
1176 #END for i in range(3)
1178 # Determine face vertex order
1180 # TODO: convert to Blender.Mathutils
1181 # get normal from blender
1182 no = face.normal
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)
1192 tri = VTriangle()
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
1197 if dot > 0:
1198 (tri.WedgeIndex2, tri.WedgeIndex1, tri.WedgeIndex0) = wedge_list
1199 elif dot < 0:
1200 (tri.WedgeIndex0, tri.WedgeIndex1, tri.WedgeIndex2) = wedge_list
1201 else:
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)
1212 face.select = True
1213 if face.use_smooth == True:
1214 tri.SmoothingGroups = 1
1215 else:
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")
1223 psk.AddFace(tri)
1225 #END if not is_1d_face(current_face, mesh.data)
1227 else:
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():
1235 psk.AddPoint(point)
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():
1243 psk.AddWedge(wedge)
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
1254 #the bone
1256 #[print(x, len(points_linked[x])) for x in points_linked]
1257 #print("pointsindex length ",len(points_linked))
1258 #vertex group
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))
1267 vertex_list = []
1269 # all vertices in the mesh...
1270 for vertex in mesh.data.vertices:
1271 #print(dir(vertex))
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
1276 p = VPointSimple()
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
1282 p.Point.X = vpos.x
1283 p.Point.Y = vpos.y
1284 p.Point.Z = vpos.z
1285 #print(p)
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!")
1294 pass
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]
1327 udk_bones = []
1328 BoneUtil.static_bone_id = 0 # replaces global
1330 # traverse bone chain
1331 print("{: <3} {: <48} {: <20}".format("ID", "Bone", "Status"))
1332 print()
1333 recurse_bone(udk_root_bone, udk_bones, psk, psa, 0, armature.matrix_local)
1335 # final validation
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
1341 return udk_bones
1343 #===========================================================================
1344 # bone current bone
1345 # bones bone list
1346 # psk the PSK file object
1347 # psa the PSA file object
1348 # parent_id
1349 # parent_matrix
1350 # indent text indent for recursive log
1351 #===========================================================================
1352 def recurse_bone( bone, bones, psk, psa, parent_id, parent_matrix, indent="" ):
1354 status = "Ok"
1356 bones.append(bone);
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
1370 else:
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)
1400 else:
1401 status = "No vertex group"
1402 #FIXME overwriting previous status error?
1404 print("{:<3} {:<48} {:<20}".format(bone_id, indent+bone.name, status))
1406 #bone.matrix_local
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?
1413 class BoneUtil:
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)))
1432 print()
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...")
1435 return
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
1441 # action loop...
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))
1448 continue
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?
1476 udk_pose_bones = []
1477 for b in udk_bones:
1478 for pb in armature.pose.bones:
1479 if b.name == pb.name:
1480 udk_pose_bones.append(pb)
1481 break;
1483 # sort in the order the bones appear in the PSA file
1484 ordered_bones = {}
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?
1490 # frame loop...
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)
1516 else:
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"
1534 psa.AddRawKey(vkey)
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
1551 # restore
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!")
1567 bready = False
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:
1570 bready = True
1571 print("Added Action Set:",action.name)
1572 break
1573 if bready == False:#don't export it
1574 print("Skipping Action Set:",action.name)
1575 continue
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
1590 armature = None
1591 mesh = None
1593 # TODO:
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:
1603 armature = obj
1604 break
1605 else:
1606 raise Error("There is no Armature in the list!")
1607 meshselected = []
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']
1610 for obj in meshes:
1611 #print(dir(obj))
1612 if obj.type == 'MESH':
1613 bexportmesh = False
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:
1617 bexportmesh = True
1618 break
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
1627 else:
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)
1631 else:
1632 print("Number of meshes:",len(meshes))
1633 print("Number of meshes (selected):",len(meshes))
1634 if len(meshes) == 1:
1635 mesh = meshes[0]
1637 elif len(meshes) > 1:
1638 if len(meshselected) >= 1:
1639 mesh = sortmesh(meshselected)
1640 else:
1641 raise Error("More than one mesh(s) parented to armature. Select object(s)!")
1642 else:
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
1651 else:
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
1658 barmselect = False
1659 for _armobj in all_armatures:
1660 if _armobj.select:
1661 armature = _armobj
1662 barmselect = True
1663 break
1664 if barmselect == False:
1665 raise Error("Please select an armatures in the scene")
1666 else:
1667 raise Error("No armatures in scene")
1669 verbose("Found armature: {}".format(armature.name))
1671 meshselected = []
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:
1678 #print(dir(obj))
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
1685 else:
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)
1689 else:
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)
1698 else:
1699 raise Error("More than one mesh(s) parented to armature. Select object(s)!")
1700 else:
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!")
1708 #else:
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...")
1715 #if deform mesh
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.
1725 # UNUSED
1726 #===========================================================================
1727 def collate_vertex_groups( mesh ):
1728 verbose("collate_vertex_groups")
1729 groups = []
1731 for group in mesh.vertex_groups:
1733 groups.append(group)
1734 verbose(" " + group.name)
1736 return groups
1738 #===========================================================================
1739 # Main
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
1744 t = time.clock()
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)")
1769 # prep
1770 psk = PSKFile()
1771 psa = PSAFile()
1773 # step 1
1774 parse_mesh(udk_mesh, psk)
1776 # step 2
1777 udk_bones = parse_armature(udk_armature, psk, psa)
1779 # step 3
1780 if context.scene.udk_option_export_psa == True:
1781 actions = collate_actions()
1782 parse_animation(udk_armature, udk_bones, actions, psa)
1784 # write files
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...")
1792 psk.PrintOut()
1793 file = open(psk_filename, "wb")
1794 file.write(psk.dump())
1795 file.close()
1796 print("Exported: " + psk_filename)
1797 print()
1799 if context.scene.udk_option_export_psa == True:
1800 print("Animation data...")
1801 if not psa.IsEmpty():
1802 psa.PrintOut()
1803 file = open(psa_filename, "wb")
1804 file.write(psa.dump())
1805 file.close()
1806 print("Exported: " + psa_filename)
1807 else:
1808 print("No Animation (.psa file) to export")
1810 print()
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 #===========================================================================
1823 # Operator
1824 #===========================================================================
1825 class Operator_UDKExport( bpy.types.Operator ):
1826 """Export to UDK"""
1827 bl_idname = "object.udk_export"
1828 bl_label = "Export now"
1830 def execute(self, context):
1831 print( "\n"*8 )
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()
1840 # cache settings
1841 restore_frame = scene.frame_current
1843 message = "Finish Export!"
1844 try:
1845 export(filepath)
1847 except Error as err:
1848 print(err.message)
1849 message = err.message
1851 # restore settings
1852 scene.frame_set(restore_frame)
1854 self.report({'ERROR'}, message)
1856 # restore settings
1857 scene.frame_set(restore_frame)
1859 return {'FINISHED'}
1861 #===========================================================================
1862 # Operator
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()
1874 return {'FINISHED'}
1876 #===========================================================================
1877 # Get filepath for export
1878 #===========================================================================
1879 def get_dst_path():
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"
1883 else:
1884 #path = os.path.split(bpy.data.filepath)[0] + "\\" + "Unknown";
1885 path = os.path.splitext(bpy.data.filepath)[0]# + ".psk"
1886 else:
1887 path = os.path.splitext(bpy.data.filepath)[0]# + ".psk"
1888 return path
1890 #Added by [MGVS]
1891 bpy.types.Scene.udk_option_filename_src = EnumProperty(
1892 name = "Filename",
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") ],
1896 default = '0')
1898 bpy.types.Scene.udk_option_export_psk = BoolProperty(
1899 name = "bool export psa",
1900 description = "Boolean for exporting psk format (Skeleton Mesh)",
1901 default = True)
1903 bpy.types.Scene.udk_option_export_psa = BoolProperty(
1904 name = "bool export psa",
1905 description = "Boolean for exporting psa format (Animation Data)",
1906 default = True)
1908 bpy.types.Scene.udk_option_clamp_uv = BoolProperty(
1909 name = "Clamp UV",
1910 description = "True is to limit Clamp UV co-ordinates to [0-1]. False is unrestricted (x,y). ",
1911 default = False)
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.",
1916 default = False)
1918 bpy.types.Scene.udk_option_export = EnumProperty(
1919 name = "Export",
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)") ],
1924 default = '2')
1926 bpy.types.Scene.udk_option_verbose = BoolProperty(
1927 name = "Verbose",
1928 description = "Verbose console output",
1929 default = False)
1931 bpy.types.Scene.udk_option_smoothing_groups = BoolProperty(
1932 name = "Smooth Groups",
1933 description = "Activate hard edges as smooth groups",
1934 default = True)
1936 bpy.types.Scene.udk_option_triangulate = BoolProperty(
1937 name = "Triangulate Mesh",
1938 description = "Convert Quads to Triangles",
1939 default = False)
1941 bpy.types.Scene.udk_option_selectanimations = BoolProperty(
1942 name = "Select Animation(s)",
1943 description = "Select animation(s) for export to psa file.",
1944 default = False)
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.",
1949 default = False)
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.",
1954 default = False)
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.",
1959 default = False)
1961 bpy.types.Scene.udk_option_scale = FloatProperty(
1962 name = "UDK Scale",
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.",
1964 default = 1)
1966 #===========================================================================
1967 # User interface
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):")
1977 bselected = False
1978 for obj in bpy.data.objects:
1979 if obj.type == 'MESH' and obj.select == True:
1980 smoothcount = 0
1981 flatcount = 0
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
1986 mesh = bmesh.new();
1987 mesh.from_mesh(obj.data)
1988 for face in mesh.faces:
1989 face.select = False
1990 for face in mesh.faces:
1991 if face.smooth == True:
1992 face.select = True
1993 smoothcount += 1
1994 else:
1995 flatcount += 1
1996 face.select = False
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)
2001 bselected = True
2002 break
2003 if bselected:
2004 print("Selected Face(s) Exectue!")
2005 self.report({'INFO'}, "Selected Face(s) Exectue!")
2006 else:
2007 print("Didn't select Mesh Object!")
2008 self.report({'INFO'}, "Didn't Select Mesh Object!")
2009 print("----------------------------------------")
2010 return{'FINISHED'}
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!")
2023 break
2024 return{'FINISHED'}
2026 def unpack_list(list_of_tuples):
2027 l = []
2028 for t in list_of_tuples:
2029 l.extend(t)
2030 return l
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
2037 obj.select = True
2038 bpy.context.scene.objects.active = obj
2040 me_ob = bpy.data.meshes.new(("Re_"+obj.name))
2041 mesh = obj.data
2042 faces = []
2043 verts = []
2044 smoothings = []
2045 uvfaces = []
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]
2053 uvs = []
2054 for uv in faceUV.uv:
2055 uvs.append((uv[0],uv[1]))
2056 uvfaces.append(uvs)
2057 #print((face.vertices[:]))
2058 if len(face.vertices) == 3:
2059 faces.extend([(face.vertices[0],face.vertices[1],face.vertices[2],0)])
2060 else:
2061 faces.extend([(face.vertices[0],face.vertices[1],face.vertices[2],face.vertices[3])])
2062 #vertex positions
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:
2068 vlist = []
2069 for v in mesh.vertices:
2070 for vg in v.groups:
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))
2118 matcount = 0
2119 #print("MATERIAL ID OREDER:")
2120 for mat in me_ob.materials:
2121 #print("-Material:",mat.name,"INDEX:",matcount)
2122 matcount += 1
2123 print("Mesh Object Name:",obmesh.name)
2124 bpy.context.scene.update()
2125 return obmesh
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...")
2136 bselected = False
2137 bpy.ops.object.mode_set(mode='OBJECT')
2138 for obj in bpy.data.objects:
2139 if obj.type == 'MESH' and obj.select == True:
2140 rebuildmesh(obj)
2141 self.report({'INFO'}, "Rebuild Mesh Finish!")
2142 print("Finish Mesh Build...")
2143 print("----------------------------------------")
2144 return{'FINISHED'}
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])
2163 else:
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]
2178 break
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)
2187 return ob_new
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...")
2199 bselected = False
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("----------------------------------------")
2206 return{'FINISHED'}
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="")
2289 #@classmethod
2290 #def poll(cls, context):
2291 # return context.active_object
2293 def draw(self, context):
2294 layout = self.layout
2295 path = get_dst_path()
2297 object_name = ""
2298 #if context.object:
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")
2306 row = layout.row()
2307 row.label(text="Active object: " + object_name)
2308 #layout.separator()
2309 layout.prop(context.scene, "udk_option_filename_src")
2310 row = layout.row()
2311 row.label(text=path)
2312 #layout.separator()
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
2346 objectl = []
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
2351 bfound_obj = False
2352 for _obj in my_objlist:
2353 if _obj.name == _objd.name and _obj.otype == _objd.type:
2354 _obj.bselect = _objd.select
2355 bfound_obj = True
2356 break
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
2363 removeobject = []
2364 for _udkobj in my_objlist:
2365 bfound_objv = False
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:
2368 bfound_objv = True
2369 break
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
2374 count = 0
2375 for _obj in my_objlist:
2376 if _obj.name == _item.name and _obj.otype == _item.otype:
2377 my_objlist.remove(count)
2378 break
2379 count += 1
2381 my_objlist = bpy.context.scene.udkmesh_list
2382 objectl = []
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
2387 bfound_obj = False
2388 for _obj in my_objlist:
2389 if _obj.name == _objd.name and _obj.otype == _objd.type:
2390 _obj.bselect = _objd.select
2391 bfound_obj = True
2392 break
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
2398 removeobject = []
2399 for _udkobj in my_objlist:
2400 bfound_objv = False
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:
2403 bfound_objv = True
2404 break
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
2409 count = 0
2410 for _obj in my_objlist:
2411 if _obj.name == _item.name and _obj.otype == _item.otype:
2412 my_objlist.remove(count)
2413 break
2414 count += 1
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):
2424 udkupdateobjects()
2425 return{'FINISHED'}
2427 def udkcheckmeshline():
2428 objmesh = None
2429 for obj in bpy.context.scene.objects:
2430 if obj.type == 'MESH' and obj.select == True:
2431 objmesh = obj
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
2437 wedges = ObjMap()
2438 points = ObjMap()
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
2443 if objmesh != None:
2444 print("found mesh")
2445 print(objmesh)
2446 print(objmesh.data.tessfaces)
2447 vertex_list = []
2448 for face in objmesh.data.tessfaces:
2449 wedge_list = []
2450 vect_list = []
2451 for i in range(3):
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
2456 p = VPoint()
2457 p.Point.X = vpos.x
2458 p.Point.Y = vpos.y
2459 p.Point.Z = vpos.z
2460 w = VVertex()
2461 w.PointIndex = points.get(p) # store keys
2462 index_wedge = wedges.get(w)
2463 wedge_list.append(index_wedge)
2464 no = face.normal
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)
2469 tri = VTriangle()
2470 if dot > 0:
2471 (tri.WedgeIndex2, tri.WedgeIndex1, tri.WedgeIndex0) = wedge_list
2472 elif dot < 0:
2473 (tri.WedgeIndex0, tri.WedgeIndex1, tri.WedgeIndex2) = wedge_list
2474 else:
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
2487 break
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"
2494 return message
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)
2505 return{'FINISHED'}
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
2518 bones = []
2519 armature = None
2520 armatures = []
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
2541 bfound = False
2542 count = 0
2543 for actionbone in action.groups:
2544 #print("Pose bone name: ",actionbone.name)
2545 for b in bones:
2546 if b == actionbone.name:
2547 count += 1
2548 #print(b," : ",actionbone.name)
2549 break
2550 for actionlist in my_sett:
2551 if action.name == actionlist.name:
2552 bactionfound = True
2553 if len(bones) == len(action.groups) == count:
2554 actionlist.bmatch = True
2555 else:
2556 actionlist.bmatch = False
2557 bfound = True
2558 break
2559 if bfound != True:
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
2566 else:
2567 my_item.bmatch = False
2568 removeactions = []
2569 #check action list and data actions
2570 for actionlist in bpy.context.scene.udkas_list:
2571 bfind = False
2572 notfound = 0
2573 for act in bpy.data.actions:
2574 if actionlist.name == act.name:
2575 bfind = True
2576 else:
2577 notfound += 1
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:
2585 actioncount = 0
2586 for actionlist in my_sett:
2587 #print("action name:",actionlist.name)
2588 if actionlist.name == actname:
2589 my_sett.remove(actioncount);
2590 break
2591 actioncount += 1
2592 return{'FINISHED'}
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",
2608 options={'HIDDEN'},
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
2618 @classmethod
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()
2631 # cache settings
2632 restore_frame = scene.frame_current
2634 message = "Finish Export!"
2635 try:
2636 export(filepath)
2638 except Error as err:
2639 print(err.message)
2640 message = err.message
2642 # restore settings
2643 scene.frame_set(restore_frame)
2645 self.report({'WARNING', 'INFO'}, message)
2646 return {'FINISHED'}
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 #===========================================================================
2658 # Entry
2659 #===========================================================================
2660 def register():
2661 #print("REGISTER")
2662 bpy.utils.register_module(__name__)
2663 bpy.types.INFO_MT_file_export.append(menu_func)
2665 def unregister():
2666 #print("UNREGISTER")
2667 bpy.utils.unregister_module(__name__)
2668 bpy.types.INFO_MT_file_export.remove(menu_func)
2670 if __name__ == "__main__":
2671 #print("\n"*4)
2672 print(header("UDK Export PSK/PSA 2.6", 'CENTER'))
2673 register()
2675 #loader
2676 #filename = "D:/Projects/BlenderScripts/io_export_udk_psa_psk_alpha.py"
2677 #exec(compile(open(filename).read(), filename, 'exec'))