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,52 +1,67 @@
1
- import { parseStandardResult } from '../utils/python-output.js';
2
- import { escapePythonString } from '../utils/python.js';
1
+ import { ensureVector3 } from '../utils/validation.js';
3
2
  export class LightingTools {
4
3
  bridge;
5
- constructor(bridge) {
4
+ automationBridge;
5
+ constructor(bridge, automationBridge) {
6
6
  this.bridge = bridge;
7
+ this.automationBridge = automationBridge;
7
8
  }
8
- ensurePythonSpawnSucceeded(label, result) {
9
- let logs = '';
10
- if (Array.isArray(result?.LogOutput)) {
11
- logs = result.LogOutput.map((l) => String(l.Output || '')).join('');
12
- }
13
- else if (typeof result === 'string') {
14
- logs = result;
15
- }
16
- // If Python reported a traceback or explicit failure, propagate as error
17
- if (/Traceback|Error:|Failed to spawn/i.test(logs)) {
18
- throw new Error(`Unreal reported error spawning '${label}': ${logs}`);
19
- }
20
- // If script executed (ReturnValue true) and no error patterns, treat as success
21
- const executed = result?.ReturnValue === true || result?.ReturnValue === 'true';
22
- if (executed)
23
- return;
24
- // Fallback: if no ReturnValue but success-like logs exist, accept
25
- if (/spawned/i.test(logs))
26
- return;
27
- // Otherwise, uncertain
28
- throw new Error(`Uncertain spawn result for '${label}'. Engine logs:\n${logs}`);
29
- }
30
- normalizeName(value, fallback) {
9
+ setAutomationBridge(automationBridge) { this.automationBridge = automationBridge; }
10
+ normalizeName(value, defaultName) {
31
11
  if (typeof value === 'string') {
32
12
  const trimmed = value.trim();
33
13
  if (trimmed.length > 0) {
34
14
  return trimmed;
35
15
  }
36
16
  }
37
- if (typeof fallback === 'string') {
38
- const trimmedFallback = fallback.trim();
39
- if (trimmedFallback.length > 0) {
40
- return trimmedFallback;
17
+ if (typeof defaultName === 'string') {
18
+ const trimmedDefault = defaultName.trim();
19
+ if (trimmedDefault.length > 0) {
20
+ return trimmedDefault;
21
+ }
22
+ }
23
+ return `Light_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
24
+ }
25
+ async spawnLightViaAutomation(lightClass, params) {
26
+ if (!this.automationBridge) {
27
+ throw new Error('Automation Bridge not available. Cannot spawn lights without plugin support.');
28
+ }
29
+ try {
30
+ const payload = {
31
+ lightClass,
32
+ name: params.name,
33
+ };
34
+ if (params.location) {
35
+ payload.location = { x: params.location[0], y: params.location[1], z: params.location[2] };
36
+ }
37
+ if (params.rotation) {
38
+ if (Array.isArray(params.rotation)) {
39
+ payload.rotation = { pitch: params.rotation[0], yaw: params.rotation[1], roll: params.rotation[2] };
40
+ }
41
+ else {
42
+ payload.rotation = params.rotation;
43
+ }
44
+ }
45
+ if (params.properties) {
46
+ payload.properties = params.properties;
47
+ }
48
+ const response = await this.automationBridge.sendAutomationRequest('spawn_light', payload, {
49
+ timeoutMs: 60000
50
+ });
51
+ if (response.success === false) {
52
+ throw new Error(response.error || response.message || 'Failed to spawn light');
41
53
  }
54
+ return response;
55
+ }
56
+ catch (error) {
57
+ throw new Error(`Failed to spawn ${lightClass}: ${error instanceof Error ? error.message : String(error)}`);
42
58
  }
43
- throw new Error('Invalid name: must be a non-empty string');
44
59
  }
45
- // Create directional light
46
60
  async createDirectionalLight(params) {
47
61
  const name = this.normalizeName(params.name);
48
- const escapedName = escapePythonString(name);
49
- // Validate numeric parameters
62
+ if (!this.automationBridge) {
63
+ throw new Error('Automation Bridge required for light spawning');
64
+ }
50
65
  if (params.intensity !== undefined) {
51
66
  if (typeof params.intensity !== 'number' || !isFinite(params.intensity)) {
52
67
  throw new Error(`Invalid intensity value: ${params.intensity}`);
@@ -60,7 +75,6 @@ export class LightingTools {
60
75
  throw new Error(`Invalid temperature value: ${params.temperature}`);
61
76
  }
62
77
  }
63
- // Validate arrays
64
78
  if (params.color !== undefined) {
65
79
  if (!Array.isArray(params.color) || params.color.length !== 3) {
66
80
  throw new Error('Invalid color: must be an array [r,g,b]');
@@ -72,88 +86,61 @@ export class LightingTools {
72
86
  }
73
87
  }
74
88
  if (params.rotation !== undefined) {
75
- if (!Array.isArray(params.rotation) || params.rotation.length !== 3) {
76
- throw new Error('Invalid rotation: must be an array [pitch,yaw,roll]');
77
- }
78
- for (const r of params.rotation) {
79
- if (typeof r !== 'number' || !isFinite(r)) {
80
- throw new Error('Invalid rotation component: must be finite numbers');
89
+ if (Array.isArray(params.rotation)) {
90
+ if (params.rotation.length !== 3) {
91
+ throw new Error('Invalid rotation: must be an array [pitch,yaw,roll]');
92
+ }
93
+ for (const r of params.rotation) {
94
+ if (typeof r !== 'number' || !isFinite(r)) {
95
+ throw new Error('Invalid rotation component: must be finite numbers');
96
+ }
81
97
  }
82
98
  }
83
99
  }
84
100
  const rot = params.rotation || [0, 0, 0];
85
- // Build property setters
86
- const propSetters = [];
101
+ const properties = params.properties || {};
87
102
  if (params.intensity !== undefined) {
88
- propSetters.push(` light_component.set_intensity(${params.intensity})`);
103
+ properties.intensity = params.intensity;
89
104
  }
90
105
  if (params.color) {
91
- propSetters.push(` light_component.set_light_color(unreal.LinearColor(${params.color[0]}, ${params.color[1]}, ${params.color[2]}, 1.0))`);
106
+ properties.color = { r: params.color[0], g: params.color[1], b: params.color[2], a: 1.0 };
92
107
  }
93
108
  if (params.castShadows !== undefined) {
94
- propSetters.push(` light_component.set_cast_shadows(${params.castShadows ? 'True' : 'False'})`);
109
+ properties.castShadows = params.castShadows;
95
110
  }
96
111
  if (params.temperature !== undefined) {
97
- propSetters.push(` light_component.set_temperature(${params.temperature})`);
98
- }
99
- const propertiesCode = propSetters.length > 0
100
- ? propSetters.join('\n')
101
- : ' pass # No additional properties';
102
- const pythonScript = `
103
- import unreal
104
-
105
- # Get editor subsystem
106
- editor_actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
107
-
108
- # Spawn the directional light
109
- directional_light_class = unreal.DirectionalLight
110
- spawn_location = unreal.Vector(0, 0, 500)
111
- spawn_rotation = unreal.Rotator(${rot[0]}, ${rot[1]}, ${rot[2]})
112
-
113
- # Spawn the actor
114
- spawned_light = editor_actor_subsystem.spawn_actor_from_class(
115
- directional_light_class,
116
- spawn_location,
117
- spawn_rotation
118
- )
119
-
120
- if spawned_light:
121
- # Set the label/name
122
- spawned_light.set_actor_label("${escapedName}")
123
-
124
- # Get the light component
125
- light_component = spawned_light.get_component_by_class(unreal.DirectionalLightComponent)
126
-
127
- if light_component:
128
- ${propertiesCode}
129
-
130
- print("Directional light '${escapedName}' spawned")
131
- else:
132
- print("Failed to spawn directional light '${escapedName}'")
133
- `;
134
- // Execute the Python script via bridge (UE 5.6-compatible)
135
- const result = await this.bridge.executePython(pythonScript);
136
- this.ensurePythonSpawnSucceeded(name, result);
137
- return { success: true, message: `Directional light '${name}' spawned` };
112
+ properties.temperature = params.temperature;
113
+ }
114
+ if (params.useAsAtmosphereSunLight !== undefined) {
115
+ properties.useAsAtmosphereSunLight = params.useAsAtmosphereSunLight;
116
+ }
117
+ try {
118
+ await this.spawnLightViaAutomation('DirectionalLight', {
119
+ name,
120
+ location: [0, 0, 500],
121
+ rotation: rot,
122
+ properties
123
+ });
124
+ return { success: true, message: `Directional light '${name}' spawned` };
125
+ }
126
+ catch (e) {
127
+ return { success: false, error: `Failed to create directional light: ${e?.message ?? e}` };
128
+ }
138
129
  }
139
- // Create point light
140
130
  async createPointLight(params) {
141
131
  const name = this.normalizeName(params.name);
142
- const escapedName = escapePythonString(name);
143
- // Validate location array
132
+ if (!this.automationBridge) {
133
+ throw new Error('Automation Bridge required for light spawning');
134
+ }
144
135
  if (params.location !== undefined) {
145
- if (!Array.isArray(params.location) || params.location.length !== 3) {
146
- throw new Error('Invalid location: must be an array [x,y,z]');
136
+ try {
137
+ params.location = ensureVector3(params.location, 'location');
147
138
  }
148
- for (const l of params.location) {
149
- if (typeof l !== 'number' || !isFinite(l)) {
150
- throw new Error('Invalid location component: must be finite numbers');
151
- }
139
+ catch (e) {
140
+ throw new Error(`Invalid location: ${e instanceof Error ? e.message : String(e)}`);
152
141
  }
153
142
  }
154
- // Default location if not provided
155
143
  const location = params.location || [0, 0, 0];
156
- // Validate numeric parameters
157
144
  if (params.intensity !== undefined) {
158
145
  if (typeof params.intensity !== 'number' || !isFinite(params.intensity)) {
159
146
  throw new Error(`Invalid intensity value: ${params.intensity}`);
@@ -175,7 +162,6 @@ else:
175
162
  throw new Error(`Invalid falloffExponent value: ${params.falloffExponent}`);
176
163
  }
177
164
  }
178
- // Validate color array
179
165
  if (params.color !== undefined) {
180
166
  if (!Array.isArray(params.color) || params.color.length !== 3) {
181
167
  throw new Error('Invalid color: must be an array [r,g,b]');
@@ -186,68 +172,40 @@ else:
186
172
  }
187
173
  }
188
174
  }
189
- // Build property setters
190
- const propSetters = [];
175
+ const properties = {};
191
176
  if (params.intensity !== undefined) {
192
- propSetters.push(` light_component.set_intensity(${params.intensity})`);
177
+ properties.intensity = params.intensity;
193
178
  }
194
179
  if (params.radius !== undefined) {
195
- propSetters.push(` light_component.set_attenuation_radius(${params.radius})`);
180
+ properties.attenuationRadius = params.radius;
196
181
  }
197
182
  if (params.color) {
198
- propSetters.push(` light_component.set_light_color(unreal.LinearColor(${params.color[0]}, ${params.color[1]}, ${params.color[2]}, 1.0))`);
183
+ properties.color = { r: params.color[0], g: params.color[1], b: params.color[2], a: 1.0 };
199
184
  }
200
185
  if (params.castShadows !== undefined) {
201
- propSetters.push(` light_component.set_cast_shadows(${params.castShadows ? 'True' : 'False'})`);
186
+ properties.castShadows = params.castShadows;
202
187
  }
203
188
  if (params.falloffExponent !== undefined) {
204
- propSetters.push(` light_component.set_light_falloff_exponent(${params.falloffExponent})`);
205
- }
206
- const propertiesCode = propSetters.length > 0
207
- ? propSetters.join('\n')
208
- : ' pass # No additional properties';
209
- const pythonScript = `
210
- import unreal
211
-
212
- # Get editor subsystem
213
- editor_actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
214
-
215
- # Spawn the point light
216
- point_light_class = unreal.PointLight
217
- spawn_location = unreal.Vector(${location[0]}, ${location[1]}, ${location[2]})
218
- spawn_rotation = unreal.Rotator(0, 0, 0)
219
-
220
- # Spawn the actor
221
- spawned_light = editor_actor_subsystem.spawn_actor_from_class(
222
- point_light_class,
223
- spawn_location,
224
- spawn_rotation
225
- )
226
-
227
- if spawned_light:
228
- # Set the label/name
229
- spawned_light.set_actor_label("${escapedName}")
230
-
231
- # Get the light component
232
- light_component = spawned_light.get_component_by_class(unreal.PointLightComponent)
233
-
234
- if light_component:
235
- ${propertiesCode}
236
-
237
- print(f"Point light '${escapedName}' spawned at {spawn_location.x}, {spawn_location.y}, {spawn_location.z}")
238
- else:
239
- print("Failed to spawn point light '${escapedName}'")
240
- `;
241
- // Execute the Python script via bridge (UE 5.6-compatible)
242
- const result = await this.bridge.executePython(pythonScript);
243
- this.ensurePythonSpawnSucceeded(name, result);
244
- return { success: true, message: `Point light '${name}' spawned at ${location.join(', ')}` };
189
+ properties.lightFalloffExponent = params.falloffExponent;
190
+ }
191
+ try {
192
+ await this.spawnLightViaAutomation('PointLight', {
193
+ name,
194
+ location,
195
+ rotation: params.rotation,
196
+ properties
197
+ });
198
+ return { success: true, message: `Point light '${name}' spawned at ${location.join(', ')}` };
199
+ }
200
+ catch (e) {
201
+ return { success: false, error: `Failed to create point light: ${e?.message ?? e}` };
202
+ }
245
203
  }
246
- // Create spot light
247
204
  async createSpotLight(params) {
248
205
  const name = this.normalizeName(params.name);
249
- const escapedName = escapePythonString(name);
250
- // Validate required location and rotation arrays
206
+ if (!this.automationBridge) {
207
+ throw new Error('Automation Bridge required for light spawning');
208
+ }
251
209
  if (!params.location || !Array.isArray(params.location) || params.location.length !== 3) {
252
210
  throw new Error('Invalid location: must be an array [x,y,z]');
253
211
  }
@@ -256,15 +214,19 @@ else:
256
214
  throw new Error('Invalid location component: must be finite numbers');
257
215
  }
258
216
  }
259
- if (!params.rotation || !Array.isArray(params.rotation) || params.rotation.length !== 3) {
260
- throw new Error('Invalid rotation: must be an array [pitch,yaw,roll]');
217
+ if (!params.rotation) {
218
+ throw new Error('Rotation is required');
261
219
  }
262
- for (const r of params.rotation) {
263
- if (typeof r !== 'number' || !isFinite(r)) {
264
- throw new Error('Invalid rotation component: must be finite numbers');
220
+ if (Array.isArray(params.rotation)) {
221
+ if (params.rotation.length !== 3) {
222
+ throw new Error('Invalid rotation: must be an array [pitch,yaw,roll]');
223
+ }
224
+ for (const r of params.rotation) {
225
+ if (typeof r !== 'number' || !isFinite(r)) {
226
+ throw new Error('Invalid rotation component: must be finite numbers');
227
+ }
265
228
  }
266
229
  }
267
- // Validate optional numeric parameters
268
230
  if (params.intensity !== undefined) {
269
231
  if (typeof params.intensity !== 'number' || !isFinite(params.intensity)) {
270
232
  throw new Error(`Invalid intensity value: ${params.intensity}`);
@@ -297,7 +259,6 @@ else:
297
259
  throw new Error('Invalid radius: must be non-negative');
298
260
  }
299
261
  }
300
- // Validate color array
301
262
  if (params.color !== undefined) {
302
263
  if (!Array.isArray(params.color) || params.color.length !== 3) {
303
264
  throw new Error('Invalid color: must be an array [r,g,b]');
@@ -308,71 +269,43 @@ else:
308
269
  }
309
270
  }
310
271
  }
311
- // Build property setters
312
- const propSetters = [];
272
+ const properties = {};
313
273
  if (params.intensity !== undefined) {
314
- propSetters.push(` light_component.set_intensity(${params.intensity})`);
274
+ properties.intensity = params.intensity;
315
275
  }
316
276
  if (params.innerCone !== undefined) {
317
- propSetters.push(` light_component.set_inner_cone_angle(${params.innerCone})`);
277
+ properties.innerConeAngle = params.innerCone;
318
278
  }
319
279
  if (params.outerCone !== undefined) {
320
- propSetters.push(` light_component.set_outer_cone_angle(${params.outerCone})`);
280
+ properties.outerConeAngle = params.outerCone;
321
281
  }
322
282
  if (params.radius !== undefined) {
323
- propSetters.push(` light_component.set_attenuation_radius(${params.radius})`);
283
+ properties.attenuationRadius = params.radius;
324
284
  }
325
285
  if (params.color) {
326
- propSetters.push(` light_component.set_light_color(unreal.LinearColor(${params.color[0]}, ${params.color[1]}, ${params.color[2]}, 1.0))`);
286
+ properties.color = { r: params.color[0], g: params.color[1], b: params.color[2], a: 1.0 };
327
287
  }
328
288
  if (params.castShadows !== undefined) {
329
- propSetters.push(` light_component.set_cast_shadows(${params.castShadows ? 'True' : 'False'})`);
330
- }
331
- const propertiesCode = propSetters.length > 0
332
- ? propSetters.join('\n')
333
- : ' pass # No additional properties';
334
- const pythonScript = `
335
- import unreal
336
-
337
- # Get editor subsystem
338
- editor_actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
339
-
340
- # Spawn the spot light
341
- spot_light_class = unreal.SpotLight
342
- spawn_location = unreal.Vector(${params.location[0]}, ${params.location[1]}, ${params.location[2]})
343
- spawn_rotation = unreal.Rotator(${params.rotation[0]}, ${params.rotation[1]}, ${params.rotation[2]})
344
-
345
- # Spawn the actor
346
- spawned_light = editor_actor_subsystem.spawn_actor_from_class(
347
- spot_light_class,
348
- spawn_location,
349
- spawn_rotation
350
- )
351
-
352
- if spawned_light:
353
- # Set the label/name
354
- spawned_light.set_actor_label("${escapedName}")
355
-
356
- # Get the light component
357
- light_component = spawned_light.get_component_by_class(unreal.SpotLightComponent)
358
-
359
- if light_component:
360
- ${propertiesCode}
361
-
362
- print(f"Spot light '${escapedName}' spawned at {spawn_location.x}, {spawn_location.y}, {spawn_location.z}")
363
- else:
364
- print("Failed to spawn spot light '${escapedName}'")
365
- `;
366
- // Execute the Python script via bridge (UE 5.6-compatible)
367
- const result = await this.bridge.executePython(pythonScript);
368
- this.ensurePythonSpawnSucceeded(name, result);
369
- return { success: true, message: `Spot light '${name}' spawned at ${params.location.join(', ')}` };
289
+ properties.castShadows = params.castShadows;
290
+ }
291
+ try {
292
+ await this.spawnLightViaAutomation('SpotLight', {
293
+ name,
294
+ location: params.location,
295
+ rotation: params.rotation,
296
+ properties
297
+ });
298
+ return { success: true, message: `Spot light '${name}' spawned at ${params.location.join(', ')}` };
299
+ }
300
+ catch (e) {
301
+ return { success: false, error: `Failed to create spot light: ${e?.message ?? e}` };
302
+ }
370
303
  }
371
- // Create rect light
372
304
  async createRectLight(params) {
373
305
  const name = this.normalizeName(params.name);
374
- const escapedName = escapePythonString(name);
375
- // Validate required location and rotation arrays
306
+ if (!this.automationBridge) {
307
+ throw new Error('Automation Bridge required for light spawning');
308
+ }
376
309
  if (!params.location || !Array.isArray(params.location) || params.location.length !== 3) {
377
310
  throw new Error('Invalid location: must be an array [x,y,z]');
378
311
  }
@@ -381,15 +314,19 @@ else:
381
314
  throw new Error('Invalid location component: must be finite numbers');
382
315
  }
383
316
  }
384
- if (!params.rotation || !Array.isArray(params.rotation) || params.rotation.length !== 3) {
385
- throw new Error('Invalid rotation: must be an array [pitch,yaw,roll]');
317
+ if (!params.rotation) {
318
+ throw new Error('Rotation is required');
386
319
  }
387
- for (const r of params.rotation) {
388
- if (typeof r !== 'number' || !isFinite(r)) {
389
- throw new Error('Invalid rotation component: must be finite numbers');
320
+ if (Array.isArray(params.rotation)) {
321
+ if (params.rotation.length !== 3) {
322
+ throw new Error('Invalid rotation: must be an array [pitch,yaw,roll]');
323
+ }
324
+ for (const r of params.rotation) {
325
+ if (typeof r !== 'number' || !isFinite(r)) {
326
+ throw new Error('Invalid rotation component: must be finite numbers');
327
+ }
390
328
  }
391
329
  }
392
- // Validate optional numeric parameters
393
330
  if (params.width !== undefined) {
394
331
  if (typeof params.width !== 'number' || !isFinite(params.width)) {
395
332
  throw new Error(`Invalid width value: ${params.width}`);
@@ -414,7 +351,6 @@ else:
414
351
  throw new Error('Invalid intensity: must be non-negative');
415
352
  }
416
353
  }
417
- // Validate color array
418
354
  if (params.color !== undefined) {
419
355
  if (!Array.isArray(params.color) || params.color.length !== 3) {
420
356
  throw new Error('Invalid color: must be an array [r,g,b]');
@@ -425,268 +361,166 @@ else:
425
361
  }
426
362
  }
427
363
  }
428
- // Build property setters
429
- const propSetters = [];
364
+ const properties = {};
430
365
  if (params.intensity !== undefined) {
431
- propSetters.push(` light_component.set_intensity(${params.intensity})`);
366
+ properties.intensity = params.intensity;
432
367
  }
433
368
  if (params.color) {
434
- propSetters.push(` light_component.set_light_color(unreal.LinearColor(${params.color[0]}, ${params.color[1]}, ${params.color[2]}, 1.0))`);
369
+ properties.color = { r: params.color[0], g: params.color[1], b: params.color[2], a: 1.0 };
435
370
  }
436
371
  if (params.width !== undefined) {
437
- propSetters.push(` light_component.set_source_width(${params.width})`);
372
+ properties.sourceWidth = params.width;
438
373
  }
439
374
  if (params.height !== undefined) {
440
- propSetters.push(` light_component.set_source_height(${params.height})`);
441
- }
442
- const propertiesCode = propSetters.length > 0
443
- ? propSetters.join('\n')
444
- : ' pass # No additional properties';
445
- const pythonScript = `
446
- import unreal
447
-
448
- # Get editor subsystem
449
- editor_actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
450
-
451
- # Spawn the rect light
452
- rect_light_class = unreal.RectLight
453
- spawn_location = unreal.Vector(${params.location[0]}, ${params.location[1]}, ${params.location[2]})
454
- spawn_rotation = unreal.Rotator(${params.rotation[0]}, ${params.rotation[1]}, ${params.rotation[2]})
455
-
456
- # Spawn the actor
457
- spawned_light = editor_actor_subsystem.spawn_actor_from_class(
458
- rect_light_class,
459
- spawn_location,
460
- spawn_rotation
461
- )
462
-
463
- if spawned_light:
464
- # Set the label/name
465
- spawned_light.set_actor_label("${escapedName}")
466
-
467
- # Get the light component
468
- light_component = spawned_light.get_component_by_class(unreal.RectLightComponent)
469
-
470
- if light_component:
471
- ${propertiesCode}
472
-
473
- print(f"Rect light '${escapedName}' spawned at {spawn_location.x}, {spawn_location.y}, {spawn_location.z}")
474
- else:
475
- print("Failed to spawn rect light '${escapedName}'")
476
- `;
477
- // Execute the Python script via bridge (UE 5.6-compatible)
478
- const result = await this.bridge.executePython(pythonScript);
479
- this.ensurePythonSpawnSucceeded(name, result);
480
- return { success: true, message: `Rect light '${name}' spawned at ${params.location.join(', ')}` };
375
+ properties.sourceHeight = params.height;
376
+ }
377
+ try {
378
+ await this.spawnLightViaAutomation('RectLight', {
379
+ name,
380
+ location: params.location,
381
+ rotation: params.rotation,
382
+ properties
383
+ });
384
+ return { success: true, message: `Rect light '${name}' spawned at ${params.location.join(', ')}` };
385
+ }
386
+ catch (e) {
387
+ return { success: false, error: `Failed to create rect light: ${e?.message ?? e}` };
388
+ }
389
+ }
390
+ async createDynamicLight(params) {
391
+ try {
392
+ const name = typeof params.name === 'string' && params.name.trim().length > 0 ? params.name.trim() : `DynamicLight_${Date.now() % 10000}`;
393
+ const lightTypeRaw = typeof params.lightType === 'string' && params.lightType.trim().length > 0 ? params.lightType.trim() : 'Point';
394
+ const location = Array.isArray(params.location) ? { x: params.location[0], y: params.location[1], z: params.location[2] } : (params.location || { x: 0, y: 0, z: 100 });
395
+ const toArray3 = (loc) => Array.isArray(loc)
396
+ ? [Number(loc[0]) || 0, Number(loc[1]) || 0, Number(loc[2]) || 0]
397
+ : [Number(loc?.x) || 0, Number(loc?.y) || 0, Number(loc?.z) || 0];
398
+ const locArr = toArray3(location);
399
+ const typeNorm = (lightTypeRaw || 'Point').toLowerCase();
400
+ switch (typeNorm) {
401
+ case 'directional':
402
+ case 'directionallight':
403
+ return await this.createDirectionalLight({ name, intensity: params.intensity, color: Array.isArray(params.color) ? [params.color[0], params.color[1], params.color[2]] : (params.color ? [params.color.r, params.color.g, params.color.b] : undefined), rotation: params.rotation });
404
+ case 'spot':
405
+ case 'spotlight':
406
+ return await this.createSpotLight({ name, location: locArr, rotation: params.rotation, intensity: params.intensity, innerCone: undefined, outerCone: undefined, color: Array.isArray(params.color) ? params.color : (params.color ? [params.color.r, params.color.g, params.color.b] : undefined) });
407
+ case 'rect':
408
+ case 'rectlight':
409
+ return await this.createRectLight({ name, location: locArr, rotation: params.rotation, width: undefined, height: undefined, intensity: params.intensity, color: Array.isArray(params.color) ? params.color : (params.color ? [params.color.r, params.color.g, params.color.b] : undefined) });
410
+ case 'point':
411
+ default:
412
+ return await this.createPointLight({ name, location: locArr, intensity: params.intensity, radius: undefined, color: Array.isArray(params.color) ? params.color : (params.color ? [params.color.r, params.color.g, params.color.b] : undefined), castShadows: undefined });
413
+ }
414
+ }
415
+ catch (err) {
416
+ return { success: false, error: `Failed to create dynamic light: ${err}` };
417
+ }
481
418
  }
482
- // Create sky light
483
419
  async createSkyLight(params) {
484
420
  const name = this.normalizeName(params.name);
485
- const escapedName = escapePythonString(name);
486
- const sourceTypeRaw = typeof params.sourceType === 'string' ? params.sourceType.trim() : undefined;
487
- const normalizedSourceType = sourceTypeRaw
488
- ? sourceTypeRaw.toLowerCase() === 'specifiedcubemap'
489
- ? 'SpecifiedCubemap'
490
- : sourceTypeRaw.toLowerCase() === 'capturedscene'
491
- ? 'CapturedScene'
492
- : undefined
493
- : undefined;
494
- const cubemapPath = typeof params.cubemapPath === 'string' ? params.cubemapPath.trim() : undefined;
495
- if (normalizedSourceType === 'SpecifiedCubemap' && (!cubemapPath || cubemapPath.length === 0)) {
421
+ if (params.sourceType === 'SpecifiedCubemap' && (!params.cubemapPath || params.cubemapPath.trim().length === 0)) {
496
422
  const message = 'cubemapPath is required when sourceType is SpecifiedCubemap';
497
423
  return { success: false, error: message, message };
498
424
  }
499
- const escapedCubemapPath = cubemapPath ? escapePythonString(cubemapPath) : '';
500
- const python = `
501
- import unreal
502
- import json
503
-
504
- result = {
505
- "success": False,
506
- "message": "",
507
- "error": "",
508
- "warnings": []
509
- }
510
-
511
- def add_warning(text):
512
- if text:
513
- result["warnings"].append(str(text))
514
-
515
- def finish():
516
- if result["success"]:
517
- if not result["message"]:
518
- result["message"] = "Sky light ensured"
519
- result.pop("error", None)
520
- else:
521
- if not result["error"]:
522
- result["error"] = result["message"] or "Failed to ensure sky light"
523
- if not result["message"]:
524
- result["message"] = result["error"]
525
- if not result["warnings"]:
526
- result.pop("warnings", None)
527
- print('RESULT:' + json.dumps(result))
528
-
529
- try:
530
- actor_sub = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
531
- if not actor_sub:
532
- result["error"] = "EditorActorSubsystem unavailable"
533
- finish()
534
- raise SystemExit(0)
535
-
536
- spawn_location = unreal.Vector(0.0, 0.0, 500.0)
537
- spawn_rotation = unreal.Rotator(0.0, 0.0, 0.0)
538
-
539
- actor = None
540
- try:
541
- for candidate in actor_sub.get_all_level_actors():
542
- try:
543
- if candidate.get_class().get_name() == 'SkyLight':
544
- actor = candidate
545
- break
546
- except Exception:
547
- continue
548
- except Exception:
549
- pass
550
-
551
- if actor is None:
552
- actor = actor_sub.spawn_actor_from_class(unreal.SkyLight, spawn_location, spawn_rotation)
553
-
554
- if not actor:
555
- result["error"] = "Failed to spawn SkyLight actor"
556
- finish()
557
- raise SystemExit(0)
558
-
559
- try:
560
- actor.set_actor_label("${escapedName}")
561
- except Exception:
562
- pass
563
-
564
- comp = actor.get_component_by_class(unreal.SkyLightComponent)
565
- if not comp:
566
- result["error"] = "SkyLight component missing"
567
- finish()
568
- raise SystemExit(0)
569
-
570
- ${params.intensity !== undefined ? `
571
- try:
572
- comp.set_intensity(${params.intensity})
573
- except Exception:
574
- try:
575
- comp.set_editor_property('intensity', ${params.intensity})
576
- except Exception:
577
- add_warning('Unable to set intensity property')
578
- ` : ''}
579
-
580
- source_type = ${normalizedSourceType ? `'${normalizedSourceType}'` : 'None'}
581
- if source_type:
582
- try:
583
- comp.set_editor_property('source_type', getattr(unreal.SkyLightSourceType, source_type))
584
- except Exception:
585
- try:
586
- comp.source_type = getattr(unreal.SkyLightSourceType, source_type)
587
- except Exception:
588
- add_warning(f"Unable to set source type {source_type}")
589
-
590
- if source_type == 'SpecifiedCubemap':
591
- path = "${escapedCubemapPath}"
592
- if not path:
593
- result["error"] = "cubemapPath is required when sourceType is SpecifiedCubemap"
594
- finish()
595
- raise SystemExit(0)
596
- try:
597
- exists = unreal.EditorAssetLibrary.does_asset_exist(path)
598
- except Exception:
599
- exists = False
600
- if not exists:
601
- result["error"] = f"Cubemap asset not found: {path}"
602
- finish()
603
- raise SystemExit(0)
604
- try:
605
- cube = unreal.EditorAssetLibrary.load_asset(path)
606
- except Exception as load_err:
607
- result["error"] = f"Failed to load cubemap asset: {load_err}"
608
- finish()
609
- raise SystemExit(0)
610
- if not cube:
611
- result["error"] = f"Cubemap asset could not be loaded: {path}"
612
- finish()
613
- raise SystemExit(0)
614
- try:
615
- if hasattr(comp, 'set_cubemap'):
616
- comp.set_cubemap(cube)
617
- else:
618
- comp.set_editor_property('cubemap', cube)
619
- except Exception as assign_err:
620
- result["error"] = f"Failed to assign cubemap: {assign_err}"
621
- finish()
622
- raise SystemExit(0)
623
-
624
- if ${params.recapture ? 'True' : 'False'}:
625
- try:
626
- comp.recapture_sky()
627
- except Exception as recapture_err:
628
- add_warning(f"Recapture failed: {recapture_err}")
629
-
630
- result["success"] = True
631
- result["message"] = "Sky light ensured"
632
- finish()
633
-
634
- except SystemExit:
635
- pass
636
- except Exception as run_err:
637
- result["error"] = str(run_err)
638
- finish()
639
- `.trim();
640
- const resp = await this.bridge.executePython(python);
641
- const parsed = parseStandardResult(resp).data;
642
- if (parsed) {
643
- if (parsed.success) {
425
+ if (!this.automationBridge) {
426
+ throw new Error('Automation Bridge required for sky light creation');
427
+ }
428
+ try {
429
+ const properties = {};
430
+ if (params.intensity !== undefined)
431
+ properties.Intensity = params.intensity;
432
+ if (params.castShadows !== undefined)
433
+ properties.CastShadows = params.castShadows;
434
+ if (params.realTimeCapture !== undefined)
435
+ properties.RealTimeCapture = params.realTimeCapture;
436
+ if (params.color)
437
+ properties.LightColor = { r: params.color[0], g: params.color[1], b: params.color[2], a: 1.0 };
438
+ const payload = {
439
+ name,
440
+ sourceType: params.sourceType || 'CapturedScene',
441
+ location: params.location,
442
+ rotation: params.rotation,
443
+ properties
444
+ };
445
+ if (params.cubemapPath) {
446
+ payload.cubemapPath = params.cubemapPath;
447
+ }
448
+ if (params.intensity !== undefined) {
449
+ payload.intensity = params.intensity;
450
+ }
451
+ if (params.recapture) {
452
+ payload.recapture = params.recapture;
453
+ }
454
+ const response = await this.automationBridge.sendAutomationRequest('spawn_sky_light', payload, {
455
+ timeoutMs: 60000
456
+ });
457
+ if (response.success === false) {
644
458
  return {
645
- success: true,
646
- message: parsed.message ?? 'Sky light ensured',
647
- warnings: Array.isArray(parsed.warnings) && parsed.warnings.length > 0 ? parsed.warnings : undefined
459
+ success: false,
460
+ error: response.error || response.message || 'Failed to create sky light'
648
461
  };
649
462
  }
463
+ return {
464
+ success: true,
465
+ message: response.message || 'Sky light created',
466
+ ...(response.result || {})
467
+ };
468
+ }
469
+ catch (error) {
650
470
  return {
651
471
  success: false,
652
- error: parsed.error ?? parsed.message ?? 'Failed to ensure sky light',
653
- warnings: Array.isArray(parsed.warnings) && parsed.warnings.length > 0 ? parsed.warnings : undefined
472
+ error: `Failed to create sky light: ${error instanceof Error ? error.message : String(error)}`
654
473
  };
655
474
  }
656
- return { success: true, message: 'Sky light ensured' };
657
475
  }
658
- // Remove duplicate SkyLights and keep only one (named target label)
659
476
  async ensureSingleSkyLight(params) {
660
- const fallbackName = 'MCP_Test_Sky';
661
- const name = this.normalizeName(params?.name, fallbackName);
662
- const escapedName = escapePythonString(name);
477
+ const defaultName = 'MCP_Test_Sky';
478
+ const name = this.normalizeName(params?.name, defaultName);
663
479
  const recapture = !!params?.recapture;
664
- const py = `\nimport unreal, json\nactor_sub = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)\nactors = actor_sub.get_all_level_actors() if actor_sub else []\nskies = []\nfor a in actors:\n try:\n if a.get_class().get_name() == 'SkyLight':\n skies.append(a)\n except Exception: pass\nkeep = None\n# Prefer one with matching label; otherwise keep the first\nfor a in skies:\n try:\n label = a.get_actor_label()\n if label == "${escapedName}":\n keep = a\n break\n except Exception: pass\nif keep is None and len(skies) > 0:\n keep = skies[0]\n# Rename the kept one if needed\nif keep is not None:\n try: keep.set_actor_label("${escapedName}")\n except Exception: pass\n# Destroy all others using the correct non-deprecated API\nremoved = 0\nfor a in skies:\n if keep is not None and a == keep:\n continue\n try:\n # Use EditorActorSubsystem.destroy_actor instead of deprecated EditorLevelLibrary\n actor_sub.destroy_actor(a)\n removed += 1\n except Exception: pass\n# Optionally recapture\nif keep is not None and ${recapture ? 'True' : 'False'}:\n try:\n comp = keep.get_component_by_class(unreal.SkyLightComponent)\n if comp: comp.recapture_sky()\n except Exception: pass\nprint('RESULT:' + json.dumps({'success': True, 'removed': removed, 'kept': True if keep else False}))\n`.trim();
665
- const resp = await this.bridge.executePython(py);
666
- let out = '';
667
- if (resp?.LogOutput && Array.isArray(resp.LogOutput)) {
668
- out = resp.LogOutput.map((l) => l.Output || '').join('');
480
+ if (!this.automationBridge) {
481
+ throw new Error('Automation Bridge required for sky light management');
669
482
  }
670
- else if (typeof resp === 'string') {
671
- out = resp;
483
+ try {
484
+ const response = await this.automationBridge.sendAutomationRequest('ensure_single_sky_light', {
485
+ name,
486
+ recapture
487
+ }, {
488
+ timeoutMs: 60000
489
+ });
490
+ if (response.success === false) {
491
+ return {
492
+ success: false,
493
+ error: response.error || response.message || 'Failed to ensure single sky light'
494
+ };
495
+ }
496
+ return {
497
+ success: true,
498
+ message: response.message || `Ensured single SkyLight (removed ${response.result?.removed || 0})`,
499
+ ...(response.result || {})
500
+ };
672
501
  }
673
- else {
674
- out = JSON.stringify(resp);
502
+ catch (error) {
503
+ return {
504
+ success: false,
505
+ error: `Failed to ensure single sky light: ${error instanceof Error ? error.message : String(error)}`
506
+ };
675
507
  }
676
- const m = out.match(/RESULT:({.*})/);
677
- if (m) {
508
+ }
509
+ async setupGlobalIllumination(params) {
510
+ if (this.automationBridge) {
678
511
  try {
679
- const parsed = JSON.parse(m[1]);
680
- if (parsed.success) {
681
- return { success: true, removed: parsed.removed, message: `Ensured single SkyLight (removed ${parsed.removed})` };
682
- }
512
+ const response = await this.automationBridge.sendAutomationRequest('setup_global_illumination', {
513
+ method: params.method,
514
+ quality: params.quality,
515
+ indirectLightingIntensity: params.indirectLightingIntensity,
516
+ bounces: params.bounces
517
+ });
518
+ if (response.success)
519
+ return { success: true, message: 'Global illumination configured via bridge', ...(response.result || {}) };
520
+ }
521
+ catch (_e) {
683
522
  }
684
- catch { }
685
523
  }
686
- return { success: true, message: 'Ensured single SkyLight' };
687
- }
688
- // Setup global illumination
689
- async setupGlobalIllumination(params) {
690
524
  const commands = [];
691
525
  switch (params.method) {
692
526
  case 'Lightmass':
@@ -715,10 +549,25 @@ except Exception as run_err:
715
549
  for (const cmd of commands) {
716
550
  await this.bridge.executeConsoleCommand(cmd);
717
551
  }
718
- return { success: true, message: 'Global illumination configured' };
552
+ return { success: true, message: 'Global illumination configured (console)' };
719
553
  }
720
- // Configure shadows
721
554
  async configureShadows(params) {
555
+ if (this.automationBridge) {
556
+ try {
557
+ const response = await this.automationBridge.sendAutomationRequest('configure_shadows', {
558
+ shadowQuality: params.shadowQuality,
559
+ cascadedShadows: params.cascadedShadows,
560
+ shadowDistance: params.shadowDistance,
561
+ contactShadows: params.contactShadows,
562
+ rayTracedShadows: params.rayTracedShadows,
563
+ virtualShadowMaps: params.rayTracedShadows
564
+ });
565
+ if (response.success)
566
+ return { success: true, message: 'Shadow settings configured via bridge', ...(response.result || {}) };
567
+ }
568
+ catch (_e) {
569
+ }
570
+ }
722
571
  const commands = [];
723
572
  if (params.shadowQuality) {
724
573
  const qualityMap = { 'Low': 0, 'Medium': 1, 'High': 2, 'Epic': 3 };
@@ -739,500 +588,120 @@ except Exception as run_err:
739
588
  for (const cmd of commands) {
740
589
  await this.bridge.executeConsoleCommand(cmd);
741
590
  }
742
- return { success: true, message: 'Shadow settings configured' };
591
+ return { success: true, message: 'Shadow settings configured (console)' };
743
592
  }
744
- // Build lighting (Python-based)
745
593
  async buildLighting(params) {
746
- const q = params.quality || 'High';
747
- const qualityMap = {
748
- 'Preview': 'QUALITY_PREVIEW',
749
- 'Medium': 'QUALITY_MEDIUM',
750
- 'High': 'QUALITY_HIGH',
751
- 'Production': 'QUALITY_PRODUCTION'
752
- };
753
- const qualityEnum = qualityMap[q] || 'QUALITY_HIGH';
754
- // First try to ensure precomputed lighting is allowed and force-no-precomputed is disabled, then save changes
755
- const disablePrecomputedPy = `
756
- import unreal, json
757
-
758
- messages = []
759
-
760
- # Precheck: verify project supports static lighting (Support Static Lighting)
761
- try:
762
- rs = unreal.get_default_object(unreal.RendererSettings)
763
- support_static = False
764
- try:
765
- support_static = bool(rs.get_editor_property('bSupportStaticLighting'))
766
- except Exception:
767
- try:
768
- support_static = bool(rs.get_editor_property('support_static_lighting'))
769
- except Exception:
770
- support_static = False
771
- if not support_static:
772
- print('RESULT:' + json.dumps({
773
- 'success': False,
774
- 'status': 'staticDisabled',
775
- 'error': 'Project has Support Static Lighting disabled (r.AllowStaticLighting=0). Enable Project Settings -> Rendering -> Support Static Lighting and restart the editor.'
776
- }))
777
- raise SystemExit(0)
778
- else:
779
- messages.append('Support Static Lighting is enabled')
780
- except Exception as e:
781
- messages.append(f'Precheck failed: {e}')
782
-
783
- # Ensure runtime CVar does not force disable precomputed lighting
784
- try:
785
- unreal.SystemLibrary.execute_console_command(None, 'r.ForceNoPrecomputedLighting 0')
786
- messages.append('Set r.ForceNoPrecomputedLighting 0')
787
- except Exception as e:
788
- messages.append(f'r.ForceNoPrecomputedLighting failed: {e}')
789
-
790
- # Temporarily disable source control prompts to avoid checkout dialogs during automated saves
791
- try:
792
- prefs = unreal.SourceControlPreferences()
793
- try:
794
- prefs.set_enable_source_control(False)
795
- except Exception:
796
- try:
797
- prefs.enable_source_control = False
798
- except Exception:
799
- pass
800
- messages.append('Disabled Source Control for this session')
801
- except Exception as e:
802
- messages.append(f'SourceControlPreferences modify failed: {e}')
803
-
804
- try:
805
- ues = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
806
- les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
807
- world = ues.get_editor_world() if ues else None
808
-
809
- if world:
810
- world_settings = world.get_world_settings()
811
- if world_settings:
812
- # Mark for modification
813
- try:
814
- world_settings.modify()
815
- except Exception:
816
- pass
817
-
818
- # Try all known variants of the property name
819
- for prop in ['force_no_precomputed_lighting', 'bForceNoPrecomputedLighting']:
820
- try:
821
- world_settings.set_editor_property(prop, False)
822
- messages.append(f"Set WorldSettings.{prop}=False")
823
- except Exception as e:
824
- messages.append(f"Failed setting {prop}: {e}")
825
-
826
- # Also update the Class Default Object (CDO) to help persistence in some versions
827
- try:
828
- ws_class = world_settings.get_class()
829
- ws_cdo = unreal.get_default_object(ws_class)
830
- if ws_cdo:
831
- try:
832
- ws_cdo.set_editor_property('bForceNoPrecomputedLighting', False)
833
- messages.append('Set CDO bForceNoPrecomputedLighting=False')
834
- except Exception:
835
- pass
836
- try:
837
- ws_cdo.set_editor_property('force_no_precomputed_lighting', False)
838
- messages.append('Set CDO force_no_precomputed_lighting=False')
839
- except Exception:
840
- pass
841
- except Exception as e:
842
- messages.append(f'CDO update failed: {e}')
843
-
844
- # Apply and save level to persist change
845
- try:
846
- if hasattr(world_settings, 'post_edit_change'):
847
- world_settings.post_edit_change()
848
- except Exception:
849
- pass
850
-
851
- # Save current level/package
852
- try:
853
- wp = world.get_path_name()
854
- pkg_path = wp.split('.')[0] if '.' in wp else wp
855
- unreal.EditorAssetLibrary.save_asset(pkg_path)
856
- messages.append(f'Saved world asset: {pkg_path}')
857
- except Exception as e:
858
- messages.append(f'Failed to save world asset: {e}')
859
-
860
- # Secondary save method
861
- try:
862
- if les:
863
- les.save_current_level()
864
- messages.append('LevelEditorSubsystem.save_current_level called')
865
- except Exception as e:
866
- messages.append(f'save_current_level failed: {e}')
867
-
868
- # Verify final value(s)
869
- try:
870
- force_val = None
871
- bforce_val = None
872
- try:
873
- force_val = bool(world_settings.get_editor_property('force_no_precomputed_lighting'))
874
- except Exception:
875
- pass
876
- try:
877
- bforce_val = bool(world_settings.get_editor_property('bForceNoPrecomputedLighting'))
878
- except Exception:
879
- pass
880
- messages.append(f'Verify WorldSettings.force_no_precomputed_lighting={force_val}')
881
- messages.append(f'Verify WorldSettings.bForceNoPrecomputedLighting={bforce_val}')
882
- except Exception as e:
883
- messages.append(f'Verify failed: {e}')
884
- except Exception as e:
885
- messages.append(f'World modification failed: {e}')
886
-
887
- print('RESULT:' + json.dumps({'success': True, 'messages': messages, 'flags': {
888
- 'force_no_precomputed_lighting': force_val if 'force_val' in locals() else None,
889
- 'bForceNoPrecomputedLighting': bforce_val if 'bforce_val' in locals() else None
890
- }}))
891
- `.trim();
892
- // Execute the disable script first and parse messages for diagnostics
893
- const preResp = await this.bridge.executePython(disablePrecomputedPy);
594
+ if (!this.automationBridge) {
595
+ throw new Error('Automation Bridge required for lighting build');
596
+ }
894
597
  try {
895
- const preOut = typeof preResp === 'string' ? preResp : JSON.stringify(preResp);
896
- const pm = preOut.match(/RESULT:({.*})/);
897
- if (pm) {
898
- try {
899
- const preJson = JSON.parse(pm[1]);
900
- if (preJson && preJson.success === false && preJson.status === 'staticDisabled') {
901
- return { success: false, error: preJson.error };
902
- }
903
- if (preJson && preJson.flags) {
904
- const f = preJson.flags;
905
- if (f.bForceNoPrecomputedLighting === true || f.force_no_precomputed_lighting === true) {
906
- return {
907
- success: false,
908
- error: 'WorldSettings.bForceNoPrecomputedLighting is true. Unreal will skip static lighting builds. Please uncheck "Force No Precomputed Lighting" in this level\'s World Settings (or enable Support Static Lighting in Project Settings) and retry. If using source control, check out the map asset first.'
909
- };
910
- }
911
- }
912
- }
913
- catch { }
598
+ const response = await this.automationBridge.sendAutomationRequest('bake_lightmap', {
599
+ quality: params.quality || 'High',
600
+ buildOnlySelected: params.buildOnlySelected || false,
601
+ buildReflectionCaptures: params.buildReflectionCaptures !== false,
602
+ levelPath: params.levelPath
603
+ }, {
604
+ timeoutMs: 300000
605
+ });
606
+ if (response.success === false) {
607
+ return {
608
+ success: false,
609
+ error: response.error || response.message || 'Failed to build lighting'
610
+ };
914
611
  }
612
+ return {
613
+ success: true,
614
+ message: response.message || 'Lighting build started',
615
+ ...(response.result || {})
616
+ };
915
617
  }
916
- catch { }
917
- // Small delay to ensure settings are applied
918
- await new Promise(resolve => setTimeout(resolve, 150));
919
- // Now execute the lighting build
920
- const py = `
921
- import unreal
922
- import json
923
-
924
- try:
925
- les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
926
- if les:
927
- # Build light maps with specified quality and reflection captures option
928
- les.build_light_maps(unreal.LightingBuildQuality.${qualityEnum}, ${params.buildReflectionCaptures !== false ? 'True' : 'False'})
929
- print('RESULT:' + json.dumps({'success': True, 'message': 'Lighting build started via LevelEditorSubsystem'}))
930
- else:
931
- # Fallback: Try using console command if subsystem not available
932
- try:
933
- unreal.SystemLibrary.execute_console_command(None, 'BuildLighting Quality=${q}')
934
- ${params.buildReflectionCaptures ? "unreal.SystemLibrary.execute_console_command(None, 'BuildReflectionCaptures')" : ''}
935
- print('RESULT:' + json.dumps({'success': True, 'message': 'Lighting build started via console command (fallback)'}))
936
- except Exception as e2:
937
- print('RESULT:' + json.dumps({'success': False, 'error': f'Build failed: {str(e2)}'}))
938
- except Exception as e:
939
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
940
- `.trim();
941
- const resp = await this.bridge.executePython(py);
942
- const out = typeof resp === 'string' ? resp : JSON.stringify(resp);
943
- const m = out.match(/RESULT:({.*})/);
944
- if (m) {
945
- try {
946
- const parsed = JSON.parse(m[1]);
947
- return parsed.success ? { success: true, message: parsed.message } : { success: false, error: parsed.error };
948
- }
949
- catch { }
618
+ catch (error) {
619
+ return {
620
+ success: false,
621
+ error: `Failed to build lighting: ${error instanceof Error ? error.message : String(error)}`
622
+ };
950
623
  }
951
- return { success: true, message: 'Lighting build started' };
952
624
  }
953
- // Create a new level with proper lighting settings as workaround
954
625
  async createLightingEnabledLevel(params) {
955
626
  const levelName = params?.levelName || 'LightingEnabledLevel';
956
- const py = `
957
- import unreal
958
- import json
959
-
960
- def create_lighting_enabled_level():
961
- """Create a new level with lighting enabled"""
962
- try:
963
- les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
964
- ues = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
965
- actor_sub = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
966
- editor_asset = unreal.EditorAssetLibrary
967
-
968
- if not les or not ues:
969
- return {'success': False, 'error': 'Required subsystems not available'}
970
-
971
- # Store current actors if we need to copy them
972
- actors_to_copy = []
973
- if ${params?.copyActors ? 'True' : 'False'}:
974
- current_world = ues.get_editor_world()
975
- if current_world:
976
- all_actors = actor_sub.get_all_level_actors()
977
- # Filter out unnecessary actors - only copy static meshes and important gameplay actors
978
- for actor in all_actors:
979
- if actor:
980
- class_name = actor.get_class().get_name()
981
- # Only copy specific actor types
982
- if class_name in ['StaticMeshActor', 'SkeletalMeshActor', 'Blueprint', 'Actor']:
983
- try:
984
- actor_data = {
985
- 'class': actor.get_class(),
986
- 'location': actor.get_actor_location(),
987
- 'rotation': actor.get_actor_rotation(),
988
- 'scale': actor.get_actor_scale3d(),
989
- 'label': actor.get_actor_label()
990
- }
991
- # Check if actor has a static mesh component
992
- mesh_comp = actor.get_component_by_class(unreal.StaticMeshComponent)
993
- if mesh_comp:
994
- mesh = mesh_comp.get_editor_property('static_mesh')
995
- if mesh:
996
- actor_data['mesh'] = mesh
997
- actors_to_copy.append(actor_data)
998
- except:
999
- pass
1000
- print(f'Stored {len(actors_to_copy)} actors to copy')
1001
-
1002
- # Create new level with proper template or blank
1003
- level_name_str = "${levelName}"
1004
- level_path = f'/Game/Maps/{level_name_str}'
1005
-
1006
- # Try different approaches to create a level with lighting enabled
1007
- level_created = False
1008
-
1009
- # Method 1: Try using the Default template (not Blank)
1010
- try:
1011
- # The Default template should have lighting enabled
1012
- template_path = '/Engine/Maps/Templates/Template_Default'
1013
- if editor_asset.does_asset_exist(template_path):
1014
- les.new_level_from_template(level_path, template_path)
1015
- print(f'Created level from Default template: {level_path}')
1016
- level_created = True
1017
- except:
1018
- pass
1019
-
1020
- # Method 2: Try TimeOfDay template
1021
- if not level_created:
1022
- try:
1023
- template_path = '/Engine/Maps/Templates/TimeOfDay'
1024
- if editor_asset.does_asset_exist(template_path):
1025
- les.new_level_from_template(level_path, template_path)
1026
- print(f'Created level from TimeOfDay template: {level_path}')
1027
- level_created = True
1028
- except:
1029
- pass
1030
-
1031
- # Method 3: Create blank and manually configure
1032
- if not level_created:
1033
- les.new_level(level_path, False)
1034
- print(f'Created new blank level: {level_path}')
1035
- level_created = True
1036
-
1037
- # CRITICAL: Force disable ForceNoPrecomputedLighting using all possible methods
1038
- new_world = ues.get_editor_world()
1039
- if new_world:
1040
- new_ws = new_world.get_world_settings()
1041
- if new_ws:
1042
- # Method 1: Direct property modification
1043
- for prop in ['force_no_precomputed_lighting', 'bForceNoPrecomputedLighting',
1044
- 'ForceNoPrecomputedLighting', 'bforce_no_precomputed_lighting']:
1045
- try:
1046
- new_ws.set_editor_property(prop, False)
1047
- except:
1048
- pass
1049
-
1050
- # Method 2: Modify via reflection
1051
- try:
1052
- # Access the property through the class default object
1053
- ws_class = new_ws.get_class()
1054
- ws_cdo = unreal.get_default_object(ws_class)
1055
- if ws_cdo:
1056
- ws_cdo.set_editor_property('force_no_precomputed_lighting', False)
1057
- ws_cdo.set_editor_property('bForceNoPrecomputedLighting', False)
1058
- except:
1059
- pass
1060
-
1061
- # Method 3: Override with Lightmass settings
1062
- try:
1063
- # Create proper Lightmass settings
1064
- lightmass_settings = unreal.LightmassWorldInfoSettings()
1065
- lightmass_settings.static_lighting_level_scale = 1.0
1066
- lightmass_settings.num_indirect_lighting_bounces = 3
1067
- lightmass_settings.use_ambient_occlusion = True
1068
- lightmass_settings.generate_ambient_occlusion_material_mask = False
1069
-
1070
- new_ws.set_editor_property('lightmass_settings', lightmass_settings)
1071
- except:
1072
- pass
1073
-
1074
- # Method 4: Force save and reload to apply changes
1075
- try:
1076
- # Mark the world settings as dirty
1077
- new_ws.modify()
1078
- # Save immediately
1079
- les.save_current_level()
1080
- # Force update
1081
- new_world.force_update_level_bounds()
1082
- except:
1083
- pass
1084
-
1085
- # Verify the setting
1086
- try:
1087
- val = new_ws.get_editor_property('force_no_precomputed_lighting')
1088
- print(f'New level force_no_precomputed_lighting: {val}')
1089
- if val:
1090
- print('WARNING: ForceNoPrecomputedLighting is persistent - project setting override detected')
1091
- print('WORKAROUND: Will use dynamic lighting only')
1092
- except:
1093
- pass
1094
-
1095
- # Copy actors if requested
1096
- if actors_to_copy and actor_sub:
1097
- print('Copying actors to new level...')
1098
- copied = 0
1099
- for actor_data in actors_to_copy:
1100
- try:
1101
- # Spawn a static mesh actor if we have mesh data
1102
- if 'mesh' in actor_data:
1103
- # Create a proper static mesh actor
1104
- spawned = actor_sub.spawn_actor_from_class(
1105
- unreal.StaticMeshActor,
1106
- actor_data['location'],
1107
- actor_data['rotation']
1108
- )
1109
- if spawned:
1110
- spawned.set_actor_scale3d(actor_data['scale'])
1111
- spawned.set_actor_label(actor_data['label'])
1112
- # Set the static mesh
1113
- mesh_comp = spawned.get_component_by_class(unreal.StaticMeshComponent)
1114
- if mesh_comp:
1115
- mesh_comp.set_static_mesh(actor_data['mesh'])
1116
- copied += 1
1117
- else:
1118
- # Spawn regular actor
1119
- spawned = actor_sub.spawn_actor_from_class(
1120
- actor_data['class'],
1121
- actor_data['location'],
1122
- actor_data['rotation']
1123
- )
1124
- if spawned:
1125
- spawned.set_actor_scale3d(actor_data['scale'])
1126
- spawned.set_actor_label(actor_data['label'])
1127
- copied += 1
1128
- except Exception as e:
1129
- pass # Silently skip failed copies
1130
- print(f'Successfully copied {copied} actors')
1131
-
1132
- # Add essential lighting actors if not using template
1133
- if not use_template:
1134
- # Add a directional light for sun
1135
- light = actor_sub.spawn_actor_from_class(
1136
- unreal.DirectionalLight,
1137
- unreal.Vector(0, 0, 500),
1138
- unreal.Rotator(-45, 45, 0)
1139
- )
1140
- if light:
1141
- light.set_actor_label('Sun_Light')
1142
- light_comp = light.get_component_by_class(unreal.DirectionalLightComponent)
1143
- if light_comp:
1144
- light_comp.set_intensity(3.14159) # Pi lux for realistic sun
1145
- light_comp.set_light_color(unreal.LinearColor(1, 0.95, 0.8, 1))
1146
- print('Added directional light')
1147
-
1148
- # Add sky light for ambient
1149
- sky = actor_sub.spawn_actor_from_class(
1150
- unreal.SkyLight,
1151
- unreal.Vector(0, 0, 300),
1152
- unreal.Rotator(0, 0, 0)
1153
- )
1154
- if sky:
1155
- sky.set_actor_label('Sky_Light')
1156
- sky_comp = sky.get_component_by_class(unreal.SkyLightComponent)
1157
- if sky_comp:
1158
- sky_comp.set_intensity(1.0)
1159
- print('Added sky light')
1160
-
1161
- # Add sky atmosphere for realistic sky
1162
- atmosphere = actor_sub.spawn_actor_from_class(
1163
- unreal.SkyAtmosphere,
1164
- unreal.Vector(0, 0, 0),
1165
- unreal.Rotator(0, 0, 0)
1166
- )
1167
- if atmosphere:
1168
- atmosphere.set_actor_label('Sky_Atmosphere')
1169
- print('Added sky atmosphere')
1170
-
1171
- # Save the new level
1172
- les.save_current_level()
1173
- print('New level saved')
1174
-
1175
- return {
1176
- 'success': True,
1177
- 'message': f'Created new level "{level_name_str}" with lighting enabled',
1178
- 'path': level_path
1179
- }
1180
-
1181
- except Exception as e:
1182
- return {'success': False, 'error': str(e)}
1183
-
1184
- result = create_lighting_enabled_level()
1185
- print('RESULT:' + json.dumps(result))
1186
- `.trim();
1187
- const resp = await this.bridge.executePython(py);
1188
- const out = typeof resp === 'string' ? resp : JSON.stringify(resp);
1189
- const m = out.match(/RESULT:({.*})/);
1190
- if (m) {
1191
- try {
1192
- const parsed = JSON.parse(m[1]);
1193
- return parsed;
627
+ if (!this.automationBridge) {
628
+ throw new Error('Automation Bridge not available. Level creation requires plugin support.');
629
+ }
630
+ try {
631
+ const response = await this.automationBridge.sendAutomationRequest('create_lighting_enabled_level', {
632
+ levelName,
633
+ copyActors: params?.copyActors === true,
634
+ useTemplate: params?.useTemplate === true,
635
+ path: params?.levelName ? `/Game/Maps/${params.levelName}` : undefined
636
+ }, {
637
+ timeoutMs: 120000
638
+ });
639
+ if (response.success === false) {
640
+ return {
641
+ success: false,
642
+ error: response.error || response.message || 'Failed to create level'
643
+ };
1194
644
  }
1195
- catch { }
645
+ return {
646
+ success: true,
647
+ message: response.message || `Created new level "${levelName}" with lighting enabled`,
648
+ ...(response.result || {})
649
+ };
650
+ }
651
+ catch (error) {
652
+ return {
653
+ success: false,
654
+ error: `Failed to create lighting-enabled level: ${error}`
655
+ };
1196
656
  }
1197
- return { success: true, message: 'New level creation attempted' };
1198
657
  }
1199
- // Create lightmass importance volume via Python
1200
658
  async createLightmassVolume(params) {
1201
659
  const name = this.normalizeName(params.name);
1202
- const escapedName = escapePythonString(name);
1203
- const [lx, ly, lz] = params.location;
1204
- const [sx, sy, sz] = params.size;
1205
- const py = `
1206
- import unreal
1207
- editor_actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
1208
- loc = unreal.Vector(${lx}, ${ly}, ${lz})
1209
- rot = unreal.Rotator(0,0,0)
1210
- actor = editor_actor_subsystem.spawn_actor_from_class(unreal.LightmassImportanceVolume, loc, rot)
1211
- if actor:
1212
- try: actor.set_actor_label("${escapedName}")
1213
- except Exception: pass
1214
- # Best-effort: set actor scale to approximate size
1215
- try:
1216
- actor.set_actor_scale3d(unreal.Vector(max(${sx}/100.0, 0.1), max(${sy}/100.0, 0.1), max(${sz}/100.0, 0.1)))
1217
- except Exception: pass
1218
- print("RESULT:{'success': True}")
1219
- else:
1220
- print("RESULT:{'success': False, 'error': 'Failed to spawn LightmassImportanceVolume'}")
1221
- `.trim();
1222
- const resp = await this.bridge.executePython(py);
1223
- const out = typeof resp === 'string' ? resp : JSON.stringify(resp);
1224
- const m = out.match(/RESULT:({.*})/);
1225
- if (m) {
1226
- try {
1227
- const parsed = JSON.parse(m[1].replace(/'/g, '"'));
1228
- return parsed.success ? { success: true, message: `LightmassImportanceVolume '${name}' created` } : { success: false, error: parsed.error };
660
+ if (!this.automationBridge) {
661
+ throw new Error('Automation Bridge not available. Lightmass volume creation requires plugin support.');
662
+ }
663
+ try {
664
+ const response = await this.automationBridge.sendAutomationRequest('create_lightmass_volume', {
665
+ name,
666
+ location: { x: params.location[0], y: params.location[1], z: params.location[2] },
667
+ size: { x: params.size[0], y: params.size[1], z: params.size[2] }
668
+ }, {
669
+ timeoutMs: 60000
670
+ });
671
+ if (response.success === false) {
672
+ return {
673
+ success: false,
674
+ error: response.error || response.message || 'Failed to create lightmass volume'
675
+ };
1229
676
  }
1230
- catch { }
677
+ return {
678
+ success: true,
679
+ message: `LightmassImportanceVolume '${name}' created`,
680
+ ...(response.result || {})
681
+ };
682
+ }
683
+ catch (error) {
684
+ return {
685
+ success: false,
686
+ error: `Failed to create lightmass volume: ${error}`
687
+ };
1231
688
  }
1232
- return { success: true, message: 'LightmassImportanceVolume creation attempted' };
1233
689
  }
1234
- // Set exposure
1235
690
  async setExposure(params) {
691
+ if (this.automationBridge) {
692
+ try {
693
+ const response = await this.automationBridge.sendAutomationRequest('set_exposure', {
694
+ method: params.method,
695
+ compensationValue: params.compensationValue,
696
+ minBrightness: params.minBrightness,
697
+ maxBrightness: params.maxBrightness
698
+ });
699
+ if (response.success)
700
+ return { success: true, message: 'Exposure settings updated via bridge', ...(response.result || {}) };
701
+ }
702
+ catch (_e) {
703
+ }
704
+ }
1236
705
  const commands = [];
1237
706
  commands.push(`r.EyeAdaptation.ExposureMethod ${params.method === 'Manual' ? 0 : 1}`);
1238
707
  if (params.compensationValue !== undefined) {
@@ -1247,10 +716,23 @@ else:
1247
716
  for (const cmd of commands) {
1248
717
  await this.bridge.executeConsoleCommand(cmd);
1249
718
  }
1250
- return { success: true, message: 'Exposure settings updated' };
719
+ return { success: true, message: 'Exposure settings updated (console)' };
1251
720
  }
1252
- // Set ambient occlusion
1253
721
  async setAmbientOcclusion(params) {
722
+ if (this.automationBridge) {
723
+ try {
724
+ const response = await this.automationBridge.sendAutomationRequest('set_ambient_occlusion', {
725
+ enabled: params.enabled,
726
+ intensity: params.intensity,
727
+ radius: params.radius,
728
+ quality: params.quality
729
+ });
730
+ if (response.success)
731
+ return { success: true, message: 'Ambient occlusion configured via bridge', ...(response.result || {}) };
732
+ }
733
+ catch (_e) {
734
+ }
735
+ }
1254
736
  const commands = [];
1255
737
  commands.push(`r.AmbientOcclusion.Enabled ${params.enabled ? 1 : 0}`);
1256
738
  if (params.intensity !== undefined) {
@@ -1266,27 +748,43 @@ else:
1266
748
  for (const cmd of commands) {
1267
749
  await this.bridge.executeConsoleCommand(cmd);
1268
750
  }
1269
- return { success: true, message: 'Ambient occlusion configured' };
751
+ return { success: true, message: 'Ambient occlusion configured (console)' };
1270
752
  }
1271
- // Setup volumetric fog (prefer Python to adjust fog actor/component)
1272
753
  async setupVolumetricFog(params) {
1273
- // Enable/disable global volumetric fog via CVar
1274
754
  await this.bridge.executeConsoleCommand(`r.VolumetricFog ${params.enabled ? 1 : 0}`);
1275
- const py = `\nimport unreal\ntry:\n actor_sub = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)\n actors = actor_sub.get_all_level_actors() if actor_sub else []\n fog = None\n for a in actors:\n try:\n if a.get_class().get_name() == 'ExponentialHeightFog':\n fog = a\n break\n except Exception: pass\n if fog:\n comp = fog.get_component_by_class(unreal.ExponentialHeightFogComponent)\n if comp:\n ${params.density !== undefined ? `\n try: comp.set_fog_density(${params.density})\n except Exception: comp.set_editor_property('fog_density', ${params.density})\n ` : ''}
1276
- ${params.scatteringIntensity !== undefined ? `\n try: comp.set_fog_max_opacity(${Math.min(Math.max(params.scatteringIntensity, 0), 1)})\n except Exception: pass\n ` : ''}
1277
- ${params.fogHeight !== undefined ? `\n try:\n L = fog.get_actor_location()\n fog.set_actor_location(unreal.Vector(L.x, L.y, ${params.fogHeight}))\n except Exception: pass\n ` : ''}
1278
- print("RESULT:{'success': True}")\nexcept Exception as e:\n print("RESULT:{'success': False, 'error': '%s'}" % str(e))\n`.trim();
1279
- const resp = await this.bridge.executePython(py);
1280
- const out = typeof resp === 'string' ? resp : JSON.stringify(resp);
1281
- const m = out.match(/RESULT:({.*})/);
1282
- if (m) {
1283
- try {
1284
- const parsed = JSON.parse(m[1].replace(/'/g, '"'));
1285
- return parsed.success ? { success: true, message: 'Volumetric fog configured' } : { success: false, error: parsed.error };
755
+ if (!this.automationBridge) {
756
+ return {
757
+ success: true,
758
+ message: 'Volumetric fog console setting applied (plugin required for fog actor adjustment)'
759
+ };
760
+ }
761
+ try {
762
+ const response = await this.automationBridge.sendAutomationRequest('setup_volumetric_fog', {
763
+ enabled: params.enabled,
764
+ density: params.density,
765
+ scatteringIntensity: params.scatteringIntensity,
766
+ fogHeight: params.fogHeight
767
+ }, {
768
+ timeoutMs: 60000
769
+ });
770
+ if (response.success === false) {
771
+ return {
772
+ success: false,
773
+ error: response.error || response.message || 'Failed to configure volumetric fog'
774
+ };
1286
775
  }
1287
- catch { }
776
+ return {
777
+ success: true,
778
+ message: 'Volumetric fog configured',
779
+ ...(response.result || {})
780
+ };
781
+ }
782
+ catch (error) {
783
+ return {
784
+ success: false,
785
+ error: `Failed to setup volumetric fog: ${error}`
786
+ };
1288
787
  }
1289
- return { success: true, message: 'Volumetric fog configured' };
1290
788
  }
1291
789
  }
1292
790
  //# sourceMappingURL=lighting.js.map