Skip to content

Instantly share code, notes, and snippets.

@anatawa12
Created August 27, 2023 16:15
Show Gist options
  • Save anatawa12/43d2ae5cade96018a6e96e8557cd05f9 to your computer and use it in GitHub Desktop.
Save anatawa12/43d2ae5cade96018a6e96e8557cd05f9 to your computer and use it in GitHub Desktop.
Tool to match fileIDs of one prefab to another prefab. this is used when upgrading original avatar.
/*
* PrefabFileIdRestore
* Tool to match fileIDs of one prefab to another prefab. this is used when upgrading original avatar.
* https://gist.github.com/anatawa12/43d2ae5cade96018a6e96e8557cd05f9
*
* Click `Tools/anatawa12 gists/PrefabFileIdRestore` to open this window.
*
* MIT License
*
* Copyright (c) 2023 anatawa12
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#if UNITY_EDITOR && (!ANATAWA12_GISTS_VPM_PACKAGE || GIST_43d2ae5cade96018a6e96e8557cd05f9)
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
namespace anatawa12.gists
{
internal class PrefabFileIdRestore : EditorWindow
{
private const string GIST_NAME = "PrefabFileIdRestore";
[MenuItem("Tools/anatawa12 gists/" + GIST_NAME)]
static void Create() => GetWindow<PrefabFileIdRestore>(GIST_NAME);
public GameObject original;
public GameObject fixTarget;
public Vector2 scroll;
// targetId -> (original, target, originalId)
private Dictionary<long, (Object original, Object target, long originalId)> _mapping;
private List<Object> _notMapped;
private void OnGUI()
{
EditorGUI.BeginChangeCheck();
original = EditorGUILayout.ObjectField("original", original, typeof(GameObject), false) as GameObject;
fixTarget = EditorGUILayout.ObjectField("fix target", fixTarget, typeof(GameObject), false) as GameObject;
if (EditorGUI.EndChangeCheck())
{
_mapping = null;
}
using (new EditorGUI.DisabledScope(
!original || !fixTarget ||
!PrefabUtility.IsPartOfPrefabAsset(original) ||
!PrefabUtility.IsPartOfPrefabAsset(fixTarget) ||
PrefabUtility.IsPartOfPrefabInstance(fixTarget)))
{
if (GUILayout.Button("Create Mapping"))
{
CreateMapping();
}
}
using (new EditorGUI.DisabledScope(!original || !fixTarget || _mapping == null))
{
if (GUILayout.Button("Apply Mapping"))
{
ApplyMapping();
}
}
if (_mapping != null)
{
GUILayout.Label("Mapping:");
scroll = EditorGUILayout.BeginScrollView(scroll);
foreach (var (original, mapped, _) in _mapping.Values)
{
GUILayout.BeginHorizontal();
EditorGUILayout.ObjectField(original, typeof(Object), false);
EditorGUILayout.ObjectField(mapped, typeof(Object), false);
GUILayout.EndHorizontal();
}
GUILayout.Label("Not Mapped:");
foreach (var notMapped in _notMapped)
{
EditorGUILayout.ObjectField(notMapped, typeof(Object), false);
}
EditorGUILayout.EndScrollView();
}
}
private void CreateMapping()
{
var mapping = new Dictionary<long, (Object, Object, long)>();
var notMapped = new List<Object>();
MapRecursive(original, fixTarget);
_mapping = mapping;
_notMapped = notMapped;
return;
// ReSharper disable ParameterHidesMember
void MapRecursive(GameObject original, GameObject fixTarget)
// ReSharper restore ParameterHidesMember
{
mapping.Add(GetFileId(fixTarget), (original, fixTarget, GetFileId(original)));
// components
var originalComponents = original.GetComponents<Component>().ToList();
foreach (var targetComponent in fixTarget.GetComponents<Component>())
{
var sameTypeIndex = originalComponents.FindIndex(x => x.GetType() == targetComponent.GetType());
if (sameTypeIndex == -1)
{
notMapped.Add(targetComponent);
}
else
{
var originalComponent = originalComponents[sameTypeIndex];
mapping.Add(GetFileId(targetComponent),
(originalComponent, targetComponent, GetFileId(originalComponent)));
originalComponents.RemoveAt(sameTypeIndex);
}
}
// children
var originalChildren = Children(original.transform).ToList();
foreach (var child in Children(fixTarget.transform))
{
var sameNameIndex = originalChildren.FindIndex(x => x.name == child.name);
if (sameNameIndex == -1)
{
notMapped.Add(child);
}
else
{
MapRecursive(originalChildren[sameNameIndex].gameObject, child.gameObject);
originalChildren.RemoveAt(sameNameIndex);
}
}
}
IEnumerable<Transform> Children(Transform parent) =>
Enumerable.Range(0, parent.childCount).Select(parent.GetChild);
}
private void ApplyMapping()
{
var referenceRegex = new Regex(@"\{fileID: (-?\d+)\}");
var defineRegexRegex = new Regex(@"^--- (.*)&(-?\d+)", RegexOptions.Multiline);
var targetPath = AssetDatabase.GetAssetPath(fixTarget);
var targetPrefab = File.ReadAllText(targetPath);
targetPrefab = referenceRegex.Replace(targetPrefab, match =>
{
var id = long.Parse(match.Groups[1].Value);
if (!_mapping.TryGetValue(id, out var mappedId))
return match.Value;
return $"{{fileID: {mappedId.originalId}}}";
});
targetPrefab = defineRegexRegex.Replace(targetPrefab, match =>
{
var id = long.Parse(match.Groups[2].Value);
if (!_mapping.TryGetValue(id, out var mappedId))
return match.Value;
return $"--- {match.Groups[1].Value}&{mappedId.originalId}";
});
File.WriteAllText(targetPath, targetPrefab);
AssetDatabase.ImportAsset(targetPath);
}
private static long GetFileId(Object asset)
{
if (!AssetDatabase.TryGetGUIDAndLocalFileIdentifier(asset, out _, out long fileId))
throw new InvalidOperationException();
return fileId;
}
}
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment