ultimate-unreal-engine-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +729 -0
- package/dist/build/error-parser.js +51 -0
- package/dist/build/fix-suggester.js +84 -0
- package/dist/build/ubt-runner.js +146 -0
- package/dist/cli.js +13 -0
- package/dist/config.js +8 -0
- package/dist/docs/data/ue57-api.js +228 -0
- package/dist/docs/doc-index.js +110 -0
- package/dist/docs/types.js +4 -0
- package/dist/generators/class-generator.js +363 -0
- package/dist/generators/file-modifier.js +276 -0
- package/dist/generators/uht-validator.js +177 -0
- package/dist/index.js +89 -0
- package/dist/parsers/cpp-class-index.js +230 -0
- package/dist/parsers/cpp-parser.js +369 -0
- package/dist/parsers/ini-parser.js +216 -0
- package/dist/parsers/uproject-parser.js +130 -0
- package/dist/plugin-bridge/client.js +217 -0
- package/dist/plugin-bridge/protocol.js +6 -0
- package/dist/plugin-bridge/retry.js +23 -0
- package/dist/setup.js +209 -0
- package/dist/tools/ai-systems/index.js +247 -0
- package/dist/tools/ai-systems/types.js +4 -0
- package/dist/tools/animation/index.js +241 -0
- package/dist/tools/animation/types.js +4 -0
- package/dist/tools/audio/index.js +204 -0
- package/dist/tools/audio/types.js +4 -0
- package/dist/tools/blueprint/index.js +495 -0
- package/dist/tools/blueprint/types.js +4 -0
- package/dist/tools/build/index.js +163 -0
- package/dist/tools/chaos/index.js +230 -0
- package/dist/tools/chaos/types.js +4 -0
- package/dist/tools/collision-physics/index.js +211 -0
- package/dist/tools/config/index.js +288 -0
- package/dist/tools/cpp/index.js +305 -0
- package/dist/tools/docs/index.js +251 -0
- package/dist/tools/editor/index.js +242 -0
- package/dist/tools/gas/index.js +222 -0
- package/dist/tools/gas/types.js +5 -0
- package/dist/tools/import-export/index.js +218 -0
- package/dist/tools/input/index.js +146 -0
- package/dist/tools/known-issues/index.js +88 -0
- package/dist/tools/known-issues/middleware.js +55 -0
- package/dist/tools/known-issues/store.js +125 -0
- package/dist/tools/livelink/index.js +203 -0
- package/dist/tools/livelink/types.js +4 -0
- package/dist/tools/material/index.js +190 -0
- package/dist/tools/motion-design/index.js +251 -0
- package/dist/tools/motion-design/types.js +6 -0
- package/dist/tools/movie-render/index.js +220 -0
- package/dist/tools/networking/index.js +149 -0
- package/dist/tools/pcg/index.js +164 -0
- package/dist/tools/selection/index.js +180 -0
- package/dist/tools/sequencer/index.js +218 -0
- package/dist/tools/validation/index.js +183 -0
- package/dist/tools/validation/types.js +4 -0
- package/dist/tools/viewport/index.js +310 -0
- package/dist/tools/worldpartition/index.js +226 -0
- package/dist/tools/worldpartition/types.js +4 -0
- package/dist/utils/execFileNoThrow.js +40 -0
- package/dist/utils/logger.js +27 -0
- package/dist/utils/path-guard.js +26 -0
- package/package.json +40 -0
- package/unreal-plugin/MCPBridge/MCPBridge.uplugin +29 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/MCPBridgeEditor.Build.cs +68 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAICommands.cpp +919 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAICommands.h +23 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPActorCommands.cpp +415 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPActorCommands.h +16 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAnimationCommands.cpp +653 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAnimationCommands.h +24 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAssetCommands.cpp +290 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAssetCommands.h +17 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAudioCommands.cpp +624 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAudioCommands.h +22 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBlueprintHandlers.cpp +616 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBlueprintHandlers.h +25 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBlueprintWriteHandlers.cpp +744 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBlueprintWriteHandlers.h +24 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBridgeEditor.cpp +23 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBridgeSubsystem.cpp +149 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBridgeSubsystem.h +38 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPChaosCommands.cpp +771 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPChaosCommands.h +22 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPCollisionPhysicsCommands.cpp +749 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPCollisionPhysicsCommands.h +22 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPEditorStateCommands.cpp +172 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPEditorStateCommands.h +16 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPGASCommands.cpp +715 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPGASCommands.h +22 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPImportExportCommands.cpp +679 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPImportExportCommands.h +22 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPInputHandlers.cpp +381 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPInputHandlers.h +24 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPLiveLinkCommands.cpp +504 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPLiveLinkCommands.h +22 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMaterialCommands.cpp +511 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMaterialCommands.h +22 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMotionDesignCommands.cpp +1110 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMotionDesignCommands.h +28 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMovieRenderCommands.cpp +590 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMovieRenderCommands.h +16 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPNetworkingCommands.cpp +482 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPNetworkingCommands.h +16 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPPieCommands.cpp +338 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPPieCommands.h +16 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPSelectionCommands.cpp +677 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPSelectionCommands.h +22 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPSequencerCommands.cpp +721 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPSequencerCommands.h +16 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPValidationCommands.cpp +368 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPValidationCommands.h +22 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPViewportCommands.cpp +1208 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPViewportCommands.h +29 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPWorldPartitionCommands.cpp +822 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPWorldPartitionCommands.h +23 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Public/MCPBridgeEditor.h +14 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/MCPBridgeRuntime.Build.cs +28 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Private/MCPBridgeRuntime.cpp +22 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Private/MCPCommandRouter.cpp +118 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Private/MCPTcpServer.cpp +196 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Public/MCPBridgeRuntime.h +15 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Public/MCPCommandRouter.h +55 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Public/MCPTcpServer.h +59 -0
|
@@ -0,0 +1,653 @@
|
|
|
1
|
+
// MCPAnimationCommands.cpp (Plan 17-01)
|
|
2
|
+
// Implements five animation asset inspection command handlers for the MCP bridge:
|
|
3
|
+
// animation.list -- list all animation assets by type (ANIM-01)
|
|
4
|
+
// animation.inspectAnimBP -- inspect AnimBlueprint state machines, states, transitions (ANIM-02)
|
|
5
|
+
// animation.inspectMontage -- read montage sections, notifies, slot assignments (ANIM-03)
|
|
6
|
+
// animation.inspectBlendSpace -- inspect blend space axes, sample points, grid config (ANIM-04)
|
|
7
|
+
// animation.retargetMappings -- read IK retarget source/target skeleton mappings (ANIM-05)
|
|
8
|
+
//
|
|
9
|
+
// All handlers run on the game thread via FMCPCommandRouter::Dispatch.
|
|
10
|
+
// All operations are read-only -- no Modify() calls needed.
|
|
11
|
+
// asset_path is validated to start with "/Game/" or "/Engine/" before any
|
|
12
|
+
// StaticLoadObject call to prevent path traversal (T-17-01).
|
|
13
|
+
|
|
14
|
+
#include "MCPAnimationCommands.h"
|
|
15
|
+
|
|
16
|
+
// Animation headers
|
|
17
|
+
#include "Animation/AnimBlueprint.h"
|
|
18
|
+
#include "Animation/AnimMontage.h"
|
|
19
|
+
#include "Animation/BlendSpace.h"
|
|
20
|
+
#include "Animation/AnimSequence.h"
|
|
21
|
+
|
|
22
|
+
// AnimGraph headers for state machine node access
|
|
23
|
+
#include "AnimGraphNode_StateMachine.h"
|
|
24
|
+
#include "AnimStateNode.h"
|
|
25
|
+
|
|
26
|
+
// Asset Registry
|
|
27
|
+
#include "AssetRegistry/AssetRegistryModule.h"
|
|
28
|
+
#include "AssetRegistry/IAssetRegistry.h"
|
|
29
|
+
|
|
30
|
+
// IKRetargeter -- from IKRig plugin
|
|
31
|
+
#if WITH_EDITOR
|
|
32
|
+
#include "Retargeter/IKRetargeter.h"
|
|
33
|
+
#endif
|
|
34
|
+
|
|
35
|
+
// JSON
|
|
36
|
+
#include "Serialization/JsonSerializer.h"
|
|
37
|
+
#include "Serialization/JsonWriter.h"
|
|
38
|
+
#include "Dom/JsonObject.h"
|
|
39
|
+
#include "Dom/JsonValue.h"
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Internal helpers
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
/** Returns a JSON success response string (without trailing newline). */
|
|
46
|
+
static FString BuildAnimSuccessResponse(const FString& CorrId, TSharedPtr<FJsonObject> Data)
|
|
47
|
+
{
|
|
48
|
+
TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
|
|
49
|
+
Obj->SetBoolField(TEXT("success"), true);
|
|
50
|
+
Obj->SetStringField(TEXT("correlationId"), CorrId);
|
|
51
|
+
if (Data.IsValid())
|
|
52
|
+
{
|
|
53
|
+
Obj->SetObjectField(TEXT("data"), Data);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
FString Output;
|
|
57
|
+
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Output);
|
|
58
|
+
FJsonSerializer::Serialize(Obj.ToSharedRef(), Writer);
|
|
59
|
+
return Output;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Returns a JSON error response string (without trailing newline). */
|
|
63
|
+
static FString BuildAnimErrorResponse(const FString& CorrId, const FString& Error)
|
|
64
|
+
{
|
|
65
|
+
TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
|
|
66
|
+
Obj->SetBoolField(TEXT("success"), false);
|
|
67
|
+
Obj->SetStringField(TEXT("correlationId"), CorrId);
|
|
68
|
+
Obj->SetStringField(TEXT("error"), Error);
|
|
69
|
+
|
|
70
|
+
FString Output;
|
|
71
|
+
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Output);
|
|
72
|
+
FJsonSerializer::Serialize(Obj.ToSharedRef(), Writer);
|
|
73
|
+
return Output;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Validate that asset_path starts with "/Game/" or "/Engine/" to prevent
|
|
78
|
+
* path traversal attacks (T-17-01).
|
|
79
|
+
*/
|
|
80
|
+
static bool IsValidAssetPath(const FString& AssetPath)
|
|
81
|
+
{
|
|
82
|
+
return AssetPath.StartsWith(TEXT("/Game/")) || AssetPath.StartsWith(TEXT("/Engine/"));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// RegisterAnimationCommands
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
void RegisterAnimationCommands(FMCPCommandRouter& Router)
|
|
90
|
+
{
|
|
91
|
+
// -----------------------------------------------------------------------
|
|
92
|
+
// animation.list (ANIM-01)
|
|
93
|
+
// Lists animation assets in the project, optionally filtered by type.
|
|
94
|
+
// Optional payload field: type_filter (AnimBlueprint, AnimMontage, BlendSpace,
|
|
95
|
+
// BlendSpace1D, AnimSequence, or empty string for all).
|
|
96
|
+
// Returns JSON array `assets` with: asset_path, asset_name, asset_type.
|
|
97
|
+
// -----------------------------------------------------------------------
|
|
98
|
+
Router.RegisterHandler(TEXT("animation.list"), [](TSharedPtr<FJsonObject> Cmd, FMCPResponseSender SendResponse)
|
|
99
|
+
{
|
|
100
|
+
const FString CorrId = Cmd->GetStringField(TEXT("correlationId"));
|
|
101
|
+
|
|
102
|
+
// Extract optional payload.
|
|
103
|
+
TSharedPtr<FJsonObject> Payload;
|
|
104
|
+
const TSharedPtr<FJsonValue>* PayloadVal = Cmd->Values.Find(TEXT("payload"));
|
|
105
|
+
if (PayloadVal && (*PayloadVal)->Type == EJson::Object)
|
|
106
|
+
{
|
|
107
|
+
Payload = (*PayloadVal)->AsObject();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
FString TypeFilter;
|
|
111
|
+
if (Payload.IsValid())
|
|
112
|
+
{
|
|
113
|
+
Payload->TryGetStringField(TEXT("type_filter"), TypeFilter);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Access the asset registry.
|
|
117
|
+
FAssetRegistryModule& RegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
|
|
118
|
+
IAssetRegistry& Registry = RegistryModule.Get();
|
|
119
|
+
|
|
120
|
+
// Build list of class names to query based on type_filter.
|
|
121
|
+
TArray<FString> ClassNames;
|
|
122
|
+
if (TypeFilter.IsEmpty() || TypeFilter == TEXT("AnimBlueprint"))
|
|
123
|
+
{
|
|
124
|
+
ClassNames.Add(TEXT("AnimBlueprint"));
|
|
125
|
+
}
|
|
126
|
+
if (TypeFilter.IsEmpty() || TypeFilter == TEXT("AnimMontage"))
|
|
127
|
+
{
|
|
128
|
+
ClassNames.Add(TEXT("AnimMontage"));
|
|
129
|
+
}
|
|
130
|
+
if (TypeFilter.IsEmpty() || TypeFilter == TEXT("BlendSpace"))
|
|
131
|
+
{
|
|
132
|
+
ClassNames.Add(TEXT("BlendSpace"));
|
|
133
|
+
}
|
|
134
|
+
if (TypeFilter.IsEmpty() || TypeFilter == TEXT("BlendSpace1D"))
|
|
135
|
+
{
|
|
136
|
+
ClassNames.Add(TEXT("BlendSpace1D"));
|
|
137
|
+
}
|
|
138
|
+
if (TypeFilter.IsEmpty() || TypeFilter == TEXT("AnimSequence"))
|
|
139
|
+
{
|
|
140
|
+
ClassNames.Add(TEXT("AnimSequence"));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// If a type_filter was provided but didn't match any known type, return an error.
|
|
144
|
+
if (!TypeFilter.IsEmpty() && ClassNames.IsEmpty())
|
|
145
|
+
{
|
|
146
|
+
SendResponse(BuildAnimErrorResponse(CorrId, TEXT("unknown_type_filter")) + TEXT("\n"));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
TArray<TSharedPtr<FJsonValue>> AssetsArray;
|
|
151
|
+
|
|
152
|
+
for (const FString& ClassName : ClassNames)
|
|
153
|
+
{
|
|
154
|
+
TArray<FAssetData> AssetList;
|
|
155
|
+
FTopLevelAssetPath ClassPath(TEXT("/Script/Engine"), *ClassName);
|
|
156
|
+
Registry.GetAssetsByClass(ClassPath, AssetList, /*bSearchSubClasses=*/true);
|
|
157
|
+
|
|
158
|
+
for (const FAssetData& Asset : AssetList)
|
|
159
|
+
{
|
|
160
|
+
TSharedPtr<FJsonObject> AssetObj = MakeShared<FJsonObject>();
|
|
161
|
+
AssetObj->SetStringField(TEXT("asset_path"), Asset.GetObjectPathString());
|
|
162
|
+
AssetObj->SetStringField(TEXT("asset_name"), Asset.AssetName.ToString());
|
|
163
|
+
AssetObj->SetStringField(TEXT("asset_type"), ClassName);
|
|
164
|
+
AssetsArray.Add(MakeShared<FJsonValueObject>(AssetObj));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
|
|
169
|
+
Data->SetArrayField(TEXT("assets"), AssetsArray);
|
|
170
|
+
Data->SetNumberField(TEXT("count"), static_cast<double>(AssetsArray.Num()));
|
|
171
|
+
if (!TypeFilter.IsEmpty())
|
|
172
|
+
{
|
|
173
|
+
Data->SetStringField(TEXT("type_filter"), TypeFilter);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
SendResponse(BuildAnimSuccessResponse(CorrId, Data) + TEXT("\n"));
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// -----------------------------------------------------------------------
|
|
180
|
+
// animation.inspectAnimBP (ANIM-02)
|
|
181
|
+
// Inspects an Animation Blueprint's state machines, states, and transitions.
|
|
182
|
+
// Required payload field: asset_path (must start with /Game/ or /Engine/).
|
|
183
|
+
// Returns: asset_path, skeleton, state_machines array.
|
|
184
|
+
// -----------------------------------------------------------------------
|
|
185
|
+
Router.RegisterHandler(TEXT("animation.inspectAnimBP"), [](TSharedPtr<FJsonObject> Cmd, FMCPResponseSender SendResponse)
|
|
186
|
+
{
|
|
187
|
+
const FString CorrId = Cmd->GetStringField(TEXT("correlationId"));
|
|
188
|
+
|
|
189
|
+
// Extract payload.
|
|
190
|
+
TSharedPtr<FJsonObject> Payload;
|
|
191
|
+
const TSharedPtr<FJsonValue>* PayloadVal = Cmd->Values.Find(TEXT("payload"));
|
|
192
|
+
if (PayloadVal && (*PayloadVal)->Type == EJson::Object)
|
|
193
|
+
{
|
|
194
|
+
Payload = (*PayloadVal)->AsObject();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
FString AssetPath;
|
|
198
|
+
if (!Payload.IsValid() || !Payload->TryGetStringField(TEXT("asset_path"), AssetPath) || AssetPath.IsEmpty())
|
|
199
|
+
{
|
|
200
|
+
SendResponse(BuildAnimErrorResponse(CorrId, TEXT("missing_asset_path")) + TEXT("\n"));
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Validate path prefix (T-17-01).
|
|
205
|
+
if (!IsValidAssetPath(AssetPath))
|
|
206
|
+
{
|
|
207
|
+
SendResponse(BuildAnimErrorResponse(CorrId, TEXT("invalid_asset_path")) + TEXT("\n"));
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Load the AnimBlueprint asset.
|
|
212
|
+
UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(StaticLoadObject(UAnimBlueprint::StaticClass(), nullptr, *AssetPath));
|
|
213
|
+
if (!AnimBP)
|
|
214
|
+
{
|
|
215
|
+
SendResponse(BuildAnimErrorResponse(CorrId, TEXT("anim_blueprint_not_found")) + TEXT("\n"));
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Extract skeleton name.
|
|
220
|
+
FString SkeletonPath;
|
|
221
|
+
if (AnimBP->TargetSkeleton)
|
|
222
|
+
{
|
|
223
|
+
SkeletonPath = AnimBP->TargetSkeleton->GetPathName();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Collect state machine information by iterating FunctionGraphs and AnimationGraphs.
|
|
227
|
+
// UAnimBlueprint stores its animation nodes in the AnimationGraph (UEdGraph).
|
|
228
|
+
TArray<TSharedPtr<FJsonValue>> StateMachinesArray;
|
|
229
|
+
|
|
230
|
+
// Iterate all graphs in the AnimBlueprint to find state machine graphs.
|
|
231
|
+
TArray<UEdGraph*> AllGraphs;
|
|
232
|
+
AllGraphs.Append(AnimBP->FunctionGraphs);
|
|
233
|
+
if (AnimBP->UbergraphPages.Num() > 0)
|
|
234
|
+
{
|
|
235
|
+
AllGraphs.Append(AnimBP->UbergraphPages);
|
|
236
|
+
}
|
|
237
|
+
// Also check AnimationGraph specifically.
|
|
238
|
+
for (UEdGraph* Graph : AnimBP->FunctionGraphs)
|
|
239
|
+
{
|
|
240
|
+
if (Graph)
|
|
241
|
+
{
|
|
242
|
+
AllGraphs.AddUnique(Graph);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Iterate all node graphs in the Blueprint looking for state machine nodes.
|
|
247
|
+
TArray<UEdGraph*> AllBPGraphs;
|
|
248
|
+
AnimBP->GetAllGraphs(AllBPGraphs);
|
|
249
|
+
|
|
250
|
+
for (UEdGraph* Graph : AllBPGraphs)
|
|
251
|
+
{
|
|
252
|
+
if (!Graph)
|
|
253
|
+
{
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
for (UEdGraphNode* Node : Graph->Nodes)
|
|
258
|
+
{
|
|
259
|
+
UAnimGraphNode_StateMachine* SMNode = Cast<UAnimGraphNode_StateMachine>(Node);
|
|
260
|
+
if (!SMNode)
|
|
261
|
+
{
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
FString SMName = SMNode->GetNodeTitle(ENodeTitleType::ListView).ToString();
|
|
266
|
+
|
|
267
|
+
// Collect states from the state machine graph.
|
|
268
|
+
TArray<TSharedPtr<FJsonValue>> StatesArray;
|
|
269
|
+
TArray<TSharedPtr<FJsonValue>> TransitionsArray;
|
|
270
|
+
|
|
271
|
+
// The state machine graph is referenced by BoundGraph.
|
|
272
|
+
UEdGraph* SMGraph = SMNode->GetBoundGraph();
|
|
273
|
+
if (SMGraph)
|
|
274
|
+
{
|
|
275
|
+
for (UEdGraphNode* SMSubNode : SMGraph->Nodes)
|
|
276
|
+
{
|
|
277
|
+
if (!SMSubNode)
|
|
278
|
+
{
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Check if this is a state node.
|
|
283
|
+
UAnimStateNode* StateNode = Cast<UAnimStateNode>(SMSubNode);
|
|
284
|
+
if (StateNode)
|
|
285
|
+
{
|
|
286
|
+
FString StateName = StateNode->GetStateName();
|
|
287
|
+
FString AnimAssetPath;
|
|
288
|
+
|
|
289
|
+
// Try to get the animation sequence from the state's bound graph.
|
|
290
|
+
UEdGraph* StateGraph = StateNode->GetBoundGraph();
|
|
291
|
+
if (StateGraph)
|
|
292
|
+
{
|
|
293
|
+
for (UEdGraphNode* StateSubNode : StateGraph->Nodes)
|
|
294
|
+
{
|
|
295
|
+
// Look for AnimSequence player nodes.
|
|
296
|
+
if (StateSubNode && StateSubNode->GetClass()->GetName().Contains(TEXT("AnimSequence")))
|
|
297
|
+
{
|
|
298
|
+
// Try to get asset reference from node properties.
|
|
299
|
+
for (TFieldIterator<FObjectProperty> PropIt(StateSubNode->GetClass()); PropIt; ++PropIt)
|
|
300
|
+
{
|
|
301
|
+
UObject* ObjVal = PropIt->GetObjectPropertyValue_InContainer(StateSubNode);
|
|
302
|
+
if (ObjVal && ObjVal->IsA(UAnimSequenceBase::StaticClass()))
|
|
303
|
+
{
|
|
304
|
+
AnimAssetPath = ObjVal->GetPathName();
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
TSharedPtr<FJsonObject> StateObj = MakeShared<FJsonObject>();
|
|
313
|
+
StateObj->SetStringField(TEXT("name"), StateName);
|
|
314
|
+
StateObj->SetStringField(TEXT("animation_asset"), AnimAssetPath);
|
|
315
|
+
StatesArray.Add(MakeShared<FJsonValueObject>(StateObj));
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Collect transitions from state machine graph.
|
|
320
|
+
for (UEdGraphNode* SMSubNode : SMGraph->Nodes)
|
|
321
|
+
{
|
|
322
|
+
if (!SMSubNode)
|
|
323
|
+
{
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
UAnimStateTransitionNode* TransNode = Cast<UAnimStateTransitionNode>(SMSubNode);
|
|
328
|
+
if (!TransNode)
|
|
329
|
+
{
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
FString SourceState;
|
|
334
|
+
FString TargetState;
|
|
335
|
+
|
|
336
|
+
// Get source and target state names via pin connections.
|
|
337
|
+
for (UEdGraphPin* Pin : TransNode->Pins)
|
|
338
|
+
{
|
|
339
|
+
if (!Pin)
|
|
340
|
+
{
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
if (Pin->Direction == EGPD_Input && Pin->LinkedTo.Num() > 0)
|
|
344
|
+
{
|
|
345
|
+
UAnimStateNode* LinkedState = Cast<UAnimStateNode>(Pin->LinkedTo[0]->GetOwningNode());
|
|
346
|
+
if (LinkedState)
|
|
347
|
+
{
|
|
348
|
+
SourceState = LinkedState->GetStateName();
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
else if (Pin->Direction == EGPD_Output && Pin->LinkedTo.Num() > 0)
|
|
352
|
+
{
|
|
353
|
+
UAnimStateNode* LinkedState = Cast<UAnimStateNode>(Pin->LinkedTo[0]->GetOwningNode());
|
|
354
|
+
if (LinkedState)
|
|
355
|
+
{
|
|
356
|
+
TargetState = LinkedState->GetStateName();
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
TSharedPtr<FJsonObject> TransObj = MakeShared<FJsonObject>();
|
|
362
|
+
TransObj->SetStringField(TEXT("source_state"), SourceState);
|
|
363
|
+
TransObj->SetStringField(TEXT("target_state"), TargetState);
|
|
364
|
+
TransObj->SetNumberField(TEXT("duration"), static_cast<double>(TransNode->CrossfadeDuration));
|
|
365
|
+
TransitionsArray.Add(MakeShared<FJsonValueObject>(TransObj));
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
TSharedPtr<FJsonObject> SMObj = MakeShared<FJsonObject>();
|
|
370
|
+
SMObj->SetStringField(TEXT("name"), SMName);
|
|
371
|
+
SMObj->SetArrayField(TEXT("states"), StatesArray);
|
|
372
|
+
SMObj->SetArrayField(TEXT("transitions"), TransitionsArray);
|
|
373
|
+
StateMachinesArray.Add(MakeShared<FJsonValueObject>(SMObj));
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
|
|
378
|
+
Data->SetStringField(TEXT("asset_path"), AssetPath);
|
|
379
|
+
Data->SetStringField(TEXT("skeleton"), SkeletonPath);
|
|
380
|
+
Data->SetArrayField(TEXT("state_machines"), StateMachinesArray);
|
|
381
|
+
|
|
382
|
+
SendResponse(BuildAnimSuccessResponse(CorrId, Data) + TEXT("\n"));
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// -----------------------------------------------------------------------
|
|
386
|
+
// animation.inspectMontage (ANIM-03)
|
|
387
|
+
// Reads montage sections, notifies, and slot assignments.
|
|
388
|
+
// Required payload field: asset_path.
|
|
389
|
+
// Returns: asset_path, sections array, notifies array, slots array.
|
|
390
|
+
// -----------------------------------------------------------------------
|
|
391
|
+
Router.RegisterHandler(TEXT("animation.inspectMontage"), [](TSharedPtr<FJsonObject> Cmd, FMCPResponseSender SendResponse)
|
|
392
|
+
{
|
|
393
|
+
const FString CorrId = Cmd->GetStringField(TEXT("correlationId"));
|
|
394
|
+
|
|
395
|
+
// Extract payload.
|
|
396
|
+
TSharedPtr<FJsonObject> Payload;
|
|
397
|
+
const TSharedPtr<FJsonValue>* PayloadVal = Cmd->Values.Find(TEXT("payload"));
|
|
398
|
+
if (PayloadVal && (*PayloadVal)->Type == EJson::Object)
|
|
399
|
+
{
|
|
400
|
+
Payload = (*PayloadVal)->AsObject();
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
FString AssetPath;
|
|
404
|
+
if (!Payload.IsValid() || !Payload->TryGetStringField(TEXT("asset_path"), AssetPath) || AssetPath.IsEmpty())
|
|
405
|
+
{
|
|
406
|
+
SendResponse(BuildAnimErrorResponse(CorrId, TEXT("missing_asset_path")) + TEXT("\n"));
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Validate path prefix (T-17-01).
|
|
411
|
+
if (!IsValidAssetPath(AssetPath))
|
|
412
|
+
{
|
|
413
|
+
SendResponse(BuildAnimErrorResponse(CorrId, TEXT("invalid_asset_path")) + TEXT("\n"));
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Load the AnimMontage asset.
|
|
418
|
+
UAnimMontage* Montage = Cast<UAnimMontage>(StaticLoadObject(UAnimMontage::StaticClass(), nullptr, *AssetPath));
|
|
419
|
+
if (!Montage)
|
|
420
|
+
{
|
|
421
|
+
SendResponse(BuildAnimErrorResponse(CorrId, TEXT("montage_not_found")) + TEXT("\n"));
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Build sections array from CompositeSections.
|
|
426
|
+
TArray<TSharedPtr<FJsonValue>> SectionsArray;
|
|
427
|
+
for (const FCompositeSection& Section : Montage->CompositeSections)
|
|
428
|
+
{
|
|
429
|
+
FString LinkedSequenceName;
|
|
430
|
+
if (Section.LinkedSequence)
|
|
431
|
+
{
|
|
432
|
+
LinkedSequenceName = Section.LinkedSequence->GetPathName();
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
TSharedPtr<FJsonObject> SectionObj = MakeShared<FJsonObject>();
|
|
436
|
+
SectionObj->SetStringField(TEXT("name"), Section.SectionName.ToString());
|
|
437
|
+
SectionObj->SetStringField(TEXT("linked_sequence"), LinkedSequenceName);
|
|
438
|
+
SectionsArray.Add(MakeShared<FJsonValueObject>(SectionObj));
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Build notifies array from Notifies.
|
|
442
|
+
TArray<TSharedPtr<FJsonValue>> NotifiesArray;
|
|
443
|
+
for (const FAnimNotifyEvent& Notify : Montage->Notifies)
|
|
444
|
+
{
|
|
445
|
+
FString NotifyName;
|
|
446
|
+
if (Notify.Notify)
|
|
447
|
+
{
|
|
448
|
+
NotifyName = Notify.Notify->GetClass()->GetName();
|
|
449
|
+
}
|
|
450
|
+
else
|
|
451
|
+
{
|
|
452
|
+
NotifyName = Notify.NotifyName.ToString();
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
TSharedPtr<FJsonObject> NotifyObj = MakeShared<FJsonObject>();
|
|
456
|
+
NotifyObj->SetStringField(TEXT("name"), NotifyName);
|
|
457
|
+
NotifyObj->SetNumberField(TEXT("trigger_time"), static_cast<double>(Notify.GetTriggerTime()));
|
|
458
|
+
NotifiesArray.Add(MakeShared<FJsonValueObject>(NotifyObj));
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Build slots array from SlotAnimTracks.
|
|
462
|
+
TArray<TSharedPtr<FJsonValue>> SlotsArray;
|
|
463
|
+
for (const FSlotAnimationTrack& SlotTrack : Montage->SlotAnimTracks)
|
|
464
|
+
{
|
|
465
|
+
SlotsArray.Add(MakeShared<FJsonValueString>(SlotTrack.SlotName.ToString()));
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
|
|
469
|
+
Data->SetStringField(TEXT("asset_path"), AssetPath);
|
|
470
|
+
Data->SetArrayField(TEXT("sections"), SectionsArray);
|
|
471
|
+
Data->SetArrayField(TEXT("notifies"), NotifiesArray);
|
|
472
|
+
Data->SetArrayField(TEXT("slots"), SlotsArray);
|
|
473
|
+
|
|
474
|
+
SendResponse(BuildAnimSuccessResponse(CorrId, Data) + TEXT("\n"));
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
// -----------------------------------------------------------------------
|
|
478
|
+
// animation.inspectBlendSpace (ANIM-04)
|
|
479
|
+
// Inspects blend space axes, sample points, and grid configuration.
|
|
480
|
+
// Required payload field: asset_path.
|
|
481
|
+
// Returns: asset_path, is_1d, axes array, samples array.
|
|
482
|
+
// -----------------------------------------------------------------------
|
|
483
|
+
Router.RegisterHandler(TEXT("animation.inspectBlendSpace"), [](TSharedPtr<FJsonObject> Cmd, FMCPResponseSender SendResponse)
|
|
484
|
+
{
|
|
485
|
+
const FString CorrId = Cmd->GetStringField(TEXT("correlationId"));
|
|
486
|
+
|
|
487
|
+
// Extract payload.
|
|
488
|
+
TSharedPtr<FJsonObject> Payload;
|
|
489
|
+
const TSharedPtr<FJsonValue>* PayloadVal = Cmd->Values.Find(TEXT("payload"));
|
|
490
|
+
if (PayloadVal && (*PayloadVal)->Type == EJson::Object)
|
|
491
|
+
{
|
|
492
|
+
Payload = (*PayloadVal)->AsObject();
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
FString AssetPath;
|
|
496
|
+
if (!Payload.IsValid() || !Payload->TryGetStringField(TEXT("asset_path"), AssetPath) || AssetPath.IsEmpty())
|
|
497
|
+
{
|
|
498
|
+
SendResponse(BuildAnimErrorResponse(CorrId, TEXT("missing_asset_path")) + TEXT("\n"));
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Validate path prefix (T-17-01).
|
|
503
|
+
if (!IsValidAssetPath(AssetPath))
|
|
504
|
+
{
|
|
505
|
+
SendResponse(BuildAnimErrorResponse(CorrId, TEXT("invalid_asset_path")) + TEXT("\n"));
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Try to load as UBlendSpace first, then UBlendSpace1D.
|
|
510
|
+
UBlendSpace* BlendSpace = Cast<UBlendSpace>(StaticLoadObject(UBlendSpace::StaticClass(), nullptr, *AssetPath));
|
|
511
|
+
bool bIs1D = false;
|
|
512
|
+
|
|
513
|
+
if (!BlendSpace)
|
|
514
|
+
{
|
|
515
|
+
// Try UBlendSpace1D explicitly.
|
|
516
|
+
BlendSpace = Cast<UBlendSpace>(StaticLoadObject(UBlendSpace1D::StaticClass(), nullptr, *AssetPath));
|
|
517
|
+
if (BlendSpace && BlendSpace->IsA(UBlendSpace1D::StaticClass()))
|
|
518
|
+
{
|
|
519
|
+
bIs1D = true;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
else if (BlendSpace->IsA(UBlendSpace1D::StaticClass()))
|
|
523
|
+
{
|
|
524
|
+
bIs1D = true;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (!BlendSpace)
|
|
528
|
+
{
|
|
529
|
+
SendResponse(BuildAnimErrorResponse(CorrId, TEXT("blend_space_not_found")) + TEXT("\n"));
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Build axes array from BlendParameters.
|
|
534
|
+
TArray<TSharedPtr<FJsonValue>> AxesArray;
|
|
535
|
+
// UBlendSpace has BlendParameters[3] (up to 3 axes; 1D uses only index 0).
|
|
536
|
+
const int32 NumAxes = bIs1D ? 1 : 2; // 1D uses 1 axis, 2D uses 2 axes (index 0 and 1).
|
|
537
|
+
for (int32 i = 0; i < NumAxes; ++i)
|
|
538
|
+
{
|
|
539
|
+
const FBlendParameter& Param = BlendSpace->BlendParameters[i];
|
|
540
|
+
TSharedPtr<FJsonObject> AxisObj = MakeShared<FJsonObject>();
|
|
541
|
+
AxisObj->SetStringField(TEXT("name"), Param.DisplayName);
|
|
542
|
+
AxisObj->SetNumberField(TEXT("min"), static_cast<double>(Param.Min));
|
|
543
|
+
AxisObj->SetNumberField(TEXT("max"), static_cast<double>(Param.Max));
|
|
544
|
+
AxisObj->SetNumberField(TEXT("grid_divisions"), static_cast<double>(Param.GridNum));
|
|
545
|
+
AxesArray.Add(MakeShared<FJsonValueObject>(AxisObj));
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Build samples array from SampleData.
|
|
549
|
+
TArray<TSharedPtr<FJsonValue>> SamplesArray;
|
|
550
|
+
for (const FBlendSample& Sample : BlendSpace->SampleData)
|
|
551
|
+
{
|
|
552
|
+
FString AnimPath;
|
|
553
|
+
if (Sample.Animation)
|
|
554
|
+
{
|
|
555
|
+
AnimPath = Sample.Animation->GetPathName();
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
TSharedPtr<FJsonObject> SampleObj = MakeShared<FJsonObject>();
|
|
559
|
+
SampleObj->SetStringField(TEXT("animation"), AnimPath);
|
|
560
|
+
SampleObj->SetNumberField(TEXT("x"), static_cast<double>(Sample.SampleValue.X));
|
|
561
|
+
SampleObj->SetNumberField(TEXT("y"), static_cast<double>(Sample.SampleValue.Y));
|
|
562
|
+
SampleObj->SetNumberField(TEXT("z"), static_cast<double>(Sample.SampleValue.Z));
|
|
563
|
+
SamplesArray.Add(MakeShared<FJsonValueObject>(SampleObj));
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
|
|
567
|
+
Data->SetStringField(TEXT("asset_path"), AssetPath);
|
|
568
|
+
Data->SetBoolField(TEXT("is_1d"), bIs1D);
|
|
569
|
+
Data->SetArrayField(TEXT("axes"), AxesArray);
|
|
570
|
+
Data->SetArrayField(TEXT("samples"), SamplesArray);
|
|
571
|
+
|
|
572
|
+
SendResponse(BuildAnimSuccessResponse(CorrId, Data) + TEXT("\n"));
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
// -----------------------------------------------------------------------
|
|
576
|
+
// animation.retargetMappings (ANIM-05)
|
|
577
|
+
// Reads IK Retargeter source/target skeleton mappings and chain assignments.
|
|
578
|
+
// Required payload field: asset_path.
|
|
579
|
+
// Returns: asset_path, source_rig, target_rig, chain_mappings array.
|
|
580
|
+
// -----------------------------------------------------------------------
|
|
581
|
+
Router.RegisterHandler(TEXT("animation.retargetMappings"), [](TSharedPtr<FJsonObject> Cmd, FMCPResponseSender SendResponse)
|
|
582
|
+
{
|
|
583
|
+
const FString CorrId = Cmd->GetStringField(TEXT("correlationId"));
|
|
584
|
+
|
|
585
|
+
// Extract payload.
|
|
586
|
+
TSharedPtr<FJsonObject> Payload;
|
|
587
|
+
const TSharedPtr<FJsonValue>* PayloadVal = Cmd->Values.Find(TEXT("payload"));
|
|
588
|
+
if (PayloadVal && (*PayloadVal)->Type == EJson::Object)
|
|
589
|
+
{
|
|
590
|
+
Payload = (*PayloadVal)->AsObject();
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
FString AssetPath;
|
|
594
|
+
if (!Payload.IsValid() || !Payload->TryGetStringField(TEXT("asset_path"), AssetPath) || AssetPath.IsEmpty())
|
|
595
|
+
{
|
|
596
|
+
SendResponse(BuildAnimErrorResponse(CorrId, TEXT("missing_asset_path")) + TEXT("\n"));
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Validate path prefix (T-17-01).
|
|
601
|
+
if (!IsValidAssetPath(AssetPath))
|
|
602
|
+
{
|
|
603
|
+
SendResponse(BuildAnimErrorResponse(CorrId, TEXT("invalid_asset_path")) + TEXT("\n"));
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
#if WITH_EDITOR
|
|
608
|
+
// Load the IKRetargeter asset.
|
|
609
|
+
UIKRetargeter* Retargeter = Cast<UIKRetargeter>(StaticLoadObject(UIKRetargeter::StaticClass(), nullptr, *AssetPath));
|
|
610
|
+
if (!Retargeter)
|
|
611
|
+
{
|
|
612
|
+
SendResponse(BuildAnimErrorResponse(CorrId, TEXT("retargeter_not_found")) + TEXT("\n"));
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Get source and target IK Rig asset paths.
|
|
617
|
+
FString SourceRigPath;
|
|
618
|
+
FString TargetRigPath;
|
|
619
|
+
|
|
620
|
+
const UIKRigDefinition* SourceRig = Retargeter->GetSourceIKRig();
|
|
621
|
+
if (SourceRig)
|
|
622
|
+
{
|
|
623
|
+
SourceRigPath = SourceRig->GetPathName();
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const UIKRigDefinition* TargetRig = Retargeter->GetTargetIKRig();
|
|
627
|
+
if (TargetRig)
|
|
628
|
+
{
|
|
629
|
+
TargetRigPath = TargetRig->GetPathName();
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Collect chain mappings.
|
|
633
|
+
TArray<TSharedPtr<FJsonValue>> ChainMappingsArray;
|
|
634
|
+
for (const FRetargetChainMap& ChainMap : Retargeter->GetChainMapping())
|
|
635
|
+
{
|
|
636
|
+
TSharedPtr<FJsonObject> MappingObj = MakeShared<FJsonObject>();
|
|
637
|
+
MappingObj->SetStringField(TEXT("source_chain"), ChainMap.SourceChain.ToString());
|
|
638
|
+
MappingObj->SetStringField(TEXT("target_chain"), ChainMap.TargetChain.ToString());
|
|
639
|
+
ChainMappingsArray.Add(MakeShared<FJsonValueObject>(MappingObj));
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
|
|
643
|
+
Data->SetStringField(TEXT("asset_path"), AssetPath);
|
|
644
|
+
Data->SetStringField(TEXT("source_rig"), SourceRigPath);
|
|
645
|
+
Data->SetStringField(TEXT("target_rig"), TargetRigPath);
|
|
646
|
+
Data->SetArrayField(TEXT("chain_mappings"), ChainMappingsArray);
|
|
647
|
+
|
|
648
|
+
SendResponse(BuildAnimSuccessResponse(CorrId, Data) + TEXT("\n"));
|
|
649
|
+
#else
|
|
650
|
+
SendResponse(BuildAnimErrorResponse(CorrId, TEXT("ik_retargeter_requires_editor")) + TEXT("\n"));
|
|
651
|
+
#endif
|
|
652
|
+
});
|
|
653
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// MCPAnimationCommands.h (Plan 17-01)
|
|
2
|
+
// Declares the registration function for all animation asset inspection MCP command handlers.
|
|
3
|
+
// Handlers: animation.list, animation.inspectAnimBP, animation.inspectMontage,
|
|
4
|
+
// animation.inspectBlendSpace, animation.retargetMappings
|
|
5
|
+
//
|
|
6
|
+
// Call RegisterAnimationCommands(*Router) in MCPBridgeSubsystem::Initialize()
|
|
7
|
+
// BEFORE the TCP server starts accepting connections.
|
|
8
|
+
|
|
9
|
+
#pragma once
|
|
10
|
+
|
|
11
|
+
#include "MCPCommandRouter.h"
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Register all five animation command handlers into the given router.
|
|
15
|
+
* Must be called on the game thread before connections arrive.
|
|
16
|
+
*
|
|
17
|
+
* Registered commands:
|
|
18
|
+
* animation.list -- list animation assets filtered by type (ANIM-01)
|
|
19
|
+
* animation.inspectAnimBP -- inspect AnimBlueprint state machines, states, transitions (ANIM-02)
|
|
20
|
+
* animation.inspectMontage -- read montage sections, notifies, slot assignments (ANIM-03)
|
|
21
|
+
* animation.inspectBlendSpace -- inspect blend space axes, sample points, grid config (ANIM-04)
|
|
22
|
+
* animation.retargetMappings -- read IK retarget source/target skeleton mappings (ANIM-05)
|
|
23
|
+
*/
|
|
24
|
+
void RegisterAnimationCommands(FMCPCommandRouter& Router);
|