diff options
Diffstat (limited to 'io_export_unreal_psk_psa.py')
-rw-r--r-- | io_export_unreal_psk_psa.py | 697 |
1 files changed, 348 insertions, 349 deletions
diff --git a/io_export_unreal_psk_psa.py b/io_export_unreal_psk_psa.py index 787ca24d..d2982b34 100644 --- a/io_export_unreal_psk_psa.py +++ b/io_export_unreal_psk_psa.py @@ -24,14 +24,13 @@ bl_info = { "location": "File > Export > Skeletal Mesh/Animation Data (.psk/.psa)", "description": "Export Skeleletal Mesh/Animation Data", "warning": "", - "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"\ + "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/" "Scripts/Import-Export/Unreal_psk_psa", - "tracker_url": "https://projects.blender.org/tracker/index.php?"\ - "func=detail&aid=21366", + "tracker_url": "https://developer.blender.org/T21366", "category": "Import-Export"} """ --- Unreal Skeletal Mesh and Animation Export (.psk and .psa) export script v0.0.1 --<br> +-- Unreal Skeletal Mesh and Animation Export (.psk and .psa) export script v0.0.1 --<br> - NOTES: - This script Exports To Unreal's PSK and PSA file formats for Skeletal Meshes and Animations. <br> @@ -45,11 +44,11 @@ bl_info = { [ - Edit by: Darknet - v0.0.3 - v0.0.12 -- This will work on UT3 and it is a stable version that work with vehicle for testing. +- This will work on UT3 and it is a stable version that work with vehicle for testing. - Main Bone fix no dummy needed to be there. - Just bone issues position, rotation, and offset for psk. - The armature bone position, rotation, and the offset of the bone is fix. It was to deal with skeleton mesh export for psk. -- Animation is fix for position, offset, rotation bone support one rotation direction when armature build. +- Animation is fix for position, offset, rotation bone support one rotation direction when armature build. - It will convert your mesh into triangular when exporting to psk file. - Did not work with psa export yet. @@ -151,7 +150,7 @@ from bpy.props import * from struct import pack # REFERENCE MATERIAL JUST IN CASE: -# +# # U = x / sqrt(x^2 + y^2 + z^2) # V = y / sqrt(x^2 + y^2 + z^2) # @@ -196,21 +195,21 @@ def verbose( msg, iteration=-1, max_iterations=4, msg_truncated="..." ): return print(msg) - + #=========================================================================== # Log header/separator #=========================================================================== def header( msg, justify='LEFT', spacer='_', cols=78 ): - + if justify == 'LEFT': s = '{:{spacer}<{cols}}'.format(msg+" ", spacer=spacer, cols=cols) - + elif justify == 'RIGHT': s = '{:{spacer}>{cols}}'.format(" "+msg, spacer=spacer, cols=cols) - + else: s = '{:{spacer}^{cols}}'.format(" "+msg+" ", spacer=spacer, cols=cols) - + return "\n" + s + "\n" #=========================================================================== @@ -218,11 +217,11 @@ def header( msg, justify='LEFT', spacer='_', cols=78 ): # the object must be usable as a dictionary key #=========================================================================== class ObjMap: - + def __init__(self): self.dict = {} self.next = 0 - + def get(self, obj): if obj in self.dict: return self.dict[obj] @@ -231,37 +230,37 @@ class ObjMap: self.next = self.next + 1 self.dict[obj] = id return id - + def items(self): getval = operator.itemgetter(0) getkey = operator.itemgetter(1) return map(getval, sorted(self.dict.items(), key=getkey)) #=========================================================================== -# RG - UNREAL DATA STRUCTS - CONVERTED FROM C STRUCTS GIVEN ON UDN SITE +# RG - UNREAL DATA STRUCTS - CONVERTED FROM C STRUCTS GIVEN ON UDN SITE # provided here: http://udn.epicgames.com/Two/BinaryFormatSpecifications.html # updated UDK (Unreal Engine 3): http://udn.epicgames.com/Three/BinaryFormatSpecifications.html #=========================================================================== class FQuat: - def __init__(self): + def __init__(self): self.X = 0.0 self.Y = 0.0 self.Z = 0.0 self.W = 1.0 - + def dump(self): return pack('ffff', self.X, self.Y, self.Z, self.W) - + def __cmp__(self, other): return cmp(self.X, other.X) \ or cmp(self.Y, other.Y) \ or cmp(self.Z, other.Z) \ or cmp(self.W, other.W) - + def __hash__(self): return hash(self.X) ^ hash(self.Y) ^ hash(self.Z) ^ hash(self.W) - + def __str__(self): return "[%f,%f,%f,%f](FQuat)" % (self.X, self.Y, self.Z, self.W) @@ -271,34 +270,34 @@ class FVector(object): self.X = X self.Y = Y self.Z = Z - + def dump(self): return pack('fff', self.X, self.Y, self.Z) - + def __cmp__(self, other): return cmp(self.X, other.X) \ or cmp(self.Y, other.Y) \ or cmp(self.Z, other.Z) - + def _key(self): return (type(self).__name__, self.X, self.Y, self.Z) - + def __hash__(self): return hash(self._key()) - + def __eq__(self, other): if not hasattr(other, '_key'): return False - return self._key() == other._key() - + return self._key() == other._key() + def dot(self, other): return self.X * other.X + self.Y * other.Y + self.Z * other.Z - + def cross(self, other): return FVector(self.Y * other.Z - self.Z * other.Y, self.Z * other.X - self.X * other.Z, self.X * other.Y - self.Y * other.X) - + def sub(self, other): return FVector(self.X - other.X, self.Y - other.Y, @@ -313,7 +312,7 @@ class VJointPos: self.XSize = 0.0 self.YSize = 0.0 self.ZSize = 0.0 - + def dump(self): return self.Orientation.dump() + self.Position.dump() + pack('4f', self.Length, self.XSize, self.YSize, self.ZSize) @@ -332,7 +331,7 @@ class AnimInfoBinary: self.StartBone = 0 self.FirstRawFrame = 0 self.NumRawFrames = 0 - + def dump(self): return pack('64s64siiiifffiii', str.encode(self.Name), str.encode(self.Group), self.TotalBones, self.RootInclude, self.KeyCompressionStyle, self.KeyQuotum, self.KeyPrediction, self.TrackTime, self.AnimRate, self.StartBone, self.FirstRawFrame, self.NumRawFrames) @@ -343,7 +342,7 @@ class VChunkHeader: self.TypeFlag = 1999801 # special value self.DataSize = type_size self.DataCount = 0 - + def dump(self): return pack('20siii', self.ChunkID, self.TypeFlag, self.DataSize, self.DataCount) @@ -357,7 +356,7 @@ class VMaterial: self.AuxFlags = 0 # DWORD self.LodBias = 0 self.LodStyle = 0 - + def dump(self): #print("DATA MATERIAL:",self.MaterialName) return pack('64siLiLii', str.encode(self.MaterialName), self.TextureIndex, self.PolyFlags, self.AuxMaterial, self.AuxFlags, self.LodBias, self.LodStyle) @@ -370,11 +369,11 @@ class VBone: self.NumChildren = 0 self.ParentIndex = 0 self.BonePos = VJointPos() - + def dump(self): return pack('64sLii', str.encode(self.Name), self.Flags, self.NumChildren, self.ParentIndex) + self.BonePos.dump() -#same as above - whatever - this is how Epic does it... +#same as above - whatever - this is how Epic does it... class FNamedBoneBinary: def __init__(self): @@ -384,7 +383,7 @@ class FNamedBoneBinary: self.ParentIndex = 0 self.BonePos = VJointPos() self.IsRealBone = 0 # this is set to 1 when the bone is actually a bone in the mesh and not a dummy - + def dump(self): return pack('64sLii', str.encode(self.Name), self.Flags, self.NumChildren, self.ParentIndex) + self.BonePos.dump() @@ -394,7 +393,7 @@ class VRawBoneInfluence: self.Weight = 0.0 self.PointIndex = 0 self.BoneIndex = 0 - + def dump(self): return pack('fii', self.Weight, self.PointIndex, self.BoneIndex) @@ -404,7 +403,7 @@ class VQuatAnimKey: self.Position = FVector() self.Orientation = FQuat() self.Time = 0.0 - + def dump(self): return self.Position.dump() + self.Orientation.dump() + pack('f', self.Time) @@ -416,25 +415,25 @@ class VVertex(object): self.V = 0.0 self.MatIndex = 0 # BYTE self.Reserved = 0 # BYTE - self.SmoothGroup = 0 - + self.SmoothGroup = 0 + def dump(self): return pack('HHffBBH', self.PointIndex, 0, self.U, self.V, self.MatIndex, self.Reserved, 0) - + def __cmp__(self, other): return cmp(self.PointIndex, other.PointIndex) \ or cmp(self.U, other.U) \ or cmp(self.V, other.V) \ or cmp(self.MatIndex, other.MatIndex) \ or cmp(self.Reserved, other.Reserved) \ - or cmp(self.SmoothGroup, other.SmoothGroup ) - + or cmp(self.SmoothGroup, other.SmoothGroup ) + def _key(self): return (type(self).__name__, self.PointIndex, self.U, self.V, self.MatIndex, self.Reserved) - + def __hash__(self): return hash(self._key()) - + def __eq__(self, other): if not hasattr(other, '_key'): return False @@ -447,7 +446,7 @@ class VPointSimple: def __cmp__(self, other): return cmp(self.Point, other.Point) - + def __hash__(self): return hash(self._key()) @@ -463,26 +462,26 @@ class VPoint(object): def __init__(self): self.Point = FVector() - self.SmoothGroup = 0 - + self.SmoothGroup = 0 + def dump(self): return self.Point.dump() - + def __cmp__(self, other): return cmp(self.Point, other.Point) \ - or cmp(self.SmoothGroup, other.SmoothGroup) - + or cmp(self.SmoothGroup, other.SmoothGroup) + def _key(self): return (type(self).__name__, self.Point, self.SmoothGroup) - + def __hash__(self): return hash(self._key()) \ - ^ hash(self.SmoothGroup) - + ^ hash(self.SmoothGroup) + def __eq__(self, other): if not hasattr(other, '_key'): return False - return self._key() == other._key() + return self._key() == other._key() class VTriangle: @@ -493,7 +492,7 @@ class VTriangle: self.MatIndex = 0 # BYTE self.AuxMatIndex = 0 # BYTE self.SmoothingGroups = 0 # DWORD - + def dump(self): return pack('HHHBBL', self.WedgeIndex0, self.WedgeIndex1, self.WedgeIndex2, self.MatIndex, self.AuxMatIndex, self.SmoothingGroups) #print("smooth",self.SmoothingGroups) @@ -503,21 +502,21 @@ class VTriangle: #=========================================================================== #=========================================================================== -# RG - helper class to handle the normal way the UT files are stored +# RG - helper class to handle the normal way the UT files are stored # as sections consisting of a header and then a list of data structures #=========================================================================== class FileSection: - + def __init__(self, name, type_size): self.Header = VChunkHeader(name, type_size) self.Data = [] # list of datatypes - + def dump(self): data = self.Header.dump() for i in range(len(self.Data)): data = data + self.Data[i].dump() return data - + def UpdateHeader(self): self.Header.DataCount = len(self.Data) @@ -525,7 +524,7 @@ class FileSection: # PSK #=========================================================================== class PSKFile: - + def __init__(self): self.GeneralHeader = VChunkHeader("ACTRHEAD", 0) self.Points = FileSection("PNTS0000", SIZE_VPOINT) # VPoint @@ -534,8 +533,8 @@ class PSKFile: self.Materials = FileSection("MATT0000", SIZE_VMATERIAL) # VMaterial self.Bones = FileSection("REFSKELT", SIZE_VBONE) # VBone self.Influences = FileSection("RAWWEIGHTS", SIZE_VRAWBONEINFLUENCE) # VRawBoneInfluence - - #RG - this mapping is not dumped, but is used internally to store the new point indices + + #RG - this mapping is not dumped, but is used internally to store the new point indices # for vertex groups calculated during the mesh dump, so they can be used again # to dump bone influences during the armature dump # @@ -545,29 +544,29 @@ class PSKFile: # Layout: # { groupname : [ (index, weight), ... ], ... } # - # example: + # example: # { 'MyVertexGroup' : [ (0, 1.0), (5, 1.0), (3, 0.5) ] , 'OtherGroup' : [(2, 1.0)] } - - self.VertexGroups = {} - + + self.VertexGroups = {} + def AddPoint(self, p): self.Points.Data.append(p) - + def AddWedge(self, w): self.Wedges.Data.append(w) - + def AddFace(self, f): self.Faces.Data.append(f) - + def AddMaterial(self, m): self.Materials.Data.append(m) - + def AddBone(self, b): self.Bones.Data.append(b) - + def AddInfluence(self, i): self.Influences.Data.append(i) - + def UpdateHeaders(self): self.Points.UpdateHeader() self.Wedges.UpdateHeader() @@ -575,12 +574,12 @@ class PSKFile: self.Materials.UpdateHeader() self.Bones.UpdateHeader() self.Influences.UpdateHeader() - + def dump(self): self.UpdateHeaders() data = self.GeneralHeader.dump() + self.Points.dump() + self.Wedges.dump() + self.Faces.dump() + self.Materials.dump() + self.Bones.dump() + self.Influences.dump() return data - + def GetMatByIndex(self, mat_index): if mat_index >= 0 and len(self.Materials.Data) > mat_index: return self.Materials.Data[mat_index] @@ -590,7 +589,7 @@ class PSKFile: m.MaterialName = MaterialName[mat_index] self.AddMaterial(m) return m - + def PrintOut(self): print( "{:>16} {:}".format( "Points", len(self.Points.Data) ) ) print( "{:>16} {:}".format( "Wedges", len(self.Wedges.Data) ) ) @@ -603,16 +602,16 @@ class PSKFile: # PSA # # Notes from UDN: -# The raw key array holds all the keys for all the bones in all the specified sequences, +# The raw key array holds all the keys for all the bones in all the specified sequences, # organized as follows: -# For each AnimInfoBinary's sequence there are [Number of bones] times [Number of frames keys] -# in the VQuatAnimKeys, laid out as tracks of [numframes] keys for each bone in the order of -# the bones as defined in the array of FnamedBoneBinary in the PSA. +# For each AnimInfoBinary's sequence there are [Number of bones] times [Number of frames keys] +# in the VQuatAnimKeys, laid out as tracks of [numframes] keys for each bone in the order of +# the bones as defined in the array of FnamedBoneBinary in the PSA. # -# Once the data from the PSK (now digested into native skeletal mesh) and PSA (digested into -# a native animation object containing one or more sequences) are associated together at runtime, -# bones are linked up by name. Any bone in a skeleton (from the PSK) that finds no partner in -# the animation sequence (from the PSA) will assume its reference pose stance ( as defined in +# Once the data from the PSK (now digested into native skeletal mesh) and PSA (digested into +# a native animation object containing one or more sequences) are associated together at runtime, +# bones are linked up by name. Any bone in a skeleton (from the PSK) that finds no partner in +# the animation sequence (from the PSA) will assume its reference pose stance ( as defined in # the offsets & rotations that are in the VBones making up the reference skeleton from the PSK) #=========================================================================== class PSAFile: @@ -624,57 +623,57 @@ class PSAFile: self.RawKeys = FileSection("ANIMKEYS", SIZE_VQUATANIMKEY) #VQuatAnimKey # this will take the format of key=Bone Name, value = (BoneIndex, Bone Object) # THIS IS NOT DUMPED - self.BoneLookup = {} + self.BoneLookup = {} def AddBone(self, b): self.Bones.Data.append(b) - + def AddAnimation(self, a): self.Animations.Data.append(a) - + def AddRawKey(self, k): self.RawKeys.Data.append(k) - + def UpdateHeaders(self): self.Bones.UpdateHeader() self.Animations.UpdateHeader() self.RawKeys.UpdateHeader() - + def GetBoneByIndex(self, bone_index): if bone_index >= 0 and len(self.Bones.Data) > bone_index: return self.Bones.Data[bone_index] - + def IsEmpty(self): return (len(self.Bones.Data) == 0 or len(self.Animations.Data) == 0) - + def StoreBone(self, b): self.BoneLookup[b.Name] = [-1, b] - + def UseBone(self, bone_name): if bone_name in self.BoneLookup: bone_data = self.BoneLookup[bone_name] - + if bone_data[0] == -1: bone_data[0] = len(self.Bones.Data) self.AddBone(bone_data[1]) #self.Bones.Data.append(bone_data[1]) - + return bone_data[0] - + def GetBoneByName(self, bone_name): if bone_name in self.BoneLookup: bone_data = self.BoneLookup[bone_name] return bone_data[1] - + def GetBoneIndex(self, bone_name): if bone_name in self.BoneLookup: bone_data = self.BoneLookup[bone_name] return bone_data[0] - + def dump(self): self.UpdateHeaders() return self.GeneralHeader.dump() + self.Bones.dump() + self.Animations.dump() + self.RawKeys.dump() - + def PrintOut(self): print( "{:>16} {:}".format( "Bones", len(self.Bones.Data) ) ) print( "{:>16} {:}".format( "Animations", len(self.Animations.Data) ) ) @@ -709,7 +708,7 @@ def make_namedbonebinary( name, parent_index, child_count, orientation_quat, pos bone.BonePos.Position.Y = position_vect.y bone.BonePos.Position.Z = position_vect.z bone.IsRealBone = is_real - return bone + return bone def make_fquat( bquat ): quat = FQuat() @@ -719,7 +718,7 @@ def make_fquat( bquat ): quat.Z = -bquat.z quat.W = bquat.w return quat - + def make_fquat_default( bquat ): quat = FQuat() #print(dir(bquat)) @@ -737,7 +736,7 @@ def is_1d_face( face, mesh ): v0 = face.vertices[0] v1 = face.vertices[1] v2 = face.vertices[2] - + return (mesh.vertices[v0].co == mesh.vertices[v1].co \ or mesh.vertices[v1].co == mesh.vertices[v2].co \ or mesh.vertices[v2].co == mesh.vertices[v0].co) @@ -748,9 +747,9 @@ def is_1d_face( face, mesh ): # (renamed to seperate it from VVertex.SmoothGroup) #=========================================================================== class SmoothingGroup: - + static_id = 1 - + def __init__(self): self.faces = [] self.neighboring_faces = [] @@ -758,12 +757,12 @@ class SmoothingGroup: self.id = -1 self.local_id = SmoothingGroup.static_id SmoothingGroup.static_id += 1 - + def __cmp__(self, other): if isinstance(other, SmoothingGroup): return cmp( self.local_id, other.local_id ) return -1 - + def __hash__(self): return hash(self.local_id) @@ -776,37 +775,37 @@ class SmoothingGroup: temp_id = temp_id << 1 else: raise Error("Smoothing Group ID Overflowed, Smoothing Group evidently has more than 31 neighboring groups") - + self.id = temp_id return self.id - + def make_neighbor(self, new_neighbor): if new_neighbor not in self.neighboring_groups: self.neighboring_groups.append( new_neighbor ) def contains_face(self, face): return (face in self.faces) - + def add_neighbor_face(self, face): if not face in self.neighboring_faces: self.neighboring_faces.append( face ) - + def add_face(self, face): if not face in self.faces: self.faces.append( face ) def determine_edge_sharing( mesh ): - + edge_sharing_list = dict() - + for edge in mesh.edges: edge_sharing_list[edge.key] = [] - + for face in mesh.tessfaces: for key in face.edge_keys: if not face in edge_sharing_list[key]: edge_sharing_list[key].append(face) # mark this face as sharing this edge - + return edge_sharing_list def find_edges( mesh, key ): @@ -819,21 +818,21 @@ def find_edges( mesh, key ): return edge.index def add_face_to_smoothgroup( mesh, face, edge_sharing_list, smoothgroup ): - + if face in smoothgroup.faces: return smoothgroup.add_face(face) - + for key in face.edge_keys: - + edge_id = find_edges(mesh, key) - + if edge_id != None: - + # not sharp if not( mesh.edges[edge_id].use_edge_sharp): - + for shared_face in edge_sharing_list[key]: if shared_face != face: # recursive @@ -845,14 +844,14 @@ def add_face_to_smoothgroup( mesh, face, edge_sharing_list, smoothgroup ): smoothgroup.add_neighbor_face( shared_face ) def determine_smoothgroup_for_face( mesh, face, edge_sharing_list, smoothgroup_list ): - + for group in smoothgroup_list: if (face in group.faces): return - + smoothgroup = SmoothingGroup(); add_face_to_smoothgroup( mesh, face, edge_sharing_list, smoothgroup ) - + if not smoothgroup in smoothgroup_list: smoothgroup_list.append( smoothgroup ) @@ -869,16 +868,16 @@ def build_neighbors_tree( smoothgroup_list ): # parse_smooth_groups #=========================================================================== def parse_smooth_groups( mesh ): - + print("Parsing smooth groups...") - + t = time.clock() smoothgroup_list = [] edge_sharing_list = determine_edge_sharing(mesh) #print("faces:",len(mesh.tessfaces)) interval = math.floor(len(mesh.tessfaces) / 100) if interval == 0: #if the faces are few do this - interval = math.floor(len(mesh.tessfaces) / 10) + interval = math.floor(len(mesh.tessfaces) / 10) #print("FACES:",len(mesh.tessfaces),"//100 =" "interval:",interval) for face in mesh.tessfaces: #print(dir(face)) @@ -888,14 +887,14 @@ def parse_smooth_groups( mesh ): print("Processing... {}%\r".format( int(face.index / len(mesh.tessfaces) * 100) ), end='') sys.stdout.flush() print("Completed" , ' '*20) - + verbose("len(smoothgroup_list)={}".format(len(smoothgroup_list))) - + build_neighbors_tree(smoothgroup_list) - + for group in smoothgroup_list: group.get_valid_smoothgroup_id() - + print("Smooth group parsing completed in {:.2f}s".format(time.clock() - t)) return smoothgroup_list @@ -904,11 +903,11 @@ def parse_smooth_groups( mesh ): # blender 2.50 format using the Operators/command convert the mesh to tri mesh #=========================================================================== def triangulate_mesh( object ): - + verbose(header("triangulateNMesh")) #print(type(object)) scene = bpy.context.scene - + me_ob = object.copy() me_ob.data = object.to_mesh(bpy.context.scene, True, 'PREVIEW') #write data object bpy.context.scene.objects.link(me_ob) @@ -916,24 +915,24 @@ def triangulate_mesh( object ): bpy.ops.object.mode_set(mode='OBJECT') for i in scene.objects: i.select = False # deselect all objects - + me_ob.select = True scene.objects.active = me_ob - + print("Copy and Convert mesh just incase any way...") - + bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.select_all(action='SELECT')# select all the face/vertex/edge bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.quads_convert_to_tris() bpy.context.scene.update() - + bpy.ops.object.mode_set(mode='OBJECT') - + bpy.context.scene.udk_option_triangulate = True - + verbose("Triangulated mesh") - + me_ob.data = me_ob.to_mesh(bpy.context.scene, True, 'PREVIEW') #write data object bpy.context.scene.update() return me_ob @@ -945,7 +944,7 @@ def meshmerge(selectedobjects): if len(selectedobjects) > 1: print("selectedobjects:",len(selectedobjects)) #print select object count = 0 #reset count - for count in range(len( selectedobjects)): + for count in range(len( selectedobjects)): #print("Index:",count) if selectedobjects[count] != None: me_da = selectedobjects[count].data.copy() #copy data @@ -967,7 +966,7 @@ def meshmerge(selectedobjects): if len(cloneobjects) > 1: bpy.types.Scene.udk_copy_merge = True return cloneobjects[0] - + #sort the mesh center top list and not center at the last array. Base on order while select to merge mesh to make them center. def sortmesh(selectmesh): print("MESH SORTING...") @@ -980,7 +979,7 @@ def sortmesh(selectmesh): else:#if not add here for not center notcentermesh.append(selectmesh[countm]) selectmesh = [] - #add mesh object in order for merge object + #add mesh object in order for merge object for countm in range(len(centermesh)): selectmesh.append(centermesh[countm]) for countm in range(len(notcentermesh)): @@ -1007,10 +1006,10 @@ def parse_mesh( mesh, psk ): bpy.context.scene.objects.unlink(setmesh) #print("FACES----:",len(mesh.data.tessfaces)) verbose("Working mesh object: {}".format(mesh.name)) - + #collect a list of the material names print("Materials...") - + mat_slot_index = 0 for slot in mesh.material_slots: @@ -1019,7 +1018,7 @@ def parse_mesh( mesh, psk ): MaterialName.append(slot.name) #if slot.material.texture_slots[0] != None: #if slot.material.texture_slots[0].texture.image.filepath != None: - #print(" Texture path {}".format(slot.material.texture_slots[0].texture.image.filepath)) + #print(" Texture path {}".format(slot.material.texture_slots[0].texture.image.filepath)) #create the current material v_material = psk.GetMatByIndex(mat_slot_index) v_material.MaterialName = slot.name @@ -1029,7 +1028,7 @@ def parse_mesh( mesh, psk ): verbose(" PSK index {}".format(v_material.TextureIndex)) #END slot in mesh.material_slots - + # object_mat = mesh.materials[0] #object_material_index = mesh.active_material_index #FIXME ^ this is redundant due to "= face.material_index" in face loop @@ -1037,19 +1036,19 @@ def parse_mesh( mesh, psk ): wedges = ObjMap() points = ObjMap() #vertex points_linked = {} - + discarded_face_count = 0 sys.setrecursionlimit(1000000) smoothgroup_list = parse_smooth_groups(mesh.data) - + print("{} faces".format(len(mesh.data.tessfaces))) - + print("Smooth groups active:", bpy.context.scene.udk_option_smoothing_groups) - + for face in mesh.data.tessfaces: - + smoothgroup_id = 0x80000000 - + for smooth_group in smoothgroup_list: if smooth_group.contains_face(face): smoothgroup_id = smooth_group.id @@ -1059,38 +1058,38 @@ def parse_mesh( mesh, psk ): #print current_face.uv_textures # modified by VendorX object_material_index = face.material_index - + if len(face.vertices) != 3: raise Error("Non-triangular face (%i)" % len(face.vertices)) - - #RG - apparently blender sometimes has problems when you do quad to triangle + + #RG - apparently blender sometimes has problems when you do quad to triangle # conversion, and ends up creating faces that have only TWO points - - # one of the points is simply in the vertex list for the face twice. - # This is bad, since we can't get a real face normal for a LINE, we need - # a plane for this. So, before we add the face to the list of real faces, - # ensure that the face is actually a plane, and not a line. If it is not + # one of the points is simply in the vertex list for the face twice. + # This is bad, since we can't get a real face normal for a LINE, we need + # a plane for this. So, before we add the face to the list of real faces, + # ensure that the face is actually a plane, and not a line. If it is not # planar, just discard it and notify the user in the console after we're # done dumping the rest of the faces - + if not is_1d_face(face, mesh.data): - + wedge_list = [] vect_list = [] - + #get or create the current material psk.GetMatByIndex(object_material_index) face_index = face.index has_uv = False face_uv = None - + if len(mesh.data.uv_textures) > 0: - has_uv = True + has_uv = True uv_layer = mesh.data.tessface_uv_textures.active face_uv = uv_layer.data[face_index] #size(data) is number of texture faces. Each face has UVs #print("DATA face uv: ",len(faceUV.uv), " >> ",(faceUV.uv[0][0])) - + for i in range(3): vert_index = face.vertices[i] vert = mesh.data.vertices[vert_index] @@ -1105,11 +1104,11 @@ def parse_mesh( mesh, psk ): else: #print ("No UVs?") uv = [0.0, 0.0] - + #flip V coordinate because UEd requires it and DOESN'T flip it on its own like it #does with the mesh Y coordinates. this is otherwise known as MAGIC-2 uv[1] = 1.0 - uv[1] - + # clamp UV coords if udk_option_clamp_uv is True if bpy.context.scene.udk_option_clamp_uv: if (uv[0] > 1): @@ -1120,11 +1119,11 @@ def parse_mesh( mesh, psk ): uv[1] = 1 if (uv[1] < 0): uv[1] = 0 - + # RE - Append untransformed vector (for normal calc below) # TODO: convert to Blender.Mathutils vect_list.append( FVector(vert.co.x, vert.co.y, vert.co.z) ) - + # Transform position for export #vpos = vert.co * object_material_index @@ -1148,13 +1147,13 @@ def parse_mesh( mesh, psk ): lPoint.Point.X = vpos.x lPoint.Point.Y = vpos.y lPoint.Point.Z = vpos.z - + if lPoint in points_linked: if not(p in points_linked[lPoint]): points_linked[lPoint].append(p) else: points_linked[lPoint] = [p] - + # Create the wedge w = VVertex() w.MatIndex = object_material_index @@ -1165,18 +1164,18 @@ def parse_mesh( mesh, psk ): w.SmoothGroup = smoothgroup_id index_wedge = wedges.get(w) wedge_list.append(index_wedge) - + #print results #print("result PointIndex={}, U={:.6f}, V={:.6f}, wedge_index={}".format( # w.PointIndex, # w.U, # w.V, # index_wedge)) - + #END for i in range(3) # Determine face vertex order - + # TODO: convert to Blender.Mathutils # get normal from blender no = face.normal @@ -1202,13 +1201,13 @@ def parse_mesh( mesh, psk ): dindex0 = face.vertices[0]; dindex1 = face.vertices[1]; dindex2 = face.vertices[2]; - + mesh.data.vertices[dindex0].select = True mesh.data.vertices[dindex1].select = True mesh.data.vertices[dindex2].select = True - + raise Error("Normal coplanar with face! points:", mesh.data.vertices[dindex0].co, mesh.data.vertices[dindex1].co, mesh.data.vertices[dindex2].co) - + face.select = True if face.use_smooth == True: tri.SmoothingGroups = 1 @@ -1219,53 +1218,53 @@ def parse_mesh( mesh, psk ): if bpy.context.scene.udk_option_smoothing_groups: tri.SmoothingGroups = smoothgroup_id print("Bool Smooth") - + psk.AddFace(tri) - #END if not is_1d_face(current_face, mesh.data) + #END if not is_1d_face(current_face, mesh.data) else: discarded_face_count += 1 - + #END face in mesh.data.faces - + print("{} points".format(len(points.dict))) - + for point in points.items(): psk.AddPoint(point) - + if len(points.dict) > 32767: raise Error("Mesh vertex limit exceeded! {} > 32767".format(len(points.dict))) - + print("{} wedges".format(len(wedges.dict))) - + for wedge in wedges.items(): psk.AddWedge(wedge) - + # alert the user to degenerate face issues if discarded_face_count > 0: print("WARNING: Mesh contained degenerate faces (non-planar)") print(" Discarded {} faces".format(discarded_face_count)) - - #RG - walk through the vertex groups and find the indexes into the PSK points array - #for them, then store that index and the weight as a tuple in a new list of + + #RG - walk through the vertex groups and find the indexes into the PSK points array + #for them, then store that index and the weight as a tuple in a new list of #verts for the group that we can look up later by bone name, since Blender matches #verts to bones for influences by having the VertexGroup named the same thing as #the bone - - #[print(x, len(points_linked[x])) for x in points_linked] + + #[print(x, len(points_linked[x])) for x in points_linked] #print("pointsindex length ",len(points_linked)) #vertex group - + # all vertex groups of the mesh (obj)... for obj_vertex_group in mesh.vertex_groups: - + #print(" bone group build:",obj_vertex_group.name)#print bone name #print(dir(obj_vertex_group)) verbose("obj_vertex_group.name={}".format(obj_vertex_group.name)) - + vertex_list = [] - + # all vertices in the mesh... for vertex in mesh.data.vertices: #print(dir(vertex)) @@ -1280,7 +1279,7 @@ def parse_mesh( mesh, psk ): vpos.y = vpos.y * bpy.context.scene.udk_option_scale vpos.z = vpos.z * bpy.context.scene.udk_option_scale p.Point.X = vpos.x - p.Point.Y = vpos.y + p.Point.Y = vpos.y p.Point.Z = vpos.z #print(p) #print(len(points_linked[p])) @@ -1292,11 +1291,11 @@ def parse_mesh( mesh, psk ): except Exception:#if get error ignore them #not safe I think print("Error link points!") pass - + #bone name, [point id and wieght] #print("Add Vertex Group:",obj_vertex_group.name, " No. Points:",len(vertex_list)) psk.VertexGroups[obj_vertex_group.name] = vertex_list - + # remove the temporary triangulated mesh if bpy.context.scene.udk_option_triangulate == True: verbose("Removing temporary triangle mesh: {}".format(mesh.name)) @@ -1308,34 +1307,34 @@ def parse_mesh( mesh, psk ): # Collate bones that belong to the UDK skeletal mesh #=========================================================================== def parse_armature( armature, psk, psa ): - + print(header("ARMATURE", 'RIGHT')) verbose("Armature object: {} Armature data: {}".format(armature.name, armature.data.name)) - + # generate a list of root bone candidates root_candidates = [b for b in armature.data.bones if b.parent == None and b.use_deform == True] - + # should be a single, unambiguous result if len(root_candidates) == 0: raise Error("Cannot find root for UDK bones. The root bone must use deform.") - + if len(root_candidates) > 1: raise Error("Ambiguous root for UDK. More than one root bone is using deform.") - + # prep for bone collection udk_root_bone = root_candidates[0] udk_bones = [] BoneUtil.static_bone_id = 0 # replaces global - + # traverse bone chain print("{: <3} {: <48} {: <20}".format("ID", "Bone", "Status")) print() recurse_bone(udk_root_bone, udk_bones, psk, psa, 0, armature.matrix_local) - + # final validation if len(udk_bones) < 3: raise Error("Less than three bones may crash UDK (legacy issue?)") - + # return a list of bones making up the entire udk skel # this is passed to parse_animation instead of working from keyed bones in the action return udk_bones @@ -1350,14 +1349,14 @@ def parse_armature( armature, psk, psa ): # indent text indent for recursive log #=========================================================================== def recurse_bone( bone, bones, psk, psa, parent_id, parent_matrix, indent="" ): - + status = "Ok" - + bones.append(bone); if not bone.use_deform: status = "No effect" - + # calc parented bone transform if bone.parent != None: quat = make_fquat(bone.matrix.to_quaternion()) @@ -1378,12 +1377,12 @@ def recurse_bone( bone, bones, psk, psa, parent_id, parent_matrix, indent="" ): translation.z = translation.z * bpy.context.scene.udk_option_scale bone_id = BoneUtil.static_bone_id # ALT VERS BoneUtil.static_bone_id += 1 # ALT VERS - + child_count = len(bone.children) - + psk.AddBone( make_vbone(bone.name, parent_id, child_count, quat, translation) ) psa.StoreBone( make_namedbonebinary(bone.name, parent_id, child_count, quat, translation, 1) ) - + #RG - dump influences for this bone - use the data we collected in the mesh dump phase to map our bones to vertex groups if bone.name in psk.VertexGroups: vertex_list = psk.VertexGroups[bone.name] @@ -1400,12 +1399,12 @@ def recurse_bone( bone, bones, psk, psa, parent_id, parent_matrix, indent="" ): else: status = "No vertex group" #FIXME overwriting previous status error? - + print("{:<3} {:<48} {:<20}".format(bone_id, indent+bone.name, status)) - + #bone.matrix_local #recursively dump child bones - + for child_bone in bone.children: recurse_bone(child_bone, bones, psk, psa, bone_id, parent_matrix, " "+indent) @@ -1420,12 +1419,12 @@ class BoneUtil: # psa the PSA file object #=========================================================================== def parse_animation( armature, udk_bones, actions_to_export, psa ): - + print(header("ANIMATION", 'RIGHT')) - + context = bpy.context anim_rate = context.scene.render.fps - + verbose("Armature object: {}".format(armature.name)) print("Scene: {} FPS: {} Frames: {} to {}".format(context.scene.name, anim_rate, context.scene.frame_start, context.scene.frame_end)) print("Processing {} action(s)".format(len(actions_to_export))) @@ -1434,13 +1433,13 @@ def parse_animation( armature, udk_bones, actions_to_export, psa ): print("None Actions Set! skipping...") return restoreAction = armature.animation_data.action # Q: is animation_data always valid? - + restoreFrame = context.scene.frame_current # we already do this in export_proxy, but we'll do it here too for now raw_frame_index = 0 # used to set FirstRawFrame, seperating actions in the raw keyframe array - + # action loop... for action in actions_to_export: - + # removed: check for armature with no animation; all it did was force you to add one if not len(action.fcurves): @@ -1451,14 +1450,14 @@ def parse_animation( armature, udk_bones, actions_to_export, psa ): # note if loop all actions that is not armature it will override and will break armature animation. armature.animation_data.action = action context.scene.update() - + # min/max frames define range framemin, framemax = action.frame_range start_frame = int(framemin) end_frame = int(framemax) scene_range = range(start_frame, end_frame + 1) frame_count = len(scene_range) - + # create the AnimInfoBinary anim = AnimInfoBinary() anim.Name = action.name @@ -1466,11 +1465,11 @@ def parse_animation( armature, udk_bones, actions_to_export, psa ): anim.NumRawFrames = frame_count anim.AnimRate = anim_rate anim.FirstRawFrame = raw_frame_index - + print("{}, frames {} to {} ({} frames)".format(action.name, start_frame, end_frame, frame_count)) - + # removed: bone lookup table - + # build a list of pose bones relevant to the collated udk_bones # fixme: could be done once, prior to loop? udk_pose_bones = [] @@ -1483,39 +1482,39 @@ def parse_animation( armature, udk_bones, actions_to_export, psa ): # sort in the order the bones appear in the PSA file ordered_bones = {} ordered_bones = sorted([(psa.UseBone(b.name), b) for b in udk_pose_bones], key=operator.itemgetter(0)) - + # NOTE: posebone.bone references the obj/edit bone # REMOVED: unique_bone_indexes is redundant? - + # frame loop... for i in range(frame_count): - + frame = scene_range[i] - + #verbose("FRAME {}".format(i), i) # test loop sampling - + # advance to frame (automatically updates the pose) context.scene.frame_set(frame) - + # compute the key for each bone for bone_data in ordered_bones: - + bone_index = bone_data[0] pose_bone = bone_data[1] pose_bone_matrix = mathutils.Matrix(pose_bone.matrix) - + if pose_bone.parent != None: pose_bone_parent_matrix = mathutils.Matrix(pose_bone.parent.matrix) pose_bone_matrix = pose_bone_parent_matrix.inverted() * pose_bone_matrix - + head = pose_bone_matrix.to_translation() quat = pose_bone_matrix.to_quaternion().normalized() - + if pose_bone.parent != None: quat = make_fquat(quat) else: quat = make_fquat_default(quat) - + #scale animation position here? if bpy.context.scene.udk_option_scale < 0 or bpy.context.scene.udk_option_scale > 1: head.x = head.x * bpy.context.scene.udk_option_scale @@ -1527,25 +1526,25 @@ def parse_animation( armature, udk_bones, actions_to_export, psa ): vkey.Position.Y = head.y vkey.Position.Z = head.z vkey.Orientation = quat - + # frame delta = 1.0 / fps vkey.Time = 1.0 / float(anim_rate) # according to C++ header this is "disregarded" - + psa.AddRawKey(vkey) - + # END for bone_data in ordered_bones raw_frame_index += 1 - + # END for i in range(frame_count) - + anim.TotalBones = len(ordered_bones) # REMOVED len(unique_bone_indexes) anim.TrackTime = float(frame_count) # frame_count/anim.AnimRate makes more sense, but this is what actually works in UDK verbose("anim.TotalBones={}, anim.TrackTime={}".format(anim.TotalBones, anim.TrackTime)) - + psa.AddAnimation(anim) - + # END for action in actions # restore @@ -1560,7 +1559,7 @@ def parse_animation( armature, udk_bones, actions_to_export, psa ): def collate_actions(): verbose(header("collate_actions")) actions_to_export = [] - + for action in bpy.data.actions: if bpy.context.scene.udk_option_selectanimations: # check if needed to select actions set for exporting it print("Action Set is selected!") @@ -1575,7 +1574,7 @@ def collate_actions(): continue verbose(" + {}".format(action.name)) #action set name actions_to_export.append(action) #add to the action array - + return actions_to_export #=========================================================================== @@ -1584,16 +1583,16 @@ def collate_actions(): #=========================================================================== def find_armature_and_mesh(): verbose(header("find_armature_and_mesh", 'LEFT', '<', 60)) - + context = bpy.context active_object = context.active_object armature = None mesh = None - + # TODO: # this could be more intuitive #bpy.ops.object.mode_set(mode='OBJECT') - + if bpy.context.scene.udk_option_selectobjects: #if checked select object true do list object on export print("select mode:") if len(bpy.context.scene.udkArm_list) > 0: @@ -1626,14 +1625,14 @@ def find_armature_and_mesh(): mesh = active_object else: raise Error("The selected mesh is not parented to the armature") - + # otherwise, expect a single mesh parented to the armature (other object types are ignored) else: print("Number of meshes:",len(meshes)) print("Number of meshes (selected):",len(meshes)) if len(meshes) == 1: mesh = meshes[0] - + elif len(meshes) > 1: if len(meshselected) >= 1: mesh = sortmesh(meshselected) @@ -1651,7 +1650,7 @@ def find_armature_and_mesh(): else: #bpy.ops.object.mode_set(mode='OBJECT') all_armatures = [obj for obj in bpy.context.scene.objects if obj.type == 'ARMATURE'] - + if len(all_armatures) == 1:#if armature has one scene just assign it armature = all_armatures[0] elif len(all_armatures) > 1:#if there more armature then find the select armature @@ -1665,15 +1664,15 @@ def find_armature_and_mesh(): raise Error("Please select an armatures in the scene") else: raise Error("No armatures in scene") - + verbose("Found armature: {}".format(armature.name)) - + meshselected = [] parented_meshes = [obj for obj in armature.children if obj.type == 'MESH'] - + if len(armature.children) == 0: raise Error("The selected Armature has no mesh parented to the Armature Object!") - + for obj in armature.children: #print(dir(obj)) if obj.type == 'MESH' and obj.select == True: @@ -1684,14 +1683,14 @@ def find_armature_and_mesh(): mesh = active_object else: raise Error("The selected mesh is not parented to the armature") - + # otherwise, expect a single mesh parented to the armature (other object types are ignored) else: print("Number of meshes:",len(parented_meshes)) print("Number of meshes (selected):",len(meshselected)) if len(parented_meshes) == 1: mesh = parented_meshes[0] - + elif len(parented_meshes) > 1: if len(meshselected) >= 1: mesh = sortmesh(meshselected) @@ -1699,7 +1698,7 @@ def find_armature_and_mesh(): raise Error("More than one mesh(s) parented to armature. Select object(s)!") else: raise Error("No mesh parented to armature") - + verbose("Found mesh: {}".format(mesh.name)) if mesh == None or armature == None: raise Error("Check Mesh and Armature are list!") @@ -1707,7 +1706,7 @@ def find_armature_and_mesh(): #print("Armature and Mesh Vertex Groups matches Ok!") #else: #raise Error("Armature bones:" + str(len(armature.pose.bones)) + " Mesh Vertex Groups:" + str(len(mesh.vertex_groups)) +" doesn't match!") - + #this will check if object need to be rebuild. if bpy.context.scene.udk_option_rebuildobjects: #print("INIT... REBUILDING...") @@ -1727,14 +1726,14 @@ def find_armature_and_mesh(): def collate_vertex_groups( mesh ): verbose("collate_vertex_groups") groups = [] - + for group in mesh.vertex_groups: - + groups.append(group) verbose(" " + group.name) - + return groups - + #=========================================================================== # Main #=========================================================================== @@ -1743,72 +1742,72 @@ def export(filepath): bpy.types.Scene.udk_copy_merge = False #in case fail to export set this to default t = time.clock() context = bpy.context - + print("Blender Version {}.{}.{}".format(bpy.app.version[0], bpy.app.version[1], bpy.app.version[2])) print("Filepath: {}".format(filepath)) - + verbose("PSK={}, PSA={}".format(context.scene.udk_option_export_psk, context.scene.udk_option_export_psa)) - + # find armature and mesh # [change this to implement alternative methods; raise Error() if not found] udk_armature, udk_mesh = find_armature_and_mesh() - + # check misc conditions if not (udk_armature.scale.x == udk_armature.scale.y == udk_armature.scale.z == 1): raise Error("bad armature scale: armature object should have uniform scale of 1 (ALT-S)") - + if not (udk_mesh.scale.x == udk_mesh.scale.y == udk_mesh.scale.z == 1): raise Error("bad mesh scale: mesh object should have uniform scale of 1 (ALT-S)") - + if not (udk_armature.location.x == udk_armature.location.y == udk_armature.location.z == 0): raise Error("bad armature location: armature should be located at origin (ALT-G)") - + if not (udk_mesh.location.x == udk_mesh.location.y == udk_mesh.location.z == 0): raise Error("bad mesh location: mesh should be located at origin (ALT-G)") - + # prep psk = PSKFile() psa = PSAFile() - + # step 1 parse_mesh(udk_mesh, psk) - + # step 2 udk_bones = parse_armature(udk_armature, psk, psa) - + # step 3 if context.scene.udk_option_export_psa == True: actions = collate_actions() parse_animation(udk_armature, udk_bones, actions, psa) - + # write files print(header("Exporting", 'CENTER')) - + psk_filename = filepath + '.psk' psa_filename = filepath + '.psa' - + if context.scene.udk_option_export_psk == True: print("Skeletal mesh data...") psk.PrintOut() - file = open(psk_filename, "wb") + file = open(psk_filename, "wb") file.write(psk.dump()) - file.close() + file.close() print("Exported: " + psk_filename) print() - + if context.scene.udk_option_export_psa == True: print("Animation data...") if not psa.IsEmpty(): psa.PrintOut() - file = open(psa_filename, "wb") + file = open(psa_filename, "wb") file.write(psa.dump()) - file.close() + file.close() print("Exported: " + psa_filename) else: print("No Animation (.psa file) to export") print() - + #if objects are rebuild do the unlink if bpy.context.scene.udk_option_rebuildobjects: print("Unlinking Objects") @@ -1826,20 +1825,20 @@ class Operator_UDKExport( bpy.types.Operator ): """Export to UDK""" bl_idname = "object.udk_export" bl_label = "Export now" - + def execute(self, context): print( "\n"*8 ) - + scene = bpy.context.scene - + scene.udk_option_export_psk = (scene.udk_option_export == '0' or scene.udk_option_export == '2') scene.udk_option_export_psa = (scene.udk_option_export == '1' or scene.udk_option_export == '2') - + filepath = get_dst_path() - + # cache settings restore_frame = scene.frame_current - + message = "Finish Export!" try: export(filepath) @@ -1847,15 +1846,15 @@ class Operator_UDKExport( bpy.types.Operator ): except Error as err: print(err.message) message = err.message - + # restore settings scene.frame_set(restore_frame) - + self.report({'ERROR'}, message) - + # restore settings scene.frame_set(restore_frame) - + return {'FINISHED'} #=========================================================================== @@ -1865,7 +1864,7 @@ class Operator_ToggleConsole( bpy.types.Operator ): """Show or hide the console""" bl_idname = "object.toggle_console" bl_label = "Toggle console" - + #def invoke(self, context, event): # bpy.ops.wm.console_toggle() # return{'FINISHED'} @@ -1894,7 +1893,7 @@ bpy.types.Scene.udk_option_filename_src = EnumProperty( items = [ ('0', "From object", "Name will be taken from object name"), ('1', "From Blend", "Name will be taken from .blend file name") ], default = '0') - + bpy.types.Scene.udk_option_export_psk = BoolProperty( name = "bool export psa", description = "Boolean for exporting psk format (Skeleton Mesh)", @@ -1909,7 +1908,7 @@ bpy.types.Scene.udk_option_clamp_uv = BoolProperty( name = "Clamp UV", description = "True is to limit Clamp UV co-ordinates to [0-1]. False is unrestricted (x,y). ", default = False) - + bpy.types.Scene.udk_copy_merge = BoolProperty( name = "Merge Mesh", description = "This will copy the mesh(s) and merge the object together and unlink the mesh to be remove while exporting the object.", @@ -1937,22 +1936,22 @@ bpy.types.Scene.udk_option_triangulate = BoolProperty( name = "Triangulate Mesh", description = "Convert Quads to Triangles", default = False) - + bpy.types.Scene.udk_option_selectanimations = BoolProperty( name = "Select Animation(s)", description = "Select animation(s) for export to psa file.", default = False) - + bpy.types.Scene.udk_option_selectobjects = BoolProperty( name = "Select Object(s)", description = "Select Armature and Mesh(s). Just make sure mesh(s) is parent to armature.", default = False) - + bpy.types.Scene.udk_option_rebuildobjects = BoolProperty( name = "Rebuild Objects", description = "In case of deform skeleton mesh and animations data. This will rebuild objects from raw format on export when checked.", default = False) - + bpy.types.Scene.udk_option_ignoreactiongroupnames = BoolProperty( name = "Ignore Action Group Names", description = "This will Ignore Action Set Group Names Check With Armature Bones. It will override armature to set action set.", @@ -1970,7 +1969,7 @@ class OBJECT_OT_UTSelectedFaceSmooth(bpy.types.Operator): """It will only select smooth faces that is select mesh""" bl_idname = "object.utselectfacesmooth" # XXX, name??? bl_label = "Select Smooth Faces"#"Select Smooth faces" - + def invoke(self, context, event): print("----------------------------------------") print("Init Select Face(s):") @@ -2006,21 +2005,21 @@ class OBJECT_OT_UTSelectedFaceSmooth(bpy.types.Operator): else: print("Didn't select Mesh Object!") self.report({'INFO'}, "Didn't Select Mesh Object!") - print("----------------------------------------") + print("----------------------------------------") return{'FINISHED'} - + class OBJECT_OT_MeshClearWeights(bpy.types.Operator): """Remove all mesh vertex groups weights for the bones.""" bl_idname = "object.meshclearweights" # XXX, name??? bl_label = "Remove Vertex Weights"#"Remove Mesh vertex weights" - + def invoke(self, context, event): for obj in bpy.data.objects: if obj.type == 'MESH' and obj.select == True: for vg in obj.vertex_groups: obj.vertex_groups.remove(vg) self.report({'INFO'}, "Mesh Vertex Groups Remove!") - break + break return{'FINISHED'} def unpack_list(list_of_tuples): @@ -2036,7 +2035,7 @@ def rebuildmesh(obj): for i in bpy.context.scene.objects: i.select = False #deselect all objects obj.select = True bpy.context.scene.objects.active = obj - + me_ob = bpy.data.meshes.new(("Re_"+obj.name)) mesh = obj.data faces = [] @@ -2061,7 +2060,7 @@ def rebuildmesh(obj): faces.extend([(face.vertices[0],face.vertices[1],face.vertices[2],face.vertices[3])]) #vertex positions for vertex in mesh.vertices: - verts.append(vertex.co.to_tuple()) + verts.append(vertex.co.to_tuple()) #vertices weight groups into array vertGroups = {} #array in strings for vgroup in obj.vertex_groups: @@ -2072,15 +2071,15 @@ def rebuildmesh(obj): vlist.append((v.index,vg.weight)) #print((v.index,vg.weight)) vertGroups[vgroup.name] = vlist - + #print("creating mesh object...") #me_ob.from_pydata(verts, [], faces) me_ob.vertices.add(len(verts)) me_ob.tessfaces.add(len(faces)) - me_ob.vertices.foreach_set("co", unpack_list(verts)) + me_ob.vertices.foreach_set("co", unpack_list(verts)) me_ob.tessfaces.foreach_set("vertices_raw",unpack_list( faces)) me_ob.tessfaces.foreach_set("use_smooth", smoothings)#smooth array from face - + #check if there is uv faces if len(uvfaces) > 0: uvtex = me_ob.tessface_uv_textures.new(name="retex") @@ -2096,7 +2095,7 @@ def rebuildmesh(obj): blender_tface.uv2 = mfaceuv[1]; blender_tface.uv3 = mfaceuv[2]; blender_tface.uv4 = mfaceuv[3]; - + me_ob.update()#need to update the information to able to see into the secne obmesh = bpy.data.objects.new(("Re_"+obj.name),me_ob) bpy.context.scene.update() @@ -2129,7 +2128,7 @@ class OBJECT_OT_UTRebuildMesh(bpy.types.Operator): """Note the scale will be 1:1 for object mode. To keep from deforming""" bl_idname = "object.utrebuildmesh" # XXX, name??? bl_label = "Rebuild Mesh"#"Rebuild Mesh" - + def invoke(self, context, event): print("----------------------------------------") print("Init Mesh Bebuild...") @@ -2155,7 +2154,7 @@ def rebuildarmature(obj): for i in bpy.context.scene.objects: i.select = False #deselect all objects ob_new.select = True bpy.context.scene.objects.active = obj - + bpy.ops.object.mode_set(mode='EDIT') for bone in obj.data.edit_bones: if bone.parent != None: @@ -2166,7 +2165,7 @@ def rebuildarmature(obj): for i in bpy.context.scene.objects: i.select = False #deselect all objects bpy.context.scene.objects.active = ob_new bpy.ops.object.mode_set(mode='EDIT') - + for bone in obj.data.bones: bpy.ops.object.mode_set(mode='EDIT') newbone = ob_new.data.edit_bones.new(bone.name) @@ -2185,14 +2184,14 @@ def rebuildarmature(obj): ob_new.animation_data.action = obj.animation_data.action #just make sure it here to do the animations if exist print("Armature Object Name:",ob_new.name) return ob_new - + class OBJECT_OT_UTRebuildArmature(bpy.types.Operator): """If mesh is deform when importing to unreal engine try this. """ \ """It rebuild the bones one at the time by select one armature object scrape to raw setup build. """ \ """Note the scale will be 1:1 for object mode. To keep from deforming""" bl_idname = "object.utrebuildarmature" # XXX, name??? bl_label = "Rebuild Armature" #Rebuild Armature - + def invoke(self, context, event): print("----------------------------------------") print("Init Rebuild Armature...") @@ -2280,12 +2279,12 @@ class Panel_UDKExport( bpy.types.Panel ): #bl_context = "object" bl_space_type = "VIEW_3D" bl_region_type = "TOOLS" - + #def draw_header(self, context): # layout = self.layout #obj = context.object #layout.prop(obj, "select", text="") - + #@classmethod #def poll(cls, context): # return context.active_object @@ -2312,7 +2311,7 @@ class Panel_UDKExport( bpy.types.Panel ): #layout.separator() layout.prop(context.scene, "udk_option_export") layout.prop(context.scene, "udk_option_selectobjects") - + if context.scene.udk_option_selectobjects: layout.operator("object.selobjectpdate") layout.label(text="ARMATURE - Index") @@ -2335,7 +2334,7 @@ class Panel_UDKExport( bpy.types.Panel ): row11.operator("object.udk_export") row11.operator("object.toggle_console") layout.operator(OBJECT_OT_UTRebuildArmature.bl_idname) - layout.label(text="Mesh") + layout.label(text="Mesh") layout.operator(OBJECT_OT_MeshClearWeights.bl_idname) layout.operator(OBJECT_OT_UTSelectedFaceSmooth.bl_idname) layout.operator(OBJECT_OT_UTRebuildMesh.bl_idname) @@ -2377,7 +2376,7 @@ def udkupdateobjects(): my_objlist.remove(count) break count += 1 - + my_objlist = bpy.context.scene.udkmesh_list objectl = [] for objarm in bpy.context.scene.objects:#list and filter only mesh and armature @@ -2412,14 +2411,14 @@ def udkupdateobjects(): my_objlist.remove(count) break count += 1 - + class OBJECT_OT_UDKObjUpdate(bpy.types.Operator): """This will update the filter of the mesh and armature.""" bl_idname = "object.selobjectpdate" bl_label = "Update Object(s)" - + actionname = bpy.props.StringProperty() - + def execute(self, context): udkupdateobjects() return{'FINISHED'} @@ -2439,7 +2438,7 @@ def udkcheckmeshline(): bpy.ops.object.mode_set(mode='EDIT') #set in edit mode bpy.ops.mesh.select_all(action='DESELECT') bpy.context.tool_settings.mesh_select_mode = (True, False, False) #select vertices - + if objmesh != None: print("found mesh") print(objmesh) @@ -2465,7 +2464,7 @@ def udkcheckmeshline(): norm = FVector(no[0], no[1], no[2]) tnorm = vect_list[1].sub(vect_list[0]).cross(vect_list[2].sub(vect_list[1])) dot = norm.dot(tnorm) - + tri = VTriangle() if dot > 0: (tri.WedgeIndex2, tri.WedgeIndex1, tri.WedgeIndex0) = wedge_list @@ -2478,7 +2477,7 @@ def udkcheckmeshline(): vertex_list.append(dindex0) vertex_list.append(dindex1) vertex_list.append(dindex2) - + bpy.ops.object.mode_set(mode='OBJECT') for vertex in objmesh.data.vertices: #loop all vertex in the mesh list for vl in vertex_list: #loop for error vertex @@ -2498,7 +2497,7 @@ class OBJECT_OT_UDKCheckMeshLines(bpy.types.Operator): """If the vertices share the same position it will causes an bug.""" bl_idname = "object.udkcheckmeshline" bl_label = "Check Mesh Vertices" - + def execute(self, context): message = udkcheckmeshline() self.report({'ERROR'}, message) @@ -2511,10 +2510,10 @@ class OBJECT_OT_ActionSetAnimUpdate(bpy.types.Operator): bl_label = "Update Action Set(s)" actionname = bpy.props.StringProperty() - + def execute(self, context): my_sett = bpy.context.scene.udkas_list - + bones = [] armature = None armatures = [] @@ -2525,14 +2524,14 @@ class OBJECT_OT_ActionSetAnimUpdate(bpy.types.Operator): armatures.append(objarm) if objarm.select == True: armatureselected.append(objarm) - + if len(armatureselected) == len(armatures) == 1: armature = armatures[0] if len(armatures) == 1: armature = armatures[0] - if len(armatureselected) == 1: + if len(armatureselected) == 1: armature = armatureselected[0] - + if armature != None: for bone in armature.pose.bones: bones.append(bone.name) @@ -2565,7 +2564,7 @@ class OBJECT_OT_ActionSetAnimUpdate(bpy.types.Operator): my_item.bmatch = True else: my_item.bmatch = False - removeactions = [] + removeactions = [] #check action list and data actions for actionlist in bpy.context.scene.udkas_list: bfind = False @@ -2578,7 +2577,7 @@ class OBJECT_OT_ActionSetAnimUpdate(bpy.types.Operator): #print("ACT NAME:",actionlist.name," COUNT",notfound) if notfound == len(bpy.data.actions): #print("remove :",actionlist.name) - removeactions.append(actionlist.name) + removeactions.append(actionlist.name) #print("Not in the action data list:",len(removeactions)) #remove list or chnages in the name the template list for actname in removeactions: @@ -2589,8 +2588,8 @@ class OBJECT_OT_ActionSetAnimUpdate(bpy.types.Operator): my_sett.remove(actioncount); break actioncount += 1 - return{'FINISHED'} - + return{'FINISHED'} + class ExportUDKAnimData(bpy.types.Operator): """Export Skeleton Mesh / Animation Data file(s). """ \ """One mesh and one armature else select one mesh or armature to be exported""" @@ -2614,7 +2613,7 @@ class ExportUDKAnimData(bpy.types.Operator): udk_option_export = bpy.types.Scene.udk_option_export udk_option_scale = bpy.types.Scene.udk_option_scale udk_option_rebuildobjects = bpy.types.Scene.udk_option_rebuildobjects - + @classmethod def poll(cls, context): return context.active_object != None @@ -2625,12 +2624,12 @@ class ExportUDKAnimData(bpy.types.Operator): scene.udk_option_export_psa = (scene.udk_option_export == '1' or scene.udk_option_export == '2') bpy.context.scene.udk_option_scale = self.udk_option_scale bpy.context.scene.udk_option_rebuildobjects = self.udk_option_rebuildobjects - + filepath = get_dst_path() - + # cache settings restore_frame = scene.frame_current - + message = "Finish Export!" try: export(filepath) @@ -2638,18 +2637,18 @@ class ExportUDKAnimData(bpy.types.Operator): except Error as err: print(err.message) message = err.message - + # restore settings scene.frame_set(restore_frame) - + self.report({'WARNING', 'INFO'}, message) return {'FINISHED'} - + def invoke(self, context, event): wm = context.window_manager wm.fileselect_add(self) return {'RUNNING_MODAL'} - + def menu_func(self, context): default_path = os.path.splitext(bpy.data.filepath)[0] + ".psk" self.layout.operator(ExportUDKAnimData.bl_idname, text="Skeleton Mesh / Animation Data (.psk/.psa)").filepath = default_path @@ -2661,17 +2660,17 @@ def register(): #print("REGISTER") bpy.utils.register_module(__name__) bpy.types.INFO_MT_file_export.append(menu_func) - + def unregister(): #print("UNREGISTER") bpy.utils.unregister_module(__name__) bpy.types.INFO_MT_file_export.remove(menu_func) - + if __name__ == "__main__": #print("\n"*4) print(header("UDK Export PSK/PSA 2.6", 'CENTER')) register() - + #loader #filename = "D:/Projects/BlenderScripts/io_export_udk_psa_psk_alpha.py" #exec(compile(open(filename).read(), filename, 'exec')) |