2023-11-10 14:37:56 +08:00
|
|
|
|
#if MA_VRCSDK3_AVATARS
|
|
|
|
|
|
2024-05-13 04:19:55 +08:00
|
|
|
|
#region
|
|
|
|
|
|
2023-12-28 16:58:47 +08:00
|
|
|
|
using System;
|
2024-05-13 04:19:55 +08:00
|
|
|
|
using System.IO;
|
2023-11-10 14:37:56 +08:00
|
|
|
|
using System.Linq;
|
2024-05-13 04:19:55 +08:00
|
|
|
|
using nadena.dev.modular_avatar.ui;
|
2023-02-25 15:45:24 +08:00
|
|
|
|
using UnityEditor;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
using VRC.SDK3.Avatars.Components;
|
|
|
|
|
using VRC.SDK3.Avatars.ScriptableObjects;
|
|
|
|
|
|
2024-05-13 04:19:55 +08:00
|
|
|
|
#endregion
|
|
|
|
|
|
2023-02-25 15:45:24 +08:00
|
|
|
|
namespace nadena.dev.modular_avatar.core.editor
|
|
|
|
|
{
|
|
|
|
|
internal class MenuExtractor
|
|
|
|
|
{
|
2024-05-13 04:19:55 +08:00
|
|
|
|
[MenuItem(UnityMenuItems.GameObject_ExtractMenu, false, UnityMenuItems.GameObject_ExtractMenuOrder)]
|
2023-02-25 15:45:24 +08:00
|
|
|
|
static void ExtractMenu(MenuCommand menuCommand)
|
|
|
|
|
{
|
|
|
|
|
if (!(menuCommand.context is GameObject gameObj)) return;
|
|
|
|
|
var avatar = gameObj.GetComponent<VRCAvatarDescriptor>();
|
2023-05-11 18:42:31 +08:00
|
|
|
|
if (avatar == null || avatar.expressionsMenu == null || avatar.expressionsMenu.controls.Count == 0) return;
|
2023-02-25 15:45:24 +08:00
|
|
|
|
|
|
|
|
|
var parent = ExtractSingleLayerMenu(avatar.expressionsMenu, gameObj, "Avatar Menu");
|
|
|
|
|
parent.AddComponent<ModularAvatarMenuInstaller>();
|
|
|
|
|
parent.AddComponent<ModularAvatarMenuGroup>();
|
|
|
|
|
|
|
|
|
|
// The VRCSDK requires that an expressions menu asset be provided if any parameters are defined.
|
|
|
|
|
// We can't just remove the asset, so we'll replace it with a dummy asset. However, to avoid users
|
|
|
|
|
// accidentally overwriting files in Packages, we'll place this dummy asset next to where the original
|
|
|
|
|
// asset was (or in the Assets root, if the original asset was in Packages).
|
|
|
|
|
Undo.RecordObject(avatar, "Extract menu");
|
|
|
|
|
|
|
|
|
|
var assetPath = AssetDatabase.GetAssetPath(avatar.expressionsMenu);
|
|
|
|
|
var dummyAssetPathBase = assetPath.Replace(".asset", " placeholder");
|
2024-05-13 04:19:55 +08:00
|
|
|
|
if (dummyAssetPathBase.StartsWith("Packages" + Path.DirectorySeparatorChar))
|
2023-02-25 15:45:24 +08:00
|
|
|
|
{
|
2024-05-13 04:19:55 +08:00
|
|
|
|
var filename = Path.GetFileName(dummyAssetPathBase);
|
|
|
|
|
dummyAssetPathBase = Path.Combine("Assets", filename);
|
2023-02-25 15:45:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check that a similarly-named file doesn't already exist
|
|
|
|
|
int i = 0;
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
var fullPath = dummyAssetPathBase + (i > 0 ? " " + i : "") + ".asset";
|
2024-05-13 04:19:55 +08:00
|
|
|
|
if (File.Exists(fullPath))
|
2023-02-25 15:45:24 +08:00
|
|
|
|
{
|
|
|
|
|
var asset = AssetDatabase.LoadAssetAtPath<VRCExpressionsMenu>(fullPath);
|
|
|
|
|
if (asset != null && asset.controls.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
avatar.expressionsMenu = asset;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-05-13 04:19:55 +08:00
|
|
|
|
else if (!File.Exists(fullPath))
|
2023-02-25 15:45:24 +08:00
|
|
|
|
{
|
|
|
|
|
var dummyAsset = ScriptableObject.CreateInstance<VRCExpressionsMenu>();
|
|
|
|
|
AssetDatabase.CreateAsset(dummyAsset, fullPath);
|
|
|
|
|
avatar.expressionsMenu = dummyAsset;
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
i++;
|
|
|
|
|
} while (true);
|
|
|
|
|
|
|
|
|
|
EditorUtility.SetDirty(avatar);
|
|
|
|
|
PrefabUtility.RecordPrefabInstancePropertyModifications(avatar);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Extracts a single expressions menu asset to Menu Item components.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="menu">The menu to extract</param>
|
|
|
|
|
/// <param name="parent">The parent object to use</param>
|
|
|
|
|
/// <param name="containerName">The name of a gameobject to place between the parent and menu item objects,
|
|
|
|
|
/// or null to skip</param>
|
|
|
|
|
/// <returns>the direct parent of the generated menu items</returns>
|
|
|
|
|
internal static GameObject ExtractSingleLayerMenu(
|
|
|
|
|
VRCExpressionsMenu menu,
|
|
|
|
|
GameObject parent,
|
|
|
|
|
string containerName = null)
|
|
|
|
|
{
|
|
|
|
|
if (containerName != null)
|
|
|
|
|
{
|
|
|
|
|
var container = new GameObject();
|
|
|
|
|
container.name = containerName;
|
|
|
|
|
container.transform.SetParent(parent.transform, false);
|
|
|
|
|
parent = container;
|
|
|
|
|
Undo.RegisterCreatedObjectUndo(container, "Convert menu");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var control in menu.controls)
|
|
|
|
|
{
|
|
|
|
|
var itemObj = new GameObject();
|
|
|
|
|
itemObj.name = string.IsNullOrEmpty(control.name) ? " " : control.name;
|
|
|
|
|
Undo.RegisterCreatedObjectUndo(itemObj, "Convert menu");
|
|
|
|
|
itemObj.transform.SetParent(parent.transform, false);
|
|
|
|
|
|
|
|
|
|
var menuItem = itemObj.AddComponent<ModularAvatarMenuItem>();
|
|
|
|
|
ControlToMenuItem(menuItem, control);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return parent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal static void ControlToMenuItem(ModularAvatarMenuItem menuItem, VRCExpressionsMenu.Control control)
|
|
|
|
|
{
|
|
|
|
|
menuItem.Control = CloneControl(control);
|
|
|
|
|
if (menuItem.Control.type == VRCExpressionsMenu.Control.ControlType.SubMenu)
|
|
|
|
|
{
|
|
|
|
|
menuItem.MenuSource = SubmenuSource.MenuAsset;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal static VRCExpressionsMenu.Control CloneControl(VRCExpressionsMenu.Control c)
|
|
|
|
|
{
|
2024-10-20 08:46:31 +08:00
|
|
|
|
var type = c.type != 0 ? c.type : VRCExpressionsMenu.Control.ControlType.Button;
|
|
|
|
|
|
2023-02-25 15:45:24 +08:00
|
|
|
|
return new VRCExpressionsMenu.Control()
|
|
|
|
|
{
|
2024-10-20 08:46:31 +08:00
|
|
|
|
type = type,
|
2023-02-25 15:45:24 +08:00
|
|
|
|
name = c.name,
|
|
|
|
|
icon = c.icon,
|
2023-12-28 16:58:47 +08:00
|
|
|
|
parameter = new VRCExpressionsMenu.Control.Parameter() { name = c.parameter?.name },
|
2023-02-25 15:45:24 +08:00
|
|
|
|
subMenu = c.subMenu,
|
|
|
|
|
subParameters = c.subParameters?.Select(p =>
|
2023-12-28 16:58:47 +08:00
|
|
|
|
new VRCExpressionsMenu.Control.Parameter() { name = p?.name })
|
|
|
|
|
?.ToArray() ?? Array.Empty<VRCExpressionsMenu.Control.Parameter>(),
|
|
|
|
|
labels = c.labels?.ToArray() ?? Array.Empty<VRCExpressionsMenu.Control.Label>(),
|
2023-02-25 15:45:24 +08:00
|
|
|
|
style = c.style,
|
|
|
|
|
value = c.value,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-11-10 14:37:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif
|