Last active
June 1, 2025 23:51
-
-
Save Myrkie/c99502d951650e9bccdd1080e670ed68 to your computer and use it in GitHub Desktop.
Automatically reorders all humanoid bones to be the first child to make VRChat's IK happy.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// originally by https://github.com/brandonvdongen/PFCTools | |
using UnityEditor; | |
using UnityEditor.SceneManagement; | |
using UnityEngine; | |
using VRC.SDK3.Avatars.Components; | |
// ReSharper disable once CheckNamespace | |
[InitializeOnLoad] | |
public class HierarchyFix : AssetPostprocessor { | |
private static readonly HumanBodyBones[] Limbs = { | |
HumanBodyBones.LeftUpperArm, | |
HumanBodyBones.LeftLowerArm, | |
HumanBodyBones.RightUpperArm, | |
HumanBodyBones.RightLowerArm, | |
HumanBodyBones.LeftLowerLeg, | |
HumanBodyBones.RightLowerLeg | |
}; | |
static HierarchyFix() | |
{ | |
// force a reorder on scene start, since these chances don't stick with packed prefabs after restarts or scene changes | |
EditorApplication.delayCall += ReorderHierarchy; | |
EditorSceneManager.sceneOpened += (scene, mode) => { | |
EditorApplication.delayCall += ReorderHierarchy; | |
}; | |
} | |
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) { | |
var hasModelFile = false; | |
foreach (var asset in importedAssets) { | |
if (asset.ToLower().EndsWith(".blend") || asset.ToLower().EndsWith(".fbx")) { | |
hasModelFile = true; | |
} | |
if (!hasModelFile) continue; | |
EditorApplication.delayCall -= ReorderHierarchy; | |
EditorApplication.delayCall += ReorderHierarchy; | |
break; | |
} | |
} | |
/// <summary> | |
/// Reorders all humanoid bones to be the first child. | |
/// </summary> | |
[MenuItem("Tools/Fix Hierarchy Order")] | |
private static void ReorderHierarchy() { | |
var descriptors = Object.FindObjectsOfType<VRCAvatarDescriptor>(); | |
foreach (var descriptor in descriptors) { | |
var animator = descriptor.GetComponent<Animator>(); | |
if (animator == null) return; | |
foreach (var bone in Limbs) { | |
var boneTransform = animator.GetBoneTransform(bone); | |
if (boneTransform == null) continue; | |
var ind = boneTransform.GetSiblingIndex(); | |
if (ind == 0) continue; | |
Debug.Log($"Avatar: {descriptor.gameObject.name}: Humanoid Bone: {bone} is child {ind} instead of 0, fixing...", boneTransform); | |
boneTransform.SetAsFirstSibling(); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment