ue-mcp 1.2.3 → 1.3.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.
Files changed (255) hide show
  1. package/README.md +4 -3
  2. package/dist/action-class.js +56 -3
  3. package/dist/action-class.js.map +1 -1
  4. package/dist/bridge-timeouts.d.ts +64 -0
  5. package/dist/bridge-timeouts.js +118 -0
  6. package/dist/bridge-timeouts.js.map +1 -0
  7. package/dist/bridge.js +10 -2
  8. package/dist/bridge.js.map +1 -1
  9. package/dist/editor-control.d.ts +72 -5
  10. package/dist/editor-control.js +164 -140
  11. package/dist/editor-control.js.map +1 -1
  12. package/dist/editor-target.d.ts +55 -1
  13. package/dist/editor-target.js +107 -10
  14. package/dist/editor-target.js.map +1 -1
  15. package/dist/engine-observer.d.ts +59 -5
  16. package/dist/engine-observer.js +249 -29
  17. package/dist/engine-observer.js.map +1 -1
  18. package/dist/engine-root.d.ts +91 -0
  19. package/dist/engine-root.js +448 -0
  20. package/dist/engine-root.js.map +1 -0
  21. package/dist/epic-enrich.js +16 -1
  22. package/dist/epic-enrich.js.map +1 -1
  23. package/dist/feedback-cli.js +4 -0
  24. package/dist/feedback-cli.js.map +1 -1
  25. package/dist/feedback-deferred.d.ts +9 -0
  26. package/dist/feedback-deferred.js +2 -0
  27. package/dist/feedback-deferred.js.map +1 -1
  28. package/dist/feedback-fallback.d.ts +99 -0
  29. package/dist/feedback-fallback.js +173 -0
  30. package/dist/feedback-fallback.js.map +1 -0
  31. package/dist/flow/write-methods.js +20 -0
  32. package/dist/flow/write-methods.js.map +1 -1
  33. package/dist/index.js +41 -2
  34. package/dist/index.js.map +1 -1
  35. package/dist/lean-context.js +8 -3
  36. package/dist/lean-context.js.map +1 -1
  37. package/dist/locking.js +11 -2
  38. package/dist/locking.js.map +1 -1
  39. package/dist/plugin/loader.d.ts +4 -0
  40. package/dist/plugin/loader.js +22 -8
  41. package/dist/plugin/loader.js.map +1 -1
  42. package/dist/plugin/manifest.d.ts +72 -52
  43. package/dist/plugin/manifest.js +118 -21
  44. package/dist/plugin/manifest.js.map +1 -1
  45. package/dist/plugin-cli.js +16 -3
  46. package/dist/plugin-cli.js.map +1 -1
  47. package/dist/project.d.ts +11 -0
  48. package/dist/project.js +23 -2
  49. package/dist/project.js.map +1 -1
  50. package/dist/python-gate.d.ts +95 -0
  51. package/dist/python-gate.js +175 -0
  52. package/dist/python-gate.js.map +1 -0
  53. package/dist/tool-counts.json +16 -16
  54. package/dist/tools/animation.js +288 -24
  55. package/dist/tools/animation.js.map +1 -1
  56. package/dist/tools/asset.js +79 -14
  57. package/dist/tools/asset.js.map +1 -1
  58. package/dist/tools/audio.js +59 -5
  59. package/dist/tools/audio.js.map +1 -1
  60. package/dist/tools/blueprint.js +29 -12
  61. package/dist/tools/blueprint.js.map +1 -1
  62. package/dist/tools/chooser.js +4 -1
  63. package/dist/tools/chooser.js.map +1 -1
  64. package/dist/tools/editor.js +81 -43
  65. package/dist/tools/editor.js.map +1 -1
  66. package/dist/tools/feedback.js +214 -48
  67. package/dist/tools/feedback.js.map +1 -1
  68. package/dist/tools/foliage.js +27 -1
  69. package/dist/tools/foliage.js.map +1 -1
  70. package/dist/tools/gameplay.js +38 -11
  71. package/dist/tools/gameplay.js.map +1 -1
  72. package/dist/tools/gas.js +17 -5
  73. package/dist/tools/gas.js.map +1 -1
  74. package/dist/tools/landscape.js +22 -7
  75. package/dist/tools/landscape.js.map +1 -1
  76. package/dist/tools/level.js +322 -51
  77. package/dist/tools/level.js.map +1 -1
  78. package/dist/tools/material.js +10 -4
  79. package/dist/tools/material.js.map +1 -1
  80. package/dist/tools/niagara.js +3 -2
  81. package/dist/tools/niagara.js.map +1 -1
  82. package/dist/tools/pcg.js +6 -5
  83. package/dist/tools/pcg.js.map +1 -1
  84. package/dist/tools/plugins.js +2 -0
  85. package/dist/tools/plugins.js.map +1 -1
  86. package/dist/tools/project.js +47 -20
  87. package/dist/tools/project.js.map +1 -1
  88. package/dist/types.d.ts +24 -0
  89. package/dist/types.js +46 -3
  90. package/dist/types.js.map +1 -1
  91. package/dist/ue-mcp.default.yml +493 -125
  92. package/package.json +2 -2
  93. package/plugin/ue_mcp_bridge/NATIVE_RUNTIME_VISIBILITY.md +53 -0
  94. package/plugin/ue_mcp_bridge/NATIVE_STATIC_MESH_USAGE_PROGRESS.md +115 -0
  95. package/plugin/ue_mcp_bridge/README_MassHandlers.md +97 -0
  96. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.cpp +14 -0
  97. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.cpp +21 -4
  98. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.h +9 -0
  99. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerJsonProperty.h +395 -69
  100. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.cpp +79 -3
  101. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.h +25 -0
  102. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRig.cpp +3 -3
  103. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRigSequencer.cpp +3061 -0
  104. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_IKRetargeterAuthoring.cpp +961 -0
  105. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_IKRigAuthoring.cpp +900 -0
  106. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Pose.cpp +769 -0
  107. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Sequence.cpp +44 -3
  108. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_SkeletalLive.cpp +217 -11
  109. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_StateMachine.cpp +551 -24
  110. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Validation.cpp +735 -0
  111. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.cpp +352 -73
  112. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.h +8 -0
  113. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkProperties.cpp +1 -1
  114. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkRead.cpp +503 -0
  115. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkRead.h +21 -0
  116. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkUpsert.cpp +2 -2
  117. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Enum.cpp +2 -2
  118. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Geometry.cpp +902 -0
  119. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Geometry.h +33 -0
  120. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Import.cpp +478 -134
  121. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Mesh.cpp +5 -5
  122. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_MeshBoolean.cpp +933 -0
  123. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_MeshBoolean.h +35 -0
  124. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Redirectors.cpp +478 -0
  125. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_RenderTarget.cpp +1 -1
  126. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Sockets.cpp +4 -4
  127. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Struct.cpp +2 -2
  128. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Subobject.cpp +319 -0
  129. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers_SoundCue.cpp +3 -3
  130. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.cpp +482 -35
  131. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.h +10 -0
  132. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Collision.cpp +502 -0
  133. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Collision.h +43 -0
  134. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Functions.cpp +89 -7
  135. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Graph.cpp +17 -51
  136. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Internal.h +107 -0
  137. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Properties.cpp +10 -10
  138. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Search.cpp +648 -0
  139. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.cpp +5 -1
  140. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DiffHandlers.cpp +252 -1
  141. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DiffHandlers.h +10 -2
  142. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.cpp +252 -41
  143. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.h +4 -0
  144. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_Build.cpp +250 -27
  145. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_PIE.cpp +303 -125
  146. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_PIERuntime.cpp +73 -50
  147. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_RuntimeVisibility.cpp +757 -0
  148. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.cpp +11 -2
  149. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.h +3 -0
  150. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers_Batch.cpp +419 -0
  151. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.cpp +39 -161
  152. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.h +66 -1
  153. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_BTAuthoring.cpp +1323 -0
  154. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_BehaviorTree.cpp +1211 -0
  155. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +2 -0
  156. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +60 -0
  157. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers_Runtime.cpp +363 -12
  158. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/HandlerEditorState.h +84 -0
  159. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.cpp +1288 -36
  160. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.h +3 -0
  161. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers_Sculpt.cpp +41 -16
  162. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.cpp +959 -213
  163. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.h +66 -0
  164. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_BatchWrite.cpp +1571 -0
  165. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Convert.cpp +316 -0
  166. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Inspection.cpp +280 -0
  167. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_InstanceProjection.cpp +666 -0
  168. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_InstanceProjection_Internal.h +63 -0
  169. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Lights.cpp +43 -12
  170. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_MultiLevel.cpp +13 -47
  171. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_PostProcess.cpp +527 -0
  172. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Query.cpp +1169 -0
  173. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Refresh.cpp +723 -0
  174. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Save.cpp +325 -0
  175. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Spatial.cpp +360 -0
  176. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Trace.cpp +178 -58
  177. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Transient.cpp +337 -0
  178. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Volumes.cpp +25 -12
  179. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_WorldPartition.cpp +10 -9
  180. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_WorldPartitionSettings.cpp +686 -0
  181. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MassHandlers.cpp +467 -0
  182. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MassHandlers.h +22 -0
  183. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.cpp +452 -175
  184. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.h +5 -0
  185. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Build.cpp +597 -0
  186. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.cpp +17 -15
  187. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.cpp +38 -168
  188. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.cpp +39 -30
  189. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.cpp +392 -78
  190. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.h +2 -0
  191. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers_Internal.h +83 -0
  192. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SkeletalMeshHandlers.cpp +200 -0
  193. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SkeletalMeshHandlers.h +15 -0
  194. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.cpp +19 -12
  195. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/StateTreeHandlers.cpp +31 -1
  196. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.cpp +307 -69
  197. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.h +84 -0
  198. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers_Extraction.cpp +8 -41
  199. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers_Properties.cpp +33 -52
  200. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/ActorSelectorTests.cpp +344 -0
  201. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AnimationControlRigTimelineTests.cpp +60 -0
  202. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AnimationPoseTests.cpp +115 -0
  203. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetBulkReadTests.cpp +177 -0
  204. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetDeleteGuardTests.cpp +276 -0
  205. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetPathResolutionTests.cpp +243 -0
  206. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetPropertyPersistTests.cpp +213 -0
  207. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetSubobjectTests.cpp +235 -0
  208. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AutomationRunnerTests.cpp +72 -0
  209. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BehaviorTreeAuthoringTests.cpp +526 -0
  210. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BehaviorTreeNodeTests.cpp +334 -0
  211. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BlueprintHandlerSurfaceTests.cpp +362 -0
  212. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BridgeProtocolTests.cpp +14 -0
  213. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/CollisionQueryTests.cpp +288 -0
  214. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/DataTableRowWriteTests.cpp +453 -0
  215. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/EditorFunctionCallTests.cpp +223 -0
  216. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/FoliageBatchTests.cpp +147 -0
  217. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/GameThreadGateTests.cpp +82 -0
  218. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/GasLiveAttributeTests.cpp +183 -0
  219. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LandscapeSampleTests.cpp +119 -0
  220. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelBatchWriteTests.cpp +176 -0
  221. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelComponentTreeInstanceCountTests.cpp +189 -0
  222. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelConvertBrushesTests.cpp +100 -0
  223. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelInstanceProjectionTests.cpp +265 -0
  224. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelPostProcessOverrideTests.cpp +258 -0
  225. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelQueryComponentsTests.cpp +195 -0
  226. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelRefreshTests.cpp +142 -0
  227. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelSaveReportTests.cpp +125 -0
  228. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelScriptHandlersTests.cpp +42 -0
  229. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelStaticMeshUsageTests.cpp +412 -0
  230. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelTraceWorldScopeTests.cpp +133 -0
  231. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelTransientActorTests.cpp +205 -0
  232. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelWorldPartitionSettingsTests.cpp +168 -0
  233. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MCPInstancedStructPathTestTypes.h +51 -0
  234. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MCPInstancedStructPathTests.cpp +379 -0
  235. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MaterialBuildTests.cpp +238 -0
  236. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MeshBooleanTests.cpp +170 -0
  237. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MeshGeometryTests.cpp +116 -0
  238. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/PackageSaveExtensionTests.cpp +161 -0
  239. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/PackageWriteGuardTests.cpp +253 -0
  240. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/RuntimeVisibilityTests.cpp +143 -0
  241. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SequencerHandlerTests.cpp +660 -0
  242. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SequencerScrubTests.cpp +50 -0
  243. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SkeletonDiffTests.cpp +685 -0
  244. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/WidgetBlueprintResolveTests.cpp +145 -0
  245. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/UE_MCP_Bridge.cpp +80 -0
  246. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerFunctionCall.h +509 -0
  247. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerQuery.h +407 -0
  248. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerUtils.h +1008 -77
  249. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/UE_MCP_Bridge.Build.cs +87 -0
  250. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/MCPEngineStatus.cpp +152 -14
  251. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/Tests/MCPEngineStatusTests.cpp +65 -0
  252. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Public/MCPEngineStatus.h +32 -1
  253. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/UE_MCP_BridgeStatus.Build.cs +2 -0
  254. package/skills/ue-mcp-animation/SKILL.md +154 -0
  255. package/skills/ue-mcp-blueprint/SKILL.md +71 -0
@@ -36,6 +36,12 @@
36
36
  #include "AnimGraphNode_BlendSpacePlayer.h"
37
37
  #include "Rig/IKRigDefinition.h"
38
38
  #include "RigEditor/IKRigController.h"
39
+ #if UE_MCP_HAS_5_8_API
40
+ #include "Rig/Solvers/IKRigFullBodyIK.h"
41
+ #include "Retargeter/IKRetargetChainMapping.h"
42
+ #include "Retargeter/IKRetargetOps.h"
43
+ #include "Retargeter/IKRetargetProcessor.h"
44
+ #endif
39
45
  #include "PoseSearch/PoseSearchDatabase.h"
40
46
  #include "PoseSearch/PoseSearchSchema.h"
41
47
  #include "PoseSearch/PoseSearchDerivedData.h"
@@ -53,12 +59,82 @@
53
59
  #include "UObject/UObjectGlobals.h"
54
60
  #include "EditorAssetLibrary.h"
55
61
  #include "Editor.h"
62
+ #include "ScopedTransaction.h"
56
63
  #include "Dom/JsonObject.h"
57
64
  #include "Dom/JsonValue.h"
58
65
 
59
66
 
60
67
  #define UE_MCP_HAS_POSESEARCH_DATABASE_ASSET_API (ENGINE_MAJOR_VERSION > 5 || (ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 4))
61
68
 
69
+ #if UE_MCP_HAS_5_8_API
70
+ static TSharedPtr<FJsonObject> AnimationVectorToJson(const FVector& Value)
71
+ {
72
+ TSharedPtr<FJsonObject> Json = MakeShared<FJsonObject>();
73
+ Json->SetNumberField(TEXT("x"), Value.X);
74
+ Json->SetNumberField(TEXT("y"), Value.Y);
75
+ Json->SetNumberField(TEXT("z"), Value.Z);
76
+ return Json;
77
+ }
78
+
79
+ static TSharedPtr<FJsonObject> AnimationQuaternionToJson(const FQuat& Value)
80
+ {
81
+ TSharedPtr<FJsonObject> Json = MakeShared<FJsonObject>();
82
+ Json->SetNumberField(TEXT("x"), Value.X);
83
+ Json->SetNumberField(TEXT("y"), Value.Y);
84
+ Json->SetNumberField(TEXT("z"), Value.Z);
85
+ Json->SetNumberField(TEXT("w"), Value.W);
86
+ return Json;
87
+ }
88
+
89
+ static TSharedPtr<FJsonObject> AnimationTransformToJson(const FTransform& Value)
90
+ {
91
+ TSharedPtr<FJsonObject> Json = MakeShared<FJsonObject>();
92
+ Json->SetObjectField(TEXT("translation"), AnimationVectorToJson(Value.GetTranslation()));
93
+ Json->SetObjectField(TEXT("rotationQuaternion"), AnimationQuaternionToJson(Value.GetRotation()));
94
+ Json->SetObjectField(TEXT("scale"), AnimationVectorToJson(Value.GetScale3D()));
95
+ return Json;
96
+ }
97
+
98
+ // Processor initialization temporarily redirects editor-instance pointers on
99
+ // the serialized op stack. Restore them after validation and batch execution.
100
+ class FScopedBatchRetargetEditorInstanceRestore
101
+ {
102
+ struct FState
103
+ {
104
+ FIKRetargetOpBase* Op = nullptr;
105
+ FIKRetargetOpBase* OpEditorInstance = nullptr;
106
+ FIKRetargetOpSettingsBase* Settings = nullptr;
107
+ FIKRetargetOpSettingsBase* SettingsEditorInstance = nullptr;
108
+ };
109
+
110
+ public:
111
+ explicit FScopedBatchRetargetEditorInstanceRestore(UIKRetargeter* Retargeter)
112
+ {
113
+ if (!Retargeter) return;
114
+ States.Reserve(Retargeter->GetRetargetOps().Num());
115
+ for (const FInstancedStruct& OpStruct : Retargeter->GetRetargetOps())
116
+ {
117
+ FIKRetargetOpBase* Op = const_cast<FIKRetargetOpBase*>(OpStruct.GetPtr<FIKRetargetOpBase>());
118
+ if (!Op) continue;
119
+ FIKRetargetOpSettingsBase* Settings = Op->GetSettings();
120
+ States.Add({Op, Op->EditorInstance, Settings, Settings ? Settings->EditorInstance : nullptr});
121
+ }
122
+ }
123
+
124
+ ~FScopedBatchRetargetEditorInstanceRestore()
125
+ {
126
+ for (const FState& State : States)
127
+ {
128
+ State.Op->EditorInstance = State.OpEditorInstance;
129
+ if (State.Settings) State.Settings->EditorInstance = State.SettingsEditorInstance;
130
+ }
131
+ }
132
+
133
+ private:
134
+ TArray<FState> States;
135
+ };
136
+ #endif
137
+
62
138
  static int32 GetPoseSearchAnimationAssetCount(const UPoseSearchDatabase* Database)
63
139
  {
64
140
  #if UE_MCP_HAS_POSESEARCH_DATABASE_ASSET_API
@@ -1278,6 +1354,9 @@ TSharedPtr<FJsonValue> FAnimationHandlers::ReadIKRig(const TSharedPtr<FJsonObjec
1278
1354
  ChainObj->SetStringField(TEXT("name"), Chain.ChainName.ToString());
1279
1355
  ChainObj->SetStringField(TEXT("startBone"), Chain.StartBone.BoneName.ToString());
1280
1356
  ChainObj->SetStringField(TEXT("endBone"), Chain.EndBone.BoneName.ToString());
1357
+ #if UE_MCP_HAS_5_8_API
1358
+ ChainObj->SetStringField(TEXT("goal"), Chain.IKGoalName.ToString());
1359
+ #endif
1281
1360
  ChainsArray.Add(MakeShared<FJsonValueObject>(ChainObj));
1282
1361
  }
1283
1362
  Result->SetArrayField(TEXT("retargetChains"), ChainsArray);
@@ -1292,8 +1371,95 @@ TSharedPtr<FJsonValue> FAnimationHandlers::ReadIKRig(const TSharedPtr<FJsonObjec
1292
1371
  Result->SetStringField(TEXT("rootBone"), RigSkeleton.BoneNames[0].ToString());
1293
1372
  }
1294
1373
 
1295
- // Solvers - enumerate via reflection since GetSolverArray not available in all UE versions
1374
+ // UE 5.8 replaced the legacy UObject solver array with typed instanced
1375
+ // structs. Keep the reflection fallback for older supported engines.
1296
1376
  TArray<TSharedPtr<FJsonValue>> SolversArray;
1377
+ #if UE_MCP_HAS_5_8_API
1378
+ UIKRigController* Controller = UIKRigController::GetController(IKRig);
1379
+ if (!Controller)
1380
+ {
1381
+ return MCPError(TEXT("IKRigController unavailable"));
1382
+ }
1383
+
1384
+ Result->SetStringField(TEXT("retargetRoot"), Controller->GetRetargetRoot().ToString());
1385
+ Result->SetStringField(TEXT("rootMotionBone"), Controller->GetRootMotionBone().ToString());
1386
+ Result->SetStringField(
1387
+ TEXT("skeletonRootBone"),
1388
+ RigSkeleton.BoneNames.IsEmpty() ? TEXT("") : RigSkeleton.BoneNames[0].ToString());
1389
+
1390
+ TArray<TSharedPtr<FJsonValue>> GoalsArray;
1391
+ for (UIKRigEffectorGoal* Goal : Controller->GetAllGoals())
1392
+ {
1393
+ if (!Goal) continue;
1394
+ TSharedPtr<FJsonObject> GoalObj = MakeShared<FJsonObject>();
1395
+ GoalObj->SetStringField(TEXT("name"), Goal->GoalName.ToString());
1396
+ GoalObj->SetStringField(TEXT("bone"), Goal->BoneName.ToString());
1397
+ GoalObj->SetNumberField(TEXT("positionAlpha"), Goal->PositionAlpha);
1398
+ GoalObj->SetNumberField(TEXT("rotationAlpha"), Goal->RotationAlpha);
1399
+ GoalObj->SetObjectField(TEXT("currentTransform"), AnimationTransformToJson(Goal->CurrentTransform));
1400
+ GoalObj->SetObjectField(TEXT("initialTransform"), AnimationTransformToJson(Goal->InitialTransform));
1401
+
1402
+ TArray<TSharedPtr<FJsonValue>> ConnectedSolvers;
1403
+ for (int32 SolverIndex = 0; SolverIndex < Controller->GetNumSolvers(); ++SolverIndex)
1404
+ {
1405
+ if (Controller->IsGoalConnectedToSolver(Goal->GoalName, SolverIndex))
1406
+ {
1407
+ ConnectedSolvers.Add(MakeShared<FJsonValueNumber>(SolverIndex));
1408
+ }
1409
+ }
1410
+ GoalObj->SetArrayField(TEXT("connectedSolverIndices"), ConnectedSolvers);
1411
+ GoalsArray.Add(MakeShared<FJsonValueObject>(GoalObj));
1412
+ }
1413
+ Result->SetArrayField(TEXT("goals"), GoalsArray);
1414
+
1415
+ TArray<TSharedPtr<FJsonValue>> ExcludedBones;
1416
+ for (const FName BoneName : RigSkeleton.BoneNames)
1417
+ {
1418
+ if (Controller->GetBoneExcluded(BoneName))
1419
+ {
1420
+ ExcludedBones.Add(MakeShared<FJsonValueString>(BoneName.ToString()));
1421
+ }
1422
+ }
1423
+ Result->SetArrayField(TEXT("excludedBones"), ExcludedBones);
1424
+
1425
+ for (int32 SolverIndex = 0; SolverIndex < Controller->GetNumSolvers(); ++SolverIndex)
1426
+ {
1427
+ FInstancedStruct* SolverStruct = Controller->GetSolverStructAtIndex(SolverIndex);
1428
+ const UScriptStruct* SolverType = SolverStruct ? SolverStruct->GetScriptStruct() : nullptr;
1429
+ TSharedPtr<FJsonObject> SolverObj = MakeShared<FJsonObject>();
1430
+ SolverObj->SetNumberField(TEXT("index"), SolverIndex);
1431
+ SolverObj->SetStringField(TEXT("name"), Controller->GetSolverUniqueName(SolverIndex));
1432
+ SolverObj->SetStringField(TEXT("type"), SolverType ? SolverType->GetPathName() : TEXT(""));
1433
+ SolverObj->SetBoolField(TEXT("enabled"), Controller->GetSolverEnabled(SolverIndex));
1434
+ SolverObj->SetStringField(TEXT("startBone"), Controller->GetStartBone(SolverIndex).ToString());
1435
+ SolverObj->SetStringField(TEXT("endBone"), Controller->GetEndBone(SolverIndex).ToString());
1436
+
1437
+ TArray<TSharedPtr<FJsonValue>> Effectors;
1438
+ if (UIKRigFBIKController* FBIKController = Cast<UIKRigFBIKController>(Controller->GetSolverController(SolverIndex)))
1439
+ {
1440
+ SolverObj->SetBoolField(TEXT("fullBodyIK"), true);
1441
+ for (UIKRigEffectorGoal* Goal : Controller->GetAllGoals())
1442
+ {
1443
+ if (!Goal || !Controller->IsGoalConnectedToSolver(Goal->GoalName, SolverIndex)) continue;
1444
+ const FIKRigFBIKGoalSettings Settings = FBIKController->GetGoalSettings(Goal->GoalName);
1445
+ TSharedPtr<FJsonObject> EffectorObj = MakeShared<FJsonObject>();
1446
+ EffectorObj->SetStringField(TEXT("goal"), Goal->GoalName.ToString());
1447
+ EffectorObj->SetStringField(TEXT("bone"), Settings.BoneName.ToString());
1448
+ EffectorObj->SetNumberField(TEXT("chainDepth"), Settings.ChainDepth);
1449
+ EffectorObj->SetNumberField(TEXT("strengthAlpha"), Settings.StrengthAlpha);
1450
+ EffectorObj->SetNumberField(TEXT("pullChainAlpha"), Settings.PullChainAlpha);
1451
+ EffectorObj->SetNumberField(TEXT("pinRotation"), Settings.PinRotation);
1452
+ Effectors.Add(MakeShared<FJsonValueObject>(EffectorObj));
1453
+ }
1454
+ }
1455
+ else
1456
+ {
1457
+ SolverObj->SetBoolField(TEXT("fullBodyIK"), false);
1458
+ }
1459
+ SolverObj->SetArrayField(TEXT("effectors"), Effectors);
1460
+ SolversArray.Add(MakeShared<FJsonValueObject>(SolverObj));
1461
+ }
1462
+ #else
1297
1463
  FProperty* SolversProp = IKRig->GetClass()->FindPropertyByName(TEXT("Solvers"));
1298
1464
  if (SolversProp)
1299
1465
  {
@@ -1307,6 +1473,7 @@ TSharedPtr<FJsonValue> FAnimationHandlers::ReadIKRig(const TSharedPtr<FJsonObjec
1307
1473
  SolversArray.Add(MakeShared<FJsonValueObject>(SolverInfo));
1308
1474
  }
1309
1475
  }
1476
+ #endif
1310
1477
  Result->SetArrayField(TEXT("solvers"), SolversArray);
1311
1478
 
1312
1479
  return MCPResult(Result);
@@ -1340,7 +1507,78 @@ TSharedPtr<FJsonValue> FAnimationHandlers::CreateIKRetargeter(const TSharedPtr<F
1340
1507
  auto Created = MCPCreateAssetIdempotent<UObject>(Name, PackagePath, OnConflict, TEXT("IKRetargeter"), RetargeterClass, Factory);
1341
1508
  if (Created.EarlyReturn) return Created.EarlyReturn;
1342
1509
  UObject* NewAsset = Created.Asset;
1510
+ const bool bAutoMap = OptionalBool(Params, TEXT("autoMapChains"), true);
1511
+ int32 ChainsMapped = 0;
1512
+ FString SrcErr;
1513
+ FString TgtErr;
1514
+ FString OpsWarning;
1515
+
1516
+ #if UE_MCP_HAS_5_8_API
1517
+ UIKRetargeter* Retargeter = Cast<UIKRetargeter>(NewAsset);
1518
+ if (!Retargeter)
1519
+ {
1520
+ UEditorAssetLibrary::DeleteAsset(NewAsset->GetPathName());
1521
+ return MCPError(TEXT("Created asset is not an IKRetargeter"));
1522
+ }
1523
+
1524
+ UIKRigDefinition* SourceRig = SourceRigPath.IsEmpty()
1525
+ ? nullptr
1526
+ : LoadObject<UIKRigDefinition>(nullptr, *SourceRigPath);
1527
+ UIKRigDefinition* TargetRig = TargetRigPath.IsEmpty()
1528
+ ? nullptr
1529
+ : LoadObject<UIKRigDefinition>(nullptr, *TargetRigPath);
1530
+ if (!SourceRigPath.IsEmpty() && !SourceRig)
1531
+ {
1532
+ UEditorAssetLibrary::DeleteAsset(NewAsset->GetPathName());
1533
+ return MCPError(FString::Printf(TEXT("IKRig not found: %s"), *SourceRigPath));
1534
+ }
1535
+ if (!TargetRigPath.IsEmpty() && !TargetRig)
1536
+ {
1537
+ UEditorAssetLibrary::DeleteAsset(NewAsset->GetPathName());
1538
+ return MCPError(FString::Printf(TEXT("IKRig not found: %s"), *TargetRigPath));
1539
+ }
1540
+
1541
+ UIKRetargeterController* Controller = UIKRetargeterController::GetController(Retargeter);
1542
+ if (!Controller)
1543
+ {
1544
+ UEditorAssetLibrary::DeleteAsset(NewAsset->GetPathName());
1545
+ return MCPError(TEXT("IKRetargeterController unavailable"));
1546
+ }
1343
1547
 
1548
+ // The factory does not install the operational stack. AddDefaultOps is
1549
+ // idempotent and must precede per-op rig assignment and chain mapping.
1550
+ Controller->AddDefaultOps();
1551
+ if (SourceRig)
1552
+ {
1553
+ Controller->SetIKRig(ERetargetSourceOrTarget::Source, SourceRig);
1554
+ Controller->AssignIKRigToAllOps(ERetargetSourceOrTarget::Source, SourceRig);
1555
+ }
1556
+ if (TargetRig)
1557
+ {
1558
+ Controller->SetIKRig(ERetargetSourceOrTarget::Target, TargetRig);
1559
+ Controller->AssignIKRigToAllOps(ERetargetSourceOrTarget::Target, TargetRig);
1560
+ }
1561
+ if (bAutoMap)
1562
+ {
1563
+ Controller->AutoMapChains(EAutoMapChainType::Exact, true);
1564
+ }
1565
+ Controller->CleanAsset();
1566
+
1567
+ if ((SourceRig && Controller->GetIKRig(ERetargetSourceOrTarget::Source) != SourceRig)
1568
+ || (TargetRig && Controller->GetIKRig(ERetargetSourceOrTarget::Target) != TargetRig))
1569
+ {
1570
+ UEditorAssetLibrary::DeleteAsset(NewAsset->GetPathName());
1571
+ return MCPError(TEXT("IK Retargeter rig assignment failed readback validation"));
1572
+ }
1573
+
1574
+ if (TargetRig)
1575
+ {
1576
+ for (const FBoneChain& Chain : TargetRig->GetRetargetChains())
1577
+ {
1578
+ if (!Controller->GetSourceChain(Chain.ChainName).IsNone()) ++ChainsMapped;
1579
+ }
1580
+ }
1581
+ #else
1344
1582
  // Optionally set source / target IK Rigs via reflection
1345
1583
  auto SetRigProperty = [&](const FString& PropName, const FString& Path) -> FString
1346
1584
  {
@@ -1354,16 +1592,13 @@ TSharedPtr<FJsonValue> FAnimationHandlers::CreateIKRetargeter(const TSharedPtr<F
1354
1592
  return Prop->ImportText_Direct(*Export, Addr, NewAsset, PPF_None) ? TEXT("") : FString::Printf(TEXT("Failed to set %s"), *PropName);
1355
1593
  };
1356
1594
 
1357
- FString SrcErr = SetRigProperty(TEXT("SourceIKRigAsset"), SourceRigPath);
1358
- FString TgtErr = SetRigProperty(TEXT("TargetIKRigAsset"), TargetRigPath);
1595
+ SrcErr = SetRigProperty(TEXT("SourceIKRigAsset"), SourceRigPath);
1596
+ TgtErr = SetRigProperty(TEXT("TargetIKRigAsset"), TargetRigPath);
1359
1597
 
1360
1598
  // UE 5.7+ ops-stack initialization (#246). After CreateAsset the per-op
1361
1599
  // IK Rig refs and chain mappings are unset, so the retargeter cannot be
1362
1600
  // driven by an Anim Graph. Mirror what the Python workaround does:
1363
1601
  // AssignIKRigToAllOps(SOURCE/TARGET) + AutoMapChains.
1364
- bool bAutoMap = OptionalBool(Params, TEXT("autoMapChains"), true);
1365
- int32 ChainsMapped = 0;
1366
- FString OpsWarning;
1367
1602
  if (bAutoMap)
1368
1603
  {
1369
1604
  UIKRetargeter* Retargeter = Cast<UIKRetargeter>(NewAsset);
@@ -1431,9 +1666,14 @@ TSharedPtr<FJsonValue> FAnimationHandlers::CreateIKRetargeter(const TSharedPtr<F
1431
1666
  }
1432
1667
  }
1433
1668
  }
1669
+ #endif
1434
1670
 
1435
- NewAsset->MarkPackageDirty();
1436
- UEditorAssetLibrary::SaveAsset(NewAsset->GetPathName());
1671
+ if (!SaveAssetPackage(NewAsset))
1672
+ {
1673
+ const FString FailedPackage = NewAsset->GetOutermost()->GetName();
1674
+ UEditorAssetLibrary::DeleteAsset(NewAsset->GetPathName());
1675
+ return MCPError(FString::Printf(TEXT("Failed to save IKRetargeter package '%s'"), *FailedPackage));
1676
+ }
1437
1677
 
1438
1678
  auto Result = MCPSuccess();
1439
1679
  MCPSetCreated(Result);
@@ -1477,6 +1717,93 @@ TSharedPtr<FJsonValue> FAnimationHandlers::ReadIKRetargeter(const TSharedPtr<FJs
1477
1717
  Result->SetStringField(TEXT("sourceRig"), SrcRig ? SrcRig->GetPathName() : TEXT(""));
1478
1718
  Result->SetStringField(TEXT("targetRig"), TgtRig ? TgtRig->GetPathName() : TEXT(""));
1479
1719
 
1720
+ #if UE_MCP_HAS_5_8_API
1721
+ USkeletalMesh* SourcePreviewMesh = Controller->GetPreviewMesh(ERetargetSourceOrTarget::Source);
1722
+ USkeletalMesh* TargetPreviewMesh = Controller->GetPreviewMesh(ERetargetSourceOrTarget::Target);
1723
+ Result->SetStringField(TEXT("sourcePreviewMesh"), SourcePreviewMesh ? SourcePreviewMesh->GetPathName() : TEXT(""));
1724
+ Result->SetStringField(TEXT("targetPreviewMesh"), TargetPreviewMesh ? TargetPreviewMesh->GetPathName() : TEXT(""));
1725
+
1726
+ auto WritePoses = [&](const ERetargetSourceOrTarget Side, const TCHAR* CurrentField, const TCHAR* PosesField)
1727
+ {
1728
+ const FName CurrentPoseName = Controller->GetCurrentRetargetPoseName(Side);
1729
+ Result->SetStringField(CurrentField, CurrentPoseName.ToString());
1730
+
1731
+ TMap<FName, FIKRetargetPose>& Poses = Controller->GetRetargetPoses(Side);
1732
+ TArray<FName> PoseNames;
1733
+ Poses.GetKeys(PoseNames);
1734
+ PoseNames.Sort([](const FName& A, const FName& B)
1735
+ {
1736
+ return A.ToString() < B.ToString();
1737
+ });
1738
+
1739
+ TArray<TSharedPtr<FJsonValue>> PosesArray;
1740
+ for (const FName PoseName : PoseNames)
1741
+ {
1742
+ const FIKRetargetPose* Pose = Poses.Find(PoseName);
1743
+ if (!Pose) continue;
1744
+ TSharedPtr<FJsonObject> PoseObj = MakeShared<FJsonObject>();
1745
+ PoseObj->SetStringField(TEXT("name"), PoseName.ToString());
1746
+ PoseObj->SetBoolField(TEXT("current"), PoseName == CurrentPoseName);
1747
+ PoseObj->SetObjectField(TEXT("rootTranslationOffset"), AnimationVectorToJson(Pose->GetRootTranslationDelta()));
1748
+
1749
+ TArray<FName> OffsetBones;
1750
+ Pose->GetAllDeltaRotations().GetKeys(OffsetBones);
1751
+ OffsetBones.Sort([](const FName& A, const FName& B)
1752
+ {
1753
+ return A.ToString() < B.ToString();
1754
+ });
1755
+
1756
+ TArray<TSharedPtr<FJsonValue>> RotationOffsets;
1757
+ for (const FName BoneName : OffsetBones)
1758
+ {
1759
+ const FQuat* Rotation = Pose->GetAllDeltaRotations().Find(BoneName);
1760
+ if (!Rotation) continue;
1761
+ TSharedPtr<FJsonObject> OffsetObj = MakeShared<FJsonObject>();
1762
+ OffsetObj->SetStringField(TEXT("bone"), BoneName.ToString());
1763
+ OffsetObj->SetObjectField(TEXT("rotationQuaternion"), AnimationQuaternionToJson(*Rotation));
1764
+ RotationOffsets.Add(MakeShared<FJsonValueObject>(OffsetObj));
1765
+ }
1766
+ PoseObj->SetArrayField(TEXT("rotationOffsets"), RotationOffsets);
1767
+ PosesArray.Add(MakeShared<FJsonValueObject>(PoseObj));
1768
+ }
1769
+ Result->SetArrayField(PosesField, PosesArray);
1770
+ };
1771
+
1772
+ WritePoses(ERetargetSourceOrTarget::Source, TEXT("currentSourcePose"), TEXT("sourcePoses"));
1773
+ WritePoses(ERetargetSourceOrTarget::Target, TEXT("currentTargetPose"), TEXT("targetPoses"));
1774
+
1775
+ TArray<TSharedPtr<FJsonValue>> RetargetOps;
1776
+ for (int32 OpIndex = 0; OpIndex < Controller->GetNumRetargetOps(); ++OpIndex)
1777
+ {
1778
+ const FName OpName = Controller->GetOpName(OpIndex);
1779
+ FInstancedStruct* OpStruct = Controller->GetRetargetOpStructAtIndex(OpIndex);
1780
+ const UScriptStruct* OpType = OpStruct ? OpStruct->GetScriptStruct() : nullptr;
1781
+ TSharedPtr<FJsonObject> OpObj = MakeShared<FJsonObject>();
1782
+ OpObj->SetNumberField(TEXT("index"), OpIndex);
1783
+ OpObj->SetStringField(TEXT("name"), OpName.ToString());
1784
+ OpObj->SetStringField(TEXT("type"), OpType ? OpType->GetPathName() : TEXT(""));
1785
+ OpObj->SetBoolField(TEXT("enabled"), Controller->GetRetargetOpEnabled(OpIndex));
1786
+ OpObj->SetStringField(TEXT("parentOp"), Controller->GetParentOpByName(OpName).ToString());
1787
+ const UIKRigDefinition* OpTargetRig = Controller->GetTargetIKRigForOp(OpName);
1788
+ OpObj->SetStringField(TEXT("targetRig"), OpTargetRig ? OpTargetRig->GetPathName() : TEXT(""));
1789
+
1790
+ TArray<TSharedPtr<FJsonValue>> OpMappings;
1791
+ if (const FRetargetChainMapping* ChainMapping = Controller->GetChainMapping(OpName))
1792
+ {
1793
+ for (const FRetargetChainPair& Pair : ChainMapping->GetChainPairs())
1794
+ {
1795
+ TSharedPtr<FJsonObject> MappingObj = MakeShared<FJsonObject>();
1796
+ MappingObj->SetStringField(TEXT("targetChain"), Pair.TargetChainName.ToString());
1797
+ MappingObj->SetStringField(TEXT("sourceChain"), Pair.SourceChainName.ToString());
1798
+ OpMappings.Add(MakeShared<FJsonValueObject>(MappingObj));
1799
+ }
1800
+ }
1801
+ OpObj->SetArrayField(TEXT("chainMappings"), OpMappings);
1802
+ RetargetOps.Add(MakeShared<FJsonValueObject>(OpObj));
1803
+ }
1804
+ Result->SetArrayField(TEXT("retargetOps"), RetargetOps);
1805
+ #endif
1806
+
1480
1807
  // Chain mappings: for each target chain, report the source chain it's mapped to.
1481
1808
  TArray<TSharedPtr<FJsonValue>> Mappings;
1482
1809
  if (TgtRig)
@@ -1841,6 +2168,10 @@ TSharedPtr<FJsonValue> FAnimationHandlers::SetIKRigMesh(const TSharedPtr<FJsonOb
1841
2168
  {
1842
2169
  FString RigPath;
1843
2170
  if (auto Err = RequireStringAlt(Params, TEXT("rigPath"), TEXT("assetPath"), RigPath)) return Err;
2171
+ if (MCPIsProtectedAssetPath(RigPath))
2172
+ {
2173
+ return MCPError(FString::Printf(TEXT("Protected asset cannot be modified: %s"), *RigPath));
2174
+ }
1844
2175
  FString MeshPath;
1845
2176
  if (auto Err = RequireStringAlt(Params, TEXT("meshPath"), TEXT("skeletalMesh"), MeshPath)) return Err;
1846
2177
 
@@ -1877,9 +2208,18 @@ TSharedPtr<FJsonValue> FAnimationHandlers::SetIKRetargeterRig(const TSharedPtr<F
1877
2208
  {
1878
2209
  FString RetargeterPath;
1879
2210
  if (auto Err = RequireStringAlt(Params, TEXT("retargeterPath"), TEXT("assetPath"), RetargeterPath)) return Err;
2211
+ if (MCPIsProtectedAssetPath(RetargeterPath))
2212
+ {
2213
+ return MCPError(FString::Printf(TEXT("Protected asset cannot be modified: %s"), *RetargeterPath));
2214
+ }
1880
2215
  FString RigPath;
1881
2216
  if (auto Err = RequireStringAlt(Params, TEXT("rigPath"), TEXT("ikRig"), RigPath)) return Err;
1882
2217
  const FString Side = OptionalString(Params, TEXT("side"), TEXT("target"));
2218
+ if (!Side.Equals(TEXT("source"), ESearchCase::IgnoreCase)
2219
+ && !Side.Equals(TEXT("target"), ESearchCase::IgnoreCase))
2220
+ {
2221
+ return MCPError(TEXT("'side' must be 'source' or 'target'"));
2222
+ }
1883
2223
 
1884
2224
  UIKRetargeter* Retargeter = LoadObject<UIKRetargeter>(nullptr, *RetargeterPath);
1885
2225
  if (!Retargeter) return MCPError(FString::Printf(TEXT("IKRetargeter not found: %s"), *RetargeterPath));
@@ -1888,8 +2228,38 @@ TSharedPtr<FJsonValue> FAnimationHandlers::SetIKRetargeterRig(const TSharedPtr<F
1888
2228
 
1889
2229
  UIKRetargeterController* Controller = UIKRetargeterController::GetController(Retargeter);
1890
2230
  if (!Controller) return MCPError(TEXT("IKRetargeterController unavailable"));
1891
- Controller->SetIKRig(ParseSourceOrTarget(Side), IKRig);
1892
- SaveAssetPackage(Retargeter);
2231
+ const ERetargetSourceOrTarget SourceOrTarget = ParseSourceOrTarget(Side);
2232
+ bool bAssignmentFailed = false;
2233
+ {
2234
+ const FScopedTransaction Transaction(NSLOCTEXT("UE_MCP", "SetIKRetargeterRig", "Set IK Retargeter Rig"));
2235
+ Retargeter->Modify();
2236
+ #if UE_MCP_HAS_5_8_API
2237
+ if (Controller->GetNumRetargetOps() == 0)
2238
+ {
2239
+ Controller->AddDefaultOps();
2240
+ }
2241
+ Controller->SetIKRig(SourceOrTarget, IKRig);
2242
+ Controller->AssignIKRigToAllOps(SourceOrTarget, IKRig);
2243
+ Controller->CleanAsset();
2244
+ #else
2245
+ Controller->SetIKRig(SourceOrTarget, IKRig);
2246
+ #endif
2247
+ bAssignmentFailed = Controller->GetIKRig(SourceOrTarget) != IKRig;
2248
+ }
2249
+ if (bAssignmentFailed)
2250
+ {
2251
+ const bool bRolledBack = GEditor && GEditor->UndoTransaction();
2252
+ return MCPError(bRolledBack
2253
+ ? TEXT("IK Retargeter rig assignment failed readback validation and was rolled back")
2254
+ : TEXT("IK Retargeter rig assignment failed readback validation and could not be rolled back"));
2255
+ }
2256
+ if (!SaveAssetPackage(Retargeter))
2257
+ {
2258
+ const bool bRolledBack = GEditor && GEditor->UndoTransaction();
2259
+ return MCPError(bRolledBack
2260
+ ? TEXT("IK Retargeter rig assignment could not be saved and was rolled back")
2261
+ : TEXT("IK Retargeter rig assignment could not be saved and the editor transaction could not be rolled back"));
2262
+ }
1893
2263
 
1894
2264
  auto Result = MCPSuccess();
1895
2265
  MCPSetUpdated(Result);
@@ -1904,6 +2274,10 @@ TSharedPtr<FJsonValue> FAnimationHandlers::AutoAlignRetargetPose(const TSharedPt
1904
2274
  {
1905
2275
  FString RetargeterPath;
1906
2276
  if (auto Err = RequireStringAlt(Params, TEXT("retargeterPath"), TEXT("assetPath"), RetargeterPath)) return Err;
2277
+ if (MCPIsProtectedAssetPath(RetargeterPath))
2278
+ {
2279
+ return MCPError(FString::Printf(TEXT("Protected asset cannot be modified: %s"), *RetargeterPath));
2280
+ }
1907
2281
  const FString Side = OptionalString(Params, TEXT("side"), TEXT("target"));
1908
2282
 
1909
2283
  UIKRetargeter* Retargeter = LoadObject<UIKRetargeter>(nullptr, *RetargeterPath);
@@ -1927,6 +2301,10 @@ TSharedPtr<FJsonValue> FAnimationHandlers::ResetRetargetPose(const TSharedPtr<FJ
1927
2301
  {
1928
2302
  FString RetargeterPath;
1929
2303
  if (auto Err = RequireStringAlt(Params, TEXT("retargeterPath"), TEXT("assetPath"), RetargeterPath)) return Err;
2304
+ if (MCPIsProtectedAssetPath(RetargeterPath))
2305
+ {
2306
+ return MCPError(FString::Printf(TEXT("Protected asset cannot be modified: %s"), *RetargeterPath));
2307
+ }
1930
2308
  const FString Side = OptionalString(Params, TEXT("side"), TEXT("target"));
1931
2309
 
1932
2310
  UIKRetargeter* Retargeter = LoadObject<UIKRetargeter>(nullptr, *RetargeterPath);
@@ -1950,6 +2328,13 @@ TSharedPtr<FJsonValue> FAnimationHandlers::ResetRetargetPose(const TSharedPtr<FJ
1950
2328
  // ─── #701 batch_retarget_animations ─────────────────────────────────
1951
2329
  TSharedPtr<FJsonValue> FAnimationHandlers::BatchRetargetAnimations(const TSharedPtr<FJsonObject>& Params)
1952
2330
  {
2331
+ #if !(ENGINE_MAJOR_VERSION > 5 || (ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 8))
2332
+ auto Result = MakeShared<FJsonObject>();
2333
+ Result->SetBoolField(TEXT("success"), false);
2334
+ Result->SetStringField(TEXT("errorCode"), TEXT("unsupported_engine_version"));
2335
+ Result->SetStringField(TEXT("error"), TEXT("batch_retarget_animations requires Unreal Engine 5.8 or newer"));
2336
+ return MCPResult(Result);
2337
+ #else
1953
2338
  FString RetargeterPath;
1954
2339
  if (auto Err = RequireStringAlt(Params, TEXT("retargeterPath"), TEXT("assetPath"), RetargeterPath)) return Err;
1955
2340
  FString SourceMeshPath, TargetMeshPath;
@@ -1962,6 +2347,46 @@ TSharedPtr<FJsonValue> FAnimationHandlers::BatchRetargetAnimations(const TShared
1962
2347
  if (!SourceMesh) return MCPError(FString::Printf(TEXT("Source mesh not found: %s"), *SourceMeshPath));
1963
2348
  USkeletalMesh* TargetMesh = LoadObject<USkeletalMesh>(nullptr, *TargetMeshPath);
1964
2349
  if (!TargetMesh) return MCPError(FString::Printf(TEXT("Target mesh not found: %s"), *TargetMeshPath));
2350
+ if (SourceMesh == TargetMesh) return MCPError(TEXT("sourceMesh and targetMesh must be different"));
2351
+ if (OptionalBool(Params, TEXT("overwrite"), false))
2352
+ {
2353
+ return MCPError(TEXT("overwrite=true is not supported; choose a new output name/path so existing assets are never replaced"));
2354
+ }
2355
+
2356
+ bool bMappingInspectionAvailable = false;
2357
+ int32 TargetChainCount = 0;
2358
+ int32 MappedChainCount = 0;
2359
+ TArray<TSharedPtr<FJsonValue>> UnmappedTargetChains;
2360
+ if (UIKRetargeterController* Controller = UIKRetargeterController::GetController(Retargeter))
2361
+ {
2362
+ if (const UIKRigDefinition* TargetRig = Controller->GetIKRig(ERetargetSourceOrTarget::Target))
2363
+ {
2364
+ bMappingInspectionAvailable = true;
2365
+ for (const FBoneChain& TargetChain : TargetRig->GetRetargetChains())
2366
+ {
2367
+ ++TargetChainCount;
2368
+ if (Controller->GetSourceChain(TargetChain.ChainName).IsNone())
2369
+ {
2370
+ UnmappedTargetChains.Add(MakeShared<FJsonValueString>(TargetChain.ChainName.ToString()));
2371
+ }
2372
+ else
2373
+ {
2374
+ ++MappedChainCount;
2375
+ }
2376
+ }
2377
+ }
2378
+ }
2379
+ const bool bRequireCompleteMapping = OptionalBool(Params, TEXT("requireCompleteMapping"), false);
2380
+ if (bRequireCompleteMapping && !bMappingInspectionAvailable)
2381
+ {
2382
+ return MCPError(TEXT("Cannot verify complete mapping because the retargeter has no target IK Rig"));
2383
+ }
2384
+ if (bRequireCompleteMapping && !UnmappedTargetChains.IsEmpty())
2385
+ {
2386
+ return MCPError(FString::Printf(
2387
+ TEXT("Retargeter has %d unmapped target chain(s) and requireCompleteMapping=true"),
2388
+ UnmappedTargetChains.Num()));
2389
+ }
1965
2390
 
1966
2391
  const TArray<TSharedPtr<FJsonValue>>* AnimArr = nullptr;
1967
2392
  if (!Params->TryGetArrayField(TEXT("animPaths"), AnimArr) || !AnimArr || AnimArr->Num() == 0)
@@ -1969,40 +2394,123 @@ TSharedPtr<FJsonValue> FAnimationHandlers::BatchRetargetAnimations(const TShared
1969
2394
  return MCPError(TEXT("Missing 'animPaths' (array of AnimSequence paths to retarget)"));
1970
2395
  }
1971
2396
 
1972
- // The FIKRetargetBatchOperationInputs / UIKRetargetBatchOperation::RunBatchRetarget
1973
- // batch API is UE 5.8+. The 5.7 batch-retarget API differs; rather than a
1974
- // partial reimplementation, return a clear error below 5.8.
1975
- #if !(ENGINE_MAJOR_VERSION > 5 || (ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 8))
1976
- return MCPError(TEXT("batch_retarget_animations requires UE 5.8+ (the FIKRetargetBatchOperationInputs / RunBatchRetarget API is unavailable in this engine version)."));
1977
- #else
1978
2397
  FIKRetargetBatchOperationInputs Inputs;
1979
2398
  Inputs.SourceMesh = SourceMesh;
1980
2399
  Inputs.TargetMesh = TargetMesh;
1981
2400
  Inputs.IKRetargetAsset = Retargeter;
1982
- Inputs.bOverwriteExistingFiles = OptionalBool(Params, TEXT("overwrite"), false);
2401
+ Inputs.bOverwriteExistingFiles = false;
2402
+ Inputs.bIncludeReferencedAssets = false;
1983
2403
  Inputs.Prefix = OptionalString(Params, TEXT("prefix"));
1984
2404
  Inputs.Suffix = OptionalString(Params, TEXT("suffix"), TEXT("_Retargeted"));
1985
2405
  const FString TargetPath = OptionalString(Params, TEXT("outputPath"));
2406
+ if (!TargetPath.IsEmpty() && MCPIsProtectedAssetPath(TargetPath))
2407
+ {
2408
+ return MCPError(FString::Printf(TEXT("Protected output path is not allowed: %s"), *TargetPath));
2409
+ }
1986
2410
  if (TargetPath.IsEmpty()) { Inputs.bUseSourcePath = true; }
1987
2411
  else { Inputs.TargetPath = TargetPath; }
1988
2412
 
2413
+ TSet<FString> SeenPaths;
1989
2414
  int32 Loaded = 0;
1990
- for (const TSharedPtr<FJsonValue>& V : *AnimArr)
2415
+ for (int32 Index = 0; Index < AnimArr->Num(); ++Index)
1991
2416
  {
2417
+ const TSharedPtr<FJsonValue>& V = (*AnimArr)[Index];
1992
2418
  FString P;
1993
- if (!V->TryGetString(P) || P.IsEmpty()) continue;
1994
- if (UAnimSequence* Anim = LoadObject<UAnimSequence>(nullptr, *P))
2419
+ if (!V.IsValid() || !V->TryGetString(P) || P.IsEmpty())
1995
2420
  {
1996
- Inputs.AssetsToRetarget.Add(FAssetData(Anim));
1997
- ++Loaded;
2421
+ return MCPError(FString::Printf(TEXT("animPaths[%d] must be a non-empty AnimSequence asset path"), Index));
1998
2422
  }
2423
+ if (SeenPaths.Contains(P))
2424
+ {
2425
+ return MCPError(FString::Printf(TEXT("Duplicate animPaths entry: %s"), *P));
2426
+ }
2427
+ if (TargetPath.IsEmpty() && MCPIsProtectedAssetPath(P))
2428
+ {
2429
+ return MCPError(FString::Printf(TEXT("Cannot create a retargeted asset beside protected source path: %s"), *P));
2430
+ }
2431
+ UAnimSequence* Anim = LoadObject<UAnimSequence>(nullptr, *P);
2432
+ if (!Anim)
2433
+ {
2434
+ return MCPError(FString::Printf(TEXT("AnimSequence not found: %s"), *P));
2435
+ }
2436
+ if (!SourceMesh->GetSkeleton() || !Anim->GetSkeleton()
2437
+ || !SourceMesh->GetSkeleton()->IsCompatibleForEditor(Anim->GetSkeleton()))
2438
+ {
2439
+ return MCPError(FString::Printf(
2440
+ TEXT("AnimSequence skeleton is incompatible with sourceMesh: %s"), *P));
2441
+ }
2442
+ SeenPaths.Add(P);
2443
+ Inputs.AssetsToRetarget.Add(FAssetData(Anim));
2444
+ ++Loaded;
2445
+ }
2446
+
2447
+ FScopedBatchRetargetEditorInstanceRestore RestoreEditorInstances(Retargeter);
2448
+ FIKRetargetProcessor ValidationProcessor;
2449
+ FRetargetInitParameters ValidationParameters;
2450
+ ValidationParameters.SourceSkeletalMesh = SourceMesh;
2451
+ ValidationParameters.TargetSkeletalMesh = TargetMesh;
2452
+ ValidationParameters.RetargeterAsset = Retargeter;
2453
+ ValidationParameters.bSuppressWarnings = false;
2454
+ ValidationProcessor.Initialize(ValidationParameters);
2455
+ const TArray<FText> ValidationErrors = ValidationProcessor.Log.GetErrors();
2456
+ if (!ValidationProcessor.IsInitialized() || !ValidationErrors.IsEmpty())
2457
+ {
2458
+ return MCPError(!ValidationErrors.IsEmpty()
2459
+ ? FString::Printf(TEXT("IK Retargeter processor validation failed: %s"), *ValidationErrors[0].ToString())
2460
+ : TEXT("IK Retargeter processor validation failed to initialize"));
1999
2461
  }
2000
- if (Loaded == 0) return MCPError(TEXT("No valid AnimSequences resolved from animPaths"));
2001
2462
 
2002
2463
  const TArray<FAssetData> Created = UIKRetargetBatchOperation::RunBatchRetarget(Inputs);
2464
+ auto DeleteCreatedAssets = [&Created]()
2465
+ {
2466
+ TArray<FString> ResidualPaths;
2467
+ for (const FAssetData& AssetData : Created)
2468
+ {
2469
+ const FString ObjectPath = AssetData.GetObjectPathString();
2470
+ if (ObjectPath.IsEmpty())
2471
+ {
2472
+ ResidualPaths.Add(TEXT("<unknown output path>"));
2473
+ continue;
2474
+ }
2475
+ UEditorAssetLibrary::DeleteAsset(ObjectPath);
2476
+ if (UEditorAssetLibrary::DoesAssetExist(ObjectPath)) ResidualPaths.Add(ObjectPath);
2477
+ }
2478
+ return ResidualPaths;
2479
+ };
2480
+ if (Created.Num() != Loaded)
2481
+ {
2482
+ const TArray<FString> ResidualPaths = DeleteCreatedAssets();
2483
+ if (!ResidualPaths.IsEmpty())
2484
+ {
2485
+ return MCPError(FString::Printf(
2486
+ TEXT("Batch retarget created %d of %d requested assets and cleanup failed for: %s"),
2487
+ Created.Num(), Loaded, *FString::Join(ResidualPaths, TEXT(", "))));
2488
+ }
2489
+ return MCPError(FString::Printf(
2490
+ TEXT("Batch retarget created %d of %d requested assets; all new outputs were deleted"),
2491
+ Created.Num(), Loaded));
2492
+ }
2003
2493
 
2004
2494
  TArray<TSharedPtr<FJsonValue>> OutPaths;
2005
- for (const FAssetData& AD : Created) OutPaths.Add(MakeShared<FJsonValueString>(AD.GetObjectPathString()));
2495
+ for (const FAssetData& AD : Created)
2496
+ {
2497
+ UObject* CreatedAsset = AD.GetAsset();
2498
+ if (!CreatedAsset || !SaveAssetPackage(CreatedAsset))
2499
+ {
2500
+ const FString FailedPath = AD.GetObjectPathString();
2501
+ const TArray<FString> ResidualPaths = DeleteCreatedAssets();
2502
+ if (!ResidualPaths.IsEmpty())
2503
+ {
2504
+ return MCPError(FString::Printf(
2505
+ TEXT("Failed to save retargeted asset '%s' and cleanup failed for: %s"),
2506
+ *FailedPath, *FString::Join(ResidualPaths, TEXT(", "))));
2507
+ }
2508
+ return MCPError(FString::Printf(
2509
+ TEXT("Failed to save retargeted asset '%s'; all new outputs were deleted"),
2510
+ *FailedPath));
2511
+ }
2512
+ OutPaths.Add(MakeShared<FJsonValueString>(AD.GetObjectPathString()));
2513
+ }
2006
2514
 
2007
2515
  auto Result = MCPSuccess();
2008
2516
  MCPSetCreated(Result);
@@ -2010,6 +2518,25 @@ TSharedPtr<FJsonValue> FAnimationHandlers::BatchRetargetAnimations(const TShared
2010
2518
  Result->SetNumberField(TEXT("requested"), Loaded);
2011
2519
  Result->SetNumberField(TEXT("createdCount"), OutPaths.Num());
2012
2520
  Result->SetArrayField(TEXT("createdAssets"), OutPaths);
2521
+ Result->SetBoolField(TEXT("includedReferencedAssets"), false);
2522
+ Result->SetBoolField(TEXT("requireCompleteMapping"), bRequireCompleteMapping);
2523
+ Result->SetBoolField(TEXT("mappingInspectionAvailable"), bMappingInspectionAvailable);
2524
+ if (bMappingInspectionAvailable)
2525
+ {
2526
+ Result->SetNumberField(TEXT("targetChainCount"), TargetChainCount);
2527
+ Result->SetNumberField(TEXT("mappedChainCount"), MappedChainCount);
2528
+ Result->SetArrayField(TEXT("unmappedTargetChains"), UnmappedTargetChains);
2529
+ Result->SetBoolField(TEXT("mappingComplete"), UnmappedTargetChains.IsEmpty());
2530
+ if (!UnmappedTargetChains.IsEmpty())
2531
+ {
2532
+ Result->SetStringField(TEXT("mappingWarning"), FString::Printf(
2533
+ TEXT("Retarget completed with %d unmapped target chain(s); inspect deformation and compare sampled poses before accepting the output"),
2534
+ UnmappedTargetChains.Num()));
2535
+ }
2536
+ }
2537
+ TSharedPtr<FJsonObject> RollbackPayload = MakeShared<FJsonObject>();
2538
+ RollbackPayload->SetArrayField(TEXT("assetPaths"), OutPaths);
2539
+ MCPSetRollback(Result, TEXT("delete_asset_batch"), RollbackPayload);
2013
2540
  return MCPResult(Result);
2014
2541
  #endif
2015
2542
  }