using Sirenix.OdinInspector.Editor; using System.Collections; using System.Collections.Generic; using UnityEditor; using UnityEngine; public class HierarchyRepair : EditorWindow { public GameObject modelA; // 已命名旧模型 public GameObject modelB; // 未命名新模型 [Header("匹配选项")] public bool rebuildHierarchy = false; // 是否重建层级 public bool keepWorldPosition = true; // 移动时保持世界坐标 public bool showDetailedLog = true; // 打印详细日志 public bool cleanupEmptyNodes = true; // 清理空节点 private Dictionary meshLookupB = new Dictionary(); private Transform newPartsGroup; private int totalMatches = 0; private int totalMeshesA = 0; private int totalUnmatched = 0; [MenuItem("Tools/模型匹配")] public static void ShowWindow() { GetWindow("模型匹配"); } void OnGUI() { GUILayout.Label("模型匹配", EditorStyles.boldLabel); modelA = (GameObject)EditorGUILayout.ObjectField("旧模型(A)", modelA, typeof(GameObject), true); modelB = (GameObject)EditorGUILayout.ObjectField("新模型(B)", modelB, typeof(GameObject), true); rebuildHierarchy = EditorGUILayout.ToggleLeft("同步层级", rebuildHierarchy); if (rebuildHierarchy) keepWorldPosition = EditorGUILayout.ToggleLeft("保持坐标", keepWorldPosition); showDetailedLog = EditorGUILayout.ToggleLeft("详细日志", showDetailedLog); cleanupEmptyNodes = EditorGUILayout.ToggleLeft("清理空节点", cleanupEmptyNodes); if (GUILayout.Button("执行")) { if (modelA == null || modelB == null) { UnityEngine.Debug.LogError("指定模型A和B"); return; } Undo.RegisterFullObjectHierarchyUndo(modelB, "Model"); RunSync(); UnityEngine.Debug.Log($"匹配完成:共 {totalMeshesA} 个 Mesh,匹配成功 {totalMatches} 个,未匹配 {totalUnmatched} 个。"); if (cleanupEmptyNodes) { int count = CleanupEmptyNodesDeep(modelB.transform); UnityEngine.Debug.Log($"清理完成:共移除 {count} 个空节点。"); } } } // 主逻辑 void RunSync() { totalMatches = 0; totalMeshesA = 0; totalUnmatched = 0; meshLookupB.Clear(); //建立 B 模型几何签名表 foreach (var renderer in modelB.GetComponentsInChildren(true)) { var mf = renderer.GetComponent(); if (mf && mf.sharedMesh) { string key = GetMeshKey(mf.sharedMesh); if (!meshLookupB.ContainsKey(key)) meshLookupB[key] = renderer.transform; } } foreach (var smr in modelB.GetComponentsInChildren(true)) { if (smr.sharedMesh) { string key = GetMeshKey(smr.sharedMesh); if (!meshLookupB.ContainsKey(key)) meshLookupB[key] = smr.transform; } } // --- 创建 NewParts 组 --- var existingGroup = modelB.transform.Find("NewParts"); if (existingGroup == null) { GameObject newGroup = new GameObject("NewParts"); newPartsGroup = newGroup.transform; newPartsGroup.SetParent(modelB.transform); } else { newPartsGroup = existingGroup; } //遍历A的结构 TraverseAndSync(modelA.transform, modelB.transform); //递归移动所有未匹配 Mesh totalUnmatched = MoveAllUnmatchedToNewParts(modelB.transform); if (showDetailedLog) UnityEngine.Debug.Log($"未匹配节点已移动到 NewParts,共 {totalUnmatched} 个"); //递归移动所有未匹配Mesh totalUnmatched = MoveAllUnmatchedToNewParts(modelB.transform); // 最后兜底扫描:把遗漏的Mesh全部移入NewParts int extraMoved = ForceCollectAllUnmatchedMeshes(modelB.transform, newPartsGroup); if (extraMoved > 0) { totalUnmatched += extraMoved; UnityEngine.Debug.Log($"额外收集未匹配Mesh{extraMoved}个,已归入NewParts"); } if (showDetailedLog) UnityEngine.Debug.Log($"未匹配节点已移动到NewParts,共{totalUnmatched}个"); } // 递归遍历 A 树结构 void TraverseAndSync(Transform aNode, Transform bParent) { if (!HasRenderer(aNode)) { Transform bGroup = bParent.Find(aNode.name); if (bGroup == null) { GameObject newGroup = new GameObject(aNode.name); newGroup.transform.SetParent(bParent, false); bGroup = newGroup.transform; } foreach (Transform aChild in aNode) TraverseAndSync(aChild, bGroup); return; } Mesh meshA = GetMesh(aNode); if (meshA == null) return; totalMeshesA++; string key = GetMeshKey(meshA); if (meshLookupB.TryGetValue(key, out Transform bNode)) { // 命名 bNode.name = aNode.name; // 层级 if (rebuildHierarchy) { Transform targetParent = EnsurePathInB(aNode, modelA.transform, modelB.transform); bNode.SetParent(targetParent, keepWorldPosition); } meshLookupB.Remove(key); totalMatches++; if (showDetailedLog) UnityEngine.Debug.Log($"匹配成功: {aNode.name}"); } else { if (showDetailedLog) UnityEngine.Debug.LogWarning($"未找到匹配Mesh: {aNode.name}"); } } // 递归移动所有未匹配的 Mesh 到 NewParts int MoveAllUnmatchedToNewParts(Transform root) { int moved = 0; var allMeshes = root.GetComponentsInChildren(true); foreach (var mr in allMeshes) { if (mr == null) continue; var mf = mr.GetComponent(); if (mf && mf.sharedMesh != null) { string key = GetMeshKey(mf.sharedMesh); if (meshLookupB.ContainsKey(key)) { mr.transform.SetParent(newPartsGroup, true); meshLookupB.Remove(key); moved++; } } } var skinned = root.GetComponentsInChildren(true); foreach (var smr in skinned) { if (smr.sharedMesh != null) { string key = GetMeshKey(smr.sharedMesh); if (meshLookupB.ContainsKey(key)) { smr.transform.SetParent(newPartsGroup, true); meshLookupB.Remove(key); moved++; } } } return moved; } // 延迟清理 int CleanupEmptyNodesDeep(Transform root) { int count = 0; List toDelete = new List(); // 收集所有空节点 foreach (Transform t in root.GetComponentsInChildren(true)) { if (t == null || t == root) continue; if (IsDeepEmpty(t)) toDelete.Add(t.gameObject); } // 逐个销毁 foreach (var go in toDelete) { if (go == null) continue; #if UNITY_EDITOR // 用 Undo 系统保证可撤销,同时防止立即触发重绘异常 Undo.DestroyObjectImmediate(go); #else Object.Destroy(go); #endif count++; } // 强制刷新编辑器层级树 EditorApplication.delayCall += () => { EditorApplication.RepaintHierarchyWindow(); }; return count; } bool IsDeepEmpty(Transform t) { if (t == null) return true; if (HasRenderer(t)) return false; // 如果有任何子物体非空,则此节点不空 foreach (Transform child in t) { if (child == null) continue; if (!IsDeepEmpty(child)) return false; } return true; } // 工具函数 bool HasRenderer(Transform t) { return t.GetComponent() != null || t.GetComponent() != null; } Mesh GetMesh(Transform t) { var mr = t.GetComponent(); if (mr) { var mf = t.GetComponent(); return mf ? mf.sharedMesh : null; } var smr = t.GetComponent(); return smr ? smr.sharedMesh : null; } string GetMeshKey(Mesh mesh) { if (mesh == null) return "null"; Vector3 s = mesh.bounds.size; return $"{mesh.vertexCount}_{mesh.triangles.Length}_{s.x:F3}_{s.y:F3}_{s.z:F3}"; } Transform EnsurePathInB(Transform aNode, Transform aRoot, Transform bRoot) { List path = new List(); Transform cur = aNode.parent; while (cur != null && cur != aRoot) { path.Add(cur.name); cur = cur.parent; } path.Reverse(); Transform current = bRoot; foreach (string name in path) { Transform next = current.Find(name); if (next == null) { GameObject g = new GameObject(name); g.transform.SetParent(current, false); next = g.transform; } current = next; } return current; } // 强制收集 int ForceCollectAllUnmatchedMeshes(Transform root, Transform newParts) { int moved = 0; foreach (Transform t in root.GetComponentsInChildren(true)) { if (t == root) continue; // 跳过已经在 NewParts 下的 if (t.IsChildOf(newParts)) continue; // 只处理有 Mesh 的节点 var mr = t.GetComponent(); var smr = t.GetComponent(); if (mr != null || smr != null) { // 过滤掉匹配成功的:如果名字已在 A 结构中出现过则认为命中 if (IsInModelANameList(t.name)) continue; // 否则强制放入 NewParts t.SetParent(newParts, true); moved++; } } return moved; } // 缓存A模型所有命名列表 HashSet modelANamesCache = null; bool IsInModelANameList(string name) { if (modelANamesCache == null) { modelANamesCache = new HashSet(); foreach (var tr in modelA.GetComponentsInChildren(true)) modelANamesCache.Add(tr.name); } return modelANamesCache.Contains(name); } } //public class HierarchyRepair : MonoBehaviour //{ // public GameObject groupedPrefab; // public GameObject ungroupedObjects; // //public string newObjectsFolderName = "NewObjects"; // // 存储预制体中的所有层级路径(用于快速查找) // private Dictionary meshRendererPaths; // private Dictionary createdNodes = new Dictionary(); // // 存储未分组物体中已处理的对象(避免重复处理) // //public MeshRenderer[] meshRenderers; // private List Notprocess = new List(); // private List Process = new List(); // [ContextMenu("更新分组")] // public void OrganizeHierarchy() // { // if (groupedPrefab == null || ungroupedObjects == null) // { // UnityEngine.Debug.LogError("请赋值已分组预制体和未分组模型!"); // return; // } // // 初始化字典(避免空引用) // createdNodes.Clear(); // meshRendererPaths = new Dictionary(); // // 清空列表(防止重复处理) // Notprocess.Clear(); // Process.Clear(); // foreach (MeshRenderer mr in ungroupedObjects.GetComponentsInChildren()) // { // Notprocess.Add(mr); // } // MeshRenderer[] allMeshRenderers = groupedPrefab.GetComponentsInChildren(true); // foreach (var renderer in allMeshRenderers) // { // // 获取相对于目标父物体的路径(而非全场景路径) // string path = GetRelativePath(renderer.transform, groupedPrefab.transform); // if (!meshRendererPaths.ContainsKey(renderer)) // { // meshRendererPaths.Add(renderer, path); // } // } // // 创建未匹配物体的存放根节点(每次处理前清空旧的) // GameObject unmatchedRoot = GameObject.Find("UnmatchedObjects"); // if (unmatchedRoot != null) // { // DestroyImmediate(unmatchedRoot); // } // unmatchedRoot = new GameObject("UnmatchedObjects"); // unmatchedRoot.transform.SetParent(ungroupedObjects.transform); // unmatchedRoot.transform.localPosition = Vector3.zero; // unmatchedRoot.transform.localRotation = Quaternion.identity; // unmatchedRoot.transform.localScale = Vector3.one; // // 遍历未处理列表,按路径匹配重组 // ProcessUnmatchedObjects(unmatchedRoot); // UnityEngine.Debug.Log($"层级结构整理完成!已匹配 {Process.Count} 个物体,未匹配 {Notprocess.Count} 个物体"); // UnityEngine.Debug.Log("层级结构整理完成!"); //} ///// ///// 处理未匹配的物体,按预制体路径重组 ///// //private void ProcessUnmatchedObjects(GameObject unmatchedRoot) //{ // // 复制一份列表用于遍历(避免修改列表时引发异常) // List tempList = new List(Notprocess); // foreach (var targetRenderer in tempList) // { // bool isMatched = false; // // 遍历预制体的路径字典,寻找匹配的路径 // foreach (var prefabPair in meshRendererPaths) // { // // 这里假设通过"名称匹配"判断是否为同一物体(可根据实际需求修改匹配规则) // if (targetRenderer.name == prefabPair.Key.name) // { // // 获取预制体中的路径模板 // string targetPath = prefabPair.Value; // if (string.IsNullOrEmpty(targetPath)) continue; // // 按路径创建节点(临时空物体),并返回最终节点的父路径 // string parentPath = CreatePathNodes(targetPath); // // 替换路径中对应的空物体(如果存在) // ReplaceEmptyNodeWithEntity(targetRenderer.gameObject, targetPath, parentPath); // // 在未分组根节点下创建对应路径的层级,并移动物体 // MoveToTargetPath(targetRenderer.gameObject, targetPath); // // 移动到已处理列表,从未处理列表移除 // Process.Add(targetRenderer); // Notprocess.Remove(targetRenderer); // isMatched = true; // break; // } // } // // 未找到匹配路径,移动到未匹配根节点 // if (!isMatched) // { // targetRenderer.transform.SetParent(unmatchedRoot.transform); // // 保持局部变换不变 // targetRenderer.transform.localPosition = Vector3.zero; // targetRenderer.transform.localRotation = Quaternion.identity; // targetRenderer.transform.localScale = Vector3.one; // } // } //} // /// // /// 将物体移动到目标路径(在ungroupedObjects下创建对应层级) // /// // private void MoveToTargetPath(GameObject targetObj, string targetPath) // { // string[] pathSegments = targetPath.Split('/'); // Transform currentParent = ungroupedObjects.transform; // for (int i = 0; i < pathSegments.Length; i++) // { // string segment = pathSegments[i]; // Transform child = currentParent.Find(segment); // if (child != null) // { // // 情况1:节点已存在,检查是否有MeshRenderer // MeshRenderer existingRenderer = child.GetComponent(); // if (existingRenderer != null) // { // // 中间节点(非最后一级)有MeshRenderer,视为冲突 // if (i < pathSegments.Length - 1) // { // UnityEngine.Debug.LogWarning($"路径节点 {segment} 已存在且带有MeshRenderer,可能导致层级冲突!路径:{targetPath}"); // // 策略1:保留原有节点,继续使用它作为父节点(推荐) // currentParent = child; // continue; // // 策略2:跳过该路径,将物体归入未匹配(根据需求选择) // // return; // } // // 最后一级节点有MeshRenderer(可能是重复物体) // else // { // UnityEngine.Debug.LogWarning($"目标节点 {segment} 已存在且带有MeshRenderer,可能重复!路径:{targetPath}"); // // 策略:重命名当前物体以避免冲突 // targetObj.name = $"{segment}_Duplicate"; // } // } // currentParent = child; // } // else // { // // 情况2:节点不存在,创建新节点(空物体,不带MeshRenderer) // GameObject newNode = new GameObject(segment); // child = newNode.transform; // child.SetParent(currentParent); // child.localPosition = Vector3.zero; // child.localRotation = Quaternion.identity; // child.localScale = Vector3.one; // currentParent = child; // } // } // // 移动目标物体到最终路径 // targetObj.transform.SetParent(currentParent); // targetObj.transform.localPosition = Vector3.zero; // targetObj.transform.localRotation = Quaternion.identity; // targetObj.transform.localScale = Vector3.one; // // 确保名称与路径最后一段一致(除非是重复物体) // if (!targetObj.name.EndsWith("_Duplicate")) // { // targetObj.name = pathSegments[pathSegments.Length - 1]; // } // } // private string GetRelativePath(Transform child, Transform parent) // { // // 若子物体就是父物体本身,路径为自身名称 // if (child == parent) // return child.name; // string path = child.name; // Transform current = child.parent; // // 向上遍历,直到找到目标父物体 // while (current != null && current != parent) // { // path = current.name + "/" + path; // current = current.parent; // } // // 若遍历到根节点仍未找到父物体,说明不是其子物体(容错处理) // if (current == null) // { // UnityEngine.Debug.LogWarning($"{child.name} 不是 {parent.name} 的子物体!"); // return null; // } // return path; // } //}