unreal-engine-mcp-server 0.4.7 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (438) hide show
  1. package/.env.example +26 -0
  2. package/.env.production +38 -7
  3. package/.eslintrc.json +0 -54
  4. package/.eslintrc.override.json +8 -0
  5. package/.github/ISSUE_TEMPLATE/bug_report.yml +94 -0
  6. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  7. package/.github/ISSUE_TEMPLATE/feature_request.yml +56 -0
  8. package/.github/copilot-instructions.md +478 -45
  9. package/.github/dependabot.yml +19 -0
  10. package/.github/labeler.yml +24 -0
  11. package/.github/labels.yml +70 -0
  12. package/.github/pull_request_template.md +42 -0
  13. package/.github/release-drafter.yml +148 -0
  14. package/.github/workflows/auto-merge.yml +38 -0
  15. package/.github/workflows/ci.yml +38 -0
  16. package/.github/workflows/dependency-review.yml +17 -0
  17. package/.github/workflows/gemini-issue-triage.yml +172 -0
  18. package/.github/workflows/greetings.yml +23 -0
  19. package/.github/workflows/labeler.yml +16 -0
  20. package/.github/workflows/links.yml +80 -0
  21. package/.github/workflows/pr-size-labeler.yml +137 -0
  22. package/.github/workflows/publish-mcp.yml +12 -7
  23. package/.github/workflows/release-drafter.yml +23 -0
  24. package/.github/workflows/release.yml +112 -0
  25. package/.github/workflows/semantic-pull-request.yml +35 -0
  26. package/.github/workflows/smoke-test.yml +36 -0
  27. package/.github/workflows/stale.yml +28 -0
  28. package/CHANGELOG.md +267 -31
  29. package/CONTRIBUTING.md +140 -0
  30. package/README.md +166 -71
  31. package/claude_desktop_config_example.json +7 -6
  32. package/dist/automation/bridge.d.ts +50 -0
  33. package/dist/automation/bridge.js +452 -0
  34. package/dist/automation/connection-manager.d.ts +23 -0
  35. package/dist/automation/connection-manager.js +107 -0
  36. package/dist/automation/handshake.d.ts +11 -0
  37. package/dist/automation/handshake.js +89 -0
  38. package/dist/automation/index.d.ts +3 -0
  39. package/dist/automation/index.js +3 -0
  40. package/dist/automation/message-handler.d.ts +12 -0
  41. package/dist/automation/message-handler.js +149 -0
  42. package/dist/automation/request-tracker.d.ts +25 -0
  43. package/dist/automation/request-tracker.js +98 -0
  44. package/dist/automation/types.d.ts +130 -0
  45. package/dist/automation/types.js +2 -0
  46. package/dist/cli.js +32 -5
  47. package/dist/config.d.ts +27 -0
  48. package/dist/config.js +60 -0
  49. package/dist/constants.d.ts +12 -0
  50. package/dist/constants.js +12 -0
  51. package/dist/graphql/resolvers.d.ts +268 -0
  52. package/dist/graphql/resolvers.js +743 -0
  53. package/dist/graphql/schema.d.ts +5 -0
  54. package/dist/graphql/schema.js +437 -0
  55. package/dist/graphql/server.d.ts +26 -0
  56. package/dist/graphql/server.js +115 -0
  57. package/dist/graphql/types.d.ts +7 -0
  58. package/dist/graphql/types.js +2 -0
  59. package/dist/handlers/resource-handlers.d.ts +20 -0
  60. package/dist/handlers/resource-handlers.js +180 -0
  61. package/dist/index.d.ts +31 -18
  62. package/dist/index.js +119 -619
  63. package/dist/prompts/index.js +4 -4
  64. package/dist/resources/actors.d.ts +17 -12
  65. package/dist/resources/actors.js +56 -76
  66. package/dist/resources/assets.d.ts +6 -14
  67. package/dist/resources/assets.js +115 -147
  68. package/dist/resources/levels.d.ts +13 -13
  69. package/dist/resources/levels.js +25 -34
  70. package/dist/server/resource-registry.d.ts +20 -0
  71. package/dist/server/resource-registry.js +37 -0
  72. package/dist/server/tool-registry.d.ts +23 -0
  73. package/dist/server/tool-registry.js +322 -0
  74. package/dist/server-setup.d.ts +21 -0
  75. package/dist/server-setup.js +111 -0
  76. package/dist/services/health-monitor.d.ts +34 -0
  77. package/dist/services/health-monitor.js +105 -0
  78. package/dist/services/metrics-server.d.ts +11 -0
  79. package/dist/services/metrics-server.js +105 -0
  80. package/dist/tools/actors.d.ts +147 -9
  81. package/dist/tools/actors.js +350 -311
  82. package/dist/tools/animation.d.ts +135 -4
  83. package/dist/tools/animation.js +510 -411
  84. package/dist/tools/assets.d.ts +117 -19
  85. package/dist/tools/assets.js +259 -284
  86. package/dist/tools/audio.d.ts +102 -42
  87. package/dist/tools/audio.js +272 -685
  88. package/dist/tools/base-tool.d.ts +17 -0
  89. package/dist/tools/base-tool.js +46 -0
  90. package/dist/tools/behavior-tree.d.ts +94 -0
  91. package/dist/tools/behavior-tree.js +39 -0
  92. package/dist/tools/blueprint/helpers.d.ts +29 -0
  93. package/dist/tools/blueprint/helpers.js +182 -0
  94. package/dist/tools/blueprint.d.ts +228 -118
  95. package/dist/tools/blueprint.js +685 -832
  96. package/dist/tools/consolidated-tool-definitions.d.ts +5462 -1781
  97. package/dist/tools/consolidated-tool-definitions.js +829 -496
  98. package/dist/tools/consolidated-tool-handlers.d.ts +2 -1
  99. package/dist/tools/consolidated-tool-handlers.js +211 -1026
  100. package/dist/tools/debug.d.ts +143 -85
  101. package/dist/tools/debug.js +234 -180
  102. package/dist/tools/dynamic-handler-registry.d.ts +11 -0
  103. package/dist/tools/dynamic-handler-registry.js +101 -0
  104. package/dist/tools/editor.d.ts +139 -18
  105. package/dist/tools/editor.js +239 -244
  106. package/dist/tools/engine.d.ts +10 -4
  107. package/dist/tools/engine.js +13 -5
  108. package/dist/tools/environment.d.ts +36 -0
  109. package/dist/tools/environment.js +267 -0
  110. package/dist/tools/foliage.d.ts +105 -14
  111. package/dist/tools/foliage.js +219 -331
  112. package/dist/tools/handlers/actor-handlers.d.ts +3 -0
  113. package/dist/tools/handlers/actor-handlers.js +232 -0
  114. package/dist/tools/handlers/animation-handlers.d.ts +3 -0
  115. package/dist/tools/handlers/animation-handlers.js +185 -0
  116. package/dist/tools/handlers/argument-helper.d.ts +16 -0
  117. package/dist/tools/handlers/argument-helper.js +80 -0
  118. package/dist/tools/handlers/asset-handlers.d.ts +3 -0
  119. package/dist/tools/handlers/asset-handlers.js +496 -0
  120. package/dist/tools/handlers/audio-handlers.d.ts +3 -0
  121. package/dist/tools/handlers/audio-handlers.js +166 -0
  122. package/dist/tools/handlers/blueprint-handlers.d.ts +4 -0
  123. package/dist/tools/handlers/blueprint-handlers.js +358 -0
  124. package/dist/tools/handlers/common-handlers.d.ts +14 -0
  125. package/dist/tools/handlers/common-handlers.js +56 -0
  126. package/dist/tools/handlers/editor-handlers.d.ts +3 -0
  127. package/dist/tools/handlers/editor-handlers.js +119 -0
  128. package/dist/tools/handlers/effect-handlers.d.ts +3 -0
  129. package/dist/tools/handlers/effect-handlers.js +171 -0
  130. package/dist/tools/handlers/environment-handlers.d.ts +3 -0
  131. package/dist/tools/handlers/environment-handlers.js +170 -0
  132. package/dist/tools/handlers/graph-handlers.d.ts +3 -0
  133. package/dist/tools/handlers/graph-handlers.js +90 -0
  134. package/dist/tools/handlers/input-handlers.d.ts +3 -0
  135. package/dist/tools/handlers/input-handlers.js +21 -0
  136. package/dist/tools/handlers/inspect-handlers.d.ts +3 -0
  137. package/dist/tools/handlers/inspect-handlers.js +383 -0
  138. package/dist/tools/handlers/level-handlers.d.ts +3 -0
  139. package/dist/tools/handlers/level-handlers.js +237 -0
  140. package/dist/tools/handlers/lighting-handlers.d.ts +3 -0
  141. package/dist/tools/handlers/lighting-handlers.js +144 -0
  142. package/dist/tools/handlers/performance-handlers.d.ts +3 -0
  143. package/dist/tools/handlers/performance-handlers.js +130 -0
  144. package/dist/tools/handlers/pipeline-handlers.d.ts +3 -0
  145. package/dist/tools/handlers/pipeline-handlers.js +110 -0
  146. package/dist/tools/handlers/sequence-handlers.d.ts +3 -0
  147. package/dist/tools/handlers/sequence-handlers.js +376 -0
  148. package/dist/tools/handlers/system-handlers.d.ts +4 -0
  149. package/dist/tools/handlers/system-handlers.js +506 -0
  150. package/dist/tools/input.d.ts +19 -0
  151. package/dist/tools/input.js +89 -0
  152. package/dist/tools/introspection.d.ts +103 -40
  153. package/dist/tools/introspection.js +425 -568
  154. package/dist/tools/landscape.d.ts +97 -36
  155. package/dist/tools/landscape.js +280 -409
  156. package/dist/tools/level.d.ts +130 -10
  157. package/dist/tools/level.js +639 -675
  158. package/dist/tools/lighting.d.ts +77 -38
  159. package/dist/tools/lighting.js +441 -943
  160. package/dist/tools/logs.d.ts +3 -3
  161. package/dist/tools/logs.js +5 -57
  162. package/dist/tools/materials.d.ts +91 -24
  163. package/dist/tools/materials.js +190 -118
  164. package/dist/tools/niagara.d.ts +149 -39
  165. package/dist/tools/niagara.js +232 -182
  166. package/dist/tools/performance.d.ts +27 -12
  167. package/dist/tools/performance.js +204 -122
  168. package/dist/tools/physics.d.ts +32 -77
  169. package/dist/tools/physics.js +171 -582
  170. package/dist/tools/property-dictionary.d.ts +13 -0
  171. package/dist/tools/property-dictionary.js +82 -0
  172. package/dist/tools/sequence.d.ts +73 -48
  173. package/dist/tools/sequence.js +196 -748
  174. package/dist/tools/tool-definition-utils.d.ts +59 -0
  175. package/dist/tools/tool-definition-utils.js +35 -0
  176. package/dist/tools/ui.d.ts +66 -34
  177. package/dist/tools/ui.js +134 -214
  178. package/dist/types/env.d.ts +0 -3
  179. package/dist/types/env.js +0 -7
  180. package/dist/types/tool-interfaces.d.ts +898 -0
  181. package/dist/types/tool-interfaces.js +2 -0
  182. package/dist/types/tool-types.d.ts +183 -19
  183. package/dist/types/tool-types.js +0 -4
  184. package/dist/unreal-bridge.d.ts +24 -131
  185. package/dist/unreal-bridge.js +364 -1506
  186. package/dist/utils/command-validator.d.ts +9 -0
  187. package/dist/utils/command-validator.js +67 -0
  188. package/dist/utils/elicitation.d.ts +1 -1
  189. package/dist/utils/elicitation.js +12 -15
  190. package/dist/utils/error-handler.d.ts +2 -51
  191. package/dist/utils/error-handler.js +11 -87
  192. package/dist/utils/ini-reader.d.ts +3 -0
  193. package/dist/utils/ini-reader.js +69 -0
  194. package/dist/utils/logger.js +9 -6
  195. package/dist/utils/normalize.d.ts +3 -0
  196. package/dist/utils/normalize.js +56 -0
  197. package/dist/utils/response-factory.d.ts +7 -0
  198. package/dist/utils/response-factory.js +33 -0
  199. package/dist/utils/response-validator.d.ts +3 -24
  200. package/dist/utils/response-validator.js +130 -81
  201. package/dist/utils/result-helpers.d.ts +4 -5
  202. package/dist/utils/result-helpers.js +15 -16
  203. package/dist/utils/safe-json.js +5 -11
  204. package/dist/utils/unreal-command-queue.d.ts +24 -0
  205. package/dist/utils/unreal-command-queue.js +120 -0
  206. package/dist/utils/validation.d.ts +0 -40
  207. package/dist/utils/validation.js +1 -78
  208. package/dist/wasm/index.d.ts +70 -0
  209. package/dist/wasm/index.js +535 -0
  210. package/docs/GraphQL-API.md +888 -0
  211. package/docs/Migration-Guide-v0.5.0.md +692 -0
  212. package/docs/Roadmap.md +53 -0
  213. package/docs/WebAssembly-Integration.md +628 -0
  214. package/docs/editor-plugin-extension.md +370 -0
  215. package/docs/handler-mapping.md +242 -0
  216. package/docs/native-automation-progress.md +128 -0
  217. package/docs/testing-guide.md +423 -0
  218. package/mcp-config-example.json +6 -6
  219. package/package.json +60 -27
  220. package/plugins/McpAutomationBridge/Config/FilterPlugin.ini +8 -0
  221. package/plugins/McpAutomationBridge/McpAutomationBridge.uplugin +64 -0
  222. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/McpAutomationBridge.Build.cs +189 -0
  223. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.cpp +22 -0
  224. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.h +30 -0
  225. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeHelpers.h +1983 -0
  226. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeModule.cpp +72 -0
  227. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSettings.cpp +46 -0
  228. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +581 -0
  229. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +2394 -0
  230. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetQueryHandlers.cpp +300 -0
  231. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetWorkflowHandlers.cpp +2807 -0
  232. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AudioHandlers.cpp +1087 -0
  233. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BehaviorTreeHandlers.cpp +488 -0
  234. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.cpp +643 -0
  235. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.h +31 -0
  236. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +1184 -0
  237. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +5652 -0
  238. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers_List.cpp +152 -0
  239. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ControlHandlers.cpp +2614 -0
  240. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_DebugHandlers.cpp +42 -0
  241. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EditorFunctionHandlers.cpp +1237 -0
  242. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +1701 -0
  243. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +2145 -0
  244. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_FoliageHandlers.cpp +954 -0
  245. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InputHandlers.cpp +209 -0
  246. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InsightsHandlers.cpp +41 -0
  247. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LandscapeHandlers.cpp +1164 -0
  248. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LevelHandlers.cpp +762 -0
  249. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +634 -0
  250. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LogHandlers.cpp +136 -0
  251. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_MaterialGraphHandlers.cpp +494 -0
  252. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraGraphHandlers.cpp +278 -0
  253. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraHandlers.cpp +625 -0
  254. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PerformanceHandlers.cpp +401 -0
  255. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PipelineHandlers.cpp +67 -0
  256. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +735 -0
  257. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PropertyHandlers.cpp +2634 -0
  258. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_RenderHandlers.cpp +189 -0
  259. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.cpp +917 -0
  260. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.h +39 -0
  261. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +2670 -0
  262. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequencerHandlers.cpp +519 -0
  263. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_TestHandlers.cpp +38 -0
  264. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_UiHandlers.cpp +668 -0
  265. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_WorldPartitionHandlers.cpp +346 -0
  266. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +1330 -0
  267. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.h +149 -0
  268. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +783 -0
  269. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSettings.h +115 -0
  270. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSubsystem.h +796 -0
  271. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpConnectionManager.h +117 -0
  272. package/scripts/check-unreal-connection.mjs +19 -0
  273. package/scripts/clean-tmp.js +23 -0
  274. package/scripts/patch-wasm.js +26 -0
  275. package/scripts/run-all-tests.mjs +131 -0
  276. package/scripts/smoke-test.ts +94 -0
  277. package/scripts/sync-mcp-plugin.js +143 -0
  278. package/scripts/test-no-plugin-alternates.mjs +113 -0
  279. package/scripts/validate-server.js +46 -0
  280. package/scripts/verify-automation-bridge.js +200 -0
  281. package/server.json +57 -21
  282. package/src/automation/bridge.ts +558 -0
  283. package/src/automation/connection-manager.ts +130 -0
  284. package/src/automation/handshake.ts +99 -0
  285. package/src/automation/index.ts +2 -0
  286. package/src/automation/message-handler.ts +167 -0
  287. package/src/automation/request-tracker.ts +123 -0
  288. package/src/automation/types.ts +107 -0
  289. package/src/cli.ts +33 -6
  290. package/src/config.ts +73 -0
  291. package/src/constants.ts +12 -0
  292. package/src/graphql/resolvers.ts +1010 -0
  293. package/src/graphql/schema.ts +452 -0
  294. package/src/graphql/server.ts +154 -0
  295. package/src/graphql/types.ts +7 -0
  296. package/src/handlers/resource-handlers.ts +186 -0
  297. package/src/index.ts +152 -663
  298. package/src/prompts/index.ts +4 -4
  299. package/src/resources/actors.ts +58 -76
  300. package/src/resources/assets.ts +147 -134
  301. package/src/resources/levels.ts +28 -33
  302. package/src/server/resource-registry.ts +47 -0
  303. package/src/server/tool-registry.ts +354 -0
  304. package/src/server-setup.ts +148 -0
  305. package/src/services/health-monitor.ts +132 -0
  306. package/src/services/metrics-server.ts +142 -0
  307. package/src/tools/actors.ts +417 -322
  308. package/src/tools/animation.ts +671 -461
  309. package/src/tools/assets.ts +353 -289
  310. package/src/tools/audio.ts +323 -766
  311. package/src/tools/base-tool.ts +52 -0
  312. package/src/tools/behavior-tree.ts +45 -0
  313. package/src/tools/blueprint/helpers.ts +189 -0
  314. package/src/tools/blueprint.ts +787 -965
  315. package/src/tools/consolidated-tool-definitions.ts +993 -515
  316. package/src/tools/consolidated-tool-handlers.ts +272 -1139
  317. package/src/tools/debug.ts +292 -187
  318. package/src/tools/dynamic-handler-registry.ts +151 -0
  319. package/src/tools/editor.ts +309 -246
  320. package/src/tools/engine.ts +14 -3
  321. package/src/tools/environment.ts +287 -0
  322. package/src/tools/foliage.ts +314 -379
  323. package/src/tools/handlers/actor-handlers.ts +271 -0
  324. package/src/tools/handlers/animation-handlers.ts +237 -0
  325. package/src/tools/handlers/argument-helper.ts +142 -0
  326. package/src/tools/handlers/asset-handlers.ts +532 -0
  327. package/src/tools/handlers/audio-handlers.ts +194 -0
  328. package/src/tools/handlers/blueprint-handlers.ts +380 -0
  329. package/src/tools/handlers/common-handlers.ts +87 -0
  330. package/src/tools/handlers/editor-handlers.ts +123 -0
  331. package/src/tools/handlers/effect-handlers.ts +220 -0
  332. package/src/tools/handlers/environment-handlers.ts +183 -0
  333. package/src/tools/handlers/graph-handlers.ts +116 -0
  334. package/src/tools/handlers/input-handlers.ts +28 -0
  335. package/src/tools/handlers/inspect-handlers.ts +450 -0
  336. package/src/tools/handlers/level-handlers.ts +252 -0
  337. package/src/tools/handlers/lighting-handlers.ts +147 -0
  338. package/src/tools/handlers/performance-handlers.ts +132 -0
  339. package/src/tools/handlers/pipeline-handlers.ts +127 -0
  340. package/src/tools/handlers/sequence-handlers.ts +415 -0
  341. package/src/tools/handlers/system-handlers.ts +564 -0
  342. package/src/tools/input.ts +101 -0
  343. package/src/tools/introspection.ts +493 -584
  344. package/src/tools/landscape.ts +394 -489
  345. package/src/tools/level.ts +752 -694
  346. package/src/tools/lighting.ts +583 -984
  347. package/src/tools/logs.ts +9 -57
  348. package/src/tools/materials.ts +231 -121
  349. package/src/tools/niagara.ts +293 -168
  350. package/src/tools/performance.ts +320 -168
  351. package/src/tools/physics.ts +268 -613
  352. package/src/tools/property-dictionary.ts +98 -0
  353. package/src/tools/sequence.ts +255 -815
  354. package/src/tools/tool-definition-utils.ts +35 -0
  355. package/src/tools/ui.ts +207 -283
  356. package/src/types/env.ts +0 -10
  357. package/src/types/tool-interfaces.ts +250 -0
  358. package/src/types/tool-types.ts +243 -21
  359. package/src/unreal-bridge.ts +460 -1550
  360. package/src/utils/command-validator.ts +75 -0
  361. package/src/utils/elicitation.ts +10 -7
  362. package/src/utils/error-handler.ts +14 -90
  363. package/src/utils/ini-reader.ts +86 -0
  364. package/src/utils/logger.ts +8 -3
  365. package/src/utils/normalize.ts +60 -0
  366. package/src/utils/response-factory.ts +39 -0
  367. package/src/utils/response-validator.ts +176 -56
  368. package/src/utils/result-helpers.ts +21 -19
  369. package/src/utils/safe-json.ts +14 -11
  370. package/src/utils/unreal-command-queue.ts +152 -0
  371. package/src/utils/validation.ts +4 -1
  372. package/src/wasm/index.ts +838 -0
  373. package/test-server.mjs +100 -0
  374. package/tests/run-unreal-tool-tests.mjs +242 -14
  375. package/tests/test-animation.mjs +44 -0
  376. package/tests/test-asset-advanced.mjs +82 -0
  377. package/tests/test-asset-errors.mjs +35 -0
  378. package/tests/test-audio.mjs +219 -0
  379. package/tests/test-automation-timeouts.mjs +98 -0
  380. package/tests/test-behavior-tree.mjs +261 -0
  381. package/tests/test-blueprint-events.mjs +35 -0
  382. package/tests/test-blueprint-graph.mjs +79 -0
  383. package/tests/test-blueprint.mjs +577 -0
  384. package/tests/test-client-mode.mjs +86 -0
  385. package/tests/test-console-command.mjs +56 -0
  386. package/tests/test-control-actor.mjs +425 -0
  387. package/tests/test-control-editor.mjs +80 -0
  388. package/tests/test-extra-tools.mjs +38 -0
  389. package/tests/test-graphql.mjs +322 -0
  390. package/tests/test-inspect.mjs +72 -0
  391. package/tests/test-landscape.mjs +60 -0
  392. package/tests/test-manage-asset.mjs +438 -0
  393. package/tests/test-manage-level.mjs +70 -0
  394. package/tests/test-materials.mjs +356 -0
  395. package/tests/test-niagara.mjs +185 -0
  396. package/tests/test-no-inline-python.mjs +122 -0
  397. package/tests/test-plugin-handshake.mjs +82 -0
  398. package/tests/test-render.mjs +33 -0
  399. package/tests/test-runner.mjs +933 -0
  400. package/tests/test-search-assets.mjs +66 -0
  401. package/tests/test-sequence.mjs +68 -0
  402. package/tests/test-system.mjs +57 -0
  403. package/tests/test-wasm.mjs +193 -0
  404. package/tests/test-world-partition.mjs +215 -0
  405. package/tsconfig.json +3 -3
  406. package/wasm/Cargo.lock +363 -0
  407. package/wasm/Cargo.toml +42 -0
  408. package/wasm/LICENSE +21 -0
  409. package/wasm/README.md +253 -0
  410. package/wasm/src/dependency_resolver.rs +377 -0
  411. package/wasm/src/lib.rs +153 -0
  412. package/wasm/src/property_parser.rs +271 -0
  413. package/wasm/src/transform_math.rs +396 -0
  414. package/wasm/tests/integration.rs +109 -0
  415. package/.github/workflows/smithery-build.yml +0 -29
  416. package/dist/tools/build_environment_advanced.d.ts +0 -65
  417. package/dist/tools/build_environment_advanced.js +0 -633
  418. package/dist/tools/rc.d.ts +0 -110
  419. package/dist/tools/rc.js +0 -437
  420. package/dist/tools/visual.d.ts +0 -40
  421. package/dist/tools/visual.js +0 -282
  422. package/dist/utils/http.d.ts +0 -6
  423. package/dist/utils/http.js +0 -151
  424. package/dist/utils/python-output.d.ts +0 -18
  425. package/dist/utils/python-output.js +0 -290
  426. package/dist/utils/python.d.ts +0 -2
  427. package/dist/utils/python.js +0 -4
  428. package/dist/utils/stdio-redirect.d.ts +0 -2
  429. package/dist/utils/stdio-redirect.js +0 -20
  430. package/docs/unreal-tool-test-cases.md +0 -574
  431. package/smithery.yaml +0 -29
  432. package/src/tools/build_environment_advanced.ts +0 -732
  433. package/src/tools/rc.ts +0 -515
  434. package/src/tools/visual.ts +0 -281
  435. package/src/utils/http.ts +0 -187
  436. package/src/utils/python-output.ts +0 -351
  437. package/src/utils/python.ts +0 -3
  438. package/src/utils/stdio-redirect.ts +0 -18
@@ -1,6 +1,5 @@
1
- import { UnrealBridge } from '../unreal-bridge.js';
2
- import { Logger } from '../utils/logger.js';
3
- import { interpretStandardResult } from '../utils/result-helpers.js';
1
+ import { BaseTool } from './base-tool.js';
2
+ import { ISequenceTools } from '../types/tool-interfaces.js';
4
3
 
5
4
  export interface LevelSequence {
6
5
  path: string;
@@ -23,896 +22,337 @@ export interface SequenceTrack {
23
22
  sections?: any[];
24
23
  }
25
24
 
26
- export class SequenceTools {
27
- private log = new Logger('SequenceTools');
25
+ export class SequenceTools extends BaseTool implements ISequenceTools {
28
26
  private sequenceCache = new Map<string, LevelSequence>();
29
- private retryAttempts = 3;
30
- private retryDelay = 1000;
31
-
32
- constructor(private bridge: UnrealBridge) {}
33
-
34
- private async ensureSequencerPrerequisites(operation: string): Promise<string[] | null> {
35
- const missing = await this.bridge.ensurePluginsEnabled(['LevelSequenceEditor', 'Sequencer'], operation);
36
- return missing.length ? missing : null;
27
+ private activeSequencePath?: string;
28
+
29
+ private resolveSequencePath(explicitPath?: unknown): string | undefined {
30
+ if (typeof explicitPath === 'string' && explicitPath.trim().length > 0) {
31
+ return explicitPath.trim();
37
32
  }
33
+ return this.activeSequencePath;
34
+ }
38
35
 
39
- /**
40
- * Execute with retry logic for transient failures
41
- */
42
- private async executeWithRetry<T>(
43
- operation: () => Promise<T>,
44
- operationName: string
45
- ): Promise<T> {
46
- let lastError: any;
47
-
48
- for (let attempt = 1; attempt <= this.retryAttempts; attempt++) {
49
- try {
50
- return await operation();
51
- } catch (error: any) {
52
- lastError = error;
53
- this.log.warn(`${operationName} attempt ${attempt} failed: ${error.message || error}`);
54
-
55
- if (attempt < this.retryAttempts) {
56
- await new Promise(resolve =>
57
- setTimeout(resolve, this.retryDelay * attempt)
58
- );
59
- }
60
- }
36
+ private async sendAction(action: string, payload: Record<string, unknown> = {}, timeoutMs?: number) {
37
+ const envDefault = Number(process.env.MCP_AUTOMATION_REQUEST_TIMEOUT_MS ?? '120000');
38
+ const defaultTimeout = Number.isFinite(envDefault) && envDefault > 0 ? envDefault : 120000;
39
+ const finalTimeout = typeof timeoutMs === 'number' && timeoutMs > 0 ? timeoutMs : defaultTimeout;
40
+
41
+ try {
42
+ const response = await this.sendAutomationRequest(
43
+ action,
44
+ payload,
45
+ { timeoutMs: finalTimeout, waitForEvent: false }
46
+ );
47
+
48
+ const success = response && response.success !== false;
49
+ const result = response.result ?? response;
50
+
51
+ return { success, message: response.message ?? undefined, error: response.success === false ? (response.error ?? response.message) : undefined, result, requestId: response.requestId } as any;
52
+ } catch (err: any) {
53
+ return { success: false, error: String(err), message: String(err) } as const;
61
54
  }
62
-
63
- throw lastError;
64
55
  }
65
56
 
66
- /**
67
- * Parse Python execution result with better error handling
68
- */
69
- private parsePythonResult(resp: unknown, operationName: string): any {
70
- const interpreted = interpretStandardResult(resp, {
71
- successMessage: `${operationName} succeeded`,
72
- failureMessage: `${operationName} failed`
73
- });
74
-
75
- if (interpreted.success) {
76
- return {
77
- ...interpreted.payload,
78
- success: true
79
- };
80
- }
81
-
82
- const baseError = interpreted.error ?? `${operationName} did not return a valid result`;
83
- const rawOutput = interpreted.rawText ?? '';
84
- const cleanedOutput = interpreted.cleanText && interpreted.cleanText.trim().length > 0
85
- ? interpreted.cleanText.trim()
86
- : baseError;
87
-
88
- if (rawOutput.includes('ModuleNotFoundError')) {
89
- return { success: false, error: 'Sequencer module not available. Ensure Sequencer is enabled.' };
90
- }
91
- if (rawOutput.includes('AttributeError')) {
92
- return { success: false, error: 'Sequencer API method not found. Check Unreal Engine version compatibility.' };
93
- }
94
-
95
- this.log.error(`${operationName} returned no parsable result: ${cleanedOutput}`);
96
- return {
97
- success: false,
98
- error: (() => {
99
- const detail = cleanedOutput === baseError
100
- ? ''
101
- : (cleanedOutput ?? '').substring(0, 200).trim();
102
- return detail ? `${baseError}: ${detail}` : baseError;
103
- })()
104
- };
57
+ private isUnknownActionResponse(res: any): boolean {
58
+ if (!res) return false;
59
+ const txt = String((res.error ?? res.message ?? '')).toLowerCase();
60
+ // Only treat specific error codes as "not implemented"
61
+ return txt.includes('unknown_action') || txt.includes('unknown automation action') || txt.includes('not_implemented') || txt === 'unknown_plugin_action';
105
62
  }
106
63
 
107
- async create(params: { name: string; path?: string }) {
64
+ async create(params: { name: string; path?: string; timeoutMs?: number }) {
108
65
  const name = params.name?.trim();
109
66
  const base = (params.path || '/Game/Sequences').replace(/\/$/, '');
110
67
  if (!name) return { success: false, error: 'name is required' };
111
- const missingPlugins = await this.ensureSequencerPrerequisites('SequenceTools.create');
112
- if (missingPlugins?.length) {
113
- const sequencePath = `${base}/${name}`;
114
- this.log.warn('Sequencer plugins missing for create; returning simulated success', { missingPlugins, sequencePath });
115
- return {
116
- success: true,
117
- simulated: true,
118
- sequencePath,
119
- message: 'Sequencer plugins disabled; reported simulated sequence creation.',
120
- warnings: [`Sequence asset reported without creating on disk because required plugins are disabled: ${missingPlugins.join(', ')}`]
121
- };
122
- }
123
- const py = `
124
- import unreal, json
125
- name = r"${name}"
126
- base = r"${base}"
127
- full = f"{base}/{name}"
128
- try:
129
- # Ensure directory exists
130
- try:
131
- if not unreal.EditorAssetLibrary.does_directory_exist(base):
132
- unreal.EditorAssetLibrary.make_directory(base)
133
- except Exception:
134
- pass
135
-
136
- if unreal.EditorAssetLibrary.does_asset_exist(full):
137
- print('RESULT:' + json.dumps({'success': True, 'sequencePath': full, 'existing': True}))
138
- else:
139
- asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
140
- factory = unreal.LevelSequenceFactoryNew()
141
- seq = asset_tools.create_asset(asset_name=name, package_path=base, asset_class=unreal.LevelSequence, factory=factory)
142
- if seq:
143
- unreal.EditorAssetLibrary.save_asset(full)
144
- print('RESULT:' + json.dumps({'success': True, 'sequencePath': full}))
145
- else:
146
- print('RESULT:' + json.dumps({'success': False, 'error': 'Create returned None'}))
147
- except Exception as e:
148
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
149
- `.trim();
150
- const resp = await this.executeWithRetry(
151
- () => this.bridge.executePython(py),
152
- 'createSequence'
153
- );
154
-
155
- const result = this.parsePythonResult(resp, 'createSequence');
156
-
157
- // Cache the sequence if successful
158
- if (result.success && result.sequencePath) {
159
- const sequence: LevelSequence = {
160
- path: result.sequencePath,
161
- name: name
162
- };
68
+
69
+ const payload = { name, path: base } as Record<string, unknown>;
70
+ const resp = await this.sendAction('sequence_create', payload, params.timeoutMs);
71
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
72
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_create' } as const;
73
+ }
74
+ if (resp.success && resp.result && resp.result.sequencePath) {
75
+ const sequence: LevelSequence = { path: resp.result.sequencePath, name };
163
76
  this.sequenceCache.set(sequence.path, sequence);
77
+ return { ...resp, sequence: resp.result.sequencePath };
164
78
  }
165
-
166
- return result;
79
+ return resp;
167
80
  }
168
81
 
169
82
  async open(params: { path: string }) {
170
- const missingPlugins = await this.ensureSequencerPrerequisites('SequenceTools.open');
171
- if (missingPlugins) {
172
- return {
173
- success: false,
174
- error: `Required Unreal plugins are not enabled: ${missingPlugins.join(', ')}`
175
- };
176
- }
177
- const py = `
178
- import unreal, json
179
- path = r"${params.path}"
180
- try:
181
- seq = unreal.load_asset(path)
182
- if not seq:
183
- print('RESULT:' + json.dumps({'success': False, 'error': 'Sequence not found'}))
184
- else:
185
- unreal.LevelSequenceEditorBlueprintLibrary.open_level_sequence(seq)
186
- print('RESULT:' + json.dumps({'success': True, 'sequencePath': path}))
187
- except Exception as e:
188
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
189
- `.trim();
190
- const resp = await this.executeWithRetry(
191
- () => this.bridge.executePython(py),
192
- 'openSequence'
193
- );
194
-
195
- return this.parsePythonResult(resp, 'openSequence');
83
+ const path = params.path?.trim();
84
+ const resp = await this.sendAction('sequence_open', { path });
85
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
86
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_open' } as const;
87
+ }
88
+ if (resp && resp.success !== false && path) {
89
+ this.activeSequencePath = path;
90
+ }
91
+ return resp;
196
92
  }
197
93
 
198
- async addCamera(params: { spawnable?: boolean }) {
199
- const missingPlugins = await this.ensureSequencerPrerequisites('SequenceTools.addCamera');
200
- if (missingPlugins?.length) {
201
- this.log.warn('Sequencer plugins missing for addCamera; returning simulated success', { missingPlugins });
202
- return {
203
- success: true,
204
- simulated: true,
205
- cameraBindingId: 'simulated_camera',
206
- cameraName: 'SimulatedCamera',
207
- warnings: [`Camera binding simulated because required plugins are disabled: ${missingPlugins.join(', ')}`]
208
- };
209
- }
210
- const py = `
211
- import unreal, json
212
- try:
213
- ls = unreal.get_editor_subsystem(unreal.LevelSequenceEditorSubsystem)
214
- if not ls:
215
- print('RESULT:' + json.dumps({'success': False, 'error': 'LevelSequenceEditorSubsystem unavailable'}))
216
- else:
217
- # create_camera returns tuple: (binding_proxy, camera_actor)
218
- result = ls.create_camera(spawnable=${params.spawnable !== false ? 'True' : 'False'})
219
- binding_id = ''
220
- camera_name = ''
221
-
222
- if result and len(result) >= 2:
223
- binding_proxy = result[0]
224
- camera_actor = result[1]
225
-
226
- # Get the current sequence
227
- seq = unreal.LevelSequenceEditorBlueprintLibrary.get_focused_level_sequence()
228
-
229
- if seq and binding_proxy:
230
- try:
231
- # Get GUID directly from binding proxy - this is more reliable
232
- binding_guid = unreal.MovieSceneBindingExtensions.get_id(binding_proxy)
233
- # The GUID itself is what we need
234
- binding_id = str(binding_guid).replace('<Guid ', '').replace('>', '').split(' ')[0] if str(binding_guid).startswith('<') else str(binding_guid)
235
-
236
- # If that didn't work, try the binding object
237
- if binding_id.startswith('<') or not binding_id:
238
- binding_obj = unreal.MovieSceneSequenceExtensions.get_binding_id(seq, binding_proxy)
239
- # Try to extract GUID from the object representation
240
- obj_str = str(binding_obj)
241
- if 'guid=' in obj_str:
242
- binding_id = obj_str.split('guid=')[1].split(',')[0].split('}')[0].strip()
243
- elif hasattr(binding_obj, 'guid'):
244
- binding_id = str(binding_obj.guid)
245
- else:
246
- # Use a hash of the binding for a consistent ID
247
- import hashlib
248
- binding_id = hashlib.md5(str(binding_proxy).encode()).hexdigest()[:8]
249
- except Exception as e:
250
- # Generate a unique ID based on camera
251
- import hashlib
252
- camera_str = camera_actor.get_name() if camera_actor else 'spawned'
253
- binding_id = f'cam_{hashlib.md5(camera_str.encode()).hexdigest()[:8]}'
254
-
255
- if camera_actor:
256
- try:
257
- camera_name = camera_actor.get_actor_label()
258
- except:
259
- camera_name = 'CineCamera'
260
-
261
- print('RESULT:' + json.dumps({
262
- 'success': True,
263
- 'cameraBindingId': binding_id,
264
- 'cameraName': camera_name
265
- }))
266
- else:
267
- # Even if result format is different, camera might still be created
268
- print('RESULT:' + json.dumps({
269
- 'success': True,
270
- 'cameraBindingId': 'camera_created',
271
- 'warning': 'Camera created but binding format unexpected'
272
- }))
273
- except Exception as e:
274
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
275
- `.trim();
276
- const resp = await this.executeWithRetry(
277
- () => this.bridge.executePython(py),
278
- 'addCamera'
279
- );
280
-
281
- return this.parsePythonResult(resp, 'addCamera');
94
+ async addCamera(params: { spawnable?: boolean; path?: string }) {
95
+ const path = this.resolveSequencePath(params.path);
96
+ const resp = await this.sendAction('sequence_add_camera', { path, spawnable: params.spawnable !== false });
97
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
98
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_add_camera' } as const;
99
+ }
100
+ return resp;
282
101
  }
283
102
 
284
- async addActor(params: { actorName: string; createBinding?: boolean }) {
285
- const missingPlugins = await this.ensureSequencerPrerequisites('SequenceTools.addActor');
286
- if (missingPlugins) {
287
- return {
288
- success: false,
289
- error: `Required Unreal plugins are not enabled: ${missingPlugins.join(', ')}`
290
- };
291
- }
292
- const py = `
293
- import unreal, json
294
- try:
295
- actor_sub = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
296
- ls = unreal.get_editor_subsystem(unreal.LevelSequenceEditorSubsystem)
297
- if not ls or not actor_sub:
298
- print('RESULT:' + json.dumps({'success': False, 'error': 'Subsystem unavailable'}))
299
- else:
300
- target = None
301
- actors = actor_sub.get_all_level_actors()
302
- for a in actors:
303
- if not a: continue
304
- label = a.get_actor_label()
305
- name = a.get_name()
306
- # Check label, name, and partial matches
307
- if label == r"${params.actorName}" or name == r"${params.actorName}" or label.startswith(r"${params.actorName}"):
308
- target = a
309
- break
310
-
311
- if not target:
312
- # Try to find any actors to debug
313
- actor_info = []
314
- for a in actors[:5]:
315
- if a:
316
- actor_info.append({'label': a.get_actor_label(), 'name': a.get_name()})
317
- print('RESULT:' + json.dumps({'success': False, 'error': f'Actor "${params.actorName}" not found. Sample actors: {actor_info}'}))
318
- else:
319
- # Make sure we have a focused sequence
320
- seq = unreal.LevelSequenceEditorBlueprintLibrary.get_focused_level_sequence()
321
- if seq:
322
- # Use add_actors method which returns binding proxies
323
- bindings = ls.add_actors([target])
324
- binding_info = []
325
-
326
- # bindings might be a list or might be empty if actor already exists
327
- if bindings and len(bindings) > 0:
328
- for binding in bindings:
329
- try:
330
- # Get binding name and GUID
331
- binding_name = unreal.MovieSceneBindingExtensions.get_name(binding)
332
- binding_guid = unreal.MovieSceneBindingExtensions.get_id(binding)
333
-
334
- # Extract clean GUID string
335
- guid_str = str(binding_guid)
336
- if guid_str.startswith('<Guid '):
337
- # Extract the actual GUID value from <Guid 'XXXX-XXXX-XXXX-XXXX'>
338
- guid_clean = guid_str.replace('<Guid ', '').replace('>', '').replace("'", '').split(' ')[0]
339
- else:
340
- guid_clean = guid_str
341
-
342
- binding_info.append({
343
- 'id': guid_clean,
344
- 'guid': guid_clean,
345
- 'name': binding_name if binding_name else target.get_actor_label()
346
- })
347
- except Exception as e:
348
- # If binding methods fail, still count it
349
- binding_info.append({
350
- 'id': 'binding_' + str(len(binding_info)),
351
- 'name': target.get_actor_label(),
352
- 'error': str(e)
353
- })
354
-
355
- print('RESULT:' + json.dumps({
356
- 'success': True,
357
- 'count': len(bindings),
358
- 'actorAdded': target.get_actor_label(),
359
- 'bindings': binding_info
360
- }))
361
- else:
362
- # Actor was likely added but no new binding returned (might already exist)
363
- # Still report success since the actor is in the sequence
364
- print('RESULT:' + json.dumps({
365
- 'success': True,
366
- 'count': 1,
367
- 'actorAdded': target.get_actor_label(),
368
- 'bindings': [{'name': target.get_actor_label(), 'note': 'Actor added to sequence'}],
369
- 'info': 'Actor processed successfully'
370
- }))
371
- else:
372
- print('RESULT:' + json.dumps({'success': False, 'error': 'No sequence is currently focused'}))
373
- except Exception as e:
374
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
375
- `.trim();
376
- const resp = await this.executeWithRetry(
377
- () => this.bridge.executePython(py),
378
- 'addActor'
379
- );
380
-
381
- return this.parsePythonResult(resp, 'addActor');
103
+ async addActor(params: { actorName: string; createBinding?: boolean; path?: string }) {
104
+ const path = this.resolveSequencePath(params.path);
105
+ const resp = await this.sendAction('sequence_add_actor', { path, actorName: params.actorName, createBinding: params.createBinding });
106
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
107
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_add_actor' } as const;
108
+ }
109
+ return resp;
382
110
  }
383
111
 
384
112
  /**
385
113
  * Play the current level sequence
386
114
  */
387
- async play(params?: { startTime?: number; loopMode?: 'once' | 'loop' | 'pingpong' }) {
388
- const loop = params?.loopMode || '';
389
- const missingPlugins = await this.ensureSequencerPrerequisites('SequenceTools.play');
390
- if (missingPlugins) {
391
- this.log.warn('Sequencer plugins missing for play; returning simulated success', { missingPlugins, loopMode: loop });
392
- return {
393
- success: true,
394
- simulated: true,
395
- playing: true,
396
- loopMode: loop || 'default',
397
- warnings: [`Playback simulated because required plugins are disabled: ${missingPlugins.join(', ')}`],
398
- message: 'Sequencer plugins disabled; playback simulated.'
399
- };
400
- }
401
- const py = `
402
- import unreal, json
403
-
404
- # Helper to resolve SequencerLoopMode from a friendly string
405
- def _resolve_loop_mode(mode_str):
406
- try:
407
- m = str(mode_str).lower()
408
- slm = unreal.SequencerLoopMode
409
- if m in ('once','noloop','no_loop'):
410
- return getattr(slm, 'SLM_NoLoop', getattr(slm, 'NoLoop'))
411
- if m in ('loop',):
412
- return getattr(slm, 'SLM_Loop', getattr(slm, 'Loop'))
413
- if m in ('pingpong','ping_pong'):
414
- return getattr(slm, 'SLM_PingPong', getattr(slm, 'PingPong'))
415
- except Exception:
416
- pass
417
- return None
418
-
419
- try:
420
- unreal.LevelSequenceEditorBlueprintLibrary.play()
421
- loop_mode = _resolve_loop_mode('${loop}')
422
- if loop_mode is not None:
423
- unreal.LevelSequenceEditorBlueprintLibrary.set_loop_mode(loop_mode)
424
- print('RESULT:' + json.dumps({'success': True, 'playing': True, 'loopMode': '${loop || 'default'}'}))
425
- except Exception as e:
426
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
427
- `.trim();
428
-
429
- const resp = await this.executeWithRetry(
430
- () => this.bridge.executePython(py),
431
- 'playSequence'
432
- );
433
-
434
- return this.parsePythonResult(resp, 'playSequence');
115
+ async play(params?: { path?: string; startTime?: number; loopMode?: 'once' | 'loop' | 'pingpong' }) {
116
+ const path = this.resolveSequencePath(params?.path);
117
+ const resp = await this.sendAction('sequence_play', { path, startTime: params?.startTime, loopMode: params?.loopMode });
118
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
119
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_play' } as const;
120
+ }
121
+ return resp;
435
122
  }
436
123
 
437
124
  /**
438
125
  * Pause the current level sequence
439
126
  */
440
- async pause() {
441
- const missingPlugins = await this.ensureSequencerPrerequisites('SequenceTools.pause');
442
- if (missingPlugins) {
443
- return {
444
- success: false,
445
- error: `Required Unreal plugins are not enabled: ${missingPlugins.join(', ')}`
446
- };
447
- }
448
- const py = `
449
- import unreal, json
450
- try:
451
- unreal.LevelSequenceEditorBlueprintLibrary.pause()
452
- print('RESULT:' + json.dumps({'success': True, 'paused': True}))
453
- except Exception as e:
454
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
455
- `.trim();
456
-
457
- const resp = await this.executeWithRetry(
458
- () => this.bridge.executePython(py),
459
- 'pauseSequence'
460
- );
461
-
462
- return this.parsePythonResult(resp, 'pauseSequence');
127
+ async pause(params?: { path?: string }) {
128
+ const path = this.resolveSequencePath(params?.path);
129
+ const resp = await this.sendAction('sequence_pause', { path });
130
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
131
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_pause' } as const;
132
+ }
133
+ return resp;
463
134
  }
464
135
 
465
136
  /**
466
137
  * Stop/close the current level sequence
467
138
  */
468
- async stop() {
469
- const missingPlugins = await this.ensureSequencerPrerequisites('SequenceTools.stop');
470
- if (missingPlugins) {
471
- return {
472
- success: false,
473
- error: `Required Unreal plugins are not enabled: ${missingPlugins.join(', ')}`
474
- };
475
- }
476
- const py = `
477
- import unreal, json
478
- try:
479
- unreal.LevelSequenceEditorBlueprintLibrary.close_level_sequence()
480
- print('RESULT:' + json.dumps({'success': True, 'stopped': True}))
481
- except Exception as e:
482
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
483
- `.trim();
484
-
485
- const resp = await this.executeWithRetry(
486
- () => this.bridge.executePython(py),
487
- 'stopSequence'
488
- );
489
-
490
- return this.parsePythonResult(resp, 'stopSequence');
139
+ async stop(params?: { path?: string }) {
140
+ const path = this.resolveSequencePath(params?.path);
141
+ const resp = await this.sendAction('sequence_stop', { path });
142
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
143
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_stop' } as const;
144
+ }
145
+ return resp;
491
146
  }
492
147
 
493
148
  /**
494
149
  * Set sequence properties including frame rate and length
495
150
  */
496
- async setSequenceProperties(params: {
151
+ async setSequenceProperties(params: {
497
152
  path?: string;
498
153
  frameRate?: number;
499
154
  lengthInFrames?: number;
500
155
  playbackStart?: number;
501
156
  playbackEnd?: number;
502
157
  }) {
503
- const missingPlugins = await this.ensureSequencerPrerequisites('SequenceTools.setSequenceProperties');
504
- if (missingPlugins) {
505
- this.log.warn('Sequencer plugins missing for setSequenceProperties; returning simulated success', { missingPlugins, params });
506
- const changes: Array<Record<string, unknown>> = [];
507
- if (typeof params.frameRate === 'number') {
508
- changes.push({ property: 'frameRate', value: params.frameRate });
509
- }
510
- if (typeof params.lengthInFrames === 'number') {
511
- changes.push({ property: 'lengthInFrames', value: params.lengthInFrames });
512
- }
513
- if (params.playbackStart !== undefined || params.playbackEnd !== undefined) {
514
- changes.push({
515
- property: 'playbackRange',
516
- start: params.playbackStart,
517
- end: params.playbackEnd
518
- });
519
- }
520
- return {
521
- success: true,
522
- simulated: true,
523
- message: 'Sequencer plugins disabled; property update simulated.',
524
- warnings: [`Property update simulated because required plugins are disabled: ${missingPlugins.join(', ')}`],
525
- changes,
526
- finalProperties: {
527
- frameRate: params.frameRate ? { numerator: params.frameRate, denominator: 1 } : undefined,
528
- playbackStart: params.playbackStart,
529
- playbackEnd: params.playbackEnd,
530
- duration: params.lengthInFrames
531
- }
532
- };
533
- }
534
- const py = `
535
- import unreal, json
536
- try:
537
- # Load the sequence
538
- seq_path = r"${params.path || ''}"
539
- if seq_path:
540
- seq = unreal.load_asset(seq_path)
541
- else:
542
- # Try to get the currently open sequence
543
- seq = unreal.LevelSequenceEditorBlueprintLibrary.get_focused_level_sequence()
544
-
545
- if not seq:
546
- print('RESULT:' + json.dumps({'success': False, 'error': 'No sequence found or loaded'}))
547
- else:
548
- result = {'success': True, 'changes': []}
549
-
550
- # Set frame rate if provided
551
- ${params.frameRate ? `
552
- frame_rate = unreal.FrameRate(numerator=${params.frameRate}, denominator=1)
553
- unreal.MovieSceneSequenceExtensions.set_display_rate(seq, frame_rate)
554
- result['changes'].append({'property': 'frameRate', 'value': ${params.frameRate}})
555
- ` : ''}
556
-
557
- # Set playback range if provided
558
- ${(params.playbackStart !== undefined || params.playbackEnd !== undefined) ? `
559
- current_range = unreal.MovieSceneSequenceExtensions.get_playback_range(seq)
560
- start = ${params.playbackStart !== undefined ? params.playbackStart : 'current_range.get_start_frame()'}
561
- end = ${params.playbackEnd !== undefined ? params.playbackEnd : 'current_range.get_end_frame()'}
562
- # Use set_playback_start and set_playback_end instead
563
- if ${params.playbackStart !== undefined}:
564
- unreal.MovieSceneSequenceExtensions.set_playback_start(seq, ${params.playbackStart})
565
- if ${params.playbackEnd !== undefined}:
566
- unreal.MovieSceneSequenceExtensions.set_playback_end(seq, ${params.playbackEnd})
567
- result['changes'].append({'property': 'playbackRange', 'start': start, 'end': end})
568
- ` : ''}
569
-
570
- # Set total length in frames if provided
571
- ${params.lengthInFrames ? `
572
- # This sets the playback end to match the desired length
573
- start = unreal.MovieSceneSequenceExtensions.get_playback_start(seq)
574
- end = start + ${params.lengthInFrames}
575
- unreal.MovieSceneSequenceExtensions.set_playback_end(seq, end)
576
- result['changes'].append({'property': 'lengthInFrames', 'value': ${params.lengthInFrames}})
577
- ` : ''}
578
-
579
- # Get final properties for confirmation
580
- final_rate = unreal.MovieSceneSequenceExtensions.get_display_rate(seq)
581
- final_range = unreal.MovieSceneSequenceExtensions.get_playback_range(seq)
582
- result['finalProperties'] = {
583
- 'frameRate': {'numerator': final_rate.numerator, 'denominator': final_rate.denominator},
584
- 'playbackStart': final_range.get_start_frame(),
585
- 'playbackEnd': final_range.get_end_frame(),
586
- 'duration': final_range.get_end_frame() - final_range.get_start_frame()
587
- }
588
-
589
- print('RESULT:' + json.dumps(result))
590
- except Exception as e:
591
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
592
- `.trim();
593
-
594
- const resp = await this.executeWithRetry(
595
- () => this.bridge.executePython(py),
596
- 'setSequenceProperties'
597
- );
598
-
599
- return this.parsePythonResult(resp, 'setSequenceProperties');
158
+ const payload: Record<string, unknown> = {
159
+ path: params.path,
160
+ frameRate: params.frameRate,
161
+ lengthInFrames: params.lengthInFrames,
162
+ playbackStart: params.playbackStart,
163
+ playbackEnd: params.playbackEnd
164
+ };
165
+ const resp = await this.sendAction('sequence_set_properties', payload);
166
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
167
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_set_properties' } as const;
168
+ }
169
+ return resp;
170
+ }
171
+
172
+ /**
173
+ * Set display rate (fps)
174
+ */
175
+ async setDisplayRate(params: { path?: string; frameRate: string | number }) {
176
+ const resp = await this.sendAction('sequence_set_display_rate', { path: params.path, frameRate: params.frameRate });
177
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
178
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_set_display_rate' } as const;
179
+ }
180
+ return resp;
600
181
  }
601
182
 
602
183
  /**
603
184
  * Get sequence properties
604
185
  */
605
186
  async getSequenceProperties(params: { path?: string }) {
606
- const py = `
607
- import unreal, json
608
- try:
609
- # Load the sequence
610
- seq_path = r"${params.path || ''}"
611
- if seq_path:
612
- seq = unreal.load_asset(seq_path)
613
- else:
614
- # Try to get the currently open sequence
615
- seq = unreal.LevelSequenceEditorBlueprintLibrary.get_focused_level_sequence()
616
-
617
- if not seq:
618
- print('RESULT:' + json.dumps({'success': False, 'error': 'No sequence found or loaded'}))
619
- else:
620
- # Get all properties
621
- display_rate = unreal.MovieSceneSequenceExtensions.get_display_rate(seq)
622
- playback_range = unreal.MovieSceneSequenceExtensions.get_playback_range(seq)
623
-
624
- # Get marked frames if any
625
- marked_frames = []
626
- try:
627
- frames = unreal.MovieSceneSequenceExtensions.get_marked_frames(seq)
628
- marked_frames = [{'frame': f.frame_number.value, 'label': f.label} for f in frames]
629
- except:
630
- pass
631
-
632
- result = {
633
- 'success': True,
634
- 'path': seq.get_path_name(),
635
- 'name': seq.get_name(),
636
- 'frameRate': {
637
- 'numerator': display_rate.numerator,
638
- 'denominator': display_rate.denominator,
639
- 'fps': float(display_rate.numerator) / float(display_rate.denominator) if display_rate.denominator > 0 else 0
640
- },
641
- 'playbackStart': playback_range.get_start_frame(),
642
- 'playbackEnd': playback_range.get_end_frame(),
643
- 'duration': playback_range.get_end_frame() - playback_range.get_start_frame(),
644
- 'markedFrames': marked_frames
645
- }
646
-
647
- print('RESULT:' + json.dumps(result))
648
- except Exception as e:
649
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
650
- `.trim();
651
-
652
- const resp = await this.executeWithRetry(
653
- () => this.bridge.executePython(py),
654
- 'getSequenceProperties'
655
- );
656
-
657
- return this.parsePythonResult(resp, 'getSequenceProperties');
187
+ const resp = await this.sendAction('sequence_get_properties', { path: params.path });
188
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
189
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_get_properties' } as const;
190
+ }
191
+ return resp;
658
192
  }
659
193
 
660
194
  /**
661
195
  * Set playback speed/rate
662
196
  */
663
- async setPlaybackSpeed(params: { speed: number }) {
664
- const py = `
665
- import unreal, json
666
- try:
667
- unreal.LevelSequenceEditorBlueprintLibrary.set_playback_speed(${params.speed})
668
- print('RESULT:' + json.dumps({'success': True, 'playbackSpeed': ${params.speed}}))
669
- except Exception as e:
670
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
671
- `.trim();
672
-
673
- const resp = await this.executeWithRetry(
674
- () => this.bridge.executePython(py),
675
- 'setPlaybackSpeed'
676
- );
677
-
678
- return this.parsePythonResult(resp, 'setPlaybackSpeed');
197
+ async setPlaybackSpeed(params: { speed: number; path?: string }) {
198
+ const path = this.resolveSequencePath(params.path);
199
+ const resp = await this.sendAction('sequence_set_playback_speed', { path, speed: params.speed });
200
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
201
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_set_playback_speed' } as const;
202
+ }
203
+ return resp;
679
204
  }
680
205
 
681
206
  /**
682
207
  * Get all bindings in the current sequence
683
208
  */
684
209
  async getBindings(params?: { path?: string }) {
685
- const py = `
686
- import unreal, json
687
- try:
688
- # Load the sequence
689
- seq_path = r"${params?.path || ''}"
690
- if seq_path:
691
- seq = unreal.load_asset(seq_path)
692
- else:
693
- # Try to get the currently open sequence
694
- seq = unreal.LevelSequenceEditorBlueprintLibrary.get_focused_level_sequence()
695
-
696
- if not seq:
697
- print('RESULT:' + json.dumps({'success': False, 'error': 'No sequence found or loaded'}))
698
- else:
699
- bindings = unreal.MovieSceneSequenceExtensions.get_bindings(seq)
700
- binding_list = []
701
- for binding in bindings:
702
- try:
703
- binding_name = unreal.MovieSceneBindingExtensions.get_name(binding)
704
- binding_guid = unreal.MovieSceneBindingExtensions.get_id(binding)
705
-
706
- # Extract clean GUID string
707
- guid_str = str(binding_guid)
708
- if guid_str.startswith('<Guid '):
709
- # Extract the actual GUID value from <Guid 'XXXX-XXXX-XXXX-XXXX'>
710
- guid_clean = guid_str.replace('<Guid ', '').replace('>', '').replace("'", '').split(' ')[0]
711
- else:
712
- guid_clean = guid_str
713
-
714
- binding_list.append({
715
- 'id': guid_clean,
716
- 'name': binding_name,
717
- 'guid': guid_clean
718
- })
719
- except:
720
- pass
721
-
722
- print('RESULT:' + json.dumps({'success': True, 'bindings': binding_list, 'count': len(binding_list)}))
723
- except Exception as e:
724
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
725
- `.trim();
726
-
727
- const resp = await this.executeWithRetry(
728
- () => this.bridge.executePython(py),
729
- 'getBindings'
730
- );
731
-
732
- return this.parsePythonResult(resp, 'getBindings');
210
+ const resp = await this.sendAction('sequence_get_bindings', { path: params?.path });
211
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
212
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_get_bindings' } as const;
213
+ }
214
+ return resp;
733
215
  }
734
216
 
735
217
  /**
736
218
  * Add multiple actors to sequence at once
737
219
  */
738
- async addActors(params: { actorNames: string[] }) {
739
- const py = `
740
- import unreal, json
741
- try:
742
- actor_sub = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
743
- ls = unreal.get_editor_subsystem(unreal.LevelSequenceEditorSubsystem)
744
- if not ls or not actor_sub:
745
- print('RESULT:' + json.dumps({'success': False, 'error': 'Subsystem unavailable'}))
746
- else:
747
- actor_names = ${JSON.stringify(params.actorNames)}
748
- actors_to_add = []
749
- not_found = []
750
-
751
- all_actors = actor_sub.get_all_level_actors()
752
- for name in actor_names:
753
- found = False
754
- for a in all_actors:
755
- if not a: continue
756
- label = a.get_actor_label()
757
- actor_name = a.get_name()
758
- if label == name or actor_name == name or label.startswith(name):
759
- actors_to_add.append(a)
760
- found = True
761
- break
762
- if not found:
763
- not_found.append(name)
764
-
765
- # Make sure we have a focused sequence
766
- seq = unreal.LevelSequenceEditorBlueprintLibrary.get_focused_level_sequence()
767
- if not seq:
768
- print('RESULT:' + json.dumps({'success': False, 'error': 'No sequence is currently focused'}))
769
- elif len(actors_to_add) == 0:
770
- print('RESULT:' + json.dumps({'success': False, 'error': f'No actors found: {not_found}'}))
771
- else:
772
- # Add all actors at once
773
- bindings = ls.add_actors(actors_to_add)
774
- added_actors = [a.get_actor_label() for a in actors_to_add]
775
- print('RESULT:' + json.dumps({
776
- 'success': True,
777
- 'count': len(bindings) if bindings else len(actors_to_add),
778
- 'actorsAdded': added_actors,
779
- 'notFound': not_found
780
- }))
781
- except Exception as e:
782
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
783
- `.trim();
784
-
785
- const resp = await this.executeWithRetry(
786
- () => this.bridge.executePython(py),
787
- 'addActors'
788
- );
789
-
790
- return this.parsePythonResult(resp, 'addActors');
220
+ async addActors(params: { actorNames: string[]; path?: string }) {
221
+ const path = this.resolveSequencePath(params.path);
222
+ const resp = await this.sendAction('sequence_add_actors', { path, actorNames: params.actorNames });
223
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
224
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_add_actors' } as const;
225
+ }
226
+ return resp;
791
227
  }
792
228
 
793
229
  /**
794
230
  * Remove actors from binding
795
231
  */
796
- async removeActors(params: { actorNames: string[] }) {
797
- const py = `
798
- import unreal, json
799
- try:
800
- actor_sub = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
801
- ls = unreal.get_editor_subsystem(unreal.LevelSequenceEditorSubsystem)
802
-
803
- if not ls or not actor_sub:
804
- print('RESULT:' + json.dumps({'success': False, 'error': 'Subsystem unavailable'}))
805
- else:
806
- # Get current sequence
807
- seq = unreal.LevelSequenceEditorBlueprintLibrary.get_focused_level_sequence()
808
- if not seq:
809
- print('RESULT:' + json.dumps({'success': False, 'error': 'No sequence is currently focused'}))
810
- else:
811
- actor_names = ${JSON.stringify(params.actorNames)}
812
- actors_to_remove = []
813
-
814
- all_actors = actor_sub.get_all_level_actors()
815
- for name in actor_names:
816
- for a in all_actors:
817
- if not a: continue
818
- label = a.get_actor_label()
819
- actor_name = a.get_name()
820
- if label == name or actor_name == name:
821
- actors_to_remove.append(a)
822
- break
823
-
824
- # Get all bindings and remove matching actors
825
- bindings = unreal.MovieSceneSequenceExtensions.get_bindings(seq)
826
- removed_count = 0
827
- for binding in bindings:
828
- try:
829
- ls.remove_actors_from_binding(actors_to_remove, binding)
830
- removed_count += 1
831
- except:
832
- pass
833
-
834
- print('RESULT:' + json.dumps({
835
- 'success': True,
836
- 'removedActors': [a.get_actor_label() for a in actors_to_remove],
837
- 'bindingsProcessed': removed_count
838
- }))
839
- except Exception as e:
840
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
841
- `.trim();
842
-
843
- const resp = await this.executeWithRetry(
844
- () => this.bridge.executePython(py),
845
- 'removeActors'
846
- );
847
-
848
- return this.parsePythonResult(resp, 'removeActors');
232
+ async removeActors(params: { actorNames: string[]; path?: string }) {
233
+ const path = this.resolveSequencePath(params.path);
234
+ const resp = await this.sendAction('sequence_remove_actors', { path, actorNames: params.actorNames });
235
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
236
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_remove_actors' } as const;
237
+ }
238
+ return resp;
849
239
  }
850
240
 
851
241
  /**
852
242
  * Create a spawnable from an actor class
853
243
  */
854
244
  async addSpawnableFromClass(params: { className: string; path?: string }) {
855
- const py = `
856
- import unreal, json
857
- try:
858
- ls = unreal.get_editor_subsystem(unreal.LevelSequenceEditorSubsystem)
859
-
860
- # Load the sequence
861
- seq_path = r"${params.path || ''}"
862
- if seq_path:
863
- seq = unreal.load_asset(seq_path)
864
- else:
865
- seq = unreal.LevelSequenceEditorBlueprintLibrary.get_focused_level_sequence()
866
-
867
- if not seq:
868
- print('RESULT:' + json.dumps({'success': False, 'error': 'No sequence found'}))
869
- else:
870
- # Try to find the class
871
- class_name = r"${params.className}"
872
- actor_class = None
873
-
874
- # Try common actor classes
875
- if class_name == "StaticMeshActor":
876
- actor_class = unreal.StaticMeshActor
877
- elif class_name == "CineCameraActor":
878
- actor_class = unreal.CineCameraActor
879
- elif class_name == "CameraActor":
880
- actor_class = unreal.CameraActor
881
- elif class_name == "PointLight":
882
- actor_class = unreal.PointLight
883
- elif class_name == "DirectionalLight":
884
- actor_class = unreal.DirectionalLight
885
- elif class_name == "SpotLight":
886
- actor_class = unreal.SpotLight
887
- else:
888
- # Try to load as a blueprint class
889
- try:
890
- actor_class = unreal.EditorAssetLibrary.load_asset(class_name)
891
- except:
892
- pass
893
-
894
- if not actor_class:
895
- print('RESULT:' + json.dumps({'success': False, 'error': f'Class {class_name} not found'}))
896
- else:
897
- spawnable = ls.add_spawnable_from_class(seq, actor_class)
898
- if spawnable:
899
- binding_id = unreal.MovieSceneSequenceExtensions.get_binding_id(seq, spawnable)
900
- print('RESULT:' + json.dumps({
901
- 'success': True,
902
- 'spawnableId': str(binding_id),
903
- 'className': class_name
904
- }))
905
- else:
906
- print('RESULT:' + json.dumps({'success': False, 'error': 'Failed to create spawnable'}))
907
- except Exception as e:
908
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
909
- `.trim();
910
-
911
- const resp = await this.executeWithRetry(
912
- () => this.bridge.executePython(py),
913
- 'addSpawnableFromClass'
914
- );
915
-
916
- return this.parsePythonResult(resp, 'addSpawnableFromClass');
245
+ const resp = await this.sendAction('sequence_add_spawnable_from_class', { className: params.className, path: params.path });
246
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
247
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_add_spawnable_from_class' } as const;
248
+ }
249
+ return resp;
250
+ }
251
+
252
+ async list(params?: { path?: string }) {
253
+ const resp = await this.sendAction('sequence_list', { path: params?.path });
254
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
255
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_list' } as const;
256
+ }
257
+ if (resp.success) {
258
+ const sequences = resp.sequences || resp.data || resp.result || [];
259
+ return {
260
+ ...resp,
261
+ sequences,
262
+ count: Array.isArray(sequences) ? sequences.length : undefined
263
+ };
264
+ }
265
+ return resp;
266
+ }
267
+
268
+ async duplicate(params: { path: string; destinationPath: string }) {
269
+ const resp = await this.sendAction('sequence_duplicate', { path: params.path, destinationPath: params.destinationPath });
270
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
271
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_duplicate' } as const;
272
+ }
273
+ return resp;
274
+ }
275
+
276
+ async rename(params: { path: string; newName: string }) {
277
+ const resp = await this.sendAction('sequence_rename', { path: params.path, newName: params.newName });
278
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
279
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_rename' } as const;
280
+ }
281
+ return resp;
282
+ }
283
+
284
+ async deleteSequence(params: { path: string }) {
285
+ const resp = await this.sendAction('sequence_delete', { path: params.path });
286
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
287
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_delete' } as const;
288
+ }
289
+ return resp;
290
+ }
291
+
292
+ async getMetadata(params: { path?: string }) {
293
+ const resp = await this.sendAction('sequence_get_metadata', { path: params.path });
294
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
295
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_get_metadata' } as const;
296
+ }
297
+ return resp;
298
+ }
299
+
300
+ /**
301
+ * Add a keyframe to a sequence binding
302
+ */
303
+ async addKeyframe(params: {
304
+ path?: string;
305
+ bindingId?: string;
306
+ actorName?: string;
307
+ property: 'Transform';
308
+ frame: number;
309
+ value: {
310
+ location?: { x: number; y: number; z: number };
311
+ rotation?: { roll: number; pitch: number; yaw: number };
312
+ scale?: { x: number; y: number; z: number };
313
+ };
314
+ }) {
315
+ const resp = await this.sendAction('sequence_add_keyframe', {
316
+ path: params.path,
317
+ bindingId: params.bindingId,
318
+ actorName: params.actorName,
319
+ property: params.property,
320
+ frame: params.frame,
321
+ value: params.value
322
+ });
323
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
324
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_add_keyframe' } as const;
325
+ }
326
+ return resp;
327
+ }
328
+
329
+ /**
330
+ * List tracks in a sequence
331
+ */
332
+ async listTracks(params: { path: string }) {
333
+ const resp = await this.sendAction('sequence_list_tracks', { path: params.path });
334
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
335
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_list_tracks' } as const;
336
+ }
337
+ if (resp.success) {
338
+ const tracks = resp.tracks || resp.data || resp.result || [];
339
+ return {
340
+ ...resp,
341
+ tracks,
342
+ count: Array.isArray(tracks) ? tracks.length : undefined
343
+ };
344
+ }
345
+ return resp;
346
+ }
347
+
348
+ /**
349
+ * Set playback work range
350
+ */
351
+ async setWorkRange(params: { path?: string; start: number; end: number }) {
352
+ const resp = await this.sendAction('sequence_set_work_range', { path: params.path, start: params.start, end: params.end });
353
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
354
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_set_work_range' } as const;
355
+ }
356
+ return resp;
917
357
  }
918
358
  }