diff --git a/Editor/EasySetupOutfit.cs b/Editor/SetupOutfit.cs similarity index 88% rename from Editor/EasySetupOutfit.cs rename to Editor/SetupOutfit.cs index 4ff0366d..f6d28c53 100644 --- a/Editor/EasySetupOutfit.cs +++ b/Editor/SetupOutfit.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using nadena.dev.modular_avatar.ui; using UnityEditor; using UnityEngine; @@ -113,25 +114,37 @@ namespace nadena.dev.modular_avatar.core.editor } } - internal static class EasySetupOutfit + public static class SetupOutfit { private static string[] errorMessageGroups; private static string errorHeader; [MenuItem(UnityMenuItems.GameObject_SetupOutfit, false, UnityMenuItems.GameObject_SetupOutfitOrder)] - internal static void SetupOutfit(MenuCommand cmd) + internal static void SetupOutfitMenu(MenuCommand cmd) { - if (!ValidateSetupOutfit()) + var outfitRoot = cmd.context as GameObject; + + SetupOutfitUI(outfitRoot); + } + + /// + /// Executes the `Setup Outfit` operation, as if the user selected `outfitRoot` and ran Setup Outfit from the + /// context menu. Any errors encountered will trigger a popup error window. + /// + /// + [PublicAPI] + public static void SetupOutfitUI(GameObject outfitRoot) + { + if (!ValidateSetupOutfit(outfitRoot)) { ESOErrorWindow.Show(errorHeader, errorMessageGroups); return; } - if (!FindBones(cmd.context, + if (!FindBones(outfitRoot, out var avatarRoot, out var avatarHips, out var outfitHips) ) return; - var outfitRoot = cmd.context as GameObject; var avatarArmature = avatarHips.transform.parent; var outfitArmature = outfitHips.transform.parent; @@ -362,45 +375,51 @@ namespace nadena.dev.modular_avatar.core.editor foreach (var obj in Selection.objects) { errorHeader = S_f("setup_outfit.err.header", obj.name); - if (!(obj is GameObject gameObj)) return false; - var xform = gameObj.transform; - if (!FindBones(obj, out var _, out var _, out var outfitHips)) - { - return false; - } + if (!ValidateSetupOutfit(gameObj)) return false; + } - // Some users have been accidentally running Setup Outfit on the avatar itself, and/or nesting avatar - // descriptors when transplanting outfits. Block this (and require that there be only one avdesc) by - // refusing to run if we detect multiple avatar descriptors above the current object (or if we're run on - // the avdesc object itself) - var nearestAvatarTransform = RuntimeUtil.FindAvatarTransformInParents(xform); - if (nearestAvatarTransform == null) - { - errorMessageGroups = new string[] - { - S_f("setup_outfit.err.no_avatar_descriptor", xform.gameObject.name) - }; - return false; - } + return true; + } - if (nearestAvatarTransform == xform) - { - errorMessageGroups = new string[] - { S_f("setup_outfit.err.run_on_avatar_itself", xform.gameObject.name) }; - return false; - } + private static bool ValidateSetupOutfit(GameObject gameObj) + { + Object obj; + errorHeader = S_f("setup_outfit.err.header", gameObj.name); + var xform = gameObj.transform; - var parent = nearestAvatarTransform.parent; - if (parent != null && RuntimeUtil.FindAvatarTransformInParents(parent) != null) + if (!FindBones(gameObj, out var _, out var _, out var outfitHips)) return false; + + // Some users have been accidentally running Setup Outfit on the avatar itself, and/or nesting avatar + // descriptors when transplanting outfits. Block this (and require that there be only one avdesc) by + // refusing to run if we detect multiple avatar descriptors above the current object (or if we're run on + // the avdesc object itself) + var nearestAvatarTransform = RuntimeUtil.FindAvatarTransformInParents(xform); + if (nearestAvatarTransform == null) + { + errorMessageGroups = new[] { - errorMessageGroups = new string[] - { - S_f("setup_outfit.err.multiple_avatar_descriptors", xform.gameObject.name) - }; - return false; - } + S_f("setup_outfit.err.no_avatar_descriptor", xform.gameObject.name) + }; + return false; + } + + if (nearestAvatarTransform == xform) + { + errorMessageGroups = new[] + { S_f("setup_outfit.err.run_on_avatar_itself", xform.gameObject.name) }; + return false; + } + + var parent = nearestAvatarTransform.parent; + if (parent != null && RuntimeUtil.FindAvatarTransformInParents(parent) != null) + { + errorMessageGroups = new[] + { + S_f("setup_outfit.err.multiple_avatar_descriptors", xform.gameObject.name) + }; + return false; } return true; diff --git a/Editor/EasySetupOutfit.cs.meta b/Editor/SetupOutfit.cs.meta similarity index 100% rename from Editor/EasySetupOutfit.cs.meta rename to Editor/SetupOutfit.cs.meta diff --git a/UnitTests~/EasySetupOutfit/ArmatureConfusionTest.cs b/UnitTests~/EasySetupOutfit/ArmatureConfusionTest.cs index 2c941729..8e00b10b 100644 --- a/UnitTests~/EasySetupOutfit/ArmatureConfusionTest.cs +++ b/UnitTests~/EasySetupOutfit/ArmatureConfusionTest.cs @@ -55,7 +55,7 @@ public class ArmatureConfusionTest : TestBase // Now do a setup outfit operation Selection.activeGameObject = inner; - EasySetupOutfit.SetupOutfit(new MenuCommand(inner)); + SetupOutfit.SetupOutfitUI(inner); // Verify that we're not confused anymore Assert.AreSame( diff --git a/UnitTests~/EasySetupOutfit/PreferFirstHipsMatch.cs b/UnitTests~/EasySetupOutfit/PreferFirstHipsMatch.cs index 2380ccc2..717e5332 100644 --- a/UnitTests~/EasySetupOutfit/PreferFirstHipsMatch.cs +++ b/UnitTests~/EasySetupOutfit/PreferFirstHipsMatch.cs @@ -22,7 +22,7 @@ public class PreferFirstHipsMatch : TestBase var outfit_armature = CreateChild(outfit, "Armature"); var outfit_hips = CreateChild(outfit_armature, "Hips"); - Assert.IsTrue(EasySetupOutfit.FindBones(outfit, out var det_av_root, out var det_av_hips, out var det_outfit_hips)); + Assert.IsTrue(SetupOutfit.FindBones(outfit, out var det_av_root, out var det_av_hips, out var det_outfit_hips)); Assert.AreSame(root, det_av_root); Assert.AreSame(root_hips, det_av_hips); Assert.AreSame(outfit_hips, det_outfit_hips);