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
@@ -1,9 +1,11 @@
1
1
  #include "SequencerHandlers.h"
2
+ #include "SequencerHandlers_Internal.h"
2
3
  #include "HandlerRegistry.h"
3
4
  #include "HandlerUtils.h"
4
5
 
5
6
  #include "LevelSequenceEditorBlueprintLibrary.h"
6
7
  #include "MovieSceneSequencePlayer.h"
8
+ #include "MovieSceneTimeUnit.h"
7
9
 
8
10
  #include "Subsystems/AssetEditorSubsystem.h"
9
11
  #include "HandlerAssetCreate.h"
@@ -21,6 +23,7 @@
21
23
  #include "Sections/MovieScene3DAttachSection.h"
22
24
  #include "Sections/MovieScene3DTransformSection.h"
23
25
  #include "Sections/MovieSceneCameraCutSection.h"
26
+ #include "Channels/MovieSceneChannel.h"
24
27
  #include "Channels/MovieSceneDoubleChannel.h"
25
28
  #include "Channels/MovieSceneFloatChannel.h"
26
29
  #include "Channels/MovieSceneChannelProxy.h"
@@ -43,6 +46,7 @@ void FSequencerHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
43
46
  Registry.RegisterHandler(TEXT("get_sequence_info"), &ReadSequenceInfo);
44
47
  Registry.RegisterHandler(TEXT("add_sequence_track"), &AddTrack);
45
48
  Registry.RegisterHandler(TEXT("play_sequence"), &SequenceControl);
49
+ Registry.RegisterHandler(TEXT("scrub_sequence"), &ScrubSequence);
46
50
  Registry.RegisterHandler(TEXT("set_sequence_playback_range"), &SetPlaybackRange);
47
51
  Registry.RegisterHandler(TEXT("add_sequence_section"), &AddSection);
48
52
  Registry.RegisterHandler(TEXT("set_sequence_keyframes"), &SetKeyframes);
@@ -66,13 +70,16 @@ namespace
66
70
  return Seq;
67
71
  }
68
72
 
69
- // Resolve (creating if needed) the actor's possessable binding GUID.
70
- bool ResolveActorBinding(ULevelSequence* Sequence, UMovieScene* MovieScene, const FString& ActorLabel, FGuid& OutGuid, FString& OutError)
73
+ // Resolve (creating if needed) the possessable binding GUID for an actor the
74
+ // caller already selected. #983: the selection happens through
75
+ // MCPResolveActor at the call site, so a duplicated label is refused before
76
+ // a binding is created against the wrong actor.
77
+ bool ResolveActorBinding(ULevelSequence* Sequence, UMovieScene* MovieScene, AActor* TargetActor, FGuid& OutGuid, FString& OutError)
71
78
  {
72
79
  UWorld* World = GetEditorWorld();
73
80
  if (!World) { OutError = TEXT("No editor world available"); return false; }
74
- AActor* TargetActor = FindActorByLabel(World, ActorLabel);
75
- if (!TargetActor) { OutError = FString::Printf(TEXT("Actor not found: %s"), *ActorLabel); return false; }
81
+ if (!TargetActor) { OutError = TEXT("Target actor is null"); return false; }
82
+ const FString ActorLabel = TargetActor->GetActorLabel();
76
83
 
77
84
  for (int32 i = 0; i < MovieScene->GetPossessableCount(); ++i)
78
85
  {
@@ -158,15 +165,30 @@ TSharedPtr<FJsonValue> FSequencerHandlers::ReadSequenceInfo(const TSharedPtr<FJs
158
165
  Result->SetStringField(TEXT("name"), Sequence->GetName());
159
166
  Result->SetStringField(TEXT("path"), Sequence->GetPathName());
160
167
 
161
- // Display rate
162
- FFrameRate DisplayRate = MovieScene->GetDisplayRate();
168
+ const FFrameRate DisplayRate = MovieScene->GetDisplayRate();
169
+ const FFrameRate TickResolution = MovieScene->GetTickResolution();
170
+ if (!UEMCP::SequencerInfo::IsUsableFrameRate(DisplayRate) ||
171
+ !UEMCP::SequencerInfo::IsUsableFrameRate(TickResolution))
172
+ {
173
+ return MCPError(FString::Printf(
174
+ TEXT("LevelSequence has invalid timing rates (displayRate=%d/%d, tickResolution=%d/%d)"),
175
+ DisplayRate.Numerator,
176
+ DisplayRate.Denominator,
177
+ TickResolution.Numerator,
178
+ TickResolution.Denominator));
179
+ }
180
+
163
181
  TSharedPtr<FJsonObject> DisplayRateObj = MakeShared<FJsonObject>();
164
- DisplayRateObj->SetNumberField(TEXT("numerator"), DisplayRate.Numerator);
165
- DisplayRateObj->SetNumberField(TEXT("denominator"), DisplayRate.Denominator);
182
+ UEMCP::SequencerInfo::SetFrameRateFields(*DisplayRateObj, DisplayRate);
166
183
  Result->SetObjectField(TEXT("displayRate"), DisplayRateObj);
167
184
 
168
- // Playback range
169
- TRange<FFrameNumber> PlaybackRange = MovieScene->GetPlaybackRange();
185
+ TSharedPtr<FJsonObject> TickResolutionObj = MakeShared<FJsonObject>();
186
+ UEMCP::SequencerInfo::SetFrameRateFields(*TickResolutionObj, TickResolution);
187
+ Result->SetObjectField(TEXT("tickResolution"), TickResolutionObj);
188
+
189
+ // Keep the legacy startFrame/endFrame values in tick-resolution units.
190
+ // The explicit fields below remove that ambiguity without changing callers.
191
+ const TRange<FFrameNumber> PlaybackRange = MovieScene->GetPlaybackRange();
170
192
  TSharedPtr<FJsonObject> RangeObj = MakeShared<FJsonObject>();
171
193
  if (PlaybackRange.HasLowerBound())
172
194
  {
@@ -176,27 +198,62 @@ TSharedPtr<FJsonValue> FSequencerHandlers::ReadSequenceInfo(const TSharedPtr<FJs
176
198
  {
177
199
  RangeObj->SetNumberField(TEXT("endFrame"), PlaybackRange.GetUpperBoundValue().Value);
178
200
  }
201
+ UEMCP::SequencerInfo::SetTimingRangeFields(*RangeObj, PlaybackRange, TickResolution, DisplayRate);
179
202
  Result->SetObjectField(TEXT("playbackRange"), RangeObj);
180
203
 
181
- // #52: optional section-level detail (attach sockets, first transform key values)
182
204
  const bool bIncludeDetails = OptionalBool(Params, TEXT("includeSectionDetails"));
205
+ UEMCP::SequencerInfo::FDetailBudget DetailBudget;
183
206
 
184
- auto ExtractSectionDetails = [&](UMovieSceneTrack* Track, TSharedPtr<FJsonObject>& TrackObj)
207
+ if (bIncludeDetails)
208
+ {
209
+ TSharedPtr<FJsonObject> LimitsObj = MakeShared<FJsonObject>();
210
+ LimitsObj->SetNumberField(TEXT("maxSections"), UEMCP::SequencerInfo::MaxDetailedSections);
211
+ LimitsObj->SetNumberField(TEXT("maxChannels"), UEMCP::SequencerInfo::MaxDetailedChannels);
212
+ LimitsObj->SetNumberField(TEXT("maxKeyTimes"), UEMCP::SequencerInfo::MaxDetailedKeyTimes);
213
+ LimitsObj->SetNumberField(TEXT("maxKeyTimesPerChannel"), UEMCP::SequencerInfo::MaxKeyTimesPerChannel);
214
+ Result->SetObjectField(TEXT("detailLimits"), LimitsObj);
215
+ }
216
+
217
+ auto ExtractSectionDetails = [&](UMovieSceneTrack* Track, const TSharedPtr<FJsonObject>& TrackObj)
185
218
  {
186
219
  if (!bIncludeDetails || !Track) return;
220
+
221
+ const TArray<UMovieSceneSection*>& AllSections = Track->GetAllSections();
187
222
  TArray<TSharedPtr<FJsonValue>> SectionsArr;
188
- for (UMovieSceneSection* Section : Track->GetAllSections())
223
+ for (int32 SectionIndex = 0; SectionIndex < AllSections.Num(); ++SectionIndex)
189
224
  {
190
- if (!Section) continue;
225
+ if (DetailBudget.Sections <= 0)
226
+ {
227
+ DetailBudget.bTruncated = true;
228
+ break;
229
+ }
230
+
231
+ UMovieSceneSection* Section = AllSections[SectionIndex];
232
+ if (!Section)
233
+ {
234
+ DetailBudget.bTruncated = true;
235
+ continue;
236
+ }
237
+ --DetailBudget.Sections;
238
+
191
239
  TSharedPtr<FJsonObject> SObj = MakeShared<FJsonObject>();
240
+ SObj->SetNumberField(TEXT("index"), SectionIndex);
241
+ SObj->SetStringField(TEXT("class"), Section->GetClass()->GetName());
242
+ UEMCP::SequencerInfo::SetTimingRangeFields(
243
+ *SObj,
244
+ Section->GetTrueRange(),
245
+ TickResolution,
246
+ DisplayRate);
247
+
192
248
  if (UMovieScene3DAttachSection* Attach = Cast<UMovieScene3DAttachSection>(Section))
193
249
  {
194
250
  SObj->SetStringField(TEXT("attachSocket"), Attach->AttachSocketName.ToString());
195
251
  SObj->SetStringField(TEXT("attachComponent"), Attach->AttachComponentName.ToString());
196
252
  }
197
- if (UMovieScene3DTransformSection* Xf = Cast<UMovieScene3DTransformSection>(Section))
253
+
254
+ const FMovieSceneChannelProxy& Proxy = Section->GetChannelProxy();
255
+ if (Cast<UMovieScene3DTransformSection>(Section))
198
256
  {
199
- FMovieSceneChannelProxy& Proxy = Xf->GetChannelProxy();
200
257
  TArray<FName> ChannelNames = {
201
258
  TEXT("Location.X"), TEXT("Location.Y"), TEXT("Location.Z"),
202
259
  TEXT("Rotation.X"), TEXT("Rotation.Y"), TEXT("Rotation.Z"),
@@ -229,12 +286,136 @@ TSharedPtr<FJsonValue> FSequencerHandlers::ReadSequenceInfo(const TSharedPtr<FJs
229
286
  }
230
287
  SObj->SetObjectField(TEXT("firstKeyValues"), FirstKeys);
231
288
  }
232
- SObj->SetStringField(TEXT("class"), Section->GetClass()->GetName());
289
+
290
+ const int32 ChannelCount = Proxy.NumChannels();
291
+ TArray<TSharedPtr<FJsonValue>> ChannelsArr;
292
+ bool bStopChannels = false;
293
+ for (const FMovieSceneChannelEntry& Entry : Proxy.GetAllEntries())
294
+ {
295
+ const FString ChannelType = Entry.GetChannelTypeName().ToString();
296
+ const TArrayView<FMovieSceneChannel* const> Channels = Entry.GetChannels();
297
+ const TArrayView<const FMovieSceneChannelMetaData> MetaData = Entry.GetMetaData();
298
+
299
+ for (int32 ChannelIndex = 0; ChannelIndex < Channels.Num(); ++ChannelIndex)
300
+ {
301
+ if (DetailBudget.Channels <= 0)
302
+ {
303
+ DetailBudget.bTruncated = true;
304
+ bStopChannels = true;
305
+ break;
306
+ }
307
+
308
+ FMovieSceneChannel* Channel = Channels[ChannelIndex];
309
+ if (!Channel)
310
+ {
311
+ DetailBudget.bTruncated = true;
312
+ continue;
313
+ }
314
+ --DetailBudget.Channels;
315
+
316
+ FString ChannelName;
317
+ if (MetaData.IsValidIndex(ChannelIndex) && !MetaData[ChannelIndex].Name.IsNone())
318
+ {
319
+ ChannelName = MetaData[ChannelIndex].Name.ToString();
320
+ }
321
+ else
322
+ {
323
+ ChannelName = FString::Printf(TEXT("%s[%d]"), *ChannelType, ChannelIndex);
324
+ }
325
+
326
+ TArray<FFrameNumber> KeyTimes;
327
+ if (DetailBudget.KeyTimes > 0)
328
+ {
329
+ Channel->GetKeys(TRange<FFrameNumber>::All(), &KeyTimes, nullptr);
330
+ KeyTimes.Sort([](const FFrameNumber A, const FFrameNumber B)
331
+ {
332
+ return A.Value < B.Value;
333
+ });
334
+ }
335
+
336
+ const int32 KeyCount = FMath::Max(Channel->GetNumKeys(), KeyTimes.Num());
337
+ const int32 KeyTimesToReturn = FMath::Min3(
338
+ KeyTimes.Num(),
339
+ UEMCP::SequencerInfo::MaxKeyTimesPerChannel,
340
+ DetailBudget.KeyTimes);
341
+
342
+ TArray<TSharedPtr<FJsonValue>> KeyTimesArr;
343
+ KeyTimesArr.Reserve(KeyTimesToReturn);
344
+ for (int32 KeyIndex = 0; KeyIndex < KeyTimesToReturn; ++KeyIndex)
345
+ {
346
+ KeyTimesArr.Add(MakeShared<FJsonValueObject>(
347
+ UEMCP::SequencerInfo::MakeKeyTimeObject(
348
+ KeyTimes[KeyIndex],
349
+ TickResolution,
350
+ DisplayRate)));
351
+ }
352
+ DetailBudget.KeyTimes -= KeyTimesToReturn;
353
+
354
+ const bool bKeyTimesTruncated = KeyTimesToReturn < KeyCount;
355
+ DetailBudget.bTruncated |= bKeyTimesTruncated;
356
+
357
+ TSharedPtr<FJsonObject> ChannelObj = MakeShared<FJsonObject>();
358
+ ChannelObj->SetNumberField(TEXT("index"), ChannelIndex);
359
+ ChannelObj->SetStringField(TEXT("name"), ChannelName);
360
+ ChannelObj->SetStringField(TEXT("type"), ChannelType);
361
+ ChannelObj->SetNumberField(TEXT("keyCount"), KeyCount);
362
+ ChannelObj->SetArrayField(TEXT("keyTimes"), KeyTimesArr);
363
+ ChannelObj->SetBoolField(TEXT("keyTimesTruncated"), bKeyTimesTruncated);
364
+ ChannelsArr.Add(MakeShared<FJsonValueObject>(ChannelObj));
365
+ }
366
+
367
+ if (bStopChannels) break;
368
+ }
369
+
370
+ const bool bChannelsTruncated = ChannelsArr.Num() < ChannelCount;
371
+ DetailBudget.bTruncated |= bChannelsTruncated;
372
+ SObj->SetNumberField(TEXT("channelCount"), ChannelCount);
373
+ SObj->SetArrayField(TEXT("channels"), ChannelsArr);
374
+ SObj->SetBoolField(TEXT("channelsTruncated"), bChannelsTruncated);
233
375
  SectionsArr.Add(MakeShared<FJsonValueObject>(SObj));
234
376
  }
377
+
378
+ const bool bSectionsTruncated = SectionsArr.Num() < AllSections.Num();
379
+ DetailBudget.bTruncated |= bSectionsTruncated;
235
380
  TrackObj->SetArrayField(TEXT("sections"), SectionsArr);
381
+ TrackObj->SetBoolField(TEXT("sectionsTruncated"), bSectionsTruncated);
382
+ };
383
+
384
+ auto MakeTrackObject = [&](UMovieSceneTrack* Track) -> TSharedPtr<FJsonObject>
385
+ {
386
+ TSharedPtr<FJsonObject> TrackObj = MakeShared<FJsonObject>();
387
+ TrackObj->SetStringField(TEXT("name"), Track->GetTrackName().ToString());
388
+ TrackObj->SetStringField(TEXT("class"), Track->GetClass()->GetName());
389
+ TrackObj->SetNumberField(TEXT("sectionCount"), Track->GetAllSections().Num());
390
+ ExtractSectionDetails(Track, TrackObj);
391
+ return TrackObj;
236
392
  };
237
393
 
394
+ // Camera cuts are stored separately from UMovieScene::GetTracks(). Emit the
395
+ // camera-cut track first so the root-track inspection is never hidden behind
396
+ // the bounded detail budget, then append the ordinary root tracks once each.
397
+ TArray<UMovieSceneTrack*> RootTracks;
398
+ if (UMovieSceneTrack* CameraCutTrack = MovieScene->GetCameraCutTrack())
399
+ {
400
+ RootTracks.Add(CameraCutTrack);
401
+ }
402
+ for (UMovieSceneTrack* Track : MovieScene->GetTracks())
403
+ {
404
+ if (Track && !RootTracks.Contains(Track))
405
+ {
406
+ RootTracks.Add(Track);
407
+ }
408
+ }
409
+
410
+ TArray<TSharedPtr<FJsonValue>> MasterTracksArray;
411
+ for (UMovieSceneTrack* Track : RootTracks)
412
+ {
413
+ if (!Track) continue;
414
+ MasterTracksArray.Add(MakeShared<FJsonValueObject>(MakeTrackObject(Track)));
415
+ }
416
+ Result->SetArrayField(TEXT("masterTracks"), MasterTracksArray);
417
+ Result->SetNumberField(TEXT("masterTrackCount"), MasterTracksArray.Num());
418
+
238
419
  // #556: collect the Sequencer binding tags (group labels) that reference a
239
420
  // given binding guid, from the MovieScene's tagged-binding map.
240
421
  auto TagsForGuid = [MovieScene](const FGuid& Guid) -> TArray<TSharedPtr<FJsonValue>>
@@ -254,80 +435,47 @@ TSharedPtr<FJsonValue> FSequencerHandlers::ReadSequenceInfo(const TSharedPtr<FJs
254
435
  return Out;
255
436
  };
256
437
 
257
- // Bindings (possessables and spawnables)
258
438
  TArray<TSharedPtr<FJsonValue>> BindingsArray;
259
- for (int32 i = 0; i < MovieScene->GetPossessableCount(); ++i)
439
+ auto AppendBinding = [&](const FString& Name, const FGuid& Guid, const TCHAR* Type)
260
440
  {
261
- const FMovieScenePossessable& Possessable = MovieScene->GetPossessable(i);
262
441
  TSharedPtr<FJsonObject> BindingObj = MakeShared<FJsonObject>();
263
- BindingObj->SetStringField(TEXT("name"), Possessable.GetName());
264
- BindingObj->SetStringField(TEXT("guid"), Possessable.GetGuid().ToString());
265
- BindingObj->SetStringField(TEXT("type"), TEXT("possessable"));
266
- BindingObj->SetArrayField(TEXT("tags"), TagsForGuid(Possessable.GetGuid()));
442
+ BindingObj->SetStringField(TEXT("name"), Name);
443
+ BindingObj->SetStringField(TEXT("guid"), Guid.ToString());
444
+ BindingObj->SetStringField(TEXT("type"), Type);
445
+ BindingObj->SetArrayField(TEXT("tags"), TagsForGuid(Guid));
267
446
 
268
- // List tracks for this binding (with optional section detail)
269
447
  TArray<TSharedPtr<FJsonValue>> TrackArr;
270
- const FMovieSceneBinding* Binding = MovieScene->FindBinding(Possessable.GetGuid());
448
+ const FMovieSceneBinding* Binding = MovieScene->FindBinding(Guid);
271
449
  if (Binding)
272
450
  {
273
451
  for (UMovieSceneTrack* Track : Binding->GetTracks())
274
452
  {
275
453
  if (!Track) continue;
276
- TSharedPtr<FJsonObject> TObj = MakeShared<FJsonObject>();
277
- TObj->SetStringField(TEXT("class"), Track->GetClass()->GetName());
278
- TObj->SetStringField(TEXT("name"), Track->GetTrackName().ToString());
279
- ExtractSectionDetails(Track, TObj);
280
- TrackArr.Add(MakeShared<FJsonValueObject>(TObj));
454
+ TrackArr.Add(MakeShared<FJsonValueObject>(MakeTrackObject(Track)));
281
455
  }
282
456
  }
283
457
  BindingObj->SetArrayField(TEXT("tracks"), TrackArr);
284
-
285
458
  BindingsArray.Add(MakeShared<FJsonValueObject>(BindingObj));
459
+ };
460
+
461
+ for (int32 i = 0; i < MovieScene->GetPossessableCount(); ++i)
462
+ {
463
+ const FMovieScenePossessable& Possessable = MovieScene->GetPossessable(i);
464
+ AppendBinding(Possessable.GetName(), Possessable.GetGuid(), TEXT("possessable"));
286
465
  }
287
466
 
288
467
  for (int32 i = 0; i < MovieScene->GetSpawnableCount(); ++i)
289
468
  {
290
469
  const FMovieSceneSpawnable& Spawnable = MovieScene->GetSpawnable(i);
291
- TSharedPtr<FJsonObject> BindingObj = MakeShared<FJsonObject>();
292
- BindingObj->SetStringField(TEXT("name"), Spawnable.GetName());
293
- BindingObj->SetStringField(TEXT("guid"), Spawnable.GetGuid().ToString());
294
- BindingObj->SetStringField(TEXT("type"), TEXT("spawnable"));
295
- BindingObj->SetArrayField(TEXT("tags"), TagsForGuid(Spawnable.GetGuid()));
296
-
297
- TArray<TSharedPtr<FJsonValue>> TrackArr;
298
- const FMovieSceneBinding* Binding = MovieScene->FindBinding(Spawnable.GetGuid());
299
- if (Binding)
300
- {
301
- for (UMovieSceneTrack* Track : Binding->GetTracks())
302
- {
303
- if (!Track) continue;
304
- TSharedPtr<FJsonObject> TObj = MakeShared<FJsonObject>();
305
- TObj->SetStringField(TEXT("class"), Track->GetClass()->GetName());
306
- TObj->SetStringField(TEXT("name"), Track->GetTrackName().ToString());
307
- ExtractSectionDetails(Track, TObj);
308
- TrackArr.Add(MakeShared<FJsonValueObject>(TObj));
309
- }
310
- }
311
- BindingObj->SetArrayField(TEXT("tracks"), TrackArr);
312
-
313
- BindingsArray.Add(MakeShared<FJsonValueObject>(BindingObj));
470
+ AppendBinding(Spawnable.GetName(), Spawnable.GetGuid(), TEXT("spawnable"));
314
471
  }
315
472
  Result->SetArrayField(TEXT("bindings"), BindingsArray);
316
473
  Result->SetNumberField(TEXT("bindingCount"), BindingsArray.Num());
317
474
 
318
- // Master tracks
319
- TArray<TSharedPtr<FJsonValue>> MasterTracksArray;
320
- for (UMovieSceneTrack* Track : MovieScene->GetTracks())
475
+ if (bIncludeDetails)
321
476
  {
322
- if (!Track) continue;
323
- TSharedPtr<FJsonObject> TrackObj = MakeShared<FJsonObject>();
324
- TrackObj->SetStringField(TEXT("name"), Track->GetTrackName().ToString());
325
- TrackObj->SetStringField(TEXT("class"), Track->GetClass()->GetName());
326
- TrackObj->SetNumberField(TEXT("sectionCount"), Track->GetAllSections().Num());
327
- MasterTracksArray.Add(MakeShared<FJsonValueObject>(TrackObj));
477
+ Result->SetBoolField(TEXT("detailsTruncated"), DetailBudget.bTruncated);
328
478
  }
329
- Result->SetArrayField(TEXT("masterTracks"), MasterTracksArray);
330
- Result->SetNumberField(TEXT("masterTrackCount"), MasterTracksArray.Num());
331
479
 
332
480
  return MCPResult(Result);
333
481
  }
@@ -390,18 +538,18 @@ TSharedPtr<FJsonValue> FSequencerHandlers::AddTrack(const TSharedPtr<FJsonObject
390
538
 
391
539
  // Check if we should add to an actor binding or as a master track
392
540
  FString ActorLabel = OptionalString(Params, TEXT("actorLabel"));
541
+ const FString ActorPath = OptionalString(Params, TEXT("actorPath"));
393
542
  auto Result = MCPSuccess();
394
543
 
395
- if (!ActorLabel.IsEmpty())
544
+ if (!ActorLabel.IsEmpty() || !ActorPath.IsEmpty())
396
545
  {
397
546
  // Find the binding for this actor
398
547
  REQUIRE_EDITOR_WORLD(World);
399
548
 
400
- AActor* TargetActor = FindActorByLabel(World, ActorLabel);
401
- if (!TargetActor)
402
- {
403
- return MCPError(FString::Printf(TEXT("Actor not found: %s"), *ActorLabel));
404
- }
549
+ TSharedPtr<FJsonValue> ActorErr;
550
+ AActor* TargetActor = MCPResolveActor(World, Params, ActorErr);
551
+ if (!TargetActor) return ActorErr;
552
+ ActorLabel = TargetActor->GetActorLabel();
405
553
 
406
554
  // Find or create a binding for this actor
407
555
  FGuid BindingGuid;
@@ -431,6 +579,7 @@ TSharedPtr<FJsonValue> FSequencerHandlers::AddTrack(const TSharedPtr<FJsonObject
431
579
  {
432
580
  MCPSetExisted(Result);
433
581
  Result->SetStringField(TEXT("actorLabel"), ActorLabel);
582
+ Result->SetStringField(TEXT("actorPath"), TargetActor->GetPathName());
434
583
  Result->SetStringField(TEXT("bindingGuid"), BindingGuid.ToString());
435
584
  Result->SetStringField(TEXT("trackType"), TrackType);
436
585
  Result->SetStringField(TEXT("trackClass"), ExistingTrack->GetClass()->GetName());
@@ -447,6 +596,7 @@ TSharedPtr<FJsonValue> FSequencerHandlers::AddTrack(const TSharedPtr<FJsonObject
447
596
 
448
597
  MCPSetCreated(Result);
449
598
  Result->SetStringField(TEXT("actorLabel"), ActorLabel);
599
+ Result->SetStringField(TEXT("actorPath"), TargetActor->GetPathName());
450
600
  Result->SetStringField(TEXT("bindingGuid"), BindingGuid.ToString());
451
601
  Result->SetStringField(TEXT("trackType"), TrackType);
452
602
  Result->SetStringField(TEXT("trackClass"), NewTrack->GetClass()->GetName());
@@ -559,6 +709,152 @@ TSharedPtr<FJsonValue> FSequencerHandlers::SequenceControl(const TSharedPtr<FJso
559
709
  return MCPResult(Result);
560
710
  }
561
711
 
712
+ // scrub_sequence (#881) - park the playhead on an exact time and evaluate there.
713
+ //
714
+ // Building a data-driven cinematic means capturing the evaluated world at a
715
+ // known frame, and play_sequence only offers play/pause/stop: realtime playback
716
+ // races capture_scene_png and the frame that lands is whatever the tick gave
717
+ // you. This puts the playhead on the frame that was asked for and forces the
718
+ // evaluation before answering, which is what makes scrub-then-capture
719
+ // reproducible.
720
+ //
721
+ // A separate action rather than a fourth verb on play_sequence: sequenceAction
722
+ // is a closed enum of play|pause|stop and widening it is a contract change on
723
+ // the transport, while a scrub carries a time argument the transport verbs have
724
+ // no use for.
725
+ TSharedPtr<FJsonValue> FSequencerHandlers::ScrubSequence(const TSharedPtr<FJsonObject>& Params)
726
+ {
727
+ // The Sequencer scripting surface acts on whatever is currently open, so a
728
+ // named sequence has to be opened first or the scrub would move a different
729
+ // one. Same rule as play_sequence.
730
+ const FString RequestedPath = OptionalString(Params, TEXT("sequencePath"), OptionalString(Params, TEXT("assetPath")));
731
+ if (!RequestedPath.IsEmpty())
732
+ {
733
+ ULevelSequence* Sequence = LoadAssetByPath<ULevelSequence>(RequestedPath);
734
+ if (!Sequence)
735
+ {
736
+ return MCPError(FString::Printf(TEXT("Level Sequence not found: %s"), *RequestedPath));
737
+ }
738
+ if (!ULevelSequenceEditorBlueprintLibrary::OpenLevelSequence(Sequence))
739
+ {
740
+ return MCPError(FString::Printf(TEXT("Failed to open '%s' in Sequencer."), *Sequence->GetPathName()));
741
+ }
742
+ }
743
+
744
+ ULevelSequence* Current = ULevelSequenceEditorBlueprintLibrary::GetCurrentLevelSequence();
745
+ if (!Current)
746
+ {
747
+ return MCPError(TEXT("No Level Sequence is open in Sequencer. Pass sequencePath to open one, or open it in the editor first."));
748
+ }
749
+ UMovieScene* MovieScene = Current->GetMovieScene();
750
+ if (!MovieScene)
751
+ {
752
+ return MCPError(TEXT("LevelSequence has no MovieScene"));
753
+ }
754
+
755
+ const FFrameRate DisplayRate = MovieScene->GetDisplayRate();
756
+ const FFrameRate TickResolution = MovieScene->GetTickResolution();
757
+
758
+ // Two units are in play and confusing them is an 800x error, so the unit is
759
+ // named rather than guessed: 'display' is the frame number Sequencer shows,
760
+ // 'tick' is what get_sequence_info's playbackRange reports.
761
+ const FString TimeUnit = OptionalString(Params, TEXT("timeUnit"), TEXT("display")).ToLower();
762
+ if (TimeUnit != TEXT("display") && TimeUnit != TEXT("tick"))
763
+ {
764
+ return MCPError(FString::Printf(
765
+ TEXT("Unknown timeUnit '%s'. Use 'display' (the frame numbers Sequencer shows) or 'tick' (the units get_sequence_info's playbackRange reports)."),
766
+ *TimeUnit));
767
+ }
768
+
769
+ double RequestedSeconds = 0.0;
770
+ double RequestedFrame = 0.0;
771
+ const bool bHasSeconds = Params->TryGetNumberField(TEXT("seconds"), RequestedSeconds);
772
+ const bool bHasFrame = Params->TryGetNumberField(TEXT("frame"), RequestedFrame);
773
+ if (bHasSeconds == bHasFrame)
774
+ {
775
+ return MCPError(TEXT("Provide exactly one of 'seconds' or 'frame'"));
776
+ }
777
+
778
+ // Everything resolves to a display-rate frame time, which is the unit
779
+ // SetGlobalPosition takes and the unit the Sequencer time field shows.
780
+ FFrameTime TargetDisplay;
781
+ if (bHasSeconds)
782
+ {
783
+ TargetDisplay = DisplayRate.AsFrameTime(RequestedSeconds);
784
+ }
785
+ else if (TimeUnit == TEXT("tick"))
786
+ {
787
+ const FFrameTime AsTicks(FFrameNumber(static_cast<int32>(FMath::RoundToDouble(RequestedFrame))));
788
+ TargetDisplay = FFrameRate::TransformTime(AsTicks, TickResolution, DisplayRate);
789
+ }
790
+ else
791
+ {
792
+ TargetDisplay = FFrameTime(FFrameNumber(static_cast<int32>(FMath::RoundToDouble(RequestedFrame))));
793
+ }
794
+
795
+ // Pause before scrubbing: a playing sequence moves the playhead again on the
796
+ // next tick, and the capture would not be at the time that was asked for.
797
+ ULevelSequenceEditorBlueprintLibrary::Pause();
798
+ const FMovieSceneSequencePlaybackParams ScrubTo(TargetDisplay, EUpdatePositionMethod::Scrub);
799
+ ULevelSequenceEditorBlueprintLibrary::SetGlobalPosition(ScrubTo, EMovieSceneTimeUnit::DisplayRate);
800
+ // Evaluate now instead of on the next tick. The playhead move alone does not
801
+ // write possessed-actor transforms; the evaluation does, and a capture taken
802
+ // before it would read the previous frame's world.
803
+ ULevelSequenceEditorBlueprintLibrary::ForceUpdate();
804
+
805
+ const FFrameTime TargetTicks = FFrameRate::TransformTime(TargetDisplay, DisplayRate, TickResolution);
806
+ const double EvaluatedSeconds = DisplayRate.AsSeconds(TargetDisplay);
807
+
808
+ auto Result = MCPSuccess();
809
+ Result->SetStringField(TEXT("sequencePath"), Current->GetPathName());
810
+ Result->SetStringField(TEXT("timeUnit"), TimeUnit);
811
+ Result->SetNumberField(TEXT("seconds"), EvaluatedSeconds);
812
+ Result->SetNumberField(TEXT("frame"), TargetDisplay.AsDecimal());
813
+ Result->SetNumberField(TEXT("tick"), TargetTicks.AsDecimal());
814
+ Result->SetBoolField(TEXT("evaluated"), true);
815
+ // Read the transport back rather than reporting what was asked for.
816
+ Result->SetBoolField(TEXT("playing"), ULevelSequenceEditorBlueprintLibrary::IsPlaying());
817
+
818
+ TSharedPtr<FJsonObject> DisplayRateObj = MakeShared<FJsonObject>();
819
+ DisplayRateObj->SetNumberField(TEXT("numerator"), DisplayRate.Numerator);
820
+ DisplayRateObj->SetNumberField(TEXT("denominator"), DisplayRate.Denominator);
821
+ Result->SetObjectField(TEXT("displayRate"), DisplayRateObj);
822
+
823
+ TSharedPtr<FJsonObject> TickRateObj = MakeShared<FJsonObject>();
824
+ TickRateObj->SetNumberField(TEXT("numerator"), TickResolution.Numerator);
825
+ TickRateObj->SetNumberField(TEXT("denominator"), TickResolution.Denominator);
826
+ Result->SetObjectField(TEXT("tickResolution"), TickRateObj);
827
+
828
+ // A scrub outside the playback range is legal and evaluates, but a track
829
+ // that has no section there reads as its nearest key, which presents as
830
+ // "the scrub did nothing". Say so rather than leaving it to be guessed.
831
+ const TRange<FFrameNumber> PlaybackRange = MovieScene->GetPlaybackRange();
832
+ TSharedPtr<FJsonObject> RangeObj = MakeShared<FJsonObject>();
833
+ if (PlaybackRange.HasLowerBound())
834
+ {
835
+ RangeObj->SetNumberField(TEXT("startTick"), PlaybackRange.GetLowerBoundValue().Value);
836
+ RangeObj->SetNumberField(TEXT("startSeconds"), TickResolution.AsSeconds(FFrameTime(PlaybackRange.GetLowerBoundValue())));
837
+ }
838
+ if (PlaybackRange.HasUpperBound())
839
+ {
840
+ RangeObj->SetNumberField(TEXT("endTick"), PlaybackRange.GetUpperBoundValue().Value);
841
+ RangeObj->SetNumberField(TEXT("endSeconds"), TickResolution.AsSeconds(FFrameTime(PlaybackRange.GetUpperBoundValue())));
842
+ }
843
+ Result->SetObjectField(TEXT("playbackRange"), RangeObj);
844
+
845
+ const bool bWithinRange = PlaybackRange.Contains(TargetTicks.FrameNumber);
846
+ Result->SetBoolField(TEXT("withinPlaybackRange"), bWithinRange);
847
+ if (!bWithinRange)
848
+ {
849
+ Result->SetStringField(TEXT("warning"), TEXT(
850
+ "The requested time is outside the sequence's playback range. The playhead moved and the sequence "
851
+ "evaluated, but a track with no section there holds its nearest key, which looks like a scrub that "
852
+ "did nothing. playbackRange above is in ticks; seconds are given alongside."));
853
+ }
854
+
855
+ return MCPResult(Result);
856
+ }
857
+
562
858
  // set_sequence_playback_range -- set a Level Sequence's playback range in
563
859
  // seconds. (#548) Params: sequencePath, startSeconds, endSeconds.
564
860
  TSharedPtr<FJsonValue> FSequencerHandlers::SetPlaybackRange(const TSharedPtr<FJsonObject>& Params)
@@ -609,12 +905,17 @@ TSharedPtr<FJsonValue> FSequencerHandlers::AddSection(const TSharedPtr<FJsonObje
609
905
  if (!TrackClass) return MCPError(FString::Printf(TEXT("Unknown track type: '%s'"), *TrackType));
610
906
 
611
907
  const FString ActorLabel = OptionalString(Params, TEXT("actorLabel"));
908
+ const FString ActorPath = OptionalString(Params, TEXT("actorPath"));
612
909
 
613
910
  UMovieSceneTrack* Track = nullptr;
614
911
  FGuid BindingGuid;
615
- if (!ActorLabel.IsEmpty())
912
+ if (!ActorLabel.IsEmpty() || !ActorPath.IsEmpty())
616
913
  {
617
- if (!ResolveActorBinding(Sequence, MovieScene, ActorLabel, BindingGuid, Err)) return MCPError(Err);
914
+ REQUIRE_EDITOR_WORLD(BindingWorld);
915
+ TSharedPtr<FJsonValue> ActorErr;
916
+ AActor* BoundActor = MCPResolveActor(BindingWorld, Params, ActorErr);
917
+ if (!BoundActor) return ActorErr;
918
+ if (!ResolveActorBinding(Sequence, MovieScene, BoundActor, BindingGuid, Err)) return MCPError(Err);
618
919
  Track = MovieScene->FindTrack(TrackClass, BindingGuid);
619
920
  if (!Track) Track = MovieScene->AddTrack(TrackClass, BindingGuid);
620
921
  }
@@ -631,10 +932,18 @@ TSharedPtr<FJsonValue> FSequencerHandlers::AddSection(const TSharedPtr<FJsonObje
631
932
  // Resolve the camera binding up front so a bad cameraActorLabel fails before
632
933
  // we create an orphan section.
633
934
  const FString CameraActorLabel = OptionalString(Params, TEXT("cameraActorLabel"));
935
+ const FString CameraActorPath = OptionalString(Params, TEXT("cameraActorPath"));
634
936
  FGuid CamGuid;
635
- if (!CameraActorLabel.IsEmpty())
937
+ if (!CameraActorLabel.IsEmpty() || !CameraActorPath.IsEmpty())
636
938
  {
637
- if (!ResolveActorBinding(Sequence, MovieScene, CameraActorLabel, CamGuid, Err)) return MCPError(Err);
939
+ REQUIRE_EDITOR_WORLD(CameraWorld);
940
+ FMCPActorSelector CameraSel;
941
+ CameraSel.LabelKey = TEXT("cameraActorLabel");
942
+ CameraSel.PathKey = TEXT("cameraActorPath");
943
+ TSharedPtr<FJsonValue> CameraErr;
944
+ AActor* CameraActor = MCPResolveActor(CameraWorld, Params, CameraErr, CameraSel);
945
+ if (!CameraActor) return CameraErr;
946
+ if (!ResolveActorBinding(Sequence, MovieScene, CameraActor, CamGuid, Err)) return MCPError(Err);
638
947
  }
639
948
 
640
949
  UMovieSceneSection* Section = Track->CreateNewSection();
@@ -715,11 +1024,16 @@ TSharedPtr<FJsonValue> FSequencerHandlers::SetKeyframes(const TSharedPtr<FJsonOb
715
1024
  }
716
1025
 
717
1026
  const FString ActorLabel = OptionalString(Params, TEXT("actorLabel"));
1027
+ const FString ActorPath = OptionalString(Params, TEXT("actorPath"));
718
1028
  UMovieSceneTrack* Track = nullptr;
719
- if (!ActorLabel.IsEmpty())
1029
+ if (!ActorLabel.IsEmpty() || !ActorPath.IsEmpty())
720
1030
  {
1031
+ REQUIRE_EDITOR_WORLD(BindingWorld);
1032
+ TSharedPtr<FJsonValue> ActorErr;
1033
+ AActor* BoundActor = MCPResolveActor(BindingWorld, Params, ActorErr);
1034
+ if (!BoundActor) return ActorErr;
721
1035
  FGuid BindingGuid;
722
- if (!ResolveActorBinding(Sequence, MovieScene, ActorLabel, BindingGuid, Err)) return MCPError(Err);
1036
+ if (!ResolveActorBinding(Sequence, MovieScene, BoundActor, BindingGuid, Err)) return MCPError(Err);
723
1037
  Track = MovieScene->FindTrack(TrackClass, BindingGuid);
724
1038
  }
725
1039
  else
@@ -16,6 +16,8 @@ private:
16
16
  static TSharedPtr<FJsonValue> ReadSequenceInfo(const TSharedPtr<FJsonObject>& Params);
17
17
  static TSharedPtr<FJsonValue> AddTrack(const TSharedPtr<FJsonObject>& Params);
18
18
  static TSharedPtr<FJsonValue> SequenceControl(const TSharedPtr<FJsonObject>& Params);
19
+ // #881: move the playhead to an exact time and evaluate there.
20
+ static TSharedPtr<FJsonValue> ScrubSequence(const TSharedPtr<FJsonObject>& Params);
19
21
  static TSharedPtr<FJsonValue> SetPlaybackRange(const TSharedPtr<FJsonObject>& Params);
20
22
  static TSharedPtr<FJsonValue> AddSection(const TSharedPtr<FJsonObject>& Params);
21
23
  static TSharedPtr<FJsonValue> SetKeyframes(const TSharedPtr<FJsonObject>& Params);