feat: ActorCore 모션 배치 import 자동화 추가
- MotionBatch_20260424에 Motion* 후보 FBX 10개를 import하고 Humanoid 리그를 정리 - Blender 기반 ActorCore 101본 정규화 스크립트와 Unity manifest 적용 메뉴 추가 - orc-stomp 단일 애셋의 extra root 리그를 정규화하고 사용 문서와 manifest를 보강 - Unity 컴파일, ApplyManifest 실제 실행, 콘솔 경고/오류 없음으로 검증
This commit is contained in:
8
Assets/External/Animations/ActorCore/MotionBatch_20260424.meta
vendored
Normal file
8
Assets/External/Animations/ActorCore/MotionBatch_20260424.meta
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9479e994fffecfc14973a2bb6331f1e2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
100
Assets/External/Animations/ActorCore/MotionBatch_20260424/_actorcore_motion_import_manifest.json
vendored
Normal file
100
Assets/External/Animations/ActorCore/MotionBatch_20260424/_actorcore_motion_import_manifest.json
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"schema": "colosseum.actorcoreMotionBatchImport@1",
|
||||
"createdAtUtc": "2026-04-24T05:33:29.240646+00:00",
|
||||
"projectRoot": "/home/dal4segno/Colosseum",
|
||||
"referenceFbx": "Assets/External/Animations/ActorCore/orc-jog.fbx",
|
||||
"actorCoreAvatar": "Assets/External/Animations/ActorCore/ActorCoreAvatar.asset",
|
||||
"targetFolder": "Assets/External/Animations/ActorCore/MotionBatch_20260424",
|
||||
"entries": [
|
||||
{
|
||||
"source": "/home/dal4segno/다운로드/Motion/forward-straight-punch-r.fbx",
|
||||
"asset": "Assets/External/Animations/ActorCore/MotionBatch_20260424/forward-straight-punch-r.fbx",
|
||||
"sha256": "9e49fe2cfc6e316b45d124ab77c4468e8af2b85c50c10d3438be0d1c07122a86",
|
||||
"size": 2007568,
|
||||
"processing": "direct",
|
||||
"unityImportMode": "copy",
|
||||
"clipName": "forward-straight-punch-r"
|
||||
},
|
||||
{
|
||||
"source": "/home/dal4segno/다운로드/Motion (1)/Motion/forward-straight-punch-r-mirror.fbx",
|
||||
"asset": "Assets/External/Animations/ActorCore/MotionBatch_20260424/forward-straight-punch-r-mirror.fbx",
|
||||
"sha256": "664e82f0f5e541de18d75ae99c29edb277fb08e18e24c34edae8a6b37be95ee9",
|
||||
"size": 2007568,
|
||||
"processing": "direct",
|
||||
"unityImportMode": "copy",
|
||||
"clipName": "forward-straight-punch-r-mirror"
|
||||
},
|
||||
{
|
||||
"source": "/home/dal4segno/다운로드/Motion (2)/Motion/uppercut-l.fbx",
|
||||
"asset": "Assets/External/Animations/ActorCore/MotionBatch_20260424/uppercut-l.fbx",
|
||||
"sha256": "f9e1cf0dc3b9ab0d90799ffd67402a82d17e75a5ec6e5162203580b9f814bf65",
|
||||
"size": 1838800,
|
||||
"processing": "direct",
|
||||
"unityImportMode": "copy",
|
||||
"clipName": "uppercut-l"
|
||||
},
|
||||
{
|
||||
"source": "/home/dal4segno/다운로드/Motion (3)/Motion/uppercut-l-mirror.fbx",
|
||||
"asset": "Assets/External/Animations/ActorCore/MotionBatch_20260424/uppercut-l-mirror.fbx",
|
||||
"sha256": "5072e63abdecbd1c30f57d86909199bf4d9c0c06333c81931015f037df796945",
|
||||
"size": 1838800,
|
||||
"processing": "direct",
|
||||
"unityImportMode": "copy",
|
||||
"clipName": "uppercut-l-mirror"
|
||||
},
|
||||
{
|
||||
"source": "/home/dal4segno/다운로드/Motion (4)/Motion/orc-stomp.fbx",
|
||||
"asset": "Assets/External/Animations/ActorCore/MotionBatch_20260424/orc-stomp.fbx",
|
||||
"sha256": "4ef22bbaf3792ebd4d8236c52427be48ab9808521364da33c1892e83077e717b",
|
||||
"size": 2511648,
|
||||
"processing": "normalized",
|
||||
"unityImportMode": "create",
|
||||
"clipName": "orc-stomp"
|
||||
},
|
||||
{
|
||||
"source": "/home/dal4segno/다운로드/Motion (5)/Motion/orc-stomp-mirror.fbx",
|
||||
"asset": "Assets/External/Animations/ActorCore/MotionBatch_20260424/orc-stomp-mirror.fbx",
|
||||
"sha256": "ab6a578b3f9d0ecabf01a6d648e5b6315f129a86a63fa96bd305ec963bb6d048",
|
||||
"size": 2511648,
|
||||
"processing": "normalized",
|
||||
"unityImportMode": "create",
|
||||
"clipName": "orc-stomp-mirror"
|
||||
},
|
||||
{
|
||||
"source": "/home/dal4segno/다운로드/Motion (6)/Motion/orc-chop.fbx",
|
||||
"asset": "Assets/External/Animations/ActorCore/MotionBatch_20260424/orc-chop.fbx",
|
||||
"sha256": "ba8045d2075e7ac867cdd2b701b06ad3f661c41ca64cd078227b7316f77c1b82",
|
||||
"size": 2329712,
|
||||
"processing": "normalized",
|
||||
"unityImportMode": "create",
|
||||
"clipName": "orc-chop"
|
||||
},
|
||||
{
|
||||
"source": "/home/dal4segno/다운로드/Motion (7)/Motion/orc-chop-mirror.fbx",
|
||||
"asset": "Assets/External/Animations/ActorCore/MotionBatch_20260424/orc-chop-mirror.fbx",
|
||||
"sha256": "9151fad6fddd44c5c7f7cbb3ebf529616532db0376bf1cdb2f279c4afb23f69a",
|
||||
"size": 2329712,
|
||||
"processing": "normalized",
|
||||
"unityImportMode": "create",
|
||||
"clipName": "orc-chop-mirror"
|
||||
},
|
||||
{
|
||||
"source": "/home/dal4segno/다운로드/Motion (8)/Motion/uppercut-l.fbx",
|
||||
"asset": "Assets/External/Animations/ActorCore/MotionBatch_20260424/uppercut-l_motion8.fbx",
|
||||
"sha256": "f9e1cf0dc3b9ab0d90799ffd67402a82d17e75a5ec6e5162203580b9f814bf65",
|
||||
"size": 1838800,
|
||||
"processing": "direct",
|
||||
"unityImportMode": "copy",
|
||||
"clipName": "uppercut-l_motion8"
|
||||
},
|
||||
{
|
||||
"source": "/home/dal4segno/다운로드/Motion (9)/Motion/orc-stomp.fbx",
|
||||
"asset": "Assets/External/Animations/ActorCore/MotionBatch_20260424/orc-stomp_motion9.fbx",
|
||||
"sha256": "1d5cb6051038b9c9ac61198a1cc355a9117788b0eec44390974aa48e4e26a260",
|
||||
"size": 1979136,
|
||||
"processing": "normalized",
|
||||
"unityImportMode": "create",
|
||||
"clipName": "orc-stomp_motion9"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 01ff949f13e1241f78f77ebf2b04f8b1
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
62
Assets/External/Animations/ActorCore/MotionBatch_20260424/_motion_import_manifest.json
vendored
Normal file
62
Assets/External/Animations/ActorCore/MotionBatch_20260424/_motion_import_manifest.json
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
[
|
||||
{
|
||||
"source": "/home/dal4segno/다운로드/Motion/forward-straight-punch-r.fbx",
|
||||
"asset": "Assets/External/Animations/ActorCore/MotionBatch_20260424/forward-straight-punch-r.fbx",
|
||||
"sha256": "9e49fe2cfc6e316b45d124ab77c4468e8af2b85c50c10d3438be0d1c07122a86",
|
||||
"size": 2007568
|
||||
},
|
||||
{
|
||||
"source": "/home/dal4segno/다운로드/Motion (1)/Motion/forward-straight-punch-r-mirror.fbx",
|
||||
"asset": "Assets/External/Animations/ActorCore/MotionBatch_20260424/forward-straight-punch-r-mirror.fbx",
|
||||
"sha256": "664e82f0f5e541de18d75ae99c29edb277fb08e18e24c34edae8a6b37be95ee9",
|
||||
"size": 2007568
|
||||
},
|
||||
{
|
||||
"source": "/home/dal4segno/다운로드/Motion (2)/Motion/uppercut-l.fbx",
|
||||
"asset": "Assets/External/Animations/ActorCore/MotionBatch_20260424/uppercut-l.fbx",
|
||||
"sha256": "f9e1cf0dc3b9ab0d90799ffd67402a82d17e75a5ec6e5162203580b9f814bf65",
|
||||
"size": 1838800
|
||||
},
|
||||
{
|
||||
"source": "/home/dal4segno/다운로드/Motion (3)/Motion/uppercut-l-mirror.fbx",
|
||||
"asset": "Assets/External/Animations/ActorCore/MotionBatch_20260424/uppercut-l-mirror.fbx",
|
||||
"sha256": "5072e63abdecbd1c30f57d86909199bf4d9c0c06333c81931015f037df796945",
|
||||
"size": 1838800
|
||||
},
|
||||
{
|
||||
"source": "/home/dal4segno/다운로드/Motion (4)/Motion/orc-stomp.fbx",
|
||||
"asset": "Assets/External/Animations/ActorCore/MotionBatch_20260424/orc-stomp.fbx",
|
||||
"sha256": "4ef22bbaf3792ebd4d8236c52427be48ab9808521364da33c1892e83077e717b",
|
||||
"size": 2511648
|
||||
},
|
||||
{
|
||||
"source": "/home/dal4segno/다운로드/Motion (5)/Motion/orc-stomp-mirror.fbx",
|
||||
"asset": "Assets/External/Animations/ActorCore/MotionBatch_20260424/orc-stomp-mirror.fbx",
|
||||
"sha256": "ab6a578b3f9d0ecabf01a6d648e5b6315f129a86a63fa96bd305ec963bb6d048",
|
||||
"size": 2511648
|
||||
},
|
||||
{
|
||||
"source": "/home/dal4segno/다운로드/Motion (6)/Motion/orc-chop.fbx",
|
||||
"asset": "Assets/External/Animations/ActorCore/MotionBatch_20260424/orc-chop.fbx",
|
||||
"sha256": "ba8045d2075e7ac867cdd2b701b06ad3f661c41ca64cd078227b7316f77c1b82",
|
||||
"size": 2329712
|
||||
},
|
||||
{
|
||||
"source": "/home/dal4segno/다운로드/Motion (7)/Motion/orc-chop-mirror.fbx",
|
||||
"asset": "Assets/External/Animations/ActorCore/MotionBatch_20260424/orc-chop-mirror.fbx",
|
||||
"sha256": "9151fad6fddd44c5c7f7cbb3ebf529616532db0376bf1cdb2f279c4afb23f69a",
|
||||
"size": 2329712
|
||||
},
|
||||
{
|
||||
"source": "/home/dal4segno/다운로드/Motion (8)/Motion/uppercut-l.fbx",
|
||||
"asset": "Assets/External/Animations/ActorCore/MotionBatch_20260424/uppercut-l_motion8.fbx",
|
||||
"sha256": "f9e1cf0dc3b9ab0d90799ffd67402a82d17e75a5ec6e5162203580b9f814bf65",
|
||||
"size": 1838800
|
||||
},
|
||||
{
|
||||
"source": "/home/dal4segno/다운로드/Motion (9)/Motion/orc-stomp.fbx",
|
||||
"asset": "Assets/External/Animations/ActorCore/MotionBatch_20260424/orc-stomp_motion9.fbx",
|
||||
"sha256": "1d5cb6051038b9c9ac61198a1cc355a9117788b0eec44390974aa48e4e26a260",
|
||||
"size": 1979136
|
||||
}
|
||||
]
|
||||
7
Assets/External/Animations/ActorCore/MotionBatch_20260424/_motion_import_manifest.json.meta
vendored
Normal file
7
Assets/External/Animations/ActorCore/MotionBatch_20260424/_motion_import_manifest.json.meta
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 36de6839bfb6ef867a3668282a656d45
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/External/Animations/ActorCore/MotionBatch_20260424/forward-straight-punch-r-mirror.fbx
vendored
Normal file
BIN
Assets/External/Animations/ActorCore/MotionBatch_20260424/forward-straight-punch-r-mirror.fbx
vendored
Normal file
Binary file not shown.
1092
Assets/External/Animations/ActorCore/MotionBatch_20260424/forward-straight-punch-r-mirror.fbx.meta
vendored
Normal file
1092
Assets/External/Animations/ActorCore/MotionBatch_20260424/forward-straight-punch-r-mirror.fbx.meta
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
Assets/External/Animations/ActorCore/MotionBatch_20260424/forward-straight-punch-r.fbx
vendored
Normal file
BIN
Assets/External/Animations/ActorCore/MotionBatch_20260424/forward-straight-punch-r.fbx
vendored
Normal file
Binary file not shown.
1092
Assets/External/Animations/ActorCore/MotionBatch_20260424/forward-straight-punch-r.fbx.meta
vendored
Normal file
1092
Assets/External/Animations/ActorCore/MotionBatch_20260424/forward-straight-punch-r.fbx.meta
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
Assets/External/Animations/ActorCore/MotionBatch_20260424/orc-chop-mirror.fbx
vendored
Normal file
BIN
Assets/External/Animations/ActorCore/MotionBatch_20260424/orc-chop-mirror.fbx
vendored
Normal file
Binary file not shown.
1092
Assets/External/Animations/ActorCore/MotionBatch_20260424/orc-chop-mirror.fbx.meta
vendored
Normal file
1092
Assets/External/Animations/ActorCore/MotionBatch_20260424/orc-chop-mirror.fbx.meta
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
Assets/External/Animations/ActorCore/MotionBatch_20260424/orc-chop.fbx
vendored
Normal file
BIN
Assets/External/Animations/ActorCore/MotionBatch_20260424/orc-chop.fbx
vendored
Normal file
Binary file not shown.
1092
Assets/External/Animations/ActorCore/MotionBatch_20260424/orc-chop.fbx.meta
vendored
Normal file
1092
Assets/External/Animations/ActorCore/MotionBatch_20260424/orc-chop.fbx.meta
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
Assets/External/Animations/ActorCore/MotionBatch_20260424/orc-stomp-mirror.fbx
vendored
Normal file
BIN
Assets/External/Animations/ActorCore/MotionBatch_20260424/orc-stomp-mirror.fbx
vendored
Normal file
Binary file not shown.
1092
Assets/External/Animations/ActorCore/MotionBatch_20260424/orc-stomp-mirror.fbx.meta
vendored
Normal file
1092
Assets/External/Animations/ActorCore/MotionBatch_20260424/orc-stomp-mirror.fbx.meta
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
Assets/External/Animations/ActorCore/MotionBatch_20260424/orc-stomp.fbx
vendored
Normal file
BIN
Assets/External/Animations/ActorCore/MotionBatch_20260424/orc-stomp.fbx
vendored
Normal file
Binary file not shown.
1092
Assets/External/Animations/ActorCore/MotionBatch_20260424/orc-stomp.fbx.meta
vendored
Normal file
1092
Assets/External/Animations/ActorCore/MotionBatch_20260424/orc-stomp.fbx.meta
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
Assets/External/Animations/ActorCore/MotionBatch_20260424/orc-stomp_motion9.fbx
vendored
Normal file
BIN
Assets/External/Animations/ActorCore/MotionBatch_20260424/orc-stomp_motion9.fbx
vendored
Normal file
Binary file not shown.
1092
Assets/External/Animations/ActorCore/MotionBatch_20260424/orc-stomp_motion9.fbx.meta
vendored
Normal file
1092
Assets/External/Animations/ActorCore/MotionBatch_20260424/orc-stomp_motion9.fbx.meta
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
Assets/External/Animations/ActorCore/MotionBatch_20260424/uppercut-l-mirror.fbx
vendored
Normal file
BIN
Assets/External/Animations/ActorCore/MotionBatch_20260424/uppercut-l-mirror.fbx
vendored
Normal file
Binary file not shown.
1092
Assets/External/Animations/ActorCore/MotionBatch_20260424/uppercut-l-mirror.fbx.meta
vendored
Normal file
1092
Assets/External/Animations/ActorCore/MotionBatch_20260424/uppercut-l-mirror.fbx.meta
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
Assets/External/Animations/ActorCore/MotionBatch_20260424/uppercut-l.fbx
vendored
Normal file
BIN
Assets/External/Animations/ActorCore/MotionBatch_20260424/uppercut-l.fbx
vendored
Normal file
Binary file not shown.
1092
Assets/External/Animations/ActorCore/MotionBatch_20260424/uppercut-l.fbx.meta
vendored
Normal file
1092
Assets/External/Animations/ActorCore/MotionBatch_20260424/uppercut-l.fbx.meta
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
Assets/External/Animations/ActorCore/MotionBatch_20260424/uppercut-l_motion8.fbx
vendored
Normal file
BIN
Assets/External/Animations/ActorCore/MotionBatch_20260424/uppercut-l_motion8.fbx
vendored
Normal file
Binary file not shown.
1092
Assets/External/Animations/ActorCore/MotionBatch_20260424/uppercut-l_motion8.fbx.meta
vendored
Normal file
1092
Assets/External/Animations/ActorCore/MotionBatch_20260424/uppercut-l_motion8.fbx.meta
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
Assets/External/Animations/ActorCore/orc-stomp.fbx
vendored
Normal file
BIN
Assets/External/Animations/ActorCore/orc-stomp.fbx
vendored
Normal file
Binary file not shown.
1092
Assets/External/Animations/ActorCore/orc-stomp.fbx.meta
vendored
Normal file
1092
Assets/External/Animations/ActorCore/orc-stomp.fbx.meta
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,340 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
using UnityEditor;
|
||||
|
||||
/// <summary>
|
||||
/// ActorCore 모션 배치 manifest를 읽어 FBX ModelImporter 휴머노이드 설정을 적용합니다.
|
||||
/// Blender 정규화 도구가 생성한 `_actorcore_motion_import_manifest.json`을 대상으로 사용합니다.
|
||||
/// </summary>
|
||||
public static class ActorCoreMotionBatchImportSettings
|
||||
{
|
||||
private const string MenuPath = "Tools/Animation/Apply ActorCore Motion Batch Import Settings";
|
||||
private const string ExpectedSchema = "colosseum.actorcoreMotionBatchImport@1";
|
||||
private const string DefaultActorCoreAvatarPath = "Assets/External/Animations/ActorCore/ActorCoreAvatar.asset";
|
||||
|
||||
[Serializable]
|
||||
private sealed class ActorCoreMotionBatchManifest
|
||||
{
|
||||
public string schema;
|
||||
public string createdAtUtc;
|
||||
public string referenceFbx;
|
||||
public string actorCoreAvatar;
|
||||
public string targetFolder;
|
||||
public List<ActorCoreMotionBatchEntry> entries;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private sealed class ActorCoreMotionBatchEntry
|
||||
{
|
||||
public string source;
|
||||
public string asset;
|
||||
public string sha256;
|
||||
public long size;
|
||||
public string processing;
|
||||
public string unityImportMode;
|
||||
public string clipName;
|
||||
public string[] removedBones;
|
||||
public string[] sourceActions;
|
||||
public string error;
|
||||
}
|
||||
|
||||
private struct ImportValidation
|
||||
{
|
||||
public bool success;
|
||||
public int transformCount;
|
||||
public bool hasArmatureExtraRoot;
|
||||
public string rootParent;
|
||||
public string[] humanClips;
|
||||
public string[] avatars;
|
||||
public string message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 선택된 manifest 또는 파일 선택창의 manifest를 적용합니다.
|
||||
/// </summary>
|
||||
[MenuItem(MenuPath, false, 26)]
|
||||
public static void ApplyFromManifestMenu()
|
||||
{
|
||||
if (EditorApplication.isPlayingOrWillChangePlaymode)
|
||||
{
|
||||
EditorUtility.DisplayDialog("ActorCore Motion Batch", "플레이 모드에서는 import 설정을 변경하지 않습니다.", "확인");
|
||||
return;
|
||||
}
|
||||
|
||||
string manifestPath = GetManifestPathFromSelectionOrDialog();
|
||||
if (string.IsNullOrEmpty(manifestPath))
|
||||
return;
|
||||
|
||||
ApplyManifest(manifestPath, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// manifest 경로를 받아 배치 import 설정을 적용합니다.
|
||||
/// </summary>
|
||||
public static bool ApplyManifest(string manifestPath, bool showDialog)
|
||||
{
|
||||
if (!TryLoadManifest(manifestPath, out ActorCoreMotionBatchManifest manifest))
|
||||
return false;
|
||||
|
||||
string avatarPath = string.IsNullOrEmpty(manifest.actorCoreAvatar)
|
||||
? DefaultActorCoreAvatarPath
|
||||
: manifest.actorCoreAvatar;
|
||||
Avatar actorCoreAvatar = AssetDatabase.LoadAssetAtPath<Avatar>(avatarPath);
|
||||
|
||||
if (actorCoreAvatar == null && HasCopyEntries(manifest))
|
||||
{
|
||||
Debug.LogError($"[ActorCoreMotionBatchImport] ActorCoreAvatar를 찾지 못했습니다: {avatarPath}");
|
||||
return false;
|
||||
}
|
||||
|
||||
int appliedCount = 0;
|
||||
int fallbackCount = 0;
|
||||
int failedCount = 0;
|
||||
List<string> reportLines = new List<string>();
|
||||
|
||||
foreach (ActorCoreMotionBatchEntry entry in manifest.entries ?? new List<ActorCoreMotionBatchEntry>())
|
||||
{
|
||||
if (entry == null || string.IsNullOrEmpty(entry.asset))
|
||||
continue;
|
||||
|
||||
if (string.Equals(entry.unityImportMode, "skip", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
failedCount++;
|
||||
reportLines.Add($"SKIP {entry.asset}: {entry.error}");
|
||||
continue;
|
||||
}
|
||||
|
||||
ModelImporter importer = AssetImporter.GetAtPath(entry.asset) as ModelImporter;
|
||||
if (importer == null)
|
||||
{
|
||||
failedCount++;
|
||||
reportLines.Add($"FAIL {entry.asset}: ModelImporter 없음");
|
||||
continue;
|
||||
}
|
||||
|
||||
string requestedMode = NormalizeImportMode(entry.unityImportMode);
|
||||
ApplyImporterSettings(entry, importer, requestedMode, actorCoreAvatar);
|
||||
ImportValidation validation = ValidateImportedAsset(entry.asset, requestedMode);
|
||||
|
||||
if (!validation.success && requestedMode == "copy")
|
||||
{
|
||||
ModelImporter fallbackImporter = AssetImporter.GetAtPath(entry.asset) as ModelImporter;
|
||||
ApplyImporterSettings(entry, fallbackImporter, "create", actorCoreAvatar);
|
||||
validation = ValidateImportedAsset(entry.asset, "create");
|
||||
fallbackCount++;
|
||||
requestedMode = "create";
|
||||
}
|
||||
|
||||
if (validation.success)
|
||||
{
|
||||
appliedCount++;
|
||||
reportLines.Add($"OK {requestedMode.ToUpperInvariant()} {Path.GetFileName(entry.asset)} clips=[{string.Join(", ", validation.humanClips)}] rootParent={validation.rootParent}");
|
||||
}
|
||||
else
|
||||
{
|
||||
failedCount++;
|
||||
reportLines.Add($"FAIL {Path.GetFileName(entry.asset)}: {validation.message}");
|
||||
}
|
||||
}
|
||||
|
||||
string summary = $"적용 {appliedCount}개, fallback {fallbackCount}개, 실패 {failedCount}개";
|
||||
Debug.Log($"[ActorCoreMotionBatchImport] {summary}\n{string.Join("\n", reportLines)}");
|
||||
|
||||
if (showDialog)
|
||||
EditorUtility.DisplayDialog("ActorCore Motion Batch", summary, "확인");
|
||||
|
||||
return failedCount == 0;
|
||||
}
|
||||
|
||||
private static bool HasCopyEntries(ActorCoreMotionBatchManifest manifest)
|
||||
{
|
||||
return manifest.entries != null
|
||||
&& manifest.entries.Any(entry => string.Equals(entry.unityImportMode, "copy", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private static void ApplyImporterSettings(
|
||||
ActorCoreMotionBatchEntry entry,
|
||||
ModelImporter importer,
|
||||
string mode,
|
||||
Avatar actorCoreAvatar)
|
||||
{
|
||||
importer.importAnimation = true;
|
||||
importer.animationType = ModelImporterAnimationType.Human;
|
||||
importer.avatarSetup = mode == "copy"
|
||||
? ModelImporterAvatarSetup.CopyFromOther
|
||||
: ModelImporterAvatarSetup.CreateFromThisModel;
|
||||
importer.sourceAvatar = mode == "copy" ? actorCoreAvatar : null;
|
||||
importer.animationCompression = ModelImporterAnimationCompression.Optimal;
|
||||
importer.optimizeGameObjects = false;
|
||||
importer.optimizeBones = true;
|
||||
importer.preserveHierarchy = false;
|
||||
TakeInfo take = SelectPrimaryTake(importer.importedTakeInfos);
|
||||
if (string.IsNullOrEmpty(take.name))
|
||||
{
|
||||
importer.clipAnimations = Array.Empty<ModelImporterClipAnimation>();
|
||||
importer.SaveAndReimport();
|
||||
|
||||
ModelImporter refreshedImporter = AssetImporter.GetAtPath(entry.asset) as ModelImporter;
|
||||
take = SelectPrimaryTake(refreshedImporter.importedTakeInfos);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(take.name))
|
||||
return;
|
||||
|
||||
ModelImporter targetImporter = AssetImporter.GetAtPath(entry.asset) as ModelImporter;
|
||||
targetImporter.clipAnimations = new[]
|
||||
{
|
||||
CreateClipAnimation(entry, take),
|
||||
};
|
||||
targetImporter.SaveAndReimport();
|
||||
}
|
||||
|
||||
private static ModelImporterClipAnimation CreateClipAnimation(ActorCoreMotionBatchEntry entry, TakeInfo take)
|
||||
{
|
||||
float sampleRate = take.sampleRate > 0f ? take.sampleRate : 30f;
|
||||
float firstFrame = take.startTime * sampleRate;
|
||||
float lastFrame = take.stopTime * sampleRate;
|
||||
|
||||
return new ModelImporterClipAnimation
|
||||
{
|
||||
takeName = take.name,
|
||||
name = GetClipName(entry),
|
||||
firstFrame = firstFrame,
|
||||
lastFrame = Mathf.Max(firstFrame + 1f, lastFrame),
|
||||
loopTime = false,
|
||||
loopPose = false,
|
||||
lockRootRotation = false,
|
||||
lockRootHeightY = false,
|
||||
lockRootPositionXZ = false,
|
||||
};
|
||||
}
|
||||
|
||||
private static TakeInfo SelectPrimaryTake(TakeInfo[] takes)
|
||||
{
|
||||
if (takes == null || takes.Length == 0)
|
||||
return default;
|
||||
|
||||
return takes
|
||||
.Where(take => !string.IsNullOrEmpty(take.name))
|
||||
.Where(take => take.name.IndexOf("T-Pose", StringComparison.OrdinalIgnoreCase) < 0)
|
||||
.OrderByDescending(take => take.stopTime - take.startTime)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
private static ImportValidation ValidateImportedAsset(string assetPath, string mode)
|
||||
{
|
||||
UnityEngine.Object[] assets = AssetDatabase.LoadAllAssetsAtPath(assetPath);
|
||||
Transform[] transforms = assets.OfType<Transform>().ToArray();
|
||||
Transform armatureTransform = transforms.FirstOrDefault(transform => transform.name == "Armature");
|
||||
Transform rootTransform = transforms.FirstOrDefault(transform => transform.name == "root");
|
||||
AnimationClip[] humanClips = assets
|
||||
.OfType<AnimationClip>()
|
||||
.Where(clip => !clip.name.StartsWith("__preview__", StringComparison.Ordinal))
|
||||
.Where(clip => clip.humanMotion)
|
||||
.ToArray();
|
||||
Avatar[] avatars = assets.OfType<Avatar>().ToArray();
|
||||
|
||||
string[] humanClipNames = humanClips
|
||||
.Select(clip => $"{clip.name}:{clip.length:0.###}")
|
||||
.ToArray();
|
||||
string[] avatarNames = avatars
|
||||
.Select(avatar => $"{avatar.name}:valid={avatar.isValid}:human={avatar.isHuman}")
|
||||
.ToArray();
|
||||
|
||||
bool hasValidOwnAvatar = mode == "copy" || avatars.Any(avatar => avatar.isValid && avatar.isHuman);
|
||||
bool success = armatureTransform == null
|
||||
&& rootTransform != null
|
||||
&& humanClips.Length > 0
|
||||
&& hasValidOwnAvatar;
|
||||
|
||||
string message = success
|
||||
? "OK"
|
||||
: $"Armature={armatureTransform != null}, root={rootTransform != null}, humanClips={humanClips.Length}, avatars=[{string.Join(", ", avatarNames)}]";
|
||||
|
||||
return new ImportValidation
|
||||
{
|
||||
success = success,
|
||||
transformCount = transforms.Length,
|
||||
hasArmatureExtraRoot = armatureTransform != null,
|
||||
rootParent = rootTransform != null && rootTransform.parent != null ? rootTransform.parent.name : "(none)",
|
||||
humanClips = humanClipNames,
|
||||
avatars = avatarNames,
|
||||
message = message,
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetClipName(ActorCoreMotionBatchEntry entry)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(entry.clipName))
|
||||
return entry.clipName;
|
||||
|
||||
return Path.GetFileNameWithoutExtension(entry.asset);
|
||||
}
|
||||
|
||||
private static string NormalizeImportMode(string mode)
|
||||
{
|
||||
return string.Equals(mode, "copy", StringComparison.OrdinalIgnoreCase)
|
||||
? "copy"
|
||||
: "create";
|
||||
}
|
||||
|
||||
private static bool TryLoadManifest(string manifestPath, out ActorCoreMotionBatchManifest manifest)
|
||||
{
|
||||
manifest = null;
|
||||
|
||||
if (string.IsNullOrEmpty(manifestPath) || !File.Exists(manifestPath))
|
||||
{
|
||||
Debug.LogError($"[ActorCoreMotionBatchImport] manifest를 찾지 못했습니다: {manifestPath}");
|
||||
return false;
|
||||
}
|
||||
|
||||
manifest = JsonUtility.FromJson<ActorCoreMotionBatchManifest>(File.ReadAllText(manifestPath));
|
||||
if (manifest == null || manifest.entries == null)
|
||||
{
|
||||
Debug.LogError($"[ActorCoreMotionBatchImport] manifest 형식이 올바르지 않습니다: {manifestPath}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.Equals(manifest.schema, ExpectedSchema, StringComparison.Ordinal))
|
||||
Debug.LogWarning($"[ActorCoreMotionBatchImport] 예상 schema와 다릅니다: {manifest.schema}");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string GetManifestPathFromSelectionOrDialog()
|
||||
{
|
||||
string selectedPath = AssetDatabase.GetAssetPath(Selection.activeObject);
|
||||
if (IsJsonAssetPath(selectedPath))
|
||||
return selectedPath;
|
||||
|
||||
string absolutePath = EditorUtility.OpenFilePanel(
|
||||
"ActorCore Motion Batch Manifest 선택",
|
||||
Application.dataPath,
|
||||
"json");
|
||||
if (string.IsNullOrEmpty(absolutePath))
|
||||
return null;
|
||||
|
||||
string projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, "..")).Replace('\\', '/');
|
||||
string normalizedPath = Path.GetFullPath(absolutePath).Replace('\\', '/');
|
||||
if (!normalizedPath.StartsWith(projectRoot + "/", StringComparison.Ordinal))
|
||||
{
|
||||
EditorUtility.DisplayDialog("ActorCore Motion Batch", "프로젝트 내부 manifest만 사용할 수 있습니다.", "확인");
|
||||
return null;
|
||||
}
|
||||
|
||||
string projectRelativePath = normalizedPath.Substring(projectRoot.Length + 1);
|
||||
return IsJsonAssetPath(projectRelativePath) ? projectRelativePath : null;
|
||||
}
|
||||
|
||||
private static bool IsJsonAssetPath(string assetPath)
|
||||
{
|
||||
return !string.IsNullOrEmpty(assetPath)
|
||||
&& assetPath.StartsWith("Assets/", StringComparison.Ordinal)
|
||||
&& assetPath.EndsWith(".json", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c79ec38acf2200d9a465f565d978ea8
|
||||
Reference in New Issue
Block a user