Fix T58881: FBX error exporting tangent space
[blender-addons.git] / io_export_unreal_psk_psa.py
blob40840e90fc4da2aa93f45757de8652ba3b982665
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, 1),
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": "https://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.
51 It was to deal with skeleton mesh export for psk.
52 - Animation is fix for position, offset, rotation bone support one rotation direction when armature build.
53 - It will convert your mesh into triangular when exporting to psk file.
54 - Did not work with psa export yet.
56 - v0.0.13
57 - The animatoin will support different bone rotations when export the animation.
59 - v0.0.14
60 - Fixed Action set keys frames when there is no pose keys and it will ignore it.
62 - v0.0.15
63 - Fixed multiple objects when exporting to psk. Select one mesh to export to psk.
64 - ]
66 - v0.1.1
67 - Blender 2.50 svn (Support)
69 Credit to:
70 - export_cal3d.py (Position of the Bones Format)
71 - blender2md5.py (Animation Translation Format)
72 - export_obj.py (Blender 2.5/Pyhton 3.x Format)
74 - freenode #blendercoder -> user -> ideasman42
76 - Give Credit to those who work on this script.
78 - http://sinsoft.com
79 """
82 # ===========================================================================
83 """
84 NOTES for Jan 2012 refactor (Spoof)
86 * THIS IS A WORK IN PROGRESS. These modifications were originally
87 intended for internal use and are incomplete. Use at your own risk! *
89 TODO
91 - (Blender 2.62) changes to Matrix math
92 - (Blender 2.62) check for long names
93 - option to manually set the root bone for export
95 CHANGES
97 - new bone parsing to allow advanced rigging
98 - identification of armature and mesh
99 - removed the need to apply an action to the armature
100 - fixed anim rate to work correctly in UDK (no more FPS fudging)
101 - progress reporting while processing smooth groups
102 - more informative logging
103 - code refactor for clarity and modularity
104 - naming conventions unified to use lowercase_with_underscore
105 - C++ datatypes and PSK/PSA classes remain CamelCaseStyle for clarity
106 - names such as 'ut' and 'unreal' unified to 'udk'
107 - simplification of code structure
108 - removed legacy code paths
110 USAGE
112 This version of the exporter is more selective over which bones are considered
113 part of the UDK skeletal mesh, and allows greater flexibility for adding
114 control bones to aid in animation.
116 Taking advantage of this script requires the following methodology:
118 * Place all exportable bones into a bone hierarchy extending from a single
119 root. This root bone must have use_deform enabled. All other root bones
120 in the armature must disable use_deform. *
122 The script searches for a root bone with use_deform set true and considers all
123 bones parented to it as part of the UDK skeletal mesh. Thus only these bones
124 are exported and all other bones are ignored.
126 This removes many restrictions on the rigger/animator, who can add control
127 bone hierarchies to the rig, and keyframe any element into actions. With this
128 approach you can build complex animation rigs in a similar vein to the Rigify
129 add-on, by Nathan Vegdahl. However...
131 * Rigify is incompatible with this script *
133 Rigify interlaces deformer bones within a single hierarchy making it difficult
134 to deconstruct for export. It also splits some meta-rig bones into multiple
135 deformer bones (bad for optimising a game character). I had partial success
136 writing a parser for the structure, but it was taking too much time and,
137 considering the other issues with Rigify, it was abandoned.
139 # ===========================================================================
141 import bmesh
142 import os
143 import time
144 import bpy
145 import mathutils
146 import math
147 import operator
148 import sys
149 from bpy.props import (
150 BoolProperty,
151 CollectionProperty,
152 EnumProperty,
153 FloatProperty,
154 IntProperty,
155 StringProperty,
157 from bpy.types import (
158 Operator,
159 Panel,
160 UIList,
161 PropertyGroup,
162 AddonPreferences,
164 from struct import pack
167 # REFERENCE MATERIAL JUST IN CASE:
169 # U = x / sqrt(x^2 + y^2 + z^2)
170 # V = y / sqrt(x^2 + y^2 + z^2)
172 # Triangles specified counter clockwise for front face
174 # defines for sizeofs
175 SIZE_FQUAT = 16
176 SIZE_FVECTOR = 12
177 SIZE_VJOINTPOS = 44
178 SIZE_ANIMINFOBINARY = 168
179 SIZE_VCHUNKHEADER = 32
180 SIZE_VMATERIAL = 88
181 SIZE_VBONE = 120
182 SIZE_FNAMEDBONEBINARY = 120
183 SIZE_VRAWBONEINFLUENCE = 12
184 SIZE_VQUATANIMKEY = 32
185 SIZE_VVERTEX = 16
186 SIZE_VPOINT = 12
187 SIZE_VTRIANGLE = 12
189 MaterialName = []
192 # Python 3 doesn't have cmp anymore
193 def cmp(a, b):
194 return (a > b) - (a < b)
197 # ===========================================================================
198 # Custom exception class
199 # ===========================================================================
200 class Error(Exception):
202 def __init__(self, message):
203 self.message = message
206 # ===========================================================================
207 # Verbose logging with loop truncation
208 # ===========================================================================
209 def verbose(msg, iteration=-1, max_iterations=4, msg_truncated="..."):
211 if bpy.context.scene.udk_option_verbose is True:
212 # limit the number of times a loop can output messages
213 if iteration > max_iterations:
214 return
215 elif iteration == max_iterations:
216 print(msg_truncated)
217 return
219 print(msg)
222 # ===========================================================================
223 # Log header/separator
224 # ===========================================================================
225 def header(msg, justify='LEFT', spacer='_', cols=78):
227 if justify == 'LEFT':
228 s = '{:{spacer}<{cols}}'.format(msg + " ", spacer=spacer, cols=cols)
230 elif justify == 'RIGHT':
231 s = '{:{spacer}>{cols}}'.format(" " + msg, spacer=spacer, cols=cols)
233 else:
234 s = '{:{spacer}^{cols}}'.format(" " + msg + " ", spacer=spacer, cols=cols)
236 return "\n" + s + "\n"
239 # ===========================================================================
240 # Generic Object->Integer mapping
241 # the object must be usable as a dictionary key
242 # ===========================================================================
243 class ObjMap:
245 def __init__(self):
246 self.dict = {}
247 self.next = 0
249 def get(self, obj):
250 if obj in self.dict:
251 return self.dict[obj]
252 else:
253 id = self.next
254 self.next = self.next + 1
255 self.dict[obj] = id
256 return id
258 def items(self):
259 getval = operator.itemgetter(0)
260 getkey = operator.itemgetter(1)
261 return map(getval, sorted(self.dict.items(), key=getkey))
264 # ===========================================================================
265 # RG - UNREAL DATA STRUCTS - CONVERTED FROM C STRUCTS GIVEN ON UDN SITE
266 # provided here: http://udn.epicgames.com/Two/BinaryFormatSpecifications.html
267 # updated UDK (Unreal Engine 3): http://udn.epicgames.com/Three/BinaryFormatSpecifications.html
268 # ===========================================================================
269 class FQuat:
271 def __init__(self):
272 self.X = 0.0
273 self.Y = 0.0
274 self.Z = 0.0
275 self.W = 1.0
277 def dump(self):
278 return pack('ffff', self.X, self.Y, self.Z, self.W)
280 def __cmp__(self, other):
281 return cmp(self.X, other.X) or \
282 cmp(self.Y, other.Y) or \
283 cmp(self.Z, other.Z) or \
284 cmp(self.W, other.W)
286 def __hash__(self):
287 return hash(self.X) ^ hash(self.Y) ^ hash(self.Z) ^ hash(self.W)
289 def __str__(self):
290 return "[%f,%f,%f,%f](FQuat)" % (self.X, self.Y, self.Z, self.W)
293 class FVector(object):
295 def __init__(self, X=0.0, Y=0.0, Z=0.0):
296 self.X = X
297 self.Y = Y
298 self.Z = Z
300 def dump(self):
301 return pack('fff', self.X, self.Y, self.Z)
303 def __cmp__(self, other):
304 return cmp(self.X, other.X) or \
305 cmp(self.Y, other.Y) or \
306 cmp(self.Z, other.Z)
308 def _key(self):
309 return (type(self).__name__, self.X, self.Y, self.Z)
311 def __hash__(self):
312 return hash(self._key())
314 def __eq__(self, other):
315 if not hasattr(other, '_key'):
316 return False
317 return self._key() == other._key()
319 def dot(self, other):
320 return self.X * other.X + self.Y * other.Y + self.Z * other.Z
322 def cross(self, other):
323 return FVector(self.Y * other.Z - self.Z * other.Y,
324 self.Z * other.X - self.X * other.Z,
325 self.X * other.Y - self.Y * other.X)
327 def sub(self, other):
328 return FVector(self.X - other.X,
329 self.Y - other.Y,
330 self.Z - other.Z)
333 class VJointPos:
335 def __init__(self):
336 self.Orientation = FQuat()
337 self.Position = FVector()
338 self.Length = 0.0
339 self.XSize = 0.0
340 self.YSize = 0.0
341 self.ZSize = 0.0
343 def dump(self):
344 return self.Orientation.dump() + self.Position.dump() + \
345 pack('4f', self.Length, self.XSize, self.YSize, self.ZSize)
348 class AnimInfoBinary:
350 def __init__(self):
351 self.Name = "" # length=64
352 self.Group = "" # length=64
353 self.TotalBones = 0
354 self.RootInclude = 0
355 self.KeyCompressionStyle = 0
356 self.KeyQuotum = 0
357 self.KeyPrediction = 0.0
358 self.TrackTime = 0.0
359 self.AnimRate = 0.0
360 self.StartBone = 0
361 self.FirstRawFrame = 0
362 self.NumRawFrames = 0
364 def dump(self):
365 return pack('64s64siiiifffiii', str.encode(self.Name), str.encode(self.Group),
366 self.TotalBones, self.RootInclude, self.KeyCompressionStyle, self.KeyQuotum,
367 self.KeyPrediction, self.TrackTime, self.AnimRate, self.StartBone,
368 self.FirstRawFrame, self.NumRawFrames)
371 class VChunkHeader:
373 def __init__(self, name, type_size):
374 self.ChunkID = str.encode(name) # length=20
375 self.TypeFlag = 1999801 # special value
376 self.DataSize = type_size
377 self.DataCount = 0
379 def dump(self):
380 return pack('20siii', self.ChunkID, self.TypeFlag, self.DataSize, self.DataCount)
383 class VMaterial:
385 def __init__(self):
386 self.MaterialName = "" # length=64
387 self.TextureIndex = 0
388 self.PolyFlags = 0 # DWORD
389 self.AuxMaterial = 0
390 self.AuxFlags = 0 # DWORD
391 self.LodBias = 0
392 self.LodStyle = 0
394 def dump(self):
395 # print("DATA MATERIAL:",self.MaterialName)
396 return pack('64siLiLii', str.encode(self.MaterialName), self.TextureIndex,
397 self.PolyFlags, self.AuxMaterial, self.AuxFlags, self.LodBias, self.LodStyle)
400 class VBone:
402 def __init__(self):
403 self.Name = "" # length = 64
404 self.Flags = 0 # DWORD
405 self.NumChildren = 0
406 self.ParentIndex = 0
407 self.BonePos = VJointPos()
409 def dump(self):
410 return pack('64sLii', str.encode(self.Name), self.Flags,
411 self.NumChildren, self.ParentIndex) + self.BonePos.dump()
414 # same as above - whatever - this is how Epic does it...
415 class FNamedBoneBinary:
417 def __init__(self):
418 self.Name = "" # length = 64
419 self.Flags = 0 # DWORD
420 self.NumChildren = 0
421 self.ParentIndex = 0
422 self.BonePos = VJointPos()
423 self.IsRealBone = 0 # this is set to 1 when the bone is actually a bone in the mesh and not a dummy
425 def dump(self):
426 return pack('64sLii', str.encode(self.Name), self.Flags,
427 self.NumChildren, self.ParentIndex) + self.BonePos.dump()
430 class VRawBoneInfluence:
432 def __init__(self):
433 self.Weight = 0.0
434 self.PointIndex = 0
435 self.BoneIndex = 0
437 def dump(self):
438 return pack('fii', self.Weight, self.PointIndex, self.BoneIndex)
441 class VQuatAnimKey:
443 def __init__(self):
444 self.Position = FVector()
445 self.Orientation = FQuat()
446 self.Time = 0.0
448 def dump(self):
449 return self.Position.dump() + self.Orientation.dump() + pack('f', self.Time)
452 class VVertex(object):
454 def __init__(self):
455 self.PointIndex = 0 # WORD
456 self.U = 0.0
457 self.V = 0.0
458 self.MatIndex = 0 # BYTE
459 self.Reserved = 0 # BYTE
460 self.SmoothGroup = 0
462 def dump(self):
463 return pack('HHffBBH', self.PointIndex, 0, self.U, self.V, self.MatIndex, self.Reserved, 0)
465 def __cmp__(self, other):
466 return cmp(self.PointIndex, other.PointIndex) or\
467 cmp(self.U, other.U) or \
468 cmp(self.V, other.V) or \
469 cmp(self.MatIndex, other.MatIndex) or \
470 cmp(self.Reserved, other.Reserved) or \
471 cmp(self.SmoothGroup, other.SmoothGroup)
473 def _key(self):
474 return (type(self).__name__, self.PointIndex, self.U, self.V, self.MatIndex, self.Reserved)
476 def __hash__(self):
477 return hash(self._key())
479 def __eq__(self, other):
480 if not hasattr(other, '_key'):
481 return False
482 return self._key() == other._key()
485 class VPointSimple:
487 def __init__(self):
488 self.Point = FVector()
490 def __cmp__(self, other):
491 return cmp(self.Point, other.Point)
493 def __hash__(self):
494 return hash(self._key())
496 def _key(self):
497 return (type(self).__name__, self.Point)
499 def __eq__(self, other):
500 if not hasattr(other, '_key'):
501 return False
502 return self._key() == other._key()
505 class VPoint(object):
507 def __init__(self):
508 self.Point = FVector()
509 self.SmoothGroup = 0
511 def dump(self):
512 return self.Point.dump()
514 def __cmp__(self, other):
515 return cmp(self.Point, other.Point) \
516 or cmp(self.SmoothGroup, other.SmoothGroup)
518 def _key(self):
519 return (type(self).__name__, self.Point, self.SmoothGroup)
521 def __hash__(self):
522 return hash(self._key()) \
523 ^ hash(self.SmoothGroup)
525 def __eq__(self, other):
526 if not hasattr(other, '_key'):
527 return False
528 return self._key() == other._key()
531 class VTriangle:
533 def __init__(self):
534 self.WedgeIndex0 = 0 # WORD
535 self.WedgeIndex1 = 0 # WORD
536 self.WedgeIndex2 = 0 # WORD
537 self.MatIndex = 0 # BYTE
538 self.AuxMatIndex = 0 # BYTE
539 self.SmoothingGroups = 0 # DWORD
541 def dump(self):
542 return pack('HHHBBL', self.WedgeIndex0, self.WedgeIndex1, self.WedgeIndex2,
543 self.MatIndex, self.AuxMatIndex, self.SmoothingGroups)
545 print("smooth",self.SmoothingGroups)
546 return pack('HHHBBI', self.WedgeIndex0, self.WedgeIndex1, self.WedgeIndex2,
547 self.MatIndex, self.AuxMatIndex, self.SmoothingGroups)
549 # END UNREAL DATA STRUCTS
550 # ===========================================================================
553 # ===========================================================================
554 # RG - helper class to handle the normal way the UT files are stored
555 # as sections consisting of a header and then a list of data structures
556 # ===========================================================================
557 class FileSection:
559 def __init__(self, name, type_size):
560 self.Header = VChunkHeader(name, type_size)
561 self.Data = [] # list of datatypes
563 def dump(self):
564 data = self.Header.dump()
565 for i in range(len(self.Data)):
566 data = data + self.Data[i].dump()
567 return data
569 def UpdateHeader(self):
570 self.Header.DataCount = len(self.Data)
573 # ===========================================================================
574 # PSK
575 # ===========================================================================
576 class PSKFile:
578 def __init__(self):
579 self.GeneralHeader = VChunkHeader("ACTRHEAD", 0)
580 self.Points = FileSection("PNTS0000", SIZE_VPOINT) # VPoint
581 self.Wedges = FileSection("VTXW0000", SIZE_VVERTEX) # VVertex
582 self.Faces = FileSection("FACE0000", SIZE_VTRIANGLE) # VTriangle
583 self.Materials = FileSection("MATT0000", SIZE_VMATERIAL) # VMaterial
584 self.Bones = FileSection("REFSKELT", SIZE_VBONE) # VBone
585 self.Influences = FileSection("RAWWEIGHTS", SIZE_VRAWBONEINFLUENCE) # VRawBoneInfluence
587 # RG - this mapping is not dumped, but is used internally to store the new point indices
588 # for vertex groups calculated during the mesh dump, so they can be used again
589 # to dump bone influences during the armature dump
591 # the key in this dictionary is the VertexGroup/Bone Name, and the value
592 # is a list of tuples containing the new point index and the weight, in that order
594 # Layout:
595 # { groupname : [ (index, weight), ... ], ... }
597 # example:
598 # {'MyVertexGroup' : [ (0, 1.0), (5, 1.0), (3, 0.5) ] , 'OtherGroup' : [(2, 1.0)]}
600 self.VertexGroups = {}
602 def AddPoint(self, p):
603 self.Points.Data.append(p)
605 def AddWedge(self, w):
606 self.Wedges.Data.append(w)
608 def AddFace(self, f):
609 self.Faces.Data.append(f)
611 def AddMaterial(self, m):
612 self.Materials.Data.append(m)
614 def AddBone(self, b):
615 self.Bones.Data.append(b)
617 def AddInfluence(self, i):
618 self.Influences.Data.append(i)
620 def UpdateHeaders(self):
621 self.Points.UpdateHeader()
622 self.Wedges.UpdateHeader()
623 self.Faces.UpdateHeader()
624 self.Materials.UpdateHeader()
625 self.Bones.UpdateHeader()
626 self.Influences.UpdateHeader()
628 def dump(self):
629 self.UpdateHeaders()
630 data = self.GeneralHeader.dump() + self.Points.dump() + self.Wedges.dump() + \
631 self.Faces.dump() + self.Materials.dump() + self.Bones.dump() + self.Influences.dump()
632 return data
634 def GetMatByIndex(self, mat_index):
635 if mat_index >= 0 and len(self.Materials.Data) > mat_index:
636 return self.Materials.Data[mat_index]
637 else:
638 m = VMaterial()
639 # modified by VendorX
640 m.MaterialName = MaterialName[mat_index]
641 self.AddMaterial(m)
642 return m
644 def PrintOut(self):
645 print("{:>16} {:}".format("Points", len(self.Points.Data)))
646 print("{:>16} {:}".format("Wedges", len(self.Wedges.Data)))
647 print("{:>16} {:}".format("Faces", len(self.Faces.Data)))
648 print("{:>16} {:}".format("Materials", len(self.Materials.Data)))
649 print("{:>16} {:}".format("Bones", len(self.Bones.Data)))
650 print("{:>16} {:}".format("Influences", len(self.Influences.Data)))
653 # ===========================================================================
654 # PSA
656 # Notes from UDN:
657 # The raw key array holds all the keys for all the bones in all the specified sequences,
658 # organized as follows:
659 # For each AnimInfoBinary's sequence there are [Number of bones] times [Number of frames keys]
660 # in the VQuatAnimKeys, laid out as tracks of [numframes] keys for each bone in the order of
661 # the bones as defined in the array of FnamedBoneBinary in the PSA.
663 # Once the data from the PSK (now digested into native skeletal mesh) and PSA (digested into
664 # a native animation object containing one or more sequences) are associated together at runtime,
665 # bones are linked up by name. Any bone in a skeleton (from the PSK) that finds no partner in
666 # the animation sequence (from the PSA) will assume its reference pose stance ( as defined in
667 # the offsets & rotations that are in the VBones making up the reference skeleton from the PSK)
668 # ===========================================================================
669 class PSAFile:
671 def __init__(self):
672 self.GeneralHeader = VChunkHeader("ANIMHEAD", 0)
673 self.Bones = FileSection("BONENAMES", SIZE_FNAMEDBONEBINARY) # FNamedBoneBinary
674 self.Animations = FileSection("ANIMINFO", SIZE_ANIMINFOBINARY) # AnimInfoBinary
675 self.RawKeys = FileSection("ANIMKEYS", SIZE_VQUATANIMKEY) # VQuatAnimKey
676 # this will take the format of key=Bone Name, value = (BoneIndex, Bone Object)
677 # THIS IS NOT DUMPED
678 self.BoneLookup = {}
680 def AddBone(self, b):
681 self.Bones.Data.append(b)
683 def AddAnimation(self, a):
684 self.Animations.Data.append(a)
686 def AddRawKey(self, k):
687 self.RawKeys.Data.append(k)
689 def UpdateHeaders(self):
690 self.Bones.UpdateHeader()
691 self.Animations.UpdateHeader()
692 self.RawKeys.UpdateHeader()
694 def GetBoneByIndex(self, bone_index):
695 if bone_index >= 0 and len(self.Bones.Data) > bone_index:
696 return self.Bones.Data[bone_index]
698 def IsEmpty(self):
699 return (len(self.Bones.Data) == 0 or len(self.Animations.Data) == 0)
701 def StoreBone(self, b):
702 self.BoneLookup[b.Name] = [-1, b]
704 def UseBone(self, bone_name):
705 if bone_name in self.BoneLookup:
706 bone_data = self.BoneLookup[bone_name]
708 if bone_data[0] == -1:
709 bone_data[0] = len(self.Bones.Data)
710 self.AddBone(bone_data[1])
711 # self.Bones.Data.append(bone_data[1])
713 return bone_data[0]
715 def GetBoneByName(self, bone_name):
716 if bone_name in self.BoneLookup:
717 bone_data = self.BoneLookup[bone_name]
718 return bone_data[1]
720 def GetBoneIndex(self, bone_name):
721 if bone_name in self.BoneLookup:
722 bone_data = self.BoneLookup[bone_name]
723 return bone_data[0]
725 def dump(self):
726 self.UpdateHeaders()
727 return self.GeneralHeader.dump() + self.Bones.dump() + self.Animations.dump() + self.RawKeys.dump()
729 def PrintOut(self):
730 print("{:>16} {:}".format("Bones", len(self.Bones.Data)))
731 print("{:>16} {:}".format("Animations", len(self.Animations.Data)))
732 print("{:>16} {:}".format("Raw keys", len(self.RawKeys.Data)))
735 # ===========================================================================
736 # Helpers to create bone structs
737 # ===========================================================================
738 def make_vbone(name, parent_index, child_count, orientation_quat, position_vect):
739 bone = VBone()
740 bone.Name = name
741 bone.ParentIndex = parent_index
742 bone.NumChildren = child_count
743 bone.BonePos.Orientation = orientation_quat
744 bone.BonePos.Position.X = position_vect.x
745 bone.BonePos.Position.Y = position_vect.y
746 bone.BonePos.Position.Z = position_vect.z
747 # these values seem to be ignored?
748 # bone.BonePos.Length = tail.length
749 # bone.BonePos.XSize = tail.x
750 # bone.BonePos.YSize = tail.y
751 # bone.BonePos.ZSize = tail.z
752 return bone
755 def make_namedbonebinary(name, parent_index, child_count, orientation_quat, position_vect, is_real):
756 bone = FNamedBoneBinary()
757 bone.Name = name
758 bone.ParentIndex = parent_index
759 bone.NumChildren = child_count
760 bone.BonePos.Orientation = orientation_quat
761 bone.BonePos.Position.X = position_vect.x
762 bone.BonePos.Position.Y = position_vect.y
763 bone.BonePos.Position.Z = position_vect.z
764 bone.IsRealBone = is_real
765 return bone
768 def make_fquat(bquat):
769 quat = FQuat()
770 # flip handedness for UT = set x,y,z to negative (rotate in other direction)
771 quat.X = -bquat.x
772 quat.Y = -bquat.y
773 quat.Z = -bquat.z
774 quat.W = bquat.w
776 return quat
779 def make_fquat_default(bquat):
780 quat = FQuat()
781 # print(dir(bquat))
782 quat.X = bquat.x
783 quat.Y = bquat.y
784 quat.Z = bquat.z
785 quat.W = bquat.w
787 return quat
790 # ===========================================================================
791 # RG - check to make sure face isnt a line
792 # ===========================================================================
793 def is_1d_face(face, mesh):
794 # ID Vertex of id point
795 v0 = face.vertices[0]
796 v1 = face.vertices[1]
797 v2 = face.vertices[2]
799 return (mesh.vertices[v0].co == mesh.vertices[v1].co or
800 mesh.vertices[v1].co == mesh.vertices[v2].co or
801 mesh.vertices[v2].co == mesh.vertices[v0].co)
802 return False
805 # ===========================================================================
806 # Smoothing group
807 # (renamed to separate it from VVertex.SmoothGroup)
808 # ===========================================================================
809 class SmoothingGroup:
811 static_id = 1
813 def __init__(self):
814 self.faces = []
815 self.neighboring_faces = []
816 self.neighboring_groups = []
817 self.id = -1
818 self.local_id = SmoothingGroup.static_id
819 SmoothingGroup.static_id += 1
821 def __cmp__(self, other):
822 if isinstance(other, SmoothingGroup):
823 return cmp(self.local_id, other.local_id)
824 return -1
826 def __hash__(self):
827 return hash(self.local_id)
829 # searches neighboring faces to determine which smoothing group ID can be used
830 def get_valid_smoothgroup_id(self):
831 temp_id = 1
832 for group in self.neighboring_groups:
833 if group is not None and group.id == temp_id:
834 if temp_id < 0x80000000:
835 temp_id = temp_id << 1
836 else:
837 raise Error("Smoothing Group ID Overflowed, "
838 "Smoothing Group evidently has more than 31 neighboring groups")
840 self.id = temp_id
841 return self.id
843 def make_neighbor(self, new_neighbor):
844 if new_neighbor not in self.neighboring_groups:
845 self.neighboring_groups.append(new_neighbor)
847 def contains_face(self, face):
848 return (face in self.faces)
850 def add_neighbor_face(self, face):
851 if face not in self.neighboring_faces:
852 self.neighboring_faces.append(face)
854 def add_face(self, face):
855 if face not in self.faces:
856 self.faces.append(face)
859 def determine_edge_sharing(mesh):
861 edge_sharing_list = dict()
863 for edge in mesh.edges:
864 edge_sharing_list[edge.key] = []
866 for face in mesh.tessfaces:
867 for key in face.edge_keys:
868 if face not in edge_sharing_list[key]:
869 edge_sharing_list[key].append(face) # mark this face as sharing this edge
871 return edge_sharing_list
874 def find_edges(mesh, key):
875 """ Temp replacement for mesh.findEdges().
876 This is painfully slow.
878 for edge in mesh.edges:
879 v = edge.vertices
880 if key[0] == v[0] and key[1] == v[1]:
881 return edge.index
884 def add_face_to_smoothgroup(mesh, face, edge_sharing_list, smoothgroup):
886 if face in smoothgroup.faces:
887 return
889 smoothgroup.add_face(face)
891 for key in face.edge_keys:
893 edge_id = find_edges(mesh, key)
895 if edge_id is not None:
897 # not sharp
898 if not (mesh.edges[edge_id].use_edge_sharp):
900 for shared_face in edge_sharing_list[key]:
901 if shared_face != face:
902 # recursive
903 add_face_to_smoothgroup(mesh, shared_face, edge_sharing_list, smoothgroup)
904 # sharp
905 else:
906 for shared_face in edge_sharing_list[key]:
907 if shared_face != face:
908 smoothgroup.add_neighbor_face(shared_face)
911 def determine_smoothgroup_for_face(mesh, face, edge_sharing_list, smoothgroup_list):
913 for group in smoothgroup_list:
914 if (face in group.faces):
915 return
917 smoothgroup = SmoothingGroup()
918 add_face_to_smoothgroup(mesh, face, edge_sharing_list, smoothgroup)
920 if smoothgroup not in smoothgroup_list:
921 smoothgroup_list.append(smoothgroup)
924 def build_neighbors_tree(smoothgroup_list):
926 for group in smoothgroup_list:
927 for face in group.neighboring_faces:
928 for neighbor_group in smoothgroup_list:
929 if neighbor_group.contains_face(face) and neighbor_group not in group.neighboring_groups:
930 group.make_neighbor(neighbor_group)
931 neighbor_group.make_neighbor(group)
934 # ===========================================================================
935 # parse_smooth_groups
936 # ===========================================================================
937 def parse_smooth_groups(mesh):
939 print("Parsing smooth groups...")
941 t = time.clock()
942 smoothgroup_list = []
943 edge_sharing_list = determine_edge_sharing(mesh)
945 # print("faces:",len(mesh.tessfaces))
946 interval = math.floor(len(mesh.tessfaces) / 100)
948 if interval == 0: # if the faces are few do this
949 interval = math.floor(len(mesh.tessfaces) / 10)
950 # print("FACES:",len(mesh.tessfaces),"//100 =" "interval:",interval)
952 for face in mesh.tessfaces:
953 # print(dir(face))
954 determine_smoothgroup_for_face(mesh, face, edge_sharing_list, smoothgroup_list)
955 # progress indicator, writes to console without scrolling
956 if face.index > 0 and (face.index % interval) == 0:
957 print("Processing... {}%\r".format(int(face.index / len(mesh.tessfaces) * 100)), end='')
958 sys.stdout.flush()
959 print("Completed", ' ' * 20)
961 verbose("len(smoothgroup_list)={}".format(len(smoothgroup_list)))
963 build_neighbors_tree(smoothgroup_list)
965 for group in smoothgroup_list:
966 group.get_valid_smoothgroup_id()
968 print("Smooth group parsing completed in {:.2f}s".format(time.clock() - t))
969 return smoothgroup_list
972 # ===========================================================================
973 # http://en.wikibooks.org/wiki/Blender_3D:_Blending_Into_Python/Cookbook#Triangulate_NMesh
974 # blender 2.50 format using the Operators/command convert the mesh to tri mesh
975 # ===========================================================================
976 def triangulate_mesh(object):
978 verbose(header("triangulateNMesh"))
979 # print(type(object))
980 scene = bpy.context.scene
982 me_ob = object.copy()
983 me_ob.data = object.to_mesh(bpy.context.scene, True, 'PREVIEW') # write data object
984 bpy.context.scene.objects.link(me_ob)
985 bpy.context.scene.update()
986 bpy.ops.object.mode_set(mode='OBJECT')
988 for i in scene.objects:
989 i.select = False # deselect all objects
991 me_ob.select = True
992 scene.objects.active = me_ob
994 print("Copy and Convert mesh just incase any way...")
996 bpy.ops.object.mode_set(mode='EDIT')
997 bpy.ops.mesh.select_all(action='SELECT') # select all the face/vertex/edge
998 bpy.ops.object.mode_set(mode='EDIT')
999 bpy.ops.mesh.quads_convert_to_tris()
1000 bpy.context.scene.update()
1002 bpy.ops.object.mode_set(mode='OBJECT')
1004 bpy.context.scene.udk_option_triangulate = True
1006 verbose("Triangulated mesh")
1008 me_ob.data = me_ob.to_mesh(bpy.context.scene, True, 'PREVIEW') # write data object
1009 bpy.context.scene.update()
1010 return me_ob
1013 # copy mesh data and then merge them into one object
1014 def meshmerge(selectedobjects):
1015 bpy.ops.object.mode_set(mode='OBJECT') # object mode and not edit mode
1016 cloneobjects = [] # object holder for copying object data
1018 if len(selectedobjects) > 1:
1019 print("selectedobjects:", len(selectedobjects)) # print select object
1020 count = 0 # reset count
1022 for count in range(len(selectedobjects)):
1023 # print("Index:",count)
1024 if selectedobjects[count] is not None:
1025 me_da = selectedobjects[count].data.copy() # copy data
1026 me_ob = selectedobjects[count].copy() # copy object
1027 # note two copy two types else it will use the current data or mesh
1028 me_ob.data = me_da # assign the data
1029 bpy.context.scene.objects.link(me_ob) # link the object to the scene #current object location
1030 print("Index:", count, "clone object", me_ob.name) # print clone object
1031 cloneobjects.append(me_ob) # add object to the array
1033 for i in bpy.data.objects:
1034 i.select = False # deselect all objects
1035 count = 0 # reset count
1036 # begin merging the mesh together as one
1037 for count in range(len(cloneobjects)):
1038 if count == 0:
1039 bpy.context.scene.objects.active = cloneobjects[count]
1040 print("Set Active Object:", cloneobjects[count].name)
1041 cloneobjects[count].select = True
1042 bpy.ops.object.join() # join object together
1043 if len(cloneobjects) > 1:
1044 bpy.types.Scene.udk_copy_merge = True
1045 return cloneobjects[0]
1048 # sort the mesh center top list and not center at the last array.
1049 # Base on order while select to merge mesh to make them center.
1050 def sortmesh(selectmesh):
1051 print("MESH SORTING...")
1052 centermesh = []
1053 notcentermesh = []
1054 for countm in range(len(selectmesh)):
1055 # if object are center add here
1056 if selectmesh[countm].location.x == 0 and \
1057 selectmesh[countm].location.y == 0 and \
1058 selectmesh[countm].location.z == 0:
1059 centermesh.append(selectmesh[countm])
1060 else: # if not add here for not center
1061 notcentermesh.append(selectmesh[countm])
1062 selectmesh = []
1063 # add mesh object in order for merge object
1064 for countm in range(len(centermesh)):
1065 selectmesh.append(centermesh[countm])
1066 for countm in range(len(notcentermesh)):
1067 selectmesh.append(notcentermesh[countm])
1068 if len(selectmesh) == 1: # if there one mesh just do some here
1069 return selectmesh[0] # return object mesh
1070 else:
1071 return meshmerge(selectmesh) # return merge object mesh
1074 import binascii
1077 # ===========================================================================
1078 # parse_mesh
1079 # ===========================================================================
1080 def parse_mesh(mesh, psk):
1081 # bpy.ops.object.mode_set(mode='OBJECT')
1082 # error ? on commands for select object?
1083 print(header("MESH", 'RIGHT'))
1084 print("Mesh object:", mesh.name)
1085 scene = bpy.context.scene
1087 for i in scene.objects:
1088 i.select = False # deselect all objects
1090 scene.objects.active = mesh
1091 setmesh = mesh
1092 mesh = triangulate_mesh(mesh)
1094 if bpy.types.Scene.udk_copy_merge is True:
1095 bpy.context.scene.objects.unlink(setmesh)
1097 # print("FACES----:",len(mesh.data.tessfaces))
1098 verbose("Working mesh object: {}".format(mesh.name))
1100 # collect a list of the material names
1101 print("Materials...")
1103 mat_slot_index = 0
1105 for slot in mesh.material_slots:
1107 print(" Material {} '{}'".format(mat_slot_index, slot.name))
1108 MaterialName.append(slot.name)
1111 if slot.material.texture_slots[0] is not None:
1112 if slot.material.texture_slots[0].texture.image.filepath is not None:
1113 print(" Texture path {}".format(slot.material.texture_slots[0].texture.image.filepath))
1116 # create the current material
1117 v_material = psk.GetMatByIndex(mat_slot_index)
1118 v_material.MaterialName = slot.name
1119 v_material.TextureIndex = mat_slot_index
1120 v_material.AuxMaterial = mat_slot_index
1121 mat_slot_index += 1
1122 verbose(" PSK index {}".format(v_material.TextureIndex))
1124 # END slot in mesh.material_slots
1126 # object_mat = mesh.materials[0]
1127 # object_material_index = mesh.active_material_index
1128 # FIXME ^ this is redundant due to "= face.material_index" in face loop
1130 wedges = ObjMap()
1131 points = ObjMap() # vertex
1132 points_linked = {}
1134 discarded_face_count = 0
1135 sys.setrecursionlimit(1000000)
1136 smoothgroup_list = parse_smooth_groups(mesh.data)
1138 print("{} faces".format(len(mesh.data.tessfaces)))
1140 print("Smooth groups active:", bpy.context.scene.udk_option_smoothing_groups)
1142 for face in mesh.data.tessfaces:
1144 smoothgroup_id = 0x80000000
1146 for smooth_group in smoothgroup_list:
1147 if smooth_group.contains_face(face):
1148 smoothgroup_id = smooth_group.id
1149 break
1151 # modified by VendorX
1152 object_material_index = face.material_index
1154 if len(face.vertices) != 3:
1155 raise Error("Non-triangular face (%i)" % len(face.vertices))
1157 # RG - apparently blender sometimes has problems when you do quad to triangle
1158 # conversion, and ends up creating faces that have only TWO points -
1159 # one of the points is simply in the vertex list for the face twice.
1160 # This is bad, since we can't get a real face normal for a LINE, we need
1161 # a plane for this. So, before we add the face to the list of real faces,
1162 # ensure that the face is actually a plane, and not a line. If it is not
1163 # planar, just discard it and notify the user in the console after we're
1164 # done dumping the rest of the faces
1166 if not is_1d_face(face, mesh.data):
1168 wedge_list = []
1169 vect_list = []
1171 # get or create the current material
1172 psk.GetMatByIndex(object_material_index)
1174 face_index = face.index
1175 has_uv = False
1176 face_uv = None
1178 if len(mesh.data.uv_textures) > 0:
1179 has_uv = True
1180 uv_layer = mesh.data.tessface_uv_textures.active
1181 face_uv = uv_layer.data[face_index]
1182 # size(data) is number of texture faces. Each face has UVs
1183 # print("DATA face uv: ",len(faceUV.uv), " >> ",(faceUV.uv[0][0]))
1185 for i in range(3):
1186 vert_index = face.vertices[i]
1187 vert = mesh.data.vertices[vert_index]
1188 uv = []
1189 # assumes 3 UVs Per face (for now)
1190 if (has_uv):
1191 if len(face_uv.uv) != 3:
1192 print("WARNING: face has more or less than 3 UV coordinates - writing 0,0...")
1193 uv = [0.0, 0.0]
1194 else:
1195 uv = [face_uv.uv[i][0], face_uv.uv[i][1]] # OR bottom works better # 24 for cube
1196 else:
1197 # print ("No UVs?")
1198 uv = [0.0, 0.0]
1200 # flip V coordinate because UEd requires it and DOESN'T flip it on its own like it
1201 # does with the mesh Y coordinates. this is otherwise known as MAGIC-2
1202 uv[1] = 1.0 - uv[1]
1204 # clamp UV coords if udk_option_clight_uv is True
1205 if bpy.context.scene.udk_option_clight_uv:
1206 if (uv[0] > 1):
1207 uv[0] = 1
1208 if (uv[0] < 0):
1209 uv[0] = 0
1210 if (uv[1] > 1):
1211 uv[1] = 1
1212 if (uv[1] < 0):
1213 uv[1] = 0
1215 # RE - Append untransformed vector (for normal calc below)
1216 # TODO: convert to Blender.Mathutils
1217 vect_list.append(FVector(vert.co.x, vert.co.y, vert.co.z))
1219 # Transform position for export
1220 # vpos = vert.co * object_material_index
1222 # should fixed this!!
1223 vpos = mesh.matrix_local * vert.co
1224 if bpy.context.scene.udk_option_scale < 0 or bpy.context.scene.udk_option_scale > 1:
1225 # print("OK!")
1226 vpos.x = vpos.x * bpy.context.scene.udk_option_scale
1227 vpos.y = vpos.y * bpy.context.scene.udk_option_scale
1228 vpos.z = vpos.z * bpy.context.scene.udk_option_scale
1230 # print("scale pos:", vpos)
1231 # Create the point
1232 p = VPoint()
1233 p.Point.X = vpos.x
1234 p.Point.Y = vpos.y
1235 p.Point.Z = vpos.z
1236 if bpy.context.scene.udk_option_smoothing_groups: # is this necessary?
1237 p.SmoothGroup = smoothgroup_id
1239 lPoint = VPointSimple()
1240 lPoint.Point.X = vpos.x
1241 lPoint.Point.Y = vpos.y
1242 lPoint.Point.Z = vpos.z
1244 if lPoint in points_linked:
1245 if not(p in points_linked[lPoint]):
1246 points_linked[lPoint].append(p)
1247 else:
1248 points_linked[lPoint] = [p]
1250 # Create the wedge
1251 w = VVertex()
1252 w.MatIndex = object_material_index
1253 w.PointIndex = points.get(p) # store keys
1254 w.U = uv[0]
1255 w.V = uv[1]
1256 if bpy.context.scene.udk_option_smoothing_groups: # is this necessary?
1257 w.SmoothGroup = smoothgroup_id
1258 index_wedge = wedges.get(w)
1259 wedge_list.append(index_wedge)
1261 # print results
1262 # print("result PointIndex={}, U={:.6f}, V={:.6f}, wedge_index={}".format(
1263 # w.PointIndex,
1264 # w.U,
1265 # w.V,
1266 # index_wedge))
1268 # END for i in range(3)
1270 # Determine face vertex order
1272 # TODO: convert to Blender.Mathutils
1273 # get normal from blender
1274 no = face.normal
1275 # convert to FVector
1276 norm = FVector(no[0], no[1], no[2])
1277 # Calculate the normal of the face in blender order
1278 tnorm = vect_list[1].sub(vect_list[0]).cross(vect_list[2].sub(vect_list[1]))
1279 # RE - dot the normal from blender order against the blender normal
1280 # this gives the product of the two vectors' lengths along the blender normal axis
1281 # all that matters is the sign
1282 dot = norm.dot(tnorm)
1284 tri = VTriangle()
1285 # RE - magic: if the dot product above > 0, order the vertices 2, 1, 0
1286 # if the dot product above < 0, order the vertices 0, 1, 2
1287 # if the dot product is 0, then blender's normal is coplanar with the face
1288 # and we cannot deduce which side of the face is the outside of the mesh
1289 if dot > 0:
1290 (tri.WedgeIndex2, tri.WedgeIndex1, tri.WedgeIndex0) = wedge_list
1291 elif dot < 0:
1292 (tri.WedgeIndex0, tri.WedgeIndex1, tri.WedgeIndex2) = wedge_list
1293 else:
1294 dindex0 = face.vertices[0]
1295 dindex1 = face.vertices[1]
1296 dindex2 = face.vertices[2]
1298 mesh.data.vertices[dindex0].select = True
1299 mesh.data.vertices[dindex1].select = True
1300 mesh.data.vertices[dindex2].select = True
1302 raise Error("Normal coplanar with face! points: %s, %s, %s" % (str(mesh.data.vertices[dindex0].co),
1303 str(mesh.data.vertices[dindex1].co),
1304 str(mesh.data.vertices[dindex2].co)))
1306 face.select = True
1307 if face.use_smooth is True:
1308 tri.SmoothingGroups = 1
1309 else:
1310 tri.SmoothingGroups = 0
1311 tri.MatIndex = object_material_index
1313 if bpy.context.scene.udk_option_smoothing_groups:
1314 tri.SmoothingGroups = smoothgroup_id
1315 print("Bool Smooth")
1317 psk.AddFace(tri)
1319 # END if not is_1d_face(current_face, mesh.data)
1321 else:
1322 discarded_face_count += 1
1324 # END face in mesh.data.faces
1326 print("{} points".format(len(points.dict)))
1328 for point in points.items():
1329 psk.AddPoint(point)
1331 if len(points.dict) > 32767:
1332 raise Error("Mesh vertex limit exceeded! {} > 32767".format(len(points.dict)))
1334 print("{} wedges".format(len(wedges.dict)))
1336 for wedge in wedges.items():
1337 psk.AddWedge(wedge)
1339 # alert the user to degenerate face issues
1340 if discarded_face_count > 0:
1341 print("WARNING: Mesh contained degenerate faces (non-planar)")
1342 print(" Discarded {} faces".format(discarded_face_count))
1344 # RG - walk through the vertex groups and find the indexes into the PSK points array
1345 # for them, then store that index and the weight as a tuple in a new list of
1346 # verts for the group that we can look up later by bone name, since Blender matches
1347 # verts to bones for influences by having the VertexGroup named the same thing as
1348 # the bone
1350 # [print(x, len(points_linked[x])) for x in points_linked]
1351 # print("pointsindex length ",len(points_linked))
1352 # vertex group
1354 # all vertex groups of the mesh (obj)...
1355 for obj_vertex_group in mesh.vertex_groups:
1357 # print(" bone group build:",obj_vertex_group.name)#print bone name
1358 # print(dir(obj_vertex_group))
1359 verbose("obj_vertex_group.name={}".format(obj_vertex_group.name))
1361 vertex_list = []
1363 # all vertices in the mesh...
1364 for vertex in mesh.data.vertices:
1365 # print(dir(vertex))
1366 # all groups this vertex is a member of...
1367 for vgroup in vertex.groups:
1368 if vgroup.group == obj_vertex_group.index:
1369 vertex_weight = vgroup.weight
1370 p = VPointSimple()
1371 vpos = mesh.matrix_local * vertex.co
1373 if bpy.context.scene.udk_option_scale < 0 or bpy.context.scene.udk_option_scale > 1:
1374 vpos.x = vpos.x * bpy.context.scene.udk_option_scale
1375 vpos.y = vpos.y * bpy.context.scene.udk_option_scale
1376 vpos.z = vpos.z * bpy.context.scene.udk_option_scale
1377 p.Point.X = vpos.x
1378 p.Point.Y = vpos.y
1379 p.Point.Z = vpos.z
1380 # print(p)
1381 # print(len(points_linked[p]))
1382 try: # check if point doesn't give error
1383 for point in points_linked[p]:
1384 point_index = points.get(point) # point index
1385 v_item = (point_index, vertex_weight)
1386 vertex_list.append(v_item)
1387 except Exception: # if get error ignore them # not safe I think
1388 print("Error link points!")
1389 pass
1391 # bone name, [point id and wieght]
1392 # print("Add Vertex Group:",obj_vertex_group.name, " No. Points:",len(vertex_list))
1393 psk.VertexGroups[obj_vertex_group.name] = vertex_list
1395 # remove the temporary triangulated mesh
1396 if bpy.context.scene.udk_option_triangulate is True:
1397 verbose("Removing temporary triangle mesh: {}".format(mesh.name))
1398 bpy.ops.object.mode_set(mode='OBJECT') # OBJECT mode
1399 mesh.parent = None # unparent to avoid phantom links
1400 bpy.context.scene.objects.unlink(mesh) # unlink
1403 # ===========================================================================
1404 # Collate bones that belong to the UDK skeletal mesh
1405 # ===========================================================================
1406 def parse_armature(armature, psk, psa):
1408 print(header("ARMATURE", 'RIGHT'))
1409 verbose("Armature object: {} Armature data: {}".format(armature.name, armature.data.name))
1411 # generate a list of root bone candidates
1412 root_candidates = [b for b in armature.data.bones if b.parent is None and b.use_deform is True]
1414 # should be a single, unambiguous result
1415 if len(root_candidates) == 0:
1416 raise Error("Cannot find root for UDK bones. The root bone must use deform.")
1418 if len(root_candidates) > 1:
1419 raise Error("Ambiguous root for UDK. More than one root bone is using deform.")
1421 # prep for bone collection
1422 udk_root_bone = root_candidates[0]
1423 udk_bones = []
1424 BoneUtil.static_bone_id = 0 # replaces global
1426 # traverse bone chain
1427 print("{: <3} {: <48} {: <20}".format("ID", "Bone", "Status"))
1428 print()
1429 recurse_bone(udk_root_bone, udk_bones, psk, psa, 0, armature.matrix_local)
1431 # final validation
1432 if len(udk_bones) < 3:
1433 raise Error("Less than three bones may crash UDK (legacy issue?)")
1435 # return a list of bones making up the entire udk skel
1436 # this is passed to parse_animation instead of working from keyed bones in the action
1437 return udk_bones
1440 # ===========================================================================
1441 # bone current bone
1442 # bones bone list
1443 # psk the PSK file object
1444 # psa the PSA file object
1445 # parent_id
1446 # parent_matrix
1447 # indent text indent for recursive log
1448 # ===========================================================================
1449 def recurse_bone(bone, bones, psk, psa, parent_id, parent_matrix, indent=""):
1451 status = "Ok"
1452 bones.append(bone)
1454 if not bone.use_deform:
1455 status = "No effect"
1457 # calc parented bone transform
1458 if bone.parent is not None:
1459 quat = make_fquat(bone.matrix.to_quaternion())
1460 quat_parent = bone.parent.matrix.to_quaternion().inverted()
1461 parent_head = quat_parent * bone.parent.head
1462 parent_tail = quat_parent * bone.parent.tail
1463 translation = (parent_tail - parent_head) + bone.head
1465 # calc root bone transform
1466 else:
1467 translation = parent_matrix * bone.head # ARMATURE OBJECT Location
1468 rot_matrix = bone.matrix * parent_matrix.to_3x3() # ARMATURE OBJECT Rotation
1469 quat = make_fquat_default(rot_matrix.to_quaternion())
1471 # udk_option_scale bones here?
1472 if bpy.context.scene.udk_option_scale < 0 or bpy.context.scene.udk_option_scale > 1:
1473 translation.x = translation.x * bpy.context.scene.udk_option_scale
1474 translation.y = translation.y * bpy.context.scene.udk_option_scale
1475 translation.z = translation.z * bpy.context.scene.udk_option_scale
1476 bone_id = BoneUtil.static_bone_id # ALT VERS
1477 BoneUtil.static_bone_id += 1 # ALT VERS
1479 child_count = len(bone.children)
1481 psk.AddBone(make_vbone(bone.name, parent_id, child_count, quat, translation))
1482 psa.StoreBone(make_namedbonebinary(bone.name, parent_id, child_count, quat, translation, 1))
1484 # RG - dump influences for this bone - use the data we collected
1485 # in the mesh dump phase to map our bones to vertex groups
1486 if bone.name in psk.VertexGroups:
1487 vertex_list = psk.VertexGroups[bone.name]
1488 # print("vertex list:", len(vertex_list), " of >" ,bone.name)
1490 for vertex_data in vertex_list:
1491 point_index = vertex_data[0]
1492 vertex_weight = vertex_data[1]
1493 influence = VRawBoneInfluence()
1494 influence.Weight = vertex_weight
1495 influence.BoneIndex = bone_id
1496 influence.PointIndex = point_index
1497 # print (" AddInfluence to vertex {}, weight={},".format(point_index, vertex_weight))
1498 psk.AddInfluence(influence)
1499 else:
1500 status = "No vertex group"
1501 # FIXME overwriting previous status error?
1503 print("{:<3} {:<48} {:<20}".format(bone_id, indent + bone.name, status))
1505 # bone.matrix_local
1506 # recursively dump child bones
1508 for child_bone in bone.children:
1509 recurse_bone(child_bone, bones, psk, psa, bone_id, parent_matrix, " " + indent)
1512 # FIXME rename? remove?
1513 class BoneUtil:
1514 static_bone_id = 0 # static property to replace global
1517 # ===========================================================================
1518 # armature the armature
1519 # udk_bones list of bones to be exported
1520 # actions_to_export list of actions to process for export
1521 # psa the PSA file object
1522 # ===========================================================================
1523 def parse_animation(armature, udk_bones, actions_to_export, psa):
1525 print(header("ANIMATION", 'RIGHT'))
1527 context = bpy.context
1528 anim_rate = context.scene.render.fps
1530 verbose("Armature object: {}".format(armature.name))
1531 print("Scene: {} FPS: {} Frames: {} to {}".format(context.scene.name, anim_rate,
1532 context.scene.frame_start, context.scene.frame_end)
1534 print("Processing {} action(s)\n".format(len(actions_to_export)))
1536 # if animation data was not create for the armature it will skip the exporting action set(s)
1537 if armature.animation_data is None:
1538 print("None Actions Set! skipping...")
1539 return
1540 restoreAction = armature.animation_data.action # Q: is animation_data always valid?
1541 # we already do this in export_proxy, but we'll do it here too for now
1542 restoreFrame = context.scene.frame_current
1543 raw_frame_index = 0 # used to set FirstRawFrame, separating actions in the raw keyframe array
1545 # action loop...
1546 for action in actions_to_export:
1548 # removed: check for armature with no animation; all it did was force you to add one
1550 if not len(action.fcurves):
1551 print("{} has no keys, skipping".format(action.name))
1552 continue
1554 # apply action to armature and update scene
1555 # note if loop all actions that is not armature it will override and will break armature animation
1556 armature.animation_data.action = action
1557 context.scene.update()
1559 # min/max frames define range
1560 framemin, framemax = action.frame_range
1561 start_frame = int(framemin)
1562 end_frame = int(framemax)
1563 scene_range = range(start_frame, end_frame + 1)
1564 frame_count = len(scene_range)
1566 # create the AnimInfoBinary
1567 anim = AnimInfoBinary()
1568 anim.Name = action.name
1569 anim.Group = "" # unused?
1570 anim.NumRawFrames = frame_count
1571 anim.AnimRate = anim_rate
1572 anim.FirstRawFrame = raw_frame_index
1574 print("{}, frames {} to {} ({} frames)".format(action.name, start_frame, end_frame, frame_count))
1576 # removed: bone lookup table
1578 # build a list of pose bones relevant to the collated udk_bones
1579 # fixme: could be done once, prior to loop?
1580 udk_pose_bones = []
1581 for b in udk_bones:
1582 for pb in armature.pose.bones:
1583 if b.name == pb.name:
1584 udk_pose_bones.append(pb)
1585 break
1587 # sort in the order the bones appear in the PSA file
1588 ordered_bones = {}
1589 ordered_bones = sorted([(psa.UseBone(b.name), b) for b in udk_pose_bones], key=operator.itemgetter(0))
1591 # NOTE: posebone.bone references the obj/edit bone
1592 # REMOVED: unique_bone_indexes is redundant?
1594 # frame loop...
1595 for i in range(frame_count):
1597 frame = scene_range[i]
1599 # verbose("FRAME {}".format(i), i) # test loop sampling
1601 # advance to frame (automatically updates the pose)
1602 context.scene.frame_set(frame)
1604 # compute the key for each bone
1605 for bone_data in ordered_bones:
1607 bone_index = bone_data[0]
1608 pose_bone = bone_data[1]
1609 pose_bone_matrix = mathutils.Matrix(pose_bone.matrix)
1611 if pose_bone.parent is not None:
1612 pose_bone_parent_matrix = mathutils.Matrix(pose_bone.parent.matrix)
1613 pose_bone_matrix = pose_bone_parent_matrix.inverted() * pose_bone_matrix
1615 head = pose_bone_matrix.to_translation()
1616 quat = pose_bone_matrix.to_quaternion().normalized()
1618 if pose_bone.parent is not None:
1619 quat = make_fquat(quat)
1620 else:
1621 quat = make_fquat_default(quat)
1623 # scale animation position here?
1624 if bpy.context.scene.udk_option_scale < 0 or bpy.context.scene.udk_option_scale > 1:
1625 head.x = head.x * bpy.context.scene.udk_option_scale
1626 head.y = head.y * bpy.context.scene.udk_option_scale
1627 head.z = head.z * bpy.context.scene.udk_option_scale
1629 vkey = VQuatAnimKey()
1630 vkey.Position.X = head.x
1631 vkey.Position.Y = head.y
1632 vkey.Position.Z = head.z
1633 vkey.Orientation = quat
1635 # frame delta = 1.0 / fps
1636 vkey.Time = 1.0 / anim_rate # according to C++ header this is "disregarded"
1638 psa.AddRawKey(vkey)
1640 # END for bone_data in ordered_bones
1642 raw_frame_index += 1
1644 # END for i in range(frame_count)
1646 # REMOVED len(unique_bone_indexes)
1647 anim.TotalBones = len(ordered_bones)
1648 # frame_count/anim.AnimRate makes more sense, but this is what actually works in UDK
1649 anim.TrackTime = float(frame_count)
1651 verbose("anim.TotalBones={}, anim.TrackTime={}".format(anim.TotalBones, anim.TrackTime))
1653 psa.AddAnimation(anim)
1655 # END for action in actions
1657 # restore
1658 armature.animation_data.action = restoreAction
1659 context.scene.frame_set(restoreFrame)
1662 # ===========================================================================
1663 # Collate actions to be exported
1664 # Modify this to filter for one, some or all actions. For now use all.
1665 # RETURNS list of actions
1666 # ===========================================================================
1667 def collate_actions():
1668 verbose(header("collate_actions"))
1669 actions_to_export = []
1671 for action in bpy.data.actions:
1672 if bpy.context.scene.udk_option_selectanimations: # check if needed to select actions set for exporting it
1673 print("Action Set is selected!")
1674 bready = False
1675 for actionlist in bpy.context.scene.udkas_list: # list the action set from the list
1676 if actionlist.name == action.name and actionlist.bmatch is True and actionlist.bexport is True:
1677 bready = True
1678 print("Added Action Set:", action.name)
1679 break
1680 if bready is False: # don't export it
1681 print("Skipping Action Set:", action.name)
1682 continue
1683 verbose(" + {}".format(action.name)) # action set name
1684 actions_to_export.append(action) # add to the action array
1686 return actions_to_export
1689 # ===========================================================================
1690 # Locate the target armature and mesh for export
1691 # RETURNS armature, mesh
1692 # ===========================================================================
1693 def find_armature_and_mesh():
1694 verbose(header("find_armature_and_mesh", 'LEFT', '<', 60))
1696 context = bpy.context
1697 active_object = context.active_object
1698 armature = None
1699 mesh = None
1701 # TODO:
1702 # this could be more intuitive
1703 # bpy.ops.object.mode_set(mode='OBJECT')
1705 if bpy.context.scene.udk_option_selectobjects: # if checked select object true do list object on export
1706 print("select mode:")
1707 if len(bpy.context.scene.udkArm_list) > 0:
1708 print("Armature Name:", bpy.context.scene.udkArm_list[bpy.context.scene.udkArm_list_idx].name)
1709 for obj in bpy.context.scene.objects:
1710 if obj.name == bpy.context.scene.udkArm_list[bpy.context.scene.udkArm_list_idx].name:
1711 armature = obj
1712 break
1713 else:
1714 raise Error("There is no Armature in the list!")
1715 meshselected = []
1716 # parented_meshes = [obj for obj in armature.children if obj.type == 'MESH']
1717 meshes = [obj for obj in bpy.context.scene.objects if obj.type == 'MESH']
1718 for obj in meshes:
1719 # print(dir(obj))
1720 if obj.type == 'MESH':
1721 bexportmesh = False
1722 # print("PARENT MESH:",obj.name)
1723 for udkmeshlist in bpy.context.scene.udkmesh_list:
1724 if obj.name == udkmeshlist.name and udkmeshlist.bexport is True:
1725 bexportmesh = True
1726 break
1727 if bexportmesh is True:
1728 print("Mesh Name:", obj.name, " < SELECT TO EXPORT!")
1729 meshselected.append(obj)
1731 print("MESH COUNT:", len(meshselected))
1732 # try the active object
1733 if active_object and active_object.type == 'MESH' and len(meshselected) == 0:
1734 if active_object.parent == armature:
1735 mesh = active_object
1736 else:
1737 raise Error("The selected mesh is not parented to the armature")
1739 # otherwise, expect a single mesh parented to the armature (other object types are ignored)
1740 else:
1741 print("Number of meshes:", len(meshes))
1742 print("Number of meshes (selected):", len(meshes))
1743 if len(meshes) == 1:
1744 mesh = meshes[0]
1746 elif len(meshes) > 1:
1747 if len(meshselected) >= 1:
1748 mesh = sortmesh(meshselected)
1749 else:
1750 raise Error("More than one mesh(s) parented to armature. Select object(s)!")
1751 else:
1752 raise Error("No mesh parented to armature")
1753 else: # if not check for select function from the list work the code here
1754 print("normal mode:")
1755 # try the active object
1756 if active_object and active_object.type == 'ARMATURE':
1757 armature = active_object
1758 bpy.ops.object.mode_set(mode='OBJECT')
1759 # otherwise, try for a single armature in the scene
1760 else:
1761 # bpy.ops.object.mode_set(mode='OBJECT')
1762 all_armatures = [obj for obj in bpy.context.scene.objects if obj.type == 'ARMATURE']
1764 if len(all_armatures) == 1: # if armature has one scene just assign it
1765 armature = all_armatures[0]
1766 elif len(all_armatures) > 1: # if there more armature then find the select armature
1767 barmselect = False
1768 for _armobj in all_armatures:
1769 if _armobj.select:
1770 armature = _armobj
1771 barmselect = True
1772 break
1773 if barmselect is False:
1774 raise Error("Please select an armatures in the scene")
1775 else:
1776 raise Error("No armatures in scene")
1778 verbose("Found armature: {}".format(armature.name))
1780 meshselected = []
1781 parented_meshes = [obj for obj in armature.children if obj.type == 'MESH']
1783 if len(armature.children) == 0:
1784 raise Error("The selected Armature has no mesh parented to the Armature Object!")
1786 for obj in armature.children:
1787 # print(dir(obj))
1788 if obj.type == 'MESH' and obj.select is True:
1789 meshselected.append(obj)
1790 # try the active object
1791 if active_object and active_object.type == 'MESH' and len(meshselected) == 0:
1792 if active_object.parent == armature:
1793 mesh = active_object
1794 else:
1795 raise Error("The selected mesh is not parented to the armature")
1797 # otherwise, expect a single mesh parented to the armature (other object types are ignored)
1798 else:
1799 print("Number of meshes:", len(parented_meshes))
1800 print("Number of meshes (selected):", len(meshselected))
1801 if len(parented_meshes) == 1:
1802 mesh = parented_meshes[0]
1804 elif len(parented_meshes) > 1:
1805 if len(meshselected) >= 1:
1806 mesh = sortmesh(meshselected)
1807 else:
1808 raise Error("More than one mesh(s) parented to armature. Select object(s)!")
1809 else:
1810 raise Error("No mesh parented to armature")
1812 verbose("Found mesh: {}".format(mesh.name))
1813 if mesh is None or armature is None:
1814 raise Error("Check Mesh and Armature are list!")
1817 if len(armature.pose.bones) == len(mesh.vertex_groups):
1818 print("Armature and Mesh Vertex Groups matches Ok!")
1819 else:
1820 raise Error("Armature bones:" + str(len(armature.pose.bones)) +
1821 " Mesh Vertex Groups:" + str(len(mesh.vertex_groups)) +" doesn't match!")
1823 # this will check if object need to be rebuild
1824 if bpy.context.scene.udk_option_rebuildobjects:
1825 # print("INIT... REBUILDING...")
1826 print("REBUILDING ARMATURE...")
1827 # if deform mesh
1828 # rebuild the armature to raw. If there IK constraint it will ignore it
1829 armature = rebuildarmature(armature)
1830 print("REBUILDING MESH...")
1831 mesh = rebuildmesh(mesh) # rebuild the mesh to raw data format.
1833 return armature, mesh
1836 # ===========================================================================
1837 # Returns a list of vertex groups in the mesh. Can be modified to filter
1838 # groups as necessary.
1839 # UNUSED
1840 # ===========================================================================
1841 def collate_vertex_groups(mesh):
1842 verbose("collate_vertex_groups")
1843 groups = []
1845 for group in mesh.vertex_groups:
1847 groups.append(group)
1848 verbose(" " + group.name)
1850 return groups
1853 # ===========================================================================
1854 # Main
1855 # ===========================================================================
1856 def export(filepath):
1857 print(header("Export", 'RIGHT'))
1858 bpy.types.Scene.udk_copy_merge = False # in case fail to export set this to default
1859 t = time.clock()
1860 context = bpy.context
1862 print("Blender Version {}.{}.{}".format(bpy.app.version[0], bpy.app.version[1], bpy.app.version[2]))
1863 print("Filepath: {}".format(filepath))
1865 verbose("PSK={}, PSA={}".format(context.scene.udk_option_export_psk, context.scene.udk_option_export_psa))
1867 # find armature and mesh
1868 # [change this to implement alternative methods; raise Error() if not found]
1869 udk_armature, udk_mesh = find_armature_and_mesh()
1871 # check misc conditions
1872 if not (udk_armature.scale.x == udk_armature.scale.y == udk_armature.scale.z == 1):
1873 raise Error("bad armature scale: armature object should have uniform scale of 1 (ALT-S)")
1875 if not (udk_mesh.scale.x == udk_mesh.scale.y == udk_mesh.scale.z == 1):
1876 raise Error("bad mesh scale: mesh object should have uniform scale of 1 (ALT-S)")
1878 if not (udk_armature.location.x == udk_armature.location.y == udk_armature.location.z == 0):
1879 raise Error("bad armature location: armature should be located at origin (ALT-G)")
1881 if not (udk_mesh.location.x == udk_mesh.location.y == udk_mesh.location.z == 0):
1882 raise Error("bad mesh location: mesh should be located at origin (ALT-G)")
1884 # prep
1885 psk = PSKFile()
1886 psa = PSAFile()
1888 # step 1
1889 parse_mesh(udk_mesh, psk)
1891 # step 2
1892 udk_bones = parse_armature(udk_armature, psk, psa)
1894 # step 3
1895 if context.scene.udk_option_export_psa is True:
1896 actions = collate_actions()
1897 parse_animation(udk_armature, udk_bones, actions, psa)
1899 # write files
1900 print(header("Exporting", 'CENTER'))
1902 psk_filename = filepath + '.psk'
1903 psa_filename = filepath + '.psa'
1905 if context.scene.udk_option_export_psk is True:
1906 print("Skeletal mesh data...")
1907 psk.PrintOut()
1908 file = open(psk_filename, "wb")
1909 file.write(psk.dump())
1910 file.close()
1911 print("Exported: " + psk_filename)
1912 print()
1914 if context.scene.udk_option_export_psa is True:
1915 print("Animation data...")
1916 if not psa.IsEmpty():
1917 psa.PrintOut()
1918 file = open(psa_filename, "wb")
1919 file.write(psa.dump())
1920 file.close()
1921 print("Exported: " + psa_filename)
1922 else:
1923 print("No Animation (.psa file) to export")
1925 print()
1927 # if objects are rebuild do the unlink
1928 if bpy.context.scene.udk_option_rebuildobjects:
1929 print("Unlinking Objects")
1930 print("Armature Object Name:", udk_armature.name) # display object name
1931 bpy.context.scene.objects.unlink(udk_armature) # remove armature from the scene
1932 print("Mesh Object Name:", udk_mesh.name) # display object name
1933 bpy.context.scene.objects.unlink(udk_mesh) # remove mesh from the scene
1935 print("Export completed in {:.2f} seconds".format((time.clock() - t)))
1938 # ===========================================================================
1939 # Operator
1940 # ===========================================================================
1941 class Operator_UDKExport(Operator):
1942 """Export to UDK"""
1943 bl_idname = "object.udk_export"
1944 bl_label = "Export now"
1946 def execute(self, context):
1947 print("\n" * 8)
1948 scene = bpy.context.scene
1950 scene.udk_option_export_psk = (scene.udk_option_export == '0' or scene.udk_option_export == '2')
1951 scene.udk_option_export_psa = (scene.udk_option_export == '1' or scene.udk_option_export == '2')
1953 filepath = get_dst_path()
1955 # cache settings
1956 restore_frame = scene.frame_current
1957 message = "Object(s) exported to: {}".format(filepath)
1958 try:
1959 export(filepath)
1961 except Error as err:
1962 print(err.message)
1963 message = err.message
1965 # restore settings
1966 scene.frame_set(restore_frame)
1968 def draw(self, context):
1969 self.layout.label(text="Export Finished")
1970 try:
1971 context.window_manager.popup_menu(draw, title=message, icon="INFO")
1972 except:
1973 pass
1975 self.report({'INFO'}, message)
1977 # restore settings
1978 scene.frame_set(restore_frame)
1980 return {'FINISHED'}
1983 # ===========================================================================
1984 # Operator
1985 # ===========================================================================
1986 class Operator_ToggleConsole(Operator):
1987 """Show or hide the console"""
1988 bl_idname = "object.toggle_console"
1989 bl_label = "Toggle console"
1991 def execute(self, context):
1992 bpy.ops.wm.console_toggle()
1993 return {'FINISHED'}
1996 # ===========================================================================
1997 # Get filepath for export
1998 # ===========================================================================
1999 def get_dst_path():
2000 if bpy.context.scene.udk_option_filename_src == '0':
2001 if bpy.context.active_object:
2002 path = os.path.split(bpy.data.filepath)[0] + "\\" + bpy.context.active_object.name # + ".psk"
2003 else:
2004 # path = os.path.split(bpy.data.filepath)[0] + "\\" + "Unknown";
2005 path = os.path.splitext(bpy.data.filepath)[0] # + ".psk"
2006 else:
2007 path = os.path.splitext(bpy.data.filepath)[0] # + ".psk"
2008 return path
2011 # ===========================================================================
2012 # User interface
2013 # ===========================================================================
2014 class OBJECT_OT_UTSelectedFaceSmooth(Operator):
2015 """It will only select smooth faces that is select mesh"""
2016 bl_idname = "object.utselectfacesmooth" # XXX, name???
2017 bl_label = "Select Smooth Faces" # "Select Smooth faces"
2019 def invoke(self, context, event):
2020 print("----------------------------------------")
2021 print("Init Select Face(s):")
2022 bselected = False
2023 for obj in bpy.data.objects:
2024 if obj.type == 'MESH' and obj.select is True:
2025 smoothcount = 0
2026 flatcount = 0
2027 bpy.ops.object.mode_set(mode='OBJECT') # it need to go into object mode to able to select the faces
2029 for i in bpy.context.scene.objects:
2030 i.select = False # deselect all objects
2032 obj.select = True # set current object select
2033 bpy.context.scene.objects.active = obj # set active object
2034 mesh = bmesh.new()
2035 mesh.from_mesh(obj.data)
2037 for face in mesh.faces:
2038 face.select = False
2040 for face in mesh.faces:
2041 if face.smooth is True:
2042 face.select = True
2043 smoothcount += 1
2044 else:
2045 flatcount += 1
2046 face.select = False
2047 mesh.to_mesh(obj.data)
2048 bpy.context.scene.update()
2049 bpy.ops.object.mode_set(mode='EDIT')
2050 print("Select Smooth Count(s):", smoothcount, " Flat Count(s):", flatcount)
2051 bselected = True
2052 break
2053 if bselected:
2054 self.report({'INFO'}, "Selected Face(s) Executed")
2055 else:
2056 self.report({'INFO'}, "Mesh Object is not selected")
2057 print("----------------------------------------")
2059 return{'FINISHED'}
2062 class OBJECT_OT_MeshClearWeights(Operator):
2063 """Remove all mesh vertex groups weights for the bones"""
2064 bl_idname = "object.meshclearweights" # XXX, name???
2065 bl_label = "Remove Vertex Weights" # "Remove Mesh vertex weights"
2067 def invoke(self, context, event):
2068 for obj in bpy.data.objects:
2069 if obj.type == 'MESH' and obj.select is True:
2070 for vg in obj.vertex_groups:
2071 obj.vertex_groups.remove(vg)
2072 self.report({'INFO'}, "Mesh Vertex Groups Removed")
2073 break
2074 return{'FINISHED'}
2077 def unpack_list(list_of_tuples):
2078 l = []
2079 for t in list_of_tuples:
2080 l.extend(t)
2081 return l
2084 def rebuildmesh(obj):
2085 # make sure it in object mode
2086 print("Mesh Object Name:", obj.name)
2087 bpy.ops.object.mode_set(mode='OBJECT')
2089 for i in bpy.context.scene.objects:
2090 i.select = False # deselect all objects
2091 obj.select = True
2092 bpy.context.scene.objects.active = obj
2094 me_ob = bpy.data.meshes.new(("Re_" + obj.name))
2095 mesh = obj.data
2096 faces = []
2097 verts = []
2098 smoothings = []
2099 uvfaces = []
2100 # print("creating array build mesh...")
2101 mmesh = obj.to_mesh(bpy.context.scene, True, 'PREVIEW')
2102 uv_layer = mmesh.tessface_uv_textures.active
2104 for face in mmesh.tessfaces:
2105 smoothings.append(face.use_smooth) # smooth or flat in boolean
2106 if uv_layer is not None: # check if there texture data exist
2107 faceUV = uv_layer.data[face.index]
2108 uvs = []
2109 for uv in faceUV.uv:
2110 uvs.append((uv[0], uv[1]))
2111 uvfaces.append(uvs)
2112 # print((face.vertices[:]))
2113 if len(face.vertices) == 3:
2114 faces.extend([(face.vertices[0], face.vertices[1], face.vertices[2], 0)])
2115 else:
2116 faces.extend([(face.vertices[0], face.vertices[1], face.vertices[2], face.vertices[3])])
2118 # vertex positions
2119 for vertex in mesh.vertices:
2120 verts.append(vertex.co.to_tuple())
2121 # vertices weight groups into array
2122 vertGroups = {} # array in strings
2124 for vgroup in obj.vertex_groups:
2125 vlist = []
2126 for v in mesh.vertices:
2127 for vg in v.groups:
2128 if vg.group == vgroup.index:
2129 vlist.append((v.index, vg.weight))
2130 # print((v.index,vg.weight))
2131 vertGroups[vgroup.name] = vlist
2133 # print("creating mesh object...")
2134 # me_ob.from_pydata(verts, [], faces)
2135 me_ob.vertices.add(len(verts))
2136 me_ob.tessfaces.add(len(faces))
2137 me_ob.vertices.foreach_set("co", unpack_list(verts))
2138 me_ob.tessfaces.foreach_set("vertices_raw", unpack_list(faces))
2139 me_ob.tessfaces.foreach_set("use_smooth", smoothings) # smooth array from face
2141 # check if there is uv faces
2142 if len(uvfaces) > 0:
2143 uvtex = me_ob.tessface_uv_textures.new(name="retex")
2144 for i, face in enumerate(me_ob.tessfaces):
2145 blender_tface = uvtex.data[i] # face
2146 mfaceuv = uvfaces[i]
2147 if len(mfaceuv) == 3:
2148 blender_tface.uv1 = mfaceuv[0]
2149 blender_tface.uv2 = mfaceuv[1]
2150 blender_tface.uv3 = mfaceuv[2]
2151 if len(mfaceuv) == 4:
2152 blender_tface.uv1 = mfaceuv[0]
2153 blender_tface.uv2 = mfaceuv[1]
2154 blender_tface.uv3 = mfaceuv[2]
2155 blender_tface.uv4 = mfaceuv[3]
2157 me_ob.update() # need to update the information to able to see into the secne
2158 obmesh = bpy.data.objects.new(("Re_" + obj.name), me_ob)
2159 bpy.context.scene.update()
2161 # Build tmp materials
2162 materialname = "ReMaterial"
2163 for matcount in mesh.materials:
2164 matdata = bpy.data.materials.new(materialname)
2165 me_ob.materials.append(matdata)
2167 # assign face to material id
2168 for face in mesh.tessfaces:
2169 me_ob.faces[face.index].material_index = face.material_index
2171 # vertices weight groups
2172 for vgroup in vertGroups:
2173 group = obmesh.vertex_groups.new(vgroup)
2174 for v in vertGroups[vgroup]:
2175 group.add([v[0]], v[1], 'ADD') # group.add(array[vertex id],weight,add)
2176 bpy.context.scene.objects.link(obmesh)
2177 # print("Mesh Material Count:",len(me_ob.materials))
2178 matcount = 0
2179 # print("MATERIAL ID OREDER:")
2180 for mat in me_ob.materials:
2181 # print("-Material:",mat.name,"INDEX:",matcount)
2182 matcount += 1
2184 print("Mesh Object Name:", obmesh.name)
2185 bpy.context.scene.update()
2187 return obmesh
2190 class OBJECT_OT_UTRebuildMesh(Operator):
2191 """It rebuild the mesh from scrape from the selected mesh object. """ \
2192 """Note the scale will be 1:1 for object mode. To keep from deforming"""
2193 bl_idname = "object.utrebuildmesh" # XXX, name???
2194 bl_label = "Rebuild Mesh" # "Rebuild Mesh"
2196 def invoke(self, context, event):
2197 print("----------------------------------------")
2198 print("Init Mesh Bebuild...")
2199 bselected = False
2200 bpy.ops.object.mode_set(mode='OBJECT')
2202 for obj in bpy.data.objects:
2203 if obj.type == 'MESH' and obj.select is True:
2204 rebuildmesh(obj)
2206 self.report({'INFO'}, "Rebuild Mesh Finished!")
2207 print("Finish Mesh Build...")
2208 print("----------------------------------------")
2209 return{'FINISHED'}
2212 def rebuildarmature(obj):
2213 currentbone = [] # select armature for roll copy
2214 print("Armature Name:", obj.name)
2215 objectname = "ArmatureDataPSK"
2216 meshname = "ArmatureObjectPSK"
2217 armdata = bpy.data.armatures.new(objectname)
2218 ob_new = bpy.data.objects.new(meshname, armdata)
2219 bpy.context.scene.objects.link(ob_new)
2220 # bpy.ops.object.mode_set(mode='OBJECT')
2222 for i in bpy.context.scene.objects:
2223 i.select = False # deselect all objects
2225 ob_new.select = True
2226 bpy.context.scene.objects.active = obj
2228 bpy.ops.object.mode_set(mode='EDIT')
2229 for bone in obj.data.edit_bones:
2230 if bone.parent is not None:
2231 currentbone.append([bone.name, bone.roll])
2232 else:
2233 currentbone.append([bone.name, bone.roll])
2234 bpy.ops.object.mode_set(mode='OBJECT')
2236 for i in bpy.context.scene.objects:
2237 i.select = False # deselect all objects
2239 bpy.context.scene.objects.active = ob_new
2240 bpy.ops.object.mode_set(mode='EDIT')
2242 for bone in obj.data.bones:
2243 bpy.ops.object.mode_set(mode='EDIT')
2244 newbone = ob_new.data.edit_bones.new(bone.name)
2245 newbone.head = bone.head_local
2246 newbone.tail = bone.tail_local
2247 for bonelist in currentbone:
2248 if bone.name == bonelist[0]:
2249 newbone.roll = bonelist[1]
2250 break
2251 if bone.parent is not None:
2252 parentbone = ob_new.data.edit_bones[bone.parent.name]
2253 newbone.parent = parentbone
2255 ob_new.animation_data_create() # create animation data
2256 if obj.animation_data is not None: # check for animation
2257 # just make sure it here to do the animations if exist
2258 ob_new.animation_data.action = obj.animation_data.action
2260 print("Armature Object Name:", ob_new.name)
2261 return ob_new
2264 class OBJECT_OT_UTRebuildArmature(Operator):
2265 """If mesh is deform when importing to unreal engine try this. """ \
2266 """It rebuild the bones one at the time by select one armature object scrape to raw setup build. """ \
2267 """Note the scale will be 1:1 for object mode. To keep from deforming"""
2268 bl_idname = "object.utrebuildarmature" # XXX, name???
2269 bl_label = "Rebuild Armature" # Rebuild Armature
2271 def invoke(self, context, event):
2272 print("----------------------------------------")
2273 print("Init Rebuild Armature...")
2274 bselected = False
2275 for obj in bpy.data.objects:
2276 if obj.type == 'ARMATURE' and obj.select is True:
2277 rebuildarmature(obj)
2278 self.report({'INFO'}, "Rebuild Armature Finish!")
2279 print("End of Rebuild Armature.")
2280 print("----------------------------------------")
2281 return{'FINISHED'}
2284 class UDKActionSetListPG(PropertyGroup):
2285 bool = BoolProperty(default=False)
2286 string = StringProperty()
2287 actionname = StringProperty()
2288 bmatch = BoolProperty(
2289 default=False,
2290 name="Match",
2291 options={"HIDDEN"},
2292 description="This check against bone names and action group "
2293 "names matches and override boolean if true"
2295 bexport = BoolProperty(
2296 default=False,
2297 name="Export",
2298 description="Check this to export the animation"
2302 class UL_UDKActionSetList(UIList):
2303 def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
2304 layout.label(item.name)
2305 layout.prop(item, "bmatch", text="Match")
2306 layout.prop(item, "bexport", text="Export")
2309 class UDKObjListPG(PropertyGroup):
2310 bool = BoolProperty(default=False)
2311 string = StringProperty()
2312 bexport = BoolProperty(
2313 default=False,
2314 name="Export",
2315 options={"HIDDEN"},
2316 description="This will be ignore when exported"
2318 bselect = BoolProperty(
2319 default=False,
2320 name="Select",
2321 options={"HIDDEN"},
2322 description="This will be ignore when exported"
2324 otype = StringProperty(
2325 name="Type",
2326 description="This will be ignore when exported"
2330 class UL_UDKObjList(UIList):
2331 def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
2332 layout.label(item.name)
2333 layout.prop(item, "otype", text="")
2334 layout.prop(item, "bselect", text="")
2337 class UDKMeshListPG(PropertyGroup):
2338 bool = BoolProperty(
2339 default=False
2341 string = StringProperty()
2342 bexport = BoolProperty(
2343 default=False,
2344 name="Export",
2345 options={"HIDDEN"},
2346 description="This object will be export when true"
2348 bselect = BoolProperty(
2349 default=False,
2350 name="Select",
2351 options={"HIDDEN"},
2352 description="Make sure you have Mesh is parent to Armature"
2354 otype = StringProperty(
2355 name="Type",
2356 description="This will be ignore when exported"
2360 class UL_UDKMeshList(UIList):
2361 def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
2362 layout.label(item.name)
2363 # layout.prop(item, "bselect", text="Select")
2364 layout.prop(item, "bexport", text="Export")
2367 class UDKArmListPG(PropertyGroup):
2368 bool = BoolProperty(default=False)
2369 string = StringProperty()
2370 bexport = BoolProperty(
2371 default=False,
2372 name="Export",
2373 options={"HIDDEN"},
2374 description="This will be ignore when exported"
2376 bselect = BoolProperty(
2377 default=False,
2378 name="Select",
2379 options={"HIDDEN"},
2380 description="This will be ignore when exported"
2382 otype = StringProperty(
2383 name="Type",
2384 description="This will be ignore when exported"
2388 class UL_UDKArmList(UIList):
2389 def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
2390 layout.label(item.name)
2393 class Panel_UDKExport(Panel):
2394 bl_label = "UDK Export"
2395 bl_idname = "OBJECT_PT_udk_tools"
2396 bl_category = "File I/O"
2397 bl_space_type = "VIEW_3D"
2398 bl_region_type = "TOOLS"
2399 bl_context = "objectmode"
2402 def draw_header(self, context):
2403 layout = self.layout
2404 obj = context.object
2405 layout.prop(obj, "select", text="")
2407 @classmethod
2408 def poll(cls, context):
2409 return context.active_object
2412 def draw(self, context):
2413 layout = self.layout
2414 path = get_dst_path()
2416 object_name = ""
2418 if context.object:
2419 object_name = context.object.name
2421 if context.active_object:
2422 object_name = context.active_object.name
2423 row10 = layout.row()
2424 row10.prop(context.scene, "udk_option_smoothing_groups")
2425 row10.prop(context.scene, "udk_option_clight_uv")
2426 row10.prop(context.scene, "udk_option_verbose")
2428 row = layout.row()
2429 row.label(text="Active object: " + object_name)
2430 layout.prop(context.scene, "udk_option_filename_src")
2431 row = layout.row()
2432 row.label(text=path)
2434 layout.prop(context.scene, "udk_option_export")
2435 layout.prop(context.scene, "udk_option_selectobjects")
2437 if context.scene.udk_option_selectobjects:
2438 layout.operator("object.selobjectpdate")
2439 layout.label(text="ARMATURE - Index")
2440 layout.template_list("UL_UDKArmList", "udk_armatures", context.scene, "udkArm_list",
2441 context.scene, "udkArm_list_idx", rows=3)
2442 layout.label(text="MESH - Export")
2443 layout.template_list("UL_UDKMeshList", "", context.scene, "udkmesh_list",
2444 context.scene, "udkmesh_list_idx", rows=5)
2445 layout.prop(context.scene, "udk_option_selectanimations")
2447 if context.scene.udk_option_selectanimations:
2448 layout.operator("action.setanimupdate")
2449 layout.label(text="Action Set(s) - Match / Export")
2450 layout.template_list("UL_UDKActionSetList", "", context.scene, "udkas_list",
2451 context.scene, "udkas_list_idx", rows=5)
2452 layout.separator()
2453 layout.prop(context.scene, "udk_option_scale")
2454 layout.prop(context.scene, "udk_option_rebuildobjects")
2455 # layout.prop(context.scene, "udk_option_ignoreactiongroupnames")
2457 row11 = layout.row()
2458 row11.operator("object.udk_export")
2459 row11.operator("object.toggle_console")
2460 layout.operator(OBJECT_OT_UTRebuildArmature.bl_idname)
2461 layout.label(text="Mesh")
2462 layout.operator(OBJECT_OT_MeshClearWeights.bl_idname)
2463 layout.operator(OBJECT_OT_UTSelectedFaceSmooth.bl_idname)
2464 layout.operator(OBJECT_OT_UTRebuildMesh.bl_idname)
2465 layout.operator(OBJECT_OT_UDKCheckMeshLines.bl_idname)
2468 def udkupdateobjects():
2469 my_objlist = bpy.context.scene.udkArm_list
2470 objectl = []
2471 for objarm in bpy.context.scene.objects: # list and filter only mesh and armature
2472 if objarm.type == 'ARMATURE':
2473 objectl.append(objarm)
2475 for _objd in objectl: # check if list has in udk list
2476 bfound_obj = False
2477 for _obj in my_objlist:
2478 if _obj.name == _objd.name and _obj.otype == _objd.type:
2479 _obj.bselect = _objd.select
2480 bfound_obj = True
2481 break
2483 if bfound_obj is False:
2484 # print("ADD ARMATURE...")
2485 my_item = my_objlist.add()
2486 my_item.name = _objd.name
2487 my_item.bselect = _objd.select
2488 my_item.otype = _objd.type
2489 removeobject = []
2490 for _udkobj in my_objlist:
2491 bfound_objv = False
2493 for _objd in bpy.context.scene.objects: # check if there no existing object from sense to remove it
2494 if _udkobj.name == _objd.name and _udkobj.otype == _objd.type:
2495 bfound_objv = True
2496 break
2498 if bfound_objv is False:
2499 removeobject.append(_udkobj)
2500 # print("remove check...")
2501 for _item in removeobject: # loop remove object from udk list object
2502 count = 0
2503 for _obj in my_objlist:
2504 if _obj.name == _item.name and _obj.otype == _item.otype:
2505 my_objlist.remove(count)
2506 break
2507 count += 1
2509 my_objlist = bpy.context.scene.udkmesh_list
2510 objectl = []
2511 for objarm in bpy.context.scene.objects: # list and filter only mesh and armature
2512 if objarm.type == 'MESH':
2513 objectl.append(objarm)
2514 for _objd in objectl: # check if list has in udk list
2515 bfound_obj = False
2516 for _obj in my_objlist:
2517 if _obj.name == _objd.name and _obj.otype == _objd.type:
2518 _obj.bselect = _objd.select
2519 bfound_obj = True
2520 break
2521 if bfound_obj is False:
2522 my_item = my_objlist.add()
2523 my_item.name = _objd.name
2524 my_item.bselect = _objd.select
2525 my_item.otype = _objd.type
2526 removeobject = []
2527 for _udkobj in my_objlist:
2528 bfound_objv = False
2529 for _objd in bpy.context.scene.objects: # check if there no existing object from sense to remove it
2530 if _udkobj.name == _objd.name and _udkobj.otype == _objd.type:
2531 bfound_objv = True
2532 break
2533 if bfound_objv is False:
2534 removeobject.append(_udkobj)
2535 # print("remove check...")
2536 for _item in removeobject: # loop remove object from udk list object
2537 count = 0
2538 for _obj in my_objlist:
2539 if _obj.name == _item.name and _obj.otype == _item.otype:
2540 my_objlist.remove(count)
2541 break
2542 count += 1
2545 class OBJECT_OT_UDKObjUpdate(Operator):
2546 """This will update the filter of the mesh and armature"""
2547 bl_idname = "object.selobjectpdate"
2548 bl_label = "Update Object(s)"
2550 actionname = bpy.props.StringProperty()
2552 def execute(self, context):
2553 udkupdateobjects()
2554 return{'FINISHED'}
2557 def udkcheckmeshline():
2558 objmesh = None
2559 for obj in bpy.context.scene.objects:
2560 if obj.type == 'MESH' and obj.select is True:
2561 objmesh = obj
2563 objmesh = triangulate_mesh(objmesh) # create a copy of the mesh
2564 bpy.ops.object.mode_set(mode='OBJECT')
2566 for i in bpy.context.scene.objects:
2567 i.select = False # deselect all objects
2569 objmesh.select = True
2570 bpy.context.scene.objects.active = objmesh # set active mesh
2571 wedges = ObjMap()
2572 points = ObjMap()
2573 bpy.ops.object.mode_set(mode='EDIT') # set in edit mode
2574 bpy.ops.mesh.select_all(action='DESELECT')
2575 bpy.context.tool_settings.mesh_select_mode = (True, False, False) # select vertices
2577 if objmesh is not None:
2578 print("found mesh")
2579 print(objmesh)
2580 print(objmesh.data.tessfaces)
2581 vertex_list = []
2582 for face in objmesh.data.tessfaces:
2583 wedge_list = []
2584 vect_list = []
2585 for i in range(3):
2586 vert_index = face.vertices[i]
2587 vert = objmesh.data.vertices[vert_index]
2588 vect_list.append(FVector(vert.co.x, vert.co.y, vert.co.z))
2589 vpos = objmesh.matrix_local * vert.co
2590 p = VPoint()
2591 p.Point.X = vpos.x
2592 p.Point.Y = vpos.y
2593 p.Point.Z = vpos.z
2594 w = VVertex()
2595 w.PointIndex = points.get(p) # store keys
2596 index_wedge = wedges.get(w)
2597 wedge_list.append(index_wedge)
2598 no = face.normal
2599 norm = FVector(no[0], no[1], no[2])
2600 tnorm = vect_list[1].sub(vect_list[0]).cross(vect_list[2].sub(vect_list[1]))
2601 dot = norm.dot(tnorm)
2603 tri = VTriangle()
2604 if dot > 0:
2605 (tri.WedgeIndex2, tri.WedgeIndex1, tri.WedgeIndex0) = wedge_list
2606 elif dot < 0:
2607 (tri.WedgeIndex0, tri.WedgeIndex1, tri.WedgeIndex2) = wedge_list
2608 else:
2609 dindex0 = face.vertices[0]
2610 dindex1 = face.vertices[1]
2611 dindex2 = face.vertices[2]
2612 vertex_list.append(dindex0)
2613 vertex_list.append(dindex1)
2614 vertex_list.append(dindex2)
2616 bpy.ops.object.mode_set(mode='OBJECT')
2617 for vertex in objmesh.data.vertices: # loop all vertex in the mesh list
2618 for vl in vertex_list: # loop for error vertex
2619 if vertex.index == vl: # if match set to select
2620 vertex.select = True
2621 break
2622 bpy.ops.object.mode_set(mode='EDIT') # set in edit mode to see the select vertex
2623 objmesh.data.update() # update object
2624 bpy.context.scene.update() # update scene
2625 message = "MESH PASS"
2626 if len(vertex_list) > 0:
2627 message = "MESH FAIL"
2628 return message
2631 class OBJECT_OT_UDKCheckMeshLines(Operator):
2632 """Select the mesh for export test. This will create dummy mesh to see which area are broken. """ \
2633 """If the vertices share the same position it will cause a bug"""
2634 bl_idname = "object.udkcheckmeshline"
2635 bl_label = "Check Mesh Vertices"
2637 def execute(self, context):
2638 message = udkcheckmeshline()
2639 self.report({'ERROR'}, message)
2640 return{'FINISHED'}
2643 class OBJECT_OT_ActionSetAnimUpdate(Operator):
2644 """Select Armture to match the action set groups. """ \
2645 """All bones keys must be set to match with number of bones"""
2646 bl_idname = "action.setanimupdate"
2647 bl_label = "Update Action Set(s)"
2649 actionname = bpy.props.StringProperty()
2651 def execute(self, context):
2652 my_sett = bpy.context.scene.udkas_list
2654 bones = []
2655 armature = None
2656 armatures = []
2657 armatureselected = []
2658 for objarm in bpy.context.scene.objects:
2659 if objarm.type == 'ARMATURE':
2660 # print("ADDED ARMATURE...")
2661 armatures.append(objarm)
2662 if objarm.select is True:
2663 armatureselected.append(objarm)
2665 if len(armatureselected) == len(armatures) == 1:
2666 armature = armatures[0]
2667 if len(armatures) == 1:
2668 armature = armatures[0]
2669 if len(armatureselected) == 1:
2670 armature = armatureselected[0]
2672 if armature is not None:
2673 for bone in armature.pose.bones:
2674 bones.append(bone.name)
2676 for action in bpy.data.actions: # action list
2677 bfound = False
2678 count = 0
2679 for actionbone in action.groups:
2680 # print("Pose bone name: ",actionbone.name)
2681 for b in bones:
2682 if b == actionbone.name:
2683 count += 1
2684 # print(b," : ",actionbone.name)
2685 break
2686 for actionlist in my_sett:
2687 if action.name == actionlist.name:
2688 bactionfound = True
2689 if len(bones) == len(action.groups) == count:
2690 actionlist.bmatch = True
2691 else:
2692 actionlist.bmatch = False
2693 bfound = True
2694 break
2695 if bfound is not True:
2696 my_item = my_sett.add()
2697 # print(dir(my_item.bmatch))
2698 my_item.name = action.name
2699 # my_item.template_list_controls = "bmatch:bexport"
2700 if len(bones) == len(action.groups) == count:
2701 my_item.bmatch = True
2702 else:
2703 my_item.bmatch = False
2704 removeactions = []
2705 # check action list and data actions
2706 for actionlist in bpy.context.scene.udkas_list:
2707 bfind = False
2708 notfound = 0
2709 for act in bpy.data.actions:
2710 if actionlist.name == act.name:
2711 bfind = True
2712 else:
2713 notfound += 1
2714 # print("ACT NAME:",actionlist.name," COUNT",notfound)
2715 if notfound == len(bpy.data.actions):
2716 # print("remove :",actionlist.name)
2717 removeactions.append(actionlist.name)
2718 # print("Not in the action data list:",len(removeactions))
2719 # remove list or changes in the name the template list
2720 for actname in removeactions:
2721 actioncount = 0
2722 for actionlist in my_sett:
2723 # print("action name:",actionlist.name)
2724 if actionlist.name == actname:
2725 my_sett.remove(actioncount)
2726 break
2727 actioncount += 1
2728 return{'FINISHED'}
2731 class ExportUDKAnimData(Operator):
2732 """Export Skeleton Mesh / Animation Data file(s). """ \
2733 """One mesh and one armature else select one mesh or armature to be exported"""
2734 bl_idname = "export_anim.udk" # this is important since its how bpy.ops.export.udk_anim_data is constructed
2735 bl_label = "Export PSK/PSA"
2737 # List of operator properties, the attributes will be assigned
2738 # to the class instance from the operator settings before calling.
2740 filepath = StringProperty(
2741 subtype='FILE_PATH',
2743 filter_glob = StringProperty(
2744 default="*.psk;*.psa",
2745 options={'HIDDEN'},
2747 udk_option_scale = FloatProperty(
2748 name="UDK Scale",
2749 description="In case you don't want to scale objects manually - "
2750 "This will just scale position when on export for the skeleton mesh and animation data",
2751 default=1
2753 udk_option_rebuildobjects = BoolProperty(
2754 name="Rebuild Objects",
2755 description="In case of deform skeleton mesh and animations data - "
2756 "This will rebuild objects from raw format on export when checked",
2757 default=False
2760 @classmethod
2761 def poll(cls, context):
2762 return context.active_object is not None
2764 def execute(self, context):
2765 scene = bpy.context.scene
2766 scene.udk_option_export_psk = (scene.udk_option_export == '0' or scene.udk_option_export == '2')
2767 scene.udk_option_export_psa = (scene.udk_option_export == '1' or scene.udk_option_export == '2')
2768 bpy.context.scene.udk_option_scale = self.udk_option_scale
2769 bpy.context.scene.udk_option_rebuildobjects = self.udk_option_rebuildobjects
2771 filepath = get_dst_path()
2773 # cache settings
2774 restore_frame = scene.frame_current
2776 message = "Finish Export!"
2777 try:
2778 export(filepath)
2780 except Error as err:
2781 print(err.message)
2782 message = err.message
2784 # restore settings
2785 scene.frame_set(restore_frame)
2787 self.report({'WARNING', 'INFO'}, message)
2788 return {'FINISHED'}
2790 def draw(self, context):
2791 layout = self.layout
2792 scene = context.scene
2794 layout.prop(scene, "udk_option_smoothing_groups")
2795 layout.prop(scene, "udk_option_clight_uv")
2796 layout.prop(scene, "udk_option_verbose")
2797 layout.prop(scene, "udk_option_filename_src")
2798 layout.prop(scene, "udk_option_export")
2799 layout.prop(self, "udk_option_scale")
2800 layout.prop(self, "udk_option_rebuildobjects")
2802 def invoke(self, context, event):
2803 self.udk_option_scale = bpy.context.scene.udk_option_scale
2804 self.udk_option_rebuildobjects = bpy.context.scene.udk_option_rebuildobjects
2806 wm = context.window_manager
2807 wm.fileselect_add(self)
2808 return {'RUNNING_MODAL'}
2811 def menu_func(self, context):
2812 default_path = os.path.splitext(bpy.data.filepath)[0] + ".psk"
2813 self.layout.operator(ExportUDKAnimData.bl_idname,
2814 text="Skeleton Mesh / Animation Data (.psk/.psa)").filepath = default_path
2817 # Add-ons Preferences Update Panel
2819 # Define Panel classes for updating
2820 panels = (
2821 Panel_UDKExport,
2825 def update_panel(self, context):
2826 message = "Export Unreal Engine Format(.psk/.psa): Updating Panel locations has failed"
2827 try:
2828 for panel in panels:
2829 if "bl_rna" in panel.__dict__:
2830 bpy.utils.unregister_class(panel)
2832 for panel in panels:
2833 panel.bl_category = context.user_preferences.addons[__name__].preferences.category
2834 bpy.utils.register_class(panel)
2836 except Exception as e:
2837 print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e))
2838 pass
2841 class PskAddonPreferences(AddonPreferences):
2842 # this must match the addon name, use '__package__'
2843 # when defining this in a submodule of a python package.
2844 bl_idname = __name__
2846 category = StringProperty(
2847 name="Tab Category",
2848 description="Choose a name for the category of the panel",
2849 default="File I/O",
2850 update=update_panel
2853 def draw(self, context):
2854 layout = self.layout
2856 row = layout.row()
2857 col = row.column()
2858 col.label(text="Tab Category:")
2859 col.prop(self, "category", text="")
2862 # ===========================================================================
2863 # Entry
2864 # ===========================================================================
2865 def register():
2867 bpy.utils.register_module(__name__)
2868 bpy.types.TOPBAR_MT_file_export.append(menu_func)
2869 update_panel(None, bpy.context)
2871 # Added by [MGVS]
2872 bpy.types.Scene.udk_option_filename_src = EnumProperty(
2873 name="Filename",
2874 description="Sets the name for the files",
2875 items=[
2876 ('0', "From object", "Name will be taken from object name"),
2877 ('1', "From Blend", "Name will be taken from .blend file name")
2879 default='0'
2881 bpy.types.Scene.udk_option_export_psk = BoolProperty(
2882 name="bool export psa",
2883 description="Boolean for exporting psk format (Skeleton Mesh)",
2884 default=True
2886 bpy.types.Scene.udk_option_export_psa = BoolProperty(
2887 name="bool export psa",
2888 description="Boolean for exporting psa format (Animation Data)",
2889 default=True
2891 bpy.types.Scene.udk_option_clight_uv = BoolProperty(
2892 name="Clamp UV",
2893 description="True is to limit Clamp UV co-ordinates to [0-1]. False is unrestricted (x,y)",
2894 default=False
2896 bpy.types.Scene.udk_copy_merge = BoolProperty(
2897 name="Merge Mesh",
2898 description="This will copy the mesh(s) and merge the object together "
2899 "and unlink the mesh to be remove while exporting the object",
2900 default=False
2902 bpy.types.Scene.udk_option_export = EnumProperty(
2903 name="Export",
2904 description="What to export",
2905 items=[
2906 ('0', "Mesh only", "Exports the PSK file for the Skeletal Mesh"),
2907 ('1', "Animation only", "Export the PSA file for Action Set(s)(Animations Data)"),
2908 ('2', "Mesh & Animation", "Export both PSK and PSA files(Skeletal Mesh/Animation(s) Data)")
2910 default='2'
2912 bpy.types.Scene.udk_option_verbose = BoolProperty(
2913 name="Verbose",
2914 description="Verbose console output",
2915 default=False
2917 bpy.types.Scene.udk_option_smoothing_groups = BoolProperty(
2918 name="Smooth Groups",
2919 description="Activate hard edges as smooth groups",
2920 default=True
2922 bpy.types.Scene.udk_option_triangulate = BoolProperty(
2923 name="Triangulate Mesh",
2924 description="Convert Quads to Triangles",
2925 default=False
2927 bpy.types.Scene.udk_option_selectanimations = BoolProperty(
2928 name="Select Animation(s)",
2929 description="Select animation(s) for export to psa file",
2930 default=False
2932 bpy.types.Scene.udk_option_selectobjects = BoolProperty(
2933 name="Select Object(s)",
2934 description="Select Armature and Mesh(s). Just make sure mesh(s) is parent to armature",
2935 default=False
2937 bpy.types.Scene.udk_option_rebuildobjects = BoolProperty(
2938 name="Rebuild Objects",
2939 description="In case of deform skeleton mesh and animations data - "
2940 "This will rebuild objects from raw format on export when checked",
2941 default=False
2943 bpy.types.Scene.udk_option_ignoreactiongroupnames = BoolProperty(
2944 name="Ignore Action Group Names",
2945 description="This will Ignore Action Set Group Names Check With Armature Bones. "
2946 "It will override armature to set action set",
2947 default=False
2949 bpy.types.Scene.udk_option_scale = FloatProperty(
2950 name="UDK Scale",
2951 description="In case you don't want to scale objects manually - "
2952 "This will just scale position when on export for the skeleton mesh and animation data",
2953 default=1
2955 bpy.types.Scene.udkas_list = CollectionProperty(
2956 type=UDKActionSetListPG
2958 bpy.types.Scene.udkas_list_idx = IntProperty()
2959 bpy.types.Scene.udkobj_list = CollectionProperty(
2960 type=UDKObjListPG
2962 bpy.types.Scene.udkobj_list_idx = IntProperty()
2963 bpy.types.Scene.udkmesh_list = CollectionProperty(
2964 type=UDKMeshListPG
2966 bpy.types.Scene.udkmesh_list_idx = IntProperty()
2967 bpy.types.Scene.udkArm_list = CollectionProperty(
2968 type=UDKArmListPG
2970 bpy.types.Scene.udkArm_list_idx = IntProperty()
2973 def unregister():
2974 bpy.utils.unregister_module(__name__)
2975 bpy.types.TOPBAR_MT_file_export.remove(menu_func)
2977 del bpy.types.Scene.udk_option_filename_src
2978 del bpy.types.Scene.udk_option_export_psk
2979 del bpy.types.Scene.udk_option_export_psa
2980 del bpy.types.Scene.udk_option_clight_uv
2981 del bpy.types.Scene.udk_copy_merge
2982 del bpy.types.Scene.udk_option_export
2983 del bpy.types.Scene.udk_option_verbose
2984 del bpy.types.Scene.udk_option_smoothing_groups
2985 del bpy.types.Scene.udk_option_triangulate
2986 del bpy.types.Scene.udk_option_selectanimations
2987 del bpy.types.Scene.udk_option_selectobjects
2988 del bpy.types.Scene.udk_option_rebuildobjects
2989 del bpy.types.Scene.udk_option_ignoreactiongroupnames
2990 del bpy.types.Scene.udk_option_scale
2991 del bpy.types.Scene.udkas_list
2992 del bpy.types.Scene.udkas_list_idx
2993 del bpy.types.Scene.udkobj_list
2994 del bpy.types.Scene.udkobj_list_idx
2995 del bpy.types.Scene.udkmesh_list
2996 del bpy.types.Scene.udkmesh_list_idx
2997 del bpy.types.Scene.udkArm_list
2998 del bpy.types.Scene.udkArm_list_idx
3001 if __name__ == "__main__":
3002 print(header("UDK Export PSK/PSA 2.6", 'CENTER'))
3003 register()