From ba9efe218ffcaf6be4bf5c968ec6a6c8fc7f948b Mon Sep 17 00:00:00 2001 From: Narazaka Date: Fri, 9 Dec 2022 06:58:35 +0900 Subject: [PATCH 01/12] docs ja typo (#136) --- .../current/reference/merge-animator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/i18n/ja/docusaurus-plugin-content-docs/current/reference/merge-animator.md b/docs/i18n/ja/docusaurus-plugin-content-docs/current/reference/merge-animator.md index 0ba50b51..901856e9 100644 --- a/docs/i18n/ja/docusaurus-plugin-content-docs/current/reference/merge-animator.md +++ b/docs/i18n/ja/docusaurus-plugin-content-docs/current/reference/merge-animator.md @@ -11,7 +11,7 @@ Merge Animatorコンポーネントは、指定したアニメーターをアバ ## 非推奨の場合 -既存のレイヤーをそのままにして、指定したコントローラーを追加するだけです。完全に既存のアニメーターを置き換える場合は将来通り +既存のレイヤーをそのままにして、指定したコントローラーを追加するだけです。完全に既存のアニメーターを置き換える場合は従来通り ユーザーに差し替えてもらいましょう。 ## セットアップ方法 From d72e529ad7f5be87e64a2e5cb6982cd2307704bf Mon Sep 17 00:00:00 2001 From: Narazaka Date: Fri, 9 Dec 2022 06:58:47 +0900 Subject: [PATCH 02/12] =?UTF-8?q?=E3=81=9F=E3=81=B6=E3=82=93Path=E3=81=AF?= =?UTF-8?q?=E3=80=8C=E3=83=91=E3=82=B9=E3=80=8D=E3=81=A8=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=81=AE=E3=81=8C=E4=B8=80=E8=88=AC=E7=9A=84=20(#137)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Editor/Localization/ja.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Packages/nadena.dev.modular-avatar/Editor/Localization/ja.json b/Packages/nadena.dev.modular-avatar/Editor/Localization/ja.json index 31c0c651..da29cbf7 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/Localization/ja.json +++ b/Packages/nadena.dev.modular-avatar/Editor/Localization/ja.json @@ -30,14 +30,14 @@ "merge_armature.suffix.tooltip": "このオブジェクトの子に付く後置詞", "merge_armature.locked": "位置を固定", "merge_armature.locked.tooltip": "このオブジェクトのボーンを統合先のボーンに常に相互的に位置を合わせる。アニメーション制作向け", - "path_mode.Relative": "相対的(このオブジェクトからのパースを使用)", - "path_mode.Absolute": "絶対的(アバタールートからのパースを使用)", + "path_mode.Relative": "相対的(このオブジェクトからのパスを使用)", + "path_mode.Absolute": "絶対的(アバタールートからのパスを使用)", "merge_animator.animator": "統合されるアニメーター", "merge_animator.layer_type": "レイヤー種別", "merge_animator.delete_attached_animator": "付属アニメーターを削除", "merge_animator.delete_attached_animator.tooltip": "統合後、このオブジェクトについているアニメーターを削除します", - "merge_animator.path_mode": "パースモード", - "merge_animator.path_mode.tooltip": "アニメーション内のパースを解釈するモード。相対的にすると、このオブジェクトについているアニメーターでアニメーション編集できます", + "merge_animator.path_mode": "パスモード", + "merge_animator.path_mode.tooltip": "アニメーション内のパスを解釈するモード。相対的にすると、このオブジェクトについているアニメーターでアニメーション編集できます", "merge_animator.match_avatar_write_defaults": "アバターのWrite Defaults設定に合わせる", "merge_animator.match_avatar_write_defaults.tooltip": "アバターの該当アニメーターのWrite Defaults設定に合わせます。アバター側の設定が矛盾する場合は、統合されるアニメーターのWD値がそのまま採用されます。", "fpvisible.normal": "このオブジェクトは一人視点で表示されます。", @@ -54,4 +54,4 @@ "boneproxy.attachment.AsChildAtRoot": "子として・ルートに配置", "boneproxy.attachment.AsChildKeepWorldPosition": "子として・ワールド位置を維持", "pb_blocker.help": "このオブジェクトは親のPhysBoneから影響を受けなくなります。" -} \ No newline at end of file +} From 811c934f407e46eced351cd3fb1fdfb16cc50ee7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=89=E3=81=84=E3=81=A1=E3=81=A1=E3=82=83=E3=82=93?= Date: Sat, 10 Dec 2022 03:55:17 +0900 Subject: [PATCH 03/12] Added support for AnimatorOverrideController (#139) --- .../Editor/AnimatorMerger.cs | 26 +++++++++++++++++++ .../Editor/RenameParametersHook.cs | 12 +++++++++ .../nadena.dev.modular-avatar/Editor/Util.cs | 7 +++++ 3 files changed, 45 insertions(+) diff --git a/Packages/nadena.dev.modular-avatar/Editor/AnimatorMerger.cs b/Packages/nadena.dev.modular-avatar/Editor/AnimatorMerger.cs index 7185925c..2d39bb8b 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/AnimatorMerger.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/AnimatorMerger.cs @@ -36,6 +36,8 @@ namespace nadena.dev.modular_avatar.core.editor internal class AnimatorCombiner { private readonly AnimatorController _combined; + + private AnimatorOverrideController _overrideController; private List _layers = new List(); @@ -90,6 +92,20 @@ namespace nadena.dev.modular_avatar.core.editor } } + public void AddOverrideController(string basePath, AnimatorOverrideController overrideController, bool? writeDefaults) + { + AnimatorController controller = overrideController.runtimeAnimatorController as AnimatorController; + if (controller == null) return; + _overrideController = overrideController; + try + { + this.AddController(basePath, controller, writeDefaults); + } finally + { + _overrideController = null; + } + } + private void insertLayer(string basePath, AnimatorControllerLayer layer, bool first, bool? writeDefaults) { var newLayer = new AnimatorControllerLayer() @@ -260,6 +276,16 @@ namespace nadena.dev.modular_avatar.core.editor throw new Exception($"Unknown type referenced from animator: {original.GetType()}"); } + // When using AnimatorOverrideController, replace the original AnimationClip based on AnimatorOverrideController. + if (_overrideController != null && original is AnimationClip srcClip) + { + T overrideClip = _overrideController[srcClip] as T; + if (overrideClip != null) + { + original = overrideClip; + } + } + if (cloneMap == null) cloneMap = new Dictionary(); if (cloneMap.ContainsKey(original)) diff --git a/Packages/nadena.dev.modular-avatar/Editor/RenameParametersHook.cs b/Packages/nadena.dev.modular-avatar/Editor/RenameParametersHook.cs index 3515b090..94ecab72 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/RenameParametersHook.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/RenameParametersHook.cs @@ -126,6 +126,12 @@ namespace nadena.dev.modular_avatar.core.editor { if (willPurgeAnimators) break; // animator will be deleted in subsequent processing + // RuntimeAnimatorController may be AnimatorOverrideController, convert in case of AnimatorOverrideController + if (anim.runtimeAnimatorController is AnimatorOverrideController overrideController) + { + anim.runtimeAnimatorController = Util.ConvertAnimatorController(overrideController); + } + var controller = anim.runtimeAnimatorController as AnimatorController; if (controller != null) { @@ -138,6 +144,12 @@ namespace nadena.dev.modular_avatar.core.editor case ModularAvatarMergeAnimator merger: { + + // RuntimeAnimatorController may be AnimatorOverrideController, convert in case of AnimatorOverrideController + if (merger.animator is AnimatorOverrideController overrideController) + { + merger.animator = Util.ConvertAnimatorController(overrideController); + } var controller = merger.animator as AnimatorController; if (controller != null) { diff --git a/Packages/nadena.dev.modular-avatar/Editor/Util.cs b/Packages/nadena.dev.modular-avatar/Editor/Util.cs index 21a608a1..54ae04ab 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/Util.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/Util.cs @@ -118,6 +118,13 @@ namespace nadena.dev.modular_avatar.core.editor return merger.Finish(); } + public static AnimatorController ConvertAnimatorController(AnimatorOverrideController overrideController) + { + var merger = new AnimatorCombiner(); + merger.AddOverrideController("", overrideController, null); + return merger.Finish(); + } + public static bool IsTemporaryAsset(Object obj) { var path = AssetDatabase.GetAssetPath(obj); From 5911973e9d1d6660ed5e2d384801c6f0b52c7fc6 Mon Sep 17 00:00:00 2001 From: bd_ Date: Fri, 9 Dec 2022 11:40:00 -0800 Subject: [PATCH 04/12] Enable (but no-op) setup outfit when outfit is already setup (#148) Closes: #132 --- .../nadena.dev.modular-avatar/Editor/EasySetupOutfit.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Packages/nadena.dev.modular-avatar/Editor/EasySetupOutfit.cs b/Packages/nadena.dev.modular-avatar/Editor/EasySetupOutfit.cs index 12dec868..5722841f 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/EasySetupOutfit.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/EasySetupOutfit.cs @@ -18,6 +18,8 @@ namespace nadena.dev.modular_avatar.core.editor var avatarArmature = avatarHips.transform.parent; var outfitArmature = outfitHips.transform.parent; + if (outfitArmature.GetComponent() != null) return; + var merge = Undo.AddComponent(outfitArmature.gameObject); merge.mergeTarget = new AvatarObjectReference(); merge.mergeTarget.referencePath = RuntimeUtil.RelativePath(avatarRoot, avatarArmature.gameObject); @@ -27,13 +29,14 @@ namespace nadena.dev.modular_avatar.core.editor [MenuItem("GameObject/[ModularAvatar] Setup Outfit", true, PRIORITY)] static bool ValidateSetupOutfit() { + if (Selection.objects.Length == 0) return false; + foreach (var obj in Selection.objects) { if (!(obj is GameObject gameObj)) return false; var xform = gameObj.transform; - if (!FindBones(obj, out var _, out var _, out var outfitHips) - || outfitHips.transform.parent.GetComponent() != null) + if (!FindBones(obj, out var _, out var _, out var outfitHips)) { return false; } From b7b4fde863ca680c5c24729ee2122ecdef830f66 Mon Sep 17 00:00:00 2001 From: bd_ Date: Fri, 9 Dec 2022 11:40:07 -0800 Subject: [PATCH 05/12] Fix NRE caused by missing root bone (#149) Fixes: #135 --- .../Editor/MeshRetargeter.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Packages/nadena.dev.modular-avatar/Editor/MeshRetargeter.cs b/Packages/nadena.dev.modular-avatar/Editor/MeshRetargeter.cs index ae4de30d..a95b089f 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/MeshRetargeter.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/MeshRetargeter.cs @@ -197,14 +197,24 @@ namespace nadena.dev.modular_avatar.core.editor newBindPoses[i] = Bp; } + var rootBone = renderer.rootBone; + var scaleBone = rootBone; + if (rootBone == null) + { + // Sometimes meshes have no root bone set. This is usually not ideal, but let's make sure we don't + // choke on the scale computation below. + scaleBone = renderer.bones[0]; + } + dst.bindposes = newBindPoses; renderer.bones = newBones; renderer.sharedMesh = dst; - var newRootBone = BoneDatabase.GetRetargetedBone(renderer.rootBone, true); + var newRootBone = BoneDatabase.GetRetargetedBone(rootBone, true); + var newScaleBone = BoneDatabase.GetRetargetedBone(scaleBone, true); - var oldLossyScale = renderer.rootBone.transform.lossyScale; - var newLossyScale = newRootBone.transform.lossyScale; + var oldLossyScale = scaleBone.transform.lossyScale; + var newLossyScale = newScaleBone.transform.lossyScale; var bounds = renderer.localBounds; bounds.extents = new Vector3( From e7ec5b08b4d25bc10782ac48c7ad74c4a5094b04 Mon Sep 17 00:00:00 2001 From: bd_ Date: Fri, 9 Dec 2022 11:40:16 -0800 Subject: [PATCH 06/12] Add warning in inspector UI for incompatible VRCSDK versions. (#147) --- .../Editor/ComponentAllowlistPatch.cs | 4 ++++ .../Editor/Inspector/InspectorCommon.cs | 6 ++++++ .../Editor/Inspector/MAEditorBase.cs | 1 + .../nadena.dev.modular-avatar/Editor/Localization/en.json | 3 ++- .../nadena.dev.modular-avatar/Editor/Localization/ja.json | 3 ++- 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Packages/nadena.dev.modular-avatar/Editor/ComponentAllowlistPatch.cs b/Packages/nadena.dev.modular-avatar/Editor/ComponentAllowlistPatch.cs index cf5a6847..62b389db 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/ComponentAllowlistPatch.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/ComponentAllowlistPatch.cs @@ -33,15 +33,19 @@ namespace nadena.dev.modular_avatar.core.editor [InitializeOnLoad] internal static class ComponentAllowlistPatch { + internal static readonly bool PATCH_OK; + static ComponentAllowlistPatch() { try { PatchAllowlist(); + PATCH_OK = true; } catch (Exception e) { Debug.LogException(e); + PATCH_OK = false; } } diff --git a/Packages/nadena.dev.modular-avatar/Editor/Inspector/InspectorCommon.cs b/Packages/nadena.dev.modular-avatar/Editor/Inspector/InspectorCommon.cs index 732ea9cd..f51f825f 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/Inspector/InspectorCommon.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/Inspector/InspectorCommon.cs @@ -1,6 +1,7 @@ using UnityEditor; using UnityEditor.Experimental.SceneManagement; using UnityEngine; +using UnityEngine.SocialPlatforms; namespace nadena.dev.modular_avatar.core.editor { @@ -19,5 +20,10 @@ namespace nadena.dev.modular_avatar.core.editor EditorGUILayout.HelpBox(Localization.S("hint.not_in_avatar"), MessageType.Warning); } } + + public static void DisplayVRCSDKVersionWarning() + { + EditorGUILayout.HelpBox(Localization.S("hint.bad_vrcsdk"), MessageType.Error); + } } } \ No newline at end of file diff --git a/Packages/nadena.dev.modular-avatar/Editor/Inspector/MAEditorBase.cs b/Packages/nadena.dev.modular-avatar/Editor/Inspector/MAEditorBase.cs index 275f2f71..3d9cd96e 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/Inspector/MAEditorBase.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/Inspector/MAEditorBase.cs @@ -116,6 +116,7 @@ namespace nadena.dev.modular_avatar.core.editor } InspectorCommon.DisplayOutOfAvatarWarning(targets); + if (!ComponentAllowlistPatch.PATCH_OK) InspectorCommon.DisplayVRCSDKVersionWarning(); OnInnerInspectorGUI(); } diff --git a/Packages/nadena.dev.modular-avatar/Editor/Localization/en.json b/Packages/nadena.dev.modular-avatar/Editor/Localization/en.json index 557d0e21..5de6d42d 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/Localization/en.json +++ b/Packages/nadena.dev.modular-avatar/Editor/Localization/en.json @@ -53,5 +53,6 @@ "boneproxy.attachment": "Attachment mode", "boneproxy.attachment.AsChildAtRoot": "As child; at root", "boneproxy.attachment.AsChildKeepWorldPosition": "As child; keep position", - "pb_blocker.help": "This object will not be affected by PhysBones attached to parents." + "pb_blocker.help": "This object will not be affected by PhysBones attached to parents.", + "hint.bad_vrcsdk": "Incompatible version of VRCSDK detected.\n\nPlease try upgrading your VRCSDK; if this does not work, check for a newer version of Modular Avatar as well." } \ No newline at end of file diff --git a/Packages/nadena.dev.modular-avatar/Editor/Localization/ja.json b/Packages/nadena.dev.modular-avatar/Editor/Localization/ja.json index da29cbf7..dc561d6b 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/Localization/ja.json +++ b/Packages/nadena.dev.modular-avatar/Editor/Localization/ja.json @@ -53,5 +53,6 @@ "boneproxy.attachment": "配置モード", "boneproxy.attachment.AsChildAtRoot": "子として・ルートに配置", "boneproxy.attachment.AsChildKeepWorldPosition": "子として・ワールド位置を維持", - "pb_blocker.help": "このオブジェクトは親のPhysBoneから影響を受けなくなります。" + "pb_blocker.help": "このオブジェクトは親のPhysBoneから影響を受けなくなります。", + "hint.bad_vrcsdk": "使用中のVRCSDKのバージョンとは互換性がありません。\n\nVRCSDKを更新してみてください。それでもだめでしたら、Modular Avatarにも最新版が出てないかチェックしてください。" } From 2ac191555eec94a70bce5076c0d2c6b3e7c3d252 Mon Sep 17 00:00:00 2001 From: bd_ Date: Fri, 9 Dec 2022 11:40:22 -0800 Subject: [PATCH 07/12] Sanitize invalid icons on menu installation (#146) --- .../Editor/MenuInstallHook.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Packages/nadena.dev.modular-avatar/Editor/MenuInstallHook.cs b/Packages/nadena.dev.modular-avatar/Editor/MenuInstallHook.cs index cb178600..95ca0dc3 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/MenuInstallHook.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/MenuInstallHook.cs @@ -61,7 +61,8 @@ namespace nadena.dev.modular_avatar.core.editor if (!_installTargets.TryGetValue(installer.installTargetMenu, out var targetMenu)) return; if (_installTargets.ContainsKey(installer.menuToAppend)) return; - targetMenu.controls.AddRange(installer.menuToAppend.controls); + // Clone before appending to sanitize menu icons + targetMenu.controls.AddRange(CloneMenu(installer.menuToAppend).controls); while (targetMenu.controls.Count > VRCExpressionsMenu.MAX_CONTROLS) { @@ -100,9 +101,23 @@ namespace nadena.dev.modular_avatar.core.editor newMenu = Object.Instantiate(menu); AssetDatabase.CreateAsset(newMenu, Util.GenerateAssetPath()); _clonedMenus[menu] = newMenu; - + foreach (var control in newMenu.controls) { + if (Util.ValidateExpressionMenuIcon(control.icon) != Util.ValidateExpressionMenuIconResult.Success) + control.icon = null; + + for (int i = 0; i < control.labels.Length; i++) + { + var label = control.labels[i]; + var labelResult = Util.ValidateExpressionMenuIcon(label.icon); + if (labelResult != Util.ValidateExpressionMenuIconResult.Success) + { + label.icon = null; + control.labels[i] = label; + } + } + if (control.type == VRCExpressionsMenu.Control.ControlType.SubMenu) { control.subMenu = CloneMenu(control.subMenu); From 56e08513a506f13a06fd83337efb330eb140b5ab Mon Sep 17 00:00:00 2001 From: bd_ Date: Fri, 9 Dec 2022 12:39:39 -0800 Subject: [PATCH 08/12] Prune duplicate PBs on merge armature (#150) This fixes issues where outfits which duplicate the PB components from the base avatar can result in PB motion breaking after merge. --- .../Editor/MergeArmatureHook.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/Packages/nadena.dev.modular-avatar/Editor/MergeArmatureHook.cs b/Packages/nadena.dev.modular-avatar/Editor/MergeArmatureHook.cs index 035591c1..a4429ae6 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/MergeArmatureHook.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/MergeArmatureHook.cs @@ -283,6 +283,8 @@ namespace nadena.dev.modular_avatar.core.editor mergedSrcBone.transform.localScale = src.transform.localScale; mergedSrcBone.transform.SetParent(newParent.transform, true); + if (zipMerge) PruneDuplicatePhysBones(newParent, src); + bool retain = HasAdditionalComponents(src, out var constraintType); if (constraintType != null) { @@ -359,5 +361,40 @@ namespace nadena.dev.modular_avatar.core.editor return retain; } + + /** + * Sometimes outfit authors copy the entire armature, including PhysBones components. If we merge these and + * end up with multiple PB components referencing the same target, PB refuses to animate the bone. So detect + * and prune this case. + * + * For simplicity - we currently only detect the case where the physbone references the component it's on. + * TODO - detect duplicate colliders, contacts, et - these can cause perf issues but usually not quite as large + * of a correctness issue. + */ + private void PruneDuplicatePhysBones(GameObject baseBone, GameObject mergeBone) + { + bool hasSelfReferencePB = false; + + foreach (var pb in baseBone.GetComponents()) + { + var target = pb.rootTransform; + if (target == null || target == baseBone.transform) + { + hasSelfReferencePB = true; + break; + } + } + + if (!hasSelfReferencePB) return; + + foreach (var pb in mergeBone.GetComponents()) + { + var target = pb.rootTransform; + if (target == null || target == baseBone.transform) + { + Object.DestroyImmediate(pb); + } + } + } } } \ No newline at end of file From 8bbe774ea198b2791c8915739d391ecea4f23c4d Mon Sep 17 00:00:00 2001 From: bd_ Date: Fri, 9 Dec 2022 12:41:07 -0800 Subject: [PATCH 09/12] Adjust callback order to support EditorOnly fully (#151) --- Packages/nadena.dev.modular-avatar/Editor/AvatarProcessor.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Packages/nadena.dev.modular-avatar/Editor/AvatarProcessor.cs b/Packages/nadena.dev.modular-avatar/Editor/AvatarProcessor.cs index 18e10bbf..3e62f89a 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/AvatarProcessor.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/AvatarProcessor.cs @@ -37,6 +37,9 @@ namespace nadena.dev.modular_avatar.core.editor [InitializeOnLoad] public class AvatarProcessor : IVRCSDKPreprocessAvatarCallback, IVRCSDKPostprocessAvatarCallback { + // Place after EditorOnly processing (which runs at -1024) but hopefully before most other user callbacks + public int callbackOrder => -25; + public delegate void AvatarProcessorCallback(GameObject obj); /// @@ -93,8 +96,6 @@ namespace nadena.dev.modular_avatar.core.editor } } - public int callbackOrder => -9000; - public void OnPostprocessAvatar() { Util.DeleteTemporaryAssets(); From c70e82127150113e5f2eb57983d4ce7ec292ae14 Mon Sep 17 00:00:00 2001 From: bd_ Date: Fri, 9 Dec 2022 19:26:34 -0800 Subject: [PATCH 10/12] Import an initial list of bone name patterns --- .../Editor/BoneNameMappings.cs | 68 +++++++++++++++++++ .../Editor/BoneNameMappings.cs.meta | 3 + 2 files changed, 71 insertions(+) create mode 100644 Packages/nadena.dev.modular-avatar/Editor/BoneNameMappings.cs create mode 100644 Packages/nadena.dev.modular-avatar/Editor/BoneNameMappings.cs.meta diff --git a/Packages/nadena.dev.modular-avatar/Editor/BoneNameMappings.cs b/Packages/nadena.dev.modular-avatar/Editor/BoneNameMappings.cs new file mode 100644 index 00000000..9ef2451e --- /dev/null +++ b/Packages/nadena.dev.modular-avatar/Editor/BoneNameMappings.cs @@ -0,0 +1,68 @@ +namespace nadena.dev.modular_avatar.core.editor +{ + internal class BoneNameMappings + { + // This list is originally from https://github.com/HhotateA/AvatarModifyTools/blob/d8ae75fed8577707253d6b63a64d6053eebbe78b/Assets/HhotateA/AvatarModifyTool/Editor/EnvironmentVariable.cs#L81-L139 + // Copyright (c) 2021 @HhotateA_xR + // Licensed under the MIT License + public static string[][] boneNamePatterns = new string[][] + { + new string[] {"Hips", "Hip"}, + new string[] {"LeftUpperLeg", "UpperLeg_Left", "UpperLeg_L", "Leg_Left", "Leg_L"}, + new string[] {"RightUpperLeg", "UpperLeg_Right", "UpperLeg_R", "Leg_Right", "Leg_R"}, + new string[] {"LeftLowerLeg", "LowerLeg_Left", "LowerLeg_L", "Knee_Left", "Knee_L"}, + new string[] {"RightLowerLeg", "LowerLeg_Right", "LowerLeg_R", "Knee_Right", "Knee_R"}, + new string[] {"LeftFoot", "Foot_Left", "Foot_L"}, + new string[] {"RightFoot", "Foot_Right", "Foot_R"}, + new string[] {"Spine"}, + new string[] {"Chest"}, + new string[] {"Neck"}, + new string[] {"Head"}, + new string[] {"LeftShoulder", "Shoulder_Left", "Shoulder_L"}, + new string[] {"RightShoulder", "Shoulder_Right", "Shoulder_R"}, + new string[] {"LeftUpperArm", "UpperArm_Left", "UpperArm_L", "Arm_Left", "Arm_L"}, + new string[] {"RightUpperArm", "UpperArm_Right", "UpperArm_R", "Arm_Right", "Arm_R"}, + new string[] {"LeftLowerArm", "LowerArm_Left", "LowerArm_L"}, + new string[] {"RightLowerArm", "LowerArm_Right", "LowerArm_R"}, + new string[] {"LeftHand", "Hand_Left", "Hand_L"}, + new string[] {"RightHand", "Hand_Right", "Hand_R"}, + new string[] {"LeftToes", "Toes_Left", "Toe_Left", "ToeIK_L", "Toes_L", "Toe_L"}, + new string[] {"RightToes", "Toes_Right", "Toe_Right", "ToeIK_R", "Toes_R", "Toe_R"}, + new string[] {"LeftEye", "Eye_Left", "Eye_L"}, + new string[] {"RightEye", "Eye_Right", "Eye_R"}, + new string[] {"Jaw"}, + new string[] {"LeftThumbProximal", "ProximalThumb_Left", "ProximalThumb_L"}, + new string[] {"LeftThumbIntermediate", "IntermediateThumb_Left", "IntermediateThumb_L"}, + new string[] {"LeftThumbDistal", "DistalThumb_Left", "DistalThumb_L"}, + new string[] {"LeftIndexProximal", "ProximalIndex_Left", "ProximalIndex_L"}, + new string[] {"LeftIndexIntermediate", "IntermediateIndex_Left", "IntermediateIndex_L"}, + new string[] {"LeftIndexDistal", "DistalIndex_Left", "DistalIndex_L"}, + new string[] {"LeftMiddleProximal", "ProximalMiddle_Left", "ProximalMiddle_L"}, + new string[] {"LeftMiddleIntermediate", "IntermediateMiddle_Left", "IntermediateMiddle_L"}, + new string[] {"LeftMiddleDistal", "DistalMiddle_Left", "DistalMiddle_L"}, + new string[] {"LeftRingProximal", "ProximalRing_Left", "ProximalRing_L"}, + new string[] {"LeftRingIntermediate", "IntermediateRing_Left", "IntermediateRing_L"}, + new string[] {"LeftRingDistal", "DistalRing_Left", "DistalRing_L"}, + new string[] {"LeftLittleProximal", "ProximalLittle_Left", "ProximalLittle_L"}, + new string[] {"LeftLittleIntermediate", "IntermediateLittle_Left", "IntermediateLittle_L"}, + new string[] {"LeftLittleDistal", "DistalLittle_Left", "DistalLittle_L"}, + new string[] {"RightThumbProximal", "ProximalThumb_Right", "ProximalThumb_R"}, + new string[] {"RightThumbIntermediate", "IntermediateThumb_Right", "IntermediateThumb_R"}, + new string[] {"RightThumbDistal", "DistalThumb_Right", "DistalThumb_R"}, + new string[] {"RightIndexProximal", "ProximalIndex_Right", "ProximalIndex_R"}, + new string[] {"RightIndexIntermediate", "IntermediateIndex_Right", "IntermediateIndex_R"}, + new string[] {"RightIndexDistal", "DistalIndex_Right", "DistalIndex_R"}, + new string[] {"RightMiddleProximal", "ProximalMiddle_Right", "ProximalMiddle_R"}, + new string[] {"RightMiddleIntermediate", "IntermediateMiddle_Right", "IntermediateMiddle_R"}, + new string[] {"RightMiddleDistal", "DistalMiddle_Right", "DistalMiddle_R"}, + new string[] {"RightRingProximal", "ProximalRing_Right", "ProximalRing_R"}, + new string[] {"RightRingIntermediate", "IntermediateRing_Right", "IntermediateRing_R"}, + new string[] {"RightRingDistal", "DistalRing_Right", "DistalRing_R"}, + new string[] {"RightLittleProximal", "ProximalLittle_Right", "ProximalLittle_R"}, + new string[] {"RightLittleIntermediate", "IntermediateLittle_Right", "IntermediateLittle_R"}, + new string[] {"RightLittleDistal", "DistalLittle_Right", "DistalLittle_R"}, + new string[] {"UpperChest"}, + new string[] {"LastBone", "Armature"}, // 本来的ではないけど,Rootもhitさせたい + }; + } +} \ No newline at end of file diff --git a/Packages/nadena.dev.modular-avatar/Editor/BoneNameMappings.cs.meta b/Packages/nadena.dev.modular-avatar/Editor/BoneNameMappings.cs.meta new file mode 100644 index 00000000..baf07316 --- /dev/null +++ b/Packages/nadena.dev.modular-avatar/Editor/BoneNameMappings.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 51d014cbf5f24b3db8316c98e75d7efd +timeCreated: 1670642736 \ No newline at end of file From 223f96d04ec2702c0cff8eababcf8ef76a1dbe56 Mon Sep 17 00:00:00 2001 From: bd_ Date: Fri, 9 Dec 2022 20:33:05 -0800 Subject: [PATCH 11/12] Initial implementation of heuristic bone matching Closes: #105 --- .../Editor/BoneNameMappings.cs | 68 ------ .../Editor/EasySetupOutfit.cs | 1 + .../Editor/HeuristicBoneMapper.cs | 219 ++++++++++++++++++ ...gs.cs.meta => HeuristicBoneMapper.cs.meta} | 0 .../Editor/Inspector/MergeArmatureEditor.cs | 10 + .../Editor/Localization/en.json | 2 + .../Editor/Localization/ja.json | 2 + 7 files changed, 234 insertions(+), 68 deletions(-) delete mode 100644 Packages/nadena.dev.modular-avatar/Editor/BoneNameMappings.cs create mode 100644 Packages/nadena.dev.modular-avatar/Editor/HeuristicBoneMapper.cs rename Packages/nadena.dev.modular-avatar/Editor/{BoneNameMappings.cs.meta => HeuristicBoneMapper.cs.meta} (100%) diff --git a/Packages/nadena.dev.modular-avatar/Editor/BoneNameMappings.cs b/Packages/nadena.dev.modular-avatar/Editor/BoneNameMappings.cs deleted file mode 100644 index 9ef2451e..00000000 --- a/Packages/nadena.dev.modular-avatar/Editor/BoneNameMappings.cs +++ /dev/null @@ -1,68 +0,0 @@ -namespace nadena.dev.modular_avatar.core.editor -{ - internal class BoneNameMappings - { - // This list is originally from https://github.com/HhotateA/AvatarModifyTools/blob/d8ae75fed8577707253d6b63a64d6053eebbe78b/Assets/HhotateA/AvatarModifyTool/Editor/EnvironmentVariable.cs#L81-L139 - // Copyright (c) 2021 @HhotateA_xR - // Licensed under the MIT License - public static string[][] boneNamePatterns = new string[][] - { - new string[] {"Hips", "Hip"}, - new string[] {"LeftUpperLeg", "UpperLeg_Left", "UpperLeg_L", "Leg_Left", "Leg_L"}, - new string[] {"RightUpperLeg", "UpperLeg_Right", "UpperLeg_R", "Leg_Right", "Leg_R"}, - new string[] {"LeftLowerLeg", "LowerLeg_Left", "LowerLeg_L", "Knee_Left", "Knee_L"}, - new string[] {"RightLowerLeg", "LowerLeg_Right", "LowerLeg_R", "Knee_Right", "Knee_R"}, - new string[] {"LeftFoot", "Foot_Left", "Foot_L"}, - new string[] {"RightFoot", "Foot_Right", "Foot_R"}, - new string[] {"Spine"}, - new string[] {"Chest"}, - new string[] {"Neck"}, - new string[] {"Head"}, - new string[] {"LeftShoulder", "Shoulder_Left", "Shoulder_L"}, - new string[] {"RightShoulder", "Shoulder_Right", "Shoulder_R"}, - new string[] {"LeftUpperArm", "UpperArm_Left", "UpperArm_L", "Arm_Left", "Arm_L"}, - new string[] {"RightUpperArm", "UpperArm_Right", "UpperArm_R", "Arm_Right", "Arm_R"}, - new string[] {"LeftLowerArm", "LowerArm_Left", "LowerArm_L"}, - new string[] {"RightLowerArm", "LowerArm_Right", "LowerArm_R"}, - new string[] {"LeftHand", "Hand_Left", "Hand_L"}, - new string[] {"RightHand", "Hand_Right", "Hand_R"}, - new string[] {"LeftToes", "Toes_Left", "Toe_Left", "ToeIK_L", "Toes_L", "Toe_L"}, - new string[] {"RightToes", "Toes_Right", "Toe_Right", "ToeIK_R", "Toes_R", "Toe_R"}, - new string[] {"LeftEye", "Eye_Left", "Eye_L"}, - new string[] {"RightEye", "Eye_Right", "Eye_R"}, - new string[] {"Jaw"}, - new string[] {"LeftThumbProximal", "ProximalThumb_Left", "ProximalThumb_L"}, - new string[] {"LeftThumbIntermediate", "IntermediateThumb_Left", "IntermediateThumb_L"}, - new string[] {"LeftThumbDistal", "DistalThumb_Left", "DistalThumb_L"}, - new string[] {"LeftIndexProximal", "ProximalIndex_Left", "ProximalIndex_L"}, - new string[] {"LeftIndexIntermediate", "IntermediateIndex_Left", "IntermediateIndex_L"}, - new string[] {"LeftIndexDistal", "DistalIndex_Left", "DistalIndex_L"}, - new string[] {"LeftMiddleProximal", "ProximalMiddle_Left", "ProximalMiddle_L"}, - new string[] {"LeftMiddleIntermediate", "IntermediateMiddle_Left", "IntermediateMiddle_L"}, - new string[] {"LeftMiddleDistal", "DistalMiddle_Left", "DistalMiddle_L"}, - new string[] {"LeftRingProximal", "ProximalRing_Left", "ProximalRing_L"}, - new string[] {"LeftRingIntermediate", "IntermediateRing_Left", "IntermediateRing_L"}, - new string[] {"LeftRingDistal", "DistalRing_Left", "DistalRing_L"}, - new string[] {"LeftLittleProximal", "ProximalLittle_Left", "ProximalLittle_L"}, - new string[] {"LeftLittleIntermediate", "IntermediateLittle_Left", "IntermediateLittle_L"}, - new string[] {"LeftLittleDistal", "DistalLittle_Left", "DistalLittle_L"}, - new string[] {"RightThumbProximal", "ProximalThumb_Right", "ProximalThumb_R"}, - new string[] {"RightThumbIntermediate", "IntermediateThumb_Right", "IntermediateThumb_R"}, - new string[] {"RightThumbDistal", "DistalThumb_Right", "DistalThumb_R"}, - new string[] {"RightIndexProximal", "ProximalIndex_Right", "ProximalIndex_R"}, - new string[] {"RightIndexIntermediate", "IntermediateIndex_Right", "IntermediateIndex_R"}, - new string[] {"RightIndexDistal", "DistalIndex_Right", "DistalIndex_R"}, - new string[] {"RightMiddleProximal", "ProximalMiddle_Right", "ProximalMiddle_R"}, - new string[] {"RightMiddleIntermediate", "IntermediateMiddle_Right", "IntermediateMiddle_R"}, - new string[] {"RightMiddleDistal", "DistalMiddle_Right", "DistalMiddle_R"}, - new string[] {"RightRingProximal", "ProximalRing_Right", "ProximalRing_R"}, - new string[] {"RightRingIntermediate", "IntermediateRing_Right", "IntermediateRing_R"}, - new string[] {"RightRingDistal", "DistalRing_Right", "DistalRing_R"}, - new string[] {"RightLittleProximal", "ProximalLittle_Right", "ProximalLittle_R"}, - new string[] {"RightLittleIntermediate", "IntermediateLittle_Right", "IntermediateLittle_R"}, - new string[] {"RightLittleDistal", "DistalLittle_Right", "DistalLittle_R"}, - new string[] {"UpperChest"}, - new string[] {"LastBone", "Armature"}, // 本来的ではないけど,Rootもhitさせたい - }; - } -} \ No newline at end of file diff --git a/Packages/nadena.dev.modular-avatar/Editor/EasySetupOutfit.cs b/Packages/nadena.dev.modular-avatar/Editor/EasySetupOutfit.cs index 5722841f..edc6aaa6 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/EasySetupOutfit.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/EasySetupOutfit.cs @@ -24,6 +24,7 @@ namespace nadena.dev.modular_avatar.core.editor merge.mergeTarget = new AvatarObjectReference(); merge.mergeTarget.referencePath = RuntimeUtil.RelativePath(avatarRoot, avatarArmature.gameObject); merge.InferPrefixSuffix(); + HeuristicBoneMapper.RenameBonesByHeuristic(merge); } [MenuItem("GameObject/[ModularAvatar] Setup Outfit", true, PRIORITY)] diff --git a/Packages/nadena.dev.modular-avatar/Editor/HeuristicBoneMapper.cs b/Packages/nadena.dev.modular-avatar/Editor/HeuristicBoneMapper.cs new file mode 100644 index 00000000..82a2345c --- /dev/null +++ b/Packages/nadena.dev.modular-avatar/Editor/HeuristicBoneMapper.cs @@ -0,0 +1,219 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using UnityEditor; +using UnityEngine; + +namespace nadena.dev.modular_avatar.core.editor +{ + internal class HeuristicBoneMapper + { + // This list is originally from https://github.com/HhotateA/AvatarModifyTools/blob/d8ae75fed8577707253d6b63a64d6053eebbe78b/Assets/HhotateA/AvatarModifyTool/Editor/EnvironmentVariable.cs#L81-L139 + // Copyright (c) 2021 @HhotateA_xR + // Licensed under the MIT License + private static string[][] boneNamePatterns = new[] + { + new[] {"Hips", "Hip"}, + new[] {"LeftUpperLeg", "UpperLeg_Left", "UpperLeg_L", "Leg_Left", "Leg_L"}, + new[] {"RightUpperLeg", "UpperLeg_Right", "UpperLeg_R", "Leg_Right", "Leg_R"}, + new[] {"LeftLowerLeg", "LowerLeg_Left", "LowerLeg_L", "Knee_Left", "Knee_L"}, + new[] {"RightLowerLeg", "LowerLeg_Right", "LowerLeg_R", "Knee_Right", "Knee_R"}, + new[] {"LeftFoot", "Foot_Left", "Foot_L"}, + new[] {"RightFoot", "Foot_Right", "Foot_R"}, + new[] {"Spine"}, + new[] {"Chest"}, + new[] {"Neck"}, + new[] {"Head"}, + new[] {"LeftShoulder", "Shoulder_Left", "Shoulder_L"}, + new[] {"RightShoulder", "Shoulder_Right", "Shoulder_R"}, + new[] {"LeftUpperArm", "UpperArm_Left", "UpperArm_L", "Arm_Left", "Arm_L"}, + new[] {"RightUpperArm", "UpperArm_Right", "UpperArm_R", "Arm_Right", "Arm_R"}, + new[] {"LeftLowerArm", "LowerArm_Left", "LowerArm_L"}, + new[] {"RightLowerArm", "LowerArm_Right", "LowerArm_R"}, + new[] {"LeftHand", "Hand_Left", "Hand_L"}, + new[] {"RightHand", "Hand_Right", "Hand_R"}, + new[] {"LeftToes", "Toes_Left", "Toe_Left", "ToeIK_L", "Toes_L", "Toe_L"}, + new[] {"RightToes", "Toes_Right", "Toe_Right", "ToeIK_R", "Toes_R", "Toe_R"}, + new[] {"LeftEye", "Eye_Left", "Eye_L"}, + new[] {"RightEye", "Eye_Right", "Eye_R"}, + new[] {"Jaw"}, + new[] {"LeftThumbProximal", "ProximalThumb_Left", "ProximalThumb_L"}, + new[] {"LeftThumbIntermediate", "IntermediateThumb_Left", "IntermediateThumb_L"}, + new[] {"LeftThumbDistal", "DistalThumb_Left", "DistalThumb_L"}, + new[] {"LeftIndexProximal", "ProximalIndex_Left", "ProximalIndex_L"}, + new[] {"LeftIndexIntermediate", "IntermediateIndex_Left", "IntermediateIndex_L"}, + new[] {"LeftIndexDistal", "DistalIndex_Left", "DistalIndex_L"}, + new[] {"LeftMiddleProximal", "ProximalMiddle_Left", "ProximalMiddle_L"}, + new[] {"LeftMiddleIntermediate", "IntermediateMiddle_Left", "IntermediateMiddle_L"}, + new[] {"LeftMiddleDistal", "DistalMiddle_Left", "DistalMiddle_L"}, + new[] {"LeftRingProximal", "ProximalRing_Left", "ProximalRing_L"}, + new[] {"LeftRingIntermediate", "IntermediateRing_Left", "IntermediateRing_L"}, + new[] {"LeftRingDistal", "DistalRing_Left", "DistalRing_L"}, + new[] {"LeftLittleProximal", "ProximalLittle_Left", "ProximalLittle_L"}, + new[] {"LeftLittleIntermediate", "IntermediateLittle_Left", "IntermediateLittle_L"}, + new[] {"LeftLittleDistal", "DistalLittle_Left", "DistalLittle_L"}, + new[] {"RightThumbProximal", "ProximalThumb_Right", "ProximalThumb_R"}, + new[] {"RightThumbIntermediate", "IntermediateThumb_Right", "IntermediateThumb_R"}, + new[] {"RightThumbDistal", "DistalThumb_Right", "DistalThumb_R"}, + new[] {"RightIndexProximal", "ProximalIndex_Right", "ProximalIndex_R"}, + new[] {"RightIndexIntermediate", "IntermediateIndex_Right", "IntermediateIndex_R"}, + new[] {"RightIndexDistal", "DistalIndex_Right", "DistalIndex_R"}, + new[] {"RightMiddleProximal", "ProximalMiddle_Right", "ProximalMiddle_R"}, + new[] {"RightMiddleIntermediate", "IntermediateMiddle_Right", "IntermediateMiddle_R"}, + new[] {"RightMiddleDistal", "DistalMiddle_Right", "DistalMiddle_R"}, + new[] {"RightRingProximal", "ProximalRing_Right", "ProximalRing_R"}, + new[] {"RightRingIntermediate", "IntermediateRing_Right", "IntermediateRing_R"}, + new[] {"RightRingDistal", "DistalRing_Right", "DistalRing_R"}, + new[] {"RightLittleProximal", "ProximalLittle_Right", "ProximalLittle_R"}, + new[] {"RightLittleIntermediate", "IntermediateLittle_Right", "IntermediateLittle_R"}, + new[] {"RightLittleDistal", "DistalLittle_Right", "DistalLittle_R"}, + new[] {"UpperChest"}, + }; + + internal static string NormalizeName(string name) + { + return name.ToLowerInvariant() + .Replace("_", "") + .Replace(".", "") + .Replace(" ", ""); + } + + internal static readonly ImmutableDictionary NameToBoneMap; + internal static readonly ImmutableDictionary> BoneToNameMap; + + static HeuristicBoneMapper() + { + var nameToBoneMap = new Dictionary(); + var boneToNameMap = new Dictionary>(); + + for (int i = 0; i < boneNamePatterns.Length; i++) + { + var bone = (HumanBodyBones) i; + foreach (var name in boneNamePatterns[i]) + { + RegisterNameForBone(NormalizeName(name), bone); + } + } + + void RegisterNameForBone(string name, HumanBodyBones bone) + { + nameToBoneMap[name] = bone; + if (!boneToNameMap.TryGetValue(bone, out var names)) + { + names = ImmutableList.Empty; + } + + if (!names.Contains(name)) + { + boneToNameMap[bone] = names.Add(name); + } + } + + NameToBoneMap = nameToBoneMap.ToImmutableDictionary(); + BoneToNameMap = boneToNameMap.ToImmutableDictionary(); + } + + + /// + /// Examines the children of src, and tries to map them to the corresponding child of newParent. + /// Unmappable bones will not be added to the resulting dictionary. Ensures that each parent bone is only mapped + /// once. + /// + internal static Dictionary AssignBoneMappings( + ModularAvatarMergeArmature config, + GameObject src, + GameObject newParent + ) + { + HashSet unassigned = new HashSet(); + Dictionary mappings = new Dictionary(); + List heuristicAssignmentPass = new List(); + + foreach (Transform child in newParent.transform) + { + unassigned.Add(child); + } + + foreach (Transform child in src.transform) + { + var childName = child.gameObject.name; + if (childName.StartsWith(config.prefix) && childName.EndsWith(config.suffix)) + { + var targetObjectName = childName.Substring(config.prefix.Length, + childName.Length - config.prefix.Length - config.suffix.Length); + var targetObject = newParent.transform.Find(targetObjectName); + + if (targetObject != null && unassigned.Contains(targetObject)) + { + mappings[child] = targetObject; + unassigned.Remove(targetObject); + } + else + { + heuristicAssignmentPass.Add(child); + } + } + } + + Dictionary lcNameToXform = new Dictionary(); + foreach (var target in unassigned) + { + lcNameToXform[NormalizeName(target.gameObject.name)] = target; + } + + foreach (var child in heuristicAssignmentPass) + { + var childName = child.gameObject.name; + var targetObjectName = childName.Substring(config.prefix.Length, + childName.Length - config.prefix.Length - config.suffix.Length); + + if (!NameToBoneMap.TryGetValue( + NormalizeName(targetObjectName.ToLowerInvariant()), out var bodyBone)) + { + continue; + } + + foreach (var otherName in BoneToNameMap[bodyBone]) + { + if (lcNameToXform.TryGetValue(otherName, out var targetObject)) + { + mappings[child] = targetObject; + unassigned.Remove(targetObject); + lcNameToXform.Remove(otherName.ToLowerInvariant()); + break; + } + } + } + + return mappings; + } + + internal static void RenameBonesByHeuristic(ModularAvatarMergeArmature config) + { + var target = config.mergeTarget.Get(RuntimeUtil.FindAvatarInParents(config.transform)); + if (target == null) return; + + Traverse(config.transform, target.transform); + + void Traverse(Transform src, Transform dst) + { + var mappings = AssignBoneMappings(config, src.gameObject, dst.gameObject); + + foreach (var pair in mappings) + { + var newName = config.prefix + pair.Value.gameObject.name + config.suffix; + var srcGameObj = pair.Key.gameObject; + var oldName = srcGameObj.name; + + if (oldName != newName) + { + Undo.RecordObject(srcGameObj, "Applying heuristic mapping"); + srcGameObj.name = newName; + PrefabUtility.RecordPrefabInstancePropertyModifications(srcGameObj); + } + + Traverse(pair.Key, pair.Value); + } + } + } + } +} \ No newline at end of file diff --git a/Packages/nadena.dev.modular-avatar/Editor/BoneNameMappings.cs.meta b/Packages/nadena.dev.modular-avatar/Editor/HeuristicBoneMapper.cs.meta similarity index 100% rename from Packages/nadena.dev.modular-avatar/Editor/BoneNameMappings.cs.meta rename to Packages/nadena.dev.modular-avatar/Editor/HeuristicBoneMapper.cs.meta diff --git a/Packages/nadena.dev.modular-avatar/Editor/Inspector/MergeArmatureEditor.cs b/Packages/nadena.dev.modular-avatar/Editor/Inspector/MergeArmatureEditor.cs index c512762c..cfdd5a77 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/Inspector/MergeArmatureEditor.cs +++ b/Packages/nadena.dev.modular-avatar/Editor/Inspector/MergeArmatureEditor.cs @@ -49,6 +49,16 @@ namespace nadena.dev.modular_avatar.core.editor } } + var enable_name_assignment = + target.mergeTarget.Get(RuntimeUtil.FindAvatarInParents(target.transform)) != null; + using (var scope = new EditorGUI.DisabledScope(!enable_name_assignment)) + { + if (GUILayout.Button(G("merge_armature.adjust_names"))) + { + HeuristicBoneMapper.RenameBonesByHeuristic(target); + } + } + Localization.ShowLanguageUI(); } } diff --git a/Packages/nadena.dev.modular-avatar/Editor/Localization/en.json b/Packages/nadena.dev.modular-avatar/Editor/Localization/en.json index 5de6d42d..5e3002dd 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/Localization/en.json +++ b/Packages/nadena.dev.modular-avatar/Editor/Localization/en.json @@ -30,6 +30,8 @@ "merge_armature.suffix.tooltip": "Suffix expected on bones in this merged armature", "merge_armature.locked": "Lock position", "merge_armature.locked.tooltip": "Lock the position of this armature's bones to the target armature (and vice versa). Useful for creating animations.", + "merge_armature.adjust_names": "Adjust bone names to match target", + "merge_armature.adjust_names.tooltip": "Changes bone names to match the target avatar. Useful for porting outfits from one avatar to another.", "path_mode.Relative": "Relative to this object", "path_mode.Absolute": "Absolute (based on avatar root)", "merge_animator.animator": "Animator to merge", diff --git a/Packages/nadena.dev.modular-avatar/Editor/Localization/ja.json b/Packages/nadena.dev.modular-avatar/Editor/Localization/ja.json index dc561d6b..9943c6d4 100644 --- a/Packages/nadena.dev.modular-avatar/Editor/Localization/ja.json +++ b/Packages/nadena.dev.modular-avatar/Editor/Localization/ja.json @@ -30,6 +30,8 @@ "merge_armature.suffix.tooltip": "このオブジェクトの子に付く後置詞", "merge_armature.locked": "位置を固定", "merge_armature.locked.tooltip": "このオブジェクトのボーンを統合先のボーンに常に相互的に位置を合わせる。アニメーション制作向け", + "merge_armature.adjust_names": "ボーン名を統合先に合わせる", + "merge_armature.adjust_names.tooltip": "統合先のボーン名に合わせて、衣装のボーン名を合わせて変更します。統合先アバターに非対応の衣装導入向け機能です。", "path_mode.Relative": "相対的(このオブジェクトからのパスを使用)", "path_mode.Absolute": "絶対的(アバタールートからのパスを使用)", "merge_animator.animator": "統合されるアニメーター", From 5b97455c0f7c157b9ab2c5345846cf529528cdae Mon Sep 17 00:00:00 2001 From: bd_ Date: Sat, 10 Dec 2022 16:09:02 -0800 Subject: [PATCH 12/12] Document new heuristic matching feature --- docs/docs/reference/merge-armature.md | 8 +++++++- .../current/reference/merge-armature.md | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/docs/reference/merge-armature.md b/docs/docs/reference/merge-armature.md index ff2c4459..dc540b97 100644 --- a/docs/docs/reference/merge-armature.md +++ b/docs/docs/reference/merge-armature.md @@ -41,4 +41,10 @@ This is intended for use when animating non-humanoid bones. For example, you cou ## Object references Although the editor UI allows you to drag in a target object for the merge armature component, internally this is saved as a path reference. -This allows the merge armature component to automatically restore its Merge Target after it is saved in a prefab. \ No newline at end of file +This allows the merge armature component to automatically restore its Merge Target after it is saved in a prefab. + +## Matching bone names + +Since Merge Animator will attempt to match bones by name, just attaching it won't always work to make an outfit designed for one avatar work with another avatar. +You can click the "Adjust bone names to match target" button to attempt to rename bones in the outfit to match the base avatar it's currently attached to. +This will be done automatically if you added the Merge Armature component using the "Setup Outfit" menu item. \ No newline at end of file diff --git a/docs/i18n/ja/docusaurus-plugin-content-docs/current/reference/merge-armature.md b/docs/i18n/ja/docusaurus-plugin-content-docs/current/reference/merge-armature.md index 5c116c41..defabbac 100644 --- a/docs/i18n/ja/docusaurus-plugin-content-docs/current/reference/merge-armature.md +++ b/docs/i18n/ja/docusaurus-plugin-content-docs/current/reference/merge-armature.md @@ -44,4 +44,10 @@ Transform以外のコンポーネントが入っているボーンがある場 ## オブジェクト引用 -エディタ上では統合先をドラッグアンドドロップで指定しますが、内部ではパスで保存されます。プレハブ化してもちゃんと統合先を保存できるということです。 \ No newline at end of file +エディタ上では統合先をドラッグアンドドロップで指定しますが、内部ではパスで保存されます。プレハブ化してもちゃんと統合先を保存できるということです。 + +## ボーン名合わせ + +Merge Animatorがボーンを名前で照合するので、つけるだけでは非対応衣装がうまく動かない場合があります。 +対策として、「ボーン名を統合先に合わせる」ボタンを押すことで、衣装側のボーン名を自動的にアバターのボーン名に合わせようとします。 +なお、「Setup outfit」でMerge Armatureをつける場合はこの処理が自動的に走ります。 \ No newline at end of file