Fixes for Live2D export

- Fixed export of live2d models from assets without containers.
- Improved method of binding model-related assets.
This commit is contained in:
VaDiM
2025-06-12 01:53:59 +03:00
parent 3a25ed9ccd
commit 7b7eac62d8
5 changed files with 181 additions and 160 deletions

View File

@ -1265,6 +1265,7 @@ namespace AssetStudioGUI
sb.AppendLine($"Pixel Per Unit: {cubismMoc.PixelPerUnit}");
sb.AppendLine($"Parameter Count: {cubismMoc.ParamCount}");
sb.AppendLine($"Part Count: {cubismMoc.PartCount}");
sb.AppendLine($"Pre-linked AnimationClips: {model?.ClipMotionList.Count}");
}
assetItem.InfoText = sb.ToString();
}

View File

@ -14,6 +14,7 @@ using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml.Linq;
using static AssetStudioGUI.Exporter;
using static CubismLive2DExtractor.CubismParsers;
using Object = AssetStudio.Object;
namespace AssetStudioGUI
@ -300,16 +301,16 @@ namespace AssetStudioGUI
}
break;
case "CubismRenderer":
BindCubismRenderer(m_MonoBehaviour);
BindCubismAsset(m_MonoBehaviour, CubismMonoBehaviourType.RenderTexture);
break;
case "CubismDisplayInfoParameterName":
BindParamDisplayInfo(m_MonoBehaviour);
BindCubismAsset(m_MonoBehaviour, CubismMonoBehaviourType.DisplayInfo, isParamInfo: true);
break;
case "CubismDisplayInfoPartName":
BindPartDisplayInfo(m_MonoBehaviour);
BindCubismAsset(m_MonoBehaviour, CubismMonoBehaviourType.DisplayInfo);
break;
case "CubismPosePart":
BindCubismPosePart(m_MonoBehaviour);
BindCubismAsset(m_MonoBehaviour, CubismMonoBehaviourType.PosePart);
break;
}
}
@ -1030,51 +1031,28 @@ namespace AssetStudioGUI
return mocPPtr.TryGet(out mocMono);
}
private static void BindCubismRenderer(MonoBehaviour m_MonoBehaviour)
{
if (!m_MonoBehaviour.m_GameObject.TryGet(out var m_GameObject))
return;
var rootTransform = GetRootTransform(m_GameObject.m_Transform);
if (rootTransform.m_GameObject.TryGet(out var rootGameObject) && rootGameObject.CubismModel != null)
{
rootGameObject.CubismModel.RenderTextureList.Add(m_MonoBehaviour);
}
}
private static void BindParamDisplayInfo(MonoBehaviour m_MonoBehaviour)
private static void BindCubismAsset(MonoBehaviour m_MonoBehaviour, CubismMonoBehaviourType type, bool isParamInfo = false)
{
if (!m_MonoBehaviour.m_GameObject.TryGet(out var m_GameObject))
return;
var rootTransform = GetRootTransform(m_GameObject.m_Transform);
if (rootTransform.m_GameObject.TryGet(out var rootGameObject) && rootGameObject.CubismModel != null)
{
rootGameObject.CubismModel.ParamDisplayInfoList.Add(m_MonoBehaviour);
}
}
private static void BindPartDisplayInfo(MonoBehaviour m_MonoBehaviour)
{
if (!m_MonoBehaviour.m_GameObject.TryGet(out var m_GameObject))
if (!TryGetModelGameObject(m_GameObject.m_Transform, out var modelGameObject))
return;
var rootTransform = GetRootTransform(m_GameObject.m_Transform);
if (rootTransform.m_GameObject.TryGet(out var rootGameObject) && rootGameObject.CubismModel != null)
switch (type)
{
rootGameObject.CubismModel.PartDisplayInfoList.Add(m_MonoBehaviour);
}
}
private static void BindCubismPosePart(MonoBehaviour m_MonoBehaviour)
{
if (!m_MonoBehaviour.m_GameObject.TryGet(out var m_GameObject))
return;
var rootTransform = GetRootTransform(m_GameObject.m_Transform);
if (rootTransform.m_GameObject.TryGet(out var rootGameObject) && rootGameObject.CubismModel != null)
{
rootGameObject.CubismModel.PosePartList.Add(m_MonoBehaviour);
case CubismMonoBehaviourType.PosePart:
modelGameObject.CubismModel.PosePartList.Add(m_MonoBehaviour);
break;
case CubismMonoBehaviourType.DisplayInfo when isParamInfo:
modelGameObject.CubismModel.ParamDisplayInfoList.Add(m_MonoBehaviour);
break;
case CubismMonoBehaviourType.DisplayInfo:
modelGameObject.CubismModel.PartDisplayInfoList.Add(m_MonoBehaviour);
break;
case CubismMonoBehaviourType.RenderTexture:
modelGameObject.CubismModel.RenderTextureList.Add(m_MonoBehaviour);
break;
}
}
@ -1106,31 +1084,41 @@ namespace AssetStudioGUI
}
}
private static Transform GetRootTransform(Transform m_Transform)
private static bool TryGetModelGameObject(Transform m_Transform, out GameObject m_GameObject)
{
m_GameObject = null;
if (m_Transform == null)
return null;
return false;
while (m_Transform.m_Father.TryGet(out var m_Father))
{
m_Transform = m_Father;
if (m_Transform.m_GameObject.TryGet(out m_GameObject) && m_GameObject.CubismModel != null)
{
return true;
}
}
return m_Transform;
return false;
}
private static Dictionary<MonoBehaviour, string> GenerateMocPathDict(Dictionary<MonoBehaviour, CubismModel> mocDict, Dictionary<Object, string> assetContainers, bool searchByFilename)
private static Dictionary<MonoBehaviour, string> GenerateMocPathDict(Dictionary<MonoBehaviour, CubismModel> mocDict, bool searchByFilename)
{
var tempMocPathDict = new Dictionary<MonoBehaviour, (string, string)>();
var mocPathDict = new Dictionary<MonoBehaviour, string>();
foreach (var mocMono in l2dModelDict.Keys)
{
if (!l2dAssetContainers.TryGetValue(mocMono, out var fullContainerPath))
continue;
var pathSepIndex = fullContainerPath.LastIndexOf('/');
var basePath = pathSepIndex > 0
? fullContainerPath.Substring(0, pathSepIndex)
: fullContainerPath;
tempMocPathDict.Add(mocMono, (fullContainerPath, basePath));
if (l2dAssetContainers.TryGetValue(mocMono, out var fullContainerPath))
{
var pathSepIndex = fullContainerPath.LastIndexOf('/');
var basePath = pathSepIndex > 0
? fullContainerPath.Substring(0, pathSepIndex)
: fullContainerPath;
tempMocPathDict.Add(mocMono, (fullContainerPath, basePath));
}
else if (searchByFilename)
{
tempMocPathDict.Add(mocMono, (mocMono.assetsFile.fullName, mocMono.assetsFile.fullName));
}
}
if (tempMocPathDict.Count > 0)
@ -1144,7 +1132,7 @@ namespace AssetStudioGUI
: tempMocPathDict[moc].Item2; //basePath
if (searchByFilename)
{
mocPathDict.Add(moc, assetContainers[moc]);
mocPathDict.Add(moc, moc.assetsFile.fullName);
if (mocDict.TryGetValue(moc, out var model) && model != null)
model.Container = mocPath;
}
@ -1178,24 +1166,36 @@ namespace AssetStudioGUI
ThreadPool.QueueUserWorkItem(state =>
{
Logger.Info("Searching for Live2D assets...");
var mode = searchByFilename ? "file names" : "containers";
Logger.Info($"Searching for Live2D assets using {mode}...");
var mocPathDict = GenerateMocPathDict(mocDict, searchByFilename);
if (!searchByFilename && mocPathDict.Count != l2dModelDict.Count)
{
Logger.Warning("Some Live2D models cannot be exported using containers\nTry to enable search by file name in the options");
}
if (searchByFilename)
{
foreach (var assetKvp in l2dAssetContainers)
foreach (var asset in exportableAssets)
{
var container = string.IsNullOrEmpty(assetKvp.Key.assetsFile.originalPath)
? assetKvp.Key.assetsFile.fullName
: assetKvp.Key.assetsFile.originalPath;
l2dContainers[assetKvp.Key] = container;
switch (asset.Type)
{
case ClassIDType.AnimationClip:
case ClassIDType.Texture2D:
case ClassIDType.MonoBehaviour:
l2dContainers[asset.Asset] = asset.Asset.assetsFile.fullName;
break;
}
}
}
var mocPathDict = GenerateMocPathDict(mocDict, l2dContainers, searchByFilename);
var assetDict = new Dictionary<MonoBehaviour, List<Object>>();
foreach (var mocKvp in mocPathDict)
{
var mocPath = mocKvp.Value;
var mocPath = searchByFilename
? mocKvp.Key.assetsFile.fullName
: mocKvp.Value;
var result = l2dContainers.Select(assetKvp =>
{
if (!assetKvp.Value.Contains(mocPath))
@ -1223,7 +1223,7 @@ namespace AssetStudioGUI
assetDict[mocKvp.Key] = result;
}
}
if (searchByFilename)
l2dContainers.Clear();
if (mocDict.Keys.First().serializedType?.m_Type == null && !assemblyLoader.Loaded)
@ -1231,7 +1231,6 @@ namespace AssetStudioGUI
Logger.Warning("Specifying the assembly folder may be needed for proper extraction");
SelectAssemblyFolder();
}
var totalModelCount = assetDict.Count;
var modelCounter = 0;
var parallelExportCount = Properties.Settings.Default.parallelExportCount <= 0
@ -1242,15 +1241,22 @@ namespace AssetStudioGUI
Live2DExtractor.Assembly = assemblyLoader;
foreach (var assetGroupKvp in assetDict)
{
var srcContainer = l2dAssetContainers[assetGroupKvp.Key];
var srcContainer = l2dAssetContainers.TryGetValue(assetGroupKvp.Key, out var result)
? result
: assetGroupKvp.Key.assetsFile.fullName;
Logger.Info($"[{modelCounter + 1}/{totalModelCount}] Exporting Live2D: \"{srcContainer}\"...");
Logger.Info($"[{modelCounter + 1}/{totalModelCount}] Exporting Live2D from: \"{srcContainer}\"...");
try
{
var cubismExtractor = new Live2DExtractor(assetGroupKvp, selClipMotions, selFadeMotions, selFadeLst);
var filename = string.IsNullOrEmpty(cubismExtractor.MocMono.assetsFile.originalPath)
? Path.GetFileNameWithoutExtension(cubismExtractor.MocMono.assetsFile.fileName)
: Path.GetFileNameWithoutExtension(cubismExtractor.MocMono.assetsFile.originalPath);
var modelName = !string.IsNullOrEmpty(cubismExtractor.Model?.Name)
? cubismExtractor.Model.Name
: filename;
Logger.Info($"Model name: \"{modelName}\"");
string modelPath;
switch (modelGroupOption)
{
@ -1258,21 +1264,21 @@ namespace AssetStudioGUI
modelPath = filename;
break;
case Live2DModelGroupOption.ModelName:
modelPath = !string.IsNullOrEmpty(cubismExtractor.Model?.Name)
? cubismExtractor.Model.Name
: filename;
modelPath = modelName;
break;
default: //ContainerPath
var container = searchByFilename && cubismExtractor.Model != null
? cubismExtractor.Model.Container
: srcContainer;
container = container == assetGroupKvp.Key.assetsFile.fullName
? filename
: container;
modelPath = Path.HasExtension(container)
? container.Replace(Path.GetExtension(container), "")
: container;
break;
}
var destPath = Path.Combine(baseDestPath, modelPath) + Path.DirectorySeparatorChar;
var destPath = Path.Combine(baseDestPath, modelPath);
cubismExtractor.ExtractCubismModel(destPath, motionMode, forceBezier, parallelExportCount);
modelCounter++;
}