ue-mcp 0.4.0-alpha

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.
Files changed (141) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +89 -0
  3. package/dist/bridge.d.ts +25 -0
  4. package/dist/bridge.js +124 -0
  5. package/dist/bridge.js.map +1 -0
  6. package/dist/deployer.d.ts +18 -0
  7. package/dist/deployer.js +175 -0
  8. package/dist/deployer.js.map +1 -0
  9. package/dist/editor-control.d.ts +15 -0
  10. package/dist/editor-control.js +181 -0
  11. package/dist/editor-control.js.map +1 -0
  12. package/dist/github-app.d.ts +4 -0
  13. package/dist/github-app.js +94 -0
  14. package/dist/github-app.js.map +1 -0
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.js +125 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/init.d.ts +2 -0
  19. package/dist/init.js +290 -0
  20. package/dist/init.js.map +1 -0
  21. package/dist/instructions.d.ts +1 -0
  22. package/dist/instructions.js +150 -0
  23. package/dist/instructions.js.map +1 -0
  24. package/dist/project.d.ts +32 -0
  25. package/dist/project.js +175 -0
  26. package/dist/project.js.map +1 -0
  27. package/dist/tools/animation.d.ts +2 -0
  28. package/dist/tools/animation.js +62 -0
  29. package/dist/tools/animation.js.map +1 -0
  30. package/dist/tools/asset.d.ts +2 -0
  31. package/dist/tools/asset.js +128 -0
  32. package/dist/tools/asset.js.map +1 -0
  33. package/dist/tools/audio.d.ts +2 -0
  34. package/dist/tools/audio.js +24 -0
  35. package/dist/tools/audio.js.map +1 -0
  36. package/dist/tools/blueprint.d.ts +2 -0
  37. package/dist/tools/blueprint.js +64 -0
  38. package/dist/tools/blueprint.js.map +1 -0
  39. package/dist/tools/demo.d.ts +2 -0
  40. package/dist/tools/demo.js +10 -0
  41. package/dist/tools/demo.js.map +1 -0
  42. package/dist/tools/editor.d.ts +2 -0
  43. package/dist/tools/editor.js +133 -0
  44. package/dist/tools/editor.js.map +1 -0
  45. package/dist/tools/feedback.d.ts +2 -0
  46. package/dist/tools/feedback.js +44 -0
  47. package/dist/tools/feedback.js.map +1 -0
  48. package/dist/tools/foliage.d.ts +2 -0
  49. package/dist/tools/foliage.js +29 -0
  50. package/dist/tools/foliage.js.map +1 -0
  51. package/dist/tools/gameplay.d.ts +2 -0
  52. package/dist/tools/gameplay.js +101 -0
  53. package/dist/tools/gameplay.js.map +1 -0
  54. package/dist/tools/gas.d.ts +2 -0
  55. package/dist/tools/gas.js +43 -0
  56. package/dist/tools/gas.js.map +1 -0
  57. package/dist/tools/landscape.d.ts +2 -0
  58. package/dist/tools/landscape.js +32 -0
  59. package/dist/tools/landscape.js.map +1 -0
  60. package/dist/tools/level.d.ts +2 -0
  61. package/dist/tools/level.js +66 -0
  62. package/dist/tools/level.js.map +1 -0
  63. package/dist/tools/material.d.ts +2 -0
  64. package/dist/tools/material.js +59 -0
  65. package/dist/tools/material.js.map +1 -0
  66. package/dist/tools/networking.d.ts +2 -0
  67. package/dist/tools/networking.js +42 -0
  68. package/dist/tools/networking.js.map +1 -0
  69. package/dist/tools/niagara.d.ts +2 -0
  70. package/dist/tools/niagara.js +42 -0
  71. package/dist/tools/niagara.js.map +1 -0
  72. package/dist/tools/pcg.d.ts +2 -0
  73. package/dist/tools/pcg.js +39 -0
  74. package/dist/tools/pcg.js.map +1 -0
  75. package/dist/tools/project.d.ts +2 -0
  76. package/dist/tools/project.js +282 -0
  77. package/dist/tools/project.js.map +1 -0
  78. package/dist/tools/reflection.d.ts +2 -0
  79. package/dist/tools/reflection.js +26 -0
  80. package/dist/tools/reflection.js.map +1 -0
  81. package/dist/tools/widget.d.ts +2 -0
  82. package/dist/tools/widget.js +34 -0
  83. package/dist/tools/widget.js.map +1 -0
  84. package/dist/types.d.ts +21 -0
  85. package/dist/types.js +38 -0
  86. package/dist/types.js.map +1 -0
  87. package/package.json +76 -0
  88. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.cpp +746 -0
  89. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.h +81 -0
  90. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.cpp +49 -0
  91. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.h +37 -0
  92. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.cpp +75 -0
  93. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.h +50 -0
  94. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.cpp +1418 -0
  95. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.h +43 -0
  96. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.cpp +1773 -0
  97. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.h +48 -0
  98. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers.cpp +289 -0
  99. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers.h +18 -0
  100. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.cpp +1982 -0
  101. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.h +44 -0
  102. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.cpp +1217 -0
  103. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.h +71 -0
  104. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DialogHandlers.cpp +465 -0
  105. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DialogHandlers.h +49 -0
  106. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.cpp +1676 -0
  107. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.h +53 -0
  108. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.cpp +876 -0
  109. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.h +22 -0
  110. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.cpp +2222 -0
  111. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.h +54 -0
  112. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +783 -0
  113. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +24 -0
  114. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.cpp +898 -0
  115. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.h +24 -0
  116. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.cpp +1270 -0
  117. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.h +34 -0
  118. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.cpp +2190 -0
  119. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.h +53 -0
  120. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.cpp +554 -0
  121. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.h +29 -0
  122. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.cpp +601 -0
  123. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.h +25 -0
  124. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.cpp +1024 -0
  125. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.h +25 -0
  126. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.cpp +370 -0
  127. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.h +19 -0
  128. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ReflectionHandlers.cpp +499 -0
  129. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ReflectionHandlers.h +27 -0
  130. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.cpp +426 -0
  131. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.h +19 -0
  132. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.cpp +303 -0
  133. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.h +18 -0
  134. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.cpp +985 -0
  135. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.h +27 -0
  136. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/JsonSerializer.cpp +181 -0
  137. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/JsonSerializer.h +42 -0
  138. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/UE_MCP_Bridge.cpp +39 -0
  139. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/UE_MCP_BridgeModule.h +14 -0
  140. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/UE_MCP_Bridge.Build.cs +87 -0
  141. package/plugin/ue_mcp_bridge/UE_MCP_Bridge.uplugin +55 -0
@@ -0,0 +1,1418 @@
1
+ #include "AnimationHandlers.h"
2
+ #include "HandlerRegistry.h"
3
+ #include "AssetRegistry/AssetRegistryModule.h"
4
+ #include "AssetToolsModule.h"
5
+ #include "IAssetTools.h"
6
+ #include "Engine/SkeletalMesh.h"
7
+ #include "Animation/Skeleton.h"
8
+ #include "Animation/AnimSequence.h"
9
+ #include "Animation/AnimMontage.h"
10
+ #include "Animation/AnimBlueprint.h"
11
+ #include "Animation/BlendSpace.h"
12
+ #include "Animation/AnimBlueprintGeneratedClass.h"
13
+ #include "Animation/AnimNotifies/AnimNotify.h"
14
+ #include "PhysicsEngine/PhysicsAsset.h"
15
+ #include "Engine/SkeletalMeshSocket.h"
16
+ #include "PhysicsEngine/SkeletalBodySetup.h"
17
+ #include "EditorScriptingUtilities/Public/EditorAssetLibrary.h"
18
+ #include "Factories/AnimBlueprintFactory.h"
19
+ #include "Factories/AnimMontageFactory.h"
20
+ #include "Factories/BlendSpaceFactoryNew.h"
21
+ #include "UObject/UObjectGlobals.h"
22
+ #include "UObject/Package.h"
23
+ #include "Misc/PackageName.h"
24
+ #include "UObject/SavePackage.h"
25
+ #include "Dom/JsonObject.h"
26
+ #include "Dom/JsonValue.h"
27
+ #include "Animation/AnimData/IAnimationDataController.h"
28
+ #include "Animation/AnimData/AnimDataModel.h"
29
+ #include "Animation/AnimData/IAnimationDataModel.h"
30
+ #include "Editor.h"
31
+
32
+ void FAnimationHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
33
+ {
34
+ Registry.RegisterHandler(TEXT("list_anim_assets"), &ListAnimAssets);
35
+ Registry.RegisterHandler(TEXT("list_skeletal_meshes"), &ListSkeletalMeshes);
36
+ Registry.RegisterHandler(TEXT("get_skeleton_info"), &GetSkeletonInfo);
37
+ Registry.RegisterHandler(TEXT("list_sockets"), &ListSockets);
38
+ Registry.RegisterHandler(TEXT("get_physics_asset_info"), &GetPhysicsAssetInfo);
39
+ Registry.RegisterHandler(TEXT("read_anim_blueprint"), &ReadAnimBlueprint);
40
+ Registry.RegisterHandler(TEXT("read_anim_montage"), &ReadAnimMontage);
41
+ Registry.RegisterHandler(TEXT("read_anim_sequence"), &ReadAnimSequence);
42
+ Registry.RegisterHandler(TEXT("create_anim_blueprint"), &CreateAnimBlueprint);
43
+ Registry.RegisterHandler(TEXT("create_montage"), &CreateMontage);
44
+ Registry.RegisterHandler(TEXT("create_anim_montage"), &CreateMontage); // alias used by TS tools
45
+ Registry.RegisterHandler(TEXT("create_blendspace"), &CreateBlendspace);
46
+ Registry.RegisterHandler(TEXT("read_blendspace"), &ReadBlendspace);
47
+ Registry.RegisterHandler(TEXT("add_anim_notify"), &AddAnimNotify);
48
+ Registry.RegisterHandler(TEXT("create_sequence"), &CreateSequence);
49
+ Registry.RegisterHandler(TEXT("create_anim_sequence"), &CreateSequence); // alias
50
+ Registry.RegisterHandler(TEXT("set_bone_keyframes"), &SetBoneKeyframes);
51
+ Registry.RegisterHandler(TEXT("get_bone_transforms"), &GetBoneTransforms);
52
+ Registry.RegisterHandler(TEXT("set_montage_sequence"), &SetMontageSequence);
53
+ }
54
+
55
+ TSharedPtr<FJsonValue> FAnimationHandlers::ListAnimAssets(const TSharedPtr<FJsonObject>& Params)
56
+ {
57
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
58
+
59
+ bool bRecursive = true;
60
+ Params->TryGetBoolField(TEXT("recursive"), bRecursive);
61
+
62
+ IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")).Get();
63
+
64
+ // Asset class names to search for
65
+ TArray<FTopLevelAssetPath> ClassPaths;
66
+ ClassPaths.Add(FTopLevelAssetPath(TEXT("/Script/Engine"), TEXT("AnimSequence")));
67
+ ClassPaths.Add(FTopLevelAssetPath(TEXT("/Script/Engine"), TEXT("AnimMontage")));
68
+ ClassPaths.Add(FTopLevelAssetPath(TEXT("/Script/Engine"), TEXT("AnimBlueprint")));
69
+ ClassPaths.Add(FTopLevelAssetPath(TEXT("/Script/Engine"), TEXT("BlendSpace")));
70
+
71
+ TArray<TSharedPtr<FJsonValue>> AssetsArray;
72
+
73
+ for (const FTopLevelAssetPath& ClassPath : ClassPaths)
74
+ {
75
+ TArray<FAssetData> AssetDataList;
76
+ AssetRegistry.GetAssetsByClass(ClassPath, AssetDataList, bRecursive);
77
+
78
+ for (const FAssetData& AssetData : AssetDataList)
79
+ {
80
+ TSharedPtr<FJsonObject> AssetObj = MakeShared<FJsonObject>();
81
+ AssetObj->SetStringField(TEXT("name"), AssetData.AssetName.ToString());
82
+ AssetObj->SetStringField(TEXT("path"), AssetData.GetObjectPathString());
83
+ AssetObj->SetStringField(TEXT("class"), AssetData.AssetClassPath.GetAssetName().ToString());
84
+ AssetObj->SetStringField(TEXT("packagePath"), AssetData.PackagePath.ToString());
85
+ AssetsArray.Add(MakeShared<FJsonValueObject>(AssetObj));
86
+ }
87
+ }
88
+
89
+ Result->SetArrayField(TEXT("assets"), AssetsArray);
90
+ Result->SetNumberField(TEXT("count"), AssetsArray.Num());
91
+ Result->SetBoolField(TEXT("success"), true);
92
+
93
+ return MakeShared<FJsonValueObject>(Result);
94
+ }
95
+
96
+ TSharedPtr<FJsonValue> FAnimationHandlers::ListSkeletalMeshes(const TSharedPtr<FJsonObject>& Params)
97
+ {
98
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
99
+
100
+ bool bRecursive = true;
101
+ Params->TryGetBoolField(TEXT("recursive"), bRecursive);
102
+
103
+ IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")).Get();
104
+
105
+ TArray<FAssetData> AssetDataList;
106
+ AssetRegistry.GetAssetsByClass(FTopLevelAssetPath(TEXT("/Script/Engine"), TEXT("SkeletalMesh")), AssetDataList, bRecursive);
107
+
108
+ TArray<TSharedPtr<FJsonValue>> AssetsArray;
109
+ for (const FAssetData& AssetData : AssetDataList)
110
+ {
111
+ TSharedPtr<FJsonObject> AssetObj = MakeShared<FJsonObject>();
112
+ AssetObj->SetStringField(TEXT("name"), AssetData.AssetName.ToString());
113
+ AssetObj->SetStringField(TEXT("path"), AssetData.GetObjectPathString());
114
+ AssetObj->SetStringField(TEXT("packagePath"), AssetData.PackagePath.ToString());
115
+ AssetsArray.Add(MakeShared<FJsonValueObject>(AssetObj));
116
+ }
117
+
118
+ Result->SetArrayField(TEXT("assets"), AssetsArray);
119
+ Result->SetNumberField(TEXT("count"), AssetsArray.Num());
120
+ Result->SetBoolField(TEXT("success"), true);
121
+
122
+ return MakeShared<FJsonValueObject>(Result);
123
+ }
124
+
125
+ TSharedPtr<FJsonValue> FAnimationHandlers::GetSkeletonInfo(const TSharedPtr<FJsonObject>& Params)
126
+ {
127
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
128
+
129
+ FString AssetPath;
130
+ if (!Params->TryGetStringField(TEXT("assetPath"), AssetPath) && !Params->TryGetStringField(TEXT("path"), AssetPath))
131
+ {
132
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'path' or 'assetPath' parameter"));
133
+ Result->SetBoolField(TEXT("success"), false);
134
+ return MakeShared<FJsonValueObject>(Result);
135
+ }
136
+
137
+ UObject* LoadedAsset = UEditorAssetLibrary::LoadAsset(AssetPath);
138
+ USkeletalMesh* SkeletalMesh = Cast<USkeletalMesh>(LoadedAsset);
139
+ if (!SkeletalMesh)
140
+ {
141
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load SkeletalMesh at '%s'"), *AssetPath));
142
+ Result->SetBoolField(TEXT("success"), false);
143
+ return MakeShared<FJsonValueObject>(Result);
144
+ }
145
+
146
+ USkeleton* Skeleton = SkeletalMesh->GetSkeleton();
147
+ if (!Skeleton)
148
+ {
149
+ Result->SetStringField(TEXT("error"), TEXT("SkeletalMesh has no Skeleton"));
150
+ Result->SetBoolField(TEXT("success"), false);
151
+ return MakeShared<FJsonValueObject>(Result);
152
+ }
153
+
154
+ const FReferenceSkeleton& RefSkeleton = Skeleton->GetReferenceSkeleton();
155
+ TArray<TSharedPtr<FJsonValue>> BonesArray;
156
+ for (int32 i = 0; i < RefSkeleton.GetNum(); ++i)
157
+ {
158
+ TSharedPtr<FJsonObject> BoneObj = MakeShared<FJsonObject>();
159
+ BoneObj->SetStringField(TEXT("name"), RefSkeleton.GetBoneName(i).ToString());
160
+ BoneObj->SetNumberField(TEXT("index"), i);
161
+ BoneObj->SetNumberField(TEXT("parentIndex"), RefSkeleton.GetParentIndex(i));
162
+ BonesArray.Add(MakeShared<FJsonValueObject>(BoneObj));
163
+ }
164
+
165
+ Result->SetStringField(TEXT("skeletonName"), Skeleton->GetName());
166
+ Result->SetArrayField(TEXT("bones"), BonesArray);
167
+ Result->SetNumberField(TEXT("boneCount"), BonesArray.Num());
168
+ Result->SetBoolField(TEXT("success"), true);
169
+
170
+ return MakeShared<FJsonValueObject>(Result);
171
+ }
172
+
173
+ TSharedPtr<FJsonValue> FAnimationHandlers::ListSockets(const TSharedPtr<FJsonObject>& Params)
174
+ {
175
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
176
+
177
+ FString AssetPath;
178
+ if (!Params->TryGetStringField(TEXT("assetPath"), AssetPath) && !Params->TryGetStringField(TEXT("path"), AssetPath))
179
+ {
180
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'path' or 'assetPath' parameter"));
181
+ Result->SetBoolField(TEXT("success"), false);
182
+ return MakeShared<FJsonValueObject>(Result);
183
+ }
184
+
185
+ UObject* LoadedAsset = UEditorAssetLibrary::LoadAsset(AssetPath);
186
+ USkeletalMesh* SkeletalMesh = Cast<USkeletalMesh>(LoadedAsset);
187
+ if (!SkeletalMesh)
188
+ {
189
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load SkeletalMesh at '%s'"), *AssetPath));
190
+ Result->SetBoolField(TEXT("success"), false);
191
+ return MakeShared<FJsonValueObject>(Result);
192
+ }
193
+
194
+ USkeleton* Skeleton = SkeletalMesh->GetSkeleton();
195
+ if (!Skeleton)
196
+ {
197
+ Result->SetStringField(TEXT("error"), TEXT("SkeletalMesh has no Skeleton"));
198
+ Result->SetBoolField(TEXT("success"), false);
199
+ return MakeShared<FJsonValueObject>(Result);
200
+ }
201
+
202
+ TArray<TSharedPtr<FJsonValue>> SocketsArray;
203
+ const TArray<USkeletalMeshSocket*>& Sockets = Skeleton->Sockets;
204
+ for (const USkeletalMeshSocket* Socket : Sockets)
205
+ {
206
+ if (!Socket) continue;
207
+
208
+ TSharedPtr<FJsonObject> SocketObj = MakeShared<FJsonObject>();
209
+ SocketObj->SetStringField(TEXT("name"), Socket->SocketName.ToString());
210
+ SocketObj->SetStringField(TEXT("boneName"), Socket->BoneName.ToString());
211
+
212
+ TSharedPtr<FJsonObject> LocationObj = MakeShared<FJsonObject>();
213
+ LocationObj->SetNumberField(TEXT("x"), Socket->RelativeLocation.X);
214
+ LocationObj->SetNumberField(TEXT("y"), Socket->RelativeLocation.Y);
215
+ LocationObj->SetNumberField(TEXT("z"), Socket->RelativeLocation.Z);
216
+ SocketObj->SetObjectField(TEXT("relativeLocation"), LocationObj);
217
+
218
+ TSharedPtr<FJsonObject> RotationObj = MakeShared<FJsonObject>();
219
+ RotationObj->SetNumberField(TEXT("pitch"), Socket->RelativeRotation.Pitch);
220
+ RotationObj->SetNumberField(TEXT("yaw"), Socket->RelativeRotation.Yaw);
221
+ RotationObj->SetNumberField(TEXT("roll"), Socket->RelativeRotation.Roll);
222
+ SocketObj->SetObjectField(TEXT("relativeRotation"), RotationObj);
223
+
224
+ TSharedPtr<FJsonObject> ScaleObj = MakeShared<FJsonObject>();
225
+ ScaleObj->SetNumberField(TEXT("x"), Socket->RelativeScale.X);
226
+ ScaleObj->SetNumberField(TEXT("y"), Socket->RelativeScale.Y);
227
+ ScaleObj->SetNumberField(TEXT("z"), Socket->RelativeScale.Z);
228
+ SocketObj->SetObjectField(TEXT("relativeScale"), ScaleObj);
229
+
230
+ SocketsArray.Add(MakeShared<FJsonValueObject>(SocketObj));
231
+ }
232
+
233
+ Result->SetArrayField(TEXT("sockets"), SocketsArray);
234
+ Result->SetNumberField(TEXT("count"), SocketsArray.Num());
235
+ Result->SetBoolField(TEXT("success"), true);
236
+
237
+ return MakeShared<FJsonValueObject>(Result);
238
+ }
239
+
240
+ TSharedPtr<FJsonValue> FAnimationHandlers::GetPhysicsAssetInfo(const TSharedPtr<FJsonObject>& Params)
241
+ {
242
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
243
+
244
+ FString AssetPath;
245
+ if (!Params->TryGetStringField(TEXT("assetPath"), AssetPath) && !Params->TryGetStringField(TEXT("path"), AssetPath))
246
+ {
247
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'path' or 'assetPath' parameter"));
248
+ Result->SetBoolField(TEXT("success"), false);
249
+ return MakeShared<FJsonValueObject>(Result);
250
+ }
251
+
252
+ UObject* LoadedAsset = UEditorAssetLibrary::LoadAsset(AssetPath);
253
+ USkeletalMesh* SkeletalMesh = Cast<USkeletalMesh>(LoadedAsset);
254
+ if (!SkeletalMesh)
255
+ {
256
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load SkeletalMesh at '%s'"), *AssetPath));
257
+ Result->SetBoolField(TEXT("success"), false);
258
+ return MakeShared<FJsonValueObject>(Result);
259
+ }
260
+
261
+ UPhysicsAsset* PhysicsAsset = SkeletalMesh->GetPhysicsAsset();
262
+ if (!PhysicsAsset)
263
+ {
264
+ Result->SetStringField(TEXT("error"), TEXT("SkeletalMesh has no PhysicsAsset"));
265
+ Result->SetBoolField(TEXT("success"), false);
266
+ return MakeShared<FJsonValueObject>(Result);
267
+ }
268
+
269
+ Result->SetStringField(TEXT("physicsAssetName"), PhysicsAsset->GetName());
270
+ Result->SetStringField(TEXT("physicsAssetPath"), PhysicsAsset->GetPathName());
271
+ Result->SetNumberField(TEXT("bodyCount"), PhysicsAsset->SkeletalBodySetups.Num());
272
+
273
+ TArray<TSharedPtr<FJsonValue>> BodiesArray;
274
+ for (const TObjectPtr<USkeletalBodySetup>& BodySetup : PhysicsAsset->SkeletalBodySetups)
275
+ {
276
+ if (!BodySetup) continue;
277
+
278
+ TSharedPtr<FJsonObject> BodyObj = MakeShared<FJsonObject>();
279
+ BodyObj->SetStringField(TEXT("boneName"), BodySetup->BoneName.ToString());
280
+ BodiesArray.Add(MakeShared<FJsonValueObject>(BodyObj));
281
+ }
282
+
283
+ Result->SetArrayField(TEXT("bodies"), BodiesArray);
284
+ Result->SetBoolField(TEXT("success"), true);
285
+
286
+ return MakeShared<FJsonValueObject>(Result);
287
+ }
288
+
289
+ // ---------------------------------------------------------------------------
290
+ // read_anim_blueprint
291
+ // ---------------------------------------------------------------------------
292
+ TSharedPtr<FJsonValue> FAnimationHandlers::ReadAnimBlueprint(const TSharedPtr<FJsonObject>& Params)
293
+ {
294
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
295
+
296
+ FString AssetPath;
297
+ if (!Params->TryGetStringField(TEXT("assetPath"), AssetPath) && !Params->TryGetStringField(TEXT("path"), AssetPath))
298
+ {
299
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'path' or 'assetPath' parameter"));
300
+ Result->SetBoolField(TEXT("success"), false);
301
+ return MakeShared<FJsonValueObject>(Result);
302
+ }
303
+
304
+ UObject* LoadedAsset = UEditorAssetLibrary::LoadAsset(AssetPath);
305
+ UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(LoadedAsset);
306
+ if (!AnimBP)
307
+ {
308
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load AnimBlueprint at '%s'"), *AssetPath));
309
+ Result->SetBoolField(TEXT("success"), false);
310
+ return MakeShared<FJsonValueObject>(Result);
311
+ }
312
+
313
+ Result->SetStringField(TEXT("assetPath"), AssetPath);
314
+ Result->SetStringField(TEXT("name"), AnimBP->GetName());
315
+ Result->SetStringField(TEXT("class"), AnimBP->GetClass()->GetName());
316
+
317
+ // Target skeleton
318
+ USkeleton* TargetSkeleton = AnimBP->TargetSkeleton.Get();
319
+ if (TargetSkeleton)
320
+ {
321
+ Result->SetStringField(TEXT("targetSkeleton"), TargetSkeleton->GetPathName());
322
+ }
323
+ else
324
+ {
325
+ Result->SetField(TEXT("targetSkeleton"), MakeShared<FJsonValueNull>());
326
+ }
327
+
328
+ // Parent class
329
+ UClass* ParentClass = AnimBP->ParentClass;
330
+ if (ParentClass)
331
+ {
332
+ Result->SetStringField(TEXT("parentClass"), ParentClass->GetName());
333
+ }
334
+ else
335
+ {
336
+ Result->SetField(TEXT("parentClass"), MakeShared<FJsonValueNull>());
337
+ }
338
+
339
+ // Groups
340
+ TArray<TSharedPtr<FJsonValue>> GroupsArray;
341
+ for (const FAnimGroupInfo& Group : AnimBP->Groups)
342
+ {
343
+ GroupsArray.Add(MakeShared<FJsonValueString>(Group.Name.ToString()));
344
+ }
345
+ Result->SetArrayField(TEXT("groups"), GroupsArray);
346
+
347
+ // Variables from the generated class
348
+ TArray<TSharedPtr<FJsonValue>> VariablesArray;
349
+ UAnimBlueprintGeneratedClass* GenClass = Cast<UAnimBlueprintGeneratedClass>(AnimBP->GeneratedClass);
350
+ if (GenClass)
351
+ {
352
+ for (TFieldIterator<FProperty> PropIt(GenClass, EFieldIteratorFlags::ExcludeSuper); PropIt; ++PropIt)
353
+ {
354
+ FProperty* Prop = *PropIt;
355
+ if (!Prop) continue;
356
+
357
+ TSharedPtr<FJsonObject> VarObj = MakeShared<FJsonObject>();
358
+ VarObj->SetStringField(TEXT("name"), Prop->GetName());
359
+ VarObj->SetStringField(TEXT("type"), Prop->GetCPPType());
360
+ VariablesArray.Add(MakeShared<FJsonValueObject>(VarObj));
361
+ }
362
+ }
363
+ Result->SetArrayField(TEXT("variables"), VariablesArray);
364
+
365
+ // State machine names from the anim graph
366
+ TArray<TSharedPtr<FJsonValue>> StateMachinesArray;
367
+ if (GenClass)
368
+ {
369
+ for (const FBakedAnimationStateMachine& SM : GenClass->BakedStateMachines)
370
+ {
371
+ StateMachinesArray.Add(MakeShared<FJsonValueString>(SM.MachineName.ToString()));
372
+ }
373
+ }
374
+ Result->SetArrayField(TEXT("stateMachines"), StateMachinesArray);
375
+
376
+ Result->SetBoolField(TEXT("success"), true);
377
+ return MakeShared<FJsonValueObject>(Result);
378
+ }
379
+
380
+ // ---------------------------------------------------------------------------
381
+ // read_anim_montage
382
+ // ---------------------------------------------------------------------------
383
+ TSharedPtr<FJsonValue> FAnimationHandlers::ReadAnimMontage(const TSharedPtr<FJsonObject>& Params)
384
+ {
385
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
386
+
387
+ FString AssetPath;
388
+ if (!Params->TryGetStringField(TEXT("assetPath"), AssetPath) && !Params->TryGetStringField(TEXT("path"), AssetPath))
389
+ {
390
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'path' or 'assetPath' parameter"));
391
+ Result->SetBoolField(TEXT("success"), false);
392
+ return MakeShared<FJsonValueObject>(Result);
393
+ }
394
+
395
+ UObject* LoadedAsset = UEditorAssetLibrary::LoadAsset(AssetPath);
396
+ UAnimMontage* Montage = Cast<UAnimMontage>(LoadedAsset);
397
+ if (!Montage)
398
+ {
399
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load AnimMontage at '%s'"), *AssetPath));
400
+ Result->SetBoolField(TEXT("success"), false);
401
+ return MakeShared<FJsonValueObject>(Result);
402
+ }
403
+
404
+ Result->SetStringField(TEXT("assetPath"), AssetPath);
405
+ Result->SetStringField(TEXT("name"), Montage->GetName());
406
+ Result->SetStringField(TEXT("class"), Montage->GetClass()->GetName());
407
+
408
+ // Blend in / blend out times
409
+ Result->SetNumberField(TEXT("blendIn"), Montage->BlendIn.GetBlendTime());
410
+ Result->SetNumberField(TEXT("blendOut"), Montage->BlendOut.GetBlendTime());
411
+
412
+ // Sequence length and rate scale
413
+ Result->SetNumberField(TEXT("sequenceLength"), Montage->GetPlayLength());
414
+ Result->SetNumberField(TEXT("rateScale"), Montage->RateScale);
415
+
416
+ // Composite sections
417
+ TArray<TSharedPtr<FJsonValue>> SectionsArray;
418
+ for (const FCompositeSection& Section : Montage->CompositeSections)
419
+ {
420
+ TSharedPtr<FJsonObject> SecObj = MakeShared<FJsonObject>();
421
+ SecObj->SetStringField(TEXT("name"), Section.SectionName.ToString());
422
+ SecObj->SetNumberField(TEXT("startTime"), Section.GetTime());
423
+ SecObj->SetStringField(TEXT("nextSection"), Section.NextSectionName.ToString());
424
+ SectionsArray.Add(MakeShared<FJsonValueObject>(SecObj));
425
+ }
426
+ Result->SetArrayField(TEXT("sections"), SectionsArray);
427
+
428
+ // Notifies
429
+ TArray<TSharedPtr<FJsonValue>> NotifiesArray;
430
+ for (const FAnimNotifyEvent& NotifyEvent : Montage->Notifies)
431
+ {
432
+ TSharedPtr<FJsonObject> NotifyObj = MakeShared<FJsonObject>();
433
+ NotifyObj->SetStringField(TEXT("name"), NotifyEvent.NotifyName.ToString());
434
+ NotifyObj->SetNumberField(TEXT("triggerTime"), NotifyEvent.GetTriggerTime());
435
+ NotifyObj->SetNumberField(TEXT("duration"), NotifyEvent.GetDuration());
436
+ if (NotifyEvent.Notify)
437
+ {
438
+ NotifyObj->SetStringField(TEXT("class"), NotifyEvent.Notify->GetClass()->GetName());
439
+ }
440
+ NotifiesArray.Add(MakeShared<FJsonValueObject>(NotifyObj));
441
+ }
442
+ Result->SetArrayField(TEXT("notifies"), NotifiesArray);
443
+
444
+ // Slot anim tracks
445
+ TArray<TSharedPtr<FJsonValue>> SlotTracksArray;
446
+ for (const FSlotAnimationTrack& SlotTrack : Montage->SlotAnimTracks)
447
+ {
448
+ TSharedPtr<FJsonObject> TrackObj = MakeShared<FJsonObject>();
449
+ TrackObj->SetStringField(TEXT("slotName"), SlotTrack.SlotName.ToString());
450
+
451
+ TArray<TSharedPtr<FJsonValue>> SegmentsArray;
452
+ for (const FAnimSegment& Segment : SlotTrack.AnimTrack.AnimSegments)
453
+ {
454
+ TSharedPtr<FJsonObject> SegObj = MakeShared<FJsonObject>();
455
+ if (Segment.GetAnimReference())
456
+ {
457
+ SegObj->SetStringField(TEXT("animation"), Segment.GetAnimReference()->GetPathName());
458
+ }
459
+ else
460
+ {
461
+ SegObj->SetField(TEXT("animation"), MakeShared<FJsonValueNull>());
462
+ }
463
+ SegObj->SetNumberField(TEXT("startPos"), Segment.AnimStartTime);
464
+ SegObj->SetNumberField(TEXT("endPos"), Segment.AnimEndTime);
465
+ SegmentsArray.Add(MakeShared<FJsonValueObject>(SegObj));
466
+ }
467
+ TrackObj->SetArrayField(TEXT("segments"), SegmentsArray);
468
+ SlotTracksArray.Add(MakeShared<FJsonValueObject>(TrackObj));
469
+ }
470
+ Result->SetArrayField(TEXT("slotAnimTracks"), SlotTracksArray);
471
+
472
+ Result->SetBoolField(TEXT("success"), true);
473
+ return MakeShared<FJsonValueObject>(Result);
474
+ }
475
+
476
+ // ---------------------------------------------------------------------------
477
+ // read_anim_sequence
478
+ // ---------------------------------------------------------------------------
479
+ TSharedPtr<FJsonValue> FAnimationHandlers::ReadAnimSequence(const TSharedPtr<FJsonObject>& Params)
480
+ {
481
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
482
+
483
+ FString AssetPath;
484
+ if (!Params->TryGetStringField(TEXT("assetPath"), AssetPath) && !Params->TryGetStringField(TEXT("path"), AssetPath))
485
+ {
486
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'path' or 'assetPath' parameter"));
487
+ Result->SetBoolField(TEXT("success"), false);
488
+ return MakeShared<FJsonValueObject>(Result);
489
+ }
490
+
491
+ UObject* LoadedAsset = UEditorAssetLibrary::LoadAsset(AssetPath);
492
+ UAnimSequence* AnimSeq = Cast<UAnimSequence>(LoadedAsset);
493
+ if (!AnimSeq)
494
+ {
495
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load AnimSequence at '%s'"), *AssetPath));
496
+ Result->SetBoolField(TEXT("success"), false);
497
+ return MakeShared<FJsonValueObject>(Result);
498
+ }
499
+
500
+ Result->SetStringField(TEXT("assetPath"), AssetPath);
501
+ Result->SetStringField(TEXT("name"), AnimSeq->GetName());
502
+ Result->SetStringField(TEXT("class"), AnimSeq->GetClass()->GetName());
503
+
504
+ // Sequence length
505
+ Result->SetNumberField(TEXT("sequenceLength"), AnimSeq->GetPlayLength());
506
+
507
+ // Rate scale
508
+ Result->SetNumberField(TEXT("rateScale"), AnimSeq->RateScale);
509
+
510
+ // Number of frames and sampling frame rate
511
+ Result->SetNumberField(TEXT("numberOfFrames"), AnimSeq->GetNumberOfSampledKeys());
512
+ double SamplingRate = AnimSeq->GetSamplingFrameRate().AsDecimal();
513
+ Result->SetNumberField(TEXT("samplingFrameRate"), SamplingRate);
514
+
515
+ // Skeleton
516
+ USkeleton* Skeleton = AnimSeq->GetSkeleton();
517
+ if (Skeleton)
518
+ {
519
+ Result->SetStringField(TEXT("skeleton"), Skeleton->GetPathName());
520
+ }
521
+ else
522
+ {
523
+ Result->SetField(TEXT("skeleton"), MakeShared<FJsonValueNull>());
524
+ }
525
+
526
+ // Additive animation type
527
+ Result->SetBoolField(TEXT("isAdditive"), AnimSeq->AdditiveAnimType != EAdditiveAnimationType::AAT_None);
528
+
529
+ // Notifies
530
+ TArray<TSharedPtr<FJsonValue>> NotifiesArray;
531
+ for (const FAnimNotifyEvent& NotifyEvent : AnimSeq->Notifies)
532
+ {
533
+ TSharedPtr<FJsonObject> NotifyObj = MakeShared<FJsonObject>();
534
+ NotifyObj->SetStringField(TEXT("name"), NotifyEvent.NotifyName.ToString());
535
+ NotifyObj->SetNumberField(TEXT("triggerTime"), NotifyEvent.GetTriggerTime());
536
+ if (NotifyEvent.Notify)
537
+ {
538
+ NotifyObj->SetStringField(TEXT("class"), NotifyEvent.Notify->GetClass()->GetName());
539
+ }
540
+ NotifiesArray.Add(MakeShared<FJsonValueObject>(NotifyObj));
541
+ }
542
+ Result->SetArrayField(TEXT("notifies"), NotifiesArray);
543
+
544
+ // Curve names
545
+ TArray<TSharedPtr<FJsonValue>> CurvesArray;
546
+ const TArray<FFloatCurve>& Curves = AnimSeq->GetCurveData().FloatCurves;
547
+ for (const FFloatCurve& Curve : Curves)
548
+ {
549
+ CurvesArray.Add(MakeShared<FJsonValueString>(Curve.GetName().ToString()));
550
+ }
551
+ Result->SetArrayField(TEXT("curveNames"), CurvesArray);
552
+
553
+ Result->SetBoolField(TEXT("success"), true);
554
+ return MakeShared<FJsonValueObject>(Result);
555
+ }
556
+
557
+ // ---------------------------------------------------------------------------
558
+ // create_anim_blueprint
559
+ // ---------------------------------------------------------------------------
560
+ TSharedPtr<FJsonValue> FAnimationHandlers::CreateAnimBlueprint(const TSharedPtr<FJsonObject>& Params)
561
+ {
562
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
563
+
564
+ FString Name;
565
+ if (!Params->TryGetStringField(TEXT("name"), Name))
566
+ {
567
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'name' parameter"));
568
+ Result->SetBoolField(TEXT("success"), false);
569
+ return MakeShared<FJsonValueObject>(Result);
570
+ }
571
+
572
+ FString SkeletonPath;
573
+ if (!Params->TryGetStringField(TEXT("skeletonPath"), SkeletonPath))
574
+ {
575
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'skeletonPath' parameter"));
576
+ Result->SetBoolField(TEXT("success"), false);
577
+ return MakeShared<FJsonValueObject>(Result);
578
+ }
579
+
580
+ FString PackagePath = TEXT("/Game/Animations");
581
+ Params->TryGetStringField(TEXT("packagePath"), PackagePath);
582
+
583
+ FString ParentClassName;
584
+ Params->TryGetStringField(TEXT("parentClass"), ParentClassName);
585
+
586
+ // Load the skeleton
587
+ UObject* SkeletonAsset = UEditorAssetLibrary::LoadAsset(SkeletonPath);
588
+ USkeleton* Skeleton = Cast<USkeleton>(SkeletonAsset);
589
+ if (!Skeleton)
590
+ {
591
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load Skeleton at '%s'"), *SkeletonPath));
592
+ Result->SetBoolField(TEXT("success"), false);
593
+ return MakeShared<FJsonValueObject>(Result);
594
+ }
595
+
596
+ // Delete existing asset if present
597
+ FString FullAssetPath = PackagePath + TEXT("/") + Name;
598
+ UEditorAssetLibrary::DeleteAsset(FullAssetPath);
599
+
600
+ // Create the AnimBlueprint via factory
601
+ FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT("AssetTools"));
602
+ IAssetTools& AssetTools = AssetToolsModule.Get();
603
+
604
+ UAnimBlueprintFactory* Factory = NewObject<UAnimBlueprintFactory>();
605
+ Factory->TargetSkeleton = Skeleton;
606
+
607
+ // Resolve parent class if specified, default to UAnimInstance
608
+ if (!ParentClassName.IsEmpty())
609
+ {
610
+ UClass* FoundClass = FindFirstObject<UClass>(*ParentClassName);
611
+ if (FoundClass && FoundClass->IsChildOf(UAnimInstance::StaticClass()))
612
+ {
613
+ Factory->ParentClass = FoundClass;
614
+ }
615
+ }
616
+
617
+ UObject* NewAsset = AssetTools.CreateAsset(Name, PackagePath, UAnimBlueprint::StaticClass(), Factory);
618
+ if (!NewAsset)
619
+ {
620
+ Result->SetStringField(TEXT("error"), TEXT("Failed to create AnimBlueprint"));
621
+ Result->SetBoolField(TEXT("success"), false);
622
+ return MakeShared<FJsonValueObject>(Result);
623
+ }
624
+
625
+ UEditorAssetLibrary::SaveAsset(NewAsset->GetPathName());
626
+
627
+ Result->SetStringField(TEXT("path"), NewAsset->GetPathName());
628
+ Result->SetStringField(TEXT("name"), NewAsset->GetName());
629
+ Result->SetStringField(TEXT("class"), NewAsset->GetClass()->GetName());
630
+ Result->SetBoolField(TEXT("success"), true);
631
+
632
+ return MakeShared<FJsonValueObject>(Result);
633
+ }
634
+
635
+ // ---------------------------------------------------------------------------
636
+ // create_montage
637
+ // ---------------------------------------------------------------------------
638
+ TSharedPtr<FJsonValue> FAnimationHandlers::CreateMontage(const TSharedPtr<FJsonObject>& Params)
639
+ {
640
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
641
+
642
+ FString Name;
643
+ if (!Params->TryGetStringField(TEXT("name"), Name))
644
+ {
645
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'name' parameter"));
646
+ Result->SetBoolField(TEXT("success"), false);
647
+ return MakeShared<FJsonValueObject>(Result);
648
+ }
649
+
650
+ FString AnimSequencePath;
651
+ if (!Params->TryGetStringField(TEXT("animSequencePath"), AnimSequencePath))
652
+ {
653
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'animSequencePath' parameter"));
654
+ Result->SetBoolField(TEXT("success"), false);
655
+ return MakeShared<FJsonValueObject>(Result);
656
+ }
657
+
658
+ FString PackagePath = TEXT("/Game/Animations");
659
+ Params->TryGetStringField(TEXT("packagePath"), PackagePath);
660
+
661
+ // Load the source anim sequence
662
+ UObject* SourceAsset = UEditorAssetLibrary::LoadAsset(AnimSequencePath);
663
+ UAnimSequence* SourceSequence = Cast<UAnimSequence>(SourceAsset);
664
+ if (!SourceSequence)
665
+ {
666
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load AnimSequence at '%s'"), *AnimSequencePath));
667
+ Result->SetBoolField(TEXT("success"), false);
668
+ return MakeShared<FJsonValueObject>(Result);
669
+ }
670
+
671
+ // Delete existing asset if present
672
+ FString FullAssetPath = PackagePath + TEXT("/") + Name;
673
+ UEditorAssetLibrary::DeleteAsset(FullAssetPath);
674
+
675
+ // Create the montage via factory
676
+ FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT("AssetTools"));
677
+ IAssetTools& AssetTools = AssetToolsModule.Get();
678
+
679
+ UAnimMontageFactory* Factory = NewObject<UAnimMontageFactory>();
680
+ Factory->TargetSkeleton = SourceSequence->GetSkeleton();
681
+ Factory->SourceAnimation = SourceSequence;
682
+
683
+ UObject* NewAsset = AssetTools.CreateAsset(Name, PackagePath, UAnimMontage::StaticClass(), Factory);
684
+ if (!NewAsset)
685
+ {
686
+ Result->SetStringField(TEXT("error"), TEXT("Failed to create AnimMontage"));
687
+ Result->SetBoolField(TEXT("success"), false);
688
+ return MakeShared<FJsonValueObject>(Result);
689
+ }
690
+
691
+ UEditorAssetLibrary::SaveAsset(NewAsset->GetPathName());
692
+
693
+ Result->SetStringField(TEXT("path"), NewAsset->GetPathName());
694
+ Result->SetStringField(TEXT("name"), NewAsset->GetName());
695
+ Result->SetStringField(TEXT("class"), NewAsset->GetClass()->GetName());
696
+ Result->SetBoolField(TEXT("success"), true);
697
+
698
+ return MakeShared<FJsonValueObject>(Result);
699
+ }
700
+
701
+ // ---------------------------------------------------------------------------
702
+ // read_blendspace
703
+ // ---------------------------------------------------------------------------
704
+ TSharedPtr<FJsonValue> FAnimationHandlers::ReadBlendspace(const TSharedPtr<FJsonObject>& Params)
705
+ {
706
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
707
+
708
+ FString AssetPath;
709
+ if (!Params->TryGetStringField(TEXT("assetPath"), AssetPath) && !Params->TryGetStringField(TEXT("path"), AssetPath))
710
+ {
711
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'path' or 'assetPath' parameter"));
712
+ Result->SetBoolField(TEXT("success"), false);
713
+ return MakeShared<FJsonValueObject>(Result);
714
+ }
715
+
716
+ UObject* LoadedAsset = UEditorAssetLibrary::LoadAsset(AssetPath);
717
+ UBlendSpace* BlendSpace = Cast<UBlendSpace>(LoadedAsset);
718
+ if (!BlendSpace)
719
+ {
720
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load BlendSpace at '%s'"), *AssetPath));
721
+ Result->SetBoolField(TEXT("success"), false);
722
+ return MakeShared<FJsonValueObject>(Result);
723
+ }
724
+
725
+ Result->SetStringField(TEXT("assetPath"), AssetPath);
726
+ Result->SetStringField(TEXT("name"), BlendSpace->GetName());
727
+ Result->SetStringField(TEXT("class"), BlendSpace->GetClass()->GetName());
728
+
729
+ // Skeleton
730
+ USkeleton* Skeleton = BlendSpace->GetSkeleton();
731
+ if (Skeleton)
732
+ {
733
+ Result->SetStringField(TEXT("skeleton"), Skeleton->GetPathName());
734
+ }
735
+ else
736
+ {
737
+ Result->SetField(TEXT("skeleton"), MakeShared<FJsonValueNull>());
738
+ }
739
+
740
+ // Axis parameters
741
+ TArray<TSharedPtr<FJsonValue>> AxesArray;
742
+ for (int32 i = 0; i < 2; ++i)
743
+ {
744
+ const FBlendParameter& Param = BlendSpace->GetBlendParameter(i);
745
+ TSharedPtr<FJsonObject> AxisObj = MakeShared<FJsonObject>();
746
+ AxisObj->SetStringField(TEXT("displayName"), Param.DisplayName);
747
+ AxisObj->SetNumberField(TEXT("min"), Param.Min);
748
+ AxisObj->SetNumberField(TEXT("max"), Param.Max);
749
+ AxisObj->SetNumberField(TEXT("gridNum"), Param.GridNum);
750
+ AxesArray.Add(MakeShared<FJsonValueObject>(AxisObj));
751
+ }
752
+ Result->SetArrayField(TEXT("axes"), AxesArray);
753
+
754
+ // Sample points
755
+ TArray<TSharedPtr<FJsonValue>> SamplesArray;
756
+ const TArray<FBlendSample>& Samples = BlendSpace->GetBlendSamples();
757
+ for (const FBlendSample& Sample : Samples)
758
+ {
759
+ TSharedPtr<FJsonObject> SampleObj = MakeShared<FJsonObject>();
760
+ if (Sample.Animation)
761
+ {
762
+ SampleObj->SetStringField(TEXT("animation"), Sample.Animation->GetPathName());
763
+ }
764
+ else
765
+ {
766
+ SampleObj->SetField(TEXT("animation"), MakeShared<FJsonValueNull>());
767
+ }
768
+
769
+ TSharedPtr<FJsonObject> ValueObj = MakeShared<FJsonObject>();
770
+ ValueObj->SetNumberField(TEXT("x"), Sample.SampleValue.X);
771
+ ValueObj->SetNumberField(TEXT("y"), Sample.SampleValue.Y);
772
+ SampleObj->SetObjectField(TEXT("sampleValue"), ValueObj);
773
+
774
+ SamplesArray.Add(MakeShared<FJsonValueObject>(SampleObj));
775
+ }
776
+ Result->SetArrayField(TEXT("samples"), SamplesArray);
777
+ Result->SetNumberField(TEXT("sampleCount"), SamplesArray.Num());
778
+
779
+ Result->SetBoolField(TEXT("success"), true);
780
+ return MakeShared<FJsonValueObject>(Result);
781
+ }
782
+
783
+ // ---------------------------------------------------------------------------
784
+ // add_anim_notify
785
+ // ---------------------------------------------------------------------------
786
+ TSharedPtr<FJsonValue> FAnimationHandlers::AddAnimNotify(const TSharedPtr<FJsonObject>& Params)
787
+ {
788
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
789
+
790
+ FString AssetPath;
791
+ if (!Params->TryGetStringField(TEXT("assetPath"), AssetPath) && !Params->TryGetStringField(TEXT("path"), AssetPath))
792
+ {
793
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'path' or 'assetPath' parameter"));
794
+ Result->SetBoolField(TEXT("success"), false);
795
+ return MakeShared<FJsonValueObject>(Result);
796
+ }
797
+
798
+ FString NotifyName;
799
+ if (!Params->TryGetStringField(TEXT("notifyName"), NotifyName))
800
+ {
801
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'notifyName' parameter"));
802
+ Result->SetBoolField(TEXT("success"), false);
803
+ return MakeShared<FJsonValueObject>(Result);
804
+ }
805
+
806
+ double TriggerTime = 0.0;
807
+ if (!Params->TryGetNumberField(TEXT("triggerTime"), TriggerTime))
808
+ {
809
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'triggerTime' parameter"));
810
+ Result->SetBoolField(TEXT("success"), false);
811
+ return MakeShared<FJsonValueObject>(Result);
812
+ }
813
+
814
+ FString NotifyClassName;
815
+ Params->TryGetStringField(TEXT("notifyClass"), NotifyClassName);
816
+
817
+ // Load the animation asset — could be a montage or a sequence
818
+ UObject* LoadedAsset = UEditorAssetLibrary::LoadAsset(AssetPath);
819
+ UAnimSequenceBase* AnimAsset = Cast<UAnimSequenceBase>(LoadedAsset);
820
+ if (!AnimAsset)
821
+ {
822
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load AnimSequenceBase at '%s'"), *AssetPath));
823
+ Result->SetBoolField(TEXT("success"), false);
824
+ return MakeShared<FJsonValueObject>(Result);
825
+ }
826
+
827
+ // Clamp trigger time to valid range
828
+ float PlayLength = AnimAsset->GetPlayLength();
829
+ float ClampedTime = FMath::Clamp(static_cast<float>(TriggerTime), 0.0f, PlayLength);
830
+
831
+ // If a notify class is specified, try to find and instantiate it
832
+ UAnimNotify* NewNotify = nullptr;
833
+ if (!NotifyClassName.IsEmpty())
834
+ {
835
+ UClass* NotifyClass = FindFirstObject<UClass>(*NotifyClassName);
836
+ if (!NotifyClass)
837
+ {
838
+ // Try with full path prefix
839
+ NotifyClass = FindFirstObject<UClass>(*(TEXT("AnimNotify_") + NotifyClassName));
840
+ }
841
+ if (NotifyClass && NotifyClass->IsChildOf(UAnimNotify::StaticClass()))
842
+ {
843
+ NewNotify = NewObject<UAnimNotify>(AnimAsset, NotifyClass);
844
+ }
845
+ }
846
+
847
+ // Create the notify event
848
+ FAnimNotifyEvent& NewEvent = AnimAsset->Notifies.AddDefaulted_GetRef();
849
+ NewEvent.NotifyName = FName(*NotifyName);
850
+ NewEvent.Link(AnimAsset, ClampedTime);
851
+ NewEvent.TriggerTimeOffset = GetTriggerTimeOffsetForType(AnimAsset->CalculateOffsetForNotify(ClampedTime));
852
+ NewEvent.TrackIndex = 0;
853
+
854
+ if (NewNotify)
855
+ {
856
+ NewEvent.Notify = NewNotify;
857
+ }
858
+
859
+ AnimAsset->SortNotifies();
860
+ AnimAsset->PostEditChange();
861
+ AnimAsset->MarkPackageDirty();
862
+
863
+ // Save the asset
864
+ UEditorAssetLibrary::SaveAsset(AssetPath);
865
+
866
+ Result->SetStringField(TEXT("assetPath"), AssetPath);
867
+ Result->SetStringField(TEXT("notifyName"), NotifyName);
868
+ Result->SetNumberField(TEXT("triggerTime"), ClampedTime);
869
+ if (NewNotify)
870
+ {
871
+ Result->SetStringField(TEXT("notifyClass"), NewNotify->GetClass()->GetName());
872
+ }
873
+ Result->SetBoolField(TEXT("success"), true);
874
+
875
+ return MakeShared<FJsonValueObject>(Result);
876
+ }
877
+
878
+ // ---------------------------------------------------------------------------
879
+ // create_blendspace
880
+ // ---------------------------------------------------------------------------
881
+ TSharedPtr<FJsonValue> FAnimationHandlers::CreateBlendspace(const TSharedPtr<FJsonObject>& Params)
882
+ {
883
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
884
+
885
+ FString Name;
886
+ if (!Params->TryGetStringField(TEXT("name"), Name))
887
+ {
888
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'name' parameter"));
889
+ Result->SetBoolField(TEXT("success"), false);
890
+ return MakeShared<FJsonValueObject>(Result);
891
+ }
892
+
893
+ FString SkeletonPath;
894
+ if (!Params->TryGetStringField(TEXT("skeletonPath"), SkeletonPath))
895
+ {
896
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'skeletonPath' parameter"));
897
+ Result->SetBoolField(TEXT("success"), false);
898
+ return MakeShared<FJsonValueObject>(Result);
899
+ }
900
+
901
+ FString PackagePath = TEXT("/Game/Animations");
902
+ Params->TryGetStringField(TEXT("packagePath"), PackagePath);
903
+
904
+ FString AxisHorizontal = TEXT("Speed");
905
+ Params->TryGetStringField(TEXT("axisHorizontal"), AxisHorizontal);
906
+
907
+ FString AxisVertical = TEXT("Direction");
908
+ Params->TryGetStringField(TEXT("axisVertical"), AxisVertical);
909
+
910
+ double HorizontalMin = 0.0;
911
+ double HorizontalMax = 500.0;
912
+ double VerticalMin = -180.0;
913
+ double VerticalMax = 180.0;
914
+ Params->TryGetNumberField(TEXT("horizontalMin"), HorizontalMin);
915
+ Params->TryGetNumberField(TEXT("horizontalMax"), HorizontalMax);
916
+ Params->TryGetNumberField(TEXT("verticalMin"), VerticalMin);
917
+ Params->TryGetNumberField(TEXT("verticalMax"), VerticalMax);
918
+
919
+ // Load the skeleton
920
+ UObject* SkeletonAsset = UEditorAssetLibrary::LoadAsset(SkeletonPath);
921
+ USkeleton* Skeleton = Cast<USkeleton>(SkeletonAsset);
922
+ if (!Skeleton)
923
+ {
924
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load Skeleton at '%s'"), *SkeletonPath));
925
+ Result->SetBoolField(TEXT("success"), false);
926
+ return MakeShared<FJsonValueObject>(Result);
927
+ }
928
+
929
+ // Delete existing asset if present
930
+ FString FullAssetPath = PackagePath + TEXT("/") + Name;
931
+ UEditorAssetLibrary::DeleteAsset(FullAssetPath);
932
+
933
+ // Create the BlendSpace via factory
934
+ FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT("AssetTools"));
935
+ IAssetTools& AssetTools = AssetToolsModule.Get();
936
+
937
+ UBlendSpaceFactoryNew* Factory = NewObject<UBlendSpaceFactoryNew>();
938
+ Factory->TargetSkeleton = Skeleton;
939
+
940
+ UObject* NewAsset = AssetTools.CreateAsset(Name, PackagePath, UBlendSpace::StaticClass(), Factory);
941
+ if (!NewAsset)
942
+ {
943
+ Result->SetStringField(TEXT("error"), TEXT("Failed to create BlendSpace"));
944
+ Result->SetBoolField(TEXT("success"), false);
945
+ return MakeShared<FJsonValueObject>(Result);
946
+ }
947
+
948
+ // Configure axis settings on the newly created BlendSpace
949
+ UBlendSpace* BlendSpace = Cast<UBlendSpace>(NewAsset);
950
+ if (BlendSpace)
951
+ {
952
+ FBlendParameter& BlendParam0 = const_cast<FBlendParameter&>(BlendSpace->GetBlendParameter(0));
953
+ BlendParam0.DisplayName = AxisHorizontal;
954
+ BlendParam0.Min = HorizontalMin;
955
+ BlendParam0.Max = HorizontalMax;
956
+
957
+ FBlendParameter& BlendParam1 = const_cast<FBlendParameter&>(BlendSpace->GetBlendParameter(1));
958
+ BlendParam1.DisplayName = AxisVertical;
959
+ BlendParam1.Min = VerticalMin;
960
+ BlendParam1.Max = VerticalMax;
961
+ }
962
+
963
+ UEditorAssetLibrary::SaveAsset(NewAsset->GetPathName());
964
+
965
+ Result->SetStringField(TEXT("path"), NewAsset->GetPathName());
966
+ Result->SetStringField(TEXT("name"), NewAsset->GetName());
967
+ Result->SetStringField(TEXT("class"), NewAsset->GetClass()->GetName());
968
+ Result->SetStringField(TEXT("axisHorizontal"), AxisHorizontal);
969
+ Result->SetStringField(TEXT("axisVertical"), AxisVertical);
970
+ Result->SetBoolField(TEXT("success"), true);
971
+
972
+ return MakeShared<FJsonValueObject>(Result);
973
+ }
974
+
975
+ // ---------------------------------------------------------------------------
976
+ // create_sequence — Create a blank AnimSequence on a skeleton
977
+ // Params: name, skeletonPath, packagePath?, numFrames?, frameRate?
978
+ // ---------------------------------------------------------------------------
979
+ TSharedPtr<FJsonValue> FAnimationHandlers::CreateSequence(const TSharedPtr<FJsonObject>& Params)
980
+ {
981
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
982
+
983
+ FString Name;
984
+ if (!Params->TryGetStringField(TEXT("name"), Name))
985
+ {
986
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'name' parameter"));
987
+ Result->SetBoolField(TEXT("success"), false);
988
+ return MakeShared<FJsonValueObject>(Result);
989
+ }
990
+
991
+ FString SkeletonPath;
992
+ if (!Params->TryGetStringField(TEXT("skeletonPath"), SkeletonPath))
993
+ {
994
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'skeletonPath' parameter"));
995
+ Result->SetBoolField(TEXT("success"), false);
996
+ return MakeShared<FJsonValueObject>(Result);
997
+ }
998
+
999
+ FString PackagePath = TEXT("/Game/Animations");
1000
+ Params->TryGetStringField(TEXT("packagePath"), PackagePath);
1001
+
1002
+ double FrameRate = 30.0;
1003
+ Params->TryGetNumberField(TEXT("frameRate"), FrameRate);
1004
+
1005
+ double NumFrames = 30.0;
1006
+ Params->TryGetNumberField(TEXT("numFrames"), NumFrames);
1007
+
1008
+ // Load the skeleton
1009
+ UObject* SkeletonAsset = UEditorAssetLibrary::LoadAsset(SkeletonPath);
1010
+ USkeleton* Skeleton = Cast<USkeleton>(SkeletonAsset);
1011
+ if (!Skeleton)
1012
+ {
1013
+ // Try loading as skeletal mesh and getting its skeleton
1014
+ USkeletalMesh* SkelMesh = Cast<USkeletalMesh>(SkeletonAsset);
1015
+ if (SkelMesh)
1016
+ {
1017
+ Skeleton = SkelMesh->GetSkeleton();
1018
+ }
1019
+ }
1020
+ if (!Skeleton)
1021
+ {
1022
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load Skeleton at '%s'"), *SkeletonPath));
1023
+ Result->SetBoolField(TEXT("success"), false);
1024
+ return MakeShared<FJsonValueObject>(Result);
1025
+ }
1026
+
1027
+ // Delete existing asset if present
1028
+ FString FullAssetPath = PackagePath / Name;
1029
+ UEditorAssetLibrary::DeleteAsset(FullAssetPath);
1030
+
1031
+ // Create the package
1032
+ FString PackageName = PackagePath / Name;
1033
+ UPackage* Package = CreatePackage(*PackageName);
1034
+ if (!Package)
1035
+ {
1036
+ Result->SetStringField(TEXT("error"), TEXT("Failed to create package"));
1037
+ Result->SetBoolField(TEXT("success"), false);
1038
+ return MakeShared<FJsonValueObject>(Result);
1039
+ }
1040
+
1041
+ // Create the AnimSequence
1042
+ UAnimSequence* NewSeq = NewObject<UAnimSequence>(Package, *Name, RF_Public | RF_Standalone);
1043
+ if (!NewSeq)
1044
+ {
1045
+ Result->SetStringField(TEXT("error"), TEXT("Failed to create AnimSequence"));
1046
+ Result->SetBoolField(TEXT("success"), false);
1047
+ return MakeShared<FJsonValueObject>(Result);
1048
+ }
1049
+
1050
+ NewSeq->SetSkeleton(Skeleton);
1051
+
1052
+ // Set up frame count and duration via the data controller
1053
+ IAnimationDataController& Controller = NewSeq->GetController();
1054
+
1055
+ FFrameRate DesiredFrameRate(static_cast<int32>(FrameRate), 1);
1056
+ int32 FrameCount = static_cast<int32>(NumFrames);
1057
+
1058
+ // Initialize the data model first — required before any modifications
1059
+ Controller.InitializeModel();
1060
+ Controller.OpenBracket(NSLOCTEXT("MCP", "CreateSequence", "MCP Create Sequence"));
1061
+ Controller.SetFrameRate(DesiredFrameRate);
1062
+ Controller.SetNumberOfFrames(FrameCount);
1063
+ Controller.NotifyPopulated();
1064
+ Controller.CloseBracket(false);
1065
+
1066
+ // Clear any lingering transactions to prevent "transaction still pending" crashes
1067
+ // when users later interact with the asset in the editor (e.g. bake to control rig)
1068
+ GEditor->ResetTransaction(NSLOCTEXT("MCP", "CreateSequenceReset", "MCP Create Sequence Complete"));
1069
+
1070
+ NewSeq->PostEditChange();
1071
+ NewSeq->MarkPackageDirty();
1072
+
1073
+ // Save
1074
+ UEditorAssetLibrary::SaveAsset(FullAssetPath);
1075
+
1076
+ Result->SetStringField(TEXT("path"), FullAssetPath);
1077
+ Result->SetStringField(TEXT("name"), Name);
1078
+ Result->SetStringField(TEXT("skeleton"), Skeleton->GetPathName());
1079
+ Result->SetNumberField(TEXT("numFrames"), NumFrames);
1080
+ Result->SetNumberField(TEXT("frameRate"), FrameRate);
1081
+ Result->SetNumberField(TEXT("sequenceLength"), NewSeq->GetPlayLength());
1082
+ Result->SetBoolField(TEXT("success"), true);
1083
+
1084
+ return MakeShared<FJsonValueObject>(Result);
1085
+ }
1086
+
1087
+ // ---------------------------------------------------------------------------
1088
+ // set_bone_keyframes — Set bone transform keyframes on an AnimSequence
1089
+ // Params: assetPath, boneName, keyframes[]
1090
+ // Each keyframe: { frame, location?: {x,y,z}, rotation?: {x,y,z,w}, scale?: {x,y,z} }
1091
+ // ---------------------------------------------------------------------------
1092
+ TSharedPtr<FJsonValue> FAnimationHandlers::SetBoneKeyframes(const TSharedPtr<FJsonObject>& Params)
1093
+ {
1094
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1095
+
1096
+ FString AssetPath;
1097
+ if (!Params->TryGetStringField(TEXT("assetPath"), AssetPath) && !Params->TryGetStringField(TEXT("path"), AssetPath))
1098
+ {
1099
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'assetPath' parameter"));
1100
+ Result->SetBoolField(TEXT("success"), false);
1101
+ return MakeShared<FJsonValueObject>(Result);
1102
+ }
1103
+
1104
+ FString BoneName;
1105
+ if (!Params->TryGetStringField(TEXT("boneName"), BoneName))
1106
+ {
1107
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'boneName' parameter"));
1108
+ Result->SetBoolField(TEXT("success"), false);
1109
+ return MakeShared<FJsonValueObject>(Result);
1110
+ }
1111
+
1112
+ const TArray<TSharedPtr<FJsonValue>>* KeyframesArray;
1113
+ if (!Params->TryGetArrayField(TEXT("keyframes"), KeyframesArray))
1114
+ {
1115
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'keyframes' array parameter"));
1116
+ Result->SetBoolField(TEXT("success"), false);
1117
+ return MakeShared<FJsonValueObject>(Result);
1118
+ }
1119
+
1120
+ // Load the anim sequence
1121
+ UObject* LoadedAsset = UEditorAssetLibrary::LoadAsset(AssetPath);
1122
+ UAnimSequence* AnimSeq = Cast<UAnimSequence>(LoadedAsset);
1123
+ if (!AnimSeq)
1124
+ {
1125
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load AnimSequence at '%s'"), *AssetPath));
1126
+ Result->SetBoolField(TEXT("success"), false);
1127
+ return MakeShared<FJsonValueObject>(Result);
1128
+ }
1129
+
1130
+ // Verify bone exists in skeleton
1131
+ USkeleton* Skeleton = AnimSeq->GetSkeleton();
1132
+ if (!Skeleton)
1133
+ {
1134
+ Result->SetStringField(TEXT("error"), TEXT("AnimSequence has no Skeleton"));
1135
+ Result->SetBoolField(TEXT("success"), false);
1136
+ return MakeShared<FJsonValueObject>(Result);
1137
+ }
1138
+
1139
+ const FReferenceSkeleton& RefSkeleton = Skeleton->GetReferenceSkeleton();
1140
+ int32 BoneIndex = RefSkeleton.FindBoneIndex(FName(*BoneName));
1141
+ if (BoneIndex == INDEX_NONE)
1142
+ {
1143
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Bone '%s' not found in skeleton"), *BoneName));
1144
+ Result->SetBoolField(TEXT("success"), false);
1145
+ return MakeShared<FJsonValueObject>(Result);
1146
+ }
1147
+
1148
+ IAnimationDataController& Controller = AnimSeq->GetController();
1149
+ Controller.OpenBracket(NSLOCTEXT("MCP", "SetBoneKeyframes", "MCP Set Bone Keyframes"));
1150
+
1151
+ // Ensure bone track exists — add it if not present
1152
+ const FName BoneFName(*BoneName);
1153
+ const IAnimationDataModel* DataModel = AnimSeq->GetDataModel();
1154
+ if (!DataModel->IsValidBoneTrackName(BoneFName))
1155
+ {
1156
+ Controller.AddBoneCurve(BoneFName);
1157
+ }
1158
+
1159
+ // Get the reference pose transform for this bone as a default
1160
+ FTransform RefPose = RefSkeleton.GetRefBonePose()[BoneIndex];
1161
+
1162
+ // Collect all keyframes into arrays, then call SetBoneTrackKeys once
1163
+ TArray<FVector> Locations;
1164
+ TArray<FQuat> Rotations;
1165
+ TArray<FVector> Scales;
1166
+
1167
+ for (const TSharedPtr<FJsonValue>& KeyframeVal : *KeyframesArray)
1168
+ {
1169
+ const TSharedPtr<FJsonObject>* KeyframeObjPtr;
1170
+ if (!KeyframeVal->TryGetObject(KeyframeObjPtr)) continue;
1171
+ const TSharedPtr<FJsonObject>& KF = *KeyframeObjPtr;
1172
+
1173
+ // Start with reference pose as defaults
1174
+ FVector Location = RefPose.GetLocation();
1175
+ FQuat Rotation = RefPose.GetRotation();
1176
+ FVector Scale = RefPose.GetScale3D();
1177
+
1178
+ // Override with provided values
1179
+ const TSharedPtr<FJsonObject>* LocObj;
1180
+ if (KF->TryGetObjectField(TEXT("location"), LocObj))
1181
+ {
1182
+ (*LocObj)->TryGetNumberField(TEXT("x"), Location.X);
1183
+ (*LocObj)->TryGetNumberField(TEXT("y"), Location.Y);
1184
+ (*LocObj)->TryGetNumberField(TEXT("z"), Location.Z);
1185
+ }
1186
+
1187
+ const TSharedPtr<FJsonObject>* RotObj;
1188
+ if (KF->TryGetObjectField(TEXT("rotation"), RotObj))
1189
+ {
1190
+ (*RotObj)->TryGetNumberField(TEXT("x"), Rotation.X);
1191
+ (*RotObj)->TryGetNumberField(TEXT("y"), Rotation.Y);
1192
+ (*RotObj)->TryGetNumberField(TEXT("z"), Rotation.Z);
1193
+ (*RotObj)->TryGetNumberField(TEXT("w"), Rotation.W);
1194
+ }
1195
+
1196
+ const TSharedPtr<FJsonObject>* ScaleObj;
1197
+ if (KF->TryGetObjectField(TEXT("scale"), ScaleObj))
1198
+ {
1199
+ (*ScaleObj)->TryGetNumberField(TEXT("x"), Scale.X);
1200
+ (*ScaleObj)->TryGetNumberField(TEXT("y"), Scale.Y);
1201
+ (*ScaleObj)->TryGetNumberField(TEXT("z"), Scale.Z);
1202
+ }
1203
+
1204
+ Locations.Add(Location);
1205
+ Rotations.Add(Rotation);
1206
+ Scales.Add(Scale);
1207
+ }
1208
+
1209
+ // Set all keys at once
1210
+ int32 KeyframeCount = Locations.Num();
1211
+ if (KeyframeCount > 0)
1212
+ {
1213
+ Controller.SetBoneTrackKeys(BoneFName, Locations, Rotations, Scales);
1214
+ }
1215
+
1216
+ Controller.CloseBracket(false);
1217
+
1218
+ // Clear any lingering transactions to prevent "transaction still pending" crashes
1219
+ GEditor->ResetTransaction(NSLOCTEXT("MCP", "SetBoneKeyframesReset", "MCP Set Bone Keyframes Complete"));
1220
+
1221
+ AnimSeq->PostEditChange();
1222
+ AnimSeq->MarkPackageDirty();
1223
+ UEditorAssetLibrary::SaveAsset(AssetPath);
1224
+
1225
+ Result->SetStringField(TEXT("assetPath"), AssetPath);
1226
+ Result->SetStringField(TEXT("boneName"), BoneName);
1227
+ Result->SetNumberField(TEXT("keyframesSet"), KeyframeCount);
1228
+ Result->SetBoolField(TEXT("success"), true);
1229
+
1230
+ return MakeShared<FJsonValueObject>(Result);
1231
+ }
1232
+
1233
+ // ---------------------------------------------------------------------------
1234
+ // get_bone_transforms — Read reference pose transforms for specified bones
1235
+ // Params: skeletonPath, boneNames[]? (if omitted, returns all bones)
1236
+ // ---------------------------------------------------------------------------
1237
+ TSharedPtr<FJsonValue> FAnimationHandlers::GetBoneTransforms(const TSharedPtr<FJsonObject>& Params)
1238
+ {
1239
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1240
+
1241
+ FString AssetPath;
1242
+ if (!Params->TryGetStringField(TEXT("skeletonPath"), AssetPath)
1243
+ && !Params->TryGetStringField(TEXT("assetPath"), AssetPath)
1244
+ && !Params->TryGetStringField(TEXT("path"), AssetPath))
1245
+ {
1246
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'skeletonPath' parameter"));
1247
+ Result->SetBoolField(TEXT("success"), false);
1248
+ return MakeShared<FJsonValueObject>(Result);
1249
+ }
1250
+
1251
+ // Load skeleton (accept either USkeleton or USkeletalMesh)
1252
+ UObject* LoadedAsset = UEditorAssetLibrary::LoadAsset(AssetPath);
1253
+ USkeleton* Skeleton = Cast<USkeleton>(LoadedAsset);
1254
+ if (!Skeleton)
1255
+ {
1256
+ USkeletalMesh* SkelMesh = Cast<USkeletalMesh>(LoadedAsset);
1257
+ if (SkelMesh) Skeleton = SkelMesh->GetSkeleton();
1258
+ }
1259
+ if (!Skeleton)
1260
+ {
1261
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load Skeleton from '%s'"), *AssetPath));
1262
+ Result->SetBoolField(TEXT("success"), false);
1263
+ return MakeShared<FJsonValueObject>(Result);
1264
+ }
1265
+
1266
+ const FReferenceSkeleton& RefSkeleton = Skeleton->GetReferenceSkeleton();
1267
+ const TArray<FTransform>& RefPose = RefSkeleton.GetRefBonePose();
1268
+
1269
+ // Optional bone name filter
1270
+ TSet<FName> FilterBones;
1271
+ const TArray<TSharedPtr<FJsonValue>>* BoneNamesArray;
1272
+ if (Params->TryGetArrayField(TEXT("boneNames"), BoneNamesArray))
1273
+ {
1274
+ for (const TSharedPtr<FJsonValue>& Val : *BoneNamesArray)
1275
+ {
1276
+ FString BoneStr;
1277
+ if (Val->TryGetString(BoneStr))
1278
+ {
1279
+ FilterBones.Add(FName(*BoneStr));
1280
+ }
1281
+ }
1282
+ }
1283
+
1284
+ TArray<TSharedPtr<FJsonValue>> BonesArray;
1285
+ for (int32 i = 0; i < RefSkeleton.GetNum(); ++i)
1286
+ {
1287
+ FName BoneName = RefSkeleton.GetBoneName(i);
1288
+ if (FilterBones.Num() > 0 && !FilterBones.Contains(BoneName)) continue;
1289
+
1290
+ const FTransform& T = RefPose[i];
1291
+
1292
+ TSharedPtr<FJsonObject> BoneObj = MakeShared<FJsonObject>();
1293
+ BoneObj->SetStringField(TEXT("name"), BoneName.ToString());
1294
+ BoneObj->SetNumberField(TEXT("index"), i);
1295
+ BoneObj->SetNumberField(TEXT("parentIndex"), RefSkeleton.GetParentIndex(i));
1296
+
1297
+ TSharedPtr<FJsonObject> LocObj = MakeShared<FJsonObject>();
1298
+ LocObj->SetNumberField(TEXT("x"), T.GetLocation().X);
1299
+ LocObj->SetNumberField(TEXT("y"), T.GetLocation().Y);
1300
+ LocObj->SetNumberField(TEXT("z"), T.GetLocation().Z);
1301
+ BoneObj->SetObjectField(TEXT("location"), LocObj);
1302
+
1303
+ FQuat Q = T.GetRotation();
1304
+ TSharedPtr<FJsonObject> RotObj = MakeShared<FJsonObject>();
1305
+ RotObj->SetNumberField(TEXT("x"), Q.X);
1306
+ RotObj->SetNumberField(TEXT("y"), Q.Y);
1307
+ RotObj->SetNumberField(TEXT("z"), Q.Z);
1308
+ RotObj->SetNumberField(TEXT("w"), Q.W);
1309
+ BoneObj->SetObjectField(TEXT("rotation"), RotObj);
1310
+
1311
+ TSharedPtr<FJsonObject> ScaleObj = MakeShared<FJsonObject>();
1312
+ ScaleObj->SetNumberField(TEXT("x"), T.GetScale3D().X);
1313
+ ScaleObj->SetNumberField(TEXT("y"), T.GetScale3D().Y);
1314
+ ScaleObj->SetNumberField(TEXT("z"), T.GetScale3D().Z);
1315
+ BoneObj->SetObjectField(TEXT("scale"), ScaleObj);
1316
+
1317
+ BonesArray.Add(MakeShared<FJsonValueObject>(BoneObj));
1318
+ }
1319
+
1320
+ Result->SetArrayField(TEXT("bones"), BonesArray);
1321
+ Result->SetNumberField(TEXT("boneCount"), BonesArray.Num());
1322
+ Result->SetBoolField(TEXT("success"), true);
1323
+
1324
+ return MakeShared<FJsonValueObject>(Result);
1325
+ }
1326
+
1327
+ // ---------------------------------------------------------------------------
1328
+ // set_montage_sequence — Replace the animation sequence in a montage's slot track
1329
+ // Params: assetPath, animSequencePath, slotIndex? (default 0)
1330
+ // ---------------------------------------------------------------------------
1331
+ TSharedPtr<FJsonValue> FAnimationHandlers::SetMontageSequence(const TSharedPtr<FJsonObject>& Params)
1332
+ {
1333
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1334
+
1335
+ FString AssetPath;
1336
+ if (!Params->TryGetStringField(TEXT("assetPath"), AssetPath) && !Params->TryGetStringField(TEXT("path"), AssetPath))
1337
+ {
1338
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'assetPath' parameter"));
1339
+ Result->SetBoolField(TEXT("success"), false);
1340
+ return MakeShared<FJsonValueObject>(Result);
1341
+ }
1342
+
1343
+ FString AnimSequencePath;
1344
+ if (!Params->TryGetStringField(TEXT("animSequencePath"), AnimSequencePath))
1345
+ {
1346
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'animSequencePath' parameter"));
1347
+ Result->SetBoolField(TEXT("success"), false);
1348
+ return MakeShared<FJsonValueObject>(Result);
1349
+ }
1350
+
1351
+ double SlotIndex = 0.0;
1352
+ Params->TryGetNumberField(TEXT("slotIndex"), SlotIndex);
1353
+
1354
+ // Load the montage
1355
+ UObject* MontageAsset = UEditorAssetLibrary::LoadAsset(AssetPath);
1356
+ UAnimMontage* Montage = Cast<UAnimMontage>(MontageAsset);
1357
+ if (!Montage)
1358
+ {
1359
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load AnimMontage at '%s'"), *AssetPath));
1360
+ Result->SetBoolField(TEXT("success"), false);
1361
+ return MakeShared<FJsonValueObject>(Result);
1362
+ }
1363
+
1364
+ // Load the new sequence
1365
+ UObject* SeqAsset = UEditorAssetLibrary::LoadAsset(AnimSequencePath);
1366
+ UAnimSequence* NewSequence = Cast<UAnimSequence>(SeqAsset);
1367
+ if (!NewSequence)
1368
+ {
1369
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load AnimSequence at '%s'"), *AnimSequencePath));
1370
+ Result->SetBoolField(TEXT("success"), false);
1371
+ return MakeShared<FJsonValueObject>(Result);
1372
+ }
1373
+
1374
+ // Access the slot tracks
1375
+ int32 TrackIdx = static_cast<int32>(SlotIndex);
1376
+ if (TrackIdx < 0 || TrackIdx >= Montage->SlotAnimTracks.Num())
1377
+ {
1378
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Slot track index %d out of range (montage has %d tracks)"), TrackIdx, Montage->SlotAnimTracks.Num()));
1379
+ Result->SetBoolField(TEXT("success"), false);
1380
+ return MakeShared<FJsonValueObject>(Result);
1381
+ }
1382
+
1383
+ FSlotAnimationTrack& SlotTrack = Montage->SlotAnimTracks[TrackIdx];
1384
+
1385
+ // Replace the animation in all segments of this track
1386
+ int32 SegmentsUpdated = 0;
1387
+ for (FAnimSegment& Segment : SlotTrack.AnimTrack.AnimSegments)
1388
+ {
1389
+ Segment.SetAnimReference(NewSequence);
1390
+ Segment.AnimStartTime = 0.0f;
1391
+ Segment.AnimEndTime = NewSequence->GetPlayLength();
1392
+ SegmentsUpdated++;
1393
+ }
1394
+
1395
+ // If no segments exist, add one
1396
+ if (SegmentsUpdated == 0)
1397
+ {
1398
+ FAnimSegment NewSegment;
1399
+ NewSegment.SetAnimReference(NewSequence);
1400
+ NewSegment.AnimStartTime = 0.0f;
1401
+ NewSegment.AnimEndTime = NewSequence->GetPlayLength();
1402
+ SlotTrack.AnimTrack.AnimSegments.Add(NewSegment);
1403
+ SegmentsUpdated = 1;
1404
+ }
1405
+
1406
+ Montage->PostEditChange();
1407
+ Montage->MarkPackageDirty();
1408
+ UEditorAssetLibrary::SaveAsset(AssetPath);
1409
+
1410
+ Result->SetStringField(TEXT("assetPath"), AssetPath);
1411
+ Result->SetStringField(TEXT("animSequencePath"), AnimSequencePath);
1412
+ Result->SetStringField(TEXT("slotName"), SlotTrack.SlotName.ToString());
1413
+ Result->SetNumberField(TEXT("segmentsUpdated"), SegmentsUpdated);
1414
+ Result->SetNumberField(TEXT("sequenceLength"), NewSequence->GetPlayLength());
1415
+ Result->SetBoolField(TEXT("success"), true);
1416
+
1417
+ return MakeShared<FJsonValueObject>(Result);
1418
+ }