unreal-engine-mcp-server 0.4.6 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +26 -0
- package/.env.production +38 -7
- package/.eslintrc.json +0 -54
- package/.eslintrc.override.json +8 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +94 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +56 -0
- package/.github/copilot-instructions.md +478 -45
- package/.github/dependabot.yml +19 -0
- package/.github/labeler.yml +24 -0
- package/.github/labels.yml +70 -0
- package/.github/pull_request_template.md +42 -0
- package/.github/release-drafter.yml +148 -0
- package/.github/workflows/auto-merge.yml +38 -0
- package/.github/workflows/ci.yml +38 -0
- package/.github/workflows/dependency-review.yml +17 -0
- package/.github/workflows/gemini-issue-triage.yml +172 -0
- package/.github/workflows/greetings.yml +23 -0
- package/.github/workflows/labeler.yml +16 -0
- package/.github/workflows/links.yml +80 -0
- package/.github/workflows/pr-size-labeler.yml +137 -0
- package/.github/workflows/publish-mcp.yml +12 -7
- package/.github/workflows/release-drafter.yml +23 -0
- package/.github/workflows/release.yml +112 -0
- package/.github/workflows/semantic-pull-request.yml +35 -0
- package/.github/workflows/smoke-test.yml +36 -0
- package/.github/workflows/stale.yml +28 -0
- package/CHANGELOG.md +269 -22
- package/CONTRIBUTING.md +140 -0
- package/README.md +166 -72
- package/claude_desktop_config_example.json +7 -6
- package/dist/automation/bridge.d.ts +50 -0
- package/dist/automation/bridge.js +452 -0
- package/dist/automation/connection-manager.d.ts +23 -0
- package/dist/automation/connection-manager.js +107 -0
- package/dist/automation/handshake.d.ts +11 -0
- package/dist/automation/handshake.js +89 -0
- package/dist/automation/index.d.ts +3 -0
- package/dist/automation/index.js +3 -0
- package/dist/automation/message-handler.d.ts +12 -0
- package/dist/automation/message-handler.js +149 -0
- package/dist/automation/request-tracker.d.ts +25 -0
- package/dist/automation/request-tracker.js +98 -0
- package/dist/automation/types.d.ts +130 -0
- package/dist/automation/types.js +2 -0
- package/dist/cli.js +32 -5
- package/dist/config.d.ts +27 -0
- package/dist/config.js +60 -0
- package/dist/constants.d.ts +12 -0
- package/dist/constants.js +12 -0
- package/dist/graphql/resolvers.d.ts +268 -0
- package/dist/graphql/resolvers.js +743 -0
- package/dist/graphql/schema.d.ts +5 -0
- package/dist/graphql/schema.js +437 -0
- package/dist/graphql/server.d.ts +26 -0
- package/dist/graphql/server.js +115 -0
- package/dist/graphql/types.d.ts +7 -0
- package/dist/graphql/types.js +2 -0
- package/dist/handlers/resource-handlers.d.ts +20 -0
- package/dist/handlers/resource-handlers.js +180 -0
- package/dist/index.d.ts +31 -18
- package/dist/index.js +119 -604
- package/dist/prompts/index.js +4 -4
- package/dist/resources/actors.d.ts +17 -12
- package/dist/resources/actors.js +56 -76
- package/dist/resources/assets.d.ts +6 -14
- package/dist/resources/assets.js +115 -147
- package/dist/resources/levels.d.ts +13 -13
- package/dist/resources/levels.js +25 -34
- package/dist/server/resource-registry.d.ts +20 -0
- package/dist/server/resource-registry.js +37 -0
- package/dist/server/tool-registry.d.ts +23 -0
- package/dist/server/tool-registry.js +322 -0
- package/dist/server-setup.d.ts +21 -0
- package/dist/server-setup.js +111 -0
- package/dist/services/health-monitor.d.ts +34 -0
- package/dist/services/health-monitor.js +105 -0
- package/dist/services/metrics-server.d.ts +11 -0
- package/dist/services/metrics-server.js +105 -0
- package/dist/tools/actors.d.ts +147 -9
- package/dist/tools/actors.js +350 -311
- package/dist/tools/animation.d.ts +135 -4
- package/dist/tools/animation.js +510 -411
- package/dist/tools/assets.d.ts +117 -19
- package/dist/tools/assets.js +259 -284
- package/dist/tools/audio.d.ts +102 -42
- package/dist/tools/audio.js +272 -685
- package/dist/tools/base-tool.d.ts +17 -0
- package/dist/tools/base-tool.js +46 -0
- package/dist/tools/behavior-tree.d.ts +94 -0
- package/dist/tools/behavior-tree.js +39 -0
- package/dist/tools/blueprint/helpers.d.ts +29 -0
- package/dist/tools/blueprint/helpers.js +182 -0
- package/dist/tools/blueprint.d.ts +228 -118
- package/dist/tools/blueprint.js +685 -832
- package/dist/tools/consolidated-tool-definitions.d.ts +5475 -1627
- package/dist/tools/consolidated-tool-definitions.js +829 -482
- package/dist/tools/consolidated-tool-handlers.d.ts +2 -1
- package/dist/tools/consolidated-tool-handlers.js +211 -1009
- package/dist/tools/debug.d.ts +143 -85
- package/dist/tools/debug.js +234 -180
- package/dist/tools/dynamic-handler-registry.d.ts +11 -0
- package/dist/tools/dynamic-handler-registry.js +101 -0
- package/dist/tools/editor.d.ts +139 -18
- package/dist/tools/editor.js +239 -244
- package/dist/tools/engine.d.ts +10 -4
- package/dist/tools/engine.js +13 -5
- package/dist/tools/environment.d.ts +36 -0
- package/dist/tools/environment.js +267 -0
- package/dist/tools/foliage.d.ts +105 -14
- package/dist/tools/foliage.js +219 -331
- package/dist/tools/handlers/actor-handlers.d.ts +3 -0
- package/dist/tools/handlers/actor-handlers.js +232 -0
- package/dist/tools/handlers/animation-handlers.d.ts +3 -0
- package/dist/tools/handlers/animation-handlers.js +185 -0
- package/dist/tools/handlers/argument-helper.d.ts +16 -0
- package/dist/tools/handlers/argument-helper.js +80 -0
- package/dist/tools/handlers/asset-handlers.d.ts +3 -0
- package/dist/tools/handlers/asset-handlers.js +496 -0
- package/dist/tools/handlers/audio-handlers.d.ts +3 -0
- package/dist/tools/handlers/audio-handlers.js +166 -0
- package/dist/tools/handlers/blueprint-handlers.d.ts +4 -0
- package/dist/tools/handlers/blueprint-handlers.js +358 -0
- package/dist/tools/handlers/common-handlers.d.ts +14 -0
- package/dist/tools/handlers/common-handlers.js +56 -0
- package/dist/tools/handlers/editor-handlers.d.ts +3 -0
- package/dist/tools/handlers/editor-handlers.js +119 -0
- package/dist/tools/handlers/effect-handlers.d.ts +3 -0
- package/dist/tools/handlers/effect-handlers.js +171 -0
- package/dist/tools/handlers/environment-handlers.d.ts +3 -0
- package/dist/tools/handlers/environment-handlers.js +170 -0
- package/dist/tools/handlers/graph-handlers.d.ts +3 -0
- package/dist/tools/handlers/graph-handlers.js +90 -0
- package/dist/tools/handlers/input-handlers.d.ts +3 -0
- package/dist/tools/handlers/input-handlers.js +21 -0
- package/dist/tools/handlers/inspect-handlers.d.ts +3 -0
- package/dist/tools/handlers/inspect-handlers.js +383 -0
- package/dist/tools/handlers/level-handlers.d.ts +3 -0
- package/dist/tools/handlers/level-handlers.js +237 -0
- package/dist/tools/handlers/lighting-handlers.d.ts +3 -0
- package/dist/tools/handlers/lighting-handlers.js +144 -0
- package/dist/tools/handlers/performance-handlers.d.ts +3 -0
- package/dist/tools/handlers/performance-handlers.js +130 -0
- package/dist/tools/handlers/pipeline-handlers.d.ts +3 -0
- package/dist/tools/handlers/pipeline-handlers.js +110 -0
- package/dist/tools/handlers/sequence-handlers.d.ts +3 -0
- package/dist/tools/handlers/sequence-handlers.js +376 -0
- package/dist/tools/handlers/system-handlers.d.ts +4 -0
- package/dist/tools/handlers/system-handlers.js +506 -0
- package/dist/tools/input.d.ts +19 -0
- package/dist/tools/input.js +89 -0
- package/dist/tools/introspection.d.ts +103 -40
- package/dist/tools/introspection.js +425 -568
- package/dist/tools/landscape.d.ts +97 -36
- package/dist/tools/landscape.js +280 -409
- package/dist/tools/level.d.ts +130 -10
- package/dist/tools/level.js +639 -675
- package/dist/tools/lighting.d.ts +77 -38
- package/dist/tools/lighting.js +441 -943
- package/dist/tools/logs.d.ts +45 -0
- package/dist/tools/logs.js +210 -0
- package/dist/tools/materials.d.ts +91 -24
- package/dist/tools/materials.js +190 -118
- package/dist/tools/niagara.d.ts +149 -39
- package/dist/tools/niagara.js +232 -182
- package/dist/tools/performance.d.ts +27 -12
- package/dist/tools/performance.js +204 -122
- package/dist/tools/physics.d.ts +32 -77
- package/dist/tools/physics.js +171 -582
- package/dist/tools/property-dictionary.d.ts +13 -0
- package/dist/tools/property-dictionary.js +82 -0
- package/dist/tools/sequence.d.ts +73 -48
- package/dist/tools/sequence.js +196 -748
- package/dist/tools/tool-definition-utils.d.ts +59 -0
- package/dist/tools/tool-definition-utils.js +35 -0
- package/dist/tools/ui.d.ts +66 -34
- package/dist/tools/ui.js +134 -214
- package/dist/types/env.d.ts +0 -3
- package/dist/types/env.js +0 -7
- package/dist/types/tool-interfaces.d.ts +898 -0
- package/dist/types/tool-interfaces.js +2 -0
- package/dist/types/tool-types.d.ts +195 -11
- package/dist/types/tool-types.js +0 -4
- package/dist/unreal-bridge.d.ts +24 -131
- package/dist/unreal-bridge.js +364 -1506
- package/dist/utils/command-validator.d.ts +9 -0
- package/dist/utils/command-validator.js +67 -0
- package/dist/utils/elicitation.d.ts +1 -1
- package/dist/utils/elicitation.js +12 -15
- package/dist/utils/error-handler.d.ts +2 -51
- package/dist/utils/error-handler.js +11 -87
- package/dist/utils/ini-reader.d.ts +3 -0
- package/dist/utils/ini-reader.js +69 -0
- package/dist/utils/logger.js +9 -6
- package/dist/utils/normalize.d.ts +3 -0
- package/dist/utils/normalize.js +56 -0
- package/dist/utils/response-factory.d.ts +7 -0
- package/dist/utils/response-factory.js +33 -0
- package/dist/utils/response-validator.d.ts +3 -24
- package/dist/utils/response-validator.js +130 -81
- package/dist/utils/result-helpers.d.ts +4 -5
- package/dist/utils/result-helpers.js +15 -16
- package/dist/utils/safe-json.js +5 -11
- package/dist/utils/unreal-command-queue.d.ts +24 -0
- package/dist/utils/unreal-command-queue.js +120 -0
- package/dist/utils/validation.d.ts +0 -40
- package/dist/utils/validation.js +1 -78
- package/dist/wasm/index.d.ts +70 -0
- package/dist/wasm/index.js +535 -0
- package/docs/GraphQL-API.md +888 -0
- package/docs/Migration-Guide-v0.5.0.md +692 -0
- package/docs/Roadmap.md +53 -0
- package/docs/WebAssembly-Integration.md +628 -0
- package/docs/editor-plugin-extension.md +370 -0
- package/docs/handler-mapping.md +242 -0
- package/docs/native-automation-progress.md +128 -0
- package/docs/testing-guide.md +423 -0
- package/mcp-config-example.json +6 -6
- package/package.json +60 -27
- package/plugins/McpAutomationBridge/Config/FilterPlugin.ini +8 -0
- package/plugins/McpAutomationBridge/McpAutomationBridge.uplugin +64 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/McpAutomationBridge.Build.cs +189 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.cpp +22 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.h +30 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeHelpers.h +1983 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeModule.cpp +72 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSettings.cpp +46 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +581 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +2394 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetQueryHandlers.cpp +300 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetWorkflowHandlers.cpp +2807 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AudioHandlers.cpp +1087 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BehaviorTreeHandlers.cpp +488 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.cpp +643 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.h +31 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +1184 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +5652 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers_List.cpp +152 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ControlHandlers.cpp +2614 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_DebugHandlers.cpp +42 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EditorFunctionHandlers.cpp +1237 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +1701 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +2145 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_FoliageHandlers.cpp +954 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InputHandlers.cpp +209 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InsightsHandlers.cpp +41 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LandscapeHandlers.cpp +1164 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LevelHandlers.cpp +762 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +634 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LogHandlers.cpp +136 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_MaterialGraphHandlers.cpp +494 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraGraphHandlers.cpp +278 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraHandlers.cpp +625 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PerformanceHandlers.cpp +401 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PipelineHandlers.cpp +67 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +735 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PropertyHandlers.cpp +2634 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_RenderHandlers.cpp +189 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.cpp +917 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.h +39 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +2670 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequencerHandlers.cpp +519 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_TestHandlers.cpp +38 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_UiHandlers.cpp +668 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_WorldPartitionHandlers.cpp +346 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +1330 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.h +149 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +783 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSettings.h +115 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSubsystem.h +796 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpConnectionManager.h +117 -0
- package/scripts/check-unreal-connection.mjs +19 -0
- package/scripts/clean-tmp.js +23 -0
- package/scripts/patch-wasm.js +26 -0
- package/scripts/run-all-tests.mjs +131 -0
- package/scripts/smoke-test.ts +94 -0
- package/scripts/sync-mcp-plugin.js +143 -0
- package/scripts/test-no-plugin-alternates.mjs +113 -0
- package/scripts/validate-server.js +46 -0
- package/scripts/verify-automation-bridge.js +200 -0
- package/server.json +57 -21
- package/src/automation/bridge.ts +558 -0
- package/src/automation/connection-manager.ts +130 -0
- package/src/automation/handshake.ts +99 -0
- package/src/automation/index.ts +2 -0
- package/src/automation/message-handler.ts +167 -0
- package/src/automation/request-tracker.ts +123 -0
- package/src/automation/types.ts +107 -0
- package/src/cli.ts +33 -6
- package/src/config.ts +73 -0
- package/src/constants.ts +12 -0
- package/src/graphql/resolvers.ts +1010 -0
- package/src/graphql/schema.ts +452 -0
- package/src/graphql/server.ts +154 -0
- package/src/graphql/types.ts +7 -0
- package/src/handlers/resource-handlers.ts +186 -0
- package/src/index.ts +152 -649
- package/src/prompts/index.ts +4 -4
- package/src/resources/actors.ts +58 -76
- package/src/resources/assets.ts +147 -134
- package/src/resources/levels.ts +28 -33
- package/src/server/resource-registry.ts +47 -0
- package/src/server/tool-registry.ts +354 -0
- package/src/server-setup.ts +148 -0
- package/src/services/health-monitor.ts +132 -0
- package/src/services/metrics-server.ts +142 -0
- package/src/tools/actors.ts +417 -322
- package/src/tools/animation.ts +671 -461
- package/src/tools/assets.ts +353 -289
- package/src/tools/audio.ts +323 -766
- package/src/tools/base-tool.ts +52 -0
- package/src/tools/behavior-tree.ts +45 -0
- package/src/tools/blueprint/helpers.ts +189 -0
- package/src/tools/blueprint.ts +787 -965
- package/src/tools/consolidated-tool-definitions.ts +993 -500
- package/src/tools/consolidated-tool-handlers.ts +272 -1122
- package/src/tools/debug.ts +292 -187
- package/src/tools/dynamic-handler-registry.ts +151 -0
- package/src/tools/editor.ts +309 -246
- package/src/tools/engine.ts +14 -3
- package/src/tools/environment.ts +287 -0
- package/src/tools/foliage.ts +314 -379
- package/src/tools/handlers/actor-handlers.ts +271 -0
- package/src/tools/handlers/animation-handlers.ts +237 -0
- package/src/tools/handlers/argument-helper.ts +142 -0
- package/src/tools/handlers/asset-handlers.ts +532 -0
- package/src/tools/handlers/audio-handlers.ts +194 -0
- package/src/tools/handlers/blueprint-handlers.ts +380 -0
- package/src/tools/handlers/common-handlers.ts +87 -0
- package/src/tools/handlers/editor-handlers.ts +123 -0
- package/src/tools/handlers/effect-handlers.ts +220 -0
- package/src/tools/handlers/environment-handlers.ts +183 -0
- package/src/tools/handlers/graph-handlers.ts +116 -0
- package/src/tools/handlers/input-handlers.ts +28 -0
- package/src/tools/handlers/inspect-handlers.ts +450 -0
- package/src/tools/handlers/level-handlers.ts +252 -0
- package/src/tools/handlers/lighting-handlers.ts +147 -0
- package/src/tools/handlers/performance-handlers.ts +132 -0
- package/src/tools/handlers/pipeline-handlers.ts +127 -0
- package/src/tools/handlers/sequence-handlers.ts +415 -0
- package/src/tools/handlers/system-handlers.ts +564 -0
- package/src/tools/input.ts +101 -0
- package/src/tools/introspection.ts +493 -584
- package/src/tools/landscape.ts +394 -489
- package/src/tools/level.ts +752 -694
- package/src/tools/lighting.ts +583 -984
- package/src/tools/logs.ts +219 -0
- package/src/tools/materials.ts +231 -121
- package/src/tools/niagara.ts +293 -168
- package/src/tools/performance.ts +320 -168
- package/src/tools/physics.ts +268 -613
- package/src/tools/property-dictionary.ts +98 -0
- package/src/tools/sequence.ts +255 -815
- package/src/tools/tool-definition-utils.ts +35 -0
- package/src/tools/ui.ts +207 -283
- package/src/types/env.ts +0 -10
- package/src/types/tool-interfaces.ts +250 -0
- package/src/types/tool-types.ts +250 -13
- package/src/unreal-bridge.ts +460 -1550
- package/src/utils/command-validator.ts +75 -0
- package/src/utils/elicitation.ts +10 -7
- package/src/utils/error-handler.ts +14 -90
- package/src/utils/ini-reader.ts +86 -0
- package/src/utils/logger.ts +8 -3
- package/src/utils/normalize.ts +60 -0
- package/src/utils/response-factory.ts +39 -0
- package/src/utils/response-validator.ts +176 -56
- package/src/utils/result-helpers.ts +21 -19
- package/src/utils/safe-json.ts +14 -11
- package/src/utils/unreal-command-queue.ts +152 -0
- package/src/utils/validation.ts +4 -1
- package/src/wasm/index.ts +838 -0
- package/test-server.mjs +100 -0
- package/tests/run-unreal-tool-tests.mjs +242 -14
- package/tests/test-animation.mjs +44 -0
- package/tests/test-asset-advanced.mjs +82 -0
- package/tests/test-asset-errors.mjs +35 -0
- package/tests/test-audio.mjs +219 -0
- package/tests/test-automation-timeouts.mjs +98 -0
- package/tests/test-behavior-tree.mjs +261 -0
- package/tests/test-blueprint-events.mjs +35 -0
- package/tests/test-blueprint-graph.mjs +79 -0
- package/tests/test-blueprint.mjs +577 -0
- package/tests/test-client-mode.mjs +86 -0
- package/tests/test-console-command.mjs +56 -0
- package/tests/test-control-actor.mjs +425 -0
- package/tests/test-control-editor.mjs +80 -0
- package/tests/test-extra-tools.mjs +38 -0
- package/tests/test-graphql.mjs +322 -0
- package/tests/test-inspect.mjs +72 -0
- package/tests/test-landscape.mjs +60 -0
- package/tests/test-manage-asset.mjs +438 -0
- package/tests/test-manage-level.mjs +70 -0
- package/tests/test-materials.mjs +356 -0
- package/tests/test-niagara.mjs +185 -0
- package/tests/test-no-inline-python.mjs +122 -0
- package/tests/test-plugin-handshake.mjs +82 -0
- package/tests/test-render.mjs +33 -0
- package/tests/test-runner.mjs +933 -0
- package/tests/test-search-assets.mjs +66 -0
- package/tests/test-sequence.mjs +68 -0
- package/tests/test-system.mjs +57 -0
- package/tests/test-wasm.mjs +193 -0
- package/tests/test-world-partition.mjs +215 -0
- package/tsconfig.json +3 -3
- package/wasm/Cargo.lock +363 -0
- package/wasm/Cargo.toml +42 -0
- package/wasm/LICENSE +21 -0
- package/wasm/README.md +253 -0
- package/wasm/src/dependency_resolver.rs +377 -0
- package/wasm/src/lib.rs +153 -0
- package/wasm/src/property_parser.rs +271 -0
- package/wasm/src/transform_math.rs +396 -0
- package/wasm/tests/integration.rs +109 -0
- package/.github/workflows/smithery-build.yml +0 -29
- package/dist/tools/build_environment_advanced.d.ts +0 -65
- package/dist/tools/build_environment_advanced.js +0 -633
- package/dist/tools/rc.d.ts +0 -110
- package/dist/tools/rc.js +0 -437
- package/dist/tools/visual.d.ts +0 -40
- package/dist/tools/visual.js +0 -282
- package/dist/utils/http.d.ts +0 -6
- package/dist/utils/http.js +0 -151
- package/dist/utils/python-output.d.ts +0 -18
- package/dist/utils/python-output.js +0 -290
- package/dist/utils/python.d.ts +0 -2
- package/dist/utils/python.js +0 -4
- package/dist/utils/stdio-redirect.d.ts +0 -2
- package/dist/utils/stdio-redirect.js +0 -20
- package/docs/unreal-tool-test-cases.md +0 -572
- package/smithery.yaml +0 -29
- package/src/tools/build_environment_advanced.ts +0 -732
- package/src/tools/rc.ts +0 -515
- package/src/tools/visual.ts +0 -281
- package/src/utils/http.ts +0 -187
- package/src/utils/python-output.ts +0 -351
- package/src/utils/python.ts +0 -3
- package/src/utils/stdio-redirect.ts +0 -18
package/src/tools/lighting.ts
CHANGED
|
@@ -1,36 +1,15 @@
|
|
|
1
|
-
// Lighting tools for Unreal Engine
|
|
1
|
+
// Lighting tools for Unreal Engine using Automation Bridge
|
|
2
2
|
import { UnrealBridge } from '../unreal-bridge.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { AutomationBridge } from '../automation/index.js';
|
|
4
|
+
import { ensureVector3 } from '../utils/validation.js';
|
|
5
5
|
|
|
6
6
|
export class LightingTools {
|
|
7
|
-
constructor(private bridge: UnrealBridge) {}
|
|
7
|
+
constructor(private bridge: UnrealBridge, private automationBridge?: AutomationBridge) { }
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
let logs = '';
|
|
11
|
-
if (Array.isArray(result?.LogOutput)) {
|
|
12
|
-
logs = result.LogOutput.map((l: any) => String(l.Output || '')).join('');
|
|
13
|
-
} else if (typeof result === 'string') {
|
|
14
|
-
logs = result;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// If Python reported a traceback or explicit failure, propagate as error
|
|
18
|
-
if (/Traceback|Error:|Failed to spawn/i.test(logs)) {
|
|
19
|
-
throw new Error(`Unreal reported error spawning '${label}': ${logs}`);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// If script executed (ReturnValue true) and no error patterns, treat as success
|
|
23
|
-
const executed = result?.ReturnValue === true || result?.ReturnValue === 'true';
|
|
24
|
-
if (executed) return;
|
|
9
|
+
setAutomationBridge(automationBridge?: AutomationBridge) { this.automationBridge = automationBridge; }
|
|
25
10
|
|
|
26
|
-
// Fallback: if no ReturnValue but success-like logs exist, accept
|
|
27
|
-
if (/spawned/i.test(logs)) return;
|
|
28
11
|
|
|
29
|
-
|
|
30
|
-
throw new Error(`Uncertain spawn result for '${label}'. Engine logs:\n${logs}`);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
private normalizeName(value: unknown, fallback?: string): string {
|
|
12
|
+
private normalizeName(value: unknown, defaultName?: string): string {
|
|
34
13
|
if (typeof value === 'string') {
|
|
35
14
|
const trimmed = value.trim();
|
|
36
15
|
if (trimmed.length > 0) {
|
|
@@ -38,14 +17,71 @@ export class LightingTools {
|
|
|
38
17
|
}
|
|
39
18
|
}
|
|
40
19
|
|
|
41
|
-
if (typeof
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
44
|
-
return
|
|
20
|
+
if (typeof defaultName === 'string') {
|
|
21
|
+
const trimmedDefault = defaultName.trim();
|
|
22
|
+
if (trimmedDefault.length > 0) {
|
|
23
|
+
return trimmedDefault;
|
|
45
24
|
}
|
|
46
25
|
}
|
|
47
26
|
|
|
48
|
-
|
|
27
|
+
// Auto-generate if no name is provided
|
|
28
|
+
return `Light_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Spawn a light actor using the Automation Bridge.
|
|
33
|
+
* @param lightClass The Unreal light class name (e.g. 'DirectionalLight', 'PointLight')
|
|
34
|
+
* @param params Light spawn parameters
|
|
35
|
+
*/
|
|
36
|
+
private async spawnLightViaAutomation(
|
|
37
|
+
lightClass: string,
|
|
38
|
+
params: {
|
|
39
|
+
name: string;
|
|
40
|
+
location?: [number, number, number];
|
|
41
|
+
rotation?: [number, number, number] | { pitch: number, yaw: number, roll: number };
|
|
42
|
+
properties?: Record<string, any>;
|
|
43
|
+
}
|
|
44
|
+
) {
|
|
45
|
+
if (!this.automationBridge) {
|
|
46
|
+
throw new Error('Automation Bridge not available. Cannot spawn lights without plugin support.');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const payload: Record<string, any> = {
|
|
51
|
+
lightClass,
|
|
52
|
+
name: params.name,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
if (params.location) {
|
|
56
|
+
payload.location = { x: params.location[0], y: params.location[1], z: params.location[2] };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (params.rotation) {
|
|
60
|
+
if (Array.isArray(params.rotation)) {
|
|
61
|
+
payload.rotation = { pitch: params.rotation[0], yaw: params.rotation[1], roll: params.rotation[2] };
|
|
62
|
+
} else {
|
|
63
|
+
payload.rotation = params.rotation;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (params.properties) {
|
|
68
|
+
payload.properties = params.properties;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const response = await this.automationBridge.sendAutomationRequest('spawn_light', payload, {
|
|
72
|
+
timeoutMs: 60000
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (response.success === false) {
|
|
76
|
+
throw new Error(response.error || response.message || 'Failed to spawn light');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return response;
|
|
80
|
+
} catch (error) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`Failed to spawn ${lightClass}: ${error instanceof Error ? error.message : String(error)}`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
49
85
|
}
|
|
50
86
|
|
|
51
87
|
// Create directional light
|
|
@@ -53,13 +89,17 @@ export class LightingTools {
|
|
|
53
89
|
name: string;
|
|
54
90
|
intensity?: number;
|
|
55
91
|
color?: [number, number, number];
|
|
56
|
-
rotation?: [number, number, number];
|
|
92
|
+
rotation?: [number, number, number] | { pitch: number, yaw: number, roll: number };
|
|
57
93
|
castShadows?: boolean;
|
|
58
94
|
temperature?: number;
|
|
95
|
+
useAsAtmosphereSunLight?: boolean;
|
|
96
|
+
properties?: Record<string, any>;
|
|
59
97
|
}) {
|
|
60
98
|
const name = this.normalizeName(params.name);
|
|
61
|
-
|
|
62
|
-
|
|
99
|
+
if (!this.automationBridge) {
|
|
100
|
+
throw new Error('Automation Bridge required for light spawning');
|
|
101
|
+
}
|
|
102
|
+
|
|
63
103
|
// Validate numeric parameters
|
|
64
104
|
if (params.intensity !== undefined) {
|
|
65
105
|
if (typeof params.intensity !== 'number' || !isFinite(params.intensity)) {
|
|
@@ -69,13 +109,13 @@ export class LightingTools {
|
|
|
69
109
|
throw new Error('Invalid intensity: must be non-negative');
|
|
70
110
|
}
|
|
71
111
|
}
|
|
72
|
-
|
|
112
|
+
|
|
73
113
|
if (params.temperature !== undefined) {
|
|
74
114
|
if (typeof params.temperature !== 'number' || !isFinite(params.temperature)) {
|
|
75
115
|
throw new Error(`Invalid temperature value: ${params.temperature}`);
|
|
76
116
|
}
|
|
77
117
|
}
|
|
78
|
-
|
|
118
|
+
|
|
79
119
|
// Validate arrays
|
|
80
120
|
if (params.color !== undefined) {
|
|
81
121
|
if (!Array.isArray(params.color) || params.color.length !== 3) {
|
|
@@ -87,77 +127,53 @@ export class LightingTools {
|
|
|
87
127
|
}
|
|
88
128
|
}
|
|
89
129
|
}
|
|
90
|
-
|
|
130
|
+
|
|
91
131
|
if (params.rotation !== undefined) {
|
|
92
|
-
if (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
132
|
+
if (Array.isArray(params.rotation)) {
|
|
133
|
+
if (params.rotation.length !== 3) {
|
|
134
|
+
throw new Error('Invalid rotation: must be an array [pitch,yaw,roll]');
|
|
135
|
+
}
|
|
136
|
+
for (const r of params.rotation) {
|
|
137
|
+
if (typeof r !== 'number' || !isFinite(r)) {
|
|
138
|
+
throw new Error('Invalid rotation component: must be finite numbers');
|
|
139
|
+
}
|
|
98
140
|
}
|
|
99
141
|
}
|
|
100
142
|
}
|
|
101
|
-
|
|
143
|
+
|
|
102
144
|
const rot = params.rotation || [0, 0, 0];
|
|
103
|
-
|
|
104
|
-
// Build
|
|
105
|
-
const
|
|
145
|
+
|
|
146
|
+
// Build properties for the light
|
|
147
|
+
const properties: Record<string, any> = params.properties || {};
|
|
106
148
|
if (params.intensity !== undefined) {
|
|
107
|
-
|
|
149
|
+
properties.intensity = params.intensity;
|
|
108
150
|
}
|
|
109
151
|
if (params.color) {
|
|
110
|
-
|
|
152
|
+
properties.color = { r: params.color[0], g: params.color[1], b: params.color[2], a: 1.0 };
|
|
111
153
|
}
|
|
112
154
|
if (params.castShadows !== undefined) {
|
|
113
|
-
|
|
155
|
+
properties.castShadows = params.castShadows;
|
|
114
156
|
}
|
|
115
157
|
if (params.temperature !== undefined) {
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
spawned_light = editor_actor_subsystem.spawn_actor_from_class(
|
|
136
|
-
directional_light_class,
|
|
137
|
-
spawn_location,
|
|
138
|
-
spawn_rotation
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
if spawned_light:
|
|
142
|
-
# Set the label/name
|
|
143
|
-
spawned_light.set_actor_label("${escapedName}")
|
|
144
|
-
|
|
145
|
-
# Get the light component
|
|
146
|
-
light_component = spawned_light.get_component_by_class(unreal.DirectionalLightComponent)
|
|
147
|
-
|
|
148
|
-
if light_component:
|
|
149
|
-
${propertiesCode}
|
|
150
|
-
|
|
151
|
-
print("Directional light '${escapedName}' spawned")
|
|
152
|
-
else:
|
|
153
|
-
print("Failed to spawn directional light '${escapedName}'")
|
|
154
|
-
`;
|
|
155
|
-
|
|
156
|
-
// Execute the Python script via bridge (UE 5.6-compatible)
|
|
157
|
-
const result = await this.bridge.executePython(pythonScript);
|
|
158
|
-
|
|
159
|
-
this.ensurePythonSpawnSucceeded(name, result);
|
|
160
|
-
return { success: true, message: `Directional light '${name}' spawned` };
|
|
158
|
+
properties.temperature = params.temperature;
|
|
159
|
+
}
|
|
160
|
+
if (params.useAsAtmosphereSunLight !== undefined) {
|
|
161
|
+
properties.useAsAtmosphereSunLight = params.useAsAtmosphereSunLight;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
await this.spawnLightViaAutomation('DirectionalLight', {
|
|
166
|
+
name,
|
|
167
|
+
location: [0, 0, 500],
|
|
168
|
+
rotation: rot,
|
|
169
|
+
properties
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
return { success: true, message: `Directional light '${name}' spawned` };
|
|
173
|
+
} catch (e: any) {
|
|
174
|
+
// Don't mask errors as "not implemented" - report the actual error from the bridge
|
|
175
|
+
return { success: false, error: `Failed to create directional light: ${e?.message ?? e}` } as any;
|
|
176
|
+
}
|
|
161
177
|
}
|
|
162
178
|
|
|
163
179
|
// Create point light
|
|
@@ -169,25 +185,27 @@ else:
|
|
|
169
185
|
color?: [number, number, number];
|
|
170
186
|
falloffExponent?: number;
|
|
171
187
|
castShadows?: boolean;
|
|
188
|
+
rotation?: [number, number, number] | { pitch: number, yaw: number, roll: number };
|
|
172
189
|
}) {
|
|
173
190
|
const name = this.normalizeName(params.name);
|
|
174
|
-
|
|
175
|
-
|
|
191
|
+
if (!this.automationBridge) {
|
|
192
|
+
throw new Error('Automation Bridge required for light spawning');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Validate location array
|
|
176
196
|
// Validate location array
|
|
177
197
|
if (params.location !== undefined) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
throw new Error('Invalid location component: must be finite numbers');
|
|
184
|
-
}
|
|
198
|
+
// Ensure location is valid array [x,y,z]
|
|
199
|
+
try {
|
|
200
|
+
params.location = ensureVector3(params.location, 'location');
|
|
201
|
+
} catch (e) {
|
|
202
|
+
throw new Error(`Invalid location: ${e instanceof Error ? e.message : String(e)}`);
|
|
185
203
|
}
|
|
186
204
|
}
|
|
187
|
-
|
|
205
|
+
|
|
188
206
|
// Default location if not provided
|
|
189
207
|
const location = params.location || [0, 0, 0];
|
|
190
|
-
|
|
208
|
+
|
|
191
209
|
// Validate numeric parameters
|
|
192
210
|
if (params.intensity !== undefined) {
|
|
193
211
|
if (typeof params.intensity !== 'number' || !isFinite(params.intensity)) {
|
|
@@ -210,7 +228,7 @@ else:
|
|
|
210
228
|
throw new Error(`Invalid falloffExponent value: ${params.falloffExponent}`);
|
|
211
229
|
}
|
|
212
230
|
}
|
|
213
|
-
|
|
231
|
+
|
|
214
232
|
// Validate color array
|
|
215
233
|
if (params.color !== undefined) {
|
|
216
234
|
if (!Array.isArray(params.color) || params.color.length !== 3) {
|
|
@@ -222,74 +240,45 @@ else:
|
|
|
222
240
|
}
|
|
223
241
|
}
|
|
224
242
|
}
|
|
225
|
-
|
|
226
|
-
// Build
|
|
227
|
-
const
|
|
243
|
+
|
|
244
|
+
// Build properties for the light
|
|
245
|
+
const properties: Record<string, any> = {};
|
|
228
246
|
if (params.intensity !== undefined) {
|
|
229
|
-
|
|
247
|
+
properties.intensity = params.intensity;
|
|
230
248
|
}
|
|
231
249
|
if (params.radius !== undefined) {
|
|
232
|
-
|
|
250
|
+
properties.attenuationRadius = params.radius;
|
|
233
251
|
}
|
|
234
252
|
if (params.color) {
|
|
235
|
-
|
|
253
|
+
properties.color = { r: params.color[0], g: params.color[1], b: params.color[2], a: 1.0 };
|
|
236
254
|
}
|
|
237
255
|
if (params.castShadows !== undefined) {
|
|
238
|
-
|
|
256
|
+
properties.castShadows = params.castShadows;
|
|
239
257
|
}
|
|
240
258
|
if (params.falloffExponent !== undefined) {
|
|
241
|
-
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
spawn_rotation = unreal.Rotator(0, 0, 0)
|
|
258
|
-
|
|
259
|
-
# Spawn the actor
|
|
260
|
-
spawned_light = editor_actor_subsystem.spawn_actor_from_class(
|
|
261
|
-
point_light_class,
|
|
262
|
-
spawn_location,
|
|
263
|
-
spawn_rotation
|
|
264
|
-
)
|
|
265
|
-
|
|
266
|
-
if spawned_light:
|
|
267
|
-
# Set the label/name
|
|
268
|
-
spawned_light.set_actor_label("${escapedName}")
|
|
269
|
-
|
|
270
|
-
# Get the light component
|
|
271
|
-
light_component = spawned_light.get_component_by_class(unreal.PointLightComponent)
|
|
272
|
-
|
|
273
|
-
if light_component:
|
|
274
|
-
${propertiesCode}
|
|
275
|
-
|
|
276
|
-
print(f"Point light '${escapedName}' spawned at {spawn_location.x}, {spawn_location.y}, {spawn_location.z}")
|
|
277
|
-
else:
|
|
278
|
-
print("Failed to spawn point light '${escapedName}'")
|
|
279
|
-
`;
|
|
280
|
-
|
|
281
|
-
// Execute the Python script via bridge (UE 5.6-compatible)
|
|
282
|
-
const result = await this.bridge.executePython(pythonScript);
|
|
283
|
-
|
|
284
|
-
this.ensurePythonSpawnSucceeded(name, result);
|
|
285
|
-
return { success: true, message: `Point light '${name}' spawned at ${location.join(', ')}` };
|
|
259
|
+
properties.lightFalloffExponent = params.falloffExponent;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
try {
|
|
263
|
+
await this.spawnLightViaAutomation('PointLight', {
|
|
264
|
+
name,
|
|
265
|
+
location,
|
|
266
|
+
rotation: params.rotation,
|
|
267
|
+
properties
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
return { success: true, message: `Point light '${name}' spawned at ${location.join(', ')}` };
|
|
271
|
+
} catch (e: any) {
|
|
272
|
+
// Don't mask errors as "not implemented" - report the actual error from the bridge
|
|
273
|
+
return { success: false, error: `Failed to create point light: ${e?.message ?? e}` } as any;
|
|
274
|
+
}
|
|
286
275
|
}
|
|
287
276
|
|
|
288
277
|
// Create spot light
|
|
289
278
|
async createSpotLight(params: {
|
|
290
279
|
name: string;
|
|
291
280
|
location: [number, number, number];
|
|
292
|
-
rotation: [number, number, number];
|
|
281
|
+
rotation: [number, number, number] | { pitch: number, yaw: number, roll: number };
|
|
293
282
|
intensity?: number;
|
|
294
283
|
innerCone?: number;
|
|
295
284
|
outerCone?: number;
|
|
@@ -298,8 +287,10 @@ else:
|
|
|
298
287
|
castShadows?: boolean;
|
|
299
288
|
}) {
|
|
300
289
|
const name = this.normalizeName(params.name);
|
|
301
|
-
|
|
302
|
-
|
|
290
|
+
if (!this.automationBridge) {
|
|
291
|
+
throw new Error('Automation Bridge required for light spawning');
|
|
292
|
+
}
|
|
293
|
+
|
|
303
294
|
// Validate required location and rotation arrays
|
|
304
295
|
if (!params.location || !Array.isArray(params.location) || params.location.length !== 3) {
|
|
305
296
|
throw new Error('Invalid location: must be an array [x,y,z]');
|
|
@@ -309,16 +300,21 @@ else:
|
|
|
309
300
|
throw new Error('Invalid location component: must be finite numbers');
|
|
310
301
|
}
|
|
311
302
|
}
|
|
312
|
-
|
|
313
|
-
if (!params.rotation
|
|
314
|
-
throw new Error('
|
|
303
|
+
|
|
304
|
+
if (!params.rotation) {
|
|
305
|
+
throw new Error('Rotation is required');
|
|
315
306
|
}
|
|
316
|
-
|
|
317
|
-
if (
|
|
318
|
-
throw new Error('Invalid rotation
|
|
307
|
+
if (Array.isArray(params.rotation)) {
|
|
308
|
+
if (params.rotation.length !== 3) {
|
|
309
|
+
throw new Error('Invalid rotation: must be an array [pitch,yaw,roll]');
|
|
310
|
+
}
|
|
311
|
+
for (const r of params.rotation) {
|
|
312
|
+
if (typeof r !== 'number' || !isFinite(r)) {
|
|
313
|
+
throw new Error('Invalid rotation component: must be finite numbers');
|
|
314
|
+
}
|
|
319
315
|
}
|
|
320
316
|
}
|
|
321
|
-
|
|
317
|
+
|
|
322
318
|
// Validate optional numeric parameters
|
|
323
319
|
if (params.intensity !== undefined) {
|
|
324
320
|
if (typeof params.intensity !== 'number' || !isFinite(params.intensity)) {
|
|
@@ -328,7 +324,7 @@ else:
|
|
|
328
324
|
throw new Error('Invalid intensity: must be non-negative');
|
|
329
325
|
}
|
|
330
326
|
}
|
|
331
|
-
|
|
327
|
+
|
|
332
328
|
if (params.innerCone !== undefined) {
|
|
333
329
|
if (typeof params.innerCone !== 'number' || !isFinite(params.innerCone)) {
|
|
334
330
|
throw new Error(`Invalid innerCone value: ${params.innerCone}`);
|
|
@@ -337,7 +333,7 @@ else:
|
|
|
337
333
|
throw new Error('Invalid innerCone: must be between 0 and 180 degrees');
|
|
338
334
|
}
|
|
339
335
|
}
|
|
340
|
-
|
|
336
|
+
|
|
341
337
|
if (params.outerCone !== undefined) {
|
|
342
338
|
if (typeof params.outerCone !== 'number' || !isFinite(params.outerCone)) {
|
|
343
339
|
throw new Error(`Invalid outerCone value: ${params.outerCone}`);
|
|
@@ -346,7 +342,7 @@ else:
|
|
|
346
342
|
throw new Error('Invalid outerCone: must be between 0 and 180 degrees');
|
|
347
343
|
}
|
|
348
344
|
}
|
|
349
|
-
|
|
345
|
+
|
|
350
346
|
if (params.radius !== undefined) {
|
|
351
347
|
if (typeof params.radius !== 'number' || !isFinite(params.radius)) {
|
|
352
348
|
throw new Error(`Invalid radius value: ${params.radius}`);
|
|
@@ -355,7 +351,7 @@ else:
|
|
|
355
351
|
throw new Error('Invalid radius: must be non-negative');
|
|
356
352
|
}
|
|
357
353
|
}
|
|
358
|
-
|
|
354
|
+
|
|
359
355
|
// Validate color array
|
|
360
356
|
if (params.color !== undefined) {
|
|
361
357
|
if (!Array.isArray(params.color) || params.color.length !== 3) {
|
|
@@ -367,83 +363,58 @@ else:
|
|
|
367
363
|
}
|
|
368
364
|
}
|
|
369
365
|
}
|
|
370
|
-
// Build
|
|
371
|
-
const
|
|
366
|
+
// Build properties for the light
|
|
367
|
+
const properties: Record<string, any> = {};
|
|
372
368
|
if (params.intensity !== undefined) {
|
|
373
|
-
|
|
369
|
+
properties.intensity = params.intensity;
|
|
374
370
|
}
|
|
375
371
|
if (params.innerCone !== undefined) {
|
|
376
|
-
|
|
372
|
+
properties.innerConeAngle = params.innerCone;
|
|
377
373
|
}
|
|
378
374
|
if (params.outerCone !== undefined) {
|
|
379
|
-
|
|
375
|
+
properties.outerConeAngle = params.outerCone;
|
|
380
376
|
}
|
|
381
377
|
if (params.radius !== undefined) {
|
|
382
|
-
|
|
378
|
+
properties.attenuationRadius = params.radius;
|
|
383
379
|
}
|
|
384
380
|
if (params.color) {
|
|
385
|
-
|
|
381
|
+
properties.color = { r: params.color[0], g: params.color[1], b: params.color[2], a: 1.0 };
|
|
386
382
|
}
|
|
387
383
|
if (params.castShadows !== undefined) {
|
|
388
|
-
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
spawn_rotation = unreal.Rotator(${params.rotation[0]}, ${params.rotation[1]}, ${params.rotation[2]})
|
|
405
|
-
|
|
406
|
-
# Spawn the actor
|
|
407
|
-
spawned_light = editor_actor_subsystem.spawn_actor_from_class(
|
|
408
|
-
spot_light_class,
|
|
409
|
-
spawn_location,
|
|
410
|
-
spawn_rotation
|
|
411
|
-
)
|
|
412
|
-
|
|
413
|
-
if spawned_light:
|
|
414
|
-
# Set the label/name
|
|
415
|
-
spawned_light.set_actor_label("${escapedName}")
|
|
416
|
-
|
|
417
|
-
# Get the light component
|
|
418
|
-
light_component = spawned_light.get_component_by_class(unreal.SpotLightComponent)
|
|
419
|
-
|
|
420
|
-
if light_component:
|
|
421
|
-
${propertiesCode}
|
|
422
|
-
|
|
423
|
-
print(f"Spot light '${escapedName}' spawned at {spawn_location.x}, {spawn_location.y}, {spawn_location.z}")
|
|
424
|
-
else:
|
|
425
|
-
print("Failed to spawn spot light '${escapedName}'")
|
|
426
|
-
`;
|
|
427
|
-
|
|
428
|
-
// Execute the Python script via bridge (UE 5.6-compatible)
|
|
429
|
-
const result = await this.bridge.executePython(pythonScript);
|
|
430
|
-
|
|
431
|
-
this.ensurePythonSpawnSucceeded(name, result);
|
|
432
|
-
return { success: true, message: `Spot light '${name}' spawned at ${params.location.join(', ')}` };
|
|
384
|
+
properties.castShadows = params.castShadows;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
try {
|
|
388
|
+
await this.spawnLightViaAutomation('SpotLight', {
|
|
389
|
+
name,
|
|
390
|
+
location: params.location,
|
|
391
|
+
rotation: params.rotation,
|
|
392
|
+
properties
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
return { success: true, message: `Spot light '${name}' spawned at ${params.location.join(', ')}` };
|
|
396
|
+
} catch (e: any) {
|
|
397
|
+
// Don't mask errors as "not implemented" - report the actual error from the bridge
|
|
398
|
+
return { success: false, error: `Failed to create spot light: ${e?.message ?? e}` } as any;
|
|
399
|
+
}
|
|
433
400
|
}
|
|
434
401
|
|
|
435
402
|
// Create rect light
|
|
436
403
|
async createRectLight(params: {
|
|
437
404
|
name: string;
|
|
438
405
|
location: [number, number, number];
|
|
439
|
-
rotation: [number, number, number];
|
|
406
|
+
rotation: [number, number, number] | { pitch: number, yaw: number, roll: number };
|
|
440
407
|
width?: number;
|
|
441
408
|
height?: number;
|
|
442
409
|
intensity?: number;
|
|
443
410
|
color?: [number, number, number];
|
|
411
|
+
castShadows?: boolean;
|
|
444
412
|
}) {
|
|
413
|
+
|
|
445
414
|
const name = this.normalizeName(params.name);
|
|
446
|
-
|
|
415
|
+
if (!this.automationBridge) {
|
|
416
|
+
throw new Error('Automation Bridge required for light spawning');
|
|
417
|
+
}
|
|
447
418
|
|
|
448
419
|
// Validate required location and rotation arrays
|
|
449
420
|
if (!params.location || !Array.isArray(params.location) || params.location.length !== 3) {
|
|
@@ -454,16 +425,21 @@ else:
|
|
|
454
425
|
throw new Error('Invalid location component: must be finite numbers');
|
|
455
426
|
}
|
|
456
427
|
}
|
|
457
|
-
|
|
458
|
-
if (!params.rotation
|
|
459
|
-
throw new Error('
|
|
428
|
+
|
|
429
|
+
if (!params.rotation) {
|
|
430
|
+
throw new Error('Rotation is required');
|
|
460
431
|
}
|
|
461
|
-
|
|
462
|
-
if (
|
|
463
|
-
throw new Error('Invalid rotation
|
|
432
|
+
if (Array.isArray(params.rotation)) {
|
|
433
|
+
if (params.rotation.length !== 3) {
|
|
434
|
+
throw new Error('Invalid rotation: must be an array [pitch,yaw,roll]');
|
|
435
|
+
}
|
|
436
|
+
for (const r of params.rotation) {
|
|
437
|
+
if (typeof r !== 'number' || !isFinite(r)) {
|
|
438
|
+
throw new Error('Invalid rotation component: must be finite numbers');
|
|
439
|
+
}
|
|
464
440
|
}
|
|
465
441
|
}
|
|
466
|
-
|
|
442
|
+
|
|
467
443
|
// Validate optional numeric parameters
|
|
468
444
|
if (params.width !== undefined) {
|
|
469
445
|
if (typeof params.width !== 'number' || !isFinite(params.width)) {
|
|
@@ -473,7 +449,7 @@ else:
|
|
|
473
449
|
throw new Error('Invalid width: must be positive');
|
|
474
450
|
}
|
|
475
451
|
}
|
|
476
|
-
|
|
452
|
+
|
|
477
453
|
if (params.height !== undefined) {
|
|
478
454
|
if (typeof params.height !== 'number' || !isFinite(params.height)) {
|
|
479
455
|
throw new Error(`Invalid height value: ${params.height}`);
|
|
@@ -482,7 +458,7 @@ else:
|
|
|
482
458
|
throw new Error('Invalid height: must be positive');
|
|
483
459
|
}
|
|
484
460
|
}
|
|
485
|
-
|
|
461
|
+
|
|
486
462
|
if (params.intensity !== undefined) {
|
|
487
463
|
if (typeof params.intensity !== 'number' || !isFinite(params.intensity)) {
|
|
488
464
|
throw new Error(`Invalid intensity value: ${params.intensity}`);
|
|
@@ -491,7 +467,7 @@ else:
|
|
|
491
467
|
throw new Error('Invalid intensity: must be non-negative');
|
|
492
468
|
}
|
|
493
469
|
}
|
|
494
|
-
|
|
470
|
+
|
|
495
471
|
// Validate color array
|
|
496
472
|
if (params.color !== undefined) {
|
|
497
473
|
if (!Array.isArray(params.color) || params.color.length !== 3) {
|
|
@@ -503,63 +479,77 @@ else:
|
|
|
503
479
|
}
|
|
504
480
|
}
|
|
505
481
|
}
|
|
506
|
-
// Build
|
|
507
|
-
const
|
|
482
|
+
// Build properties for the light
|
|
483
|
+
const properties: Record<string, any> = {};
|
|
508
484
|
if (params.intensity !== undefined) {
|
|
509
|
-
|
|
485
|
+
properties.intensity = params.intensity;
|
|
510
486
|
}
|
|
511
487
|
if (params.color) {
|
|
512
|
-
|
|
488
|
+
properties.color = { r: params.color[0], g: params.color[1], b: params.color[2], a: 1.0 };
|
|
513
489
|
}
|
|
514
490
|
if (params.width !== undefined) {
|
|
515
|
-
|
|
491
|
+
properties.sourceWidth = params.width;
|
|
516
492
|
}
|
|
517
493
|
if (params.height !== undefined) {
|
|
518
|
-
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
494
|
+
properties.sourceHeight = params.height;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
try {
|
|
498
|
+
await this.spawnLightViaAutomation('RectLight', {
|
|
499
|
+
name,
|
|
500
|
+
location: params.location,
|
|
501
|
+
rotation: params.rotation,
|
|
502
|
+
properties
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
return { success: true, message: `Rect light '${name}' spawned at ${params.location.join(', ')}` };
|
|
506
|
+
} catch (e: any) {
|
|
507
|
+
// Don't mask errors as "not implemented" - report the actual error from the bridge
|
|
508
|
+
return { success: false, error: `Failed to create rect light: ${e?.message ?? e}` } as any;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Create dynamic light
|
|
514
|
+
*/
|
|
515
|
+
async createDynamicLight(params: {
|
|
516
|
+
name?: string;
|
|
517
|
+
lightType?: 'Point' | 'Spot' | 'Directional' | 'Rect' | string;
|
|
518
|
+
location?: [number, number, number] | { x: number; y: number; z: number };
|
|
519
|
+
rotation?: [number, number, number];
|
|
520
|
+
intensity?: number;
|
|
521
|
+
color?: [number, number, number, number] | { r: number; g: number; b: number; a?: number };
|
|
522
|
+
pulse?: { enabled?: boolean; frequency?: number };
|
|
523
|
+
}) {
|
|
524
|
+
try {
|
|
525
|
+
const name = typeof params.name === 'string' && params.name.trim().length > 0 ? params.name.trim() : `DynamicLight_${Date.now() % 10000}`;
|
|
526
|
+
const lightTypeRaw = typeof params.lightType === 'string' && params.lightType.trim().length > 0 ? params.lightType.trim() : 'Point';
|
|
527
|
+
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 });
|
|
528
|
+
|
|
529
|
+
// C++ plugin does not strictly implement 'create_dynamic_light' action; it supports 'spawn_light'.
|
|
530
|
+
// However, we rely on the specific helper methods below which correctly map to 'spawn_light'
|
|
531
|
+
// with the appropriate class and properties.
|
|
532
|
+
|
|
533
|
+
const toArray3 = (loc: any): [number, number, number] => Array.isArray(loc)
|
|
534
|
+
? [Number(loc[0]) || 0, Number(loc[1]) || 0, Number(loc[2]) || 0]
|
|
535
|
+
: [Number(loc?.x) || 0, Number(loc?.y) || 0, Number(loc?.z) || 0];
|
|
536
|
+
const locArr = toArray3(location);
|
|
537
|
+
const typeNorm = (lightTypeRaw || 'Point').toLowerCase();
|
|
538
|
+
|
|
539
|
+
switch (typeNorm) {
|
|
540
|
+
case 'directional': case 'directionallight':
|
|
541
|
+
return await this.createDirectionalLight({ name, intensity: params.intensity, color: Array.isArray(params.color) ? [params.color[0], params.color[1], params.color[2]] as any : (params.color ? [params.color.r, params.color.g, params.color.b] : undefined), rotation: params.rotation as any });
|
|
542
|
+
case 'spot': case 'spotlight':
|
|
543
|
+
return await this.createSpotLight({ name, location: locArr, rotation: params.rotation as any, intensity: params.intensity, innerCone: undefined, outerCone: undefined, color: Array.isArray(params.color) ? params.color as any : (params.color ? [params.color.r, params.color.g, params.color.b] : undefined) });
|
|
544
|
+
case 'rect': case 'rectlight':
|
|
545
|
+
return await this.createRectLight({ name, location: locArr, rotation: params.rotation as any, width: undefined, height: undefined, intensity: params.intensity, color: Array.isArray(params.color) ? params.color as any : (params.color ? [params.color.r, params.color.g, params.color.b] : undefined) });
|
|
546
|
+
case 'point': default:
|
|
547
|
+
return await this.createPointLight({ name, location: locArr, intensity: params.intensity, radius: undefined, color: Array.isArray(params.color) ? params.color as any : (params.color ? [params.color.r, params.color.g, params.color.b] : undefined), castShadows: undefined });
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
} catch (err) {
|
|
551
|
+
return { success: false, error: `Failed to create dynamic light: ${err}` };
|
|
552
|
+
}
|
|
563
553
|
}
|
|
564
554
|
|
|
565
555
|
// Create sky light
|
|
@@ -569,210 +559,107 @@ else:
|
|
|
569
559
|
cubemapPath?: string;
|
|
570
560
|
intensity?: number;
|
|
571
561
|
recapture?: boolean;
|
|
562
|
+
location?: [number, number, number];
|
|
563
|
+
rotation?: [number, number, number] | { pitch: number, yaw: number, roll: number };
|
|
564
|
+
realTimeCapture?: boolean;
|
|
565
|
+
castShadows?: boolean;
|
|
566
|
+
color?: [number, number, number];
|
|
572
567
|
}) {
|
|
573
568
|
const name = this.normalizeName(params.name);
|
|
574
|
-
|
|
575
|
-
const sourceTypeRaw = typeof params.sourceType === 'string' ? params.sourceType.trim() : undefined;
|
|
576
|
-
const normalizedSourceType = sourceTypeRaw
|
|
577
|
-
? sourceTypeRaw.toLowerCase() === 'specifiedcubemap'
|
|
578
|
-
? 'SpecifiedCubemap'
|
|
579
|
-
: sourceTypeRaw.toLowerCase() === 'capturedscene'
|
|
580
|
-
? 'CapturedScene'
|
|
581
|
-
: undefined
|
|
582
|
-
: undefined;
|
|
583
|
-
const cubemapPath = typeof params.cubemapPath === 'string' ? params.cubemapPath.trim() : undefined;
|
|
584
|
-
|
|
585
|
-
if (normalizedSourceType === 'SpecifiedCubemap' && (!cubemapPath || cubemapPath.length === 0)) {
|
|
569
|
+
if (params.sourceType === 'SpecifiedCubemap' && (!params.cubemapPath || params.cubemapPath.trim().length === 0)) {
|
|
586
570
|
const message = 'cubemapPath is required when sourceType is SpecifiedCubemap';
|
|
587
571
|
return { success: false, error: message, message };
|
|
588
572
|
}
|
|
589
|
-
const escapedCubemapPath = cubemapPath ? escapePythonString(cubemapPath) : '';
|
|
590
|
-
const python = `
|
|
591
|
-
import unreal
|
|
592
|
-
import json
|
|
593
573
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
}
|
|
574
|
+
if (!this.automationBridge) {
|
|
575
|
+
throw new Error('Automation Bridge required for sky light creation');
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
try {
|
|
579
|
+
const properties: Record<string, any> = {};
|
|
580
|
+
if (params.intensity !== undefined) properties.Intensity = params.intensity;
|
|
581
|
+
if (params.castShadows !== undefined) properties.CastShadows = params.castShadows;
|
|
582
|
+
if (params.realTimeCapture !== undefined) properties.RealTimeCapture = params.realTimeCapture;
|
|
583
|
+
if (params.color) properties.LightColor = { r: params.color[0], g: params.color[1], b: params.color[2], a: 1.0 };
|
|
584
|
+
|
|
585
|
+
const payload: Record<string, any> = {
|
|
586
|
+
name,
|
|
587
|
+
sourceType: params.sourceType || 'CapturedScene',
|
|
588
|
+
location: params.location,
|
|
589
|
+
rotation: params.rotation,
|
|
590
|
+
properties
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
if (params.cubemapPath) {
|
|
594
|
+
payload.cubemapPath = params.cubemapPath;
|
|
595
|
+
}
|
|
596
|
+
if (params.intensity !== undefined) {
|
|
597
|
+
payload.intensity = params.intensity;
|
|
598
|
+
}
|
|
599
|
+
if (params.recapture) {
|
|
600
|
+
payload.recapture = params.recapture;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const response = await this.automationBridge.sendAutomationRequest('spawn_sky_light', payload, {
|
|
604
|
+
timeoutMs: 60000
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
if (response.success === false) {
|
|
608
|
+
return {
|
|
609
|
+
success: false,
|
|
610
|
+
error: response.error || response.message || 'Failed to create sky light'
|
|
611
|
+
};
|
|
612
|
+
}
|
|
600
613
|
|
|
601
|
-
def add_warning(text):
|
|
602
|
-
if text:
|
|
603
|
-
result["warnings"].append(str(text))
|
|
604
|
-
|
|
605
|
-
def finish():
|
|
606
|
-
if result["success"]:
|
|
607
|
-
if not result["message"]:
|
|
608
|
-
result["message"] = "Sky light ensured"
|
|
609
|
-
result.pop("error", None)
|
|
610
|
-
else:
|
|
611
|
-
if not result["error"]:
|
|
612
|
-
result["error"] = result["message"] or "Failed to ensure sky light"
|
|
613
|
-
if not result["message"]:
|
|
614
|
-
result["message"] = result["error"]
|
|
615
|
-
if not result["warnings"]:
|
|
616
|
-
result.pop("warnings", None)
|
|
617
|
-
print('RESULT:' + json.dumps(result))
|
|
618
|
-
|
|
619
|
-
try:
|
|
620
|
-
actor_sub = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
|
|
621
|
-
if not actor_sub:
|
|
622
|
-
result["error"] = "EditorActorSubsystem unavailable"
|
|
623
|
-
finish()
|
|
624
|
-
raise SystemExit(0)
|
|
625
|
-
|
|
626
|
-
spawn_location = unreal.Vector(0.0, 0.0, 500.0)
|
|
627
|
-
spawn_rotation = unreal.Rotator(0.0, 0.0, 0.0)
|
|
628
|
-
|
|
629
|
-
actor = None
|
|
630
|
-
try:
|
|
631
|
-
for candidate in actor_sub.get_all_level_actors():
|
|
632
|
-
try:
|
|
633
|
-
if candidate.get_class().get_name() == 'SkyLight':
|
|
634
|
-
actor = candidate
|
|
635
|
-
break
|
|
636
|
-
except Exception:
|
|
637
|
-
continue
|
|
638
|
-
except Exception:
|
|
639
|
-
pass
|
|
640
|
-
|
|
641
|
-
if actor is None:
|
|
642
|
-
actor = actor_sub.spawn_actor_from_class(unreal.SkyLight, spawn_location, spawn_rotation)
|
|
643
|
-
|
|
644
|
-
if not actor:
|
|
645
|
-
result["error"] = "Failed to spawn SkyLight actor"
|
|
646
|
-
finish()
|
|
647
|
-
raise SystemExit(0)
|
|
648
|
-
|
|
649
|
-
try:
|
|
650
|
-
actor.set_actor_label("${escapedName}")
|
|
651
|
-
except Exception:
|
|
652
|
-
pass
|
|
653
|
-
|
|
654
|
-
comp = actor.get_component_by_class(unreal.SkyLightComponent)
|
|
655
|
-
if not comp:
|
|
656
|
-
result["error"] = "SkyLight component missing"
|
|
657
|
-
finish()
|
|
658
|
-
raise SystemExit(0)
|
|
659
|
-
|
|
660
|
-
${params.intensity !== undefined ? `
|
|
661
|
-
try:
|
|
662
|
-
comp.set_intensity(${params.intensity})
|
|
663
|
-
except Exception:
|
|
664
|
-
try:
|
|
665
|
-
comp.set_editor_property('intensity', ${params.intensity})
|
|
666
|
-
except Exception:
|
|
667
|
-
add_warning('Unable to set intensity property')
|
|
668
|
-
` : ''}
|
|
669
|
-
|
|
670
|
-
source_type = ${normalizedSourceType ? `'${normalizedSourceType}'` : 'None'}
|
|
671
|
-
if source_type:
|
|
672
|
-
try:
|
|
673
|
-
comp.set_editor_property('source_type', getattr(unreal.SkyLightSourceType, source_type))
|
|
674
|
-
except Exception:
|
|
675
|
-
try:
|
|
676
|
-
comp.source_type = getattr(unreal.SkyLightSourceType, source_type)
|
|
677
|
-
except Exception:
|
|
678
|
-
add_warning(f"Unable to set source type {source_type}")
|
|
679
|
-
|
|
680
|
-
if source_type == 'SpecifiedCubemap':
|
|
681
|
-
path = "${escapedCubemapPath}"
|
|
682
|
-
if not path:
|
|
683
|
-
result["error"] = "cubemapPath is required when sourceType is SpecifiedCubemap"
|
|
684
|
-
finish()
|
|
685
|
-
raise SystemExit(0)
|
|
686
|
-
try:
|
|
687
|
-
exists = unreal.EditorAssetLibrary.does_asset_exist(path)
|
|
688
|
-
except Exception:
|
|
689
|
-
exists = False
|
|
690
|
-
if not exists:
|
|
691
|
-
result["error"] = f"Cubemap asset not found: {path}"
|
|
692
|
-
finish()
|
|
693
|
-
raise SystemExit(0)
|
|
694
|
-
try:
|
|
695
|
-
cube = unreal.EditorAssetLibrary.load_asset(path)
|
|
696
|
-
except Exception as load_err:
|
|
697
|
-
result["error"] = f"Failed to load cubemap asset: {load_err}"
|
|
698
|
-
finish()
|
|
699
|
-
raise SystemExit(0)
|
|
700
|
-
if not cube:
|
|
701
|
-
result["error"] = f"Cubemap asset could not be loaded: {path}"
|
|
702
|
-
finish()
|
|
703
|
-
raise SystemExit(0)
|
|
704
|
-
try:
|
|
705
|
-
if hasattr(comp, 'set_cubemap'):
|
|
706
|
-
comp.set_cubemap(cube)
|
|
707
|
-
else:
|
|
708
|
-
comp.set_editor_property('cubemap', cube)
|
|
709
|
-
except Exception as assign_err:
|
|
710
|
-
result["error"] = f"Failed to assign cubemap: {assign_err}"
|
|
711
|
-
finish()
|
|
712
|
-
raise SystemExit(0)
|
|
713
|
-
|
|
714
|
-
if ${params.recapture ? 'True' : 'False'}:
|
|
715
|
-
try:
|
|
716
|
-
comp.recapture_sky()
|
|
717
|
-
except Exception as recapture_err:
|
|
718
|
-
add_warning(f"Recapture failed: {recapture_err}")
|
|
719
|
-
|
|
720
|
-
result["success"] = True
|
|
721
|
-
result["message"] = "Sky light ensured"
|
|
722
|
-
finish()
|
|
723
|
-
|
|
724
|
-
except SystemExit:
|
|
725
|
-
pass
|
|
726
|
-
except Exception as run_err:
|
|
727
|
-
result["error"] = str(run_err)
|
|
728
|
-
finish()
|
|
729
|
-
`.trim();
|
|
730
|
-
const resp = await this.bridge.executePython(python);
|
|
731
|
-
const parsed = parseStandardResult(resp).data;
|
|
732
|
-
if (parsed) {
|
|
733
|
-
if (parsed.success) {
|
|
734
614
|
return {
|
|
735
615
|
success: true,
|
|
736
|
-
message:
|
|
737
|
-
|
|
616
|
+
message: response.message || 'Sky light created',
|
|
617
|
+
...(response.result || {})
|
|
618
|
+
};
|
|
619
|
+
} catch (error) {
|
|
620
|
+
return {
|
|
621
|
+
success: false,
|
|
622
|
+
error: `Failed to create sky light: ${error instanceof Error ? error.message : String(error)}`
|
|
738
623
|
};
|
|
739
624
|
}
|
|
740
|
-
return {
|
|
741
|
-
success: false,
|
|
742
|
-
error: parsed.error ?? parsed.message ?? 'Failed to ensure sky light',
|
|
743
|
-
warnings: Array.isArray(parsed.warnings) && parsed.warnings.length > 0 ? parsed.warnings : undefined
|
|
744
|
-
};
|
|
745
|
-
}
|
|
746
|
-
return { success: true, message: 'Sky light ensured' };
|
|
747
625
|
}
|
|
748
626
|
|
|
749
627
|
// Remove duplicate SkyLights and keep only one (named target label)
|
|
750
628
|
async ensureSingleSkyLight(params?: { name?: string; recapture?: boolean }) {
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
const escapedName = escapePythonString(name);
|
|
629
|
+
const defaultName = 'MCP_Test_Sky';
|
|
630
|
+
const name = this.normalizeName(params?.name, defaultName);
|
|
754
631
|
const recapture = !!params?.recapture;
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
}
|
|
773
|
-
}
|
|
632
|
+
|
|
633
|
+
if (!this.automationBridge) {
|
|
634
|
+
throw new Error('Automation Bridge required for sky light management');
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
try {
|
|
638
|
+
const response = await this.automationBridge.sendAutomationRequest('ensure_single_sky_light', {
|
|
639
|
+
name,
|
|
640
|
+
recapture
|
|
641
|
+
}, {
|
|
642
|
+
timeoutMs: 60000
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
if (response.success === false) {
|
|
646
|
+
return {
|
|
647
|
+
success: false,
|
|
648
|
+
error: response.error || response.message || 'Failed to ensure single sky light'
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
return {
|
|
653
|
+
success: true,
|
|
654
|
+
message: response.message || `Ensured single SkyLight (removed ${(response.result as any)?.removed || 0})`,
|
|
655
|
+
...(response.result || {})
|
|
656
|
+
};
|
|
657
|
+
} catch (error) {
|
|
658
|
+
return {
|
|
659
|
+
success: false,
|
|
660
|
+
error: `Failed to ensure single sky light: ${error instanceof Error ? error.message : String(error)}`
|
|
661
|
+
};
|
|
774
662
|
}
|
|
775
|
-
return { success: true, message: 'Ensured single SkyLight' };
|
|
776
663
|
}
|
|
777
664
|
|
|
778
665
|
// Setup global illumination
|
|
@@ -782,8 +669,22 @@ except Exception as run_err:
|
|
|
782
669
|
indirectLightingIntensity?: number;
|
|
783
670
|
bounces?: number;
|
|
784
671
|
}) {
|
|
672
|
+
if (this.automationBridge) {
|
|
673
|
+
try {
|
|
674
|
+
const response = await this.automationBridge.sendAutomationRequest('setup_global_illumination', {
|
|
675
|
+
method: params.method,
|
|
676
|
+
quality: params.quality,
|
|
677
|
+
indirectLightingIntensity: params.indirectLightingIntensity,
|
|
678
|
+
bounces: params.bounces
|
|
679
|
+
});
|
|
680
|
+
if (response.success) return { success: true, message: 'Global illumination configured via bridge', ...(response.result || {}) };
|
|
681
|
+
} catch (_e) {
|
|
682
|
+
// Fallback to console commands
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
785
686
|
const commands = [];
|
|
786
|
-
|
|
687
|
+
|
|
787
688
|
switch (params.method) {
|
|
788
689
|
case 'Lightmass':
|
|
789
690
|
commands.push('r.DynamicGlobalIlluminationMethod 0');
|
|
@@ -798,25 +699,25 @@ except Exception as run_err:
|
|
|
798
699
|
commands.push('r.DynamicGlobalIlluminationMethod 3');
|
|
799
700
|
break;
|
|
800
701
|
}
|
|
801
|
-
|
|
702
|
+
|
|
802
703
|
if (params.quality) {
|
|
803
704
|
const qualityMap = { 'Low': 0, 'Medium': 1, 'High': 2, 'Epic': 3 };
|
|
804
705
|
commands.push(`r.Lumen.Quality ${qualityMap[params.quality]}`);
|
|
805
706
|
}
|
|
806
|
-
|
|
707
|
+
|
|
807
708
|
if (params.indirectLightingIntensity !== undefined) {
|
|
808
709
|
commands.push(`r.IndirectLightingIntensity ${params.indirectLightingIntensity}`);
|
|
809
710
|
}
|
|
810
|
-
|
|
711
|
+
|
|
811
712
|
if (params.bounces !== undefined) {
|
|
812
713
|
commands.push(`r.Lumen.MaxReflectionBounces ${params.bounces}`);
|
|
813
714
|
}
|
|
814
|
-
|
|
715
|
+
|
|
815
716
|
for (const cmd of commands) {
|
|
816
717
|
await this.bridge.executeConsoleCommand(cmd);
|
|
817
718
|
}
|
|
818
|
-
|
|
819
|
-
return { success: true, message: 'Global illumination configured' };
|
|
719
|
+
|
|
720
|
+
return { success: true, message: 'Global illumination configured (console)' };
|
|
820
721
|
}
|
|
821
722
|
|
|
822
723
|
// Configure shadows
|
|
@@ -827,529 +728,174 @@ except Exception as run_err:
|
|
|
827
728
|
contactShadows?: boolean;
|
|
828
729
|
rayTracedShadows?: boolean;
|
|
829
730
|
}) {
|
|
731
|
+
if (this.automationBridge) {
|
|
732
|
+
try {
|
|
733
|
+
const response = await this.automationBridge.sendAutomationRequest('configure_shadows', {
|
|
734
|
+
shadowQuality: params.shadowQuality,
|
|
735
|
+
cascadedShadows: params.cascadedShadows,
|
|
736
|
+
shadowDistance: params.shadowDistance,
|
|
737
|
+
contactShadows: params.contactShadows,
|
|
738
|
+
rayTracedShadows: params.rayTracedShadows,
|
|
739
|
+
virtualShadowMaps: params.rayTracedShadows // Map to VSM for C++ handler
|
|
740
|
+
});
|
|
741
|
+
if (response.success) return { success: true, message: 'Shadow settings configured via bridge', ...(response.result || {}) };
|
|
742
|
+
} catch (_e) {
|
|
743
|
+
// Fallback
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
830
747
|
const commands = [];
|
|
831
|
-
|
|
748
|
+
|
|
832
749
|
if (params.shadowQuality) {
|
|
833
750
|
const qualityMap = { 'Low': 0, 'Medium': 1, 'High': 2, 'Epic': 3 };
|
|
834
751
|
commands.push(`r.ShadowQuality ${qualityMap[params.shadowQuality]}`);
|
|
835
752
|
}
|
|
836
|
-
|
|
753
|
+
|
|
837
754
|
if (params.cascadedShadows !== undefined) {
|
|
838
755
|
commands.push(`r.Shadow.CSM.MaxCascades ${params.cascadedShadows ? 4 : 1}`);
|
|
839
756
|
}
|
|
840
|
-
|
|
757
|
+
|
|
841
758
|
if (params.shadowDistance !== undefined) {
|
|
842
759
|
commands.push(`r.Shadow.DistanceScale ${params.shadowDistance}`);
|
|
843
760
|
}
|
|
844
|
-
|
|
761
|
+
|
|
845
762
|
if (params.contactShadows !== undefined) {
|
|
846
763
|
commands.push(`r.ContactShadows ${params.contactShadows ? 1 : 0}`);
|
|
847
764
|
}
|
|
848
|
-
|
|
765
|
+
|
|
849
766
|
if (params.rayTracedShadows !== undefined) {
|
|
850
767
|
commands.push(`r.RayTracing.Shadows ${params.rayTracedShadows ? 1 : 0}`);
|
|
851
768
|
}
|
|
852
|
-
|
|
769
|
+
|
|
853
770
|
for (const cmd of commands) {
|
|
854
771
|
await this.bridge.executeConsoleCommand(cmd);
|
|
855
772
|
}
|
|
856
|
-
|
|
857
|
-
return { success: true, message: 'Shadow settings configured' };
|
|
773
|
+
|
|
774
|
+
return { success: true, message: 'Shadow settings configured (console)' };
|
|
858
775
|
}
|
|
859
776
|
|
|
860
|
-
// Build lighting
|
|
777
|
+
// Build lighting
|
|
861
778
|
async buildLighting(params: {
|
|
862
779
|
quality?: 'Preview' | 'Medium' | 'High' | 'Production';
|
|
863
|
-
buildOnlySelected?: boolean;
|
|
780
|
+
buildOnlySelected?: boolean;
|
|
864
781
|
buildReflectionCaptures?: boolean;
|
|
782
|
+
levelPath?: string;
|
|
865
783
|
}) {
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
'High': 'QUALITY_HIGH',
|
|
871
|
-
'Production': 'QUALITY_PRODUCTION'
|
|
872
|
-
};
|
|
873
|
-
const qualityEnum = qualityMap[q] || 'QUALITY_HIGH';
|
|
874
|
-
|
|
875
|
-
// First try to ensure precomputed lighting is allowed and force-no-precomputed is disabled, then save changes
|
|
876
|
-
const disablePrecomputedPy = `
|
|
877
|
-
import unreal, json
|
|
878
|
-
|
|
879
|
-
messages = []
|
|
880
|
-
|
|
881
|
-
# Precheck: verify project supports static lighting (Support Static Lighting)
|
|
882
|
-
try:
|
|
883
|
-
rs = unreal.get_default_object(unreal.RendererSettings)
|
|
884
|
-
support_static = False
|
|
885
|
-
try:
|
|
886
|
-
support_static = bool(rs.get_editor_property('bSupportStaticLighting'))
|
|
887
|
-
except Exception:
|
|
888
|
-
try:
|
|
889
|
-
support_static = bool(rs.get_editor_property('support_static_lighting'))
|
|
890
|
-
except Exception:
|
|
891
|
-
support_static = False
|
|
892
|
-
if not support_static:
|
|
893
|
-
print('RESULT:' + json.dumps({
|
|
894
|
-
'success': False,
|
|
895
|
-
'status': 'staticDisabled',
|
|
896
|
-
'error': 'Project has Support Static Lighting disabled (r.AllowStaticLighting=0). Enable Project Settings -> Rendering -> Support Static Lighting and restart the editor.'
|
|
897
|
-
}))
|
|
898
|
-
raise SystemExit(0)
|
|
899
|
-
else:
|
|
900
|
-
messages.append('Support Static Lighting is enabled')
|
|
901
|
-
except Exception as e:
|
|
902
|
-
messages.append(f'Precheck failed: {e}')
|
|
903
|
-
|
|
904
|
-
# Ensure runtime CVar does not force disable precomputed lighting
|
|
905
|
-
try:
|
|
906
|
-
unreal.SystemLibrary.execute_console_command(None, 'r.ForceNoPrecomputedLighting 0')
|
|
907
|
-
messages.append('Set r.ForceNoPrecomputedLighting 0')
|
|
908
|
-
except Exception as e:
|
|
909
|
-
messages.append(f'r.ForceNoPrecomputedLighting failed: {e}')
|
|
910
|
-
|
|
911
|
-
# Temporarily disable source control prompts to avoid checkout dialogs during automated saves
|
|
912
|
-
try:
|
|
913
|
-
prefs = unreal.SourceControlPreferences()
|
|
914
|
-
try:
|
|
915
|
-
prefs.set_enable_source_control(False)
|
|
916
|
-
except Exception:
|
|
917
|
-
try:
|
|
918
|
-
prefs.enable_source_control = False
|
|
919
|
-
except Exception:
|
|
920
|
-
pass
|
|
921
|
-
messages.append('Disabled Source Control for this session')
|
|
922
|
-
except Exception as e:
|
|
923
|
-
messages.append(f'SourceControlPreferences modify failed: {e}')
|
|
924
|
-
|
|
925
|
-
try:
|
|
926
|
-
ues = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
|
|
927
|
-
les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
|
|
928
|
-
world = ues.get_editor_world() if ues else None
|
|
929
|
-
|
|
930
|
-
if world:
|
|
931
|
-
world_settings = world.get_world_settings()
|
|
932
|
-
if world_settings:
|
|
933
|
-
# Mark for modification
|
|
934
|
-
try:
|
|
935
|
-
world_settings.modify()
|
|
936
|
-
except Exception:
|
|
937
|
-
pass
|
|
938
|
-
|
|
939
|
-
# Try all known variants of the property name
|
|
940
|
-
for prop in ['force_no_precomputed_lighting', 'bForceNoPrecomputedLighting']:
|
|
941
|
-
try:
|
|
942
|
-
world_settings.set_editor_property(prop, False)
|
|
943
|
-
messages.append(f"Set WorldSettings.{prop}=False")
|
|
944
|
-
except Exception as e:
|
|
945
|
-
messages.append(f"Failed setting {prop}: {e}")
|
|
946
|
-
|
|
947
|
-
# Also update the Class Default Object (CDO) to help persistence in some versions
|
|
948
|
-
try:
|
|
949
|
-
ws_class = world_settings.get_class()
|
|
950
|
-
ws_cdo = unreal.get_default_object(ws_class)
|
|
951
|
-
if ws_cdo:
|
|
952
|
-
try:
|
|
953
|
-
ws_cdo.set_editor_property('bForceNoPrecomputedLighting', False)
|
|
954
|
-
messages.append('Set CDO bForceNoPrecomputedLighting=False')
|
|
955
|
-
except Exception:
|
|
956
|
-
pass
|
|
957
|
-
try:
|
|
958
|
-
ws_cdo.set_editor_property('force_no_precomputed_lighting', False)
|
|
959
|
-
messages.append('Set CDO force_no_precomputed_lighting=False')
|
|
960
|
-
except Exception:
|
|
961
|
-
pass
|
|
962
|
-
except Exception as e:
|
|
963
|
-
messages.append(f'CDO update failed: {e}')
|
|
964
|
-
|
|
965
|
-
# Apply and save level to persist change
|
|
966
|
-
try:
|
|
967
|
-
if hasattr(world_settings, 'post_edit_change'):
|
|
968
|
-
world_settings.post_edit_change()
|
|
969
|
-
except Exception:
|
|
970
|
-
pass
|
|
971
|
-
|
|
972
|
-
# Save current level/package
|
|
973
|
-
try:
|
|
974
|
-
wp = world.get_path_name()
|
|
975
|
-
pkg_path = wp.split('.')[0] if '.' in wp else wp
|
|
976
|
-
unreal.EditorAssetLibrary.save_asset(pkg_path)
|
|
977
|
-
messages.append(f'Saved world asset: {pkg_path}')
|
|
978
|
-
except Exception as e:
|
|
979
|
-
messages.append(f'Failed to save world asset: {e}')
|
|
980
|
-
|
|
981
|
-
# Secondary save method
|
|
982
|
-
try:
|
|
983
|
-
if les:
|
|
984
|
-
les.save_current_level()
|
|
985
|
-
messages.append('LevelEditorSubsystem.save_current_level called')
|
|
986
|
-
except Exception as e:
|
|
987
|
-
messages.append(f'save_current_level failed: {e}')
|
|
988
|
-
|
|
989
|
-
# Verify final value(s)
|
|
990
|
-
try:
|
|
991
|
-
force_val = None
|
|
992
|
-
bforce_val = None
|
|
993
|
-
try:
|
|
994
|
-
force_val = bool(world_settings.get_editor_property('force_no_precomputed_lighting'))
|
|
995
|
-
except Exception:
|
|
996
|
-
pass
|
|
997
|
-
try:
|
|
998
|
-
bforce_val = bool(world_settings.get_editor_property('bForceNoPrecomputedLighting'))
|
|
999
|
-
except Exception:
|
|
1000
|
-
pass
|
|
1001
|
-
messages.append(f'Verify WorldSettings.force_no_precomputed_lighting={force_val}')
|
|
1002
|
-
messages.append(f'Verify WorldSettings.bForceNoPrecomputedLighting={bforce_val}')
|
|
1003
|
-
except Exception as e:
|
|
1004
|
-
messages.append(f'Verify failed: {e}')
|
|
1005
|
-
except Exception as e:
|
|
1006
|
-
messages.append(f'World modification failed: {e}')
|
|
1007
|
-
|
|
1008
|
-
print('RESULT:' + json.dumps({'success': True, 'messages': messages, 'flags': {
|
|
1009
|
-
'force_no_precomputed_lighting': force_val if 'force_val' in locals() else None,
|
|
1010
|
-
'bForceNoPrecomputedLighting': bforce_val if 'bforce_val' in locals() else None
|
|
1011
|
-
}}))
|
|
1012
|
-
`.trim();
|
|
1013
|
-
|
|
1014
|
-
// Execute the disable script first and parse messages for diagnostics
|
|
1015
|
-
const preResp = await this.bridge.executePython(disablePrecomputedPy);
|
|
784
|
+
if (!this.automationBridge) {
|
|
785
|
+
throw new Error('Automation Bridge required for lighting build');
|
|
786
|
+
}
|
|
787
|
+
|
|
1016
788
|
try {
|
|
1017
|
-
const
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
} as any;
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
} catch {}
|
|
789
|
+
const response = await this.automationBridge.sendAutomationRequest('bake_lightmap', {
|
|
790
|
+
quality: params.quality || 'High',
|
|
791
|
+
buildOnlySelected: params.buildOnlySelected || false,
|
|
792
|
+
buildReflectionCaptures: params.buildReflectionCaptures !== false,
|
|
793
|
+
levelPath: params.levelPath
|
|
794
|
+
}, {
|
|
795
|
+
timeoutMs: 300000 // 5 minutes for lighting builds
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
if (response.success === false) {
|
|
799
|
+
return {
|
|
800
|
+
success: false,
|
|
801
|
+
error: response.error || response.message || 'Failed to build lighting'
|
|
802
|
+
};
|
|
1035
803
|
}
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
if les:
|
|
1049
|
-
# Build light maps with specified quality and reflection captures option
|
|
1050
|
-
les.build_light_maps(unreal.LightingBuildQuality.${qualityEnum}, ${params.buildReflectionCaptures !== false ? 'True' : 'False'})
|
|
1051
|
-
print('RESULT:' + json.dumps({'success': True, 'message': 'Lighting build started via LevelEditorSubsystem'}))
|
|
1052
|
-
else:
|
|
1053
|
-
# Fallback: Try using console command if subsystem not available
|
|
1054
|
-
try:
|
|
1055
|
-
unreal.SystemLibrary.execute_console_command(None, 'BuildLighting Quality=${q}')
|
|
1056
|
-
${params.buildReflectionCaptures ? "unreal.SystemLibrary.execute_console_command(None, 'BuildReflectionCaptures')" : ''}
|
|
1057
|
-
print('RESULT:' + json.dumps({'success': True, 'message': 'Lighting build started via console command (fallback)'}))
|
|
1058
|
-
except Exception as e2:
|
|
1059
|
-
print('RESULT:' + json.dumps({'success': False, 'error': f'Build failed: {str(e2)}'}))
|
|
1060
|
-
except Exception as e:
|
|
1061
|
-
print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
|
|
1062
|
-
`.trim();
|
|
1063
|
-
const resp = await this.bridge.executePython(py);
|
|
1064
|
-
const out = typeof resp === 'string' ? resp : JSON.stringify(resp);
|
|
1065
|
-
const m = out.match(/RESULT:({.*})/);
|
|
1066
|
-
if (m) { try { const parsed = JSON.parse(m[1]); return parsed.success ? { success: true, message: parsed.message } : { success: false, error: parsed.error }; } catch {} }
|
|
1067
|
-
return { success: true, message: 'Lighting build started' };
|
|
804
|
+
|
|
805
|
+
return {
|
|
806
|
+
success: true,
|
|
807
|
+
message: response.message || 'Lighting build started',
|
|
808
|
+
...(response.result || {})
|
|
809
|
+
} as any;
|
|
810
|
+
} catch (error) {
|
|
811
|
+
return {
|
|
812
|
+
success: false,
|
|
813
|
+
error: `Failed to build lighting: ${error instanceof Error ? error.message : String(error)}`
|
|
814
|
+
} as any;
|
|
815
|
+
}
|
|
1068
816
|
}
|
|
1069
817
|
|
|
1070
|
-
// Create a new level with proper lighting settings
|
|
818
|
+
// Create a new level with proper lighting settings
|
|
1071
819
|
async createLightingEnabledLevel(params?: {
|
|
1072
820
|
levelName?: string;
|
|
1073
821
|
copyActors?: boolean;
|
|
1074
822
|
useTemplate?: boolean;
|
|
1075
823
|
}) {
|
|
1076
824
|
const levelName = params?.levelName || 'LightingEnabledLevel';
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
actors_to_copy = []
|
|
1094
|
-
if ${params?.copyActors ? 'True' : 'False'}:
|
|
1095
|
-
current_world = ues.get_editor_world()
|
|
1096
|
-
if current_world:
|
|
1097
|
-
all_actors = actor_sub.get_all_level_actors()
|
|
1098
|
-
# Filter out unnecessary actors - only copy static meshes and important gameplay actors
|
|
1099
|
-
for actor in all_actors:
|
|
1100
|
-
if actor:
|
|
1101
|
-
class_name = actor.get_class().get_name()
|
|
1102
|
-
# Only copy specific actor types
|
|
1103
|
-
if class_name in ['StaticMeshActor', 'SkeletalMeshActor', 'Blueprint', 'Actor']:
|
|
1104
|
-
try:
|
|
1105
|
-
actor_data = {
|
|
1106
|
-
'class': actor.get_class(),
|
|
1107
|
-
'location': actor.get_actor_location(),
|
|
1108
|
-
'rotation': actor.get_actor_rotation(),
|
|
1109
|
-
'scale': actor.get_actor_scale3d(),
|
|
1110
|
-
'label': actor.get_actor_label()
|
|
1111
|
-
}
|
|
1112
|
-
# Check if actor has a static mesh component
|
|
1113
|
-
mesh_comp = actor.get_component_by_class(unreal.StaticMeshComponent)
|
|
1114
|
-
if mesh_comp:
|
|
1115
|
-
mesh = mesh_comp.get_editor_property('static_mesh')
|
|
1116
|
-
if mesh:
|
|
1117
|
-
actor_data['mesh'] = mesh
|
|
1118
|
-
actors_to_copy.append(actor_data)
|
|
1119
|
-
except:
|
|
1120
|
-
pass
|
|
1121
|
-
print(f'Stored {len(actors_to_copy)} actors to copy')
|
|
1122
|
-
|
|
1123
|
-
# Create new level with proper template or blank
|
|
1124
|
-
level_name_str = "${levelName}"
|
|
1125
|
-
level_path = f'/Game/Maps/{level_name_str}'
|
|
1126
|
-
|
|
1127
|
-
# Try different approaches to create a level with lighting enabled
|
|
1128
|
-
level_created = False
|
|
1129
|
-
|
|
1130
|
-
# Method 1: Try using the Default template (not Blank)
|
|
1131
|
-
try:
|
|
1132
|
-
# The Default template should have lighting enabled
|
|
1133
|
-
template_path = '/Engine/Maps/Templates/Template_Default'
|
|
1134
|
-
if editor_asset.does_asset_exist(template_path):
|
|
1135
|
-
les.new_level_from_template(level_path, template_path)
|
|
1136
|
-
print(f'Created level from Default template: {level_path}')
|
|
1137
|
-
level_created = True
|
|
1138
|
-
except:
|
|
1139
|
-
pass
|
|
1140
|
-
|
|
1141
|
-
# Method 2: Try TimeOfDay template
|
|
1142
|
-
if not level_created:
|
|
1143
|
-
try:
|
|
1144
|
-
template_path = '/Engine/Maps/Templates/TimeOfDay'
|
|
1145
|
-
if editor_asset.does_asset_exist(template_path):
|
|
1146
|
-
les.new_level_from_template(level_path, template_path)
|
|
1147
|
-
print(f'Created level from TimeOfDay template: {level_path}')
|
|
1148
|
-
level_created = True
|
|
1149
|
-
except:
|
|
1150
|
-
pass
|
|
1151
|
-
|
|
1152
|
-
# Method 3: Create blank and manually configure
|
|
1153
|
-
if not level_created:
|
|
1154
|
-
les.new_level(level_path, False)
|
|
1155
|
-
print(f'Created new blank level: {level_path}')
|
|
1156
|
-
level_created = True
|
|
1157
|
-
|
|
1158
|
-
# CRITICAL: Force disable ForceNoPrecomputedLighting using all possible methods
|
|
1159
|
-
new_world = ues.get_editor_world()
|
|
1160
|
-
if new_world:
|
|
1161
|
-
new_ws = new_world.get_world_settings()
|
|
1162
|
-
if new_ws:
|
|
1163
|
-
# Method 1: Direct property modification
|
|
1164
|
-
for prop in ['force_no_precomputed_lighting', 'bForceNoPrecomputedLighting',
|
|
1165
|
-
'ForceNoPrecomputedLighting', 'bforce_no_precomputed_lighting']:
|
|
1166
|
-
try:
|
|
1167
|
-
new_ws.set_editor_property(prop, False)
|
|
1168
|
-
except:
|
|
1169
|
-
pass
|
|
1170
|
-
|
|
1171
|
-
# Method 2: Modify via reflection
|
|
1172
|
-
try:
|
|
1173
|
-
# Access the property through the class default object
|
|
1174
|
-
ws_class = new_ws.get_class()
|
|
1175
|
-
ws_cdo = unreal.get_default_object(ws_class)
|
|
1176
|
-
if ws_cdo:
|
|
1177
|
-
ws_cdo.set_editor_property('force_no_precomputed_lighting', False)
|
|
1178
|
-
ws_cdo.set_editor_property('bForceNoPrecomputedLighting', False)
|
|
1179
|
-
except:
|
|
1180
|
-
pass
|
|
1181
|
-
|
|
1182
|
-
# Method 3: Override with Lightmass settings
|
|
1183
|
-
try:
|
|
1184
|
-
# Create proper Lightmass settings
|
|
1185
|
-
lightmass_settings = unreal.LightmassWorldInfoSettings()
|
|
1186
|
-
lightmass_settings.static_lighting_level_scale = 1.0
|
|
1187
|
-
lightmass_settings.num_indirect_lighting_bounces = 3
|
|
1188
|
-
lightmass_settings.use_ambient_occlusion = True
|
|
1189
|
-
lightmass_settings.generate_ambient_occlusion_material_mask = False
|
|
1190
|
-
|
|
1191
|
-
new_ws.set_editor_property('lightmass_settings', lightmass_settings)
|
|
1192
|
-
except:
|
|
1193
|
-
pass
|
|
1194
|
-
|
|
1195
|
-
# Method 4: Force save and reload to apply changes
|
|
1196
|
-
try:
|
|
1197
|
-
# Mark the world settings as dirty
|
|
1198
|
-
new_ws.modify()
|
|
1199
|
-
# Save immediately
|
|
1200
|
-
les.save_current_level()
|
|
1201
|
-
# Force update
|
|
1202
|
-
new_world.force_update_level_bounds()
|
|
1203
|
-
except:
|
|
1204
|
-
pass
|
|
1205
|
-
|
|
1206
|
-
# Verify the setting
|
|
1207
|
-
try:
|
|
1208
|
-
val = new_ws.get_editor_property('force_no_precomputed_lighting')
|
|
1209
|
-
print(f'New level force_no_precomputed_lighting: {val}')
|
|
1210
|
-
if val:
|
|
1211
|
-
print('WARNING: ForceNoPrecomputedLighting is persistent - project setting override detected')
|
|
1212
|
-
print('WORKAROUND: Will use dynamic lighting only')
|
|
1213
|
-
except:
|
|
1214
|
-
pass
|
|
1215
|
-
|
|
1216
|
-
# Copy actors if requested
|
|
1217
|
-
if actors_to_copy and actor_sub:
|
|
1218
|
-
print('Copying actors to new level...')
|
|
1219
|
-
copied = 0
|
|
1220
|
-
for actor_data in actors_to_copy:
|
|
1221
|
-
try:
|
|
1222
|
-
# Spawn a static mesh actor if we have mesh data
|
|
1223
|
-
if 'mesh' in actor_data:
|
|
1224
|
-
# Create a proper static mesh actor
|
|
1225
|
-
spawned = actor_sub.spawn_actor_from_class(
|
|
1226
|
-
unreal.StaticMeshActor,
|
|
1227
|
-
actor_data['location'],
|
|
1228
|
-
actor_data['rotation']
|
|
1229
|
-
)
|
|
1230
|
-
if spawned:
|
|
1231
|
-
spawned.set_actor_scale3d(actor_data['scale'])
|
|
1232
|
-
spawned.set_actor_label(actor_data['label'])
|
|
1233
|
-
# Set the static mesh
|
|
1234
|
-
mesh_comp = spawned.get_component_by_class(unreal.StaticMeshComponent)
|
|
1235
|
-
if mesh_comp:
|
|
1236
|
-
mesh_comp.set_static_mesh(actor_data['mesh'])
|
|
1237
|
-
copied += 1
|
|
1238
|
-
else:
|
|
1239
|
-
# Spawn regular actor
|
|
1240
|
-
spawned = actor_sub.spawn_actor_from_class(
|
|
1241
|
-
actor_data['class'],
|
|
1242
|
-
actor_data['location'],
|
|
1243
|
-
actor_data['rotation']
|
|
1244
|
-
)
|
|
1245
|
-
if spawned:
|
|
1246
|
-
spawned.set_actor_scale3d(actor_data['scale'])
|
|
1247
|
-
spawned.set_actor_label(actor_data['label'])
|
|
1248
|
-
copied += 1
|
|
1249
|
-
except Exception as e:
|
|
1250
|
-
pass # Silently skip failed copies
|
|
1251
|
-
print(f'Successfully copied {copied} actors')
|
|
1252
|
-
|
|
1253
|
-
# Add essential lighting actors if not using template
|
|
1254
|
-
if not use_template:
|
|
1255
|
-
# Add a directional light for sun
|
|
1256
|
-
light = actor_sub.spawn_actor_from_class(
|
|
1257
|
-
unreal.DirectionalLight,
|
|
1258
|
-
unreal.Vector(0, 0, 500),
|
|
1259
|
-
unreal.Rotator(-45, 45, 0)
|
|
1260
|
-
)
|
|
1261
|
-
if light:
|
|
1262
|
-
light.set_actor_label('Sun_Light')
|
|
1263
|
-
light_comp = light.get_component_by_class(unreal.DirectionalLightComponent)
|
|
1264
|
-
if light_comp:
|
|
1265
|
-
light_comp.set_intensity(3.14159) # Pi lux for realistic sun
|
|
1266
|
-
light_comp.set_light_color(unreal.LinearColor(1, 0.95, 0.8, 1))
|
|
1267
|
-
print('Added directional light')
|
|
1268
|
-
|
|
1269
|
-
# Add sky light for ambient
|
|
1270
|
-
sky = actor_sub.spawn_actor_from_class(
|
|
1271
|
-
unreal.SkyLight,
|
|
1272
|
-
unreal.Vector(0, 0, 300),
|
|
1273
|
-
unreal.Rotator(0, 0, 0)
|
|
1274
|
-
)
|
|
1275
|
-
if sky:
|
|
1276
|
-
sky.set_actor_label('Sky_Light')
|
|
1277
|
-
sky_comp = sky.get_component_by_class(unreal.SkyLightComponent)
|
|
1278
|
-
if sky_comp:
|
|
1279
|
-
sky_comp.set_intensity(1.0)
|
|
1280
|
-
print('Added sky light')
|
|
1281
|
-
|
|
1282
|
-
# Add sky atmosphere for realistic sky
|
|
1283
|
-
atmosphere = actor_sub.spawn_actor_from_class(
|
|
1284
|
-
unreal.SkyAtmosphere,
|
|
1285
|
-
unreal.Vector(0, 0, 0),
|
|
1286
|
-
unreal.Rotator(0, 0, 0)
|
|
1287
|
-
)
|
|
1288
|
-
if atmosphere:
|
|
1289
|
-
atmosphere.set_actor_label('Sky_Atmosphere')
|
|
1290
|
-
print('Added sky atmosphere')
|
|
1291
|
-
|
|
1292
|
-
# Save the new level
|
|
1293
|
-
les.save_current_level()
|
|
1294
|
-
print('New level saved')
|
|
1295
|
-
|
|
825
|
+
|
|
826
|
+
if (!this.automationBridge) {
|
|
827
|
+
throw new Error('Automation Bridge not available. Level creation requires plugin support.');
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
try {
|
|
831
|
+
const response = await this.automationBridge.sendAutomationRequest('create_lighting_enabled_level', {
|
|
832
|
+
levelName,
|
|
833
|
+
copyActors: params?.copyActors === true,
|
|
834
|
+
useTemplate: params?.useTemplate === true,
|
|
835
|
+
path: params?.levelName ? `/Game/Maps/${params.levelName}` : undefined // Ensure path is sent
|
|
836
|
+
}, {
|
|
837
|
+
timeoutMs: 120000 // 2 minutes for level creation
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
if (response.success === false) {
|
|
1296
841
|
return {
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
result
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
try {
|
|
1314
|
-
const parsed = JSON.parse(m[1]);
|
|
1315
|
-
return parsed;
|
|
1316
|
-
} catch {}
|
|
1317
|
-
}
|
|
1318
|
-
return { success: true, message: 'New level creation attempted' };
|
|
842
|
+
success: false,
|
|
843
|
+
error: response.error || response.message || 'Failed to create level'
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
return {
|
|
848
|
+
success: true,
|
|
849
|
+
message: response.message || `Created new level "${levelName}" with lighting enabled`,
|
|
850
|
+
...(response.result || {})
|
|
851
|
+
};
|
|
852
|
+
} catch (error) {
|
|
853
|
+
return {
|
|
854
|
+
success: false,
|
|
855
|
+
error: `Failed to create lighting-enabled level: ${error}`
|
|
856
|
+
};
|
|
857
|
+
}
|
|
1319
858
|
}
|
|
1320
859
|
|
|
1321
|
-
// Create lightmass importance volume
|
|
860
|
+
// Create lightmass importance volume
|
|
1322
861
|
async createLightmassVolume(params: {
|
|
1323
862
|
name: string;
|
|
1324
863
|
location: [number, number, number];
|
|
1325
864
|
size: [number, number, number];
|
|
1326
865
|
}) {
|
|
1327
866
|
const name = this.normalizeName(params.name);
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
867
|
+
|
|
868
|
+
if (!this.automationBridge) {
|
|
869
|
+
throw new Error('Automation Bridge not available. Lightmass volume creation requires plugin support.');
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
try {
|
|
873
|
+
const response = await this.automationBridge.sendAutomationRequest('create_lightmass_volume', {
|
|
874
|
+
name,
|
|
875
|
+
location: { x: params.location[0], y: params.location[1], z: params.location[2] },
|
|
876
|
+
size: { x: params.size[0], y: params.size[1], z: params.size[2] }
|
|
877
|
+
}, {
|
|
878
|
+
timeoutMs: 60000
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
if (response.success === false) {
|
|
882
|
+
return {
|
|
883
|
+
success: false,
|
|
884
|
+
error: response.error || response.message || 'Failed to create lightmass volume'
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
return {
|
|
889
|
+
success: true,
|
|
890
|
+
message: `LightmassImportanceVolume '${name}' created`,
|
|
891
|
+
...(response.result || {})
|
|
892
|
+
};
|
|
893
|
+
} catch (error) {
|
|
894
|
+
return {
|
|
895
|
+
success: false,
|
|
896
|
+
error: `Failed to create lightmass volume: ${error}`
|
|
897
|
+
};
|
|
898
|
+
}
|
|
1353
899
|
}
|
|
1354
900
|
|
|
1355
901
|
// Set exposure
|
|
@@ -1359,27 +905,41 @@ else:
|
|
|
1359
905
|
minBrightness?: number;
|
|
1360
906
|
maxBrightness?: number;
|
|
1361
907
|
}) {
|
|
908
|
+
if (this.automationBridge) {
|
|
909
|
+
try {
|
|
910
|
+
const response = await this.automationBridge.sendAutomationRequest('set_exposure', {
|
|
911
|
+
method: params.method,
|
|
912
|
+
compensationValue: params.compensationValue,
|
|
913
|
+
minBrightness: params.minBrightness,
|
|
914
|
+
maxBrightness: params.maxBrightness
|
|
915
|
+
});
|
|
916
|
+
if (response.success) return { success: true, message: 'Exposure settings updated via bridge', ...(response.result || {}) };
|
|
917
|
+
} catch (_e) {
|
|
918
|
+
// Fallback
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
1362
922
|
const commands = [];
|
|
1363
|
-
|
|
923
|
+
|
|
1364
924
|
commands.push(`r.EyeAdaptation.ExposureMethod ${params.method === 'Manual' ? 0 : 1}`);
|
|
1365
|
-
|
|
925
|
+
|
|
1366
926
|
if (params.compensationValue !== undefined) {
|
|
1367
927
|
commands.push(`r.EyeAdaptation.ExposureCompensation ${params.compensationValue}`);
|
|
1368
928
|
}
|
|
1369
|
-
|
|
929
|
+
|
|
1370
930
|
if (params.minBrightness !== undefined) {
|
|
1371
931
|
commands.push(`r.EyeAdaptation.MinBrightness ${params.minBrightness}`);
|
|
1372
932
|
}
|
|
1373
|
-
|
|
933
|
+
|
|
1374
934
|
if (params.maxBrightness !== undefined) {
|
|
1375
935
|
commands.push(`r.EyeAdaptation.MaxBrightness ${params.maxBrightness}`);
|
|
1376
936
|
}
|
|
1377
|
-
|
|
937
|
+
|
|
1378
938
|
for (const cmd of commands) {
|
|
1379
939
|
await this.bridge.executeConsoleCommand(cmd);
|
|
1380
940
|
}
|
|
1381
|
-
|
|
1382
|
-
return { success: true, message: 'Exposure settings updated' };
|
|
941
|
+
|
|
942
|
+
return { success: true, message: 'Exposure settings updated (console)' };
|
|
1383
943
|
}
|
|
1384
944
|
|
|
1385
945
|
// Set ambient occlusion
|
|
@@ -1389,31 +949,45 @@ else:
|
|
|
1389
949
|
radius?: number;
|
|
1390
950
|
quality?: 'Low' | 'Medium' | 'High';
|
|
1391
951
|
}) {
|
|
952
|
+
if (this.automationBridge) {
|
|
953
|
+
try {
|
|
954
|
+
const response = await this.automationBridge.sendAutomationRequest('set_ambient_occlusion', {
|
|
955
|
+
enabled: params.enabled,
|
|
956
|
+
intensity: params.intensity,
|
|
957
|
+
radius: params.radius,
|
|
958
|
+
quality: params.quality
|
|
959
|
+
});
|
|
960
|
+
if (response.success) return { success: true, message: 'Ambient occlusion configured via bridge', ...(response.result || {}) };
|
|
961
|
+
} catch (_e) {
|
|
962
|
+
// Fallback
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
|
|
1392
966
|
const commands = [];
|
|
1393
|
-
|
|
967
|
+
|
|
1394
968
|
commands.push(`r.AmbientOcclusion.Enabled ${params.enabled ? 1 : 0}`);
|
|
1395
|
-
|
|
969
|
+
|
|
1396
970
|
if (params.intensity !== undefined) {
|
|
1397
971
|
commands.push(`r.AmbientOcclusion.Intensity ${params.intensity}`);
|
|
1398
972
|
}
|
|
1399
|
-
|
|
973
|
+
|
|
1400
974
|
if (params.radius !== undefined) {
|
|
1401
975
|
commands.push(`r.AmbientOcclusion.Radius ${params.radius}`);
|
|
1402
976
|
}
|
|
1403
|
-
|
|
977
|
+
|
|
1404
978
|
if (params.quality) {
|
|
1405
979
|
const qualityMap = { 'Low': 0, 'Medium': 1, 'High': 2 };
|
|
1406
980
|
commands.push(`r.AmbientOcclusion.Quality ${qualityMap[params.quality]}`);
|
|
1407
981
|
}
|
|
1408
|
-
|
|
982
|
+
|
|
1409
983
|
for (const cmd of commands) {
|
|
1410
984
|
await this.bridge.executeConsoleCommand(cmd);
|
|
1411
985
|
}
|
|
1412
|
-
|
|
1413
|
-
return { success: true, message: 'Ambient occlusion configured' };
|
|
986
|
+
|
|
987
|
+
return { success: true, message: 'Ambient occlusion configured (console)' };
|
|
1414
988
|
}
|
|
1415
989
|
|
|
1416
|
-
// Setup volumetric fog
|
|
990
|
+
// Setup volumetric fog
|
|
1417
991
|
async setupVolumetricFog(params: {
|
|
1418
992
|
enabled: boolean;
|
|
1419
993
|
density?: number;
|
|
@@ -1423,15 +997,40 @@ else:
|
|
|
1423
997
|
// Enable/disable global volumetric fog via CVar
|
|
1424
998
|
await this.bridge.executeConsoleCommand(`r.VolumetricFog ${params.enabled ? 1 : 0}`);
|
|
1425
999
|
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1000
|
+
if (!this.automationBridge) {
|
|
1001
|
+
return {
|
|
1002
|
+
success: true,
|
|
1003
|
+
message: 'Volumetric fog console setting applied (plugin required for fog actor adjustment)'
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
try {
|
|
1008
|
+
const response = await this.automationBridge.sendAutomationRequest('setup_volumetric_fog', {
|
|
1009
|
+
enabled: params.enabled,
|
|
1010
|
+
density: params.density,
|
|
1011
|
+
scatteringIntensity: params.scatteringIntensity,
|
|
1012
|
+
fogHeight: params.fogHeight
|
|
1013
|
+
}, {
|
|
1014
|
+
timeoutMs: 60000
|
|
1015
|
+
});
|
|
1016
|
+
|
|
1017
|
+
if (response.success === false) {
|
|
1018
|
+
return {
|
|
1019
|
+
success: false,
|
|
1020
|
+
error: response.error || response.message || 'Failed to configure volumetric fog'
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1430
1023
|
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1024
|
+
return {
|
|
1025
|
+
success: true,
|
|
1026
|
+
message: 'Volumetric fog configured',
|
|
1027
|
+
...(response.result || {})
|
|
1028
|
+
};
|
|
1029
|
+
} catch (error) {
|
|
1030
|
+
return {
|
|
1031
|
+
success: false,
|
|
1032
|
+
error: `Failed to setup volumetric fog: ${error}`
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1436
1035
|
}
|
|
1437
1036
|
}
|