Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new Rename Face For MMD component #64

Merged
merged 7 commits into from
May 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ public bool RunChecks(BlendShapeMappings blendShapeMappings)
return true;
}

public bool RunChecks(VRCAvatarDescriptor avatarDescriptor)
// TODO: use an enum or types to decide what checks to skip?
public bool RunChecks(VRCAvatarDescriptor avatarDescriptor, bool ignoreBodyName = false)
{
if (avatarDescriptor == null)
{
Expand All @@ -87,7 +88,7 @@ public bool RunChecks(VRCAvatarDescriptor avatarDescriptor)
return false;
}

if (visemeSkinnedMesh.name != "Body")
if (visemeSkinnedMesh.name != "Body" && !ignoreBodyName)
{
LogLocalized(Severity.Warning, "CommonChecks:AvatarFaceSMRNotCalledBody");
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,18 @@ msgstr "Unsafe to remove"

msgid "RemoveAnimatorLayersComponentEditor:CustomLayersToRemove"
msgstr "Custom Layers To Remove"

msgid "RenameFaceForMmdComponentEditor:AlreadyCalledBody"
msgstr "Your avatar's face mesh is already called \"Body\". This component is not required."

msgid "RenameFaceForMmdComponentEditor:ActionToPerform"
msgstr "This component will non-destructively rename your avatar's face mesh \"{0}\" to the required name for MMD \"Body\"."

msgid "RenameFaceForMmdComponentEditor:ActionToPerformHasConflicts"
msgstr "Your avatar already has another \"Body\" that is not a face mesh, which will also be renamed."

msgid "RenameFaceForMmdComponentEditor:ActionToPerformSuffix"
msgstr "The following renames will be performed:"

msgid "RenameFaceForMmdComponentEditor:IsPlaying"
msgstr "Currently in play mode. Renames have been applied if necessary."
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,18 @@ msgstr "安全に削除できない"

msgid "RemoveAnimatorLayersComponentEditor:CustomLayersToRemove"
msgstr "削除するカスタムのレイヤー"

msgid "RenameFaceForMmdComponentEditor:AlreadyCalledBody"
msgstr "このアバターの顔メッシュはすでに「Body」という名前が付けられています。このコンポーネントの必要はありません。"

msgid "RenameFaceForMmdComponentEditor:ActionToPerform"
msgstr "このコンポーネントは、非破壊的にアバターの顔のメッシュの名前を「{0}」からMMD用の名前「Body」に変更します。"

msgid "RenameFaceForMmdComponentEditor:ActionToPerformHasConflicts"
msgstr "このアバターはすでに顔じゃない「Body」オブジェクトがあるため、追加に変更します。"

msgid "RenameFaceForMmdComponentEditor:ActionToPerformSuffix"
msgstr "下記の名前の変更を行います:"

msgid "RenameFaceForMmdComponentEditor:IsPlaying"
msgstr "現在はプレイモードに入っています。必要な名前変更は行いました。"
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#if NDMMD_VRCSDK3_AVATARS

using enitimeago.NonDestructiveMMD.vendor;
using nadena.dev.ndmf;

namespace enitimeago.NonDestructiveMMD
Expand All @@ -13,6 +14,10 @@ protected override void Configure()
{
var seq = InPhase(BuildPhase.Transforming);
seq.Run(BlendShapeMappingsPass.Instance);
seq.WithRequiredExtension(typeof(AnimationServicesContext), _ =>
{
seq.Run(RenameFaceForMmdPass.Instance);
});
seq.Run(RemoveAnimatorLayersPass.Instance);
seq.Run(WriteDefaultsPass.Instance);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using UnityEditor;
using UnityEngine;
using VRC.SDK3.Avatars.Components;
using L = enitimeago.NonDestructiveMMD.Localization;

namespace enitimeago.NonDestructiveMMD
{
[CustomEditor(typeof(RenameFaceForMmdComponent))]
public class RenameFaceForMmdComponentEditor : Editor
{
private CommonChecks _commonChecks;

public void OnEnable()
{
_commonChecks = new CommonChecks(isEditor: true);
}

public override void OnInspectorGUI()
{
var data = (RenameFaceForMmdComponent)target;
var avatar = data.GetComponentInParent<VRCAvatarDescriptor>();

EditorGUILayout.BeginHorizontal();
L.DrawLanguagePicker();
EditorGUILayout.EndHorizontal();

// Run asserts, however continue rendering GUI if errors are encountered.
_commonChecks.RunChecks(avatar, ignoreBodyName: true);
if (avatar.VisemeSkinnedMesh.name == null)
{
return;
}

if (!EditorApplication.isPlaying)
{
// Let the user know what will happen.
if (avatar.VisemeSkinnedMesh.name == "Body")
{
EditorGUILayout.HelpBox(L.Tr("RenameFaceForMmdComponentEditor:AlreadyCalledBody"), MessageType.Info);
}
else
{
GUILayout.Label(string.Format(L.Tr("RenameFaceForMmdComponentEditor:ActionToPerform"), avatar.VisemeSkinnedMesh.name), EditorStyles.wordWrappedLabel);

var toRenames = RenameFaceForMmdPass.DetermineRenames(avatar.GetComponentsInChildren<SkinnedMeshRenderer>());
if (toRenames.Count > 0)
{
GUILayout.Label(L.Tr("RenameFaceForMmdComponentEditor:ActionToPerformHasConflicts"), EditorStyles.wordWrappedLabel);
}
GUILayout.Label(L.Tr("RenameFaceForMmdComponentEditor:ActionToPerformSuffix"), EditorStyles.wordWrappedLabel);

// TODO: can this be a table so that the columns are the same width?
EditorGUI.indentLevel++;
{
foreach (var toRename in toRenames)
{
EditorGUILayout.BeginHorizontal();
GUILayout.Label(string.Format("{0} ←", toRename.newName), EditorStyles.wordWrappedLabel);
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.ObjectField(toRename.skinnedMeshRenderer, typeof(GameObject), true);
EditorGUI.EndDisabledGroup();
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.BeginHorizontal();
GUILayout.Label("Body ←", EditorStyles.wordWrappedLabel);
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.ObjectField(avatar.VisemeSkinnedMesh, typeof(GameObject), true);
EditorGUI.EndDisabledGroup();
EditorGUILayout.EndHorizontal();
}
EditorGUI.indentLevel--;
}
}
else
{
GUILayout.Label(L.Tr("RenameFaceForMmdComponentEditor:IsPlaying"), EditorStyles.wordWrappedLabel);
}
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using nadena.dev.ndmf;
using UnityEngine;
using VRC.SDK3.Avatars.Components;
using L = enitimeago.NonDestructiveMMD.Localization;

namespace enitimeago.NonDestructiveMMD
{
public class RenameFaceForMmdPass : Pass<RenameFaceForMmdPass>
{
public override string DisplayName => "Rename Face For MMD";

protected override void Execute(BuildContext context)
{
Execute(context.AvatarRootObject);
}

internal void Execute(GameObject avatarRootObject)
{
// TODO: need some checks but if we run checks we'll see mmd shape keys exist
// var commonChecks = new CommonChecks(isEditor: false);
// if (!commonChecks.RunChecks(avatarRootObject))
// {
// return;
// }

var renameFaceForMmdComponent = avatarRootObject.GetComponentInChildren<RenameFaceForMmdComponent>();
if (renameFaceForMmdComponent == null)
{
return;
}
var descriptor = avatarRootObject.GetComponent<VRCAvatarDescriptor>();
if (descriptor.VisemeSkinnedMesh == null)
{
Debug.LogWarning("No face object to rename for MMD, skipping");
return;
}
if (descriptor.VisemeSkinnedMesh.name == "Body")
{
Debug.LogWarning("Face object is already called Body, skipping");
return;
}

var skinnedMeshRenderers = avatarRootObject.GetComponentsInChildren<SkinnedMeshRenderer>();

// Objects called "Body" on the avatar need to be renamed to something else.
// TODO: maybe this only needs to happen to top-level objects. or only Body needs to be top-level.
var renames = DetermineRenames(skinnedMeshRenderers);
foreach (var rename in renames)
{
rename.skinnedMeshRenderer.name = rename.newName;
}

// Rename the face object to "Body".
descriptor.VisemeSkinnedMesh.name = "Body";

Object.DestroyImmediate(renameFaceForMmdComponent);
}

public struct RenameInfo
{
public SkinnedMeshRenderer skinnedMeshRenderer;
public string newName;
}

public static List<RenameInfo> DetermineRenames(IEnumerable<SkinnedMeshRenderer> skinnedMeshRenderers)
{
var renames = new List<RenameInfo>();
int suffixCount = 0;
var existingNames = skinnedMeshRenderers.Select(smr => smr.name).ToImmutableHashSet();
foreach (var skinnedMeshRenderer in skinnedMeshRenderers)
{
if (skinnedMeshRenderer.name == "Body")
{
string candidateName;
do
{
candidateName = "Body (Original)" + (suffixCount > 0 ? $" ({suffixCount})" : "");
suffixCount++;
}
while (existingNames.Contains(candidateName));
renames.Add(new RenameInfo { skinnedMeshRenderer = skinnedMeshRenderer, newName = candidateName });
}
}
return renames;
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading