unreal-engine-mcp-server 0.5.4 → 0.5.5

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 (468) hide show
  1. package/dist/automation/bridge.d.ts.map +1 -0
  2. package/dist/automation/bridge.js.map +1 -0
  3. package/dist/automation/connection-manager.d.ts.map +1 -0
  4. package/dist/automation/connection-manager.js.map +1 -0
  5. package/dist/automation/handshake.d.ts.map +1 -0
  6. package/dist/automation/handshake.js.map +1 -0
  7. package/dist/automation/index.d.ts.map +1 -0
  8. package/dist/automation/index.js.map +1 -0
  9. package/dist/automation/message-handler.d.ts.map +1 -0
  10. package/dist/automation/message-handler.js.map +1 -0
  11. package/dist/automation/request-tracker.d.ts.map +1 -0
  12. package/dist/automation/request-tracker.js.map +1 -0
  13. package/dist/automation/types.d.ts.map +1 -0
  14. package/dist/automation/types.js.map +1 -0
  15. package/dist/cli.d.ts.map +1 -0
  16. package/dist/cli.js +4 -3
  17. package/dist/cli.js.map +1 -0
  18. package/dist/config/class-aliases.d.ts.map +1 -0
  19. package/dist/config/class-aliases.js.map +1 -0
  20. package/dist/config.d.ts.map +1 -0
  21. package/dist/config.js.map +1 -0
  22. package/dist/constants.d.ts.map +1 -0
  23. package/dist/constants.js.map +1 -0
  24. package/dist/graphql/loaders.d.ts.map +1 -0
  25. package/dist/graphql/loaders.js.map +1 -0
  26. package/dist/graphql/resolvers.d.ts.map +1 -0
  27. package/dist/graphql/resolvers.js +29 -29
  28. package/dist/graphql/resolvers.js.map +1 -0
  29. package/dist/graphql/schema.d.ts.map +1 -0
  30. package/dist/graphql/schema.js.map +1 -0
  31. package/dist/graphql/server.d.ts.map +1 -0
  32. package/dist/graphql/server.js.map +1 -0
  33. package/dist/graphql/types.d.ts.map +1 -0
  34. package/dist/graphql/types.js.map +1 -0
  35. package/dist/handlers/resource-handlers.d.ts.map +1 -0
  36. package/dist/handlers/resource-handlers.js.map +1 -0
  37. package/dist/index.d.ts +1 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +64 -7
  40. package/dist/index.js.map +1 -0
  41. package/dist/resources/actors.d.ts.map +1 -0
  42. package/dist/resources/actors.js.map +1 -0
  43. package/dist/resources/assets.d.ts.map +1 -0
  44. package/dist/resources/assets.js +6 -4
  45. package/dist/resources/assets.js.map +1 -0
  46. package/dist/resources/levels.d.ts.map +1 -0
  47. package/dist/resources/levels.js.map +1 -0
  48. package/dist/server/resource-registry.d.ts.map +1 -0
  49. package/dist/server/resource-registry.js.map +1 -0
  50. package/dist/server/tool-registry.d.ts.map +1 -0
  51. package/dist/server/tool-registry.js.map +1 -0
  52. package/dist/server-setup.d.ts.map +1 -0
  53. package/dist/server-setup.js.map +1 -0
  54. package/dist/services/health-monitor.d.ts.map +1 -0
  55. package/dist/services/health-monitor.js.map +1 -0
  56. package/dist/services/metrics-server.d.ts.map +1 -0
  57. package/dist/services/metrics-server.js.map +1 -0
  58. package/dist/tools/actors.d.ts.map +1 -0
  59. package/dist/tools/actors.js +3 -1
  60. package/dist/tools/actors.js.map +1 -0
  61. package/dist/tools/animation.d.ts.map +1 -0
  62. package/dist/tools/animation.js +2 -2
  63. package/dist/tools/animation.js.map +1 -0
  64. package/dist/tools/assets.d.ts.map +1 -0
  65. package/dist/tools/assets.js.map +1 -0
  66. package/dist/tools/audio.d.ts.map +1 -0
  67. package/dist/tools/audio.js.map +1 -0
  68. package/dist/tools/base-tool.d.ts.map +1 -0
  69. package/dist/tools/base-tool.js.map +1 -0
  70. package/dist/tools/behavior-tree.d.ts.map +1 -0
  71. package/dist/tools/behavior-tree.js.map +1 -0
  72. package/dist/tools/blueprint.d.ts.map +1 -0
  73. package/dist/tools/blueprint.js +4 -2
  74. package/dist/tools/blueprint.js.map +1 -0
  75. package/dist/tools/consolidated-tool-definitions.d.ts.map +1 -0
  76. package/dist/tools/consolidated-tool-definitions.js.map +1 -0
  77. package/dist/tools/consolidated-tool-handlers.d.ts.map +1 -0
  78. package/dist/tools/consolidated-tool-handlers.js.map +1 -0
  79. package/dist/tools/debug.d.ts.map +1 -0
  80. package/dist/tools/debug.js +3 -1
  81. package/dist/tools/debug.js.map +1 -0
  82. package/dist/tools/dynamic-handler-registry.d.ts.map +1 -0
  83. package/dist/tools/dynamic-handler-registry.js +3 -1
  84. package/dist/tools/dynamic-handler-registry.js.map +1 -0
  85. package/dist/tools/editor.d.ts.map +1 -0
  86. package/dist/tools/editor.js +1 -1
  87. package/dist/tools/editor.js.map +1 -0
  88. package/dist/tools/engine.d.ts.map +1 -0
  89. package/dist/tools/engine.js.map +1 -0
  90. package/dist/tools/environment.d.ts.map +1 -0
  91. package/dist/tools/environment.js +2 -2
  92. package/dist/tools/environment.js.map +1 -0
  93. package/dist/tools/foliage.d.ts.map +1 -0
  94. package/dist/tools/foliage.js.map +1 -0
  95. package/dist/tools/handlers/actor-handlers.d.ts +1 -1
  96. package/dist/tools/handlers/actor-handlers.d.ts.map +1 -0
  97. package/dist/tools/handlers/actor-handlers.js +6 -5
  98. package/dist/tools/handlers/actor-handlers.js.map +1 -0
  99. package/dist/tools/handlers/animation-handlers.d.ts.map +1 -0
  100. package/dist/tools/handlers/animation-handlers.js.map +1 -0
  101. package/dist/tools/handlers/argument-helper.d.ts.map +1 -0
  102. package/dist/tools/handlers/argument-helper.js.map +1 -0
  103. package/dist/tools/handlers/asset-handlers.d.ts.map +1 -0
  104. package/dist/tools/handlers/asset-handlers.js +5 -1
  105. package/dist/tools/handlers/asset-handlers.js.map +1 -0
  106. package/dist/tools/handlers/audio-handlers.d.ts.map +1 -0
  107. package/dist/tools/handlers/audio-handlers.js.map +1 -0
  108. package/dist/tools/handlers/blueprint-handlers.d.ts.map +1 -0
  109. package/dist/tools/handlers/blueprint-handlers.js +2 -1
  110. package/dist/tools/handlers/blueprint-handlers.js.map +1 -0
  111. package/dist/tools/handlers/common-handlers.d.ts.map +1 -0
  112. package/dist/tools/handlers/common-handlers.js.map +1 -0
  113. package/dist/tools/handlers/editor-handlers.d.ts.map +1 -0
  114. package/dist/tools/handlers/editor-handlers.js +12 -2
  115. package/dist/tools/handlers/editor-handlers.js.map +1 -0
  116. package/dist/tools/handlers/effect-handlers.d.ts.map +1 -0
  117. package/dist/tools/handlers/effect-handlers.js.map +1 -0
  118. package/dist/tools/handlers/environment-handlers.d.ts.map +1 -0
  119. package/dist/tools/handlers/environment-handlers.js.map +1 -0
  120. package/dist/tools/handlers/graph-handlers.d.ts.map +1 -0
  121. package/dist/tools/handlers/graph-handlers.js +61 -1
  122. package/dist/tools/handlers/graph-handlers.js.map +1 -0
  123. package/dist/tools/handlers/input-handlers.d.ts.map +1 -0
  124. package/dist/tools/handlers/input-handlers.js.map +1 -0
  125. package/dist/tools/handlers/inspect-handlers.d.ts.map +1 -0
  126. package/dist/tools/handlers/inspect-handlers.js.map +1 -0
  127. package/dist/tools/handlers/level-handlers.d.ts.map +1 -0
  128. package/dist/tools/handlers/level-handlers.js.map +1 -0
  129. package/dist/tools/handlers/lighting-handlers.d.ts.map +1 -0
  130. package/dist/tools/handlers/lighting-handlers.js +23 -1
  131. package/dist/tools/handlers/lighting-handlers.js.map +1 -0
  132. package/dist/tools/handlers/performance-handlers.d.ts.map +1 -0
  133. package/dist/tools/handlers/performance-handlers.js +15 -2
  134. package/dist/tools/handlers/performance-handlers.js.map +1 -0
  135. package/dist/tools/handlers/pipeline-handlers.d.ts.map +1 -0
  136. package/dist/tools/handlers/pipeline-handlers.js.map +1 -0
  137. package/dist/tools/handlers/sequence-handlers.d.ts.map +1 -0
  138. package/dist/tools/handlers/sequence-handlers.js.map +1 -0
  139. package/dist/tools/handlers/system-handlers.d.ts.map +1 -0
  140. package/dist/tools/handlers/system-handlers.js +16 -1
  141. package/dist/tools/handlers/system-handlers.js.map +1 -0
  142. package/dist/tools/input.d.ts.map +1 -0
  143. package/dist/tools/input.js +3 -1
  144. package/dist/tools/input.js.map +1 -0
  145. package/dist/tools/introspection.d.ts.map +1 -0
  146. package/dist/tools/introspection.js.map +1 -0
  147. package/dist/tools/landscape.d.ts.map +1 -0
  148. package/dist/tools/landscape.js +3 -1
  149. package/dist/tools/landscape.js.map +1 -0
  150. package/dist/tools/level.d.ts.map +1 -0
  151. package/dist/tools/level.js.map +1 -0
  152. package/dist/tools/lighting.d.ts.map +1 -0
  153. package/dist/tools/lighting.js +3 -1
  154. package/dist/tools/lighting.js.map +1 -0
  155. package/dist/tools/logs.d.ts.map +1 -0
  156. package/dist/tools/logs.js.map +1 -0
  157. package/dist/tools/materials.d.ts.map +1 -0
  158. package/dist/tools/materials.js +3 -1
  159. package/dist/tools/materials.js.map +1 -0
  160. package/dist/tools/niagara.d.ts.map +1 -0
  161. package/dist/tools/niagara.js +7 -5
  162. package/dist/tools/niagara.js.map +1 -0
  163. package/dist/tools/performance.d.ts.map +1 -0
  164. package/dist/tools/performance.js.map +1 -0
  165. package/dist/tools/physics.d.ts.map +1 -0
  166. package/dist/tools/physics.js +9 -7
  167. package/dist/tools/physics.js.map +1 -0
  168. package/dist/tools/property-dictionary.d.ts.map +1 -0
  169. package/dist/tools/property-dictionary.js.map +1 -0
  170. package/dist/tools/sequence.d.ts.map +1 -0
  171. package/dist/tools/sequence.js +3 -1
  172. package/dist/tools/sequence.js.map +1 -0
  173. package/dist/tools/tool-definition-utils.d.ts.map +1 -0
  174. package/dist/tools/tool-definition-utils.js.map +1 -0
  175. package/dist/tools/ui.d.ts.map +1 -0
  176. package/dist/tools/ui.js +3 -1
  177. package/dist/tools/ui.js.map +1 -0
  178. package/dist/types/automation-responses.d.ts.map +1 -0
  179. package/dist/types/automation-responses.js.map +1 -0
  180. package/dist/types/env.d.ts.map +1 -0
  181. package/dist/types/env.js.map +1 -0
  182. package/dist/types/handler-types.d.ts.map +1 -0
  183. package/dist/types/handler-types.js.map +1 -0
  184. package/dist/types/tool-interfaces.d.ts.map +1 -0
  185. package/dist/types/tool-interfaces.js.map +1 -0
  186. package/dist/types/tool-types.d.ts.map +1 -0
  187. package/dist/types/tool-types.js.map +1 -0
  188. package/dist/unreal-bridge.d.ts +1 -0
  189. package/dist/unreal-bridge.d.ts.map +1 -0
  190. package/dist/unreal-bridge.js +8 -0
  191. package/dist/unreal-bridge.js.map +1 -0
  192. package/dist/utils/command-validator.d.ts.map +1 -0
  193. package/dist/utils/command-validator.js.map +1 -0
  194. package/dist/utils/elicitation.d.ts.map +1 -0
  195. package/dist/utils/elicitation.js.map +1 -0
  196. package/dist/utils/error-handler.d.ts.map +1 -0
  197. package/dist/utils/error-handler.js.map +1 -0
  198. package/dist/utils/ini-reader.d.ts.map +1 -0
  199. package/dist/utils/ini-reader.js.map +1 -0
  200. package/dist/utils/logger.d.ts.map +1 -0
  201. package/dist/utils/logger.js.map +1 -0
  202. package/dist/utils/normalize.d.ts.map +1 -0
  203. package/dist/utils/normalize.js.map +1 -0
  204. package/dist/utils/path-security.d.ts.map +1 -0
  205. package/dist/utils/path-security.js.map +1 -0
  206. package/dist/utils/response-factory.d.ts.map +1 -0
  207. package/dist/utils/response-factory.js +3 -1
  208. package/dist/utils/response-factory.js.map +1 -0
  209. package/dist/utils/response-validator.d.ts.map +1 -0
  210. package/dist/utils/response-validator.js.map +1 -0
  211. package/dist/utils/result-helpers.d.ts.map +1 -0
  212. package/dist/utils/result-helpers.js.map +1 -0
  213. package/dist/utils/safe-json.d.ts.map +1 -0
  214. package/dist/utils/safe-json.js.map +1 -0
  215. package/dist/utils/unreal-command-queue.d.ts.map +1 -0
  216. package/dist/utils/unreal-command-queue.js.map +1 -0
  217. package/dist/utils/validation.d.ts.map +1 -0
  218. package/dist/utils/validation.js.map +1 -0
  219. package/dist/wasm/index.d.ts.map +1 -0
  220. package/dist/wasm/index.js.map +1 -0
  221. package/package.json +12 -34
  222. package/server.json +2 -2
  223. package/.dockerignore +0 -57
  224. package/.env.example +0 -26
  225. package/.env.production +0 -61
  226. package/.eslintrc.json +0 -0
  227. package/.eslintrc.override.json +0 -8
  228. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -94
  229. package/.github/ISSUE_TEMPLATE/config.yml +0 -8
  230. package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -56
  231. package/.github/copilot-instructions.md +0 -478
  232. package/.github/dependabot.yml +0 -19
  233. package/.github/labeler.yml +0 -24
  234. package/.github/labels.yml +0 -70
  235. package/.github/pull_request_template.md +0 -42
  236. package/.github/release-drafter-config.yml +0 -51
  237. package/.github/workflows/auto-merge.yml +0 -38
  238. package/.github/workflows/ci.yml +0 -38
  239. package/.github/workflows/dependency-review.yml +0 -17
  240. package/.github/workflows/gemini-issue-triage.yml +0 -172
  241. package/.github/workflows/greetings.yml +0 -27
  242. package/.github/workflows/labeler.yml +0 -17
  243. package/.github/workflows/links.yml +0 -80
  244. package/.github/workflows/pr-size-labeler.yml +0 -137
  245. package/.github/workflows/publish-mcp.yml +0 -79
  246. package/.github/workflows/release-drafter.yml +0 -24
  247. package/.github/workflows/release.yml +0 -112
  248. package/.github/workflows/semantic-pull-request.yml +0 -35
  249. package/.github/workflows/smoke-test.yml +0 -36
  250. package/.github/workflows/stale.yml +0 -28
  251. package/CONTRIBUTING.md +0 -140
  252. package/Dockerfile +0 -37
  253. package/GEMINI.md +0 -115
  254. package/Public/Plugin_setup_guide.mp4 +0 -0
  255. package/Public/icon.png +0 -0
  256. package/claude_desktop_config_example.json +0 -15
  257. package/dist/types/responses.d.ts +0 -249
  258. package/dist/types/responses.js +0 -2
  259. package/docs/GraphQL-API.md +0 -888
  260. package/docs/Migration-Guide-v0.5.0.md +0 -684
  261. package/docs/Roadmap.md +0 -53
  262. package/docs/WebAssembly-Integration.md +0 -628
  263. package/docs/editor-plugin-extension.md +0 -370
  264. package/docs/handler-mapping.md +0 -249
  265. package/docs/native-automation-progress.md +0 -128
  266. package/docs/testing-guide.md +0 -423
  267. package/eslint.config.mjs +0 -68
  268. package/mcp-config-example.json +0 -14
  269. package/plugins/McpAutomationBridge/Config/FilterPlugin.ini +0 -8
  270. package/plugins/McpAutomationBridge/McpAutomationBridge.uplugin +0 -64
  271. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/McpAutomationBridge.Build.cs +0 -189
  272. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.cpp +0 -22
  273. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.h +0 -30
  274. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeHelpers.h +0 -1983
  275. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeModule.cpp +0 -72
  276. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSettings.cpp +0 -46
  277. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +0 -846
  278. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +0 -2393
  279. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetQueryHandlers.cpp +0 -300
  280. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetWorkflowHandlers.cpp +0 -2807
  281. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AudioHandlers.cpp +0 -1087
  282. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BehaviorTreeHandlers.cpp +0 -488
  283. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.cpp +0 -643
  284. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.h +0 -31
  285. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +0 -1094
  286. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +0 -5750
  287. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers_List.cpp +0 -152
  288. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ControlHandlers.cpp +0 -2614
  289. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_DebugHandlers.cpp +0 -42
  290. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EditorFunctionHandlers.cpp +0 -1237
  291. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +0 -1725
  292. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +0 -2265
  293. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_FoliageHandlers.cpp +0 -954
  294. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InputHandlers.cpp +0 -209
  295. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InsightsHandlers.cpp +0 -41
  296. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LandscapeHandlers.cpp +0 -1164
  297. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LevelHandlers.cpp +0 -762
  298. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +0 -663
  299. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LogHandlers.cpp +0 -136
  300. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_MaterialGraphHandlers.cpp +0 -494
  301. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraGraphHandlers.cpp +0 -278
  302. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraHandlers.cpp +0 -625
  303. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PerformanceHandlers.cpp +0 -401
  304. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PipelineHandlers.cpp +0 -67
  305. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +0 -472
  306. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PropertyHandlers.cpp +0 -2634
  307. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_RenderHandlers.cpp +0 -189
  308. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.cpp +0 -917
  309. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.h +0 -39
  310. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +0 -2706
  311. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequencerHandlers.cpp +0 -519
  312. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_TestHandlers.cpp +0 -38
  313. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_UiHandlers.cpp +0 -668
  314. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_WorldPartitionHandlers.cpp +0 -346
  315. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +0 -1345
  316. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.h +0 -149
  317. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +0 -782
  318. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSettings.h +0 -115
  319. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSubsystem.h +0 -796
  320. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpConnectionManager.h +0 -117
  321. package/scripts/check-unreal-connection.mjs +0 -19
  322. package/scripts/clean-tmp.js +0 -23
  323. package/scripts/patch-wasm.js +0 -26
  324. package/scripts/run-all-tests.mjs +0 -136
  325. package/scripts/smoke-test.ts +0 -94
  326. package/scripts/sync-mcp-plugin.js +0 -143
  327. package/scripts/test-no-plugin-alternates.mjs +0 -113
  328. package/scripts/validate-server.js +0 -46
  329. package/scripts/verify-automation-bridge.js +0 -200
  330. package/src/automation/bridge.ts +0 -630
  331. package/src/automation/connection-manager.ts +0 -148
  332. package/src/automation/handshake.ts +0 -99
  333. package/src/automation/index.ts +0 -2
  334. package/src/automation/message-handler.ts +0 -192
  335. package/src/automation/request-tracker.ts +0 -155
  336. package/src/automation/types.ts +0 -108
  337. package/src/cli.ts +0 -34
  338. package/src/config/class-aliases.ts +0 -65
  339. package/src/config.ts +0 -73
  340. package/src/constants.ts +0 -29
  341. package/src/graphql/loaders.ts +0 -244
  342. package/src/graphql/resolvers.ts +0 -1008
  343. package/src/graphql/schema.ts +0 -452
  344. package/src/graphql/server.ts +0 -156
  345. package/src/graphql/types.ts +0 -10
  346. package/src/handlers/resource-handlers.ts +0 -186
  347. package/src/index.ts +0 -243
  348. package/src/resources/actors.ts +0 -127
  349. package/src/resources/assets.ts +0 -286
  350. package/src/resources/levels.ts +0 -68
  351. package/src/server/resource-registry.ts +0 -47
  352. package/src/server/tool-registry.ts +0 -354
  353. package/src/server-setup.ts +0 -114
  354. package/src/services/health-monitor.ts +0 -132
  355. package/src/services/metrics-server.ts +0 -176
  356. package/src/tools/actors.ts +0 -564
  357. package/src/tools/animation.ts +0 -941
  358. package/src/tools/assets.ts +0 -394
  359. package/src/tools/audio.ts +0 -499
  360. package/src/tools/base-tool.ts +0 -52
  361. package/src/tools/behavior-tree.ts +0 -45
  362. package/src/tools/blueprint.ts +0 -940
  363. package/src/tools/consolidated-tool-definitions.ts +0 -1256
  364. package/src/tools/consolidated-tool-handlers.ts +0 -302
  365. package/src/tools/debug.ts +0 -622
  366. package/src/tools/dynamic-handler-registry.ts +0 -33
  367. package/src/tools/editor.ts +0 -435
  368. package/src/tools/engine.ts +0 -43
  369. package/src/tools/environment.ts +0 -281
  370. package/src/tools/foliage.ts +0 -596
  371. package/src/tools/handlers/actor-handlers.ts +0 -244
  372. package/src/tools/handlers/animation-handlers.ts +0 -237
  373. package/src/tools/handlers/argument-helper.ts +0 -142
  374. package/src/tools/handlers/asset-handlers.ts +0 -550
  375. package/src/tools/handlers/audio-handlers.ts +0 -194
  376. package/src/tools/handlers/blueprint-handlers.ts +0 -380
  377. package/src/tools/handlers/common-handlers.ts +0 -108
  378. package/src/tools/handlers/editor-handlers.ts +0 -124
  379. package/src/tools/handlers/effect-handlers.ts +0 -224
  380. package/src/tools/handlers/environment-handlers.ts +0 -183
  381. package/src/tools/handlers/graph-handlers.ts +0 -117
  382. package/src/tools/handlers/input-handlers.ts +0 -28
  383. package/src/tools/handlers/inspect-handlers.ts +0 -450
  384. package/src/tools/handlers/level-handlers.ts +0 -253
  385. package/src/tools/handlers/lighting-handlers.ts +0 -151
  386. package/src/tools/handlers/performance-handlers.ts +0 -132
  387. package/src/tools/handlers/pipeline-handlers.ts +0 -194
  388. package/src/tools/handlers/sequence-handlers.ts +0 -438
  389. package/src/tools/handlers/system-handlers.ts +0 -564
  390. package/src/tools/input.ts +0 -160
  391. package/src/tools/introspection.ts +0 -689
  392. package/src/tools/landscape.ts +0 -649
  393. package/src/tools/level.ts +0 -989
  394. package/src/tools/lighting.ts +0 -1052
  395. package/src/tools/logs.ts +0 -219
  396. package/src/tools/materials.ts +0 -295
  397. package/src/tools/niagara.ts +0 -485
  398. package/src/tools/performance.ts +0 -661
  399. package/src/tools/physics.ts +0 -679
  400. package/src/tools/property-dictionary.ts +0 -98
  401. package/src/tools/sequence.ts +0 -385
  402. package/src/tools/tool-definition-utils.ts +0 -35
  403. package/src/tools/ui.ts +0 -452
  404. package/src/types/automation-responses.ts +0 -119
  405. package/src/types/env.ts +0 -17
  406. package/src/types/handler-types.ts +0 -442
  407. package/src/types/responses.ts +0 -355
  408. package/src/types/tool-interfaces.ts +0 -250
  409. package/src/types/tool-types.ts +0 -575
  410. package/src/unreal-bridge.ts +0 -693
  411. package/src/utils/command-validator.ts +0 -139
  412. package/src/utils/elicitation.ts +0 -132
  413. package/src/utils/error-handler.ts +0 -287
  414. package/src/utils/ini-reader.ts +0 -86
  415. package/src/utils/logger.ts +0 -35
  416. package/src/utils/normalize.test.ts +0 -162
  417. package/src/utils/normalize.ts +0 -146
  418. package/src/utils/path-security.ts +0 -43
  419. package/src/utils/response-factory.ts +0 -44
  420. package/src/utils/response-validator.ts +0 -395
  421. package/src/utils/result-helpers.ts +0 -195
  422. package/src/utils/safe-json.test.ts +0 -90
  423. package/src/utils/safe-json.ts +0 -70
  424. package/src/utils/unreal-command-queue.ts +0 -166
  425. package/src/utils/validation.test.ts +0 -184
  426. package/src/utils/validation.ts +0 -312
  427. package/src/wasm/index.ts +0 -838
  428. package/test-server.mjs +0 -100
  429. package/tests/test-animation.mjs +0 -369
  430. package/tests/test-asset-advanced.mjs +0 -82
  431. package/tests/test-asset-graph.mjs +0 -311
  432. package/tests/test-audio.mjs +0 -417
  433. package/tests/test-automation-timeouts.mjs +0 -98
  434. package/tests/test-behavior-tree.mjs +0 -444
  435. package/tests/test-blueprint-graph.mjs +0 -410
  436. package/tests/test-blueprint.mjs +0 -577
  437. package/tests/test-client-mode.mjs +0 -86
  438. package/tests/test-console-command.mjs +0 -56
  439. package/tests/test-control-actor.mjs +0 -425
  440. package/tests/test-control-editor.mjs +0 -112
  441. package/tests/test-graphql.mjs +0 -372
  442. package/tests/test-input.mjs +0 -349
  443. package/tests/test-inspect.mjs +0 -302
  444. package/tests/test-landscape.mjs +0 -316
  445. package/tests/test-lighting.mjs +0 -428
  446. package/tests/test-manage-asset.mjs +0 -438
  447. package/tests/test-manage-level.mjs +0 -89
  448. package/tests/test-materials.mjs +0 -356
  449. package/tests/test-niagara.mjs +0 -185
  450. package/tests/test-no-inline-python.mjs +0 -122
  451. package/tests/test-performance.mjs +0 -539
  452. package/tests/test-plugin-handshake.mjs +0 -82
  453. package/tests/test-runner.mjs +0 -993
  454. package/tests/test-sequence.mjs +0 -104
  455. package/tests/test-system.mjs +0 -96
  456. package/tests/test-wasm.mjs +0 -283
  457. package/tests/test-world-partition.mjs +0 -215
  458. package/tsconfig.json +0 -56
  459. package/vitest.config.ts +0 -35
  460. package/wasm/Cargo.lock +0 -363
  461. package/wasm/Cargo.toml +0 -42
  462. package/wasm/LICENSE +0 -21
  463. package/wasm/README.md +0 -253
  464. package/wasm/src/dependency_resolver.rs +0 -377
  465. package/wasm/src/lib.rs +0 -153
  466. package/wasm/src/property_parser.rs +0 -271
  467. package/wasm/src/transform_math.rs +0 -396
  468. package/wasm/tests/integration.rs +0 -109
@@ -1,2706 +0,0 @@
1
- #include "LevelSequence.h"
2
- #include "McpAutomationBridgeGlobals.h"
3
- #include "McpAutomationBridgeHelpers.h"
4
- #include "McpAutomationBridgeSubsystem.h"
5
- #include "MovieScene.h"
6
- #include "MovieSceneBinding.h"
7
- #include "MovieSceneSection.h"
8
- #include "MovieSceneSequence.h"
9
- #include "MovieSceneTrack.h"
10
- #include "UObject/UObjectIterator.h"
11
-
12
- #if WITH_EDITOR
13
- #include "Editor.h"
14
- #include "EditorAssetLibrary.h"
15
- #if __has_include("Subsystems/EditorActorSubsystem.h")
16
- #include "Subsystems/EditorActorSubsystem.h"
17
- #define MCP_HAS_EDITOR_ACTOR_SUBSYSTEM 1
18
- #elif __has_include("EditorActorSubsystem.h")
19
- #include "EditorActorSubsystem.h"
20
- #define MCP_HAS_EDITOR_ACTOR_SUBSYSTEM 1
21
- #else
22
- #define MCP_HAS_EDITOR_ACTOR_SUBSYSTEM 0
23
- #endif
24
-
25
- #include "AssetRegistry/AssetRegistryModule.h"
26
- #include "AssetToolsModule.h"
27
- #include "Editor/EditorEngine.h"
28
- #include "Engine/Selection.h"
29
- #include "IAssetTools.h"
30
- #include "LevelSequenceEditorBlueprintLibrary.h"
31
- #include "Subsystems/AssetEditorSubsystem.h"
32
-
33
- // Header checks removed causing issues with private headers
34
-
35
- #if __has_include("LevelSequenceEditorSubsystem.h")
36
- #include "LevelSequenceEditorSubsystem.h"
37
- #define MCP_HAS_LEVELSEQUENCE_EDITOR_SUBSYSTEM 1
38
- #else
39
- #define MCP_HAS_LEVELSEQUENCE_EDITOR_SUBSYSTEM 0
40
- #endif
41
-
42
- #if __has_include("ILevelSequenceEditorToolkit.h")
43
- #include "ILevelSequenceEditorToolkit.h"
44
- #endif
45
-
46
- #if __has_include("ISequencer.h")
47
- #include "ISequencer.h"
48
- #include "MovieSceneSequencePlayer.h"
49
- #endif
50
-
51
- #if __has_include("Tracks/MovieSceneFloatTrack.h")
52
- #include "Sections/MovieSceneFloatSection.h"
53
- #include "Tracks/MovieSceneFloatTrack.h"
54
-
55
- #endif
56
-
57
- #if __has_include("Tracks/MovieSceneBoolTrack.h")
58
- #include "Sections/MovieSceneBoolSection.h"
59
- #include "Tracks/MovieSceneBoolTrack.h"
60
-
61
- #endif
62
-
63
- #if __has_include("Tracks/MovieScene3DTransformTrack.h")
64
- #include "Tracks/MovieScene3DTransformTrack.h"
65
- #endif
66
-
67
- #include "Tracks/MovieSceneAudioTrack.h"
68
- #include "Tracks/MovieSceneEventTrack.h"
69
-
70
- #if __has_include("Sections/MovieScene3DTransformSection.h")
71
- #include "Sections/MovieScene3DTransformSection.h"
72
- #endif
73
- #if __has_include("Channels/MovieSceneDoubleChannel.h")
74
- #include "Channels/MovieSceneDoubleChannel.h"
75
- #endif
76
- #if __has_include("Channels/MovieSceneChannelProxy.h")
77
- #include "Channels/MovieSceneChannelProxy.h"
78
- #endif
79
-
80
- // Optional components check
81
- #if __has_include("Misc/ScopedTransaction.h")
82
- #include "Misc/ScopedTransaction.h"
83
- #endif
84
- #if __has_include("Camera/CameraActor.h")
85
- #include "Camera/CameraActor.h"
86
- #endif
87
- #endif
88
-
89
- FString UMcpAutomationBridgeSubsystem::ResolveSequencePath(
90
- const TSharedPtr<FJsonObject> &Payload) {
91
- FString Path;
92
- if (Payload.IsValid() && Payload->TryGetStringField(TEXT("path"), Path) &&
93
- !Path.IsEmpty()) {
94
- #if WITH_EDITOR
95
- // Check existence first to avoid error log spam
96
- if (UEditorAssetLibrary::DoesAssetExist(Path)) {
97
- UObject *Obj = UEditorAssetLibrary::LoadAsset(Path);
98
- if (Obj) {
99
- return Obj->GetPathName();
100
- }
101
- }
102
- #endif
103
- return Path;
104
- }
105
- if (!GCurrentSequencePath.IsEmpty())
106
- return GCurrentSequencePath;
107
- return FString();
108
- }
109
-
110
- TSharedPtr<FJsonObject>
111
- UMcpAutomationBridgeSubsystem::EnsureSequenceEntry(const FString &SeqPath) {
112
- if (SeqPath.IsEmpty())
113
- return nullptr;
114
- if (TSharedPtr<FJsonObject> *Found = GSequenceRegistry.Find(SeqPath))
115
- return *Found;
116
- TSharedPtr<FJsonObject> NewObj = MakeShared<FJsonObject>();
117
- NewObj->SetStringField(TEXT("sequencePath"), SeqPath);
118
- GSequenceRegistry.Add(SeqPath, NewObj);
119
- return NewObj;
120
- }
121
-
122
- bool UMcpAutomationBridgeSubsystem::HandleSequenceCreate(
123
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
124
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
125
- #if WITH_EDITOR
126
- TSharedPtr<FJsonObject> LocalPayload =
127
- Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
128
- FString Name;
129
- LocalPayload->TryGetStringField(TEXT("name"), Name);
130
- FString Path;
131
- LocalPayload->TryGetStringField(TEXT("path"), Path);
132
- if (Name.IsEmpty()) {
133
- SendAutomationResponse(Socket, RequestId, false,
134
- TEXT("sequence_create requires name"), nullptr,
135
- TEXT("INVALID_ARGUMENT"));
136
- return true;
137
- }
138
- FString FullPath = Path.IsEmpty()
139
- ? FString::Printf(TEXT("/Game/%s"), *Name)
140
- : FString::Printf(TEXT("%s/%s"), *Path, *Name);
141
-
142
- FString DestFolder = Path.IsEmpty() ? TEXT("/Game") : Path;
143
- if (DestFolder.StartsWith(TEXT("/Content"), ESearchCase::IgnoreCase)) {
144
- DestFolder = FString::Printf(TEXT("/Game%s"), *DestFolder.RightChop(8));
145
- }
146
-
147
- TWeakObjectPtr<UMcpAutomationBridgeSubsystem> WeakSubsystem(this);
148
- FString RequestIdArg = RequestId;
149
-
150
- // Execute on Game Thread
151
- UMcpAutomationBridgeSubsystem *Subsystem = this;
152
- UE_LOG(LogMcpAutomationBridgeSubsystem, Warning,
153
- TEXT("HandleSequenceCreate: Handing RequestID=%s Path=%s"),
154
- *RequestIdArg, *FullPath);
155
-
156
- // Check existence first to avoid error log spam
157
- if (UEditorAssetLibrary::DoesAssetExist(FullPath)) {
158
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
159
- Resp->SetStringField(TEXT("sequencePath"), FullPath);
160
- UE_LOG(LogMcpAutomationBridgeSubsystem, Warning,
161
- TEXT("HandleSequenceCreate: Sequence exists, sending response for "
162
- "RequestID=%s"),
163
- *RequestIdArg);
164
- Subsystem->SendAutomationResponse(Socket, RequestIdArg, true,
165
- TEXT("Sequence already exists"), Resp,
166
- FString());
167
- return true;
168
- }
169
-
170
- // Dynamic factory lookup
171
- UClass *FactoryClass = FindObject<UClass>(
172
- nullptr, TEXT("/Script/LevelSequenceEditor.LevelSequenceFactoryNew"));
173
- if (!FactoryClass)
174
- FactoryClass = LoadClass<UClass>(
175
- nullptr, TEXT("/Script/LevelSequenceEditor.LevelSequenceFactoryNew"));
176
-
177
- if (FactoryClass) {
178
- UFactory *Factory =
179
- NewObject<UFactory>(GetTransientPackage(), FactoryClass);
180
- FAssetToolsModule &AssetToolsModule =
181
- FModuleManager::LoadModuleChecked<FAssetToolsModule>(
182
- TEXT("AssetTools"));
183
- UObject *NewObj = AssetToolsModule.Get().CreateAsset(
184
- Name, DestFolder, ULevelSequence::StaticClass(), Factory);
185
- if (NewObj) {
186
- UEditorAssetLibrary::SaveAsset(FullPath);
187
- GCurrentSequencePath = FullPath;
188
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
189
- Resp->SetStringField(TEXT("sequencePath"), FullPath);
190
- UE_LOG(LogMcpAutomationBridgeSubsystem, Warning,
191
- TEXT("HandleSequenceCreate: Created sequence, sending response "
192
- "for RequestID=%s"),
193
- *RequestIdArg);
194
- Subsystem->SendAutomationResponse(Socket, RequestIdArg, true,
195
- TEXT("Sequence created"), Resp,
196
- FString());
197
- } else {
198
- UE_LOG(
199
- LogMcpAutomationBridgeSubsystem, Error,
200
- TEXT("HandleSequenceCreate: Failed to create asset for RequestID=%s"),
201
- *RequestIdArg);
202
- Subsystem->SendAutomationResponse(Socket, RequestIdArg, false,
203
- TEXT("Failed to create sequence asset"),
204
- nullptr, TEXT("CREATE_ASSET_FAILED"));
205
- }
206
- } else {
207
- UE_LOG(LogMcpAutomationBridgeSubsystem, Error,
208
- TEXT("HandleSequenceCreate: Factory not found for RequestID=%s"),
209
- *RequestIdArg);
210
- Subsystem->SendAutomationResponse(
211
- Socket, RequestIdArg, false,
212
- TEXT("LevelSequenceFactoryNew class not found (Module not loaded?)"),
213
- nullptr, TEXT("FACTORY_NOT_AVAILABLE"));
214
- }
215
- return true;
216
- return true;
217
-
218
- #else
219
- SendAutomationResponse(Socket, RequestId, false,
220
- TEXT("sequence_create requires editor build"), nullptr,
221
- TEXT("NOT_AVAILABLE"));
222
- return true;
223
- #endif
224
- }
225
-
226
- bool UMcpAutomationBridgeSubsystem::HandleSequenceSetDisplayRate(
227
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
228
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
229
- #if WITH_EDITOR
230
- TSharedPtr<FJsonObject> LocalPayload =
231
- Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
232
- FString SeqPath = ResolveSequencePath(LocalPayload);
233
- if (SeqPath.IsEmpty()) {
234
- SendAutomationResponse(
235
- Socket, RequestId, false,
236
- TEXT("sequence_set_display_rate requires a sequence path"), nullptr,
237
- TEXT("INVALID_SEQUENCE"));
238
- return true;
239
- }
240
-
241
- UObject *SeqObj = UEditorAssetLibrary::LoadAsset(SeqPath);
242
- if (!SeqObj) {
243
- SendAutomationResponse(Socket, RequestId, false, TEXT("Sequence not found"),
244
- nullptr, TEXT("INVALID_SEQUENCE"));
245
- return true;
246
- }
247
-
248
- if (ULevelSequence *LevelSeq = Cast<ULevelSequence>(SeqObj)) {
249
- if (UMovieScene *MovieScene = LevelSeq->GetMovieScene()) {
250
- FString FrameRateStr;
251
- double FrameRateVal = 0.0;
252
-
253
- FFrameRate NewRate;
254
- bool bRateFound = false;
255
-
256
- if (LocalPayload->TryGetStringField(TEXT("frameRate"), FrameRateStr)) {
257
- // Parse "30fps", "24000/1001", etc.
258
- // Simple parsing for standard rates
259
- if (FrameRateStr.EndsWith(TEXT("fps"))) {
260
- FrameRateStr.RemoveFromEnd(TEXT("fps"));
261
- NewRate = FFrameRate(FCString::Atoi(*FrameRateStr), 1);
262
- bRateFound = true;
263
- } else if (FrameRateStr.Contains(TEXT("/"))) {
264
- // Rational
265
- FString NumStr, DenomStr;
266
- if (FrameRateStr.Split(TEXT("/"), &NumStr, &DenomStr)) {
267
- NewRate =
268
- FFrameRate(FCString::Atoi(*NumStr), FCString::Atoi(*DenomStr));
269
- bRateFound = true;
270
- }
271
- } else {
272
- // Decimal string?
273
- if (FrameRateStr.IsNumeric()) {
274
- NewRate = FFrameRate(FCString::Atoi(*FrameRateStr), 1);
275
- bRateFound = true;
276
- }
277
- }
278
- } else if (LocalPayload->TryGetNumberField(TEXT("frameRate"),
279
- FrameRateVal)) {
280
- NewRate = FFrameRate(FMath::RoundToInt(FrameRateVal), 1);
281
- bRateFound = true;
282
- }
283
-
284
- if (bRateFound) {
285
- MovieScene->SetDisplayRate(NewRate);
286
- MovieScene->Modify();
287
-
288
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
289
- Resp->SetBoolField(TEXT("success"), true);
290
- Resp->SetStringField(TEXT("displayRate"),
291
- NewRate.ToPrettyText().ToString());
292
- SendAutomationResponse(Socket, RequestId, true,
293
- TEXT("Display rate set"), Resp, FString());
294
- return true;
295
- }
296
-
297
- SendAutomationResponse(Socket, RequestId, false,
298
- TEXT("Invalid frameRate format"), nullptr,
299
- TEXT("INVALID_ARGUMENT"));
300
- return true;
301
- }
302
- }
303
-
304
- SendAutomationResponse(Socket, RequestId, false,
305
- TEXT("Invalid sequence type"), nullptr,
306
- TEXT("INVALID_SEQUENCE"));
307
- return true;
308
- #else
309
- SendAutomationResponse(
310
- Socket, RequestId, false,
311
- TEXT("sequence_set_display_rate requires editor build"), nullptr,
312
- TEXT("NOT_IMPLEMENTED"));
313
- return true;
314
- #endif
315
- }
316
-
317
- bool UMcpAutomationBridgeSubsystem::HandleSequenceSetProperties(
318
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
319
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
320
- TSharedPtr<FJsonObject> LocalPayload =
321
- Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
322
- FString SeqPath = ResolveSequencePath(LocalPayload);
323
- if (SeqPath.IsEmpty()) {
324
- SendAutomationResponse(
325
- Socket, RequestId, false,
326
- TEXT("sequence_set_properties requires a sequence path"), nullptr,
327
- TEXT("INVALID_SEQUENCE"));
328
- return true;
329
- }
330
-
331
- #if WITH_EDITOR
332
- TWeakObjectPtr<UMcpAutomationBridgeSubsystem> WeakSubsystem(this);
333
- FString RequestIdArg = RequestId;
334
-
335
- // Capture simple types. For JsonObject, we need to capture the data, not the
336
- // pointer if we want to be safe, but since we parsed it above, we should
337
- // capture the parsed values. Parsing logic happens above. We'll capture the
338
- // parsed variables. But wait, the parsing logic in the original code is
339
- // INSIDE the block I'm replacing (lines 176-185). I need to include the
340
- // parsing inside the Async task or move it out. I'll move the parsing INSIDE
341
- // the Async task, but I need to capture LocalPayload. LocalPayload is a
342
- // SharedPtr, so it's safe to capture.
343
-
344
- UMcpAutomationBridgeSubsystem *Subsystem = this;
345
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
346
- UObject *SeqObj = UEditorAssetLibrary::LoadAsset(SeqPath);
347
- if (!SeqObj) {
348
- Subsystem->SendAutomationResponse(Socket, RequestIdArg, false,
349
- TEXT("Sequence not found"), nullptr,
350
- TEXT("INVALID_SEQUENCE"));
351
- return true;
352
- }
353
-
354
- if (ULevelSequence *LevelSeq = Cast<ULevelSequence>(SeqObj)) {
355
- if (UMovieScene *MovieScene = LevelSeq->GetMovieScene()) {
356
- bool bModified = false;
357
- double FrameRateValue = 0.0;
358
- double LengthInFramesValue = 0.0;
359
- double PlaybackStartValue = 0.0;
360
- double PlaybackEndValue = 0.0;
361
-
362
- const bool bHasFrameRate =
363
- LocalPayload->TryGetNumberField(TEXT("frameRate"), FrameRateValue);
364
- const bool bHasLengthInFrames = LocalPayload->TryGetNumberField(
365
- TEXT("lengthInFrames"), LengthInFramesValue);
366
- const bool bHasPlaybackStart = LocalPayload->TryGetNumberField(
367
- TEXT("playbackStart"), PlaybackStartValue);
368
- const bool bHasPlaybackEnd = LocalPayload->TryGetNumberField(
369
- TEXT("playbackEnd"), PlaybackEndValue);
370
-
371
- if (bHasFrameRate) {
372
- if (FrameRateValue <= 0.0) {
373
- Subsystem->SendAutomationResponse(Socket, RequestIdArg, false,
374
- TEXT("frameRate must be > 0"),
375
- nullptr, TEXT("INVALID_ARGUMENT"));
376
- return true;
377
- }
378
- const int32 Rounded =
379
- FMath::Clamp<int32>(FMath::RoundToInt(FrameRateValue), 1, 960);
380
- FFrameRate CurrentRate = MovieScene->GetDisplayRate();
381
- FFrameRate NewRate(Rounded, 1);
382
- if (NewRate != CurrentRate) {
383
- MovieScene->SetDisplayRate(NewRate);
384
- bModified = true;
385
- }
386
- }
387
-
388
- if (bHasPlaybackStart || bHasPlaybackEnd || bHasLengthInFrames) {
389
- TRange<FFrameNumber> ExistingRange = MovieScene->GetPlaybackRange();
390
- FFrameNumber StartFrame = ExistingRange.GetLowerBoundValue();
391
- FFrameNumber EndFrame = ExistingRange.GetUpperBoundValue();
392
-
393
- if (bHasPlaybackStart)
394
- StartFrame = FFrameNumber(static_cast<int32>(PlaybackStartValue));
395
- if (bHasPlaybackEnd)
396
- EndFrame = FFrameNumber(static_cast<int32>(PlaybackEndValue));
397
- else if (bHasLengthInFrames)
398
- EndFrame =
399
- StartFrame +
400
- FMath::Max<int32>(0, static_cast<int32>(LengthInFramesValue));
401
-
402
- if (EndFrame < StartFrame)
403
- EndFrame = StartFrame;
404
- MovieScene->SetPlaybackRange(
405
- TRange<FFrameNumber>(StartFrame, EndFrame));
406
- bModified = true;
407
- }
408
-
409
- if (bModified)
410
- MovieScene->Modify();
411
-
412
- FFrameRate FR = MovieScene->GetDisplayRate();
413
- TSharedPtr<FJsonObject> FrameRateObj = MakeShared<FJsonObject>();
414
- FrameRateObj->SetNumberField(TEXT("numerator"), FR.Numerator);
415
- FrameRateObj->SetNumberField(TEXT("denominator"), FR.Denominator);
416
- Resp->SetObjectField(TEXT("frameRate"), FrameRateObj);
417
-
418
- TRange<FFrameNumber> Range = MovieScene->GetPlaybackRange();
419
- const double Start =
420
- static_cast<double>(Range.GetLowerBoundValue().Value);
421
- const double End = static_cast<double>(Range.GetUpperBoundValue().Value);
422
- Resp->SetNumberField(TEXT("playbackStart"), Start);
423
- Resp->SetNumberField(TEXT("playbackEnd"), End);
424
- Resp->SetNumberField(TEXT("duration"), End - Start);
425
- Resp->SetBoolField(TEXT("applied"), bModified);
426
-
427
- Subsystem->SendAutomationResponse(Socket, RequestIdArg, true,
428
- TEXT("properties updated"), Resp,
429
- FString());
430
- return true;
431
- }
432
- }
433
- Resp->SetObjectField(TEXT("frameRate"), MakeShared<FJsonObject>());
434
- Resp->SetNumberField(TEXT("playbackStart"), 0.0);
435
- Resp->SetNumberField(TEXT("playbackEnd"), 0.0);
436
- Resp->SetNumberField(TEXT("duration"), 0.0);
437
- Resp->SetBoolField(TEXT("applied"), false);
438
- Subsystem->SendAutomationResponse(
439
- Socket, RequestIdArg, false,
440
- TEXT("sequence_set_properties is not available in this editor build or "
441
- "for this sequence type"),
442
- Resp, TEXT("NOT_IMPLEMENTED"));
443
- return true;
444
- return true;
445
- #else
446
- SendAutomationResponse(Socket, RequestId, false,
447
- TEXT("sequence_set_properties requires editor build."),
448
- nullptr, TEXT("NOT_IMPLEMENTED"));
449
- return true;
450
- #endif
451
- }
452
-
453
- bool UMcpAutomationBridgeSubsystem::HandleSequenceOpen(
454
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
455
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
456
- TSharedPtr<FJsonObject> LocalPayload =
457
- Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
458
- FString SeqPath = ResolveSequencePath(LocalPayload);
459
- if (SeqPath.IsEmpty()) {
460
- SendAutomationResponse(Socket, RequestId, false,
461
- TEXT("sequence_open requires a sequence path"),
462
- nullptr, TEXT("INVALID_SEQUENCE"));
463
- return true;
464
- }
465
-
466
- #if WITH_EDITOR
467
- TWeakObjectPtr<UMcpAutomationBridgeSubsystem> WeakSubsystem(this);
468
- FString RequestIdArg = RequestId;
469
- UMcpAutomationBridgeSubsystem *Subsystem = this;
470
- UE_LOG(LogMcpAutomationBridgeSubsystem, Warning,
471
- TEXT("HandleSequenceOpen: Opening sequence %s for RequestID=%s"),
472
- *SeqPath, *RequestIdArg);
473
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
474
- UObject *SeqObj = UEditorAssetLibrary::LoadAsset(SeqPath);
475
- if (!SeqObj) {
476
- Subsystem->SendAutomationResponse(Socket, RequestIdArg, false,
477
- TEXT("Sequence not found"), nullptr,
478
- TEXT("INVALID_SEQUENCE"));
479
- return true;
480
- }
481
-
482
- if (ULevelSequence *LevelSeq = Cast<ULevelSequence>(SeqObj)) {
483
- if (GEditor) {
484
- if (ULevelSequenceEditorSubsystem *LSES =
485
- GEditor->GetEditorSubsystem<ULevelSequenceEditorSubsystem>()) {
486
- if (UAssetEditorSubsystem *AssetEditorSS =
487
- GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()) {
488
- AssetEditorSS->OpenEditorForAsset(LevelSeq);
489
- Resp->SetStringField(TEXT("sequencePath"), SeqPath);
490
- Resp->SetStringField(TEXT("message"), TEXT("Sequence opened"));
491
- UE_LOG(LogMcpAutomationBridgeSubsystem, Warning,
492
- TEXT("HandleSequenceOpen: Successfully opened in LSES, "
493
- "sending response for RequestID=%s"),
494
- *RequestIdArg);
495
- Subsystem->SendAutomationResponse(Socket, RequestIdArg, true,
496
- TEXT("Sequence opened"), Resp,
497
- FString());
498
- return true;
499
- }
500
- }
501
- }
502
- }
503
-
504
- if (GEditor) {
505
- if (UAssetEditorSubsystem *AssetEditorSS =
506
- GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()) {
507
- AssetEditorSS->OpenEditorForAsset(SeqObj);
508
- }
509
- }
510
- Resp->SetStringField(TEXT("sequencePath"), SeqPath);
511
- Resp->SetStringField(TEXT("message"), TEXT("Sequence opened (asset editor)"));
512
- UE_LOG(LogMcpAutomationBridgeSubsystem, Warning,
513
- TEXT("HandleSequenceOpen: Opened via AssetEditorSS, sending response "
514
- "for RequestID=%s"),
515
- *RequestIdArg);
516
- Subsystem->SendAutomationResponse(Socket, RequestIdArg, true,
517
- TEXT("Sequence opened"), Resp, FString());
518
- return true;
519
- return true;
520
- #else
521
- SendAutomationResponse(Socket, RequestId, false,
522
- TEXT("sequence_open requires editor build."), nullptr,
523
- TEXT("NOT_AVAILABLE"));
524
- return true;
525
- #endif
526
- }
527
-
528
- bool UMcpAutomationBridgeSubsystem::HandleSequenceAddCamera(
529
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
530
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
531
- TSharedPtr<FJsonObject> LocalPayload =
532
- Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
533
- FString SeqPath = ResolveSequencePath(LocalPayload);
534
- if (SeqPath.IsEmpty()) {
535
- SendAutomationResponse(Socket, RequestId, false,
536
- TEXT("sequence_add_camera requires a sequence path"),
537
- nullptr, TEXT("INVALID_SEQUENCE"));
538
- return true;
539
- }
540
-
541
- #if WITH_EDITOR
542
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
543
- UObject *SeqObj = UEditorAssetLibrary::LoadAsset(SeqPath);
544
- if (!SeqObj) {
545
- SendAutomationResponse(Socket, RequestId, false, TEXT("Sequence not found"),
546
- nullptr, TEXT("INVALID_SEQUENCE"));
547
- return true;
548
- }
549
-
550
- #if MCP_HAS_EDITOR_ACTOR_SUBSYSTEM
551
- if (GEditor) {
552
- UClass *CameraClass = ACameraActor::StaticClass();
553
- AActor *Spawned = SpawnActorInActiveWorld<AActor>(
554
- CameraClass, FVector::ZeroVector, FRotator::ZeroRotator,
555
- TEXT("SequenceCamera"));
556
- if (Spawned) {
557
- // Fix for Issue #6: Auto-bind the camera to the sequence
558
- if (ULevelSequence *LevelSeq = Cast<ULevelSequence>(SeqObj)) {
559
- if (UMovieScene *MovieScene = LevelSeq->GetMovieScene()) {
560
- FGuid BindingGuid = MovieScene->AddPossessable(
561
- Spawned->GetActorLabel(), Spawned->GetClass());
562
- if (MovieScene->FindPossessable(BindingGuid)) {
563
- MovieScene->Modify();
564
- Resp->SetStringField(TEXT("bindingGuid"), BindingGuid.ToString());
565
- }
566
- }
567
- }
568
-
569
- Resp->SetBoolField(TEXT("success"), true);
570
- Resp->SetStringField(TEXT("actorLabel"), Spawned->GetActorLabel());
571
- SendAutomationResponse(Socket, RequestId, true,
572
- TEXT("Camera actor spawned and bound to sequence"),
573
- Resp, FString());
574
- return true;
575
- }
576
- }
577
- SendAutomationResponse(Socket, RequestId, false, TEXT("Failed to add camera"),
578
- nullptr, TEXT("ADD_CAMERA_FAILED"));
579
- return true;
580
- #else
581
- SendAutomationResponse(Socket, RequestId, false,
582
- TEXT("UEditorActorSubsystem not available"), nullptr,
583
- TEXT("NOT_AVAILABLE"));
584
- return true;
585
- #endif
586
- #else
587
- SendAutomationResponse(Socket, RequestId, false,
588
- TEXT("sequence_add_camera requires editor build."),
589
- nullptr, TEXT("NOT_IMPLEMENTED"));
590
- return true;
591
- #endif
592
- }
593
-
594
- bool UMcpAutomationBridgeSubsystem::HandleSequencePlay(
595
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
596
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
597
- TSharedPtr<FJsonObject> LocalPayload =
598
- Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
599
- FString SeqPath = ResolveSequencePath(LocalPayload);
600
- if (SeqPath.IsEmpty()) {
601
- SendAutomationResponse(Socket, RequestId, false,
602
- TEXT("No sequence selected or path provided"),
603
- nullptr, TEXT("INVALID_SEQUENCE"));
604
- return true;
605
- }
606
-
607
- #if WITH_EDITOR
608
- TWeakObjectPtr<UMcpAutomationBridgeSubsystem> WeakSubsystem(this);
609
- FString RequestIdArg = RequestId;
610
- UMcpAutomationBridgeSubsystem *Subsystem = this;
611
- ULevelSequence *LevelSeq =
612
- Cast<ULevelSequence>(UEditorAssetLibrary::LoadAsset(SeqPath));
613
- if (LevelSeq) {
614
- if (ULevelSequenceEditorBlueprintLibrary::OpenLevelSequence(LevelSeq)) {
615
- ULevelSequenceEditorBlueprintLibrary::Play();
616
- Subsystem->SendAutomationResponse(Socket, RequestIdArg, true,
617
- TEXT("Sequence playing"), nullptr);
618
- return true;
619
- }
620
- }
621
- Subsystem->SendAutomationResponse(Socket, RequestIdArg, false,
622
- TEXT("Failed to open or play sequence"),
623
- nullptr, TEXT("EXECUTION_ERROR"));
624
- return true;
625
- return true;
626
- #else
627
- SendAutomationResponse(Socket, RequestId, false,
628
- TEXT("sequence_play requires editor build."), nullptr,
629
- TEXT("NOT_AVAILABLE"));
630
- return true;
631
- #endif
632
- }
633
-
634
- bool UMcpAutomationBridgeSubsystem::HandleSequenceAddActor(
635
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
636
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
637
- TSharedPtr<FJsonObject> LocalPayload =
638
- Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
639
- FString ActorName;
640
- LocalPayload->TryGetStringField(TEXT("actorName"), ActorName);
641
- if (ActorName.IsEmpty()) {
642
- SendAutomationResponse(Socket, RequestId, false, TEXT("actorName required"),
643
- nullptr, TEXT("INVALID_ARGUMENT"));
644
- return true;
645
- }
646
- FString SeqPath = ResolveSequencePath(LocalPayload);
647
- if (SeqPath.IsEmpty()) {
648
- SendAutomationResponse(Socket, RequestId, false,
649
- TEXT("sequence_add_actor requires a sequence path"),
650
- nullptr, TEXT("INVALID_SEQUENCE"));
651
- return true;
652
- }
653
-
654
- #if WITH_EDITOR
655
- // Reuse multi-actor binding logic for a single actor by forwarding to
656
- // HandleSequenceAddActors with a one-element actorNames array and the
657
- // resolved sequence path. This ensures real LevelSequence bindings are
658
- // applied when supported by the editor build.
659
- TSharedPtr<FJsonObject> ForwardPayload = MakeShared<FJsonObject>();
660
- ForwardPayload->SetStringField(TEXT("path"), SeqPath);
661
- TArray<TSharedPtr<FJsonValue>> NamesArray;
662
- NamesArray.Add(MakeShared<FJsonValueString>(ActorName));
663
- ForwardPayload->SetArrayField(TEXT("actorNames"), NamesArray);
664
-
665
- return HandleSequenceAddActors(RequestId, ForwardPayload, Socket);
666
- #else
667
- SendAutomationResponse(Socket, RequestId, false,
668
- TEXT("sequence_add_actor requires editor build."),
669
- nullptr, TEXT("NOT_IMPLEMENTED"));
670
- return true;
671
- #endif
672
- }
673
-
674
- bool UMcpAutomationBridgeSubsystem::HandleSequenceAddActors(
675
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
676
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
677
- TSharedPtr<FJsonObject> LocalPayload =
678
- Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
679
- const TArray<TSharedPtr<FJsonValue>> *Arr = nullptr;
680
- LocalPayload->TryGetArrayField(TEXT("actorNames"), Arr);
681
- if (!Arr || Arr->Num() == 0) {
682
- SendAutomationResponse(Socket, RequestId, false,
683
- TEXT("actorNames required"), nullptr,
684
- TEXT("INVALID_ARGUMENT"));
685
- return true;
686
- }
687
- FString SeqPath = ResolveSequencePath(LocalPayload);
688
- if (SeqPath.IsEmpty()) {
689
- SendAutomationResponse(Socket, RequestId, false,
690
- TEXT("sequence_add_actors requires a sequence path"),
691
- nullptr, TEXT("INVALID_SEQUENCE"));
692
- return true;
693
- }
694
-
695
- #if WITH_EDITOR
696
- TArray<FString> Names;
697
- Names.Reserve(Arr->Num());
698
- for (const TSharedPtr<FJsonValue> &V : *Arr) {
699
- if (V.IsValid() && V->Type == EJson::String)
700
- Names.Add(V->AsString());
701
- }
702
-
703
- TWeakObjectPtr<UMcpAutomationBridgeSubsystem> WeakSubsystem(this);
704
- FString RequestIdArg = RequestId;
705
- UMcpAutomationBridgeSubsystem *Subsystem = this;
706
- UObject *SeqObj = UEditorAssetLibrary::LoadAsset(SeqPath);
707
- if (!SeqObj) {
708
- Subsystem->SendAutomationResponse(Socket, RequestIdArg, false,
709
- TEXT("Sequence not found"), nullptr,
710
- TEXT("INVALID_SEQUENCE"));
711
- return true;
712
- }
713
- if (!GEditor) {
714
- Subsystem->SendAutomationResponse(Socket, RequestIdArg, false,
715
- TEXT("Editor not available"), nullptr,
716
- TEXT("EDITOR_NOT_AVAILABLE"));
717
- return true;
718
- }
719
-
720
- #if MCP_HAS_EDITOR_ACTOR_SUBSYSTEM
721
- if (UEditorActorSubsystem *ActorSS =
722
- GEditor->GetEditorSubsystem<UEditorActorSubsystem>()) {
723
- TArray<TSharedPtr<FJsonValue>> Results;
724
- Results.Reserve(Names.Num());
725
- for (const FString &Name : Names) {
726
- TSharedPtr<FJsonObject> Item = MakeShared<FJsonObject>();
727
- Item->SetStringField(TEXT("name"), Name);
728
- // Use robust actor lookup that checks label, name, and UAID
729
- AActor *Found = Subsystem->FindActorByName(Name);
730
-
731
- if (!Found) {
732
- Item->SetBoolField(TEXT("success"), false);
733
- Item->SetStringField(TEXT("error"), TEXT("Actor not found"));
734
- } else {
735
- if (ULevelSequence *LevelSeq = Cast<ULevelSequence>(SeqObj)) {
736
- UMovieScene *MovieScene = LevelSeq->GetMovieScene();
737
- if (MovieScene) {
738
- FGuid BindingGuid = MovieScene->AddPossessable(
739
- Found->GetActorLabel(), Found->GetClass());
740
- if (MovieScene->FindPossessable(BindingGuid)) {
741
- Item->SetBoolField(TEXT("success"), true);
742
- Item->SetStringField(TEXT("bindingGuid"), BindingGuid.ToString());
743
- MovieScene->Modify();
744
- } else {
745
- Item->SetBoolField(TEXT("success"), false);
746
- Item->SetStringField(
747
- TEXT("error"), TEXT("Failed to create possessable binding"));
748
- }
749
- } else {
750
- Item->SetBoolField(TEXT("success"), false);
751
- Item->SetStringField(TEXT("error"),
752
- TEXT("Sequence has no MovieScene"));
753
- }
754
- } else {
755
- Item->SetBoolField(TEXT("success"), false);
756
- Item->SetStringField(TEXT("error"),
757
- TEXT("Sequence object is not a LevelSequence"));
758
- }
759
- }
760
- Results.Add(MakeShared<FJsonValueObject>(Item));
761
- }
762
- TSharedPtr<FJsonObject> Out = MakeShared<FJsonObject>();
763
- Out->SetArrayField(TEXT("results"), Results);
764
- Subsystem->SendAutomationResponse(Socket, RequestIdArg, true,
765
- TEXT("Actors processed"), Out, FString());
766
- return true;
767
- }
768
- Subsystem->SendAutomationResponse(
769
- Socket, RequestIdArg, false, TEXT("EditorActorSubsystem not available"),
770
- nullptr, TEXT("EDITOR_ACTOR_SUBSYSTEM_MISSING"));
771
- return true;
772
- #else
773
- Subsystem->SendAutomationResponse(Socket, RequestIdArg, false,
774
- TEXT("UEditorActorSubsystem not available"),
775
- nullptr, TEXT("NOT_AVAILABLE"));
776
- #endif
777
- return true;
778
- #else
779
- SendAutomationResponse(Socket, RequestId, false,
780
- TEXT("sequence_add_actors requires editor build."),
781
- nullptr, TEXT("NOT_IMPLEMENTED"));
782
- return true;
783
- #endif
784
- }
785
-
786
- bool UMcpAutomationBridgeSubsystem::HandleSequenceAddSpawnable(
787
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
788
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
789
- TSharedPtr<FJsonObject> LocalPayload =
790
- Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
791
- FString ClassName;
792
- LocalPayload->TryGetStringField(TEXT("className"), ClassName);
793
- if (ClassName.IsEmpty()) {
794
- SendAutomationResponse(Socket, RequestId, false, TEXT("className required"),
795
- nullptr, TEXT("INVALID_ARGUMENT"));
796
- return true;
797
- }
798
- FString SeqPath = ResolveSequencePath(LocalPayload);
799
- if (SeqPath.IsEmpty()) {
800
- SendAutomationResponse(
801
- Socket, RequestId, false,
802
- TEXT("sequence_add_spawnable_from_class requires a sequence path"),
803
- nullptr, TEXT("INVALID_SEQUENCE"));
804
- return true;
805
- }
806
-
807
- #if WITH_EDITOR
808
- UObject *SeqObj = UEditorAssetLibrary::LoadAsset(SeqPath);
809
- if (!SeqObj) {
810
- SendAutomationResponse(Socket, RequestId, false, TEXT("Sequence not found"),
811
- nullptr, TEXT("INVALID_SEQUENCE"));
812
- return true;
813
- }
814
-
815
- UClass *ResolvedClass = nullptr;
816
- if (ClassName.StartsWith(TEXT("/")) || ClassName.Contains(TEXT("/"))) {
817
- if (UObject *Loaded = UEditorAssetLibrary::LoadAsset(ClassName)) {
818
- if (UBlueprint *BP = Cast<UBlueprint>(Loaded))
819
- ResolvedClass = BP->GeneratedClass;
820
- else if (UClass *C = Cast<UClass>(Loaded))
821
- ResolvedClass = C;
822
- }
823
- }
824
- if (!ResolvedClass)
825
- ResolvedClass = ResolveClassByName(ClassName);
826
- if (!ResolvedClass) {
827
- SendAutomationResponse(Socket, RequestId, false, TEXT("Class not found"),
828
- nullptr, TEXT("CLASS_NOT_FOUND"));
829
- return true;
830
- }
831
-
832
- if (ULevelSequence *LevelSeq = Cast<ULevelSequence>(SeqObj)) {
833
- UMovieScene *MovieScene = LevelSeq->GetMovieScene();
834
- if (MovieScene) {
835
- UObject *DefaultObject = ResolvedClass->GetDefaultObject();
836
- if (DefaultObject) {
837
- FGuid BindingGuid = MovieScene->AddSpawnable(ClassName, *DefaultObject);
838
- if (MovieScene->FindSpawnable(BindingGuid)) {
839
- MovieScene->Modify();
840
- TSharedPtr<FJsonObject> SpawnableResp = MakeShared<FJsonObject>();
841
- SpawnableResp->SetBoolField(TEXT("success"), true);
842
- SpawnableResp->SetStringField(TEXT("className"), ClassName);
843
- SpawnableResp->SetStringField(TEXT("bindingGuid"),
844
- BindingGuid.ToString());
845
- SendAutomationResponse(Socket, RequestId, true,
846
- TEXT("Spawnable added to sequence"),
847
- SpawnableResp, FString());
848
- return true;
849
- }
850
- }
851
- }
852
- SendAutomationResponse(Socket, RequestId, false,
853
- TEXT("Failed to create spawnable binding"), nullptr,
854
- TEXT("SPAWNABLE_CREATION_FAILED"));
855
- return true;
856
- }
857
- SendAutomationResponse(Socket, RequestId, false,
858
- TEXT("Sequence object is not a LevelSequence"),
859
- nullptr, TEXT("INVALID_SEQUENCE_TYPE"));
860
- return true;
861
- #else
862
- SendAutomationResponse(
863
- Socket, RequestId, false,
864
- TEXT("sequence_add_spawnable_from_class requires editor build."), nullptr,
865
- TEXT("NOT_IMPLEMENTED"));
866
- return true;
867
- #endif
868
- }
869
-
870
- bool UMcpAutomationBridgeSubsystem::HandleSequenceRemoveActors(
871
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
872
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
873
- TSharedPtr<FJsonObject> LocalPayload =
874
- Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
875
- const TArray<TSharedPtr<FJsonValue>> *Arr = nullptr;
876
- LocalPayload->TryGetArrayField(TEXT("actorNames"), Arr);
877
- if (!Arr || Arr->Num() == 0) {
878
- SendAutomationResponse(Socket, RequestId, false,
879
- TEXT("actorNames required"), nullptr,
880
- TEXT("INVALID_ARGUMENT"));
881
- return true;
882
- }
883
- FString SeqPath = ResolveSequencePath(LocalPayload);
884
- if (SeqPath.IsEmpty()) {
885
- SendAutomationResponse(
886
- Socket, RequestId, false,
887
- TEXT("sequence_remove_actors requires a sequence path"), nullptr,
888
- TEXT("INVALID_SEQUENCE"));
889
- return true;
890
- }
891
-
892
- #if WITH_EDITOR
893
- UObject *SeqObj = UEditorAssetLibrary::LoadAsset(SeqPath);
894
- if (!SeqObj) {
895
- SendAutomationResponse(Socket, RequestId, false, TEXT("Sequence not found"),
896
- nullptr, TEXT("INVALID_SEQUENCE"));
897
- return true;
898
- }
899
- if (!GEditor) {
900
- SendAutomationResponse(Socket, RequestId, false,
901
- TEXT("Editor not available"), nullptr,
902
- TEXT("EDITOR_NOT_AVAILABLE"));
903
- return true;
904
- }
905
-
906
- #if MCP_HAS_EDITOR_ACTOR_SUBSYSTEM
907
- if (UEditorActorSubsystem *ActorSS =
908
- GEditor->GetEditorSubsystem<UEditorActorSubsystem>()) {
909
- TArray<TSharedPtr<FJsonValue>> Removed;
910
- int32 RemovedCount = 0;
911
- for (const TSharedPtr<FJsonValue> &V : *Arr) {
912
- if (!V.IsValid() || V->Type != EJson::String)
913
- continue;
914
- FString Name = V->AsString();
915
- TSharedPtr<FJsonObject> Item = MakeShared<FJsonObject>();
916
- Item->SetStringField(TEXT("name"), Name);
917
-
918
- if (ULevelSequence *LevelSeq = Cast<ULevelSequence>(SeqObj)) {
919
- UMovieScene *MovieScene = LevelSeq->GetMovieScene();
920
- if (MovieScene) {
921
- bool bRemoved = false;
922
- for (const FMovieSceneBinding &Binding :
923
- const_cast<const UMovieScene *>(MovieScene)->GetBindings()) {
924
- FString BindingName;
925
- if (FMovieScenePossessable *Possessable =
926
- MovieScene->FindPossessable(Binding.GetObjectGuid())) {
927
- BindingName = Possessable->GetName();
928
- } else if (FMovieSceneSpawnable *Spawnable =
929
- MovieScene->FindSpawnable(Binding.GetObjectGuid())) {
930
- BindingName = Spawnable->GetName();
931
- }
932
-
933
- if (BindingName.Equals(Name, ESearchCase::IgnoreCase)) {
934
- MovieScene->RemovePossessable(Binding.GetObjectGuid());
935
- MovieScene->Modify();
936
- bRemoved = true;
937
- break;
938
- }
939
- }
940
- if (bRemoved) {
941
- Item->SetBoolField(TEXT("success"), true);
942
- Item->SetStringField(TEXT("status"), TEXT("Actor removed"));
943
- RemovedCount++;
944
- } else {
945
- Item->SetBoolField(TEXT("success"), false);
946
- Item->SetStringField(TEXT("error"),
947
- TEXT("Actor not found in sequence bindings"));
948
- }
949
- } else {
950
- Item->SetBoolField(TEXT("success"), false);
951
- Item->SetStringField(TEXT("error"),
952
- TEXT("Sequence has no MovieScene"));
953
- }
954
- } else {
955
- Item->SetBoolField(TEXT("success"), false);
956
- Item->SetStringField(TEXT("error"),
957
- TEXT("Sequence object is not a LevelSequence"));
958
- }
959
- Removed.Add(MakeShared<FJsonValueObject>(Item));
960
- }
961
- TSharedPtr<FJsonObject> Out = MakeShared<FJsonObject>();
962
- Out->SetArrayField(TEXT("removedActors"), Removed);
963
- Out->SetNumberField(TEXT("bindingsProcessed"), RemovedCount);
964
- SendAutomationResponse(Socket, RequestId, true,
965
- TEXT("Actors processed for removal"), Out,
966
- FString());
967
- return true;
968
- }
969
- SendAutomationResponse(Socket, RequestId, false,
970
- TEXT("EditorActorSubsystem not available"), nullptr,
971
- TEXT("EDITOR_ACTOR_SUBSYSTEM_MISSING"));
972
- return true;
973
- #else
974
- SendAutomationResponse(Socket, RequestId, false,
975
- TEXT("UEditorActorSubsystem not available"), nullptr,
976
- TEXT("NOT_AVAILABLE"));
977
- return true;
978
- #endif
979
- #else
980
- SendAutomationResponse(Socket, RequestId, false,
981
- TEXT("sequence_remove_actors requires editor build."),
982
- nullptr, TEXT("NOT_IMPLEMENTED"));
983
- return true;
984
- #endif
985
- }
986
-
987
- bool UMcpAutomationBridgeSubsystem::HandleSequenceGetBindings(
988
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
989
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
990
- TSharedPtr<FJsonObject> LocalPayload =
991
- Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
992
- FString SeqPath = ResolveSequencePath(LocalPayload);
993
- if (SeqPath.IsEmpty()) {
994
- SendAutomationResponse(
995
- Socket, RequestId, false,
996
- TEXT("sequence_get_bindings requires a sequence path"), nullptr,
997
- TEXT("INVALID_SEQUENCE"));
998
- return true;
999
- }
1000
- #if WITH_EDITOR
1001
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
1002
- UObject *SeqObj = UEditorAssetLibrary::LoadAsset(SeqPath);
1003
- if (!SeqObj) {
1004
- SendAutomationResponse(Socket, RequestId, false, TEXT("Sequence not found"),
1005
- nullptr, TEXT("INVALID_SEQUENCE"));
1006
- return true;
1007
- }
1008
-
1009
- if (ULevelSequence *LevelSeq = Cast<ULevelSequence>(SeqObj)) {
1010
- if (UMovieScene *MovieScene = LevelSeq->GetMovieScene()) {
1011
- TArray<TSharedPtr<FJsonValue>> BindingsArray;
1012
- for (const FMovieSceneBinding &B :
1013
- const_cast<const UMovieScene *>(MovieScene)->GetBindings()) {
1014
- TSharedPtr<FJsonObject> Bobj = MakeShared<FJsonObject>();
1015
- Bobj->SetStringField(TEXT("id"), B.GetObjectGuid().ToString());
1016
-
1017
- FString BindingName;
1018
- if (FMovieScenePossessable *Possessable =
1019
- MovieScene->FindPossessable(B.GetObjectGuid())) {
1020
- BindingName = Possessable->GetName();
1021
- } else if (FMovieSceneSpawnable *Spawnable =
1022
- MovieScene->FindSpawnable(B.GetObjectGuid())) {
1023
- BindingName = Spawnable->GetName();
1024
- }
1025
-
1026
- Bobj->SetStringField(TEXT("name"), BindingName);
1027
- BindingsArray.Add(MakeShared<FJsonValueObject>(Bobj));
1028
- }
1029
- Resp->SetArrayField(TEXT("bindings"), BindingsArray);
1030
- SendAutomationResponse(Socket, RequestId, true, TEXT("bindings listed"),
1031
- Resp, FString());
1032
- return true;
1033
- }
1034
- }
1035
- Resp->SetArrayField(TEXT("bindings"), TArray<TSharedPtr<FJsonValue>>());
1036
- SendAutomationResponse(Socket, RequestId, true,
1037
- TEXT("bindings listed (empty)"), Resp, FString());
1038
- return true;
1039
- #else
1040
- SendAutomationResponse(Socket, RequestId, false,
1041
- TEXT("sequence_get_bindings requires editor build."),
1042
- nullptr, TEXT("NOT_IMPLEMENTED"));
1043
- return true;
1044
- #endif
1045
- }
1046
-
1047
- bool UMcpAutomationBridgeSubsystem::HandleSequenceGetProperties(
1048
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
1049
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
1050
- TSharedPtr<FJsonObject> LocalPayload =
1051
- Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
1052
- FString SeqPath = ResolveSequencePath(LocalPayload);
1053
- if (SeqPath.IsEmpty()) {
1054
- SendAutomationResponse(
1055
- Socket, RequestId, false,
1056
- TEXT("sequence_get_properties requires a sequence path"), nullptr,
1057
- TEXT("INVALID_SEQUENCE"));
1058
- return true;
1059
- }
1060
- #if WITH_EDITOR
1061
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
1062
- UObject *SeqObj = UEditorAssetLibrary::LoadAsset(SeqPath);
1063
- if (!SeqObj) {
1064
- SendAutomationResponse(Socket, RequestId, false, TEXT("Sequence not found"),
1065
- nullptr, TEXT("INVALID_SEQUENCE"));
1066
- return true;
1067
- }
1068
-
1069
- if (ULevelSequence *LevelSeq = Cast<ULevelSequence>(SeqObj)) {
1070
- if (UMovieScene *MovieScene = LevelSeq->GetMovieScene()) {
1071
- FFrameRate FR = MovieScene->GetDisplayRate();
1072
- TSharedPtr<FJsonObject> FrameRateObj = MakeShared<FJsonObject>();
1073
- FrameRateObj->SetNumberField(TEXT("numerator"), FR.Numerator);
1074
- FrameRateObj->SetNumberField(TEXT("denominator"), FR.Denominator);
1075
- Resp->SetObjectField(TEXT("frameRate"), FrameRateObj);
1076
- TRange<FFrameNumber> Range = MovieScene->GetPlaybackRange();
1077
- const double Start =
1078
- static_cast<double>(Range.GetLowerBoundValue().Value);
1079
- const double End = static_cast<double>(Range.GetUpperBoundValue().Value);
1080
- Resp->SetNumberField(TEXT("playbackStart"), Start);
1081
- Resp->SetNumberField(TEXT("playbackEnd"), End);
1082
- Resp->SetNumberField(TEXT("duration"), End - Start);
1083
- SendAutomationResponse(Socket, RequestId, true,
1084
- TEXT("properties retrieved"), Resp, FString());
1085
- return true;
1086
- }
1087
- }
1088
- Resp->SetObjectField(TEXT("frameRate"), MakeShared<FJsonObject>());
1089
- Resp->SetNumberField(TEXT("playbackStart"), 0.0);
1090
- Resp->SetNumberField(TEXT("playbackEnd"), 0.0);
1091
- Resp->SetNumberField(TEXT("duration"), 0.0);
1092
- SendAutomationResponse(Socket, RequestId, true, TEXT("properties retrieved"),
1093
- Resp, FString());
1094
- return true;
1095
- #else
1096
- SendAutomationResponse(Socket, RequestId, false,
1097
- TEXT("sequence_get_properties requires editor build."),
1098
- nullptr, TEXT("NOT_IMPLEMENTED"));
1099
- return true;
1100
- #endif
1101
- }
1102
-
1103
- bool UMcpAutomationBridgeSubsystem::HandleSequenceSetPlaybackSpeed(
1104
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
1105
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
1106
- TSharedPtr<FJsonObject> LocalPayload =
1107
- Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
1108
- double Speed = 1.0;
1109
- LocalPayload->TryGetNumberField(TEXT("speed"), Speed);
1110
- if (Speed <= 0.0) {
1111
- SendAutomationResponse(Socket, RequestId, false,
1112
- TEXT("Invalid speed (must be > 0)"), nullptr,
1113
- TEXT("INVALID_ARGUMENT"));
1114
- return true;
1115
- }
1116
- FString SeqPath = ResolveSequencePath(LocalPayload);
1117
- if (SeqPath.IsEmpty()) {
1118
- SendAutomationResponse(
1119
- Socket, RequestId, false,
1120
- TEXT("sequence_set_playback_speed requires a sequence path"), nullptr,
1121
- TEXT("INVALID_SEQUENCE"));
1122
- return true;
1123
- }
1124
-
1125
- #if WITH_EDITOR
1126
- TWeakObjectPtr<UMcpAutomationBridgeSubsystem> WeakSubsystem(this);
1127
- FString RequestIdArg = RequestId; // Capture
1128
-
1129
- // Execute on Game Thread
1130
- UMcpAutomationBridgeSubsystem *Subsystem = this;
1131
- UObject *SeqObj = UEditorAssetLibrary::LoadAsset(SeqPath);
1132
- if (!SeqObj) {
1133
- Subsystem->SendAutomationResponse(Socket, RequestIdArg, false,
1134
- TEXT("Sequence not found"), nullptr,
1135
- TEXT("INVALID_SEQUENCE"));
1136
- return true;
1137
- }
1138
-
1139
- if (GEditor) {
1140
- if (UAssetEditorSubsystem *AssetEditorSS =
1141
- GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()) {
1142
- IAssetEditorInstance *Editor =
1143
- AssetEditorSS->FindEditorForAsset(SeqObj, false);
1144
- if (Editor && Editor->GetEditorName() == FName("LevelSequenceEditor")) {
1145
- // We assume it implements ILevelSequenceEditorToolkit if the name
1146
- // matches
1147
- ILevelSequenceEditorToolkit *LSEditor =
1148
- static_cast<ILevelSequenceEditorToolkit *>(Editor);
1149
- if (LSEditor && LSEditor->GetSequencer().IsValid()) {
1150
- UE_LOG(LogMcpAutomationBridgeSubsystem, Display,
1151
- TEXT("HandleSequenceSetPlaybackSpeed: Setting speed to %.2f"),
1152
- Speed);
1153
- LSEditor->GetSequencer()->SetPlaybackSpeed(static_cast<float>(Speed));
1154
- Subsystem->SendAutomationResponse(
1155
- Socket, RequestIdArg, true,
1156
- FString::Printf(TEXT("Playback speed set to %.2f"), Speed),
1157
- nullptr);
1158
- return true;
1159
- } else {
1160
- UE_LOG(LogMcpAutomationBridgeSubsystem, Error,
1161
- TEXT("HandleSequenceSetPlaybackSpeed: Sequencer invalid for "
1162
- "asset %s"),
1163
- *SeqObj->GetName());
1164
- }
1165
- }
1166
- }
1167
- }
1168
-
1169
- Subsystem->SendAutomationResponse(
1170
- Socket, RequestIdArg, false,
1171
- TEXT("Sequence editor not open or interface unavailable"), nullptr,
1172
- TEXT("EDITOR_NOT_OPEN"));
1173
- return true;
1174
- return true;
1175
- #else
1176
- SendAutomationResponse(
1177
- Socket, RequestId, false,
1178
- TEXT("sequence_set_playback_speed requires editor build."), nullptr,
1179
- TEXT("NOT_AVAILABLE"));
1180
- return true;
1181
- #endif
1182
- }
1183
-
1184
- bool UMcpAutomationBridgeSubsystem::HandleSequencePause(
1185
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
1186
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
1187
- TSharedPtr<FJsonObject> LocalPayload =
1188
- Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
1189
- FString SeqPath = ResolveSequencePath(LocalPayload);
1190
- if (SeqPath.IsEmpty()) {
1191
- SendAutomationResponse(Socket, RequestId, false,
1192
- TEXT("sequence_pause requires a sequence path"),
1193
- nullptr, TEXT("INVALID_SEQUENCE"));
1194
- return true;
1195
- }
1196
- #if WITH_EDITOR
1197
- TWeakObjectPtr<UMcpAutomationBridgeSubsystem> WeakSubsystem(this);
1198
- FString RequestIdArg = RequestId;
1199
- UMcpAutomationBridgeSubsystem *Subsystem = this;
1200
-
1201
- ULevelSequence *LevelSeq =
1202
- Cast<ULevelSequence>(UEditorAssetLibrary::LoadAsset(SeqPath));
1203
- if (LevelSeq) {
1204
- // Ensure it's the active one
1205
- if (ULevelSequenceEditorBlueprintLibrary::GetCurrentLevelSequence() ==
1206
- LevelSeq) {
1207
- ULevelSequenceEditorBlueprintLibrary::Pause();
1208
- Subsystem->SendAutomationResponse(Socket, RequestIdArg, true,
1209
- TEXT("Sequence paused"), nullptr);
1210
- return true;
1211
- }
1212
- }
1213
- Subsystem->SendAutomationResponse(
1214
- Socket, RequestIdArg, false,
1215
- TEXT("Sequence not currently open in editor"), nullptr,
1216
- TEXT("EXECUTION_ERROR"));
1217
- return true;
1218
- return true;
1219
- #else
1220
- SendAutomationResponse(Socket, RequestId, false,
1221
- TEXT("sequence_pause requires editor build."), nullptr,
1222
- TEXT("NOT_AVAILABLE"));
1223
- return true;
1224
- #endif
1225
- }
1226
-
1227
- bool UMcpAutomationBridgeSubsystem::HandleSequenceStop(
1228
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
1229
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
1230
- TSharedPtr<FJsonObject> LocalPayload =
1231
- Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
1232
- FString SeqPath = ResolveSequencePath(LocalPayload);
1233
- if (SeqPath.IsEmpty()) {
1234
- SendAutomationResponse(Socket, RequestId, false,
1235
- TEXT("sequence_stop requires a sequence path"),
1236
- nullptr, TEXT("INVALID_SEQUENCE"));
1237
- return true;
1238
- }
1239
- #if WITH_EDITOR
1240
- TWeakObjectPtr<UMcpAutomationBridgeSubsystem> WeakSubsystem(this);
1241
- FString RequestIdArg = RequestId;
1242
- UMcpAutomationBridgeSubsystem *Subsystem = this;
1243
-
1244
- ULevelSequence *LevelSeq =
1245
- Cast<ULevelSequence>(UEditorAssetLibrary::LoadAsset(SeqPath));
1246
- if (LevelSeq) {
1247
- if (ULevelSequenceEditorBlueprintLibrary::GetCurrentLevelSequence() ==
1248
- LevelSeq) {
1249
- ULevelSequenceEditorBlueprintLibrary::Pause();
1250
-
1251
- FMovieSceneSequencePlaybackParams PlaybackParams;
1252
- PlaybackParams.Frame = FFrameTime(0);
1253
- PlaybackParams.UpdateMethod = EUpdatePositionMethod::Scrub;
1254
- ULevelSequenceEditorBlueprintLibrary::SetGlobalPosition(PlaybackParams);
1255
-
1256
- Subsystem->SendAutomationResponse(
1257
- Socket, RequestIdArg, true, TEXT("Sequence stopped (reset to start)"),
1258
- nullptr);
1259
- return true;
1260
- }
1261
- }
1262
- Subsystem->SendAutomationResponse(
1263
- Socket, RequestIdArg, false,
1264
- TEXT("Sequence not currently open in editor"), nullptr,
1265
- TEXT("EXECUTION_ERROR"));
1266
- return true;
1267
- return true;
1268
- #else
1269
- SendAutomationResponse(Socket, RequestId, false,
1270
- TEXT("sequence_stop requires editor build."), nullptr,
1271
- TEXT("NOT_AVAILABLE"));
1272
- return true;
1273
- #endif
1274
- }
1275
-
1276
- bool UMcpAutomationBridgeSubsystem::HandleSequenceList(
1277
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
1278
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
1279
- #if WITH_EDITOR
1280
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
1281
- TArray<TSharedPtr<FJsonValue>> SequencesArray;
1282
-
1283
- // Use Asset Registry to find all LevelSequence assets, not string matching
1284
- FAssetRegistryModule &AssetRegistryModule =
1285
- FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
1286
- IAssetRegistry &AssetRegistry = AssetRegistryModule.Get();
1287
-
1288
- FARFilter Filter;
1289
- Filter.ClassPaths.Add(ULevelSequence::StaticClass()->GetClassPathName());
1290
- Filter.bRecursiveClasses = true;
1291
- Filter.bRecursivePaths = true;
1292
- Filter.PackagePaths.Add(FName("/Game"));
1293
-
1294
- TArray<FAssetData> AssetList;
1295
- AssetRegistry.GetAssets(Filter, AssetList);
1296
-
1297
- for (const FAssetData &Asset : AssetList) {
1298
- TSharedPtr<FJsonObject> SeqObj = MakeShared<FJsonObject>();
1299
- SeqObj->SetStringField(TEXT("path"), Asset.GetObjectPathString());
1300
- SeqObj->SetStringField(TEXT("name"), Asset.AssetName.ToString());
1301
- SequencesArray.Add(MakeShared<FJsonValueObject>(SeqObj));
1302
- }
1303
-
1304
- Resp->SetArrayField(TEXT("sequences"), SequencesArray);
1305
- Resp->SetNumberField(TEXT("count"), SequencesArray.Num());
1306
- SendAutomationResponse(
1307
- Socket, RequestId, true,
1308
- FString::Printf(TEXT("Found %d sequences"), SequencesArray.Num()), Resp,
1309
- FString());
1310
- return true;
1311
- #else
1312
- SendAutomationResponse(Socket, RequestId, false,
1313
- TEXT("sequence_list requires editor build."), nullptr,
1314
- TEXT("NOT_AVAILABLE"));
1315
- return true;
1316
- #endif
1317
- }
1318
-
1319
- bool UMcpAutomationBridgeSubsystem::HandleSequenceDuplicate(
1320
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
1321
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
1322
- TSharedPtr<FJsonObject> LocalPayload =
1323
- Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
1324
- FString SourcePath;
1325
- LocalPayload->TryGetStringField(TEXT("path"), SourcePath);
1326
- FString DestinationPath;
1327
- LocalPayload->TryGetStringField(TEXT("destinationPath"), DestinationPath);
1328
- if (SourcePath.IsEmpty() || DestinationPath.IsEmpty()) {
1329
- SendAutomationResponse(
1330
- Socket, RequestId, false,
1331
- TEXT("sequence_duplicate requires path and destinationPath"), nullptr,
1332
- TEXT("INVALID_ARGUMENT"));
1333
- return true;
1334
- }
1335
-
1336
- // Auto-resolve relative destination path (if just a name is provided)
1337
- if (!DestinationPath.IsEmpty() && !DestinationPath.StartsWith(TEXT("/"))) {
1338
- FString ParentPath = FPaths::GetPath(SourcePath);
1339
- DestinationPath =
1340
- FString::Printf(TEXT("%s/%s"), *ParentPath, *DestinationPath);
1341
- }
1342
-
1343
- #if WITH_EDITOR
1344
- UObject *SourceSeq = UEditorAssetLibrary::LoadAsset(SourcePath);
1345
- if (!SourceSeq) {
1346
- SendAutomationResponse(
1347
- Socket, RequestId, false,
1348
- FString::Printf(TEXT("Source sequence not found: %s"), *SourcePath),
1349
- nullptr, TEXT("INVALID_SEQUENCE"));
1350
- return true;
1351
- }
1352
- UObject *DuplicatedSeq =
1353
- UEditorAssetLibrary::DuplicateAsset(SourcePath, DestinationPath);
1354
- if (DuplicatedSeq) {
1355
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
1356
- Resp->SetStringField(TEXT("sourcePath"), SourcePath);
1357
- Resp->SetStringField(TEXT("destinationPath"), DestinationPath);
1358
- Resp->SetStringField(TEXT("duplicatedPath"), DuplicatedSeq->GetPathName());
1359
- SendAutomationResponse(Socket, RequestId, true,
1360
- TEXT("Sequence duplicated successfully"), Resp,
1361
- FString());
1362
- return true;
1363
- }
1364
- SendAutomationResponse(Socket, RequestId, false,
1365
- TEXT("Failed to duplicate sequence"), nullptr,
1366
- TEXT("OPERATION_FAILED"));
1367
- return true;
1368
- #else
1369
- SendAutomationResponse(Socket, RequestId, false,
1370
- TEXT("sequence_duplicate requires editor build."),
1371
- nullptr, TEXT("NOT_AVAILABLE"));
1372
- return true;
1373
- #endif
1374
- }
1375
-
1376
- bool UMcpAutomationBridgeSubsystem::HandleSequenceRename(
1377
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
1378
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
1379
- TSharedPtr<FJsonObject> LocalPayload =
1380
- Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
1381
- FString Path;
1382
- LocalPayload->TryGetStringField(TEXT("path"), Path);
1383
- FString NewName;
1384
- LocalPayload->TryGetStringField(TEXT("newName"), NewName);
1385
- if (Path.IsEmpty() || NewName.IsEmpty()) {
1386
- SendAutomationResponse(Socket, RequestId, false,
1387
- TEXT("sequence_rename requires path and newName"),
1388
- nullptr, TEXT("INVALID_ARGUMENT"));
1389
- return true;
1390
- }
1391
-
1392
- // Auto-resolve relative new name to full path
1393
- if (!NewName.IsEmpty() && !NewName.StartsWith(TEXT("/"))) {
1394
- FString ParentPath = FPaths::GetPath(Path);
1395
- NewName = FString::Printf(TEXT("%s/%s"), *ParentPath, *NewName);
1396
- }
1397
-
1398
- #if WITH_EDITOR
1399
- if (UEditorAssetLibrary::RenameAsset(Path, NewName)) {
1400
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
1401
- Resp->SetStringField(TEXT("oldPath"), Path);
1402
- Resp->SetStringField(TEXT("newName"), NewName);
1403
- SendAutomationResponse(Socket, RequestId, true,
1404
- TEXT("Sequence renamed successfully"), Resp,
1405
- FString());
1406
- return true;
1407
- }
1408
- SendAutomationResponse(Socket, RequestId, false,
1409
- TEXT("Failed to rename sequence"), nullptr,
1410
- TEXT("OPERATION_FAILED"));
1411
- return true;
1412
- #else
1413
- SendAutomationResponse(Socket, RequestId, false,
1414
- TEXT("sequence_rename requires editor build."),
1415
- nullptr, TEXT("NOT_AVAILABLE"));
1416
- return true;
1417
- #endif
1418
- }
1419
-
1420
- bool UMcpAutomationBridgeSubsystem::HandleSequenceDelete(
1421
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
1422
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
1423
- TSharedPtr<FJsonObject> LocalPayload =
1424
- Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
1425
- FString Path;
1426
- LocalPayload->TryGetStringField(TEXT("path"), Path);
1427
- if (Path.IsEmpty()) {
1428
- SendAutomationResponse(Socket, RequestId, false,
1429
- TEXT("sequence_delete requires path"), nullptr,
1430
- TEXT("INVALID_ARGUMENT"));
1431
- return true;
1432
- }
1433
- #if WITH_EDITOR
1434
- if (!UEditorAssetLibrary::DoesAssetExist(Path)) {
1435
- // Idempotent success - if it's already gone, good.
1436
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
1437
- Resp->SetStringField(TEXT("deletedPath"), Path);
1438
- SendAutomationResponse(Socket, RequestId, true,
1439
- TEXT("Sequence deleted (or did not exist)"), Resp,
1440
- FString());
1441
- return true;
1442
- }
1443
-
1444
- if (UEditorAssetLibrary::DeleteAsset(Path)) {
1445
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
1446
- Resp->SetStringField(TEXT("deletedPath"), Path);
1447
- SendAutomationResponse(Socket, RequestId, true,
1448
- TEXT("Sequence deleted successfully"), Resp,
1449
- FString());
1450
- return true;
1451
- }
1452
- SendAutomationResponse(Socket, RequestId, false,
1453
- TEXT("Failed to delete sequence"), nullptr,
1454
- TEXT("OPERATION_FAILED"));
1455
- return true;
1456
- #else
1457
- SendAutomationResponse(Socket, RequestId, false,
1458
- TEXT("sequence_delete requires editor build."),
1459
- nullptr, TEXT("NOT_AVAILABLE"));
1460
- return true;
1461
- #endif
1462
- }
1463
-
1464
- bool UMcpAutomationBridgeSubsystem::HandleSequenceGetMetadata(
1465
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
1466
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
1467
- TSharedPtr<FJsonObject> LocalPayload =
1468
- Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
1469
- FString SeqPath = ResolveSequencePath(LocalPayload);
1470
- if (SeqPath.IsEmpty()) {
1471
- SendAutomationResponse(
1472
- Socket, RequestId, false,
1473
- TEXT("sequence_get_metadata requires a sequence path"), nullptr,
1474
- TEXT("INVALID_SEQUENCE"));
1475
- return true;
1476
- }
1477
- #if WITH_EDITOR
1478
- UObject *SeqObj = UEditorAssetLibrary::LoadAsset(SeqPath);
1479
- if (!SeqObj) {
1480
- SendAutomationResponse(Socket, RequestId, false, TEXT("Sequence not found"),
1481
- nullptr, TEXT("INVALID_SEQUENCE"));
1482
- return true;
1483
- }
1484
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
1485
- Resp->SetStringField(TEXT("path"), SeqPath);
1486
- Resp->SetStringField(TEXT("name"), SeqObj->GetName());
1487
- Resp->SetStringField(TEXT("class"), SeqObj->GetClass()->GetName());
1488
- SendAutomationResponse(Socket, RequestId, true,
1489
- TEXT("Sequence metadata retrieved"), Resp, FString());
1490
- return true;
1491
- #else
1492
- SendAutomationResponse(Socket, RequestId, false,
1493
- TEXT("sequence_get_metadata requires editor build."),
1494
- nullptr, TEXT("NOT_AVAILABLE"));
1495
- return true;
1496
- #endif
1497
- }
1498
-
1499
- bool UMcpAutomationBridgeSubsystem::HandleSequenceAddKeyframe(
1500
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
1501
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
1502
- TSharedPtr<FJsonObject> LocalPayload =
1503
- Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
1504
- FString SeqPath = ResolveSequencePath(LocalPayload);
1505
- if (SeqPath.IsEmpty()) {
1506
- SendAutomationResponse(
1507
- Socket, RequestId, false,
1508
- TEXT("sequence_add_keyframe requires a sequence path"), nullptr,
1509
- TEXT("INVALID_SEQUENCE"));
1510
- return true;
1511
- }
1512
-
1513
- FString BindingIdStr;
1514
- LocalPayload->TryGetStringField(TEXT("bindingId"), BindingIdStr);
1515
- FString ActorName;
1516
- LocalPayload->TryGetStringField(TEXT("actorName"), ActorName);
1517
- FString PropertyName;
1518
- LocalPayload->TryGetStringField(TEXT("property"), PropertyName);
1519
-
1520
- if (BindingIdStr.IsEmpty() && ActorName.IsEmpty()) {
1521
- SendAutomationResponse(
1522
- Socket, RequestId, false,
1523
- TEXT("Either bindingId or actorName must be provided. bindingId is the "
1524
- "GUID from add_actor/get_bindings. actorName is the label of an "
1525
- "actor already bound to the sequence. Example: {\"actorName\": "
1526
- "\"MySphere\", \"property\": \"Location\", \"frame\": 0, "
1527
- "\"value\": {\"x\":0,\"y\":0,\"z\":0}}"),
1528
- nullptr, TEXT("INVALID_ARGUMENT"));
1529
- return true;
1530
- }
1531
-
1532
- double Frame = 0.0;
1533
- if (!LocalPayload->TryGetNumberField(TEXT("frame"), Frame)) {
1534
- SendAutomationResponse(Socket, RequestId, false,
1535
- TEXT("frame number is required. Example: "
1536
- "{\"frame\": 30} for keyframe at frame 30"),
1537
- nullptr, TEXT("INVALID_ARGUMENT"));
1538
- return true;
1539
- }
1540
-
1541
- #if WITH_EDITOR
1542
- UObject *SeqObj = UEditorAssetLibrary::LoadAsset(SeqPath);
1543
- if (!SeqObj) {
1544
- SendAutomationResponse(Socket, RequestId, false, TEXT("Sequence not found"),
1545
- nullptr, TEXT("INVALID_SEQUENCE"));
1546
- return true;
1547
- }
1548
-
1549
- if (ULevelSequence *LevelSeq = Cast<ULevelSequence>(SeqObj)) {
1550
- UMovieScene *MovieScene = LevelSeq->GetMovieScene();
1551
- if (MovieScene) {
1552
- FGuid BindingGuid;
1553
- if (!BindingIdStr.IsEmpty()) {
1554
- FGuid::Parse(BindingIdStr, BindingGuid);
1555
- } else if (!ActorName.IsEmpty()) {
1556
- for (const FMovieSceneBinding &Binding :
1557
- const_cast<const UMovieScene *>(MovieScene)->GetBindings()) {
1558
- FString BindingName;
1559
- if (FMovieScenePossessable *Possessable =
1560
- MovieScene->FindPossessable(Binding.GetObjectGuid())) {
1561
- BindingName = Possessable->GetName();
1562
- } else if (FMovieSceneSpawnable *Spawnable =
1563
- MovieScene->FindSpawnable(Binding.GetObjectGuid())) {
1564
- BindingName = Spawnable->GetName();
1565
- }
1566
-
1567
- if (BindingName.Equals(ActorName, ESearchCase::IgnoreCase)) {
1568
- BindingGuid = Binding.GetObjectGuid();
1569
- break;
1570
- }
1571
- }
1572
- }
1573
-
1574
- if (!BindingGuid.IsValid()) {
1575
- FString Target = !BindingIdStr.IsEmpty() ? BindingIdStr : ActorName;
1576
- SendAutomationResponse(
1577
- Socket, RequestId, false,
1578
- FString::Printf(TEXT("Binding not found for '%s'. Ensure actor is "
1579
- "bound to sequence."),
1580
- *Target),
1581
- nullptr, TEXT("BINDING_NOT_FOUND"));
1582
- return true;
1583
- }
1584
-
1585
- FMovieSceneBinding *Binding = MovieScene->FindBinding(BindingGuid);
1586
- if (!Binding) {
1587
- SendAutomationResponse(Socket, RequestId, false,
1588
- TEXT("Binding object not found in sequence"),
1589
- nullptr, TEXT("BINDING_NOT_FOUND"));
1590
- return true;
1591
- }
1592
-
1593
- if (PropertyName.Equals(TEXT("Transform"), ESearchCase::IgnoreCase)) {
1594
- UMovieScene3DTransformTrack *Track =
1595
- MovieScene->FindTrack<UMovieScene3DTransformTrack>(
1596
- BindingGuid, FName("Transform"));
1597
- if (!Track) {
1598
- Track =
1599
- MovieScene->AddTrack<UMovieScene3DTransformTrack>(BindingGuid);
1600
- }
1601
-
1602
- if (Track) {
1603
- bool bSectionAdded = false;
1604
- UMovieScene3DTransformSection *Section =
1605
- Cast<UMovieScene3DTransformSection>(
1606
- Track->FindOrAddSection(0, bSectionAdded));
1607
- if (Section) {
1608
- FFrameRate TickResolution = MovieScene->GetTickResolution();
1609
- FFrameRate DisplayRate = MovieScene->GetDisplayRate();
1610
- FFrameNumber FrameNum = FFrameNumber(static_cast<int32>(Frame));
1611
- FFrameNumber TickFrame =
1612
- FFrameRate::TransformTime(FFrameTime(FrameNum), DisplayRate,
1613
- TickResolution)
1614
- .FloorToFrame();
1615
-
1616
- bool bModified = false;
1617
- const TSharedPtr<FJsonObject> *ValueObj = nullptr;
1618
-
1619
- FMovieSceneChannelProxy &Proxy = Section->GetChannelProxy();
1620
- TArrayView<FMovieSceneDoubleChannel *> Channels =
1621
- Proxy.GetChannels<FMovieSceneDoubleChannel>();
1622
-
1623
- if (LocalPayload->TryGetObjectField(TEXT("value"), ValueObj) &&
1624
- ValueObj && Channels.Num() >= 9) {
1625
- const TSharedPtr<FJsonObject> *LocObj = nullptr;
1626
- if ((*ValueObj)->TryGetObjectField(TEXT("location"), LocObj)) {
1627
- double X, Y, Z;
1628
- if ((*LocObj)->TryGetNumberField(TEXT("x"), X)) {
1629
- Channels[0]->GetData().AddKey(TickFrame,
1630
- FMovieSceneDoubleValue(X));
1631
- bModified = true;
1632
- }
1633
- if ((*LocObj)->TryGetNumberField(TEXT("y"), Y)) {
1634
- Channels[1]->GetData().AddKey(TickFrame,
1635
- FMovieSceneDoubleValue(Y));
1636
- bModified = true;
1637
- }
1638
- if ((*LocObj)->TryGetNumberField(TEXT("z"), Z)) {
1639
- Channels[2]->GetData().AddKey(TickFrame,
1640
- FMovieSceneDoubleValue(Z));
1641
- bModified = true;
1642
- }
1643
- }
1644
-
1645
- const TSharedPtr<FJsonObject> *RotObj = nullptr;
1646
- if ((*ValueObj)->TryGetObjectField(TEXT("rotation"), RotObj)) {
1647
- double P, Yaw, R;
1648
- // 0=Roll(X), 1=Pitch(Y), 2=Yaw(Z) in Transform Track channels
1649
- // usually. Channels 3, 4, 5.
1650
- if ((*RotObj)->TryGetNumberField(TEXT("roll"), R)) {
1651
- Channels[3]->GetData().AddKey(TickFrame,
1652
- FMovieSceneDoubleValue(R));
1653
- bModified = true;
1654
- }
1655
- if ((*RotObj)->TryGetNumberField(TEXT("pitch"), P)) {
1656
- Channels[4]->GetData().AddKey(TickFrame,
1657
- FMovieSceneDoubleValue(P));
1658
- bModified = true;
1659
- }
1660
- if ((*RotObj)->TryGetNumberField(TEXT("yaw"), Yaw)) {
1661
- Channels[5]->GetData().AddKey(TickFrame,
1662
- FMovieSceneDoubleValue(Yaw));
1663
- bModified = true;
1664
- }
1665
- }
1666
-
1667
- const TSharedPtr<FJsonObject> *ScaleObj = nullptr;
1668
- if ((*ValueObj)->TryGetObjectField(TEXT("scale"), ScaleObj)) {
1669
- double X, Y, Z;
1670
- if ((*ScaleObj)->TryGetNumberField(TEXT("x"), X)) {
1671
- Channels[6]->GetData().AddKey(TickFrame,
1672
- FMovieSceneDoubleValue(X));
1673
- bModified = true;
1674
- }
1675
- if ((*ScaleObj)->TryGetNumberField(TEXT("y"), Y)) {
1676
- Channels[7]->GetData().AddKey(TickFrame,
1677
- FMovieSceneDoubleValue(Y));
1678
- bModified = true;
1679
- }
1680
- if ((*ScaleObj)->TryGetNumberField(TEXT("z"), Z)) {
1681
- Channels[8]->GetData().AddKey(TickFrame,
1682
- FMovieSceneDoubleValue(Z));
1683
- bModified = true;
1684
- }
1685
- }
1686
- }
1687
-
1688
- if (bModified) {
1689
- MovieScene->Modify();
1690
- SendAutomationResponse(Socket, RequestId, true,
1691
- TEXT("Keyframe added"), nullptr,
1692
- FString());
1693
- return true;
1694
- }
1695
- }
1696
- }
1697
- } else {
1698
- // Try generic property tracks
1699
- const TSharedPtr<FJsonValue> Val =
1700
- LocalPayload->TryGetField(TEXT("value"));
1701
- if (Val.IsValid() && Val->Type == EJson::Number) {
1702
- UMovieSceneFloatTrack *Track =
1703
- MovieScene->FindTrack<UMovieSceneFloatTrack>(
1704
- BindingGuid, FName(*PropertyName));
1705
- if (!Track) {
1706
- Track = MovieScene->AddTrack<UMovieSceneFloatTrack>(BindingGuid);
1707
- if (Track)
1708
- Track->SetPropertyNameAndPath(FName(*PropertyName), PropertyName);
1709
- }
1710
- if (Track) {
1711
- bool bSectionAdded = false;
1712
- UMovieSceneFloatSection *Section = Cast<UMovieSceneFloatSection>(
1713
- Track->FindOrAddSection(0, bSectionAdded));
1714
- if (Section) {
1715
- FFrameRate TickResolution = MovieScene->GetTickResolution();
1716
- FFrameRate DisplayRate = MovieScene->GetDisplayRate();
1717
- FFrameNumber FrameNum = FFrameNumber(static_cast<int32>(Frame));
1718
- FFrameNumber TickFrame =
1719
- FFrameRate::TransformTime(FFrameTime(FrameNum), DisplayRate,
1720
- TickResolution)
1721
- .FloorToFrame();
1722
-
1723
- FMovieSceneFloatChannel *Channel =
1724
- Section->GetChannelProxy()
1725
- .GetChannel<FMovieSceneFloatChannel>(0);
1726
- if (Channel) {
1727
- Channel->GetData().UpdateOrAddKey(
1728
- TickFrame, FMovieSceneFloatValue((float)Val->AsNumber()));
1729
- MovieScene->Modify();
1730
- SendAutomationResponse(Socket, RequestId, true,
1731
- TEXT("Float Keyframe added"), nullptr);
1732
- return true;
1733
- }
1734
- }
1735
- }
1736
- } else if (Val.IsValid() && Val->Type == EJson::Boolean) {
1737
- UMovieSceneBoolTrack *Track =
1738
- MovieScene->FindTrack<UMovieSceneBoolTrack>(BindingGuid,
1739
- FName(*PropertyName));
1740
- if (!Track) {
1741
- Track = MovieScene->AddTrack<UMovieSceneBoolTrack>(BindingGuid);
1742
- if (Track)
1743
- Track->SetPropertyNameAndPath(FName(*PropertyName), PropertyName);
1744
- }
1745
- if (Track) {
1746
- bool bSectionAdded = false;
1747
- UMovieSceneBoolSection *Section = Cast<UMovieSceneBoolSection>(
1748
- Track->FindOrAddSection(0, bSectionAdded));
1749
- if (Section) {
1750
- FFrameRate TickResolution = MovieScene->GetTickResolution();
1751
- FFrameRate DisplayRate = MovieScene->GetDisplayRate();
1752
- FFrameNumber FrameNum = FFrameNumber(static_cast<int32>(Frame));
1753
- FFrameNumber TickFrame =
1754
- FFrameRate::TransformTime(FFrameTime(FrameNum), DisplayRate,
1755
- TickResolution)
1756
- .FloorToFrame();
1757
-
1758
- FMovieSceneBoolChannel *Channel =
1759
- Section->GetChannelProxy().GetChannel<FMovieSceneBoolChannel>(
1760
- 0);
1761
- if (Channel) {
1762
- Channel->GetData().UpdateOrAddKey(TickFrame, Val->AsBool());
1763
- MovieScene->Modify();
1764
- SendAutomationResponse(Socket, RequestId, true,
1765
- TEXT("Bool Keyframe added"), nullptr);
1766
- return true;
1767
- }
1768
- }
1769
- }
1770
- }
1771
- }
1772
-
1773
- SendAutomationResponse(
1774
- Socket, RequestId, false,
1775
- TEXT("Unsupported property or failed to create track"), nullptr,
1776
- TEXT("UNSUPPORTED_PROPERTY"));
1777
- return true;
1778
- }
1779
- }
1780
- SendAutomationResponse(Socket, RequestId, false,
1781
- TEXT("Sequence object is not a LevelSequence"),
1782
- nullptr, TEXT("INVALID_SEQUENCE_TYPE"));
1783
- return true;
1784
- #else
1785
- SendAutomationResponse(Socket, RequestId, false,
1786
- TEXT("sequence_add_keyframe requires editor build."),
1787
- nullptr, TEXT("NOT_IMPLEMENTED"));
1788
- return true;
1789
- #endif
1790
- }
1791
-
1792
- bool UMcpAutomationBridgeSubsystem::HandleSequenceAddSection(
1793
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
1794
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
1795
- #if WITH_EDITOR
1796
- FString SeqPath = ResolveSequencePath(Payload);
1797
- if (SeqPath.IsEmpty()) {
1798
- SendAutomationResponse(
1799
- Socket, RequestId, false,
1800
- TEXT("sequence_add_section requires a sequence path"), nullptr,
1801
- TEXT("INVALID_SEQUENCE"));
1802
- return true;
1803
- }
1804
-
1805
- FString TrackName;
1806
- Payload->TryGetStringField(TEXT("trackName"), TrackName);
1807
- FString ActorName;
1808
- Payload->TryGetStringField(TEXT("actorName"), ActorName);
1809
- double StartFrame = 0.0, EndFrame = 100.0;
1810
- Payload->TryGetNumberField(TEXT("startFrame"), StartFrame);
1811
- Payload->TryGetNumberField(TEXT("endFrame"), EndFrame);
1812
-
1813
- ULevelSequence *Sequence = LoadObject<ULevelSequence>(nullptr, *SeqPath);
1814
- if (!Sequence || !Sequence->GetMovieScene()) {
1815
- SendAutomationResponse(Socket, RequestId, false, TEXT("Sequence not found"),
1816
- nullptr, TEXT("SEQUENCE_NOT_FOUND"));
1817
- return true;
1818
- }
1819
-
1820
- UMovieScene *MovieScene = Sequence->GetMovieScene();
1821
-
1822
- // Find the track - either from master tracks or from actor binding
1823
- UMovieSceneTrack *Track = nullptr;
1824
-
1825
- // First check master tracks
1826
- for (UMovieSceneTrack *MasterTrack : MovieScene->GetTracks()) {
1827
- if (MasterTrack &&
1828
- (MasterTrack->GetName().Contains(TrackName) ||
1829
- MasterTrack->GetDisplayName().ToString().Contains(TrackName))) {
1830
- Track = MasterTrack;
1831
- break;
1832
- }
1833
- }
1834
-
1835
- // If not found in master tracks, check bindings
1836
- // Search all bindings if ActorName is empty, or filter by ActorName if
1837
- // provided
1838
- if (!Track) {
1839
- for (const FMovieSceneBinding &Binding :
1840
- const_cast<const UMovieScene *>(MovieScene)->GetBindings()) {
1841
- FString BindingName;
1842
- if (FMovieScenePossessable *Possessable =
1843
- MovieScene->FindPossessable(Binding.GetObjectGuid())) {
1844
- BindingName = Possessable->GetName();
1845
- } else if (FMovieSceneSpawnable *Spawnable =
1846
- MovieScene->FindSpawnable(Binding.GetObjectGuid())) {
1847
- BindingName = Spawnable->GetName();
1848
- }
1849
-
1850
- // If ActorName is provided, filter by it; otherwise search all bindings
1851
- if (ActorName.IsEmpty() || BindingName.Contains(ActorName)) {
1852
- for (UMovieSceneTrack *BindingTrack : Binding.GetTracks()) {
1853
- if (BindingTrack &&
1854
- (BindingTrack->GetName().Contains(TrackName) ||
1855
- BindingTrack->GetDisplayName().ToString().Contains(TrackName))) {
1856
- Track = BindingTrack;
1857
- break;
1858
- }
1859
- }
1860
- if (Track)
1861
- break;
1862
- }
1863
- }
1864
- }
1865
-
1866
- if (!Track) {
1867
- SendAutomationResponse(Socket, RequestId, false, TEXT("Track not found"),
1868
- nullptr, TEXT("TRACK_NOT_FOUND"));
1869
- return true;
1870
- }
1871
-
1872
- // Create the section
1873
- UMovieSceneSection *NewSection = Track->CreateNewSection();
1874
- if (NewSection) {
1875
- FFrameRate TickResolution = MovieScene->GetTickResolution();
1876
- FFrameNumber Start((int32)FMath::RoundToInt(StartFrame));
1877
- FFrameNumber End((int32)FMath::RoundToInt(EndFrame));
1878
- NewSection->SetRange(TRange<FFrameNumber>(Start, End));
1879
- Track->AddSection(*NewSection);
1880
- MovieScene->Modify();
1881
-
1882
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
1883
- Resp->SetStringField(TEXT("trackName"), Track->GetName());
1884
- Resp->SetNumberField(TEXT("startFrame"), StartFrame);
1885
- Resp->SetNumberField(TEXT("endFrame"), EndFrame);
1886
- SendAutomationResponse(Socket, RequestId, true,
1887
- TEXT("Section added to track"), Resp);
1888
- } else {
1889
- SendAutomationResponse(Socket, RequestId, false,
1890
- TEXT("Failed to create section"), nullptr,
1891
- TEXT("SECTION_CREATION_FAILED"));
1892
- }
1893
- return true;
1894
- #else
1895
- SendAutomationResponse(Socket, RequestId, false,
1896
- TEXT("sequence_add_section requires editor build"),
1897
- nullptr, TEXT("EDITOR_ONLY"));
1898
- return true;
1899
- #endif
1900
- }
1901
-
1902
- bool UMcpAutomationBridgeSubsystem::HandleSequenceSetTickResolution(
1903
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
1904
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
1905
- #if WITH_EDITOR
1906
- FString ResolutionStr;
1907
- Payload->TryGetStringField(TEXT("resolution"), ResolutionStr);
1908
-
1909
- FString SeqPath = ResolveSequencePath(Payload);
1910
- if (SeqPath.IsEmpty()) {
1911
- SendAutomationResponse(Socket, RequestId, false, TEXT("path required"),
1912
- nullptr, TEXT("INVALID_ARGUMENT"));
1913
- return true;
1914
- }
1915
-
1916
- ULevelSequence *Sequence = LoadObject<ULevelSequence>(nullptr, *SeqPath);
1917
- if (Sequence && Sequence->GetMovieScene()) {
1918
- FFrameRate TickResolution;
1919
- // Simplified parsing
1920
- if (ResolutionStr.Contains(TEXT("24000")))
1921
- TickResolution = FFrameRate(24000, 1);
1922
- else if (ResolutionStr.Contains(TEXT("60000")))
1923
- TickResolution = FFrameRate(60000, 1);
1924
- else
1925
- TickResolution = FFrameRate(24000, 1); // Default
1926
-
1927
- Sequence->GetMovieScene()->SetTickResolutionDirectly(TickResolution);
1928
- Sequence->GetMovieScene()->Modify();
1929
- SendAutomationResponse(Socket, RequestId, true, TEXT("Tick resolution set"),
1930
- nullptr);
1931
- } else {
1932
- SendAutomationResponse(Socket, RequestId, false, TEXT("Sequence not found"),
1933
- nullptr, TEXT("NOT_FOUND"));
1934
- }
1935
- return true;
1936
- #else
1937
- return false;
1938
- #endif
1939
- }
1940
-
1941
- bool UMcpAutomationBridgeSubsystem::HandleSequenceSetViewRange(
1942
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
1943
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
1944
- #if WITH_EDITOR
1945
- double Start = 0;
1946
- double End = 10;
1947
- Payload->TryGetNumberField(TEXT("start"), Start);
1948
- Payload->TryGetNumberField(TEXT("end"), End);
1949
- FString SeqPath = ResolveSequencePath(Payload);
1950
-
1951
- if (SeqPath.IsEmpty()) {
1952
- SendAutomationResponse(Socket, RequestId, false, TEXT("path required"),
1953
- nullptr, TEXT("INVALID_ARGUMENT"));
1954
- return true;
1955
- }
1956
-
1957
- ULevelSequence *Sequence = LoadObject<ULevelSequence>(nullptr, *SeqPath);
1958
- if (Sequence && Sequence->GetMovieScene()) {
1959
- Sequence->GetMovieScene()->SetViewRange(Start, End);
1960
- Sequence->GetMovieScene()->Modify();
1961
- SendAutomationResponse(Socket, RequestId, true, TEXT("View range set"),
1962
- nullptr);
1963
- } else {
1964
- SendAutomationResponse(Socket, RequestId, false, TEXT("Sequence not found"),
1965
- nullptr, TEXT("NOT_FOUND"));
1966
- }
1967
- return true;
1968
- #else
1969
- return false;
1970
- #endif
1971
- }
1972
-
1973
- bool UMcpAutomationBridgeSubsystem::HandleSequenceSetTrackMuted(
1974
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
1975
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
1976
- #if WITH_EDITOR
1977
- FString SeqPath = ResolveSequencePath(Payload);
1978
- if (SeqPath.IsEmpty()) {
1979
- SendAutomationResponse(Socket, RequestId, false,
1980
- TEXT("sequence path required"), nullptr,
1981
- TEXT("INVALID_SEQUENCE"));
1982
- return true;
1983
- }
1984
-
1985
- FString TrackName;
1986
- Payload->TryGetStringField(TEXT("trackName"), TrackName);
1987
- bool bMuted = true;
1988
- Payload->TryGetBoolField(TEXT("muted"), bMuted);
1989
-
1990
- ULevelSequence *Sequence = LoadObject<ULevelSequence>(nullptr, *SeqPath);
1991
- if (!Sequence || !Sequence->GetMovieScene()) {
1992
- SendAutomationResponse(Socket, RequestId, false, TEXT("Sequence not found"),
1993
- nullptr, TEXT("SEQUENCE_NOT_FOUND"));
1994
- return true;
1995
- }
1996
-
1997
- UMovieScene *MovieScene = Sequence->GetMovieScene();
1998
- UMovieSceneTrack *Track = nullptr;
1999
-
2000
- // Search master tracks and binding tracks
2001
- for (UMovieSceneTrack *MasterTrack : MovieScene->GetTracks()) {
2002
- if (MasterTrack && MasterTrack->GetName().Contains(TrackName)) {
2003
- Track = MasterTrack;
2004
- break;
2005
- }
2006
- }
2007
-
2008
- if (!Track) {
2009
- for (const FMovieSceneBinding &Binding :
2010
- const_cast<const UMovieScene *>(MovieScene)->GetBindings()) {
2011
- for (UMovieSceneTrack *BindingTrack : Binding.GetTracks()) {
2012
- if (BindingTrack && BindingTrack->GetName().Contains(TrackName)) {
2013
- Track = BindingTrack;
2014
- break;
2015
- }
2016
- }
2017
- if (Track)
2018
- break;
2019
- }
2020
- }
2021
-
2022
- if (!Track) {
2023
- SendAutomationResponse(Socket, RequestId, false, TEXT("Track not found"),
2024
- nullptr, TEXT("TRACK_NOT_FOUND"));
2025
- return true;
2026
- }
2027
-
2028
- // Set muted state via EvalOptions
2029
- Track->SetEvalDisabled(bMuted);
2030
- MovieScene->Modify();
2031
-
2032
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
2033
- Resp->SetStringField(TEXT("trackName"), Track->GetName());
2034
- Resp->SetBoolField(TEXT("muted"), bMuted);
2035
- SendAutomationResponse(Socket, RequestId, true,
2036
- bMuted ? TEXT("Track muted") : TEXT("Track unmuted"),
2037
- Resp);
2038
- return true;
2039
- #else
2040
- SendAutomationResponse(Socket, RequestId, false,
2041
- TEXT("sequence_set_track_muted requires editor build"),
2042
- nullptr, TEXT("EDITOR_ONLY"));
2043
- return true;
2044
- #endif
2045
- }
2046
-
2047
- bool UMcpAutomationBridgeSubsystem::HandleSequenceSetTrackSolo(
2048
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
2049
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
2050
- #if WITH_EDITOR
2051
- // Note: UE doesn't have a direct "solo" property on tracks, but we can
2052
- // simulate it by muting all other tracks
2053
- FString SeqPath = ResolveSequencePath(Payload);
2054
- if (SeqPath.IsEmpty()) {
2055
- SendAutomationResponse(Socket, RequestId, false,
2056
- TEXT("sequence path required"), nullptr,
2057
- TEXT("INVALID_SEQUENCE"));
2058
- return true;
2059
- }
2060
-
2061
- FString TrackName;
2062
- Payload->TryGetStringField(TEXT("trackName"), TrackName);
2063
- bool bSolo = true;
2064
- Payload->TryGetBoolField(TEXT("solo"), bSolo);
2065
-
2066
- ULevelSequence *Sequence = LoadObject<ULevelSequence>(nullptr, *SeqPath);
2067
- if (!Sequence || !Sequence->GetMovieScene()) {
2068
- SendAutomationResponse(Socket, RequestId, false, TEXT("Sequence not found"),
2069
- nullptr, TEXT("SEQUENCE_NOT_FOUND"));
2070
- return true;
2071
- }
2072
-
2073
- UMovieScene *MovieScene = Sequence->GetMovieScene();
2074
- UMovieSceneTrack *SoloTrack = nullptr;
2075
-
2076
- // Find the track to solo
2077
- TArray<UMovieSceneTrack *> AllTracks;
2078
- for (UMovieSceneTrack *Track : MovieScene->GetTracks()) {
2079
- if (Track) {
2080
- AllTracks.Add(Track);
2081
- if (Track->GetName().Contains(TrackName)) {
2082
- SoloTrack = Track;
2083
- }
2084
- }
2085
- }
2086
-
2087
- for (const FMovieSceneBinding &Binding :
2088
- const_cast<const UMovieScene *>(MovieScene)->GetBindings()) {
2089
- for (UMovieSceneTrack *Track : Binding.GetTracks()) {
2090
- if (Track) {
2091
- AllTracks.Add(Track);
2092
- if (Track->GetName().Contains(TrackName)) {
2093
- SoloTrack = Track;
2094
- }
2095
- }
2096
- }
2097
- }
2098
-
2099
- if (!SoloTrack) {
2100
- SendAutomationResponse(Socket, RequestId, false, TEXT("Track not found"),
2101
- nullptr, TEXT("TRACK_NOT_FOUND"));
2102
- return true;
2103
- }
2104
-
2105
- // If enabling solo, mute all other tracks; if disabling, unmute all
2106
- for (UMovieSceneTrack *Track : AllTracks) {
2107
- if (bSolo) {
2108
- Track->SetEvalDisabled(Track != SoloTrack);
2109
- } else {
2110
- Track->SetEvalDisabled(false);
2111
- }
2112
- }
2113
- MovieScene->Modify();
2114
-
2115
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
2116
- Resp->SetStringField(TEXT("trackName"), SoloTrack->GetName());
2117
- Resp->SetBoolField(TEXT("solo"), bSolo);
2118
- SendAutomationResponse(
2119
- Socket, RequestId, true,
2120
- bSolo ? TEXT("Track solo enabled") : TEXT("Solo disabled"), Resp);
2121
- return true;
2122
- #else
2123
- SendAutomationResponse(Socket, RequestId, false,
2124
- TEXT("sequence_set_track_solo requires editor build"),
2125
- nullptr, TEXT("EDITOR_ONLY"));
2126
- return true;
2127
- #endif
2128
- }
2129
-
2130
- bool UMcpAutomationBridgeSubsystem::HandleSequenceSetTrackLocked(
2131
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
2132
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
2133
- #if WITH_EDITOR
2134
- FString SeqPath = ResolveSequencePath(Payload);
2135
- if (SeqPath.IsEmpty()) {
2136
- SendAutomationResponse(Socket, RequestId, false,
2137
- TEXT("sequence path required"), nullptr,
2138
- TEXT("INVALID_SEQUENCE"));
2139
- return true;
2140
- }
2141
-
2142
- FString TrackName;
2143
- Payload->TryGetStringField(TEXT("trackName"), TrackName);
2144
- bool bLocked = true;
2145
- Payload->TryGetBoolField(TEXT("locked"), bLocked);
2146
-
2147
- ULevelSequence *Sequence = LoadObject<ULevelSequence>(nullptr, *SeqPath);
2148
- if (!Sequence || !Sequence->GetMovieScene()) {
2149
- SendAutomationResponse(Socket, RequestId, false, TEXT("Sequence not found"),
2150
- nullptr, TEXT("SEQUENCE_NOT_FOUND"));
2151
- return true;
2152
- }
2153
-
2154
- UMovieScene *MovieScene = Sequence->GetMovieScene();
2155
- UMovieSceneTrack *Track = nullptr;
2156
-
2157
- // Search for track
2158
- for (UMovieSceneTrack *MasterTrack : MovieScene->GetTracks()) {
2159
- if (MasterTrack && MasterTrack->GetName().Contains(TrackName)) {
2160
- Track = MasterTrack;
2161
- break;
2162
- }
2163
- }
2164
-
2165
- if (!Track) {
2166
- for (const FMovieSceneBinding &Binding :
2167
- const_cast<const UMovieScene *>(MovieScene)->GetBindings()) {
2168
- for (UMovieSceneTrack *BindingTrack : Binding.GetTracks()) {
2169
- if (BindingTrack && BindingTrack->GetName().Contains(TrackName)) {
2170
- Track = BindingTrack;
2171
- break;
2172
- }
2173
- }
2174
- if (Track)
2175
- break;
2176
- }
2177
- }
2178
-
2179
- if (!Track) {
2180
- SendAutomationResponse(Socket, RequestId, false, TEXT("Track not found"),
2181
- nullptr, TEXT("TRACK_NOT_FOUND"));
2182
- return true;
2183
- }
2184
-
2185
- // Lock all sections in the track
2186
- for (UMovieSceneSection *Section : Track->GetAllSections()) {
2187
- if (Section) {
2188
- Section->SetIsLocked(bLocked);
2189
- }
2190
- }
2191
- MovieScene->Modify();
2192
-
2193
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
2194
- Resp->SetStringField(TEXT("trackName"), Track->GetName());
2195
- Resp->SetBoolField(TEXT("locked"), bLocked);
2196
- SendAutomationResponse(
2197
- Socket, RequestId, true,
2198
- bLocked ? TEXT("Track locked") : TEXT("Track unlocked"), Resp);
2199
- return true;
2200
- #else
2201
- SendAutomationResponse(
2202
- Socket, RequestId, false,
2203
- TEXT("sequence_set_track_locked requires editor build"), nullptr,
2204
- TEXT("EDITOR_ONLY"));
2205
- return true;
2206
- #endif
2207
- }
2208
-
2209
- bool UMcpAutomationBridgeSubsystem::HandleSequenceRemoveTrack(
2210
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
2211
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
2212
- #if WITH_EDITOR
2213
- FString SeqPath = ResolveSequencePath(Payload);
2214
- if (SeqPath.IsEmpty()) {
2215
- SendAutomationResponse(Socket, RequestId, false,
2216
- TEXT("sequence path required"), nullptr,
2217
- TEXT("INVALID_SEQUENCE"));
2218
- return true;
2219
- }
2220
-
2221
- FString TrackName;
2222
- Payload->TryGetStringField(TEXT("trackName"), TrackName);
2223
-
2224
- ULevelSequence *Sequence = LoadObject<ULevelSequence>(nullptr, *SeqPath);
2225
- if (!Sequence || !Sequence->GetMovieScene()) {
2226
- SendAutomationResponse(Socket, RequestId, false, TEXT("Sequence not found"),
2227
- nullptr, TEXT("SEQUENCE_NOT_FOUND"));
2228
- return true;
2229
- }
2230
-
2231
- UMovieScene *MovieScene = Sequence->GetMovieScene();
2232
- bool bRemoved = false;
2233
- FString RemovedTrackName;
2234
-
2235
- // Try to remove from master tracks first
2236
- for (UMovieSceneTrack *Track : MovieScene->GetTracks()) {
2237
- if (Track && Track->GetName().Contains(TrackName)) {
2238
- RemovedTrackName = Track->GetName();
2239
- MovieScene->RemoveTrack(*Track);
2240
- bRemoved = true;
2241
- break;
2242
- }
2243
- }
2244
-
2245
- // If not found, try binding tracks
2246
- if (!bRemoved) {
2247
- for (const FMovieSceneBinding &Binding :
2248
- const_cast<const UMovieScene *>(MovieScene)->GetBindings()) {
2249
- for (UMovieSceneTrack *Track : Binding.GetTracks()) {
2250
- if (Track && Track->GetName().Contains(TrackName)) {
2251
- RemovedTrackName = Track->GetName();
2252
- MovieScene->RemoveTrack(*Track);
2253
- bRemoved = true;
2254
- break;
2255
- }
2256
- }
2257
- if (bRemoved)
2258
- break;
2259
- }
2260
- }
2261
-
2262
- if (bRemoved) {
2263
- MovieScene->Modify();
2264
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
2265
- Resp->SetStringField(TEXT("trackName"), RemovedTrackName);
2266
- SendAutomationResponse(Socket, RequestId, true, TEXT("Track removed"),
2267
- Resp);
2268
- } else {
2269
- SendAutomationResponse(Socket, RequestId, false, TEXT("Track not found"),
2270
- nullptr, TEXT("TRACK_NOT_FOUND"));
2271
- }
2272
- return true;
2273
- #else
2274
- SendAutomationResponse(Socket, RequestId, false,
2275
- TEXT("sequence_remove_track requires editor build"),
2276
- nullptr, TEXT("EDITOR_ONLY"));
2277
- return true;
2278
- #endif
2279
- }
2280
-
2281
- bool UMcpAutomationBridgeSubsystem::HandleSequenceAction(
2282
- const FString &RequestId, const FString &Action,
2283
- const TSharedPtr<FJsonObject> &Payload,
2284
- TSharedPtr<FMcpBridgeWebSocket> RequestingSocket) {
2285
- const FString Lower = Action.ToLower();
2286
- // Also handle manage_sequence which acts as a dispatcher for sub-actions
2287
- if (!Lower.StartsWith(TEXT("sequence_")) &&
2288
- !Lower.Equals(TEXT("manage_sequence")))
2289
- return false;
2290
-
2291
- TSharedPtr<FJsonObject> LocalPayload =
2292
- Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
2293
- FString EffectiveAction = Lower;
2294
-
2295
- // If generic manage_sequence, extract the sub-action to determine behavior
2296
- if (Lower.Equals(TEXT("manage_sequence"))) {
2297
- FString Sub;
2298
- if (LocalPayload->TryGetStringField(TEXT("subAction"), Sub) &&
2299
- !Sub.IsEmpty()) {
2300
- EffectiveAction = Sub.ToLower();
2301
- // If subAction is just "create", map to "sequence_create" for consistency
2302
- if (EffectiveAction == TEXT("create"))
2303
- EffectiveAction = TEXT("sequence_create");
2304
- else if (!EffectiveAction.StartsWith(TEXT("sequence_")))
2305
- EffectiveAction = TEXT("sequence_") + EffectiveAction;
2306
- }
2307
- }
2308
-
2309
- if (EffectiveAction == TEXT("sequence_create"))
2310
- return HandleSequenceCreate(RequestId, LocalPayload, RequestingSocket);
2311
- if (EffectiveAction == TEXT("sequence_set_display_rate"))
2312
- return HandleSequenceSetDisplayRate(RequestId, LocalPayload,
2313
- RequestingSocket);
2314
- if (EffectiveAction == TEXT("sequence_set_properties"))
2315
- return HandleSequenceSetProperties(RequestId, LocalPayload,
2316
- RequestingSocket);
2317
- if (EffectiveAction == TEXT("sequence_open"))
2318
- return HandleSequenceOpen(RequestId, LocalPayload, RequestingSocket);
2319
- if (EffectiveAction == TEXT("sequence_add_camera"))
2320
- return HandleSequenceAddCamera(RequestId, LocalPayload, RequestingSocket);
2321
- if (EffectiveAction == TEXT("sequence_play"))
2322
- return HandleSequencePlay(RequestId, LocalPayload, RequestingSocket);
2323
- if (EffectiveAction == TEXT("sequence_add_actor"))
2324
- return HandleSequenceAddActor(RequestId, LocalPayload, RequestingSocket);
2325
- if (EffectiveAction == TEXT("sequence_add_actors"))
2326
- return HandleSequenceAddActors(RequestId, LocalPayload, RequestingSocket);
2327
- if (EffectiveAction == TEXT("sequence_add_spawnable_from_class"))
2328
- return HandleSequenceAddSpawnable(RequestId, LocalPayload,
2329
- RequestingSocket);
2330
- if (EffectiveAction == TEXT("sequence_remove_actors"))
2331
- return HandleSequenceRemoveActors(RequestId, LocalPayload,
2332
- RequestingSocket);
2333
- if (EffectiveAction == TEXT("sequence_get_bindings"))
2334
- return HandleSequenceGetBindings(RequestId, LocalPayload, RequestingSocket);
2335
- if (EffectiveAction == TEXT("sequence_get_properties"))
2336
- return HandleSequenceGetProperties(RequestId, LocalPayload,
2337
- RequestingSocket);
2338
- if (EffectiveAction == TEXT("sequence_set_playback_speed"))
2339
- return HandleSequenceSetPlaybackSpeed(RequestId, LocalPayload,
2340
- RequestingSocket);
2341
- if (EffectiveAction == TEXT("sequence_pause"))
2342
- return HandleSequencePause(RequestId, LocalPayload, RequestingSocket);
2343
- if (EffectiveAction == TEXT("sequence_stop"))
2344
- return HandleSequenceStop(RequestId, LocalPayload, RequestingSocket);
2345
- if (EffectiveAction == TEXT("sequence_list"))
2346
- return HandleSequenceList(RequestId, LocalPayload, RequestingSocket);
2347
- if (EffectiveAction == TEXT("sequence_duplicate"))
2348
- return HandleSequenceDuplicate(RequestId, LocalPayload, RequestingSocket);
2349
- if (EffectiveAction == TEXT("sequence_rename"))
2350
- return HandleSequenceRename(RequestId, LocalPayload, RequestingSocket);
2351
- if (EffectiveAction == TEXT("sequence_delete"))
2352
- return HandleSequenceDelete(RequestId, LocalPayload, RequestingSocket);
2353
- if (EffectiveAction == TEXT("sequence_get_metadata"))
2354
- return HandleSequenceGetMetadata(RequestId, LocalPayload, RequestingSocket);
2355
- if (EffectiveAction == TEXT("sequence_add_keyframe"))
2356
- return HandleSequenceAddKeyframe(RequestId, LocalPayload, RequestingSocket);
2357
-
2358
- // New handlers
2359
- if (EffectiveAction == TEXT("sequence_add_section"))
2360
- return HandleSequenceAddSection(RequestId, LocalPayload, RequestingSocket);
2361
- if (EffectiveAction == TEXT("sequence_set_tick_resolution"))
2362
- return HandleSequenceSetTickResolution(RequestId, LocalPayload,
2363
- RequestingSocket);
2364
- if (EffectiveAction == TEXT("sequence_set_view_range"))
2365
- return HandleSequenceSetViewRange(RequestId, LocalPayload,
2366
- RequestingSocket);
2367
- if (EffectiveAction == TEXT("sequence_set_track_muted"))
2368
- return HandleSequenceSetTrackMuted(RequestId, LocalPayload,
2369
- RequestingSocket);
2370
- if (EffectiveAction == TEXT("sequence_set_track_solo"))
2371
- return HandleSequenceSetTrackSolo(RequestId, LocalPayload,
2372
- RequestingSocket);
2373
- if (EffectiveAction == TEXT("sequence_set_track_locked"))
2374
- return HandleSequenceSetTrackLocked(RequestId, LocalPayload,
2375
- RequestingSocket);
2376
- if (EffectiveAction == TEXT("sequence_remove_track"))
2377
- return HandleSequenceRemoveTrack(RequestId, LocalPayload, RequestingSocket);
2378
-
2379
- if (EffectiveAction == TEXT("sequence_list_track_types")) {
2380
- // Discovery: list available track types
2381
- TArray<TSharedPtr<FJsonValue>> Types;
2382
- // Add common shortcuts first
2383
- Types.Add(MakeShared<FJsonValueString>(TEXT("transform")));
2384
- Types.Add(MakeShared<FJsonValueString>(TEXT("3dtransform")));
2385
- Types.Add(MakeShared<FJsonValueString>(TEXT("audio")));
2386
- Types.Add(MakeShared<FJsonValueString>(TEXT("event")));
2387
-
2388
- // Discover all UMovieSceneTrack subclasses via reflection
2389
- TSet<FString> AddedNames;
2390
- AddedNames.Add(TEXT("transform"));
2391
- AddedNames.Add(TEXT("3dtransform"));
2392
- AddedNames.Add(TEXT("audio"));
2393
- AddedNames.Add(TEXT("event"));
2394
-
2395
- for (TObjectIterator<UClass> It; It; ++It) {
2396
- if (It->IsChildOf(UMovieSceneTrack::StaticClass()) &&
2397
- !It->HasAnyClassFlags(CLASS_Abstract) &&
2398
- !AddedNames.Contains(It->GetName())) {
2399
- Types.Add(MakeShared<FJsonValueString>(It->GetName()));
2400
- AddedNames.Add(It->GetName());
2401
- }
2402
- }
2403
-
2404
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
2405
- Resp->SetArrayField(TEXT("types"), Types);
2406
- Resp->SetNumberField(TEXT("count"), Types.Num());
2407
- SendAutomationResponse(RequestingSocket, RequestId, true,
2408
- TEXT("Available track types"), Resp);
2409
- return true;
2410
- }
2411
-
2412
- if (EffectiveAction == TEXT("sequence_add_track")) {
2413
- // add_track action: Add a track to a binding in a level sequence
2414
- FString SeqPath = ResolveSequencePath(LocalPayload);
2415
- if (SeqPath.IsEmpty()) {
2416
- SendAutomationResponse(
2417
- RequestingSocket, RequestId, false,
2418
- TEXT("sequence_add_track requires a sequence path"), nullptr,
2419
- TEXT("INVALID_SEQUENCE"));
2420
- return true;
2421
- }
2422
-
2423
- ULevelSequence *Sequence = LoadObject<ULevelSequence>(nullptr, *SeqPath);
2424
- if (!Sequence) {
2425
- SendAutomationResponse(RequestingSocket, RequestId, false,
2426
- TEXT("Level sequence not found"), nullptr,
2427
- TEXT("SEQUENCE_NOT_FOUND"));
2428
- return true;
2429
- }
2430
-
2431
- UMovieScene *MovieScene = Sequence->GetMovieScene();
2432
- if (!MovieScene) {
2433
- SendAutomationResponse(RequestingSocket, RequestId, false,
2434
- TEXT("MovieScene not available"), nullptr,
2435
- TEXT("MOVIESCENE_UNAVAILABLE"));
2436
- return true;
2437
- }
2438
-
2439
- FString TrackType;
2440
- LocalPayload->TryGetStringField(TEXT("trackType"), TrackType);
2441
- if (TrackType.IsEmpty()) {
2442
- SendAutomationResponse(
2443
- RequestingSocket, RequestId, false,
2444
- TEXT("trackType required (e.g., Transform, Animation, Audio, Event)"),
2445
- nullptr, TEXT("INVALID_ARGUMENT"));
2446
- return true;
2447
- }
2448
-
2449
- FString TrackName;
2450
- LocalPayload->TryGetStringField(TEXT("trackName"), TrackName);
2451
-
2452
- FString ActorName;
2453
- LocalPayload->TryGetStringField(TEXT("actorName"), ActorName);
2454
-
2455
- // Find or use master track if no actor specified
2456
- FGuid BindingGuid;
2457
- if (!ActorName.IsEmpty()) {
2458
- // Find binding by actor name
2459
- // Use const interface to avoid deprecation warning
2460
- const UMovieScene *ConstMovieScene = MovieScene;
2461
- for (const FMovieSceneBinding &Binding : ConstMovieScene->GetBindings()) {
2462
- FString BindingName;
2463
- if (FMovieScenePossessable *Possessable =
2464
- MovieScene->FindPossessable(Binding.GetObjectGuid())) {
2465
- BindingName = Possessable->GetName();
2466
- } else if (FMovieSceneSpawnable *Spawnable =
2467
- MovieScene->FindSpawnable(Binding.GetObjectGuid())) {
2468
- BindingName = Spawnable->GetName();
2469
- }
2470
-
2471
- if (BindingName.Contains(ActorName)) {
2472
- BindingGuid = Binding.GetObjectGuid();
2473
- break;
2474
- }
2475
- }
2476
- if (!BindingGuid.IsValid()) {
2477
- SendAutomationResponse(
2478
- RequestingSocket, RequestId, false,
2479
- FString::Printf(TEXT("Binding not found for actor: %s"),
2480
- *ActorName),
2481
- nullptr, TEXT("BINDING_NOT_FOUND"));
2482
- return true;
2483
- }
2484
- }
2485
-
2486
- // Add the track
2487
- UMovieSceneTrack *NewTrack = nullptr;
2488
-
2489
- // Dynamic resolution with heuristics
2490
- UClass *TrackClass = ResolveUClass(TrackType);
2491
-
2492
- // Try with common prefixes
2493
- if (!TrackClass) {
2494
- TrackClass = ResolveUClass(
2495
- FString::Printf(TEXT("UMovieScene%sTrack"), *TrackType));
2496
- }
2497
- if (!TrackClass) {
2498
- TrackClass =
2499
- ResolveUClass(FString::Printf(TEXT("MovieScene%sTrack"), *TrackType));
2500
- }
2501
- // Try simple "U" prefix
2502
- if (!TrackClass) {
2503
- TrackClass = ResolveUClass(FString::Printf(TEXT("U%s"), *TrackType));
2504
- }
2505
-
2506
- // Validate it's actually a track class
2507
- if (TrackClass && TrackClass->IsChildOf(UMovieSceneTrack::StaticClass())) {
2508
- if (BindingGuid.IsValid()) {
2509
- NewTrack = MovieScene->AddTrack(TrackClass, BindingGuid);
2510
- } else {
2511
- NewTrack = MovieScene->AddTrack(TrackClass);
2512
- }
2513
- } else if (TrackClass) {
2514
- // Found a class but it's not a track
2515
- SendAutomationError(
2516
- RequestingSocket, RequestId,
2517
- FString::Printf(TEXT("Class '%s' is not a UMovieSceneTrack"),
2518
- *TrackClass->GetName()),
2519
- TEXT("INVALID_CLASS_TYPE"));
2520
- return true;
2521
- }
2522
-
2523
- if (NewTrack) {
2524
- Sequence->MarkPackageDirty();
2525
-
2526
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
2527
- Resp->SetBoolField(TEXT("success"), true);
2528
- Resp->SetStringField(TEXT("sequencePath"), SeqPath);
2529
- Resp->SetStringField(TEXT("trackType"), TrackType);
2530
- Resp->SetStringField(TEXT("trackName"),
2531
- TrackName.IsEmpty() ? TrackType : TrackName);
2532
- if (!ActorName.IsEmpty()) {
2533
- Resp->SetStringField(TEXT("actorName"), ActorName);
2534
- Resp->SetStringField(TEXT("bindingGuid"), BindingGuid.ToString());
2535
- }
2536
- SendAutomationResponse(RequestingSocket, RequestId, true,
2537
- TEXT("Track added successfully"), Resp, FString());
2538
- } else {
2539
- SendAutomationResponse(
2540
- RequestingSocket, RequestId, false,
2541
- FString::Printf(TEXT("Failed to add track of type: %s"), *TrackType),
2542
- nullptr, TEXT("TRACK_CREATION_FAILED"));
2543
- }
2544
- return true;
2545
- }
2546
-
2547
- // sequence_list_tracks: List all tracks for a sequence binding
2548
- if (EffectiveAction == TEXT("sequence_list_tracks")) {
2549
- FString SeqPath = ResolveSequencePath(LocalPayload);
2550
- if (SeqPath.IsEmpty()) {
2551
- SendAutomationResponse(
2552
- RequestingSocket, RequestId, false,
2553
- TEXT("sequence_list_tracks requires a sequence path"), nullptr,
2554
- TEXT("INVALID_SEQUENCE"));
2555
- return true;
2556
- }
2557
-
2558
- #if WITH_EDITOR
2559
- ULevelSequence *Sequence = LoadObject<ULevelSequence>(nullptr, *SeqPath);
2560
- if (!Sequence) {
2561
- SendAutomationResponse(RequestingSocket, RequestId, false,
2562
- TEXT("Level sequence not found"), nullptr,
2563
- TEXT("SEQUENCE_NOT_FOUND"));
2564
- return true;
2565
- }
2566
-
2567
- UMovieScene *MovieScene = Sequence->GetMovieScene();
2568
- if (!MovieScene) {
2569
- SendAutomationResponse(RequestingSocket, RequestId, false,
2570
- TEXT("MovieScene not available"), nullptr,
2571
- TEXT("MOVIESCENE_UNAVAILABLE"));
2572
- return true;
2573
- }
2574
-
2575
- TArray<TSharedPtr<FJsonValue>> TracksArray;
2576
-
2577
- // Get Tracks (formerly GetMasterTracks)
2578
- for (UMovieSceneTrack *Track : MovieScene->GetTracks()) {
2579
- if (!Track)
2580
- continue;
2581
- TSharedPtr<FJsonObject> TrackObj = MakeShared<FJsonObject>();
2582
- TrackObj->SetStringField(TEXT("trackName"), Track->GetName());
2583
- TrackObj->SetStringField(TEXT("trackType"), Track->GetClass()->GetName());
2584
- TrackObj->SetStringField(TEXT("displayName"),
2585
- Track->GetDisplayName().ToString());
2586
- TrackObj->SetBoolField(TEXT("isMasterTrack"), true);
2587
- TrackObj->SetNumberField(TEXT("sectionCount"),
2588
- Track->GetAllSections().Num());
2589
- TracksArray.Add(MakeShared<FJsonValueObject>(TrackObj));
2590
- }
2591
-
2592
- // Get tracks from bindings
2593
- for (const FMovieSceneBinding &Binding :
2594
- const_cast<const UMovieScene *>(MovieScene)->GetBindings()) {
2595
- FString BindingName;
2596
- if (FMovieScenePossessable *Possessable =
2597
- MovieScene->FindPossessable(Binding.GetObjectGuid())) {
2598
- BindingName = Possessable->GetName();
2599
- } else if (FMovieSceneSpawnable *Spawnable =
2600
- MovieScene->FindSpawnable(Binding.GetObjectGuid())) {
2601
- BindingName = Spawnable->GetName();
2602
- }
2603
-
2604
- for (UMovieSceneTrack *Track : Binding.GetTracks()) {
2605
- if (!Track)
2606
- continue;
2607
- TSharedPtr<FJsonObject> TrackObj = MakeShared<FJsonObject>();
2608
- TrackObj->SetStringField(TEXT("trackName"), Track->GetName());
2609
- TrackObj->SetStringField(TEXT("trackType"),
2610
- Track->GetClass()->GetName());
2611
- TrackObj->SetStringField(TEXT("displayName"),
2612
- Track->GetDisplayName().ToString());
2613
- TrackObj->SetBoolField(TEXT("isMasterTrack"), false);
2614
- TrackObj->SetStringField(TEXT("bindingName"), BindingName);
2615
- TrackObj->SetStringField(TEXT("bindingGuid"),
2616
- Binding.GetObjectGuid().ToString());
2617
- TrackObj->SetNumberField(TEXT("sectionCount"),
2618
- Track->GetAllSections().Num());
2619
- TracksArray.Add(MakeShared<FJsonValueObject>(TrackObj));
2620
- }
2621
- }
2622
-
2623
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
2624
- Resp->SetArrayField(TEXT("tracks"), TracksArray);
2625
- Resp->SetNumberField(TEXT("trackCount"), TracksArray.Num());
2626
- Resp->SetStringField(TEXT("sequencePath"), SeqPath);
2627
- SendAutomationResponse(
2628
- RequestingSocket, RequestId, true,
2629
- FString::Printf(TEXT("Found %d tracks"), TracksArray.Num()), Resp,
2630
- FString());
2631
- return true;
2632
- #else
2633
- SendAutomationResponse(RequestingSocket, RequestId, false,
2634
- TEXT("sequence_list_tracks requires editor build"),
2635
- nullptr, TEXT("EDITOR_ONLY"));
2636
- return true;
2637
- #endif
2638
- }
2639
-
2640
- // sequence_set_work_range: Set the work range of a sequence
2641
- if (EffectiveAction == TEXT("sequence_set_work_range")) {
2642
- FString SeqPath = ResolveSequencePath(LocalPayload);
2643
- if (SeqPath.IsEmpty()) {
2644
- SendAutomationResponse(
2645
- RequestingSocket, RequestId, false,
2646
- TEXT("sequence_set_work_range requires a sequence path"), nullptr,
2647
- TEXT("INVALID_SEQUENCE"));
2648
- return true;
2649
- }
2650
-
2651
- #if WITH_EDITOR
2652
- ULevelSequence *Sequence = LoadObject<ULevelSequence>(nullptr, *SeqPath);
2653
- if (!Sequence) {
2654
- SendAutomationResponse(RequestingSocket, RequestId, false,
2655
- TEXT("Level sequence not found"), nullptr,
2656
- TEXT("SEQUENCE_NOT_FOUND"));
2657
- return true;
2658
- }
2659
-
2660
- UMovieScene *MovieScene = Sequence->GetMovieScene();
2661
- if (!MovieScene) {
2662
- SendAutomationResponse(RequestingSocket, RequestId, false,
2663
- TEXT("MovieScene not available"), nullptr,
2664
- TEXT("MOVIESCENE_UNAVAILABLE"));
2665
- return true;
2666
- }
2667
-
2668
- double Start = 0.0, End = 0.0;
2669
- LocalPayload->TryGetNumberField(TEXT("start"), Start);
2670
- LocalPayload->TryGetNumberField(TEXT("end"), End);
2671
-
2672
- FFrameRate TickResolution = MovieScene->GetTickResolution();
2673
- // Round to int32 for FFrameNumber constructor
2674
- FFrameNumber StartFrame(
2675
- (int32)FMath::RoundToInt(Start * TickResolution.AsDecimal()));
2676
- FFrameNumber EndFrame(
2677
- (int32)FMath::RoundToInt(End * TickResolution.AsDecimal()));
2678
-
2679
- // SetWorkingRange expects seconds (double)
2680
- MovieScene->SetWorkingRange(Start, End);
2681
- MovieScene->Modify();
2682
-
2683
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
2684
- Resp->SetNumberField(TEXT("startFrame"), StartFrame.Value);
2685
- Resp->SetNumberField(TEXT("endFrame"), EndFrame.Value);
2686
- Resp->SetStringField(TEXT("sequencePath"), SeqPath);
2687
- SendAutomationResponse(RequestingSocket, RequestId, true,
2688
- TEXT("Work range set successfully"), Resp,
2689
- FString());
2690
- return true;
2691
- #else
2692
- SendAutomationResponse(
2693
- RequestingSocket, RequestId, false,
2694
- TEXT("sequence_set_work_range requires editor build"), nullptr,
2695
- TEXT("EDITOR_ONLY"));
2696
- return true;
2697
- #endif
2698
- }
2699
-
2700
- SendAutomationResponse(
2701
- RequestingSocket, RequestId, false,
2702
- FString::Printf(TEXT("Sequence action not implemented by plugin: %s"),
2703
- *Action),
2704
- nullptr, TEXT("NOT_IMPLEMENTED"));
2705
- return true;
2706
- }