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