unreal-engine-mcp-server 0.4.7 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (438) hide show
  1. package/.env.example +26 -0
  2. package/.env.production +38 -7
  3. package/.eslintrc.json +0 -54
  4. package/.eslintrc.override.json +8 -0
  5. package/.github/ISSUE_TEMPLATE/bug_report.yml +94 -0
  6. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  7. package/.github/ISSUE_TEMPLATE/feature_request.yml +56 -0
  8. package/.github/copilot-instructions.md +478 -45
  9. package/.github/dependabot.yml +19 -0
  10. package/.github/labeler.yml +24 -0
  11. package/.github/labels.yml +70 -0
  12. package/.github/pull_request_template.md +42 -0
  13. package/.github/release-drafter.yml +148 -0
  14. package/.github/workflows/auto-merge.yml +38 -0
  15. package/.github/workflows/ci.yml +38 -0
  16. package/.github/workflows/dependency-review.yml +17 -0
  17. package/.github/workflows/gemini-issue-triage.yml +172 -0
  18. package/.github/workflows/greetings.yml +23 -0
  19. package/.github/workflows/labeler.yml +16 -0
  20. package/.github/workflows/links.yml +80 -0
  21. package/.github/workflows/pr-size-labeler.yml +137 -0
  22. package/.github/workflows/publish-mcp.yml +12 -7
  23. package/.github/workflows/release-drafter.yml +23 -0
  24. package/.github/workflows/release.yml +112 -0
  25. package/.github/workflows/semantic-pull-request.yml +35 -0
  26. package/.github/workflows/smoke-test.yml +36 -0
  27. package/.github/workflows/stale.yml +28 -0
  28. package/CHANGELOG.md +267 -31
  29. package/CONTRIBUTING.md +140 -0
  30. package/README.md +166 -71
  31. package/claude_desktop_config_example.json +7 -6
  32. package/dist/automation/bridge.d.ts +50 -0
  33. package/dist/automation/bridge.js +452 -0
  34. package/dist/automation/connection-manager.d.ts +23 -0
  35. package/dist/automation/connection-manager.js +107 -0
  36. package/dist/automation/handshake.d.ts +11 -0
  37. package/dist/automation/handshake.js +89 -0
  38. package/dist/automation/index.d.ts +3 -0
  39. package/dist/automation/index.js +3 -0
  40. package/dist/automation/message-handler.d.ts +12 -0
  41. package/dist/automation/message-handler.js +149 -0
  42. package/dist/automation/request-tracker.d.ts +25 -0
  43. package/dist/automation/request-tracker.js +98 -0
  44. package/dist/automation/types.d.ts +130 -0
  45. package/dist/automation/types.js +2 -0
  46. package/dist/cli.js +32 -5
  47. package/dist/config.d.ts +27 -0
  48. package/dist/config.js +60 -0
  49. package/dist/constants.d.ts +12 -0
  50. package/dist/constants.js +12 -0
  51. package/dist/graphql/resolvers.d.ts +268 -0
  52. package/dist/graphql/resolvers.js +743 -0
  53. package/dist/graphql/schema.d.ts +5 -0
  54. package/dist/graphql/schema.js +437 -0
  55. package/dist/graphql/server.d.ts +26 -0
  56. package/dist/graphql/server.js +115 -0
  57. package/dist/graphql/types.d.ts +7 -0
  58. package/dist/graphql/types.js +2 -0
  59. package/dist/handlers/resource-handlers.d.ts +20 -0
  60. package/dist/handlers/resource-handlers.js +180 -0
  61. package/dist/index.d.ts +31 -18
  62. package/dist/index.js +119 -619
  63. package/dist/prompts/index.js +4 -4
  64. package/dist/resources/actors.d.ts +17 -12
  65. package/dist/resources/actors.js +56 -76
  66. package/dist/resources/assets.d.ts +6 -14
  67. package/dist/resources/assets.js +115 -147
  68. package/dist/resources/levels.d.ts +13 -13
  69. package/dist/resources/levels.js +25 -34
  70. package/dist/server/resource-registry.d.ts +20 -0
  71. package/dist/server/resource-registry.js +37 -0
  72. package/dist/server/tool-registry.d.ts +23 -0
  73. package/dist/server/tool-registry.js +322 -0
  74. package/dist/server-setup.d.ts +21 -0
  75. package/dist/server-setup.js +111 -0
  76. package/dist/services/health-monitor.d.ts +34 -0
  77. package/dist/services/health-monitor.js +105 -0
  78. package/dist/services/metrics-server.d.ts +11 -0
  79. package/dist/services/metrics-server.js +105 -0
  80. package/dist/tools/actors.d.ts +147 -9
  81. package/dist/tools/actors.js +350 -311
  82. package/dist/tools/animation.d.ts +135 -4
  83. package/dist/tools/animation.js +510 -411
  84. package/dist/tools/assets.d.ts +117 -19
  85. package/dist/tools/assets.js +259 -284
  86. package/dist/tools/audio.d.ts +102 -42
  87. package/dist/tools/audio.js +272 -685
  88. package/dist/tools/base-tool.d.ts +17 -0
  89. package/dist/tools/base-tool.js +46 -0
  90. package/dist/tools/behavior-tree.d.ts +94 -0
  91. package/dist/tools/behavior-tree.js +39 -0
  92. package/dist/tools/blueprint/helpers.d.ts +29 -0
  93. package/dist/tools/blueprint/helpers.js +182 -0
  94. package/dist/tools/blueprint.d.ts +228 -118
  95. package/dist/tools/blueprint.js +685 -832
  96. package/dist/tools/consolidated-tool-definitions.d.ts +5462 -1781
  97. package/dist/tools/consolidated-tool-definitions.js +829 -496
  98. package/dist/tools/consolidated-tool-handlers.d.ts +2 -1
  99. package/dist/tools/consolidated-tool-handlers.js +211 -1026
  100. package/dist/tools/debug.d.ts +143 -85
  101. package/dist/tools/debug.js +234 -180
  102. package/dist/tools/dynamic-handler-registry.d.ts +11 -0
  103. package/dist/tools/dynamic-handler-registry.js +101 -0
  104. package/dist/tools/editor.d.ts +139 -18
  105. package/dist/tools/editor.js +239 -244
  106. package/dist/tools/engine.d.ts +10 -4
  107. package/dist/tools/engine.js +13 -5
  108. package/dist/tools/environment.d.ts +36 -0
  109. package/dist/tools/environment.js +267 -0
  110. package/dist/tools/foliage.d.ts +105 -14
  111. package/dist/tools/foliage.js +219 -331
  112. package/dist/tools/handlers/actor-handlers.d.ts +3 -0
  113. package/dist/tools/handlers/actor-handlers.js +232 -0
  114. package/dist/tools/handlers/animation-handlers.d.ts +3 -0
  115. package/dist/tools/handlers/animation-handlers.js +185 -0
  116. package/dist/tools/handlers/argument-helper.d.ts +16 -0
  117. package/dist/tools/handlers/argument-helper.js +80 -0
  118. package/dist/tools/handlers/asset-handlers.d.ts +3 -0
  119. package/dist/tools/handlers/asset-handlers.js +496 -0
  120. package/dist/tools/handlers/audio-handlers.d.ts +3 -0
  121. package/dist/tools/handlers/audio-handlers.js +166 -0
  122. package/dist/tools/handlers/blueprint-handlers.d.ts +4 -0
  123. package/dist/tools/handlers/blueprint-handlers.js +358 -0
  124. package/dist/tools/handlers/common-handlers.d.ts +14 -0
  125. package/dist/tools/handlers/common-handlers.js +56 -0
  126. package/dist/tools/handlers/editor-handlers.d.ts +3 -0
  127. package/dist/tools/handlers/editor-handlers.js +119 -0
  128. package/dist/tools/handlers/effect-handlers.d.ts +3 -0
  129. package/dist/tools/handlers/effect-handlers.js +171 -0
  130. package/dist/tools/handlers/environment-handlers.d.ts +3 -0
  131. package/dist/tools/handlers/environment-handlers.js +170 -0
  132. package/dist/tools/handlers/graph-handlers.d.ts +3 -0
  133. package/dist/tools/handlers/graph-handlers.js +90 -0
  134. package/dist/tools/handlers/input-handlers.d.ts +3 -0
  135. package/dist/tools/handlers/input-handlers.js +21 -0
  136. package/dist/tools/handlers/inspect-handlers.d.ts +3 -0
  137. package/dist/tools/handlers/inspect-handlers.js +383 -0
  138. package/dist/tools/handlers/level-handlers.d.ts +3 -0
  139. package/dist/tools/handlers/level-handlers.js +237 -0
  140. package/dist/tools/handlers/lighting-handlers.d.ts +3 -0
  141. package/dist/tools/handlers/lighting-handlers.js +144 -0
  142. package/dist/tools/handlers/performance-handlers.d.ts +3 -0
  143. package/dist/tools/handlers/performance-handlers.js +130 -0
  144. package/dist/tools/handlers/pipeline-handlers.d.ts +3 -0
  145. package/dist/tools/handlers/pipeline-handlers.js +110 -0
  146. package/dist/tools/handlers/sequence-handlers.d.ts +3 -0
  147. package/dist/tools/handlers/sequence-handlers.js +376 -0
  148. package/dist/tools/handlers/system-handlers.d.ts +4 -0
  149. package/dist/tools/handlers/system-handlers.js +506 -0
  150. package/dist/tools/input.d.ts +19 -0
  151. package/dist/tools/input.js +89 -0
  152. package/dist/tools/introspection.d.ts +103 -40
  153. package/dist/tools/introspection.js +425 -568
  154. package/dist/tools/landscape.d.ts +97 -36
  155. package/dist/tools/landscape.js +280 -409
  156. package/dist/tools/level.d.ts +130 -10
  157. package/dist/tools/level.js +639 -675
  158. package/dist/tools/lighting.d.ts +77 -38
  159. package/dist/tools/lighting.js +441 -943
  160. package/dist/tools/logs.d.ts +3 -3
  161. package/dist/tools/logs.js +5 -57
  162. package/dist/tools/materials.d.ts +91 -24
  163. package/dist/tools/materials.js +190 -118
  164. package/dist/tools/niagara.d.ts +149 -39
  165. package/dist/tools/niagara.js +232 -182
  166. package/dist/tools/performance.d.ts +27 -12
  167. package/dist/tools/performance.js +204 -122
  168. package/dist/tools/physics.d.ts +32 -77
  169. package/dist/tools/physics.js +171 -582
  170. package/dist/tools/property-dictionary.d.ts +13 -0
  171. package/dist/tools/property-dictionary.js +82 -0
  172. package/dist/tools/sequence.d.ts +73 -48
  173. package/dist/tools/sequence.js +196 -748
  174. package/dist/tools/tool-definition-utils.d.ts +59 -0
  175. package/dist/tools/tool-definition-utils.js +35 -0
  176. package/dist/tools/ui.d.ts +66 -34
  177. package/dist/tools/ui.js +134 -214
  178. package/dist/types/env.d.ts +0 -3
  179. package/dist/types/env.js +0 -7
  180. package/dist/types/tool-interfaces.d.ts +898 -0
  181. package/dist/types/tool-interfaces.js +2 -0
  182. package/dist/types/tool-types.d.ts +183 -19
  183. package/dist/types/tool-types.js +0 -4
  184. package/dist/unreal-bridge.d.ts +24 -131
  185. package/dist/unreal-bridge.js +364 -1506
  186. package/dist/utils/command-validator.d.ts +9 -0
  187. package/dist/utils/command-validator.js +67 -0
  188. package/dist/utils/elicitation.d.ts +1 -1
  189. package/dist/utils/elicitation.js +12 -15
  190. package/dist/utils/error-handler.d.ts +2 -51
  191. package/dist/utils/error-handler.js +11 -87
  192. package/dist/utils/ini-reader.d.ts +3 -0
  193. package/dist/utils/ini-reader.js +69 -0
  194. package/dist/utils/logger.js +9 -6
  195. package/dist/utils/normalize.d.ts +3 -0
  196. package/dist/utils/normalize.js +56 -0
  197. package/dist/utils/response-factory.d.ts +7 -0
  198. package/dist/utils/response-factory.js +33 -0
  199. package/dist/utils/response-validator.d.ts +3 -24
  200. package/dist/utils/response-validator.js +130 -81
  201. package/dist/utils/result-helpers.d.ts +4 -5
  202. package/dist/utils/result-helpers.js +15 -16
  203. package/dist/utils/safe-json.js +5 -11
  204. package/dist/utils/unreal-command-queue.d.ts +24 -0
  205. package/dist/utils/unreal-command-queue.js +120 -0
  206. package/dist/utils/validation.d.ts +0 -40
  207. package/dist/utils/validation.js +1 -78
  208. package/dist/wasm/index.d.ts +70 -0
  209. package/dist/wasm/index.js +535 -0
  210. package/docs/GraphQL-API.md +888 -0
  211. package/docs/Migration-Guide-v0.5.0.md +692 -0
  212. package/docs/Roadmap.md +53 -0
  213. package/docs/WebAssembly-Integration.md +628 -0
  214. package/docs/editor-plugin-extension.md +370 -0
  215. package/docs/handler-mapping.md +242 -0
  216. package/docs/native-automation-progress.md +128 -0
  217. package/docs/testing-guide.md +423 -0
  218. package/mcp-config-example.json +6 -6
  219. package/package.json +60 -27
  220. package/plugins/McpAutomationBridge/Config/FilterPlugin.ini +8 -0
  221. package/plugins/McpAutomationBridge/McpAutomationBridge.uplugin +64 -0
  222. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/McpAutomationBridge.Build.cs +189 -0
  223. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.cpp +22 -0
  224. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.h +30 -0
  225. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeHelpers.h +1983 -0
  226. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeModule.cpp +72 -0
  227. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSettings.cpp +46 -0
  228. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +581 -0
  229. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +2394 -0
  230. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetQueryHandlers.cpp +300 -0
  231. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetWorkflowHandlers.cpp +2807 -0
  232. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AudioHandlers.cpp +1087 -0
  233. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BehaviorTreeHandlers.cpp +488 -0
  234. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.cpp +643 -0
  235. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.h +31 -0
  236. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +1184 -0
  237. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +5652 -0
  238. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers_List.cpp +152 -0
  239. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ControlHandlers.cpp +2614 -0
  240. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_DebugHandlers.cpp +42 -0
  241. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EditorFunctionHandlers.cpp +1237 -0
  242. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +1701 -0
  243. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +2145 -0
  244. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_FoliageHandlers.cpp +954 -0
  245. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InputHandlers.cpp +209 -0
  246. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InsightsHandlers.cpp +41 -0
  247. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LandscapeHandlers.cpp +1164 -0
  248. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LevelHandlers.cpp +762 -0
  249. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +634 -0
  250. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LogHandlers.cpp +136 -0
  251. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_MaterialGraphHandlers.cpp +494 -0
  252. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraGraphHandlers.cpp +278 -0
  253. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraHandlers.cpp +625 -0
  254. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PerformanceHandlers.cpp +401 -0
  255. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PipelineHandlers.cpp +67 -0
  256. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +735 -0
  257. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PropertyHandlers.cpp +2634 -0
  258. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_RenderHandlers.cpp +189 -0
  259. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.cpp +917 -0
  260. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.h +39 -0
  261. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +2670 -0
  262. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequencerHandlers.cpp +519 -0
  263. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_TestHandlers.cpp +38 -0
  264. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_UiHandlers.cpp +668 -0
  265. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_WorldPartitionHandlers.cpp +346 -0
  266. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +1330 -0
  267. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.h +149 -0
  268. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +783 -0
  269. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSettings.h +115 -0
  270. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSubsystem.h +796 -0
  271. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpConnectionManager.h +117 -0
  272. package/scripts/check-unreal-connection.mjs +19 -0
  273. package/scripts/clean-tmp.js +23 -0
  274. package/scripts/patch-wasm.js +26 -0
  275. package/scripts/run-all-tests.mjs +131 -0
  276. package/scripts/smoke-test.ts +94 -0
  277. package/scripts/sync-mcp-plugin.js +143 -0
  278. package/scripts/test-no-plugin-alternates.mjs +113 -0
  279. package/scripts/validate-server.js +46 -0
  280. package/scripts/verify-automation-bridge.js +200 -0
  281. package/server.json +57 -21
  282. package/src/automation/bridge.ts +558 -0
  283. package/src/automation/connection-manager.ts +130 -0
  284. package/src/automation/handshake.ts +99 -0
  285. package/src/automation/index.ts +2 -0
  286. package/src/automation/message-handler.ts +167 -0
  287. package/src/automation/request-tracker.ts +123 -0
  288. package/src/automation/types.ts +107 -0
  289. package/src/cli.ts +33 -6
  290. package/src/config.ts +73 -0
  291. package/src/constants.ts +12 -0
  292. package/src/graphql/resolvers.ts +1010 -0
  293. package/src/graphql/schema.ts +452 -0
  294. package/src/graphql/server.ts +154 -0
  295. package/src/graphql/types.ts +7 -0
  296. package/src/handlers/resource-handlers.ts +186 -0
  297. package/src/index.ts +152 -663
  298. package/src/prompts/index.ts +4 -4
  299. package/src/resources/actors.ts +58 -76
  300. package/src/resources/assets.ts +147 -134
  301. package/src/resources/levels.ts +28 -33
  302. package/src/server/resource-registry.ts +47 -0
  303. package/src/server/tool-registry.ts +354 -0
  304. package/src/server-setup.ts +148 -0
  305. package/src/services/health-monitor.ts +132 -0
  306. package/src/services/metrics-server.ts +142 -0
  307. package/src/tools/actors.ts +417 -322
  308. package/src/tools/animation.ts +671 -461
  309. package/src/tools/assets.ts +353 -289
  310. package/src/tools/audio.ts +323 -766
  311. package/src/tools/base-tool.ts +52 -0
  312. package/src/tools/behavior-tree.ts +45 -0
  313. package/src/tools/blueprint/helpers.ts +189 -0
  314. package/src/tools/blueprint.ts +787 -965
  315. package/src/tools/consolidated-tool-definitions.ts +993 -515
  316. package/src/tools/consolidated-tool-handlers.ts +272 -1139
  317. package/src/tools/debug.ts +292 -187
  318. package/src/tools/dynamic-handler-registry.ts +151 -0
  319. package/src/tools/editor.ts +309 -246
  320. package/src/tools/engine.ts +14 -3
  321. package/src/tools/environment.ts +287 -0
  322. package/src/tools/foliage.ts +314 -379
  323. package/src/tools/handlers/actor-handlers.ts +271 -0
  324. package/src/tools/handlers/animation-handlers.ts +237 -0
  325. package/src/tools/handlers/argument-helper.ts +142 -0
  326. package/src/tools/handlers/asset-handlers.ts +532 -0
  327. package/src/tools/handlers/audio-handlers.ts +194 -0
  328. package/src/tools/handlers/blueprint-handlers.ts +380 -0
  329. package/src/tools/handlers/common-handlers.ts +87 -0
  330. package/src/tools/handlers/editor-handlers.ts +123 -0
  331. package/src/tools/handlers/effect-handlers.ts +220 -0
  332. package/src/tools/handlers/environment-handlers.ts +183 -0
  333. package/src/tools/handlers/graph-handlers.ts +116 -0
  334. package/src/tools/handlers/input-handlers.ts +28 -0
  335. package/src/tools/handlers/inspect-handlers.ts +450 -0
  336. package/src/tools/handlers/level-handlers.ts +252 -0
  337. package/src/tools/handlers/lighting-handlers.ts +147 -0
  338. package/src/tools/handlers/performance-handlers.ts +132 -0
  339. package/src/tools/handlers/pipeline-handlers.ts +127 -0
  340. package/src/tools/handlers/sequence-handlers.ts +415 -0
  341. package/src/tools/handlers/system-handlers.ts +564 -0
  342. package/src/tools/input.ts +101 -0
  343. package/src/tools/introspection.ts +493 -584
  344. package/src/tools/landscape.ts +394 -489
  345. package/src/tools/level.ts +752 -694
  346. package/src/tools/lighting.ts +583 -984
  347. package/src/tools/logs.ts +9 -57
  348. package/src/tools/materials.ts +231 -121
  349. package/src/tools/niagara.ts +293 -168
  350. package/src/tools/performance.ts +320 -168
  351. package/src/tools/physics.ts +268 -613
  352. package/src/tools/property-dictionary.ts +98 -0
  353. package/src/tools/sequence.ts +255 -815
  354. package/src/tools/tool-definition-utils.ts +35 -0
  355. package/src/tools/ui.ts +207 -283
  356. package/src/types/env.ts +0 -10
  357. package/src/types/tool-interfaces.ts +250 -0
  358. package/src/types/tool-types.ts +243 -21
  359. package/src/unreal-bridge.ts +460 -1550
  360. package/src/utils/command-validator.ts +75 -0
  361. package/src/utils/elicitation.ts +10 -7
  362. package/src/utils/error-handler.ts +14 -90
  363. package/src/utils/ini-reader.ts +86 -0
  364. package/src/utils/logger.ts +8 -3
  365. package/src/utils/normalize.ts +60 -0
  366. package/src/utils/response-factory.ts +39 -0
  367. package/src/utils/response-validator.ts +176 -56
  368. package/src/utils/result-helpers.ts +21 -19
  369. package/src/utils/safe-json.ts +14 -11
  370. package/src/utils/unreal-command-queue.ts +152 -0
  371. package/src/utils/validation.ts +4 -1
  372. package/src/wasm/index.ts +838 -0
  373. package/test-server.mjs +100 -0
  374. package/tests/run-unreal-tool-tests.mjs +242 -14
  375. package/tests/test-animation.mjs +44 -0
  376. package/tests/test-asset-advanced.mjs +82 -0
  377. package/tests/test-asset-errors.mjs +35 -0
  378. package/tests/test-audio.mjs +219 -0
  379. package/tests/test-automation-timeouts.mjs +98 -0
  380. package/tests/test-behavior-tree.mjs +261 -0
  381. package/tests/test-blueprint-events.mjs +35 -0
  382. package/tests/test-blueprint-graph.mjs +79 -0
  383. package/tests/test-blueprint.mjs +577 -0
  384. package/tests/test-client-mode.mjs +86 -0
  385. package/tests/test-console-command.mjs +56 -0
  386. package/tests/test-control-actor.mjs +425 -0
  387. package/tests/test-control-editor.mjs +80 -0
  388. package/tests/test-extra-tools.mjs +38 -0
  389. package/tests/test-graphql.mjs +322 -0
  390. package/tests/test-inspect.mjs +72 -0
  391. package/tests/test-landscape.mjs +60 -0
  392. package/tests/test-manage-asset.mjs +438 -0
  393. package/tests/test-manage-level.mjs +70 -0
  394. package/tests/test-materials.mjs +356 -0
  395. package/tests/test-niagara.mjs +185 -0
  396. package/tests/test-no-inline-python.mjs +122 -0
  397. package/tests/test-plugin-handshake.mjs +82 -0
  398. package/tests/test-render.mjs +33 -0
  399. package/tests/test-runner.mjs +933 -0
  400. package/tests/test-search-assets.mjs +66 -0
  401. package/tests/test-sequence.mjs +68 -0
  402. package/tests/test-system.mjs +57 -0
  403. package/tests/test-wasm.mjs +193 -0
  404. package/tests/test-world-partition.mjs +215 -0
  405. package/tsconfig.json +3 -3
  406. package/wasm/Cargo.lock +363 -0
  407. package/wasm/Cargo.toml +42 -0
  408. package/wasm/LICENSE +21 -0
  409. package/wasm/README.md +253 -0
  410. package/wasm/src/dependency_resolver.rs +377 -0
  411. package/wasm/src/lib.rs +153 -0
  412. package/wasm/src/property_parser.rs +271 -0
  413. package/wasm/src/transform_math.rs +396 -0
  414. package/wasm/tests/integration.rs +109 -0
  415. package/.github/workflows/smithery-build.yml +0 -29
  416. package/dist/tools/build_environment_advanced.d.ts +0 -65
  417. package/dist/tools/build_environment_advanced.js +0 -633
  418. package/dist/tools/rc.d.ts +0 -110
  419. package/dist/tools/rc.js +0 -437
  420. package/dist/tools/visual.d.ts +0 -40
  421. package/dist/tools/visual.js +0 -282
  422. package/dist/utils/http.d.ts +0 -6
  423. package/dist/utils/http.js +0 -151
  424. package/dist/utils/python-output.d.ts +0 -18
  425. package/dist/utils/python-output.js +0 -290
  426. package/dist/utils/python.d.ts +0 -2
  427. package/dist/utils/python.js +0 -4
  428. package/dist/utils/stdio-redirect.d.ts +0 -2
  429. package/dist/utils/stdio-redirect.js +0 -20
  430. package/docs/unreal-tool-test-cases.md +0 -574
  431. package/smithery.yaml +0 -29
  432. package/src/tools/build_environment_advanced.ts +0 -732
  433. package/src/tools/rc.ts +0 -515
  434. package/src/tools/visual.ts +0 -281
  435. package/src/utils/http.ts +0 -187
  436. package/src/utils/python-output.ts +0 -351
  437. package/src/utils/python.ts +0 -3
  438. package/src/utils/stdio-redirect.ts +0 -18
@@ -1,90 +1,54 @@
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';
4
6
 
5
7
  export class PhysicsTools {
6
- constructor(private bridge: UnrealBridge) {}
7
-
8
+ constructor(private bridge: UnrealBridge, private automationBridge?: AutomationBridge) { }
9
+
10
+ setAutomationBridge(automationBridge?: AutomationBridge) { this.automationBridge = automationBridge; }
11
+
8
12
  /**
9
13
  * Helper to find a valid skeletal mesh in the project
10
14
  */
11
15
  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
- `;
16
+ if (!this.automationBridge) {
17
+ // Return common fallback paths without plugin
18
+ return '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple';
19
+ }
60
20
 
61
21
  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'
22
+ const response = await this.automationBridge.sendAutomationRequest('find_skeletal_mesh', {
23
+ commonPaths: [
24
+ '/Game/Characters/Mannequins/Meshes/SKM_Manny',
25
+ '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
26
+ '/Game/Characters/Mannequins/Meshes/SKM_Manny_Complex',
27
+ '/Game/Characters/Mannequins/Meshes/SKM_Quinn',
28
+ '/Game/Characters/Mannequins/Meshes/SKM_Quinn_Simple',
29
+ '/Game/Characters/Mannequins/Meshes/SKM_Quinn_Complex'
30
+ ],
31
+ fallback: '/Engine/EngineMeshes/SkeletalCube'
32
+ }, {
33
+ timeoutMs: 30000
66
34
  });
67
35
 
68
- if (interpreted.success) {
69
- const meshPath = coerceString(interpreted.payload.meshPath);
36
+ if (response.success !== false && response.result) {
37
+ const meshPath = coerceString((response.result as any).meshPath);
70
38
  if (meshPath) {
71
39
  return meshPath;
72
40
  }
73
41
  }
74
42
 
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);
43
+ // Fallback to alternate path
44
+ const alternate = coerceString((response.result as any)?.alternate);
45
+ if (alternate) {
46
+ return alternate;
83
47
  }
84
48
  } catch (error) {
85
49
  console.error('Failed to find skeletal mesh:', error);
86
50
  }
87
-
51
+
88
52
  return '/Engine/EngineMeshes/SkeletalCube';
89
53
  }
90
54
 
@@ -119,33 +83,33 @@ print('RESULT:' + json.dumps(result))
119
83
  error: 'Name cannot be empty'
120
84
  };
121
85
  }
122
-
86
+
123
87
  // Check for invalid characters in name
124
- if (params.physicsAssetName.includes('@') || params.physicsAssetName.includes('#') ||
125
- params.physicsAssetName.includes('$') || params.physicsAssetName.includes('%')) {
88
+ if (params.physicsAssetName.includes('@') || params.physicsAssetName.includes('#') ||
89
+ params.physicsAssetName.includes('$') || params.physicsAssetName.includes('%')) {
126
90
  return {
127
91
  success: false,
128
92
  message: 'Failed to setup ragdoll: Name contains invalid characters',
129
93
  error: 'Name contains invalid characters'
130
94
  };
131
95
  }
132
-
96
+
133
97
  // 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_'))) {
98
+ if (params.skeletonPath && (params.skeletonPath.includes('_Skeleton') ||
99
+ params.skeletonPath.includes('SK_Mannequin') && !params.skeletonPath.includes('SKM_'))) {
136
100
  return {
137
101
  success: false,
138
102
  message: 'Failed to setup ragdoll: Must specify a valid skeletal mesh',
139
103
  error: 'Must specify a valid skeletal mesh, not a skeleton'
140
104
  };
141
105
  }
142
-
106
+
143
107
  // Validate and sanitize parameters
144
108
  const validation = validateAssetParams({
145
109
  name: params.physicsAssetName,
146
110
  savePath: params.savePath || '/Game/Physics'
147
111
  });
148
-
112
+
149
113
  if (!validation.valid) {
150
114
  return {
151
115
  success: false,
@@ -153,20 +117,20 @@ print('RESULT:' + json.dumps(result))
153
117
  error: validation.error
154
118
  };
155
119
  }
156
-
120
+
157
121
  const sanitizedParams = validation.sanitized;
158
122
  const path = sanitizedParams.savePath || '/Game/Physics';
159
-
123
+
160
124
  // Resolve skeletal mesh path
161
125
  let meshPath = params.skeletonPath;
162
-
126
+
163
127
  // Try to resolve skeleton to mesh mapping
164
128
  const resolvedPath = resolveSkeletalMeshPath(meshPath);
165
129
  if (resolvedPath && resolvedPath !== meshPath) {
166
130
  console.error(`Auto-correcting path from ${meshPath} to ${resolvedPath}`);
167
131
  meshPath = resolvedPath;
168
132
  }
169
-
133
+
170
134
  // Auto-resolve if it looks like a skeleton path or is empty
171
135
  if (!meshPath || meshPath.includes('_Skeleton') || meshPath === 'None' || meshPath === '') {
172
136
  console.error('Resolving skeletal mesh path...');
@@ -176,15 +140,15 @@ print('RESULT:' + json.dumps(result))
176
140
  console.error(`Using resolved skeletal mesh: ${meshPath}`);
177
141
  }
178
142
  }
179
-
143
+
180
144
  // Add concurrency delay to prevent race conditions
181
145
  await concurrencyDelay();
182
-
146
+
183
147
  // IMPORTANT: Physics assets require a SKELETAL MESH, not a skeleton
184
148
  // UE5 uses: /Game/Characters/Mannequins/Meshes/SKM_Manny_Simple or SKM_Quinn_Simple
185
149
  // UE4 used: /Game/Mannequin/Character/Mesh/SK_Mannequin (which no longer exists)
186
- // Fallback: /Engine/EngineMeshes/SkeletalCube
187
-
150
+ // Alternate path: /Engine/EngineMeshes/SkeletalCube
151
+
188
152
  // Common skeleton paths that should be replaced with actual skeletal mesh paths
189
153
  const skeletonToMeshMap: { [key: string]: string } = {
190
154
  '/Game/Mannequin/Character/Mesh/UE4_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
@@ -193,356 +157,50 @@ print('RESULT:' + json.dumps(result))
193
157
  '/Game/Characters/Mannequins/Skeletons/UE5_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
194
158
  '/Game/Characters/Mannequins/Skeletons/UE5_Female_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Quinn_Simple'
195
159
  };
196
-
160
+
197
161
  // Auto-fix common incorrect paths
198
162
  let actualSkeletonPath = params.skeletonPath;
199
163
  if (actualSkeletonPath && skeletonToMeshMap[actualSkeletonPath]) {
200
164
  console.error(`Auto-correcting path from ${actualSkeletonPath} to ${skeletonToMeshMap[actualSkeletonPath]}`);
201
165
  actualSkeletonPath = skeletonToMeshMap[actualSkeletonPath];
202
166
  }
203
-
167
+
204
168
  if (actualSkeletonPath && (actualSkeletonPath.includes('_Skeleton') || actualSkeletonPath.includes('SK_Mannequin'))) {
205
169
  // This is likely a skeleton path, not a skeletal mesh
206
170
  console.error('Warning: Path appears to be a skeleton, not a skeletal mesh. Auto-correcting to SKM_Manny_Simple.');
207
171
  }
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
172
 
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
173
+ // Use Automation Bridge for physics asset creation
174
+ if (!this.automationBridge) {
175
+ throw new Error('Automation Bridge not available. Physics asset creation requires plugin support.');
176
+ }
177
+
500
178
  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}`
179
+ const response = await this.automationBridge.sendAutomationRequest('setup_ragdoll', {
180
+ meshPath,
181
+ physicsAssetName: sanitizedParams.name,
182
+ savePath: path,
183
+ blendWeight: params.blendWeight,
184
+ constraints: params.constraints
185
+ }, {
186
+ timeoutMs: 120000 // 2 minutes for complex physics asset creation
505
187
  });
506
188
 
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}`
189
+ if (response.success === false) {
190
+ return {
191
+ success: false,
192
+ message: response.error || response.message || `Failed to setup ragdoll for ${sanitizedParams.name}`,
193
+ error: response.error || response.message || 'Failed to setup ragdoll'
522
194
  };
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
195
  }
537
196
 
538
- const errorMessage = interpreted.error ?? `Failed to setup ragdoll for ${sanitizedParams.name}`;
539
-
197
+ const result = response.result as any;
540
198
  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
199
+ success: true,
200
+ message: response.message || `Ragdoll physics setup completed for ${sanitizedParams.name}`,
201
+ path: coerceString(result?.path) ?? coerceString(result?.physicsAssetPath) ?? `${path}/${sanitizedParams.name}`,
202
+ existingAsset: result?.existingAsset,
203
+ ...(result || {})
546
204
  };
547
205
  } catch (error) {
548
206
  return {
@@ -556,6 +214,7 @@ print('RESULT:' + json.dumps(result))
556
214
  }
557
215
  }
558
216
 
217
+
559
218
  /**
560
219
  * Create Physics Constraint
561
220
  */
@@ -577,17 +236,17 @@ print('RESULT:' + json.dumps(result))
577
236
  // Spawn constraint actor
578
237
  const spawnCmd = `spawnactor /Script/Engine.PhysicsConstraintActor ${params.location[0]} ${params.location[1]} ${params.location[2]}`;
579
238
  await this.bridge.executeConsoleCommand(spawnCmd);
580
-
239
+
581
240
  // Configure constraint
582
241
  const commands = [
583
242
  `SetConstraintActors ${params.name} ${params.actor1} ${params.actor2}`,
584
243
  `SetConstraintType ${params.name} ${params.constraintType}`
585
244
  ];
586
-
245
+
587
246
  if (params.breakThreshold) {
588
247
  commands.push(`SetConstraintBreakThreshold ${params.name} ${params.breakThreshold}`);
589
248
  }
590
-
249
+
591
250
  if (params.limits) {
592
251
  const limits = params.limits;
593
252
  if (limits.swing1 !== undefined) {
@@ -603,12 +262,12 @@ print('RESULT:' + json.dumps(result))
603
262
  commands.push(`SetConstraintLinear ${params.name} ${limits.linear}`);
604
263
  }
605
264
  }
606
-
265
+
607
266
  await this.bridge.executeConsoleCommands(commands);
608
-
609
- return {
610
- success: true,
611
- message: `Physics constraint ${params.name} created between ${params.actor1} and ${params.actor2}`
267
+
268
+ return {
269
+ success: true,
270
+ message: `Physics constraint ${params.name} created between ${params.actor1} and ${params.actor2}`
612
271
  };
613
272
  } catch (err) {
614
273
  return { success: false, error: `Failed to create constraint: ${err}` };
@@ -632,11 +291,11 @@ print('RESULT:' + json.dumps(result))
632
291
  }) {
633
292
  try {
634
293
  const path = params.savePath || '/Game/Destruction';
635
-
294
+
636
295
  const commands = [
637
296
  `CreateGeometryCollection ${params.destructionName} ${params.meshPath} ${path}`
638
297
  ];
639
-
298
+
640
299
  // Configure fracture
641
300
  if (params.fractureSettings) {
642
301
  const settings = params.fractureSettings;
@@ -644,21 +303,21 @@ print('RESULT:' + json.dumps(result))
644
303
  `FractureGeometry ${params.destructionName} ${settings.cellCount} ${settings.minimumVolumeSize} ${settings.seed}`
645
304
  );
646
305
  }
647
-
306
+
648
307
  // Set damage threshold
649
308
  if (params.damageThreshold) {
650
309
  commands.push(`SetDamageThreshold ${params.destructionName} ${params.damageThreshold}`);
651
310
  }
652
-
311
+
653
312
  // Set debris lifetime
654
313
  if (params.debrisLifetime) {
655
314
  commands.push(`SetDebrisLifetime ${params.destructionName} ${params.debrisLifetime}`);
656
315
  }
657
-
316
+
658
317
  await this.bridge.executeConsoleCommands(commands);
659
-
660
- return {
661
- success: true,
318
+
319
+ return {
320
+ success: true,
662
321
  message: `Chaos destruction ${params.destructionName} created`,
663
322
  path: `${path}/${params.destructionName}`
664
323
  };
@@ -689,58 +348,126 @@ print('RESULT:' + json.dumps(result))
689
348
  gears: number[];
690
349
  finalDriveRatio: number;
691
350
  };
351
+ pluginDependencies?: string[];
692
352
  }) {
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
- }
353
+ // Plugin check removed as ensurePluginsEnabled is deprecated.
354
+ // Users should ensure required plugins are enabled in the editor.
355
+
356
+ const rawParams: any = params as any;
357
+
358
+ const pluginDeps: string[] | undefined = Array.isArray(params.pluginDependencies) && params.pluginDependencies.length > 0
359
+ ? params.pluginDependencies
360
+ : (Array.isArray(rawParams.plugins) && rawParams.plugins.length > 0 ? rawParams.plugins : undefined);
361
+
362
+ if (pluginDeps && pluginDeps.length > 0) {
363
+ return {
364
+ success: false,
365
+ error: 'MISSING_ENGINE_PLUGINS',
366
+ missingPlugins: pluginDeps,
367
+ message: `Required engine plugins not enabled: ${pluginDeps.join(', ')}`
368
+ };
369
+ }
370
+
371
+ const warnings: string[] = [];
372
+
373
+ const hasExplicitEmptyWheels = Array.isArray(params.wheels) && params.wheels.length === 0;
374
+
375
+ const effectiveVehicleType = typeof params.vehicleType === 'string' && params.vehicleType.trim().length > 0
376
+ ? params.vehicleType
377
+ : 'Car';
378
+
379
+ const commands = [
380
+ `CreateVehicle ${params.vehicleName} ${effectiveVehicleType}`
381
+ ];
382
+
383
+ // Configure wheels when provided
384
+ if (Array.isArray(params.wheels) && params.wheels.length > 0) {
385
+ for (const wheel of params.wheels) {
386
+ commands.push(
387
+ `AddVehicleWheel ${params.vehicleName} ${wheel.name} ${wheel.radius} ${wheel.width} ${wheel.mass}`
388
+ );
389
+
390
+ if (wheel.isSteering) {
391
+ commands.push(`SetWheelSteering ${params.vehicleName} ${wheel.name} true`);
711
392
  }
393
+ if (wheel.isDriving) {
394
+ commands.push(`SetWheelDriving ${params.vehicleName} ${wheel.name} true`);
395
+ }
396
+ }
397
+ }
398
+
399
+ // Configure engine (optional). Clamp negative RPMs and tolerate missing torqueCurve.
400
+ const effectiveEngine = params.engine ?? ((typeof rawParams.maxRPM === 'number' || Array.isArray(rawParams.torqueCurve))
401
+ ? { maxRPM: rawParams.maxRPM, torqueCurve: rawParams.torqueCurve }
402
+ : undefined);
403
+
404
+ if (effectiveEngine) {
405
+ let maxRPM = typeof effectiveEngine.maxRPM === 'number' ? effectiveEngine.maxRPM : 0;
406
+ if (maxRPM < 0) {
407
+ maxRPM = 0;
408
+ warnings.push('Engine maxRPM was negative and has been clamped to 0.');
712
409
  }
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) {
410
+ commands.push(`SetEngineMaxRPM ${params.vehicleName} ${maxRPM}`);
411
+
412
+ const rawCurve = Array.isArray(effectiveEngine.torqueCurve) ? effectiveEngine.torqueCurve : [];
413
+ for (const point of rawCurve) {
414
+ let rpm: number | undefined;
415
+ let torque: number | undefined;
416
+
417
+ if (Array.isArray(point) && point.length >= 2) {
418
+ rpm = Number(point[0]);
419
+ torque = Number(point[1]);
420
+ } else if (point && typeof point === 'object') {
421
+ const anyPoint: any = point;
422
+ rpm = typeof anyPoint.rpm === 'number' ? anyPoint.rpm : undefined;
423
+ torque = typeof anyPoint.torque === 'number' ? anyPoint.torque : undefined;
424
+ }
425
+
426
+ if (typeof rpm === 'number' && typeof torque === 'number') {
719
427
  commands.push(`AddTorqueCurvePoint ${params.vehicleName} ${rpm} ${torque}`);
720
428
  }
721
429
  }
722
-
723
- // Configure transmission
724
- if (params.transmission) {
430
+ }
431
+
432
+ // Configure transmission
433
+ if (params.transmission) {
434
+ if (Array.isArray(params.transmission.gears)) {
725
435
  for (let i = 0; i < params.transmission.gears.length; i++) {
726
436
  commands.push(
727
437
  `SetGearRatio ${params.vehicleName} ${i} ${params.transmission.gears[i]}`
728
438
  );
729
439
  }
440
+ }
441
+ if (typeof params.transmission.finalDriveRatio === 'number') {
730
442
  commands.push(
731
443
  `SetFinalDriveRatio ${params.vehicleName} ${params.transmission.finalDriveRatio}`
732
444
  );
733
445
  }
734
-
446
+ }
447
+
448
+ try {
735
449
  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}` };
450
+ } catch (_error) {
451
+ // If vehicle console commands fail (e.g., `Command not executed`), treat this as
452
+ // a best-effort configuration that falls back to engine defaults.
453
+ if (warnings.length === 0) {
454
+ warnings.push('Vehicle configuration commands could not be executed; using engine defaults.');
455
+ }
456
+ }
457
+
458
+ if (hasExplicitEmptyWheels) {
459
+ warnings.push('No wheels specified; using default wheels from vehicle preset.');
743
460
  }
461
+
462
+ if (warnings.length === 0) {
463
+ warnings.push('Verify wheel class assignments and offsets in the vehicle movement component to ensure they match your project defaults.');
464
+ }
465
+
466
+ return {
467
+ success: true,
468
+ message: `Vehicle ${params.vehicleName} configured`,
469
+ warnings
470
+ };
744
471
  }
745
472
 
746
473
  /**
@@ -753,148 +480,37 @@ print('RESULT:' + json.dumps(result))
753
480
  boneName?: string;
754
481
  isLocal?: boolean;
755
482
  }) {
483
+ if (!this.automationBridge) {
484
+ throw new Error('Automation Bridge not available. Physics force application requires plugin support.');
485
+ }
486
+
756
487
  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'
488
+ const response = await this.automationBridge.sendAutomationRequest('apply_force', {
489
+ actorName: params.actorName,
490
+ forceType: params.forceType,
491
+ vector: params.vector,
492
+ boneName: params.boneName,
493
+ isLocal: params.isLocal
494
+ }, {
495
+ timeoutMs: 30000
861
496
  });
862
497
 
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)) {
498
+ if (response.success === false) {
499
+ const result = response.result as any;
885
500
  return {
886
501
  success: false,
887
- error: interpreted.error ?? (fallbackText || 'Force application failed'),
888
- availableActors,
889
- details: interpreted.details ?? (fallbackText ? [fallbackText] : undefined)
502
+ error: response.error || response.message || 'Force application failed',
503
+ availableActors: result?.available_actors ? coerceStringArray(result.available_actors) : undefined,
504
+ details: result?.details
890
505
  };
891
506
  }
892
507
 
508
+ const result = response.result as any;
893
509
  return {
894
- success: false,
895
- error: interpreted.error ?? 'No valid result from Python',
896
- availableActors,
897
- details: interpreted.details ?? (fallbackText ? [fallbackText] : undefined)
510
+ success: true,
511
+ message: response.message || `Applied ${params.forceType} to ${params.actorName}`,
512
+ availableActors: result?.available_actors ? coerceStringArray(result.available_actors) : undefined,
513
+ ...(result || {})
898
514
  };
899
515
  } catch (err) {
900
516
  return { success: false, error: `Failed to apply force: ${err}` };
@@ -921,10 +537,10 @@ print(f"RESULT:{json.dumps(result)}")
921
537
  `EnableClothSimulation ${params.meshName}`,
922
538
  `SetClothPreset ${params.meshName} ${params.clothPreset}`
923
539
  ];
924
-
540
+
925
541
  if (params.clothPreset === 'Custom' && params.customSettings) {
926
542
  const settings = params.customSettings;
927
-
543
+
928
544
  if (settings.stiffness !== undefined) {
929
545
  commands.push(`SetClothStiffness ${params.meshName} ${settings.stiffness}`);
930
546
  }
@@ -945,12 +561,12 @@ print(f"RESULT:{json.dumps(result)}")
945
561
  commands.push(`SetClothWind ${params.meshName} ${wind[0]} ${wind[1]} ${wind[2]}`);
946
562
  }
947
563
  }
948
-
564
+
949
565
  await this.bridge.executeConsoleCommands(commands);
950
-
951
- return {
952
- success: true,
953
- message: `Cloth simulation enabled for ${params.meshName}`
566
+
567
+ return {
568
+ success: true,
569
+ message: `Cloth simulation enabled for ${params.meshName}`
954
570
  };
955
571
  } catch (err) {
956
572
  return { success: false, error: `Failed to setup cloth: ${err}` };
@@ -976,14 +592,14 @@ print(f"RESULT:{json.dumps(result)}")
976
592
  try {
977
593
  const locStr = `${params.location[0]} ${params.location[1]} ${params.location[2]}`;
978
594
  const volStr = `${params.volume[0]} ${params.volume[1]} ${params.volume[2]}`;
979
-
595
+
980
596
  const commands = [
981
597
  `CreateFluidSimulation ${params.name} ${params.fluidType} ${locStr} ${volStr}`
982
598
  ];
983
-
599
+
984
600
  if (params.customSettings) {
985
601
  const settings = params.customSettings;
986
-
602
+
987
603
  if (settings.viscosity !== undefined) {
988
604
  commands.push(`SetFluidViscosity ${params.name} ${settings.viscosity}`);
989
605
  }
@@ -1003,16 +619,55 @@ print(f"RESULT:{json.dumps(result)}")
1003
619
  );
1004
620
  }
1005
621
  }
1006
-
622
+
1007
623
  await this.bridge.executeConsoleCommands(commands);
1008
-
1009
- return {
1010
- success: true,
1011
- message: `Fluid simulation ${params.name} created`
624
+
625
+ return {
626
+ success: true,
627
+ message: `Fluid simulation ${params.name} created`
1012
628
  };
1013
629
  } catch (err) {
1014
630
  return { success: false, error: `Failed to create fluid simulation: ${err}` };
1015
631
  }
1016
632
  }
1017
633
 
634
+ /**
635
+ * Setup Physics Simulation (Create Physics Asset)
636
+ */
637
+ async setupPhysicsSimulation(params: {
638
+ meshPath?: string;
639
+ skeletonPath?: string;
640
+ physicsAssetName?: string;
641
+ savePath?: string;
642
+ }) {
643
+ if (!this.automationBridge) {
644
+ throw new Error('Automation Bridge not available. Physics asset creation requires plugin support.');
645
+ }
646
+
647
+ try {
648
+ const response = await this.automationBridge.sendAutomationRequest('animation_physics', {
649
+ action: 'setup_physics_simulation',
650
+ ...params
651
+ }, {
652
+ timeoutMs: 60000
653
+ });
654
+
655
+ if (response.success === false) {
656
+ return {
657
+ success: false,
658
+ message: response.error || response.message || 'Failed to setup physics simulation',
659
+ error: response.error || response.message
660
+ };
661
+ }
662
+
663
+ return {
664
+ success: true,
665
+ message: response.message || 'Physics simulation setup completed',
666
+ ...(response.result || {})
667
+ };
668
+ } catch (err) {
669
+ return { success: false, error: `Failed to setup physics simulation: ${err}` };
670
+ }
671
+ }
672
+
1018
673
  }