Skip to content
Snippets Groups Projects
io_export_unreal_psk_psa.py 104 KiB
Newer Older
  • Learn to ignore specific revisions
  • 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307
        for i in scene.objects: i.select = False # deselect all objects
        scene.objects.active    = mesh
        setmesh = mesh
        mesh = triangulate_mesh(mesh)
        if bpy.types.Scene.udk_copy_merge == True:
            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:
    
            print("  Material {} '{}'".format(mat_slot_index, slot.name))
            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)) 
            #create the current material
            v_material              = psk.GetMatByIndex(mat_slot_index)
            v_material.MaterialName = slot.name
            v_material.TextureIndex = mat_slot_index
            v_material.AuxMaterial  = mat_slot_index
            mat_slot_index += 1
            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
    
        wedges          = ObjMap()
        points          = ObjMap()
        points_linked   = {}
        
        discarded_face_count = 0
    
        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
                    break
    
            #print ' -- Dumping UVs -- '
            #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 
            #   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 
            #   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   
                    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]
                    uv          = []
                    #assumes 3 UVs Per face (for now)
                    if (has_uv):
                        if len(face_uv.uv) != 3:
                            print("WARNING: face has more or less than 3 UV coordinates - writing 0,0...")
                            uv = [0.0, 0.0]
                        else:
                            uv = [face_uv.uv[i][0],face_uv.uv[i][1]] #OR bottom works better # 24 for cube
                    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):
                            uv[0] = 1
                        if (uv[0] < 0):
                            uv[0] = 0
                        if (uv[1] > 1):
                            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
    
                    #should fixed this!!
                    
                    
                    vpos = mesh.matrix_local * vert.co
                    if bpy.context.scene.udk_option_scale < 0 or bpy.context.scene.udk_option_scale > 1:
                        print("OK!")
                        vpos.x = vpos.x * bpy.context.scene.udk_option_scale
                        vpos.y = vpos.y * bpy.context.scene.udk_option_scale
                        vpos.z = vpos.z * bpy.context.scene.udk_option_scale
                    #print("scale pos:", vpos)
                    # Create the point
                    p               = VPoint()
                    p.Point.X       = vpos.x
                    p.Point.Y       = vpos.y
                    p.Point.Z       = vpos.z
                    if bpy.context.scene.udk_option_smoothing_groups:#is this necessary?
                        p.SmoothGroup = smoothgroup_id
    
                    lPoint          = VPointSimple()
                    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
                    w.PointIndex    = points.get(p) # store keys
                    w.U             = uv[0]
                    w.V             = uv[1]
                    if bpy.context.scene.udk_option_smoothing_groups:#is this necessary?
                        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
                # convert to FVector
                norm = FVector(no[0], no[1], no[2])
                # Calculate the normal of the face in blender order
                tnorm = vect_list[1].sub(vect_list[0]).cross(vect_list[2].sub(vect_list[1]))
                # RE - dot the normal from blender order against the blender normal
                # this gives the product of the two vectors' lengths along the blender normal axis
                # all that matters is the sign
                dot = norm.dot(tnorm)
    
                tri = VTriangle()
                # RE - magic: if the dot product above > 0, order the vertices 2, 1, 0
                #      if the dot product above < 0, order the vertices 0, 1, 2
                #      if the dot product is 0, then blender's normal is coplanar with the face
                #      and we cannot deduce which side of the face is the outside of the mesh
                if dot > 0:
                    (tri.WedgeIndex2, tri.WedgeIndex1, tri.WedgeIndex0) = wedge_list
                elif dot < 0:
                    (tri.WedgeIndex0, tri.WedgeIndex1, tri.WedgeIndex2) = wedge_list
                else:
                    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
                #print("smooth:",(current_face.use_smooth))
                #not sure if this right
                #tri.SmoothingGroups
                if face.use_smooth == True:
                    tri.SmoothingGroups = 1
                else:
                    tri.SmoothingGroups = 0
                
                #tri.SmoothingGroups = 1
                tri.MatIndex = object_material_index
    
                if bpy.context.scene.udk_option_smoothing_groups:
                    tri.SmoothingGroups = smoothgroup_id
                
                psk.AddFace(tri)
    
            #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 
        #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("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))
                # all groups this vertex is a member of...
                for vgroup in vertex.groups:
                    
                    if vgroup.group == obj_vertex_group.index:
                        
                        vertex_weight   = vgroup.weight
                        p               = VPointSimple()
                        vpos            = mesh.matrix_local * vertex.co
                        if bpy.context.scene.udk_option_scale < 0 or bpy.context.scene.udk_option_scale > 1:
                            vpos.x = vpos.x * bpy.context.scene.udk_option_scale
                            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.Z       = vpos.z
                            
                        for point in points_linked[p]:
                            point_index = points.get(point) #point index
                            v_item      = (point_index, vertex_weight)
                            vertex_list.append(v_item)
                        
            #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))
            bpy.ops.object.mode_set(mode='OBJECT')    # OBJECT mode
            mesh.parent = None                        # unparent to avoid phantom links
            bpy.context.scene.objects.unlink(mesh)    # unlink
    
    
    #===========================================================================
    # 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
    
    
    #===========================================================================
    
    # bone              current bone
    # bones             bone list
    # psk               the PSK file object
    # psa               the PSA file object
    
    # 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())
            quat_parent = bone.parent.matrix.to_quaternion().inverted()
            parent_head = quat_parent * bone.parent.head
            parent_tail = quat_parent * bone.parent.tail
            translation = (parent_tail - parent_head) + bone.head
    
        # calc root bone transform
        else:
            translation = parent_matrix * bone.head             # ARMATURE OBJECT Location
            rot_matrix  = bone.matrix * parent_matrix.to_3x3()  # ARMATURE OBJECT Rotation
            quat        = make_fquat_default(rot_matrix.to_quaternion())
        #udk_option_scale bones here?
        if bpy.context.scene.udk_option_scale < 0 or bpy.context.scene.udk_option_scale > 1:
            translation.x = translation.x * bpy.context.scene.udk_option_scale
            translation.y = translation.y * bpy.context.scene.udk_option_scale
            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]
            #print("vertex list:", len(vertex_list), " of >" ,bone.name )
            for vertex_data in vertex_list:
                
                point_index             = vertex_data[0]
                vertex_weight           = vertex_data[1]
                influence               = VRawBoneInfluence()
                influence.Weight        = vertex_weight
                influence.BoneIndex     = bone_id
                influence.PointIndex    = point_index
                #print ("   AddInfluence to vertex {}, weight={},".format(point_index, vertex_weight))
                psk.AddInfluence(influence)
    
        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)
    
    
    # FIXME rename? remove?
    class BoneUtil:
    
        static_bone_id = 0 # static property to replace global
    
    
    #===========================================================================
    
    # armature          the armature
    # udk_bones         list of bones to be exported
    # actions_to_export list of actions to process for export
    # 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)))
        print()
        if armature.animation_data == None: #this will make sure if animation data was create for the armature else it skip it.
            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):
                print("{} has no keys, skipping".format(action.name))
                continue
            '''
            if bpy.context.scene.udk_option_selectanimations:
                print("Action Set is selected!")
                bready = False
                for actionlist in bpy.context.scene.udkas_list:
                    if actionlist.name == action.name and actionlist.bmatch == True and actionlist.bexport == True:
                        bready = True
                        print("Added Action Set:",action.name)
                        break
                if bready == False:#don't export it
                    print("Skipping Action Set:",action.name)
                    continue
            '''
            # apply action to armature and update scene
            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
            anim.Group          = "" # unused?
            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 = []
            for b in udk_bones:
                for pb in armature.pose.bones:
                    if b.name == pb.name:
                        udk_pose_bones.append(pb)
                        break;
    
            # 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
                        head.y = head.y * bpy.context.scene.udk_option_scale
                        head.z = head.z * bpy.context.scene.udk_option_scale
    
                    vkey                = VQuatAnimKey()
                    vkey.Position.X     = head.x
                    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
        armature.animation_data.action = restoreAction
        context.scene.frame_set(restoreFrame)
    
    
    #===========================================================================
    # Collate actions to be exported
    # Modify this to filter for one, some or all actions. For now use all.
    # RETURNS list of actions
    #===========================================================================
    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!")
                bready = False
                for actionlist in bpy.context.scene.udkas_list: #list the action set from the list
                    if actionlist.name == action.name and actionlist.bmatch == True and actionlist.bexport == True:
                        bready = True
                        print("Added Action Set:",action.name)
                        break
                if bready == False:#don't export it
                    print("Skipping Action Set:",action.name)
                    continue
            verbose(" + {}".format(action.name)) #action set name
            actions_to_export.append(action) #add to the action array
        
        return actions_to_export
    
    
    #===========================================================================
    # Locate the target armature and mesh for export
    # RETURNS armature, mesh
    #===========================================================================
    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:
                print("Armature Name:",bpy.context.scene.udkArm_list[bpy.context.scene.udkArm_list_idx].name)
                for obj in bpy.context.scene.objects:
                    if obj.name == bpy.context.scene.udkArm_list[bpy.context.scene.udkArm_list_idx].name:
                        armature = obj
                        break
            else:
                raise Error("There is no Armature in the list!")
            meshselected = []
            parented_meshes = [obj for obj in armature.children if obj.type == 'MESH']
            for obj in armature.children:
                #print(dir(obj))
                if obj.type == 'MESH' and obj.select == True:
                    bexportmesh = False
                    #print("PARENT MESH:",obj.name)
                    for udkmeshlist in bpy.context.scene.udkmesh_list:
                        if obj.name == udkmeshlist.name and udkmeshlist.bexport == True:
                            bexportmesh = True
                            break
                    if bexportmesh == True:
                        print("Mesh Name:",obj.name," < SELECT TO EXPORT!")
                        meshselected.append(obj)
            print("MESH COUNT:",len(meshselected))
            # try the active object
            if active_object and active_object.type == 'MESH' and len(meshselected) == 0:
                if active_object.parent == armature:
                    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)
                    else:
                        raise Error("More than one mesh(s) parented to armature. Select object(s)!")
                else:
                    raise Error("No mesh parented to armature")
        else: #if not check for select function from the list work the code here
            print("normal mode:")
            # try the active object
            if active_object and active_object.type == 'ARMATURE':
                armature = active_object
                bpy.ops.object.mode_set(mode='OBJECT')
            # otherwise, try for a single armature in the scene
            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:
                    armature = all_armatures[0]
                elif len(all_armatures) > 1:
                    raise Error("Please select an armature 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']
            for obj in armature.children:
                #print(dir(obj))
                if obj.type == 'MESH' and obj.select == True:
                    meshselected.append(obj)
            # try the active object
            if active_object and active_object.type == 'MESH' and len(meshselected) == 0:
                if active_object.parent == armature:
                    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)
                    else:
                        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!")
        if len(armature.pose.bones) == len(mesh.vertex_groups):
            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...")
            print("REBUILDING ARMATURE...")
            #if deform mesh
            armature =  rebuildarmature(armature) #rebuild the armature to raw . If there IK constraint it will ignore it.
            print("REBUILDING MESH...")
            mesh = rebuildmesh(mesh) #rebuild the mesh to raw data format.
    
        return armature, mesh
    
    
    #===========================================================================
    # Returns a list of vertex groups in the mesh. Can be modified to filter
    # groups as necessary.
    # UNUSED
    #===========================================================================
    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
    #===========================================================================
    def export(filepath):
    
        print(header("Export", 'RIGHT'))
        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.write(psk.dump())
            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.write(psa.dump())
                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")
            print("Armature Object Name:",udk_armature.name) #display object name
            bpy.context.scene.objects.unlink(udk_armature) #remove armature from the scene
            print("Mesh Object Name:",udk_mesh.name) #display object name
            bpy.context.scene.objects.unlink(udk_mesh) #remove mesh from the scene
    
        print("Export completed in {:.2f} seconds".format((time.clock() - t)))
    
    
    #===========================================================================
    # Operator
    #===========================================================================
    class Operator_UDKExport( bpy.types.Operator ):
    
    
        bl_idname   = "object.udk_export"
        bl_label    = "Export now"
        __doc__     = "Export to UDK"
        
        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)
    
            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'}
    
    
    #===========================================================================
    # Operator
    #===========================================================================
    class Operator_ToggleConsole( bpy.types.Operator ):
    
    
        bl_idname   = "object.toggle_console"
        bl_label    = "Toggle console"
        __doc__     = "Show or hide the console"
        
        #def invoke(self, context, event):
        #   bpy.ops.wm.console_toggle()
        #   return{'FINISHED'}
        def execute(self, context):
            bpy.ops.wm.console_toggle()
            return {'FINISHED'}
    
    
    #===========================================================================
    # Get filepath for export
    #===========================================================================
    def get_dst_path():
    
        if bpy.context.scene.udk_option_filename_src == '0':
            if bpy.context.active_object:
                path = os.path.split(bpy.data.filepath)[0] + "\\" + bpy.context.active_object.name# + ".psk"
            else:
                #path = os.path.split(bpy.data.filepath)[0] + "\\" + "Unknown";
                path = os.path.splitext(bpy.data.filepath)[0]# + ".psk"
        else:
            path = os.path.splitext(bpy.data.filepath)[0]# + ".psk"
        return path
    
    
    #Added by [MGVS]
    bpy.types.Scene.udk_option_filename_src = EnumProperty(
    
            name        = "Filename",
            description = "Sets the name for the files",
            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)",
            default     = True)
    
    
    bpy.types.Scene.udk_option_export_psa = BoolProperty(
    
            name        = "bool export psa",
            description = "Boolean for exporting psa format (Animation Data)",
            default     = True)
    
    
    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.",
            default     = False)
    
    
    bpy.types.Scene.udk_option_export = EnumProperty(
    
            name        = "Export",
            description = "What to export",
            items       = [ ('0', "Mesh only",          "Exports the PSK file for the Skeletal Mesh"),
                            ('1', "Animation only",     "Export the PSA file for Action Set(s)(Animations Data)"),
                            ('2', "Mesh & Animation",   "Export both PSK and PSA files(Skeletal Mesh/Animation(s) Data)") ],
            default     = '2')
    
    
    bpy.types.Scene.udk_option_verbose = BoolProperty(
    
            name        = "Verbose",
            description = "Verbose console output",
            default     = False)
    
    
    bpy.types.Scene.udk_option_smoothing_groups = BoolProperty(
    
            name        = "Smooth Groups",
            description = "Activate hard edges as smooth groups",
            default     = True)
    
    
    bpy.types.Scene.udk_option_triangulate = BoolProperty(
    
            name        = "Triangulate Mesh",
            description = "Convert Quads to Triangles",
            default     = False)
            
    
    John Phan's avatar
    John Phan committed
    bpy.types.Scene.udk_option_selectanimations = BoolProperty(
    
            name        = "Select Animation(s)",
            description = "Select animation(s) for export to psa file.",
            default     = False)
            
    
    John Phan's avatar
    John Phan committed
    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_scale = FloatProperty(
        name = "UDK Scale",
        description = "In case you don't want to scale objects manually. This will just scale position when on export for the skeleton mesh and animation data.",
        default     = 1)
    
    
    #===========================================================================
    # User interface
    #===========================================================================
    
    class OBJECT_OT_UTSelectedFaceSmooth(bpy.types.Operator):
        bl_idname = "object.utselectfacesmooth"  # XXX, name???
    
    John Phan's avatar
    John Phan committed
        bl_label = "Select Smooth Faces"#"Select Smooth faces"
    
        __doc__ = """It will only select smooth faces that is select mesh"""
        
        def invoke(self, context, event):
            print("----------------------------------------")
            print("Init Select Face(s):")
            bselected = False
            for obj in bpy.data.objects:
                if obj.type == 'MESH' and obj.select == True:
                    smoothcount = 0
                    flatcount = 0
                    bpy.ops.object.mode_set(mode='OBJECT')#it need to go into object mode to able to select the faces
                    for i in bpy.context.scene.objects: i.select = False #deselect all objects
                    obj.select = True #set current object select
                    bpy.context.scene.objects.active = obj #set active object
                    mesh = bmesh.new();
                    mesh.from_mesh(obj.data)
                    for face in mesh.faces:
                        face.select = False
                    for face in mesh.faces:
                        if face.smooth == True:
                            face.select = True
                            smoothcount += 1
                        else:
                            flatcount += 1
                            face.select = False