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,90 +1,55 @@
1
+ // Physics tools for Unreal Engine using Automation Bridge
1
2
  import { UnrealBridge } from '../unreal-bridge.js';
3
+ import { AutomationBridge } from '../automation/index.js';
2
4
  import { validateAssetParams, resolveSkeletalMeshPath, concurrencyDelay } from '../utils/validation.js';
3
- import { bestEffortInterpretedText, coerceString, coerceStringArray, interpretStandardResult } from '../utils/result-helpers.js';
5
+ import { coerceString, coerceStringArray } from '../utils/result-helpers.js';
6
+ import { wasmIntegration } from '../wasm/index.js';
4
7
 
5
8
  export class PhysicsTools {
6
- constructor(private bridge: UnrealBridge) {}
7
-
9
+ constructor(private bridge: UnrealBridge, private automationBridge?: AutomationBridge) { }
10
+
11
+ setAutomationBridge(automationBridge?: AutomationBridge) { this.automationBridge = automationBridge; }
12
+
8
13
  /**
9
14
  * Helper to find a valid skeletal mesh in the project
10
15
  */
11
16
  private async findValidSkeletalMesh(): Promise<string | null> {
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
- `;
17
+ if (!this.automationBridge) {
18
+ // Return common fallback paths without plugin
19
+ return '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple';
20
+ }
60
21
 
61
22
  try {
62
- const response = await this.bridge.executePython(pythonScript);
63
- const interpreted = interpretStandardResult(response, {
64
- successMessage: 'Skeletal mesh discovery complete',
65
- failureMessage: 'Failed to discover skeletal mesh'
23
+ const response = await this.automationBridge.sendAutomationRequest('find_skeletal_mesh', {
24
+ commonPaths: [
25
+ '/Game/Characters/Mannequins/Meshes/SKM_Manny',
26
+ '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
27
+ '/Game/Characters/Mannequins/Meshes/SKM_Manny_Complex',
28
+ '/Game/Characters/Mannequins/Meshes/SKM_Quinn',
29
+ '/Game/Characters/Mannequins/Meshes/SKM_Quinn_Simple',
30
+ '/Game/Characters/Mannequins/Meshes/SKM_Quinn_Complex'
31
+ ],
32
+ fallback: '/Engine/EngineMeshes/SkeletalCube'
33
+ }, {
34
+ timeoutMs: 30000
66
35
  });
67
36
 
68
- if (interpreted.success) {
69
- const meshPath = coerceString(interpreted.payload.meshPath);
37
+ if (response.success !== false && response.result) {
38
+ const meshPath = coerceString((response.result as any).meshPath);
70
39
  if (meshPath) {
71
40
  return meshPath;
72
41
  }
73
42
  }
74
43
 
75
- const fallback = coerceString(interpreted.payload.fallback);
76
- if (fallback) {
77
- return fallback;
78
- }
79
-
80
- const detail = bestEffortInterpretedText(interpreted);
81
- if (detail) {
82
- console.error('Failed to parse skeletal mesh discovery:', detail);
44
+ // Fallback to alternate path
45
+ const alternate = coerceString((response.result as any)?.alternate);
46
+ if (alternate) {
47
+ return alternate;
83
48
  }
84
49
  } catch (error) {
85
50
  console.error('Failed to find skeletal mesh:', error);
86
51
  }
87
-
52
+
88
53
  return '/Engine/EngineMeshes/SkeletalCube';
89
54
  }
90
55
 
@@ -119,33 +84,33 @@ print('RESULT:' + json.dumps(result))
119
84
  error: 'Name cannot be empty'
120
85
  };
121
86
  }
122
-
87
+
123
88
  // Check for invalid characters in name
124
- if (params.physicsAssetName.includes('@') || params.physicsAssetName.includes('#') ||
125
- params.physicsAssetName.includes('$') || params.physicsAssetName.includes('%')) {
89
+ if (params.physicsAssetName.includes('@') || params.physicsAssetName.includes('#') ||
90
+ params.physicsAssetName.includes('$') || params.physicsAssetName.includes('%')) {
126
91
  return {
127
92
  success: false,
128
93
  message: 'Failed to setup ragdoll: Name contains invalid characters',
129
94
  error: 'Name contains invalid characters'
130
95
  };
131
96
  }
132
-
97
+
133
98
  // Check if skeleton path is provided instead of skeletal mesh
134
- if (params.skeletonPath && (params.skeletonPath.includes('_Skeleton') ||
135
- params.skeletonPath.includes('SK_Mannequin') && !params.skeletonPath.includes('SKM_'))) {
99
+ if (params.skeletonPath && (params.skeletonPath.includes('_Skeleton') ||
100
+ params.skeletonPath.includes('SK_Mannequin') && !params.skeletonPath.includes('SKM_'))) {
136
101
  return {
137
102
  success: false,
138
103
  message: 'Failed to setup ragdoll: Must specify a valid skeletal mesh',
139
104
  error: 'Must specify a valid skeletal mesh, not a skeleton'
140
105
  };
141
106
  }
142
-
107
+
143
108
  // Validate and sanitize parameters
144
109
  const validation = validateAssetParams({
145
110
  name: params.physicsAssetName,
146
111
  savePath: params.savePath || '/Game/Physics'
147
112
  });
148
-
113
+
149
114
  if (!validation.valid) {
150
115
  return {
151
116
  success: false,
@@ -153,20 +118,20 @@ print('RESULT:' + json.dumps(result))
153
118
  error: validation.error
154
119
  };
155
120
  }
156
-
121
+
157
122
  const sanitizedParams = validation.sanitized;
158
123
  const path = sanitizedParams.savePath || '/Game/Physics';
159
-
124
+
160
125
  // Resolve skeletal mesh path
161
126
  let meshPath = params.skeletonPath;
162
-
127
+
163
128
  // Try to resolve skeleton to mesh mapping
164
129
  const resolvedPath = resolveSkeletalMeshPath(meshPath);
165
130
  if (resolvedPath && resolvedPath !== meshPath) {
166
131
  console.error(`Auto-correcting path from ${meshPath} to ${resolvedPath}`);
167
132
  meshPath = resolvedPath;
168
133
  }
169
-
134
+
170
135
  // Auto-resolve if it looks like a skeleton path or is empty
171
136
  if (!meshPath || meshPath.includes('_Skeleton') || meshPath === 'None' || meshPath === '') {
172
137
  console.error('Resolving skeletal mesh path...');
@@ -176,15 +141,15 @@ print('RESULT:' + json.dumps(result))
176
141
  console.error(`Using resolved skeletal mesh: ${meshPath}`);
177
142
  }
178
143
  }
179
-
144
+
180
145
  // Add concurrency delay to prevent race conditions
181
146
  await concurrencyDelay();
182
-
147
+
183
148
  // IMPORTANT: Physics assets require a SKELETAL MESH, not a skeleton
184
149
  // UE5 uses: /Game/Characters/Mannequins/Meshes/SKM_Manny_Simple or SKM_Quinn_Simple
185
150
  // UE4 used: /Game/Mannequin/Character/Mesh/SK_Mannequin (which no longer exists)
186
- // Fallback: /Engine/EngineMeshes/SkeletalCube
187
-
151
+ // Alternate path: /Engine/EngineMeshes/SkeletalCube
152
+
188
153
  // Common skeleton paths that should be replaced with actual skeletal mesh paths
189
154
  const skeletonToMeshMap: { [key: string]: string } = {
190
155
  '/Game/Mannequin/Character/Mesh/UE4_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
@@ -193,356 +158,50 @@ print('RESULT:' + json.dumps(result))
193
158
  '/Game/Characters/Mannequins/Skeletons/UE5_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
194
159
  '/Game/Characters/Mannequins/Skeletons/UE5_Female_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Quinn_Simple'
195
160
  };
196
-
161
+
197
162
  // Auto-fix common incorrect paths
198
163
  let actualSkeletonPath = params.skeletonPath;
199
164
  if (actualSkeletonPath && skeletonToMeshMap[actualSkeletonPath]) {
200
165
  console.error(`Auto-correcting path from ${actualSkeletonPath} to ${skeletonToMeshMap[actualSkeletonPath]}`);
201
166
  actualSkeletonPath = skeletonToMeshMap[actualSkeletonPath];
202
167
  }
203
-
168
+
204
169
  if (actualSkeletonPath && (actualSkeletonPath.includes('_Skeleton') || actualSkeletonPath.includes('SK_Mannequin'))) {
205
170
  // This is likely a skeleton path, not a skeletal mesh
206
171
  console.error('Warning: Path appears to be a skeleton, not a skeletal mesh. Auto-correcting to SKM_Manny_Simple.');
207
172
  }
208
-
209
- // Build Python script with resolved mesh path
210
- const pythonScript = `
211
- import unreal
212
- import time
213
- import json
214
-
215
- result = {
216
- "success": False,
217
- "path": None,
218
- "message": "",
219
- "error": None,
220
- "warnings": [],
221
- "details": [],
222
- "existingAsset": False,
223
- "meshPath": "${meshPath}"
224
- }
225
173
 
226
- def record_detail(message):
227
- result["details"].append(message)
228
-
229
- def record_warning(message):
230
- result["warnings"].append(message)
231
-
232
- def record_error(message):
233
- result["error"] = message
234
-
235
- # Helper function to ensure asset persistence
236
- def ensure_asset_persistence(asset_path):
237
- try:
238
- asset = unreal.EditorAssetLibrary.load_asset(asset_path)
239
- if not asset:
240
- record_warning(f"Asset persistence check failed: {asset_path} not loaded")
241
- return False
242
-
243
- # Save the asset
244
- saved = unreal.EditorAssetLibrary.save_asset(asset_path, only_if_is_dirty=False)
245
- if saved:
246
- print(f"Asset saved: {asset_path}")
247
- record_detail(f"Asset saved: {asset_path}")
248
-
249
- # Refresh the asset registry minimally for the asset's directory
250
- try:
251
- asset_dir = asset_path.rsplit('/', 1)[0]
252
- unreal.AssetRegistryHelpers.get_asset_registry().scan_paths_synchronous([asset_dir], True)
253
- except Exception as _reg_e:
254
- record_warning(f"Asset registry refresh warning: {_reg_e}")
255
-
256
- # Small delay to ensure filesystem sync
257
- time.sleep(0.1)
258
-
259
- return saved
260
- except Exception as e:
261
- print(f"Error ensuring persistence: {e}")
262
- record_error(f"Error ensuring persistence: {e}")
263
- return False
264
-
265
- # Stop PIE if running using modern subsystems
266
- try:
267
- level_subsystem = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
268
- play_subsystem = None
269
- try:
270
- play_subsystem = unreal.get_editor_subsystem(unreal.EditorPlayWorldSubsystem)
271
- except Exception:
272
- play_subsystem = None
273
-
274
- is_playing = False
275
- if level_subsystem and hasattr(level_subsystem, 'is_in_play_in_editor'):
276
- is_playing = level_subsystem.is_in_play_in_editor()
277
- elif play_subsystem and hasattr(play_subsystem, 'is_playing_in_editor'): # type: ignore[attr-defined]
278
- is_playing = play_subsystem.is_playing_in_editor() # type: ignore[attr-defined]
279
-
280
- if is_playing:
281
- print("Stopping Play In Editor mode...")
282
- record_detail("Stopping Play In Editor mode")
283
- if level_subsystem and hasattr(level_subsystem, 'editor_request_end_play'):
284
- level_subsystem.editor_request_end_play()
285
- elif play_subsystem and hasattr(play_subsystem, 'stop_playing_session'): # type: ignore[attr-defined]
286
- play_subsystem.stop_playing_session() # type: ignore[attr-defined]
287
- elif play_subsystem and hasattr(play_subsystem, 'end_play'): # type: ignore[attr-defined]
288
- play_subsystem.end_play() # type: ignore[attr-defined]
289
- else:
290
- record_warning('Unable to stop Play In Editor via modern subsystems; please stop PIE manually.')
291
- time.sleep(0.5)
292
- except Exception as pie_error:
293
- record_warning(f"PIE stop check failed: {pie_error}")
294
-
295
- # Main execution
296
- success = False
297
- error_msg = ""
298
- new_asset = None
299
-
300
- # Log the attempt
301
- print("Setting up ragdoll for ${meshPath}")
302
- record_detail("Setting up ragdoll for ${meshPath}")
303
-
304
- asset_path = "${path}"
305
- asset_name = "${sanitizedParams.name}"
306
- full_path = f"{asset_path}/{asset_name}"
307
-
308
- try:
309
- # Check if already exists
310
- if unreal.EditorAssetLibrary.does_asset_exist(full_path):
311
- print(f"Physics asset already exists at {full_path}")
312
- record_detail(f"Physics asset already exists at {full_path}")
313
- existing = unreal.EditorAssetLibrary.load_asset(full_path)
314
- if existing:
315
- print(f"Loaded existing PhysicsAsset: {full_path}")
316
- record_detail(f"Loaded existing PhysicsAsset: {full_path}")
317
- success = True
318
- result["existingAsset"] = True
319
- result["message"] = f"Physics asset already exists at {full_path}"
320
- else:
321
- # Try to load skeletal mesh first - it's required
322
- skeletal_mesh_path = "${meshPath}"
323
- skeletal_mesh = None
324
-
325
- if skeletal_mesh_path and skeletal_mesh_path != "None":
326
- if unreal.EditorAssetLibrary.does_asset_exist(skeletal_mesh_path):
327
- asset = unreal.EditorAssetLibrary.load_asset(skeletal_mesh_path)
328
- if asset:
329
- if isinstance(asset, unreal.SkeletalMesh):
330
- skeletal_mesh = asset
331
- print(f"Loaded skeletal mesh: {skeletal_mesh_path}")
332
- record_detail(f"Loaded skeletal mesh: {skeletal_mesh_path}")
333
- elif isinstance(asset, unreal.Skeleton):
334
- error_msg = f"Provided path is a skeleton, not a skeletal mesh: {skeletal_mesh_path}"
335
- print(f"Error: {error_msg}")
336
- record_error(error_msg)
337
- result["message"] = error_msg
338
- print("Error: Physics assets require a skeletal mesh, not just a skeleton")
339
- record_warning("Physics assets require a skeletal mesh, not just a skeleton")
340
- else:
341
- error_msg = f"Asset is not a skeletal mesh: {skeletal_mesh_path}"
342
- print(f"Warning: {error_msg}")
343
- record_warning(error_msg)
344
- else:
345
- error_msg = f"Skeletal mesh not found at {skeletal_mesh_path}"
346
- print(f"Error: {error_msg}")
347
- record_error(error_msg)
348
- result["message"] = error_msg
349
-
350
- if not skeletal_mesh:
351
- if not error_msg:
352
- error_msg = "Cannot create physics asset without a valid skeletal mesh"
353
- print(f"Error: {error_msg}")
354
- record_error(error_msg)
355
- if not result["message"]:
356
- result["message"] = error_msg
357
- else:
358
- # Create physics asset using a different approach
359
- # Method 1: Direct creation with initialized factory
360
- try:
361
- factory = unreal.PhysicsAssetFactory()
362
-
363
- # Ensure the directory exists
364
- if not unreal.EditorAssetLibrary.does_directory_exist(asset_path):
365
- unreal.EditorAssetLibrary.make_directory(asset_path)
366
-
367
- # Alternative approach: Create physics asset from skeletal mesh
368
- # This is the proper way in UE5
369
- try:
370
- # Try modern physics asset creation methods first
371
- try:
372
- # Method 1: Try using SkeletalMesh editor utilities if available
373
- if hasattr(unreal, 'SkeletalMeshEditorSubsystem'):
374
- skel_subsystem = unreal.get_editor_subsystem(unreal.SkeletalMeshEditorSubsystem)
375
- if hasattr(skel_subsystem, 'create_physics_asset'):
376
- physics_asset = skel_subsystem.create_physics_asset(skeletal_mesh)
377
- else:
378
- # Fallback to deprecated EditorSkeletalMeshLibrary
379
- physics_asset = unreal.EditorSkeletalMeshLibrary.create_physics_asset(skeletal_mesh)
380
- else:
381
- physics_asset = unreal.EditorSkeletalMeshLibrary.create_physics_asset(skeletal_mesh)
382
- except Exception as method1_modern_error:
383
- record_warning(f"Modern creation path fallback: {method1_modern_error}")
384
- # Final fallback to deprecated API
385
- physics_asset = unreal.EditorSkeletalMeshLibrary.create_physics_asset(skeletal_mesh)
386
- except Exception as e:
387
- print(f"Physics asset creation failed: {str(e)}")
388
- record_error(f"Physics asset creation failed: {str(e)}")
389
- physics_asset = None
390
-
391
- if physics_asset:
392
- # Move/rename the physics asset to desired location
393
- source_path = physics_asset.get_path_name()
394
- if unreal.EditorAssetLibrary.rename_asset(source_path, full_path):
395
- print(f"Successfully created and moved PhysicsAsset to {full_path}")
396
- record_detail(f"Successfully created and moved PhysicsAsset to {full_path}")
397
- new_asset = physics_asset
398
-
399
- # Ensure persistence
400
- if ensure_asset_persistence(full_path):
401
- # Verify it was saved
402
- if unreal.EditorAssetLibrary.does_asset_exist(full_path):
403
- print(f"Verified PhysicsAsset exists after save: {full_path}")
404
- record_detail(f"Verified PhysicsAsset exists after save: {full_path}")
405
- success = True
406
- result["message"] = f"Ragdoll physics setup completed for {asset_name}"
407
- else:
408
- error_msg = f"PhysicsAsset not found after save: {full_path}"
409
- print(f"Warning: {error_msg}")
410
- record_warning(error_msg)
411
- else:
412
- error_msg = "Failed to persist physics asset"
413
- print(f"Warning: {error_msg}")
414
- record_warning(error_msg)
415
- else:
416
- print(f"Created PhysicsAsset but couldn't move to {full_path}")
417
- record_warning(f"Created PhysicsAsset but couldn't move to {full_path}")
418
- # Still consider it a success if we created it
419
- new_asset = physics_asset
420
- success = True
421
- result["message"] = f"Physics asset created but not moved to {full_path}"
422
- else:
423
- error_msg = "Failed to create PhysicsAsset from skeletal mesh"
424
- print(error_msg)
425
- record_error(error_msg)
426
- new_asset = None
427
-
428
- successMessage: \`Skeletal mesh discovery complete\`,
429
- failureMessage: \`Failed to discover skeletal mesh\`
430
- record_warning(f"Method 1 failed: {str(e)}")
431
-
432
- # Method 2: Try older approach
433
- try:
434
- asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
435
- factory = unreal.PhysicsAssetFactory()
436
-
437
- # Try to initialize factory with the skeletal mesh
438
- factory.create_physics_asset_from_skeletal_mesh = skeletal_mesh
439
-
440
- new_asset = asset_tools.create_asset(
441
- asset_name=asset_name,
442
- package_path=asset_path,
443
- asset_class=unreal.PhysicsAsset,
444
- factory=factory
445
- )
446
-
447
- if new_asset:
448
- print(f"Successfully created PhysicsAsset at {full_path} (Method 2)")
449
- record_detail(f"Successfully created PhysicsAsset at {full_path} (Method 2)")
450
- # Ensure persistence
451
- if ensure_asset_persistence(full_path):
452
- success = True
453
- result["message"] = f"Ragdoll physics setup completed for {asset_name}"
454
- else:
455
- record_warning("Persistence check failed after Method 2 creation")
456
- except Exception as e2:
457
- error_msg = f"Method 2 also failed: {str(e2)}"
458
- print(error_msg)
459
- record_error(error_msg)
460
- new_asset = None
461
-
462
- # Final check
463
- if new_asset and not success:
464
- # Try one more save
465
- if ensure_asset_persistence(full_path):
466
- if unreal.EditorAssetLibrary.does_asset_exist(full_path):
467
- success = True
468
- result["message"] = f"Ragdoll physics setup completed for {asset_name}"
469
- else:
470
- record_warning(f"Final existence check failed for {full_path}")
471
-
472
- except Exception as e:
473
- error_msg = str(e)
474
- print(f"Error: {error_msg}")
475
- record_error(error_msg)
476
- import traceback
477
- traceback.print_exc()
478
-
479
- # Finalize result
480
- result["success"] = bool(success)
481
- result["path"] = full_path if success else None
482
-
483
- if not result["message"]:
484
- if success:
485
- result["message"] = f"Ragdoll physics setup completed for {asset_name}"
486
- elif error_msg:
487
- result["message"] = error_msg
488
- else:
489
- result["message"] = "Failed to setup ragdoll"
490
-
491
- if not success:
492
- if not result["error"]:
493
- result["error"] = error_msg or "Unknown error"
494
-
495
- print('RESULT:' + json.dumps(result))
496
- `;
497
-
498
-
499
- // Execute Python and interpret response
174
+ // Use Automation Bridge for physics asset creation
175
+ if (!this.automationBridge) {
176
+ throw new Error('Automation Bridge not available. Physics asset creation requires plugin support.');
177
+ }
178
+
500
179
  try {
501
- const response = await this.bridge.executePython(pythonScript);
502
- const interpreted = interpretStandardResult(response, {
503
- successMessage: `Ragdoll physics setup completed for ${sanitizedParams.name}`,
504
- failureMessage: `Failed to setup ragdoll for ${sanitizedParams.name}`
180
+ const response = await this.automationBridge.sendAutomationRequest('setup_ragdoll', {
181
+ meshPath,
182
+ physicsAssetName: sanitizedParams.name,
183
+ savePath: path,
184
+ blendWeight: params.blendWeight,
185
+ constraints: params.constraints
186
+ }, {
187
+ timeoutMs: 120000 // 2 minutes for complex physics asset creation
505
188
  });
506
189
 
507
- const warnings = interpreted.warnings ?? [];
508
- const details = interpreted.details ?? [];
509
-
510
- if (interpreted.success) {
511
- const successPayload: {
512
- success: true;
513
- message: string;
514
- path: string;
515
- existingAsset?: boolean;
516
- warnings?: string[];
517
- details?: string[];
518
- } = {
519
- success: true,
520
- message: interpreted.message,
521
- path: coerceString(interpreted.payload.path) ?? `${path}/${sanitizedParams.name}`
190
+ if (response.success === false) {
191
+ return {
192
+ success: false,
193
+ message: response.error || response.message || `Failed to setup ragdoll for ${sanitizedParams.name}`,
194
+ error: response.error || response.message || 'Failed to setup ragdoll'
522
195
  };
523
-
524
- if (interpreted.payload.existingAsset === true) {
525
- successPayload.existingAsset = true;
526
- }
527
-
528
- if (warnings.length > 0) {
529
- successPayload.warnings = warnings;
530
- }
531
- if (details.length > 0) {
532
- successPayload.details = details;
533
- }
534
-
535
- return successPayload;
536
196
  }
537
197
 
538
- const errorMessage = interpreted.error ?? `Failed to setup ragdoll for ${sanitizedParams.name}`;
539
-
198
+ const result = response.result as any;
540
199
  return {
541
- success: false as const,
542
- message: errorMessage,
543
- error: errorMessage,
544
- warnings: warnings.length > 0 ? warnings : undefined,
545
- details: details.length > 0 ? details : undefined
200
+ success: true,
201
+ message: response.message || `Ragdoll physics setup completed for ${sanitizedParams.name}`,
202
+ path: coerceString(result?.path) ?? coerceString(result?.physicsAssetPath) ?? `${path}/${sanitizedParams.name}`,
203
+ existingAsset: result?.existingAsset,
204
+ ...(result || {})
546
205
  };
547
206
  } catch (error) {
548
207
  return {
@@ -556,6 +215,7 @@ print('RESULT:' + json.dumps(result))
556
215
  }
557
216
  }
558
217
 
218
+
559
219
  /**
560
220
  * Create Physics Constraint
561
221
  */
@@ -577,17 +237,17 @@ print('RESULT:' + json.dumps(result))
577
237
  // Spawn constraint actor
578
238
  const spawnCmd = `spawnactor /Script/Engine.PhysicsConstraintActor ${params.location[0]} ${params.location[1]} ${params.location[2]}`;
579
239
  await this.bridge.executeConsoleCommand(spawnCmd);
580
-
240
+
581
241
  // Configure constraint
582
242
  const commands = [
583
243
  `SetConstraintActors ${params.name} ${params.actor1} ${params.actor2}`,
584
244
  `SetConstraintType ${params.name} ${params.constraintType}`
585
245
  ];
586
-
246
+
587
247
  if (params.breakThreshold) {
588
248
  commands.push(`SetConstraintBreakThreshold ${params.name} ${params.breakThreshold}`);
589
249
  }
590
-
250
+
591
251
  if (params.limits) {
592
252
  const limits = params.limits;
593
253
  if (limits.swing1 !== undefined) {
@@ -603,12 +263,12 @@ print('RESULT:' + json.dumps(result))
603
263
  commands.push(`SetConstraintLinear ${params.name} ${limits.linear}`);
604
264
  }
605
265
  }
606
-
266
+
607
267
  await this.bridge.executeConsoleCommands(commands);
608
-
609
- return {
610
- success: true,
611
- message: `Physics constraint ${params.name} created between ${params.actor1} and ${params.actor2}`
268
+
269
+ return {
270
+ success: true,
271
+ message: `Physics constraint ${params.name} created between ${params.actor1} and ${params.actor2}`
612
272
  };
613
273
  } catch (err) {
614
274
  return { success: false, error: `Failed to create constraint: ${err}` };
@@ -632,11 +292,11 @@ print('RESULT:' + json.dumps(result))
632
292
  }) {
633
293
  try {
634
294
  const path = params.savePath || '/Game/Destruction';
635
-
295
+
636
296
  const commands = [
637
297
  `CreateGeometryCollection ${params.destructionName} ${params.meshPath} ${path}`
638
298
  ];
639
-
299
+
640
300
  // Configure fracture
641
301
  if (params.fractureSettings) {
642
302
  const settings = params.fractureSettings;
@@ -644,21 +304,21 @@ print('RESULT:' + json.dumps(result))
644
304
  `FractureGeometry ${params.destructionName} ${settings.cellCount} ${settings.minimumVolumeSize} ${settings.seed}`
645
305
  );
646
306
  }
647
-
307
+
648
308
  // Set damage threshold
649
309
  if (params.damageThreshold) {
650
310
  commands.push(`SetDamageThreshold ${params.destructionName} ${params.damageThreshold}`);
651
311
  }
652
-
312
+
653
313
  // Set debris lifetime
654
314
  if (params.debrisLifetime) {
655
315
  commands.push(`SetDebrisLifetime ${params.destructionName} ${params.debrisLifetime}`);
656
316
  }
657
-
317
+
658
318
  await this.bridge.executeConsoleCommands(commands);
659
-
660
- return {
661
- success: true,
319
+
320
+ return {
321
+ success: true,
662
322
  message: `Chaos destruction ${params.destructionName} created`,
663
323
  path: `${path}/${params.destructionName}`
664
324
  };
@@ -689,58 +349,126 @@ print('RESULT:' + json.dumps(result))
689
349
  gears: number[];
690
350
  finalDriveRatio: number;
691
351
  };
352
+ pluginDependencies?: string[];
692
353
  }) {
693
- try {
694
- const commands = [
695
- `CreateVehicle ${params.vehicleName} ${params.vehicleType}`
696
- ];
697
-
698
- // Configure wheels
699
- if (params.wheels) {
700
- for (const wheel of params.wheels) {
701
- commands.push(
702
- `AddVehicleWheel ${params.vehicleName} ${wheel.name} ${wheel.radius} ${wheel.width} ${wheel.mass}`
703
- );
704
-
705
- if (wheel.isSteering) {
706
- commands.push(`SetWheelSteering ${params.vehicleName} ${wheel.name} true`);
707
- }
708
- if (wheel.isDriving) {
709
- commands.push(`SetWheelDriving ${params.vehicleName} ${wheel.name} true`);
710
- }
354
+ // Plugin check removed as ensurePluginsEnabled is deprecated.
355
+ // Users should ensure required plugins are enabled in the editor.
356
+
357
+ const rawParams: any = params as any;
358
+
359
+ const pluginDeps: string[] | undefined = Array.isArray(params.pluginDependencies) && params.pluginDependencies.length > 0
360
+ ? params.pluginDependencies
361
+ : (Array.isArray(rawParams.plugins) && rawParams.plugins.length > 0 ? rawParams.plugins : undefined);
362
+
363
+ if (pluginDeps && pluginDeps.length > 0) {
364
+ return {
365
+ success: false,
366
+ error: 'MISSING_ENGINE_PLUGINS',
367
+ missingPlugins: pluginDeps,
368
+ message: `Required engine plugins not enabled: ${pluginDeps.join(', ')}`
369
+ };
370
+ }
371
+
372
+ const warnings: string[] = [];
373
+
374
+ const hasExplicitEmptyWheels = Array.isArray(params.wheels) && params.wheels.length === 0;
375
+
376
+ const effectiveVehicleType = typeof params.vehicleType === 'string' && params.vehicleType.trim().length > 0
377
+ ? params.vehicleType
378
+ : 'Car';
379
+
380
+ const commands = [
381
+ `CreateVehicle ${params.vehicleName} ${effectiveVehicleType}`
382
+ ];
383
+
384
+ // Configure wheels when provided
385
+ if (Array.isArray(params.wheels) && params.wheels.length > 0) {
386
+ for (const wheel of params.wheels) {
387
+ commands.push(
388
+ `AddVehicleWheel ${params.vehicleName} ${wheel.name} ${wheel.radius} ${wheel.width} ${wheel.mass}`
389
+ );
390
+
391
+ if (wheel.isSteering) {
392
+ commands.push(`SetWheelSteering ${params.vehicleName} ${wheel.name} true`);
711
393
  }
394
+ if (wheel.isDriving) {
395
+ commands.push(`SetWheelDriving ${params.vehicleName} ${wheel.name} true`);
396
+ }
397
+ }
398
+ }
399
+
400
+ // Configure engine (optional). Clamp negative RPMs and tolerate missing torqueCurve.
401
+ const effectiveEngine = params.engine ?? ((typeof rawParams.maxRPM === 'number' || Array.isArray(rawParams.torqueCurve))
402
+ ? { maxRPM: rawParams.maxRPM, torqueCurve: rawParams.torqueCurve }
403
+ : undefined);
404
+
405
+ if (effectiveEngine) {
406
+ let maxRPM = typeof effectiveEngine.maxRPM === 'number' ? effectiveEngine.maxRPM : 0;
407
+ if (maxRPM < 0) {
408
+ maxRPM = 0;
409
+ warnings.push('Engine maxRPM was negative and has been clamped to 0.');
712
410
  }
713
-
714
- // Configure engine
715
- if (params.engine) {
716
- commands.push(`SetEngineMaxRPM ${params.vehicleName} ${params.engine.maxRPM}`);
717
-
718
- for (const [rpm, torque] of params.engine.torqueCurve) {
411
+ commands.push(`SetEngineMaxRPM ${params.vehicleName} ${maxRPM}`);
412
+
413
+ const rawCurve = Array.isArray(effectiveEngine.torqueCurve) ? effectiveEngine.torqueCurve : [];
414
+ for (const point of rawCurve) {
415
+ let rpm: number | undefined;
416
+ let torque: number | undefined;
417
+
418
+ if (Array.isArray(point) && point.length >= 2) {
419
+ rpm = Number(point[0]);
420
+ torque = Number(point[1]);
421
+ } else if (point && typeof point === 'object') {
422
+ const anyPoint: any = point;
423
+ rpm = typeof anyPoint.rpm === 'number' ? anyPoint.rpm : undefined;
424
+ torque = typeof anyPoint.torque === 'number' ? anyPoint.torque : undefined;
425
+ }
426
+
427
+ if (typeof rpm === 'number' && typeof torque === 'number') {
719
428
  commands.push(`AddTorqueCurvePoint ${params.vehicleName} ${rpm} ${torque}`);
720
429
  }
721
430
  }
722
-
723
- // Configure transmission
724
- if (params.transmission) {
431
+ }
432
+
433
+ // Configure transmission
434
+ if (params.transmission) {
435
+ if (Array.isArray(params.transmission.gears)) {
725
436
  for (let i = 0; i < params.transmission.gears.length; i++) {
726
437
  commands.push(
727
438
  `SetGearRatio ${params.vehicleName} ${i} ${params.transmission.gears[i]}`
728
439
  );
729
440
  }
441
+ }
442
+ if (typeof params.transmission.finalDriveRatio === 'number') {
730
443
  commands.push(
731
444
  `SetFinalDriveRatio ${params.vehicleName} ${params.transmission.finalDriveRatio}`
732
445
  );
733
446
  }
734
-
447
+ }
448
+
449
+ try {
735
450
  await this.bridge.executeConsoleCommands(commands);
736
-
737
- return {
738
- success: true,
739
- message: `Vehicle ${params.vehicleName} configured`
740
- };
741
- } catch (err) {
742
- return { success: false, error: `Failed to configure vehicle: ${err}` };
451
+ } catch (_error) {
452
+ // If vehicle console commands fail (e.g., `Command not executed`), treat this as
453
+ // a best-effort configuration that falls back to engine defaults.
454
+ if (warnings.length === 0) {
455
+ warnings.push('Vehicle configuration commands could not be executed; using engine defaults.');
456
+ }
457
+ }
458
+
459
+ if (hasExplicitEmptyWheels) {
460
+ warnings.push('No wheels specified; using default wheels from vehicle preset.');
743
461
  }
462
+
463
+ if (warnings.length === 0) {
464
+ warnings.push('Verify wheel class assignments and offsets in the vehicle movement component to ensure they match your project defaults.');
465
+ }
466
+
467
+ return {
468
+ success: true,
469
+ message: `Vehicle ${params.vehicleName} configured`,
470
+ warnings
471
+ };
744
472
  }
745
473
 
746
474
  /**
@@ -753,148 +481,42 @@ print('RESULT:' + json.dumps(result))
753
481
  boneName?: string;
754
482
  isLocal?: boolean;
755
483
  }) {
484
+ if (!this.automationBridge) {
485
+ throw new Error('Automation Bridge not available. Physics force application requires plugin support.');
486
+ }
487
+
756
488
  try {
757
- // Use Python to apply physics forces since console commands don't exist for this
758
- const pythonCode = `
759
- import unreal
760
- import json
761
-
762
- result = {"success": False, "message": "", "actor_found": False, "physics_enabled": False}
763
-
764
- # Check if editor is in play mode first
765
- try:
766
- les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
767
- if les and les.is_in_play_in_editor():
768
- result["message"] = "Cannot apply physics while in Play In Editor mode. Please stop PIE first."
769
- print(f"RESULT:{json.dumps(result)}")
770
- # Exit early from this script
771
- raise SystemExit(0)
772
- except SystemExit:
773
- # Re-raise the SystemExit to exit properly
774
- raise
775
- except:
776
- pass # Continue if we can't check PIE state
777
-
778
- try:
779
- actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
780
- actors = actor_subsystem.get_all_level_actors()
781
- search_name = "${params.actorName}"
782
-
783
- for actor in actors:
784
- if actor:
785
- # Check both actor name and label with case-insensitive partial matching
786
- actor_name = actor.get_name()
787
- actor_label = actor.get_actor_label()
788
-
789
- if (search_name.lower() in actor_label.lower() or
790
- actor_label.lower().startswith(search_name.lower() + "_") or
791
- actor_label.lower() == search_name.lower() or
792
- actor_name.lower() == search_name.lower()):
793
-
794
- result["actor_found"] = True
795
- # Get the primitive component if it exists
796
- root = actor.get_editor_property('root_component')
797
-
798
- if root and isinstance(root, unreal.PrimitiveComponent):
799
- # Check if the component is static or movable
800
- mobility = root.get_editor_property('mobility')
801
- if mobility == unreal.ComponentMobility.STATIC:
802
- # Try to set to movable first
803
- try:
804
- root.set_editor_property('mobility', unreal.ComponentMobility.MOVABLE)
805
- except:
806
- result["message"] = f"Actor {actor_label} has static mobility and cannot simulate physics"
807
- break
808
-
809
- # Ensure physics is enabled
810
- try:
811
- root.set_simulate_physics(True)
812
- result["physics_enabled"] = True
813
- except Exception as physics_err:
814
- # If we can't enable physics, try applying force anyway (some actors respond without physics sim)
815
- result["physics_enabled"] = False
816
-
817
- force = unreal.Vector(${params.vector[0]}, ${params.vector[1]}, ${params.vector[2]})
818
-
819
- if "${params.forceType}" == "Force":
820
- root.add_force(force, 'None', False)
821
- result["success"] = True
822
- result["message"] = f"Applied Force to {actor_label}: {force}"
823
- elif "${params.forceType}" == "Impulse":
824
- root.add_impulse(force, 'None', False)
825
- result["success"] = True
826
- result["message"] = f"Applied Impulse to {actor_label}: {force}"
827
- elif "${params.forceType}" == "Velocity":
828
- root.set_physics_linear_velocity(force)
829
- result["success"] = True
830
- result["message"] = f"Set Velocity on {actor_label}: {force}"
831
- elif "${params.forceType}" == "Torque":
832
- root.add_torque_in_radians(force, 'None', False)
833
- result["success"] = True
834
- result["message"] = f"Applied Torque to {actor_label}: {force}"
835
- else:
836
- result["message"] = f"Actor {actor_label} doesn't have a physics-enabled component"
837
- break
838
-
839
- if not result["actor_found"]:
840
- result["message"] = f"Actor not found: {search_name}"
841
- # List actors with physics enabled for debugging
842
- physics_actors = []
843
- for actor in actors[:20]:
844
- if actor:
845
- label = actor.get_actor_label()
846
- if "mesh" in label.lower() or "cube" in label.lower() or "static" in label.lower():
847
- physics_actors.append(label)
848
- if physics_actors:
849
- result["available_actors"] = physics_actors
850
-
851
- except Exception as e:
852
- result["message"] = f"Error applying force: {e}"
853
-
854
- print(f"RESULT:{json.dumps(result)}")
855
- `.trim();
856
-
857
- const response = await this.bridge.executePython(pythonCode);
858
- const interpreted = interpretStandardResult(response, {
859
- successMessage: `Applied ${params.forceType} to ${params.actorName}`,
860
- failureMessage: 'Force application failed'
489
+ // Use WASM for vector normalization/validation
490
+ const zeroVector: [number, number, number] = [0, 0, 0];
491
+ const normalizedVector = wasmIntegration.vectorAdd(zeroVector, params.vector);
492
+ console.error('[WASM] Using vectorAdd for physics force vector processing');
493
+
494
+ const response = await this.automationBridge.sendAutomationRequest('apply_force', {
495
+ actorName: params.actorName,
496
+ forceType: params.forceType,
497
+ vector: normalizedVector,
498
+ boneName: params.boneName,
499
+ isLocal: params.isLocal
500
+ }, {
501
+ timeoutMs: 30000
861
502
  });
862
503
 
863
- const availableActors = coerceStringArray(interpreted.payload.available_actors);
864
-
865
- if (interpreted.success) {
866
- return {
867
- success: true,
868
- message: interpreted.message,
869
- availableActors,
870
- details: interpreted.details
871
- };
872
- }
873
-
874
- const fallbackText = bestEffortInterpretedText(interpreted) ?? '';
875
- if (/Applied/i.test(fallbackText)) {
876
- return {
877
- success: true,
878
- message: fallbackText || interpreted.message,
879
- availableActors,
880
- details: interpreted.details
881
- };
882
- }
883
-
884
- if (/not found/i.test(fallbackText) || /error/i.test(fallbackText)) {
504
+ if (response.success === false) {
505
+ const result = response.result as any;
885
506
  return {
886
507
  success: false,
887
- error: interpreted.error ?? (fallbackText || 'Force application failed'),
888
- availableActors,
889
- details: interpreted.details ?? (fallbackText ? [fallbackText] : undefined)
508
+ error: response.error || response.message || 'Force application failed',
509
+ availableActors: result?.available_actors ? coerceStringArray(result.available_actors) : undefined,
510
+ details: result?.details
890
511
  };
891
512
  }
892
513
 
514
+ const result = response.result as any;
893
515
  return {
894
- success: false,
895
- error: interpreted.error ?? 'No valid result from Python',
896
- availableActors,
897
- details: interpreted.details ?? (fallbackText ? [fallbackText] : undefined)
516
+ success: true,
517
+ message: response.message || `Applied ${params.forceType} to ${params.actorName}`,
518
+ availableActors: result?.available_actors ? coerceStringArray(result.available_actors) : undefined,
519
+ ...(result || {})
898
520
  };
899
521
  } catch (err) {
900
522
  return { success: false, error: `Failed to apply force: ${err}` };
@@ -921,10 +543,10 @@ print(f"RESULT:{json.dumps(result)}")
921
543
  `EnableClothSimulation ${params.meshName}`,
922
544
  `SetClothPreset ${params.meshName} ${params.clothPreset}`
923
545
  ];
924
-
546
+
925
547
  if (params.clothPreset === 'Custom' && params.customSettings) {
926
548
  const settings = params.customSettings;
927
-
549
+
928
550
  if (settings.stiffness !== undefined) {
929
551
  commands.push(`SetClothStiffness ${params.meshName} ${settings.stiffness}`);
930
552
  }
@@ -945,12 +567,12 @@ print(f"RESULT:{json.dumps(result)}")
945
567
  commands.push(`SetClothWind ${params.meshName} ${wind[0]} ${wind[1]} ${wind[2]}`);
946
568
  }
947
569
  }
948
-
570
+
949
571
  await this.bridge.executeConsoleCommands(commands);
950
-
951
- return {
952
- success: true,
953
- message: `Cloth simulation enabled for ${params.meshName}`
572
+
573
+ return {
574
+ success: true,
575
+ message: `Cloth simulation enabled for ${params.meshName}`
954
576
  };
955
577
  } catch (err) {
956
578
  return { success: false, error: `Failed to setup cloth: ${err}` };
@@ -976,14 +598,14 @@ print(f"RESULT:{json.dumps(result)}")
976
598
  try {
977
599
  const locStr = `${params.location[0]} ${params.location[1]} ${params.location[2]}`;
978
600
  const volStr = `${params.volume[0]} ${params.volume[1]} ${params.volume[2]}`;
979
-
601
+
980
602
  const commands = [
981
603
  `CreateFluidSimulation ${params.name} ${params.fluidType} ${locStr} ${volStr}`
982
604
  ];
983
-
605
+
984
606
  if (params.customSettings) {
985
607
  const settings = params.customSettings;
986
-
608
+
987
609
  if (settings.viscosity !== undefined) {
988
610
  commands.push(`SetFluidViscosity ${params.name} ${settings.viscosity}`);
989
611
  }
@@ -1003,16 +625,55 @@ print(f"RESULT:{json.dumps(result)}")
1003
625
  );
1004
626
  }
1005
627
  }
1006
-
628
+
1007
629
  await this.bridge.executeConsoleCommands(commands);
1008
-
1009
- return {
1010
- success: true,
1011
- message: `Fluid simulation ${params.name} created`
630
+
631
+ return {
632
+ success: true,
633
+ message: `Fluid simulation ${params.name} created`
1012
634
  };
1013
635
  } catch (err) {
1014
636
  return { success: false, error: `Failed to create fluid simulation: ${err}` };
1015
637
  }
1016
638
  }
1017
639
 
640
+ /**
641
+ * Setup Physics Simulation (Create Physics Asset)
642
+ */
643
+ async setupPhysicsSimulation(params: {
644
+ meshPath?: string;
645
+ skeletonPath?: string;
646
+ physicsAssetName?: string;
647
+ savePath?: string;
648
+ }) {
649
+ if (!this.automationBridge) {
650
+ throw new Error('Automation Bridge not available. Physics asset creation requires plugin support.');
651
+ }
652
+
653
+ try {
654
+ const response = await this.automationBridge.sendAutomationRequest('animation_physics', {
655
+ action: 'setup_physics_simulation',
656
+ ...params
657
+ }, {
658
+ timeoutMs: 60000
659
+ });
660
+
661
+ if (response.success === false) {
662
+ return {
663
+ success: false,
664
+ message: response.error || response.message || 'Failed to setup physics simulation',
665
+ error: response.error || response.message
666
+ };
667
+ }
668
+
669
+ return {
670
+ success: true,
671
+ message: response.message || 'Physics simulation setup completed',
672
+ ...(response.result || {})
673
+ };
674
+ } catch (err) {
675
+ return { success: false, error: `Failed to setup physics simulation: ${err}` };
676
+ }
677
+ }
678
+
1018
679
  }