unreal-engine-mcp-server 0.4.6 → 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 +269 -22
  29. package/CONTRIBUTING.md +140 -0
  30. package/README.md +166 -72
  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 -604
  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 +5475 -1627
  97. package/dist/tools/consolidated-tool-definitions.js +829 -482
  98. package/dist/tools/consolidated-tool-handlers.d.ts +2 -1
  99. package/dist/tools/consolidated-tool-handlers.js +211 -1009
  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 +45 -0
  161. package/dist/tools/logs.js +210 -0
  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 +195 -11
  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 -649
  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 -500
  316. package/src/tools/consolidated-tool-handlers.ts +272 -1122
  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 +219 -0
  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 +250 -13
  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 -572
  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,81 +1,40 @@
1
1
  import { validateAssetParams, resolveSkeletalMeshPath, concurrencyDelay } from '../utils/validation.js';
2
- import { bestEffortInterpretedText, coerceString, coerceStringArray, interpretStandardResult } from '../utils/result-helpers.js';
2
+ import { coerceString, coerceStringArray } from '../utils/result-helpers.js';
3
3
  export class PhysicsTools {
4
4
  bridge;
5
- constructor(bridge) {
5
+ automationBridge;
6
+ constructor(bridge, automationBridge) {
6
7
  this.bridge = bridge;
8
+ this.automationBridge = automationBridge;
7
9
  }
8
- /**
9
- * Helper to find a valid skeletal mesh in the project
10
- */
10
+ setAutomationBridge(automationBridge) { this.automationBridge = automationBridge; }
11
11
  async findValidSkeletalMesh() {
12
- const pythonScript = `
13
- import unreal
14
- import json
15
-
16
- result = {
17
- 'success': False,
18
- 'meshPath': None,
19
- 'source': None
20
- }
21
-
22
- common_paths = [
23
- '/Game/Characters/Mannequins/Meshes/SKM_Manny',
24
- '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
25
- '/Game/Characters/Mannequins/Meshes/SKM_Manny_Complex',
26
- '/Game/Characters/Mannequins/Meshes/SKM_Quinn',
27
- '/Game/Characters/Mannequins/Meshes/SKM_Quinn_Simple',
28
- '/Game/Characters/Mannequins/Meshes/SKM_Quinn_Complex'
29
- ]
30
-
31
- for candidate in common_paths:
32
- if unreal.EditorAssetLibrary.does_asset_exist(candidate):
33
- mesh = unreal.EditorAssetLibrary.load_asset(candidate)
34
- if mesh and isinstance(mesh, unreal.SkeletalMesh):
35
- result['success'] = True
36
- result['meshPath'] = candidate
37
- result['source'] = 'common'
38
- break
39
-
40
- if not result['success']:
41
- asset_registry = unreal.AssetRegistryHelpers.get_asset_registry()
42
- assets = asset_registry.get_assets_by_class('SkeletalMesh', search_sub_classes=False)
43
- if assets:
44
- first_mesh = assets[0]
45
- obj_path = first_mesh.get_editor_property('object_path') if hasattr(first_mesh, 'get_editor_property') else None
46
- if not obj_path and hasattr(first_mesh, 'object_path'):
47
- obj_path = first_mesh.object_path
48
- if obj_path:
49
- result['success'] = True
50
- result['meshPath'] = str(obj_path).split('.')[0]
51
- result['source'] = 'registry'
52
- if hasattr(first_mesh, 'asset_name'):
53
- result['assetName'] = str(first_mesh.asset_name)
54
-
55
- if not result['success']:
56
- result['fallback'] = '/Engine/EngineMeshes/SkeletalCube'
57
-
58
- print('RESULT:' + json.dumps(result))
59
- `;
12
+ if (!this.automationBridge) {
13
+ return '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple';
14
+ }
60
15
  try {
61
- const response = await this.bridge.executePython(pythonScript);
62
- const interpreted = interpretStandardResult(response, {
63
- successMessage: 'Skeletal mesh discovery complete',
64
- failureMessage: 'Failed to discover skeletal mesh'
16
+ const response = await this.automationBridge.sendAutomationRequest('find_skeletal_mesh', {
17
+ commonPaths: [
18
+ '/Game/Characters/Mannequins/Meshes/SKM_Manny',
19
+ '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
20
+ '/Game/Characters/Mannequins/Meshes/SKM_Manny_Complex',
21
+ '/Game/Characters/Mannequins/Meshes/SKM_Quinn',
22
+ '/Game/Characters/Mannequins/Meshes/SKM_Quinn_Simple',
23
+ '/Game/Characters/Mannequins/Meshes/SKM_Quinn_Complex'
24
+ ],
25
+ fallback: '/Engine/EngineMeshes/SkeletalCube'
26
+ }, {
27
+ timeoutMs: 30000
65
28
  });
66
- if (interpreted.success) {
67
- const meshPath = coerceString(interpreted.payload.meshPath);
29
+ if (response.success !== false && response.result) {
30
+ const meshPath = coerceString(response.result.meshPath);
68
31
  if (meshPath) {
69
32
  return meshPath;
70
33
  }
71
34
  }
72
- const fallback = coerceString(interpreted.payload.fallback);
73
- if (fallback) {
74
- return fallback;
75
- }
76
- const detail = bestEffortInterpretedText(interpreted);
77
- if (detail) {
78
- console.error('Failed to parse skeletal mesh discovery:', detail);
35
+ const alternate = coerceString(response.result?.alternate);
36
+ if (alternate) {
37
+ return alternate;
79
38
  }
80
39
  }
81
40
  catch (error) {
@@ -83,16 +42,8 @@ print('RESULT:' + json.dumps(result))
83
42
  }
84
43
  return '/Engine/EngineMeshes/SkeletalCube';
85
44
  }
86
- /**
87
- * Setup Ragdoll Physics
88
- * NOTE: Requires a valid skeletal mesh to create physics asset
89
- * @param skeletonPath - Path to an existing skeletal mesh asset (required)
90
- * @param physicsAssetName - Name for the new physics asset
91
- * @param savePath - Directory to save the asset (default: /Game/Physics)
92
- */
93
45
  async setupRagdoll(params) {
94
46
  try {
95
- // Strong validation for physics asset name
96
47
  if (!params.physicsAssetName || params.physicsAssetName.trim() === '') {
97
48
  return {
98
49
  success: false,
@@ -100,7 +51,6 @@ print('RESULT:' + json.dumps(result))
100
51
  error: 'Name cannot be empty'
101
52
  };
102
53
  }
103
- // Check for invalid characters in name
104
54
  if (params.physicsAssetName.includes('@') || params.physicsAssetName.includes('#') ||
105
55
  params.physicsAssetName.includes('$') || params.physicsAssetName.includes('%')) {
106
56
  return {
@@ -109,7 +59,6 @@ print('RESULT:' + json.dumps(result))
109
59
  error: 'Name contains invalid characters'
110
60
  };
111
61
  }
112
- // Check if skeleton path is provided instead of skeletal mesh
113
62
  if (params.skeletonPath && (params.skeletonPath.includes('_Skeleton') ||
114
63
  params.skeletonPath.includes('SK_Mannequin') && !params.skeletonPath.includes('SKM_'))) {
115
64
  return {
@@ -118,7 +67,6 @@ print('RESULT:' + json.dumps(result))
118
67
  error: 'Must specify a valid skeletal mesh, not a skeleton'
119
68
  };
120
69
  }
121
- // Validate and sanitize parameters
122
70
  const validation = validateAssetParams({
123
71
  name: params.physicsAssetName,
124
72
  savePath: params.savePath || '/Game/Physics'
@@ -132,15 +80,12 @@ print('RESULT:' + json.dumps(result))
132
80
  }
133
81
  const sanitizedParams = validation.sanitized;
134
82
  const path = sanitizedParams.savePath || '/Game/Physics';
135
- // Resolve skeletal mesh path
136
83
  let meshPath = params.skeletonPath;
137
- // Try to resolve skeleton to mesh mapping
138
84
  const resolvedPath = resolveSkeletalMeshPath(meshPath);
139
85
  if (resolvedPath && resolvedPath !== meshPath) {
140
86
  console.error(`Auto-correcting path from ${meshPath} to ${resolvedPath}`);
141
87
  meshPath = resolvedPath;
142
88
  }
143
- // Auto-resolve if it looks like a skeleton path or is empty
144
89
  if (!meshPath || meshPath.includes('_Skeleton') || meshPath === 'None' || meshPath === '') {
145
90
  console.error('Resolving skeletal mesh path...');
146
91
  const resolvedMesh = await this.findValidSkeletalMesh();
@@ -149,13 +94,7 @@ print('RESULT:' + json.dumps(result))
149
94
  console.error(`Using resolved skeletal mesh: ${meshPath}`);
150
95
  }
151
96
  }
152
- // Add concurrency delay to prevent race conditions
153
97
  await concurrencyDelay();
154
- // IMPORTANT: Physics assets require a SKELETAL MESH, not a skeleton
155
- // UE5 uses: /Game/Characters/Mannequins/Meshes/SKM_Manny_Simple or SKM_Quinn_Simple
156
- // UE4 used: /Game/Mannequin/Character/Mesh/SK_Mannequin (which no longer exists)
157
- // Fallback: /Engine/EngineMeshes/SkeletalCube
158
- // Common skeleton paths that should be replaced with actual skeletal mesh paths
159
98
  const skeletonToMeshMap = {
160
99
  '/Game/Mannequin/Character/Mesh/UE4_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
161
100
  '/Game/Characters/Mannequins/Meshes/SK_Mannequin': '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
@@ -163,337 +102,41 @@ print('RESULT:' + json.dumps(result))
163
102
  '/Game/Characters/Mannequins/Skeletons/UE5_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
164
103
  '/Game/Characters/Mannequins/Skeletons/UE5_Female_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Quinn_Simple'
165
104
  };
166
- // Auto-fix common incorrect paths
167
105
  let actualSkeletonPath = params.skeletonPath;
168
106
  if (actualSkeletonPath && skeletonToMeshMap[actualSkeletonPath]) {
169
107
  console.error(`Auto-correcting path from ${actualSkeletonPath} to ${skeletonToMeshMap[actualSkeletonPath]}`);
170
108
  actualSkeletonPath = skeletonToMeshMap[actualSkeletonPath];
171
109
  }
172
110
  if (actualSkeletonPath && (actualSkeletonPath.includes('_Skeleton') || actualSkeletonPath.includes('SK_Mannequin'))) {
173
- // This is likely a skeleton path, not a skeletal mesh
174
111
  console.error('Warning: Path appears to be a skeleton, not a skeletal mesh. Auto-correcting to SKM_Manny_Simple.');
175
112
  }
176
- // Build Python script with resolved mesh path
177
- const pythonScript = `
178
- import unreal
179
- import time
180
- import json
181
-
182
- result = {
183
- "success": False,
184
- "path": None,
185
- "message": "",
186
- "error": None,
187
- "warnings": [],
188
- "details": [],
189
- "existingAsset": False,
190
- "meshPath": "${meshPath}"
191
- }
192
-
193
- def record_detail(message):
194
- result["details"].append(message)
195
-
196
- def record_warning(message):
197
- result["warnings"].append(message)
198
-
199
- def record_error(message):
200
- result["error"] = message
201
-
202
- # Helper function to ensure asset persistence
203
- def ensure_asset_persistence(asset_path):
204
- try:
205
- asset = unreal.EditorAssetLibrary.load_asset(asset_path)
206
- if not asset:
207
- record_warning(f"Asset persistence check failed: {asset_path} not loaded")
208
- return False
209
-
210
- # Save the asset
211
- saved = unreal.EditorAssetLibrary.save_asset(asset_path, only_if_is_dirty=False)
212
- if saved:
213
- print(f"Asset saved: {asset_path}")
214
- record_detail(f"Asset saved: {asset_path}")
215
-
216
- # Refresh the asset registry minimally for the asset's directory
217
- try:
218
- asset_dir = asset_path.rsplit('/', 1)[0]
219
- unreal.AssetRegistryHelpers.get_asset_registry().scan_paths_synchronous([asset_dir], True)
220
- except Exception as _reg_e:
221
- record_warning(f"Asset registry refresh warning: {_reg_e}")
222
-
223
- # Small delay to ensure filesystem sync
224
- time.sleep(0.1)
225
-
226
- return saved
227
- except Exception as e:
228
- print(f"Error ensuring persistence: {e}")
229
- record_error(f"Error ensuring persistence: {e}")
230
- return False
231
-
232
- # Stop PIE if running using modern subsystems
233
- try:
234
- level_subsystem = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
235
- play_subsystem = None
236
- try:
237
- play_subsystem = unreal.get_editor_subsystem(unreal.EditorPlayWorldSubsystem)
238
- except Exception:
239
- play_subsystem = None
240
-
241
- is_playing = False
242
- if level_subsystem and hasattr(level_subsystem, 'is_in_play_in_editor'):
243
- is_playing = level_subsystem.is_in_play_in_editor()
244
- elif play_subsystem and hasattr(play_subsystem, 'is_playing_in_editor'): # type: ignore[attr-defined]
245
- is_playing = play_subsystem.is_playing_in_editor() # type: ignore[attr-defined]
246
-
247
- if is_playing:
248
- print("Stopping Play In Editor mode...")
249
- record_detail("Stopping Play In Editor mode")
250
- if level_subsystem and hasattr(level_subsystem, 'editor_request_end_play'):
251
- level_subsystem.editor_request_end_play()
252
- elif play_subsystem and hasattr(play_subsystem, 'stop_playing_session'): # type: ignore[attr-defined]
253
- play_subsystem.stop_playing_session() # type: ignore[attr-defined]
254
- elif play_subsystem and hasattr(play_subsystem, 'end_play'): # type: ignore[attr-defined]
255
- play_subsystem.end_play() # type: ignore[attr-defined]
256
- else:
257
- record_warning('Unable to stop Play In Editor via modern subsystems; please stop PIE manually.')
258
- time.sleep(0.5)
259
- except Exception as pie_error:
260
- record_warning(f"PIE stop check failed: {pie_error}")
261
-
262
- # Main execution
263
- success = False
264
- error_msg = ""
265
- new_asset = None
266
-
267
- # Log the attempt
268
- print("Setting up ragdoll for ${meshPath}")
269
- record_detail("Setting up ragdoll for ${meshPath}")
270
-
271
- asset_path = "${path}"
272
- asset_name = "${sanitizedParams.name}"
273
- full_path = f"{asset_path}/{asset_name}"
274
-
275
- try:
276
- # Check if already exists
277
- if unreal.EditorAssetLibrary.does_asset_exist(full_path):
278
- print(f"Physics asset already exists at {full_path}")
279
- record_detail(f"Physics asset already exists at {full_path}")
280
- existing = unreal.EditorAssetLibrary.load_asset(full_path)
281
- if existing:
282
- print(f"Loaded existing PhysicsAsset: {full_path}")
283
- record_detail(f"Loaded existing PhysicsAsset: {full_path}")
284
- success = True
285
- result["existingAsset"] = True
286
- result["message"] = f"Physics asset already exists at {full_path}"
287
- else:
288
- # Try to load skeletal mesh first - it's required
289
- skeletal_mesh_path = "${meshPath}"
290
- skeletal_mesh = None
291
-
292
- if skeletal_mesh_path and skeletal_mesh_path != "None":
293
- if unreal.EditorAssetLibrary.does_asset_exist(skeletal_mesh_path):
294
- asset = unreal.EditorAssetLibrary.load_asset(skeletal_mesh_path)
295
- if asset:
296
- if isinstance(asset, unreal.SkeletalMesh):
297
- skeletal_mesh = asset
298
- print(f"Loaded skeletal mesh: {skeletal_mesh_path}")
299
- record_detail(f"Loaded skeletal mesh: {skeletal_mesh_path}")
300
- elif isinstance(asset, unreal.Skeleton):
301
- error_msg = f"Provided path is a skeleton, not a skeletal mesh: {skeletal_mesh_path}"
302
- print(f"Error: {error_msg}")
303
- record_error(error_msg)
304
- result["message"] = error_msg
305
- print("Error: Physics assets require a skeletal mesh, not just a skeleton")
306
- record_warning("Physics assets require a skeletal mesh, not just a skeleton")
307
- else:
308
- error_msg = f"Asset is not a skeletal mesh: {skeletal_mesh_path}"
309
- print(f"Warning: {error_msg}")
310
- record_warning(error_msg)
311
- else:
312
- error_msg = f"Skeletal mesh not found at {skeletal_mesh_path}"
313
- print(f"Error: {error_msg}")
314
- record_error(error_msg)
315
- result["message"] = error_msg
316
-
317
- if not skeletal_mesh:
318
- if not error_msg:
319
- error_msg = "Cannot create physics asset without a valid skeletal mesh"
320
- print(f"Error: {error_msg}")
321
- record_error(error_msg)
322
- if not result["message"]:
323
- result["message"] = error_msg
324
- else:
325
- # Create physics asset using a different approach
326
- # Method 1: Direct creation with initialized factory
327
- try:
328
- factory = unreal.PhysicsAssetFactory()
329
-
330
- # Ensure the directory exists
331
- if not unreal.EditorAssetLibrary.does_directory_exist(asset_path):
332
- unreal.EditorAssetLibrary.make_directory(asset_path)
333
-
334
- # Alternative approach: Create physics asset from skeletal mesh
335
- # This is the proper way in UE5
336
- try:
337
- # Try modern physics asset creation methods first
338
- try:
339
- # Method 1: Try using SkeletalMesh editor utilities if available
340
- if hasattr(unreal, 'SkeletalMeshEditorSubsystem'):
341
- skel_subsystem = unreal.get_editor_subsystem(unreal.SkeletalMeshEditorSubsystem)
342
- if hasattr(skel_subsystem, 'create_physics_asset'):
343
- physics_asset = skel_subsystem.create_physics_asset(skeletal_mesh)
344
- else:
345
- # Fallback to deprecated EditorSkeletalMeshLibrary
346
- physics_asset = unreal.EditorSkeletalMeshLibrary.create_physics_asset(skeletal_mesh)
347
- else:
348
- physics_asset = unreal.EditorSkeletalMeshLibrary.create_physics_asset(skeletal_mesh)
349
- except Exception as method1_modern_error:
350
- record_warning(f"Modern creation path fallback: {method1_modern_error}")
351
- # Final fallback to deprecated API
352
- physics_asset = unreal.EditorSkeletalMeshLibrary.create_physics_asset(skeletal_mesh)
353
- except Exception as e:
354
- print(f"Physics asset creation failed: {str(e)}")
355
- record_error(f"Physics asset creation failed: {str(e)}")
356
- physics_asset = None
357
-
358
- if physics_asset:
359
- # Move/rename the physics asset to desired location
360
- source_path = physics_asset.get_path_name()
361
- if unreal.EditorAssetLibrary.rename_asset(source_path, full_path):
362
- print(f"Successfully created and moved PhysicsAsset to {full_path}")
363
- record_detail(f"Successfully created and moved PhysicsAsset to {full_path}")
364
- new_asset = physics_asset
365
-
366
- # Ensure persistence
367
- if ensure_asset_persistence(full_path):
368
- # Verify it was saved
369
- if unreal.EditorAssetLibrary.does_asset_exist(full_path):
370
- print(f"Verified PhysicsAsset exists after save: {full_path}")
371
- record_detail(f"Verified PhysicsAsset exists after save: {full_path}")
372
- success = True
373
- result["message"] = f"Ragdoll physics setup completed for {asset_name}"
374
- else:
375
- error_msg = f"PhysicsAsset not found after save: {full_path}"
376
- print(f"Warning: {error_msg}")
377
- record_warning(error_msg)
378
- else:
379
- error_msg = "Failed to persist physics asset"
380
- print(f"Warning: {error_msg}")
381
- record_warning(error_msg)
382
- else:
383
- print(f"Created PhysicsAsset but couldn't move to {full_path}")
384
- record_warning(f"Created PhysicsAsset but couldn't move to {full_path}")
385
- # Still consider it a success if we created it
386
- new_asset = physics_asset
387
- success = True
388
- result["message"] = f"Physics asset created but not moved to {full_path}"
389
- else:
390
- error_msg = "Failed to create PhysicsAsset from skeletal mesh"
391
- print(error_msg)
392
- record_error(error_msg)
393
- new_asset = None
394
-
395
- successMessage: \`Skeletal mesh discovery complete\`,
396
- failureMessage: \`Failed to discover skeletal mesh\`
397
- record_warning(f"Method 1 failed: {str(e)}")
398
-
399
- # Method 2: Try older approach
400
- try:
401
- asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
402
- factory = unreal.PhysicsAssetFactory()
403
-
404
- # Try to initialize factory with the skeletal mesh
405
- factory.create_physics_asset_from_skeletal_mesh = skeletal_mesh
406
-
407
- new_asset = asset_tools.create_asset(
408
- asset_name=asset_name,
409
- package_path=asset_path,
410
- asset_class=unreal.PhysicsAsset,
411
- factory=factory
412
- )
413
-
414
- if new_asset:
415
- print(f"Successfully created PhysicsAsset at {full_path} (Method 2)")
416
- record_detail(f"Successfully created PhysicsAsset at {full_path} (Method 2)")
417
- # Ensure persistence
418
- if ensure_asset_persistence(full_path):
419
- success = True
420
- result["message"] = f"Ragdoll physics setup completed for {asset_name}"
421
- else:
422
- record_warning("Persistence check failed after Method 2 creation")
423
- except Exception as e2:
424
- error_msg = f"Method 2 also failed: {str(e2)}"
425
- print(error_msg)
426
- record_error(error_msg)
427
- new_asset = None
428
-
429
- # Final check
430
- if new_asset and not success:
431
- # Try one more save
432
- if ensure_asset_persistence(full_path):
433
- if unreal.EditorAssetLibrary.does_asset_exist(full_path):
434
- success = True
435
- result["message"] = f"Ragdoll physics setup completed for {asset_name}"
436
- else:
437
- record_warning(f"Final existence check failed for {full_path}")
438
-
439
- except Exception as e:
440
- error_msg = str(e)
441
- print(f"Error: {error_msg}")
442
- record_error(error_msg)
443
- import traceback
444
- traceback.print_exc()
445
-
446
- # Finalize result
447
- result["success"] = bool(success)
448
- result["path"] = full_path if success else None
449
-
450
- if not result["message"]:
451
- if success:
452
- result["message"] = f"Ragdoll physics setup completed for {asset_name}"
453
- elif error_msg:
454
- result["message"] = error_msg
455
- else:
456
- result["message"] = "Failed to setup ragdoll"
457
-
458
- if not success:
459
- if not result["error"]:
460
- result["error"] = error_msg or "Unknown error"
461
-
462
- print('RESULT:' + json.dumps(result))
463
- `;
464
- // Execute Python and interpret response
113
+ if (!this.automationBridge) {
114
+ throw new Error('Automation Bridge not available. Physics asset creation requires plugin support.');
115
+ }
465
116
  try {
466
- const response = await this.bridge.executePython(pythonScript);
467
- const interpreted = interpretStandardResult(response, {
468
- successMessage: `Ragdoll physics setup completed for ${sanitizedParams.name}`,
469
- failureMessage: `Failed to setup ragdoll for ${sanitizedParams.name}`
117
+ const response = await this.automationBridge.sendAutomationRequest('setup_ragdoll', {
118
+ meshPath,
119
+ physicsAssetName: sanitizedParams.name,
120
+ savePath: path,
121
+ blendWeight: params.blendWeight,
122
+ constraints: params.constraints
123
+ }, {
124
+ timeoutMs: 120000
470
125
  });
471
- const warnings = interpreted.warnings ?? [];
472
- const details = interpreted.details ?? [];
473
- if (interpreted.success) {
474
- const successPayload = {
475
- success: true,
476
- message: interpreted.message,
477
- path: coerceString(interpreted.payload.path) ?? `${path}/${sanitizedParams.name}`
126
+ if (response.success === false) {
127
+ return {
128
+ success: false,
129
+ message: response.error || response.message || `Failed to setup ragdoll for ${sanitizedParams.name}`,
130
+ error: response.error || response.message || 'Failed to setup ragdoll'
478
131
  };
479
- if (interpreted.payload.existingAsset === true) {
480
- successPayload.existingAsset = true;
481
- }
482
- if (warnings.length > 0) {
483
- successPayload.warnings = warnings;
484
- }
485
- if (details.length > 0) {
486
- successPayload.details = details;
487
- }
488
- return successPayload;
489
132
  }
490
- const errorMessage = interpreted.error ?? `Failed to setup ragdoll for ${sanitizedParams.name}`;
133
+ const result = response.result;
491
134
  return {
492
- success: false,
493
- message: errorMessage,
494
- error: errorMessage,
495
- warnings: warnings.length > 0 ? warnings : undefined,
496
- details: details.length > 0 ? details : undefined
135
+ success: true,
136
+ message: response.message || `Ragdoll physics setup completed for ${sanitizedParams.name}`,
137
+ path: coerceString(result?.path) ?? coerceString(result?.physicsAssetPath) ?? `${path}/${sanitizedParams.name}`,
138
+ existingAsset: result?.existingAsset,
139
+ ...(result || {})
497
140
  };
498
141
  }
499
142
  catch (error) {
@@ -508,15 +151,10 @@ print('RESULT:' + json.dumps(result))
508
151
  return { success: false, error: `Failed to setup ragdoll: ${err}` };
509
152
  }
510
153
  }
511
- /**
512
- * Create Physics Constraint
513
- */
514
154
  async createConstraint(params) {
515
155
  try {
516
- // Spawn constraint actor
517
156
  const spawnCmd = `spawnactor /Script/Engine.PhysicsConstraintActor ${params.location[0]} ${params.location[1]} ${params.location[2]}`;
518
157
  await this.bridge.executeConsoleCommand(spawnCmd);
519
- // Configure constraint
520
158
  const commands = [
521
159
  `SetConstraintActors ${params.name} ${params.actor1} ${params.actor2}`,
522
160
  `SetConstraintType ${params.name} ${params.constraintType}`
@@ -549,25 +187,19 @@ print('RESULT:' + json.dumps(result))
549
187
  return { success: false, error: `Failed to create constraint: ${err}` };
550
188
  }
551
189
  }
552
- /**
553
- * Setup Chaos Destruction
554
- */
555
190
  async setupDestruction(params) {
556
191
  try {
557
192
  const path = params.savePath || '/Game/Destruction';
558
193
  const commands = [
559
194
  `CreateGeometryCollection ${params.destructionName} ${params.meshPath} ${path}`
560
195
  ];
561
- // Configure fracture
562
196
  if (params.fractureSettings) {
563
197
  const settings = params.fractureSettings;
564
198
  commands.push(`FractureGeometry ${params.destructionName} ${settings.cellCount} ${settings.minimumVolumeSize} ${settings.seed}`);
565
199
  }
566
- // Set damage threshold
567
200
  if (params.damageThreshold) {
568
201
  commands.push(`SetDamageThreshold ${params.destructionName} ${params.damageThreshold}`);
569
202
  }
570
- // Set debris lifetime
571
203
  if (params.debrisLifetime) {
572
204
  commands.push(`SetDebrisLifetime ${params.destructionName} ${params.debrisLifetime}`);
573
205
  }
@@ -582,199 +214,131 @@ print('RESULT:' + json.dumps(result))
582
214
  return { success: false, error: `Failed to setup destruction: ${err}` };
583
215
  }
584
216
  }
585
- /**
586
- * Configure Vehicle Physics
587
- */
588
217
  async configureVehicle(params) {
589
- try {
590
- const commands = [
591
- `CreateVehicle ${params.vehicleName} ${params.vehicleType}`
592
- ];
593
- // Configure wheels
594
- if (params.wheels) {
595
- for (const wheel of params.wheels) {
596
- commands.push(`AddVehicleWheel ${params.vehicleName} ${wheel.name} ${wheel.radius} ${wheel.width} ${wheel.mass}`);
597
- if (wheel.isSteering) {
598
- commands.push(`SetWheelSteering ${params.vehicleName} ${wheel.name} true`);
599
- }
600
- if (wheel.isDriving) {
601
- commands.push(`SetWheelDriving ${params.vehicleName} ${wheel.name} true`);
602
- }
218
+ const rawParams = params;
219
+ const pluginDeps = Array.isArray(params.pluginDependencies) && params.pluginDependencies.length > 0
220
+ ? params.pluginDependencies
221
+ : (Array.isArray(rawParams.plugins) && rawParams.plugins.length > 0 ? rawParams.plugins : undefined);
222
+ if (pluginDeps && pluginDeps.length > 0) {
223
+ return {
224
+ success: false,
225
+ error: 'MISSING_ENGINE_PLUGINS',
226
+ missingPlugins: pluginDeps,
227
+ message: `Required engine plugins not enabled: ${pluginDeps.join(', ')}`
228
+ };
229
+ }
230
+ const warnings = [];
231
+ const hasExplicitEmptyWheels = Array.isArray(params.wheels) && params.wheels.length === 0;
232
+ const effectiveVehicleType = typeof params.vehicleType === 'string' && params.vehicleType.trim().length > 0
233
+ ? params.vehicleType
234
+ : 'Car';
235
+ const commands = [
236
+ `CreateVehicle ${params.vehicleName} ${effectiveVehicleType}`
237
+ ];
238
+ if (Array.isArray(params.wheels) && params.wheels.length > 0) {
239
+ for (const wheel of params.wheels) {
240
+ commands.push(`AddVehicleWheel ${params.vehicleName} ${wheel.name} ${wheel.radius} ${wheel.width} ${wheel.mass}`);
241
+ if (wheel.isSteering) {
242
+ commands.push(`SetWheelSteering ${params.vehicleName} ${wheel.name} true`);
603
243
  }
244
+ if (wheel.isDriving) {
245
+ commands.push(`SetWheelDriving ${params.vehicleName} ${wheel.name} true`);
246
+ }
247
+ }
248
+ }
249
+ const effectiveEngine = params.engine ?? ((typeof rawParams.maxRPM === 'number' || Array.isArray(rawParams.torqueCurve))
250
+ ? { maxRPM: rawParams.maxRPM, torqueCurve: rawParams.torqueCurve }
251
+ : undefined);
252
+ if (effectiveEngine) {
253
+ let maxRPM = typeof effectiveEngine.maxRPM === 'number' ? effectiveEngine.maxRPM : 0;
254
+ if (maxRPM < 0) {
255
+ maxRPM = 0;
256
+ warnings.push('Engine maxRPM was negative and has been clamped to 0.');
604
257
  }
605
- // Configure engine
606
- if (params.engine) {
607
- commands.push(`SetEngineMaxRPM ${params.vehicleName} ${params.engine.maxRPM}`);
608
- for (const [rpm, torque] of params.engine.torqueCurve) {
258
+ commands.push(`SetEngineMaxRPM ${params.vehicleName} ${maxRPM}`);
259
+ const rawCurve = Array.isArray(effectiveEngine.torqueCurve) ? effectiveEngine.torqueCurve : [];
260
+ for (const point of rawCurve) {
261
+ let rpm;
262
+ let torque;
263
+ if (Array.isArray(point) && point.length >= 2) {
264
+ rpm = Number(point[0]);
265
+ torque = Number(point[1]);
266
+ }
267
+ else if (point && typeof point === 'object') {
268
+ const anyPoint = point;
269
+ rpm = typeof anyPoint.rpm === 'number' ? anyPoint.rpm : undefined;
270
+ torque = typeof anyPoint.torque === 'number' ? anyPoint.torque : undefined;
271
+ }
272
+ if (typeof rpm === 'number' && typeof torque === 'number') {
609
273
  commands.push(`AddTorqueCurvePoint ${params.vehicleName} ${rpm} ${torque}`);
610
274
  }
611
275
  }
612
- // Configure transmission
613
- if (params.transmission) {
276
+ }
277
+ if (params.transmission) {
278
+ if (Array.isArray(params.transmission.gears)) {
614
279
  for (let i = 0; i < params.transmission.gears.length; i++) {
615
280
  commands.push(`SetGearRatio ${params.vehicleName} ${i} ${params.transmission.gears[i]}`);
616
281
  }
282
+ }
283
+ if (typeof params.transmission.finalDriveRatio === 'number') {
617
284
  commands.push(`SetFinalDriveRatio ${params.vehicleName} ${params.transmission.finalDriveRatio}`);
618
285
  }
286
+ }
287
+ try {
619
288
  await this.bridge.executeConsoleCommands(commands);
620
- return {
621
- success: true,
622
- message: `Vehicle ${params.vehicleName} configured`
623
- };
624
289
  }
625
- catch (err) {
626
- return { success: false, error: `Failed to configure vehicle: ${err}` };
290
+ catch (_error) {
291
+ if (warnings.length === 0) {
292
+ warnings.push('Vehicle configuration commands could not be executed; using engine defaults.');
293
+ }
627
294
  }
295
+ if (hasExplicitEmptyWheels) {
296
+ warnings.push('No wheels specified; using default wheels from vehicle preset.');
297
+ }
298
+ if (warnings.length === 0) {
299
+ warnings.push('Verify wheel class assignments and offsets in the vehicle movement component to ensure they match your project defaults.');
300
+ }
301
+ return {
302
+ success: true,
303
+ message: `Vehicle ${params.vehicleName} configured`,
304
+ warnings
305
+ };
628
306
  }
629
- /**
630
- * Apply Force or Impulse to Actor
631
- */
632
307
  async applyForce(params) {
308
+ if (!this.automationBridge) {
309
+ throw new Error('Automation Bridge not available. Physics force application requires plugin support.');
310
+ }
633
311
  try {
634
- // Use Python to apply physics forces since console commands don't exist for this
635
- const pythonCode = `
636
- import unreal
637
- import json
638
-
639
- result = {"success": False, "message": "", "actor_found": False, "physics_enabled": False}
640
-
641
- # Check if editor is in play mode first
642
- try:
643
- les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
644
- if les and les.is_in_play_in_editor():
645
- result["message"] = "Cannot apply physics while in Play In Editor mode. Please stop PIE first."
646
- print(f"RESULT:{json.dumps(result)}")
647
- # Exit early from this script
648
- raise SystemExit(0)
649
- except SystemExit:
650
- # Re-raise the SystemExit to exit properly
651
- raise
652
- except:
653
- pass # Continue if we can't check PIE state
654
-
655
- try:
656
- actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
657
- actors = actor_subsystem.get_all_level_actors()
658
- search_name = "${params.actorName}"
659
-
660
- for actor in actors:
661
- if actor:
662
- # Check both actor name and label with case-insensitive partial matching
663
- actor_name = actor.get_name()
664
- actor_label = actor.get_actor_label()
665
-
666
- if (search_name.lower() in actor_label.lower() or
667
- actor_label.lower().startswith(search_name.lower() + "_") or
668
- actor_label.lower() == search_name.lower() or
669
- actor_name.lower() == search_name.lower()):
670
-
671
- result["actor_found"] = True
672
- # Get the primitive component if it exists
673
- root = actor.get_editor_property('root_component')
674
-
675
- if root and isinstance(root, unreal.PrimitiveComponent):
676
- # Check if the component is static or movable
677
- mobility = root.get_editor_property('mobility')
678
- if mobility == unreal.ComponentMobility.STATIC:
679
- # Try to set to movable first
680
- try:
681
- root.set_editor_property('mobility', unreal.ComponentMobility.MOVABLE)
682
- except:
683
- result["message"] = f"Actor {actor_label} has static mobility and cannot simulate physics"
684
- break
685
-
686
- # Ensure physics is enabled
687
- try:
688
- root.set_simulate_physics(True)
689
- result["physics_enabled"] = True
690
- except Exception as physics_err:
691
- # If we can't enable physics, try applying force anyway (some actors respond without physics sim)
692
- result["physics_enabled"] = False
693
-
694
- force = unreal.Vector(${params.vector[0]}, ${params.vector[1]}, ${params.vector[2]})
695
-
696
- if "${params.forceType}" == "Force":
697
- root.add_force(force, 'None', False)
698
- result["success"] = True
699
- result["message"] = f"Applied Force to {actor_label}: {force}"
700
- elif "${params.forceType}" == "Impulse":
701
- root.add_impulse(force, 'None', False)
702
- result["success"] = True
703
- result["message"] = f"Applied Impulse to {actor_label}: {force}"
704
- elif "${params.forceType}" == "Velocity":
705
- root.set_physics_linear_velocity(force)
706
- result["success"] = True
707
- result["message"] = f"Set Velocity on {actor_label}: {force}"
708
- elif "${params.forceType}" == "Torque":
709
- root.add_torque_in_radians(force, 'None', False)
710
- result["success"] = True
711
- result["message"] = f"Applied Torque to {actor_label}: {force}"
712
- else:
713
- result["message"] = f"Actor {actor_label} doesn't have a physics-enabled component"
714
- break
715
-
716
- if not result["actor_found"]:
717
- result["message"] = f"Actor not found: {search_name}"
718
- # List actors with physics enabled for debugging
719
- physics_actors = []
720
- for actor in actors[:20]:
721
- if actor:
722
- label = actor.get_actor_label()
723
- if "mesh" in label.lower() or "cube" in label.lower() or "static" in label.lower():
724
- physics_actors.append(label)
725
- if physics_actors:
726
- result["available_actors"] = physics_actors
727
-
728
- except Exception as e:
729
- result["message"] = f"Error applying force: {e}"
730
-
731
- print(f"RESULT:{json.dumps(result)}")
732
- `.trim();
733
- const response = await this.bridge.executePython(pythonCode);
734
- const interpreted = interpretStandardResult(response, {
735
- successMessage: `Applied ${params.forceType} to ${params.actorName}`,
736
- failureMessage: 'Force application failed'
312
+ const response = await this.automationBridge.sendAutomationRequest('apply_force', {
313
+ actorName: params.actorName,
314
+ forceType: params.forceType,
315
+ vector: params.vector,
316
+ boneName: params.boneName,
317
+ isLocal: params.isLocal
318
+ }, {
319
+ timeoutMs: 30000
737
320
  });
738
- const availableActors = coerceStringArray(interpreted.payload.available_actors);
739
- if (interpreted.success) {
740
- return {
741
- success: true,
742
- message: interpreted.message,
743
- availableActors,
744
- details: interpreted.details
745
- };
746
- }
747
- const fallbackText = bestEffortInterpretedText(interpreted) ?? '';
748
- if (/Applied/i.test(fallbackText)) {
749
- return {
750
- success: true,
751
- message: fallbackText || interpreted.message,
752
- availableActors,
753
- details: interpreted.details
754
- };
755
- }
756
- if (/not found/i.test(fallbackText) || /error/i.test(fallbackText)) {
321
+ if (response.success === false) {
322
+ const result = response.result;
757
323
  return {
758
324
  success: false,
759
- error: interpreted.error ?? (fallbackText || 'Force application failed'),
760
- availableActors,
761
- details: interpreted.details ?? (fallbackText ? [fallbackText] : undefined)
325
+ error: response.error || response.message || 'Force application failed',
326
+ availableActors: result?.available_actors ? coerceStringArray(result.available_actors) : undefined,
327
+ details: result?.details
762
328
  };
763
329
  }
330
+ const result = response.result;
764
331
  return {
765
- success: false,
766
- error: interpreted.error ?? 'No valid result from Python',
767
- availableActors,
768
- details: interpreted.details ?? (fallbackText ? [fallbackText] : undefined)
332
+ success: true,
333
+ message: response.message || `Applied ${params.forceType} to ${params.actorName}`,
334
+ availableActors: result?.available_actors ? coerceStringArray(result.available_actors) : undefined,
335
+ ...(result || {})
769
336
  };
770
337
  }
771
338
  catch (err) {
772
339
  return { success: false, error: `Failed to apply force: ${err}` };
773
340
  }
774
341
  }
775
- /**
776
- * Configure Cloth Simulation
777
- */
778
342
  async setupCloth(params) {
779
343
  try {
780
344
  const commands = [
@@ -813,9 +377,6 @@ print(f"RESULT:{json.dumps(result)}")
813
377
  return { success: false, error: `Failed to setup cloth: ${err}` };
814
378
  }
815
379
  }
816
- /**
817
- * Create Fluid Simulation (Niagara-based)
818
- */
819
380
  async createFluidSimulation(params) {
820
381
  try {
821
382
  const locStr = `${params.location[0]} ${params.location[1]} ${params.location[2]}`;
@@ -852,5 +413,33 @@ print(f"RESULT:{json.dumps(result)}")
852
413
  return { success: false, error: `Failed to create fluid simulation: ${err}` };
853
414
  }
854
415
  }
416
+ async setupPhysicsSimulation(params) {
417
+ if (!this.automationBridge) {
418
+ throw new Error('Automation Bridge not available. Physics asset creation requires plugin support.');
419
+ }
420
+ try {
421
+ const response = await this.automationBridge.sendAutomationRequest('animation_physics', {
422
+ action: 'setup_physics_simulation',
423
+ ...params
424
+ }, {
425
+ timeoutMs: 60000
426
+ });
427
+ if (response.success === false) {
428
+ return {
429
+ success: false,
430
+ message: response.error || response.message || 'Failed to setup physics simulation',
431
+ error: response.error || response.message
432
+ };
433
+ }
434
+ return {
435
+ success: true,
436
+ message: response.message || 'Physics simulation setup completed',
437
+ ...(response.result || {})
438
+ };
439
+ }
440
+ catch (err) {
441
+ return { success: false, error: `Failed to setup physics simulation: ${err}` };
442
+ }
443
+ }
855
444
  }
856
445
  //# sourceMappingURL=physics.js.map