mirror of
https://github.com/aelurum/AssetStudio.git
synced 2025-05-25 05:40:21 -04:00
Add split object fbx export to CLI
This commit is contained in:
parent
5ac597c935
commit
7cca301f7a
@ -13,6 +13,7 @@ namespace AssetStudioCLI
|
||||
public ClassIDType Type;
|
||||
public string Text;
|
||||
public string UniqueID;
|
||||
public GameObjectNode Node;
|
||||
|
||||
public AssetItem(Object asset)
|
||||
{
|
||||
|
16
AssetStudioCLI/Components/BaseNode.cs
Normal file
16
AssetStudioCLI/Components/BaseNode.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using AssetStudio;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AssetStudioCLI
|
||||
{
|
||||
internal class BaseNode
|
||||
{
|
||||
public List<BaseNode> nodes = new List<BaseNode>();
|
||||
|
||||
public BaseNode()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
16
AssetStudioCLI/Components/GameObjectNode.cs
Normal file
16
AssetStudioCLI/Components/GameObjectNode.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using AssetStudio;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AssetStudioCLI
|
||||
{
|
||||
internal class GameObjectNode : BaseNode
|
||||
{
|
||||
public GameObject gameObject;
|
||||
|
||||
public GameObjectNode(GameObject gameObject)
|
||||
{
|
||||
this.gameObject = gameObject;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
using AssetStudio;
|
||||
using AssetStudioCLI.Options;
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@ -264,6 +265,34 @@ namespace AssetStudioCLI
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void ExportGameObject(GameObject gameObject, string exportPath, List<AssetItem> animationList = null)
|
||||
{
|
||||
var convert = animationList != null
|
||||
? new ModelConverter(gameObject, CLIOptions.o_imageFormat.Value, animationList.Select(x => (AnimationClip)x.Asset).ToArray())
|
||||
: new ModelConverter(gameObject, CLIOptions.o_imageFormat.Value);
|
||||
exportPath = exportPath + FixFileName(gameObject.m_Name) + ".fbx";
|
||||
ExportFbx(convert, exportPath);
|
||||
}
|
||||
|
||||
private static void ExportFbx(IImported convert, string exportPath)
|
||||
{
|
||||
var eulerFilter = true;
|
||||
var filterPrecision = (float)0.25f;
|
||||
var exportAllNodes = true;
|
||||
var exportSkins = true;
|
||||
var exportAnimations = true;
|
||||
var exportBlendShape = true;
|
||||
var castToBone = false;
|
||||
var boneSize = CLIOptions.o_fbxBoneSize.Value;
|
||||
var exportAllUvsAsDiffuseMaps = false;
|
||||
var scaleFactor = CLIOptions.o_fbxScaleFactor.Value;
|
||||
var fbxVersion = 3;
|
||||
var fbxFormat = 0;
|
||||
ModelExporter.ExportFbx(exportPath, convert, eulerFilter, filterPrecision,
|
||||
exportAllNodes, exportSkins, exportAnimations, exportBlendShape, castToBone, boneSize, exportAllUvsAsDiffuseMaps, scaleFactor, fbxVersion, fbxFormat == 1);
|
||||
}
|
||||
|
||||
|
||||
public static bool ExportDumpFile(AssetItem item, string exportPath)
|
||||
{
|
||||
if (!TryExportFile(exportPath, item, ".txt", out var exportFullPath))
|
||||
|
@ -12,6 +12,7 @@ namespace AssetStudioCLI.Options
|
||||
General,
|
||||
Convert,
|
||||
Logger,
|
||||
FBX,
|
||||
Advanced,
|
||||
}
|
||||
|
||||
@ -22,6 +23,7 @@ namespace AssetStudioCLI.Options
|
||||
Dump,
|
||||
Info,
|
||||
ExportLive2D,
|
||||
SplitObjects,
|
||||
}
|
||||
|
||||
internal enum AssetGroupOption
|
||||
@ -79,6 +81,9 @@ namespace AssetStudioCLI.Options
|
||||
public static bool convertTexture;
|
||||
public static Option<ImageFormat> o_imageFormat;
|
||||
public static Option<AudioFormat> o_audioFormat;
|
||||
//fbx
|
||||
public static Option<float> o_fbxScaleFactor;
|
||||
public static Option<int> o_fbxBoneSize;
|
||||
//advanced
|
||||
public static Option<ExportListType> o_exportAssetList;
|
||||
public static Option<List<string>> o_filterByName;
|
||||
@ -158,6 +163,7 @@ namespace AssetStudioCLI.Options
|
||||
"Dump - Makes asset dumps\n" +
|
||||
"Info - Loads file(s), shows the number of available for export assets and exits\n" +
|
||||
"Live2D - Exports Live2D Cubism 3 models\n" +
|
||||
"SplitObjects - Export split objects\n" +
|
||||
"Example: \"-m info\"\n",
|
||||
optionHelpGroup: HelpGroups.General
|
||||
);
|
||||
@ -249,6 +255,27 @@ namespace AssetStudioCLI.Options
|
||||
);
|
||||
#endregion
|
||||
|
||||
#region Init FBX Options
|
||||
o_fbxScaleFactor = new GroupedOption<float>
|
||||
(
|
||||
optionDefaultValue: 1f,
|
||||
optionName: "--fbx-scale-factor <value>",
|
||||
optionDescription: "Specify the FBX Scale Factor\n" +
|
||||
"<Value: float number from 0 to 100 (default=1)\n" +
|
||||
"Example: \"--fbx-scale-factor 50\"\n",
|
||||
optionHelpGroup: HelpGroups.FBX
|
||||
);
|
||||
o_fbxBoneSize = new GroupedOption<int>
|
||||
(
|
||||
optionDefaultValue: 10,
|
||||
optionName: "--fbx-bone-size <value>",
|
||||
optionDescription: "Specify the FBX Bone Size\n" +
|
||||
"<Value: integer number from 0 to 100 (default=10)\n" +
|
||||
"Example: \"--fbx-bone-size 10\"",
|
||||
optionHelpGroup: HelpGroups.FBX
|
||||
);
|
||||
#endregion
|
||||
|
||||
#region Init Advanced Options
|
||||
o_exportAssetList = new GroupedOption<ExportListType>
|
||||
(
|
||||
@ -421,6 +448,19 @@ namespace AssetStudioCLI.Options
|
||||
ClassIDType.Transform,
|
||||
};
|
||||
break;
|
||||
case "splitobjects":
|
||||
o_workMode.Value = WorkMode.SplitObjects;
|
||||
o_exportAssetTypes.Value = new List<ClassIDType>()
|
||||
{
|
||||
ClassIDType.GameObject,
|
||||
ClassIDType.Texture2D,
|
||||
ClassIDType.Material,
|
||||
ClassIDType.Transform,
|
||||
ClassIDType.Mesh,
|
||||
ClassIDType.MeshRenderer,
|
||||
ClassIDType.MeshFilter
|
||||
};
|
||||
break;
|
||||
default:
|
||||
Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported working mode: [{value.Color(brightRed)}].\n");
|
||||
Console.WriteLine(o_workMode.Description);
|
||||
@ -617,6 +657,32 @@ namespace AssetStudioCLI.Options
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case "--fbx-scale-factor":
|
||||
var isFloat = float.TryParse(value, out float floatValue);
|
||||
if (isFloat && floatValue >= 0 && floatValue <= 100)
|
||||
{
|
||||
o_fbxScaleFactor.Value = floatValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported scale factor value: [{value.Color(brightRed)}].\n");
|
||||
Console.WriteLine(o_fbxScaleFactor.Description);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case "--fbx-bone-size":
|
||||
var isInt = int.TryParse(value, out int intValue);
|
||||
if (isInt && intValue >= 0 && intValue <= 100)
|
||||
{
|
||||
o_fbxBoneSize.Value = intValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"{"Error".Color(brightRed)} during parsing [{option}] option. Unsupported bone size value: [{value.Color(brightRed)}].\n");
|
||||
Console.WriteLine(o_fbxBoneSize.Description);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case "--export-asset-list":
|
||||
switch (value.ToLower())
|
||||
{
|
||||
|
@ -51,6 +51,9 @@ namespace AssetStudioCLI
|
||||
case WorkMode.ExportLive2D:
|
||||
Studio.ExportLive2D();
|
||||
break;
|
||||
case WorkMode.SplitObjects:
|
||||
Studio.ExportSplitObjects();
|
||||
break;
|
||||
default:
|
||||
Studio.ExportAssets();
|
||||
break;
|
||||
|
@ -24,6 +24,7 @@ General Options:
|
||||
Dump - Makes asset dumps
|
||||
Info - Loads file(s), shows the number of available for export assets and exits
|
||||
Live2D - Exports Live2D Cubism 3 models
|
||||
SplitObjects - Export split objects
|
||||
Example: "-m info"
|
||||
|
||||
-t, --asset-type <value(s)> Specify asset type(s) to export
|
||||
@ -67,6 +68,15 @@ Convert Options:
|
||||
None - Do not convert audios and export them in their own format
|
||||
Example: "--audio-format wav"
|
||||
|
||||
FBX Options:
|
||||
--fbx-scale-factor <value> Specify the FBX Scale Factor
|
||||
<Value: float number from 0 to 100 (default=1)
|
||||
Example: "--fbx-scale-factor 50"
|
||||
|
||||
--fbx-bone-size <value> Specify the FBX Bone Size
|
||||
<Value: integer number from 0 to 100 (default=10)
|
||||
Example: "--fbx-bone-size 10"
|
||||
|
||||
Advanced Options:
|
||||
--export-asset-list <value> Specify the format in which you want to export asset list
|
||||
<Value: none(default) | xml>
|
||||
|
@ -15,6 +15,7 @@ namespace AssetStudioCLI
|
||||
{
|
||||
public static AssetsManager assetsManager = new AssetsManager();
|
||||
public static List<AssetItem> parsedAssetsList = new List<AssetItem>();
|
||||
public static List<BaseNode> gameObjectTree = new List<BaseNode>();
|
||||
public static AssemblyLoader assemblyLoader = new AssemblyLoader();
|
||||
private static Dictionary<AssetStudio.Object, string> containers = new Dictionary<AssetStudio.Object, string>();
|
||||
|
||||
@ -53,6 +54,7 @@ namespace AssetStudioCLI
|
||||
|
||||
var fileAssetsList = new List<AssetItem>();
|
||||
var objectCount = assetsManager.assetsFileList.Sum(x => x.Objects.Count);
|
||||
var objectAssetItemDic = new Dictionary<AssetStudio.Object, AssetItem>(objectCount);
|
||||
|
||||
Progress.Reset();
|
||||
var i = 0;
|
||||
@ -61,6 +63,7 @@ namespace AssetStudioCLI
|
||||
foreach (var asset in assetsFile.Objects)
|
||||
{
|
||||
var assetItem = new AssetItem(asset);
|
||||
objectAssetItemDic.Add(asset, assetItem);
|
||||
assetItem.UniqueID = "_#" + i;
|
||||
var isExportable = false;
|
||||
switch (asset)
|
||||
@ -90,6 +93,9 @@ namespace AssetStudioCLI
|
||||
}
|
||||
}
|
||||
break;
|
||||
case GameObject m_GameObject:
|
||||
assetItem.Text = m_GameObject.m_Name;
|
||||
break;
|
||||
case Texture2D m_Texture2D:
|
||||
if (!string.IsNullOrEmpty(m_Texture2D.m_StreamData?.path))
|
||||
assetItem.FullSize = asset.byteSize + m_Texture2D.m_StreamData.size;
|
||||
@ -153,6 +159,12 @@ namespace AssetStudioCLI
|
||||
containers.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (CLIOptions.o_workMode.Value == WorkMode.SplitObjects)
|
||||
{
|
||||
BuildTreeStructure(objectAssetItemDic);
|
||||
}
|
||||
|
||||
var log = $"Finished loading {assetsManager.assetsFileList.Count} files with {parsedAssetsList.Count} exportable assets";
|
||||
var unityVer = assetsManager.assetsFileList[0].version;
|
||||
long m_ObjectsCount;
|
||||
@ -175,6 +187,93 @@ namespace AssetStudioCLI
|
||||
Logger.Info(log);
|
||||
}
|
||||
|
||||
public static void BuildTreeStructure(Dictionary<AssetStudio.Object, AssetItem> objectAssetItemDic)
|
||||
{
|
||||
foreach (var entry in containers)
|
||||
{
|
||||
objectAssetItemDic[entry.Key].Container = entry.Value;
|
||||
}
|
||||
|
||||
containers.Clear();
|
||||
|
||||
Logger.Info("Building tree structure...");
|
||||
|
||||
var treeNodeDictionary = new Dictionary<GameObject, GameObjectNode>();
|
||||
var assetsFileCount = assetsManager.assetsFileList.Count;
|
||||
int j = 0;
|
||||
Progress.Reset();
|
||||
foreach (var assetsFile in assetsManager.assetsFileList)
|
||||
{
|
||||
var fileNode = new BaseNode();
|
||||
|
||||
foreach (var obj in assetsFile.Objects)
|
||||
{
|
||||
if (obj is GameObject m_GameObject)
|
||||
{
|
||||
if (!treeNodeDictionary.TryGetValue(m_GameObject, out var currentNode))
|
||||
{
|
||||
currentNode = new GameObjectNode(m_GameObject);
|
||||
treeNodeDictionary.Add(m_GameObject, currentNode);
|
||||
}
|
||||
|
||||
foreach (var pptr in m_GameObject.m_Components)
|
||||
{
|
||||
if (pptr.TryGet(out var m_Component))
|
||||
{
|
||||
objectAssetItemDic[m_Component].Node = currentNode;
|
||||
if (m_Component is MeshFilter m_MeshFilter)
|
||||
{
|
||||
if (m_MeshFilter.m_Mesh.TryGet(out var m_Mesh))
|
||||
{
|
||||
objectAssetItemDic[m_Mesh].Node = currentNode;
|
||||
}
|
||||
}
|
||||
else if (m_Component is SkinnedMeshRenderer m_SkinnedMeshRenderer)
|
||||
{
|
||||
if (m_SkinnedMeshRenderer.m_Mesh.TryGet(out var m_Mesh))
|
||||
{
|
||||
objectAssetItemDic[m_Mesh].Node = currentNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var parentNode = fileNode;
|
||||
|
||||
if (m_GameObject.m_Transform != null)
|
||||
{
|
||||
if (m_GameObject.m_Transform.m_Father.TryGet(out var m_Father))
|
||||
{
|
||||
if (m_Father.m_GameObject.TryGet(out var parentGameObject))
|
||||
{
|
||||
if (!treeNodeDictionary.TryGetValue(parentGameObject, out var parentGameObjectNode))
|
||||
{
|
||||
parentGameObjectNode = new GameObjectNode(parentGameObject);
|
||||
treeNodeDictionary.Add(parentGameObject, parentGameObjectNode);
|
||||
}
|
||||
parentNode = parentGameObjectNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parentNode.nodes.Add(currentNode);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (fileNode.nodes.Count > 0)
|
||||
{
|
||||
gameObjectTree.Add(fileNode);
|
||||
}
|
||||
|
||||
Progress.Report(++j, assetsFileCount);
|
||||
}
|
||||
|
||||
treeNodeDictionary.Clear();
|
||||
|
||||
objectAssetItemDic.Clear();
|
||||
}
|
||||
|
||||
public static void ShowExportableAssetsInfo()
|
||||
{
|
||||
var exportableAssetsCountDict = new Dictionary<ClassIDType, int>();
|
||||
@ -403,6 +502,67 @@ namespace AssetStudioCLI
|
||||
Logger.Info($"Finished exporting asset list with {parsedAssetsList.Count} items.");
|
||||
}
|
||||
|
||||
public static void ExportSplitObjects()
|
||||
{
|
||||
var savePath = CLIOptions.o_outputFolder.Value;
|
||||
var count = parsedAssetsList.Count();
|
||||
int k = 0;
|
||||
Progress.Reset();
|
||||
foreach (var node in gameObjectTree)
|
||||
{
|
||||
foreach(GameObjectNode j in node.nodes)
|
||||
{
|
||||
var gameObjects = new List<GameObject>();
|
||||
CollectNode(j, gameObjects);
|
||||
|
||||
if (gameObjects.All(x => x.m_SkinnedMeshRenderer == null && x.m_MeshFilter == null))
|
||||
{
|
||||
Progress.Report(++k, count);
|
||||
continue;
|
||||
}
|
||||
|
||||
var filename = FixFileName(j.gameObject.m_Name);
|
||||
var targetPath = $"{savePath}{filename}{Path.DirectorySeparatorChar}";
|
||||
//重名文件处理
|
||||
for (int i = 1; ; i++)
|
||||
{
|
||||
if (Directory.Exists(targetPath))
|
||||
{
|
||||
targetPath = $"{savePath}{filename} ({i}){Path.DirectorySeparatorChar}";
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
Directory.CreateDirectory(targetPath);
|
||||
//导出FBX
|
||||
Logger.Info($"Exporting {filename}.fbx");
|
||||
try
|
||||
{
|
||||
ExportGameObject(j.gameObject, targetPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error($"Export GameObject:{j.gameObject.m_Name} error", ex);
|
||||
}
|
||||
|
||||
Progress.Report(++k, count);
|
||||
Logger.Info($"Finished exporting {filename}.fbx");
|
||||
}
|
||||
}
|
||||
Logger.Info("Finished");
|
||||
}
|
||||
|
||||
private static void CollectNode(GameObjectNode node, List<GameObject> gameObjects)
|
||||
{
|
||||
gameObjects.Add(node.gameObject);
|
||||
foreach (GameObjectNode i in node.nodes)
|
||||
{
|
||||
CollectNode(i, gameObjects);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ExportLive2D()
|
||||
{
|
||||
var baseDestPath = Path.Combine(CLIOptions.o_outputFolder.Value, "Live2DOutput");
|
||||
|
Loading…
Reference in New Issue
Block a user