#if UNITY_EDITOR using System.IO; using System.Linq; using UnityEditor; using UnityEngine; public static class FixFbxClipNames { // -------- Helpers -------- static string Sanitize(string s) { foreach (var c in Path.GetInvalidFileNameChars()) s = s.Replace(c, '_'); return s.Replace(' ', '_'); } static string GetFBXName(string assetPath) => Path.GetFileNameWithoutExtension(assetPath); static ModelImporterClipAnimation CloneClip(ModelImporterClipAnimation src) { return new ModelImporterClipAnimation { name = src.name, firstFrame = src.firstFrame, lastFrame = src.lastFrame, loopTime = src.loopTime, loopPose = src.loopPose, lockRootRotation = src.lockRootRotation, lockRootHeightY = src.lockRootHeightY, lockRootPositionXZ = src.lockRootPositionXZ, keepOriginalOrientation = src.keepOriginalOrientation, keepOriginalPositionXZ = src.keepOriginalPositionXZ, keepOriginalPositionY = src.keepOriginalPositionY, heightFromFeet = src.heightFromFeet, mirror = src.mirror, additiveReferencePoseFrame = src.additiveReferencePoseFrame, maskType = src.maskType, hasAdditiveReferencePose = src.hasAdditiveReferencePose, }; } // -------- 1) Rename in importer -------- [MenuItem("Tools/Animations/Fix FBX Clip Names (Rename in Importer)")] public static void FixInImporter() { var guids = Selection.assetGUIDs.Length > 0 ? Selection.assetGUIDs : AssetDatabase.FindAssets("t:Model"); int changedAssets = 0, changedClips = 0; foreach (var guid in guids) { var path = AssetDatabase.GUIDToAssetPath(guid); if (!path.EndsWith(".fbx", System.StringComparison.OrdinalIgnoreCase)) continue; var importer = AssetImporter.GetAtPath(path) as ModelImporter; if (importer == null) continue; var clips = importer.clipAnimations; if (clips == null || clips.Length == 0) clips = importer.defaultClipAnimations; if (clips == null || clips.Length == 0) continue; string fbxName = Sanitize(GetFBXName(path)); bool anyChange = false; var newClips = clips.Select(c => { var nc = CloneClip(c); string old = string.IsNullOrEmpty(c.name) ? "Clip" : c.name; string newName = (clips.Length == 1) ? fbxName : $"{fbxName}_{Sanitize(old)}"; if (nc.name != newName) { nc.name = newName; anyChange = true; changedClips++; } return nc; }).ToArray(); if (anyChange) { importer.clipAnimations = newClips; // write custom list try { importer.SaveAndReimport(); changedAssets++; Debug.Log($"[FixFbxClipNames] Renamed clips in: {path}"); } catch (System.Exception e) { Debug.LogError($"[FixFbxClipNames] Failed to reimport {path}: {e.Message}"); } } } Debug.Log($"[FixFbxClipNames] Done. Assets changed: {changedAssets}, Clips renamed: {changedClips}"); } // -------- 2) Extract .anim copies (optional) -------- [MenuItem("Tools/Animations/Extract .anim Copies With Clean Names")] public static void ExtractAnimCopies() { var guids = Selection.assetGUIDs.Length > 0 ? Selection.assetGUIDs : AssetDatabase.FindAssets("t:Model"); int created = 0; foreach (var guid in guids) { var path = AssetDatabase.GUIDToAssetPath(guid); if (!path.EndsWith(".fbx", System.StringComparison.OrdinalIgnoreCase)) continue; string fbxName = Sanitize(GetFBXName(path)); string folder = Path.GetDirectoryName(path).Replace('\\', '/'); string outDir = folder + "/Animations_Extracted"; if (!AssetDatabase.IsValidFolder(outDir)) AssetDatabase.CreateFolder(folder, "Animations_Extracted"); var subAssets = AssetDatabase.LoadAllAssetsAtPath(path); var clips = subAssets.OfType() .Where(c => c != null && c.name != "__preview__") .ToArray(); foreach (var clip in clips) { string baseName = (clips.Length == 1) ? fbxName : $"{fbxName}_{Sanitize(clip.name)}"; string outPath = $"{outDir}/{baseName}.anim"; if (File.Exists(outPath)) continue; var dup = new AnimationClip(); EditorUtility.CopySerialized(clip, dup); AssetDatabase.CreateAsset(dup, outPath); created++; Debug.Log($"[Extract] {outPath}"); } } AssetDatabase.SaveAssets(); Debug.Log($"[FixFbxClipNames] Extracted {created} .anim files."); } } #endif