unreal-engine-mcp-server 0.4.7 → 0.5.1

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 (454) 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-config.yml +51 -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 +27 -0
  19. package/.github/workflows/labeler.yml +17 -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 +13 -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 +338 -31
  29. package/CONTRIBUTING.md +140 -0
  30. package/GEMINI.md +115 -0
  31. package/Public/Plugin_setup_guide.mp4 +0 -0
  32. package/README.md +189 -128
  33. package/claude_desktop_config_example.json +7 -6
  34. package/dist/automation/bridge.d.ts +50 -0
  35. package/dist/automation/bridge.js +452 -0
  36. package/dist/automation/connection-manager.d.ts +23 -0
  37. package/dist/automation/connection-manager.js +107 -0
  38. package/dist/automation/handshake.d.ts +11 -0
  39. package/dist/automation/handshake.js +89 -0
  40. package/dist/automation/index.d.ts +3 -0
  41. package/dist/automation/index.js +3 -0
  42. package/dist/automation/message-handler.d.ts +12 -0
  43. package/dist/automation/message-handler.js +149 -0
  44. package/dist/automation/request-tracker.d.ts +25 -0
  45. package/dist/automation/request-tracker.js +98 -0
  46. package/dist/automation/types.d.ts +130 -0
  47. package/dist/automation/types.js +2 -0
  48. package/dist/cli.js +32 -5
  49. package/dist/config.d.ts +26 -0
  50. package/dist/config.js +59 -0
  51. package/dist/constants.d.ts +16 -0
  52. package/dist/constants.js +16 -0
  53. package/dist/graphql/loaders.d.ts +64 -0
  54. package/dist/graphql/loaders.js +117 -0
  55. package/dist/graphql/resolvers.d.ts +268 -0
  56. package/dist/graphql/resolvers.js +746 -0
  57. package/dist/graphql/schema.d.ts +5 -0
  58. package/dist/graphql/schema.js +437 -0
  59. package/dist/graphql/server.d.ts +26 -0
  60. package/dist/graphql/server.js +117 -0
  61. package/dist/graphql/types.d.ts +9 -0
  62. package/dist/graphql/types.js +2 -0
  63. package/dist/handlers/resource-handlers.d.ts +20 -0
  64. package/dist/handlers/resource-handlers.js +180 -0
  65. package/dist/index.d.ts +33 -18
  66. package/dist/index.js +130 -619
  67. package/dist/resources/actors.d.ts +17 -12
  68. package/dist/resources/actors.js +56 -76
  69. package/dist/resources/assets.d.ts +6 -14
  70. package/dist/resources/assets.js +115 -147
  71. package/dist/resources/levels.d.ts +13 -13
  72. package/dist/resources/levels.js +25 -34
  73. package/dist/server/resource-registry.d.ts +20 -0
  74. package/dist/server/resource-registry.js +37 -0
  75. package/dist/server/tool-registry.d.ts +23 -0
  76. package/dist/server/tool-registry.js +322 -0
  77. package/dist/server-setup.d.ts +20 -0
  78. package/dist/server-setup.js +71 -0
  79. package/dist/services/health-monitor.d.ts +34 -0
  80. package/dist/services/health-monitor.js +105 -0
  81. package/dist/services/metrics-server.d.ts +11 -0
  82. package/dist/services/metrics-server.js +105 -0
  83. package/dist/tools/actors.d.ts +163 -9
  84. package/dist/tools/actors.js +356 -311
  85. package/dist/tools/animation.d.ts +135 -4
  86. package/dist/tools/animation.js +510 -411
  87. package/dist/tools/assets.d.ts +75 -29
  88. package/dist/tools/assets.js +265 -284
  89. package/dist/tools/audio.d.ts +102 -42
  90. package/dist/tools/audio.js +272 -685
  91. package/dist/tools/base-tool.d.ts +17 -0
  92. package/dist/tools/base-tool.js +46 -0
  93. package/dist/tools/behavior-tree.d.ts +94 -0
  94. package/dist/tools/behavior-tree.js +39 -0
  95. package/dist/tools/blueprint.d.ts +208 -126
  96. package/dist/tools/blueprint.js +685 -832
  97. package/dist/tools/consolidated-tool-definitions.d.ts +5462 -1781
  98. package/dist/tools/consolidated-tool-definitions.js +829 -496
  99. package/dist/tools/consolidated-tool-handlers.d.ts +2 -1
  100. package/dist/tools/consolidated-tool-handlers.js +198 -1027
  101. package/dist/tools/debug.d.ts +143 -85
  102. package/dist/tools/debug.js +234 -180
  103. package/dist/tools/dynamic-handler-registry.d.ts +13 -0
  104. package/dist/tools/dynamic-handler-registry.js +23 -0
  105. package/dist/tools/editor.d.ts +30 -83
  106. package/dist/tools/editor.js +247 -244
  107. package/dist/tools/engine.d.ts +10 -4
  108. package/dist/tools/engine.js +13 -5
  109. package/dist/tools/environment.d.ts +30 -0
  110. package/dist/tools/environment.js +267 -0
  111. package/dist/tools/foliage.d.ts +65 -99
  112. package/dist/tools/foliage.js +221 -331
  113. package/dist/tools/handlers/actor-handlers.d.ts +3 -0
  114. package/dist/tools/handlers/actor-handlers.js +227 -0
  115. package/dist/tools/handlers/animation-handlers.d.ts +3 -0
  116. package/dist/tools/handlers/animation-handlers.js +185 -0
  117. package/dist/tools/handlers/argument-helper.d.ts +16 -0
  118. package/dist/tools/handlers/argument-helper.js +80 -0
  119. package/dist/tools/handlers/asset-handlers.d.ts +3 -0
  120. package/dist/tools/handlers/asset-handlers.js +496 -0
  121. package/dist/tools/handlers/audio-handlers.d.ts +3 -0
  122. package/dist/tools/handlers/audio-handlers.js +166 -0
  123. package/dist/tools/handlers/blueprint-handlers.d.ts +4 -0
  124. package/dist/tools/handlers/blueprint-handlers.js +358 -0
  125. package/dist/tools/handlers/common-handlers.d.ts +14 -0
  126. package/dist/tools/handlers/common-handlers.js +56 -0
  127. package/dist/tools/handlers/editor-handlers.d.ts +3 -0
  128. package/dist/tools/handlers/editor-handlers.js +119 -0
  129. package/dist/tools/handlers/effect-handlers.d.ts +3 -0
  130. package/dist/tools/handlers/effect-handlers.js +171 -0
  131. package/dist/tools/handlers/environment-handlers.d.ts +3 -0
  132. package/dist/tools/handlers/environment-handlers.js +170 -0
  133. package/dist/tools/handlers/graph-handlers.d.ts +3 -0
  134. package/dist/tools/handlers/graph-handlers.js +90 -0
  135. package/dist/tools/handlers/input-handlers.d.ts +3 -0
  136. package/dist/tools/handlers/input-handlers.js +21 -0
  137. package/dist/tools/handlers/inspect-handlers.d.ts +3 -0
  138. package/dist/tools/handlers/inspect-handlers.js +383 -0
  139. package/dist/tools/handlers/level-handlers.d.ts +3 -0
  140. package/dist/tools/handlers/level-handlers.js +237 -0
  141. package/dist/tools/handlers/lighting-handlers.d.ts +3 -0
  142. package/dist/tools/handlers/lighting-handlers.js +144 -0
  143. package/dist/tools/handlers/performance-handlers.d.ts +3 -0
  144. package/dist/tools/handlers/performance-handlers.js +130 -0
  145. package/dist/tools/handlers/pipeline-handlers.d.ts +3 -0
  146. package/dist/tools/handlers/pipeline-handlers.js +110 -0
  147. package/dist/tools/handlers/sequence-handlers.d.ts +3 -0
  148. package/dist/tools/handlers/sequence-handlers.js +376 -0
  149. package/dist/tools/handlers/system-handlers.d.ts +4 -0
  150. package/dist/tools/handlers/system-handlers.js +506 -0
  151. package/dist/tools/input.d.ts +19 -0
  152. package/dist/tools/input.js +89 -0
  153. package/dist/tools/introspection.d.ts +103 -40
  154. package/dist/tools/introspection.js +425 -568
  155. package/dist/tools/landscape.d.ts +54 -93
  156. package/dist/tools/landscape.js +284 -409
  157. package/dist/tools/level.d.ts +66 -27
  158. package/dist/tools/level.js +647 -675
  159. package/dist/tools/lighting.d.ts +77 -38
  160. package/dist/tools/lighting.js +445 -943
  161. package/dist/tools/logs.d.ts +3 -3
  162. package/dist/tools/logs.js +5 -57
  163. package/dist/tools/materials.d.ts +91 -24
  164. package/dist/tools/materials.js +194 -118
  165. package/dist/tools/niagara.d.ts +149 -39
  166. package/dist/tools/niagara.js +267 -182
  167. package/dist/tools/performance.d.ts +27 -13
  168. package/dist/tools/performance.js +203 -122
  169. package/dist/tools/physics.d.ts +32 -77
  170. package/dist/tools/physics.js +175 -582
  171. package/dist/tools/property-dictionary.d.ts +13 -0
  172. package/dist/tools/property-dictionary.js +82 -0
  173. package/dist/tools/sequence.d.ts +85 -60
  174. package/dist/tools/sequence.js +208 -747
  175. package/dist/tools/tool-definition-utils.d.ts +59 -0
  176. package/dist/tools/tool-definition-utils.js +35 -0
  177. package/dist/tools/ui.d.ts +64 -34
  178. package/dist/tools/ui.js +134 -214
  179. package/dist/types/automation-responses.d.ts +115 -0
  180. package/dist/types/automation-responses.js +2 -0
  181. package/dist/types/env.d.ts +0 -3
  182. package/dist/types/env.js +0 -7
  183. package/dist/types/responses.d.ts +249 -0
  184. package/dist/types/responses.js +2 -0
  185. package/dist/types/tool-interfaces.d.ts +898 -0
  186. package/dist/types/tool-interfaces.js +2 -0
  187. package/dist/types/tool-types.d.ts +183 -19
  188. package/dist/types/tool-types.js +0 -4
  189. package/dist/unreal-bridge.d.ts +24 -131
  190. package/dist/unreal-bridge.js +364 -1506
  191. package/dist/utils/command-validator.d.ts +9 -0
  192. package/dist/utils/command-validator.js +68 -0
  193. package/dist/utils/elicitation.d.ts +1 -1
  194. package/dist/utils/elicitation.js +12 -15
  195. package/dist/utils/error-handler.d.ts +2 -51
  196. package/dist/utils/error-handler.js +11 -87
  197. package/dist/utils/ini-reader.d.ts +3 -0
  198. package/dist/utils/ini-reader.js +69 -0
  199. package/dist/utils/logger.js +9 -6
  200. package/dist/utils/normalize.d.ts +3 -0
  201. package/dist/utils/normalize.js +56 -0
  202. package/dist/utils/path-security.d.ts +2 -0
  203. package/dist/utils/path-security.js +24 -0
  204. package/dist/utils/response-factory.d.ts +7 -0
  205. package/dist/utils/response-factory.js +27 -0
  206. package/dist/utils/response-validator.d.ts +3 -24
  207. package/dist/utils/response-validator.js +130 -81
  208. package/dist/utils/result-helpers.d.ts +4 -5
  209. package/dist/utils/result-helpers.js +15 -16
  210. package/dist/utils/safe-json.js +5 -11
  211. package/dist/utils/unreal-command-queue.d.ts +24 -0
  212. package/dist/utils/unreal-command-queue.js +120 -0
  213. package/dist/utils/validation.d.ts +0 -40
  214. package/dist/utils/validation.js +1 -78
  215. package/dist/wasm/index.d.ts +70 -0
  216. package/dist/wasm/index.js +535 -0
  217. package/docs/GraphQL-API.md +888 -0
  218. package/docs/Migration-Guide-v0.5.0.md +684 -0
  219. package/docs/Roadmap.md +53 -0
  220. package/docs/WebAssembly-Integration.md +628 -0
  221. package/docs/editor-plugin-extension.md +370 -0
  222. package/docs/handler-mapping.md +242 -0
  223. package/docs/native-automation-progress.md +128 -0
  224. package/docs/testing-guide.md +423 -0
  225. package/mcp-config-example.json +6 -6
  226. package/package.json +67 -28
  227. package/plugins/McpAutomationBridge/Config/FilterPlugin.ini +8 -0
  228. package/plugins/McpAutomationBridge/McpAutomationBridge.uplugin +64 -0
  229. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/McpAutomationBridge.Build.cs +189 -0
  230. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.cpp +22 -0
  231. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.h +30 -0
  232. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeHelpers.h +1983 -0
  233. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeModule.cpp +72 -0
  234. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSettings.cpp +46 -0
  235. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +581 -0
  236. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +2394 -0
  237. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetQueryHandlers.cpp +300 -0
  238. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetWorkflowHandlers.cpp +2807 -0
  239. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AudioHandlers.cpp +1087 -0
  240. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BehaviorTreeHandlers.cpp +488 -0
  241. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.cpp +643 -0
  242. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.h +31 -0
  243. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +1184 -0
  244. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +5652 -0
  245. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers_List.cpp +152 -0
  246. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ControlHandlers.cpp +2614 -0
  247. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_DebugHandlers.cpp +42 -0
  248. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EditorFunctionHandlers.cpp +1237 -0
  249. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +1701 -0
  250. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +2145 -0
  251. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_FoliageHandlers.cpp +954 -0
  252. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InputHandlers.cpp +209 -0
  253. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InsightsHandlers.cpp +41 -0
  254. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LandscapeHandlers.cpp +1164 -0
  255. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LevelHandlers.cpp +762 -0
  256. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +634 -0
  257. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LogHandlers.cpp +136 -0
  258. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_MaterialGraphHandlers.cpp +494 -0
  259. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraGraphHandlers.cpp +278 -0
  260. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraHandlers.cpp +625 -0
  261. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PerformanceHandlers.cpp +401 -0
  262. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PipelineHandlers.cpp +67 -0
  263. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +735 -0
  264. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PropertyHandlers.cpp +2634 -0
  265. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_RenderHandlers.cpp +189 -0
  266. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.cpp +917 -0
  267. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.h +39 -0
  268. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +2670 -0
  269. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequencerHandlers.cpp +519 -0
  270. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_TestHandlers.cpp +38 -0
  271. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_UiHandlers.cpp +668 -0
  272. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_WorldPartitionHandlers.cpp +346 -0
  273. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +1330 -0
  274. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.h +149 -0
  275. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +783 -0
  276. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSettings.h +115 -0
  277. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSubsystem.h +796 -0
  278. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpConnectionManager.h +117 -0
  279. package/scripts/check-unreal-connection.mjs +19 -0
  280. package/scripts/clean-tmp.js +23 -0
  281. package/scripts/patch-wasm.js +26 -0
  282. package/scripts/run-all-tests.mjs +136 -0
  283. package/scripts/smoke-test.ts +94 -0
  284. package/scripts/sync-mcp-plugin.js +143 -0
  285. package/scripts/test-no-plugin-alternates.mjs +113 -0
  286. package/scripts/validate-server.js +46 -0
  287. package/scripts/verify-automation-bridge.js +200 -0
  288. package/server.json +58 -21
  289. package/src/automation/bridge.ts +558 -0
  290. package/src/automation/connection-manager.ts +130 -0
  291. package/src/automation/handshake.ts +99 -0
  292. package/src/automation/index.ts +2 -0
  293. package/src/automation/message-handler.ts +167 -0
  294. package/src/automation/request-tracker.ts +123 -0
  295. package/src/automation/types.ts +107 -0
  296. package/src/cli.ts +33 -6
  297. package/src/config.ts +73 -0
  298. package/src/constants.ts +19 -0
  299. package/src/graphql/loaders.ts +244 -0
  300. package/src/graphql/resolvers.ts +1008 -0
  301. package/src/graphql/schema.ts +452 -0
  302. package/src/graphql/server.ts +156 -0
  303. package/src/graphql/types.ts +10 -0
  304. package/src/handlers/resource-handlers.ts +186 -0
  305. package/src/index.ts +166 -664
  306. package/src/resources/actors.ts +58 -76
  307. package/src/resources/assets.ts +148 -134
  308. package/src/resources/levels.ts +28 -33
  309. package/src/server/resource-registry.ts +47 -0
  310. package/src/server/tool-registry.ts +354 -0
  311. package/src/server-setup.ts +114 -0
  312. package/src/services/health-monitor.ts +132 -0
  313. package/src/services/metrics-server.ts +142 -0
  314. package/src/tools/actors.ts +426 -323
  315. package/src/tools/animation.ts +672 -461
  316. package/src/tools/assets.ts +364 -289
  317. package/src/tools/audio.ts +323 -766
  318. package/src/tools/base-tool.ts +52 -0
  319. package/src/tools/behavior-tree.ts +45 -0
  320. package/src/tools/blueprint.ts +792 -970
  321. package/src/tools/consolidated-tool-definitions.ts +993 -515
  322. package/src/tools/consolidated-tool-handlers.ts +258 -1146
  323. package/src/tools/debug.ts +292 -187
  324. package/src/tools/dynamic-handler-registry.ts +33 -0
  325. package/src/tools/editor.ts +329 -253
  326. package/src/tools/engine.ts +14 -3
  327. package/src/tools/environment.ts +281 -0
  328. package/src/tools/foliage.ts +330 -392
  329. package/src/tools/handlers/actor-handlers.ts +265 -0
  330. package/src/tools/handlers/animation-handlers.ts +237 -0
  331. package/src/tools/handlers/argument-helper.ts +142 -0
  332. package/src/tools/handlers/asset-handlers.ts +532 -0
  333. package/src/tools/handlers/audio-handlers.ts +194 -0
  334. package/src/tools/handlers/blueprint-handlers.ts +380 -0
  335. package/src/tools/handlers/common-handlers.ts +87 -0
  336. package/src/tools/handlers/editor-handlers.ts +123 -0
  337. package/src/tools/handlers/effect-handlers.ts +220 -0
  338. package/src/tools/handlers/environment-handlers.ts +183 -0
  339. package/src/tools/handlers/graph-handlers.ts +116 -0
  340. package/src/tools/handlers/input-handlers.ts +28 -0
  341. package/src/tools/handlers/inspect-handlers.ts +450 -0
  342. package/src/tools/handlers/level-handlers.ts +252 -0
  343. package/src/tools/handlers/lighting-handlers.ts +147 -0
  344. package/src/tools/handlers/performance-handlers.ts +132 -0
  345. package/src/tools/handlers/pipeline-handlers.ts +127 -0
  346. package/src/tools/handlers/sequence-handlers.ts +415 -0
  347. package/src/tools/handlers/system-handlers.ts +564 -0
  348. package/src/tools/input.ts +101 -0
  349. package/src/tools/introspection.ts +493 -584
  350. package/src/tools/landscape.ts +418 -507
  351. package/src/tools/level.ts +786 -708
  352. package/src/tools/lighting.ts +588 -984
  353. package/src/tools/logs.ts +9 -57
  354. package/src/tools/materials.ts +237 -121
  355. package/src/tools/niagara.ts +335 -168
  356. package/src/tools/performance.ts +320 -169
  357. package/src/tools/physics.ts +274 -613
  358. package/src/tools/property-dictionary.ts +98 -0
  359. package/src/tools/sequence.ts +276 -820
  360. package/src/tools/tool-definition-utils.ts +35 -0
  361. package/src/tools/ui.ts +205 -283
  362. package/src/types/automation-responses.ts +119 -0
  363. package/src/types/env.ts +0 -10
  364. package/src/types/responses.ts +355 -0
  365. package/src/types/tool-interfaces.ts +250 -0
  366. package/src/types/tool-types.ts +243 -21
  367. package/src/unreal-bridge.ts +460 -1550
  368. package/src/utils/command-validator.ts +76 -0
  369. package/src/utils/elicitation.ts +10 -7
  370. package/src/utils/error-handler.ts +14 -90
  371. package/src/utils/ini-reader.ts +86 -0
  372. package/src/utils/logger.ts +8 -3
  373. package/src/utils/normalize.test.ts +162 -0
  374. package/src/utils/normalize.ts +60 -0
  375. package/src/utils/path-security.ts +43 -0
  376. package/src/utils/response-factory.ts +44 -0
  377. package/src/utils/response-validator.ts +176 -56
  378. package/src/utils/result-helpers.ts +21 -19
  379. package/src/utils/safe-json.test.ts +90 -0
  380. package/src/utils/safe-json.ts +14 -11
  381. package/src/utils/unreal-command-queue.ts +152 -0
  382. package/src/utils/validation.test.ts +184 -0
  383. package/src/utils/validation.ts +4 -1
  384. package/src/wasm/index.ts +838 -0
  385. package/test-server.mjs +100 -0
  386. package/tests/run-unreal-tool-tests.mjs +242 -14
  387. package/tests/test-animation.mjs +369 -0
  388. package/tests/test-asset-advanced.mjs +82 -0
  389. package/tests/test-asset-errors.mjs +35 -0
  390. package/tests/test-asset-graph.mjs +311 -0
  391. package/tests/test-audio.mjs +417 -0
  392. package/tests/test-automation-timeouts.mjs +98 -0
  393. package/tests/test-behavior-tree.mjs +444 -0
  394. package/tests/test-blueprint-graph.mjs +410 -0
  395. package/tests/test-blueprint.mjs +577 -0
  396. package/tests/test-client-mode.mjs +86 -0
  397. package/tests/test-console-command.mjs +56 -0
  398. package/tests/test-control-actor.mjs +425 -0
  399. package/tests/test-control-editor.mjs +112 -0
  400. package/tests/test-graphql.mjs +372 -0
  401. package/tests/test-input.mjs +349 -0
  402. package/tests/test-inspect.mjs +302 -0
  403. package/tests/test-landscape.mjs +316 -0
  404. package/tests/test-lighting.mjs +428 -0
  405. package/tests/test-manage-asset.mjs +438 -0
  406. package/tests/test-manage-level.mjs +89 -0
  407. package/tests/test-materials.mjs +356 -0
  408. package/tests/test-niagara.mjs +185 -0
  409. package/tests/test-no-inline-python.mjs +122 -0
  410. package/tests/test-performance.mjs +539 -0
  411. package/tests/test-plugin-handshake.mjs +82 -0
  412. package/tests/test-runner.mjs +933 -0
  413. package/tests/test-sequence.mjs +104 -0
  414. package/tests/test-system.mjs +96 -0
  415. package/tests/test-wasm.mjs +283 -0
  416. package/tests/test-world-partition.mjs +215 -0
  417. package/tsconfig.json +3 -3
  418. package/vitest.config.ts +35 -0
  419. package/wasm/Cargo.lock +363 -0
  420. package/wasm/Cargo.toml +42 -0
  421. package/wasm/LICENSE +21 -0
  422. package/wasm/README.md +253 -0
  423. package/wasm/src/dependency_resolver.rs +377 -0
  424. package/wasm/src/lib.rs +153 -0
  425. package/wasm/src/property_parser.rs +271 -0
  426. package/wasm/src/transform_math.rs +396 -0
  427. package/wasm/tests/integration.rs +109 -0
  428. package/.github/workflows/smithery-build.yml +0 -29
  429. package/dist/prompts/index.d.ts +0 -21
  430. package/dist/prompts/index.js +0 -217
  431. package/dist/tools/build_environment_advanced.d.ts +0 -65
  432. package/dist/tools/build_environment_advanced.js +0 -633
  433. package/dist/tools/rc.d.ts +0 -110
  434. package/dist/tools/rc.js +0 -437
  435. package/dist/tools/visual.d.ts +0 -40
  436. package/dist/tools/visual.js +0 -282
  437. package/dist/utils/http.d.ts +0 -6
  438. package/dist/utils/http.js +0 -151
  439. package/dist/utils/python-output.d.ts +0 -18
  440. package/dist/utils/python-output.js +0 -290
  441. package/dist/utils/python.d.ts +0 -2
  442. package/dist/utils/python.js +0 -4
  443. package/dist/utils/stdio-redirect.d.ts +0 -2
  444. package/dist/utils/stdio-redirect.js +0 -20
  445. package/docs/unreal-tool-test-cases.md +0 -574
  446. package/smithery.yaml +0 -29
  447. package/src/prompts/index.ts +0 -249
  448. package/src/tools/build_environment_advanced.ts +0 -732
  449. package/src/tools/rc.ts +0 -515
  450. package/src/tools/visual.ts +0 -281
  451. package/src/utils/http.ts +0 -187
  452. package/src/utils/python-output.ts +0 -351
  453. package/src/utils/python.ts +0 -3
  454. package/src/utils/stdio-redirect.ts +0 -18
@@ -1,81 +1,41 @@
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
+ import { wasmIntegration } from '../wasm/index.js';
3
4
  export class PhysicsTools {
4
5
  bridge;
5
- constructor(bridge) {
6
+ automationBridge;
7
+ constructor(bridge, automationBridge) {
6
8
  this.bridge = bridge;
9
+ this.automationBridge = automationBridge;
7
10
  }
8
- /**
9
- * Helper to find a valid skeletal mesh in the project
10
- */
11
+ setAutomationBridge(automationBridge) { this.automationBridge = automationBridge; }
11
12
  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
- `;
13
+ if (!this.automationBridge) {
14
+ return '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple';
15
+ }
60
16
  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'
17
+ const response = await this.automationBridge.sendAutomationRequest('find_skeletal_mesh', {
18
+ commonPaths: [
19
+ '/Game/Characters/Mannequins/Meshes/SKM_Manny',
20
+ '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
21
+ '/Game/Characters/Mannequins/Meshes/SKM_Manny_Complex',
22
+ '/Game/Characters/Mannequins/Meshes/SKM_Quinn',
23
+ '/Game/Characters/Mannequins/Meshes/SKM_Quinn_Simple',
24
+ '/Game/Characters/Mannequins/Meshes/SKM_Quinn_Complex'
25
+ ],
26
+ fallback: '/Engine/EngineMeshes/SkeletalCube'
27
+ }, {
28
+ timeoutMs: 30000
65
29
  });
66
- if (interpreted.success) {
67
- const meshPath = coerceString(interpreted.payload.meshPath);
30
+ if (response.success !== false && response.result) {
31
+ const meshPath = coerceString(response.result.meshPath);
68
32
  if (meshPath) {
69
33
  return meshPath;
70
34
  }
71
35
  }
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);
36
+ const alternate = coerceString(response.result?.alternate);
37
+ if (alternate) {
38
+ return alternate;
79
39
  }
80
40
  }
81
41
  catch (error) {
@@ -83,16 +43,8 @@ print('RESULT:' + json.dumps(result))
83
43
  }
84
44
  return '/Engine/EngineMeshes/SkeletalCube';
85
45
  }
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
46
  async setupRagdoll(params) {
94
47
  try {
95
- // Strong validation for physics asset name
96
48
  if (!params.physicsAssetName || params.physicsAssetName.trim() === '') {
97
49
  return {
98
50
  success: false,
@@ -100,7 +52,6 @@ print('RESULT:' + json.dumps(result))
100
52
  error: 'Name cannot be empty'
101
53
  };
102
54
  }
103
- // Check for invalid characters in name
104
55
  if (params.physicsAssetName.includes('@') || params.physicsAssetName.includes('#') ||
105
56
  params.physicsAssetName.includes('$') || params.physicsAssetName.includes('%')) {
106
57
  return {
@@ -109,7 +60,6 @@ print('RESULT:' + json.dumps(result))
109
60
  error: 'Name contains invalid characters'
110
61
  };
111
62
  }
112
- // Check if skeleton path is provided instead of skeletal mesh
113
63
  if (params.skeletonPath && (params.skeletonPath.includes('_Skeleton') ||
114
64
  params.skeletonPath.includes('SK_Mannequin') && !params.skeletonPath.includes('SKM_'))) {
115
65
  return {
@@ -118,7 +68,6 @@ print('RESULT:' + json.dumps(result))
118
68
  error: 'Must specify a valid skeletal mesh, not a skeleton'
119
69
  };
120
70
  }
121
- // Validate and sanitize parameters
122
71
  const validation = validateAssetParams({
123
72
  name: params.physicsAssetName,
124
73
  savePath: params.savePath || '/Game/Physics'
@@ -132,15 +81,12 @@ print('RESULT:' + json.dumps(result))
132
81
  }
133
82
  const sanitizedParams = validation.sanitized;
134
83
  const path = sanitizedParams.savePath || '/Game/Physics';
135
- // Resolve skeletal mesh path
136
84
  let meshPath = params.skeletonPath;
137
- // Try to resolve skeleton to mesh mapping
138
85
  const resolvedPath = resolveSkeletalMeshPath(meshPath);
139
86
  if (resolvedPath && resolvedPath !== meshPath) {
140
87
  console.error(`Auto-correcting path from ${meshPath} to ${resolvedPath}`);
141
88
  meshPath = resolvedPath;
142
89
  }
143
- // Auto-resolve if it looks like a skeleton path or is empty
144
90
  if (!meshPath || meshPath.includes('_Skeleton') || meshPath === 'None' || meshPath === '') {
145
91
  console.error('Resolving skeletal mesh path...');
146
92
  const resolvedMesh = await this.findValidSkeletalMesh();
@@ -149,13 +95,7 @@ print('RESULT:' + json.dumps(result))
149
95
  console.error(`Using resolved skeletal mesh: ${meshPath}`);
150
96
  }
151
97
  }
152
- // Add concurrency delay to prevent race conditions
153
98
  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
99
  const skeletonToMeshMap = {
160
100
  '/Game/Mannequin/Character/Mesh/UE4_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
161
101
  '/Game/Characters/Mannequins/Meshes/SK_Mannequin': '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
@@ -163,337 +103,41 @@ print('RESULT:' + json.dumps(result))
163
103
  '/Game/Characters/Mannequins/Skeletons/UE5_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
164
104
  '/Game/Characters/Mannequins/Skeletons/UE5_Female_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Quinn_Simple'
165
105
  };
166
- // Auto-fix common incorrect paths
167
106
  let actualSkeletonPath = params.skeletonPath;
168
107
  if (actualSkeletonPath && skeletonToMeshMap[actualSkeletonPath]) {
169
108
  console.error(`Auto-correcting path from ${actualSkeletonPath} to ${skeletonToMeshMap[actualSkeletonPath]}`);
170
109
  actualSkeletonPath = skeletonToMeshMap[actualSkeletonPath];
171
110
  }
172
111
  if (actualSkeletonPath && (actualSkeletonPath.includes('_Skeleton') || actualSkeletonPath.includes('SK_Mannequin'))) {
173
- // This is likely a skeleton path, not a skeletal mesh
174
112
  console.error('Warning: Path appears to be a skeleton, not a skeletal mesh. Auto-correcting to SKM_Manny_Simple.');
175
113
  }
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
114
+ if (!this.automationBridge) {
115
+ throw new Error('Automation Bridge not available. Physics asset creation requires plugin support.');
116
+ }
465
117
  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}`
118
+ const response = await this.automationBridge.sendAutomationRequest('setup_ragdoll', {
119
+ meshPath,
120
+ physicsAssetName: sanitizedParams.name,
121
+ savePath: path,
122
+ blendWeight: params.blendWeight,
123
+ constraints: params.constraints
124
+ }, {
125
+ timeoutMs: 120000
470
126
  });
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}`
127
+ if (response.success === false) {
128
+ return {
129
+ success: false,
130
+ message: response.error || response.message || `Failed to setup ragdoll for ${sanitizedParams.name}`,
131
+ error: response.error || response.message || 'Failed to setup ragdoll'
478
132
  };
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
133
  }
490
- const errorMessage = interpreted.error ?? `Failed to setup ragdoll for ${sanitizedParams.name}`;
134
+ const result = response.result;
491
135
  return {
492
- success: false,
493
- message: errorMessage,
494
- error: errorMessage,
495
- warnings: warnings.length > 0 ? warnings : undefined,
496
- details: details.length > 0 ? details : undefined
136
+ success: true,
137
+ message: response.message || `Ragdoll physics setup completed for ${sanitizedParams.name}`,
138
+ path: coerceString(result?.path) ?? coerceString(result?.physicsAssetPath) ?? `${path}/${sanitizedParams.name}`,
139
+ existingAsset: result?.existingAsset,
140
+ ...(result || {})
497
141
  };
498
142
  }
499
143
  catch (error) {
@@ -508,15 +152,10 @@ print('RESULT:' + json.dumps(result))
508
152
  return { success: false, error: `Failed to setup ragdoll: ${err}` };
509
153
  }
510
154
  }
511
- /**
512
- * Create Physics Constraint
513
- */
514
155
  async createConstraint(params) {
515
156
  try {
516
- // Spawn constraint actor
517
157
  const spawnCmd = `spawnactor /Script/Engine.PhysicsConstraintActor ${params.location[0]} ${params.location[1]} ${params.location[2]}`;
518
158
  await this.bridge.executeConsoleCommand(spawnCmd);
519
- // Configure constraint
520
159
  const commands = [
521
160
  `SetConstraintActors ${params.name} ${params.actor1} ${params.actor2}`,
522
161
  `SetConstraintType ${params.name} ${params.constraintType}`
@@ -549,25 +188,19 @@ print('RESULT:' + json.dumps(result))
549
188
  return { success: false, error: `Failed to create constraint: ${err}` };
550
189
  }
551
190
  }
552
- /**
553
- * Setup Chaos Destruction
554
- */
555
191
  async setupDestruction(params) {
556
192
  try {
557
193
  const path = params.savePath || '/Game/Destruction';
558
194
  const commands = [
559
195
  `CreateGeometryCollection ${params.destructionName} ${params.meshPath} ${path}`
560
196
  ];
561
- // Configure fracture
562
197
  if (params.fractureSettings) {
563
198
  const settings = params.fractureSettings;
564
199
  commands.push(`FractureGeometry ${params.destructionName} ${settings.cellCount} ${settings.minimumVolumeSize} ${settings.seed}`);
565
200
  }
566
- // Set damage threshold
567
201
  if (params.damageThreshold) {
568
202
  commands.push(`SetDamageThreshold ${params.destructionName} ${params.damageThreshold}`);
569
203
  }
570
- // Set debris lifetime
571
204
  if (params.debrisLifetime) {
572
205
  commands.push(`SetDebrisLifetime ${params.destructionName} ${params.debrisLifetime}`);
573
206
  }
@@ -582,199 +215,134 @@ print('RESULT:' + json.dumps(result))
582
215
  return { success: false, error: `Failed to setup destruction: ${err}` };
583
216
  }
584
217
  }
585
- /**
586
- * Configure Vehicle Physics
587
- */
588
218
  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
- }
219
+ const rawParams = params;
220
+ const pluginDeps = Array.isArray(params.pluginDependencies) && params.pluginDependencies.length > 0
221
+ ? params.pluginDependencies
222
+ : (Array.isArray(rawParams.plugins) && rawParams.plugins.length > 0 ? rawParams.plugins : undefined);
223
+ if (pluginDeps && pluginDeps.length > 0) {
224
+ return {
225
+ success: false,
226
+ error: 'MISSING_ENGINE_PLUGINS',
227
+ missingPlugins: pluginDeps,
228
+ message: `Required engine plugins not enabled: ${pluginDeps.join(', ')}`
229
+ };
230
+ }
231
+ const warnings = [];
232
+ const hasExplicitEmptyWheels = Array.isArray(params.wheels) && params.wheels.length === 0;
233
+ const effectiveVehicleType = typeof params.vehicleType === 'string' && params.vehicleType.trim().length > 0
234
+ ? params.vehicleType
235
+ : 'Car';
236
+ const commands = [
237
+ `CreateVehicle ${params.vehicleName} ${effectiveVehicleType}`
238
+ ];
239
+ if (Array.isArray(params.wheels) && params.wheels.length > 0) {
240
+ for (const wheel of params.wheels) {
241
+ commands.push(`AddVehicleWheel ${params.vehicleName} ${wheel.name} ${wheel.radius} ${wheel.width} ${wheel.mass}`);
242
+ if (wheel.isSteering) {
243
+ commands.push(`SetWheelSteering ${params.vehicleName} ${wheel.name} true`);
603
244
  }
245
+ if (wheel.isDriving) {
246
+ commands.push(`SetWheelDriving ${params.vehicleName} ${wheel.name} true`);
247
+ }
248
+ }
249
+ }
250
+ const effectiveEngine = params.engine ?? ((typeof rawParams.maxRPM === 'number' || Array.isArray(rawParams.torqueCurve))
251
+ ? { maxRPM: rawParams.maxRPM, torqueCurve: rawParams.torqueCurve }
252
+ : undefined);
253
+ if (effectiveEngine) {
254
+ let maxRPM = typeof effectiveEngine.maxRPM === 'number' ? effectiveEngine.maxRPM : 0;
255
+ if (maxRPM < 0) {
256
+ maxRPM = 0;
257
+ warnings.push('Engine maxRPM was negative and has been clamped to 0.');
604
258
  }
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) {
259
+ commands.push(`SetEngineMaxRPM ${params.vehicleName} ${maxRPM}`);
260
+ const rawCurve = Array.isArray(effectiveEngine.torqueCurve) ? effectiveEngine.torqueCurve : [];
261
+ for (const point of rawCurve) {
262
+ let rpm;
263
+ let torque;
264
+ if (Array.isArray(point) && point.length >= 2) {
265
+ rpm = Number(point[0]);
266
+ torque = Number(point[1]);
267
+ }
268
+ else if (point && typeof point === 'object') {
269
+ const anyPoint = point;
270
+ rpm = typeof anyPoint.rpm === 'number' ? anyPoint.rpm : undefined;
271
+ torque = typeof anyPoint.torque === 'number' ? anyPoint.torque : undefined;
272
+ }
273
+ if (typeof rpm === 'number' && typeof torque === 'number') {
609
274
  commands.push(`AddTorqueCurvePoint ${params.vehicleName} ${rpm} ${torque}`);
610
275
  }
611
276
  }
612
- // Configure transmission
613
- if (params.transmission) {
277
+ }
278
+ if (params.transmission) {
279
+ if (Array.isArray(params.transmission.gears)) {
614
280
  for (let i = 0; i < params.transmission.gears.length; i++) {
615
281
  commands.push(`SetGearRatio ${params.vehicleName} ${i} ${params.transmission.gears[i]}`);
616
282
  }
283
+ }
284
+ if (typeof params.transmission.finalDriveRatio === 'number') {
617
285
  commands.push(`SetFinalDriveRatio ${params.vehicleName} ${params.transmission.finalDriveRatio}`);
618
286
  }
287
+ }
288
+ try {
619
289
  await this.bridge.executeConsoleCommands(commands);
620
- return {
621
- success: true,
622
- message: `Vehicle ${params.vehicleName} configured`
623
- };
624
290
  }
625
- catch (err) {
626
- return { success: false, error: `Failed to configure vehicle: ${err}` };
291
+ catch (_error) {
292
+ if (warnings.length === 0) {
293
+ warnings.push('Vehicle configuration commands could not be executed; using engine defaults.');
294
+ }
627
295
  }
296
+ if (hasExplicitEmptyWheels) {
297
+ warnings.push('No wheels specified; using default wheels from vehicle preset.');
298
+ }
299
+ if (warnings.length === 0) {
300
+ warnings.push('Verify wheel class assignments and offsets in the vehicle movement component to ensure they match your project defaults.');
301
+ }
302
+ return {
303
+ success: true,
304
+ message: `Vehicle ${params.vehicleName} configured`,
305
+ warnings
306
+ };
628
307
  }
629
- /**
630
- * Apply Force or Impulse to Actor
631
- */
632
308
  async applyForce(params) {
309
+ if (!this.automationBridge) {
310
+ throw new Error('Automation Bridge not available. Physics force application requires plugin support.');
311
+ }
633
312
  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'
313
+ const zeroVector = [0, 0, 0];
314
+ const normalizedVector = wasmIntegration.vectorAdd(zeroVector, params.vector);
315
+ console.error('[WASM] Using vectorAdd for physics force vector processing');
316
+ const response = await this.automationBridge.sendAutomationRequest('apply_force', {
317
+ actorName: params.actorName,
318
+ forceType: params.forceType,
319
+ vector: normalizedVector,
320
+ boneName: params.boneName,
321
+ isLocal: params.isLocal
322
+ }, {
323
+ timeoutMs: 30000
737
324
  });
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)) {
325
+ if (response.success === false) {
326
+ const result = response.result;
757
327
  return {
758
328
  success: false,
759
- error: interpreted.error ?? (fallbackText || 'Force application failed'),
760
- availableActors,
761
- details: interpreted.details ?? (fallbackText ? [fallbackText] : undefined)
329
+ error: response.error || response.message || 'Force application failed',
330
+ availableActors: result?.available_actors ? coerceStringArray(result.available_actors) : undefined,
331
+ details: result?.details
762
332
  };
763
333
  }
334
+ const result = response.result;
764
335
  return {
765
- success: false,
766
- error: interpreted.error ?? 'No valid result from Python',
767
- availableActors,
768
- details: interpreted.details ?? (fallbackText ? [fallbackText] : undefined)
336
+ success: true,
337
+ message: response.message || `Applied ${params.forceType} to ${params.actorName}`,
338
+ availableActors: result?.available_actors ? coerceStringArray(result.available_actors) : undefined,
339
+ ...(result || {})
769
340
  };
770
341
  }
771
342
  catch (err) {
772
343
  return { success: false, error: `Failed to apply force: ${err}` };
773
344
  }
774
345
  }
775
- /**
776
- * Configure Cloth Simulation
777
- */
778
346
  async setupCloth(params) {
779
347
  try {
780
348
  const commands = [
@@ -813,9 +381,6 @@ print(f"RESULT:{json.dumps(result)}")
813
381
  return { success: false, error: `Failed to setup cloth: ${err}` };
814
382
  }
815
383
  }
816
- /**
817
- * Create Fluid Simulation (Niagara-based)
818
- */
819
384
  async createFluidSimulation(params) {
820
385
  try {
821
386
  const locStr = `${params.location[0]} ${params.location[1]} ${params.location[2]}`;
@@ -852,5 +417,33 @@ print(f"RESULT:{json.dumps(result)}")
852
417
  return { success: false, error: `Failed to create fluid simulation: ${err}` };
853
418
  }
854
419
  }
420
+ async setupPhysicsSimulation(params) {
421
+ if (!this.automationBridge) {
422
+ throw new Error('Automation Bridge not available. Physics asset creation requires plugin support.');
423
+ }
424
+ try {
425
+ const response = await this.automationBridge.sendAutomationRequest('animation_physics', {
426
+ action: 'setup_physics_simulation',
427
+ ...params
428
+ }, {
429
+ timeoutMs: 60000
430
+ });
431
+ if (response.success === false) {
432
+ return {
433
+ success: false,
434
+ message: response.error || response.message || 'Failed to setup physics simulation',
435
+ error: response.error || response.message
436
+ };
437
+ }
438
+ return {
439
+ success: true,
440
+ message: response.message || 'Physics simulation setup completed',
441
+ ...(response.result || {})
442
+ };
443
+ }
444
+ catch (err) {
445
+ return { success: false, error: `Failed to setup physics simulation: ${err}` };
446
+ }
447
+ }
855
448
  }
856
449
  //# sourceMappingURL=physics.js.map