unreal-engine-mcp-server 0.4.7 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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-config.yml +51 -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 +27 -0
- package/.github/workflows/labeler.yml +17 -0
- package/.github/workflows/links.yml +80 -0
- package/.github/workflows/pr-size-labeler.yml +137 -0
- package/.github/workflows/publish-mcp.yml +13 -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 +338 -31
- package/CONTRIBUTING.md +140 -0
- package/GEMINI.md +115 -0
- package/Public/Plugin_setup_guide.mp4 +0 -0
- package/README.md +189 -128
- 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 +26 -0
- package/dist/config.js +59 -0
- package/dist/constants.d.ts +16 -0
- package/dist/constants.js +16 -0
- package/dist/graphql/loaders.d.ts +64 -0
- package/dist/graphql/loaders.js +117 -0
- package/dist/graphql/resolvers.d.ts +268 -0
- package/dist/graphql/resolvers.js +746 -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 +117 -0
- package/dist/graphql/types.d.ts +9 -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 +33 -18
- package/dist/index.js +130 -619
- 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 +20 -0
- package/dist/server-setup.js +71 -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 +163 -9
- package/dist/tools/actors.js +356 -311
- package/dist/tools/animation.d.ts +135 -4
- package/dist/tools/animation.js +510 -411
- package/dist/tools/assets.d.ts +75 -29
- package/dist/tools/assets.js +265 -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.d.ts +208 -126
- package/dist/tools/blueprint.js +685 -832
- package/dist/tools/consolidated-tool-definitions.d.ts +5462 -1781
- package/dist/tools/consolidated-tool-definitions.js +829 -496
- package/dist/tools/consolidated-tool-handlers.d.ts +2 -1
- package/dist/tools/consolidated-tool-handlers.js +198 -1027
- package/dist/tools/debug.d.ts +143 -85
- package/dist/tools/debug.js +234 -180
- package/dist/tools/dynamic-handler-registry.d.ts +13 -0
- package/dist/tools/dynamic-handler-registry.js +23 -0
- package/dist/tools/editor.d.ts +30 -83
- package/dist/tools/editor.js +247 -244
- package/dist/tools/engine.d.ts +10 -4
- package/dist/tools/engine.js +13 -5
- package/dist/tools/environment.d.ts +30 -0
- package/dist/tools/environment.js +267 -0
- package/dist/tools/foliage.d.ts +65 -99
- package/dist/tools/foliage.js +221 -331
- package/dist/tools/handlers/actor-handlers.d.ts +3 -0
- package/dist/tools/handlers/actor-handlers.js +227 -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 +54 -93
- package/dist/tools/landscape.js +284 -409
- package/dist/tools/level.d.ts +66 -27
- package/dist/tools/level.js +647 -675
- package/dist/tools/lighting.d.ts +77 -38
- package/dist/tools/lighting.js +445 -943
- package/dist/tools/logs.d.ts +3 -3
- package/dist/tools/logs.js +5 -57
- package/dist/tools/materials.d.ts +91 -24
- package/dist/tools/materials.js +194 -118
- package/dist/tools/niagara.d.ts +149 -39
- package/dist/tools/niagara.js +267 -182
- package/dist/tools/performance.d.ts +27 -13
- package/dist/tools/performance.js +203 -122
- package/dist/tools/physics.d.ts +32 -77
- package/dist/tools/physics.js +175 -582
- package/dist/tools/property-dictionary.d.ts +13 -0
- package/dist/tools/property-dictionary.js +82 -0
- package/dist/tools/sequence.d.ts +85 -60
- package/dist/tools/sequence.js +208 -747
- 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 +64 -34
- package/dist/tools/ui.js +134 -214
- package/dist/types/automation-responses.d.ts +115 -0
- package/dist/types/automation-responses.js +2 -0
- package/dist/types/env.d.ts +0 -3
- package/dist/types/env.js +0 -7
- package/dist/types/responses.d.ts +249 -0
- package/dist/types/responses.js +2 -0
- package/dist/types/tool-interfaces.d.ts +898 -0
- package/dist/types/tool-interfaces.js +2 -0
- package/dist/types/tool-types.d.ts +183 -19
- 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 +68 -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/path-security.d.ts +2 -0
- package/dist/utils/path-security.js +24 -0
- package/dist/utils/response-factory.d.ts +7 -0
- package/dist/utils/response-factory.js +27 -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 +684 -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 +67 -28
- 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 +136 -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 +58 -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 +19 -0
- package/src/graphql/loaders.ts +244 -0
- package/src/graphql/resolvers.ts +1008 -0
- package/src/graphql/schema.ts +452 -0
- package/src/graphql/server.ts +156 -0
- package/src/graphql/types.ts +10 -0
- package/src/handlers/resource-handlers.ts +186 -0
- package/src/index.ts +166 -664
- package/src/resources/actors.ts +58 -76
- package/src/resources/assets.ts +148 -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 +114 -0
- package/src/services/health-monitor.ts +132 -0
- package/src/services/metrics-server.ts +142 -0
- package/src/tools/actors.ts +426 -323
- package/src/tools/animation.ts +672 -461
- package/src/tools/assets.ts +364 -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.ts +792 -970
- package/src/tools/consolidated-tool-definitions.ts +993 -515
- package/src/tools/consolidated-tool-handlers.ts +258 -1146
- package/src/tools/debug.ts +292 -187
- package/src/tools/dynamic-handler-registry.ts +33 -0
- package/src/tools/editor.ts +329 -253
- package/src/tools/engine.ts +14 -3
- package/src/tools/environment.ts +281 -0
- package/src/tools/foliage.ts +330 -392
- package/src/tools/handlers/actor-handlers.ts +265 -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 +418 -507
- package/src/tools/level.ts +786 -708
- package/src/tools/lighting.ts +588 -984
- package/src/tools/logs.ts +9 -57
- package/src/tools/materials.ts +237 -121
- package/src/tools/niagara.ts +335 -168
- package/src/tools/performance.ts +320 -169
- package/src/tools/physics.ts +274 -613
- package/src/tools/property-dictionary.ts +98 -0
- package/src/tools/sequence.ts +276 -820
- package/src/tools/tool-definition-utils.ts +35 -0
- package/src/tools/ui.ts +205 -283
- package/src/types/automation-responses.ts +119 -0
- package/src/types/env.ts +0 -10
- package/src/types/responses.ts +355 -0
- package/src/types/tool-interfaces.ts +250 -0
- package/src/types/tool-types.ts +243 -21
- package/src/unreal-bridge.ts +460 -1550
- package/src/utils/command-validator.ts +76 -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.test.ts +162 -0
- package/src/utils/normalize.ts +60 -0
- package/src/utils/path-security.ts +43 -0
- package/src/utils/response-factory.ts +44 -0
- package/src/utils/response-validator.ts +176 -56
- package/src/utils/result-helpers.ts +21 -19
- package/src/utils/safe-json.test.ts +90 -0
- package/src/utils/safe-json.ts +14 -11
- package/src/utils/unreal-command-queue.ts +152 -0
- package/src/utils/validation.test.ts +184 -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 +369 -0
- package/tests/test-asset-advanced.mjs +82 -0
- package/tests/test-asset-errors.mjs +35 -0
- package/tests/test-asset-graph.mjs +311 -0
- package/tests/test-audio.mjs +417 -0
- package/tests/test-automation-timeouts.mjs +98 -0
- package/tests/test-behavior-tree.mjs +444 -0
- package/tests/test-blueprint-graph.mjs +410 -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 +112 -0
- package/tests/test-graphql.mjs +372 -0
- package/tests/test-input.mjs +349 -0
- package/tests/test-inspect.mjs +302 -0
- package/tests/test-landscape.mjs +316 -0
- package/tests/test-lighting.mjs +428 -0
- package/tests/test-manage-asset.mjs +438 -0
- package/tests/test-manage-level.mjs +89 -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-performance.mjs +539 -0
- package/tests/test-plugin-handshake.mjs +82 -0
- package/tests/test-runner.mjs +933 -0
- package/tests/test-sequence.mjs +104 -0
- package/tests/test-system.mjs +96 -0
- package/tests/test-wasm.mjs +283 -0
- package/tests/test-world-partition.mjs +215 -0
- package/tsconfig.json +3 -3
- package/vitest.config.ts +35 -0
- 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/prompts/index.d.ts +0 -21
- package/dist/prompts/index.js +0 -217
- 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 -574
- package/smithery.yaml +0 -29
- package/src/prompts/index.ts +0 -249
- 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,16 @@
|
|
|
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
|
+
import { wasmIntegration } from '../wasm/index.js';
|
|
5
6
|
|
|
6
7
|
export class LightingTools {
|
|
7
|
-
constructor(private bridge: UnrealBridge) {}
|
|
8
|
+
constructor(private bridge: UnrealBridge, private automationBridge?: AutomationBridge) { }
|
|
8
9
|
|
|
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;
|
|
10
|
+
setAutomationBridge(automationBridge?: AutomationBridge) { this.automationBridge = automationBridge; }
|
|
25
11
|
|
|
26
|
-
// Fallback: if no ReturnValue but success-like logs exist, accept
|
|
27
|
-
if (/spawned/i.test(logs)) return;
|
|
28
12
|
|
|
29
|
-
|
|
30
|
-
throw new Error(`Uncertain spawn result for '${label}'. Engine logs:\n${logs}`);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
private normalizeName(value: unknown, fallback?: string): string {
|
|
13
|
+
private normalizeName(value: unknown, defaultName?: string): string {
|
|
34
14
|
if (typeof value === 'string') {
|
|
35
15
|
const trimmed = value.trim();
|
|
36
16
|
if (trimmed.length > 0) {
|
|
@@ -38,14 +18,75 @@ export class LightingTools {
|
|
|
38
18
|
}
|
|
39
19
|
}
|
|
40
20
|
|
|
41
|
-
if (typeof
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
44
|
-
return
|
|
21
|
+
if (typeof defaultName === 'string') {
|
|
22
|
+
const trimmedDefault = defaultName.trim();
|
|
23
|
+
if (trimmedDefault.length > 0) {
|
|
24
|
+
return trimmedDefault;
|
|
45
25
|
}
|
|
46
26
|
}
|
|
47
27
|
|
|
48
|
-
|
|
28
|
+
// Auto-generate if no name is provided
|
|
29
|
+
return `Light_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Spawn a light actor using the Automation Bridge.
|
|
34
|
+
* @param lightClass The Unreal light class name (e.g. 'DirectionalLight', 'PointLight')
|
|
35
|
+
* @param params Light spawn parameters
|
|
36
|
+
*/
|
|
37
|
+
private async spawnLightViaAutomation(
|
|
38
|
+
lightClass: string,
|
|
39
|
+
params: {
|
|
40
|
+
name: string;
|
|
41
|
+
location?: [number, number, number];
|
|
42
|
+
rotation?: [number, number, number] | { pitch: number, yaw: number, roll: number };
|
|
43
|
+
properties?: Record<string, any>;
|
|
44
|
+
}
|
|
45
|
+
) {
|
|
46
|
+
if (!this.automationBridge) {
|
|
47
|
+
throw new Error('Automation Bridge not available. Cannot spawn lights without plugin support.');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const payload: Record<string, any> = {
|
|
52
|
+
lightClass,
|
|
53
|
+
name: params.name,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
if (params.location) {
|
|
57
|
+
// Use WASM vectorAdd for light location processing
|
|
58
|
+
const zeroVector: [number, number, number] = [0, 0, 0];
|
|
59
|
+
const processedLocation = wasmIntegration.vectorAdd(zeroVector, params.location);
|
|
60
|
+
console.error('[WASM] Using vectorAdd for light positioning');
|
|
61
|
+
payload.location = { x: processedLocation[0], y: processedLocation[1], z: processedLocation[2] };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (params.rotation) {
|
|
65
|
+
if (Array.isArray(params.rotation)) {
|
|
66
|
+
payload.rotation = { pitch: params.rotation[0], yaw: params.rotation[1], roll: params.rotation[2] };
|
|
67
|
+
} else {
|
|
68
|
+
payload.rotation = params.rotation;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (params.properties) {
|
|
73
|
+
payload.properties = params.properties;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const response = await this.automationBridge.sendAutomationRequest('spawn_light', payload, {
|
|
77
|
+
timeoutMs: 60000
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (response.success === false) {
|
|
81
|
+
throw new Error(response.error || response.message || 'Failed to spawn light');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return response;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`Failed to spawn ${lightClass}: ${error instanceof Error ? error.message : String(error)}`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
49
90
|
}
|
|
50
91
|
|
|
51
92
|
// Create directional light
|
|
@@ -53,13 +94,17 @@ export class LightingTools {
|
|
|
53
94
|
name: string;
|
|
54
95
|
intensity?: number;
|
|
55
96
|
color?: [number, number, number];
|
|
56
|
-
rotation?: [number, number, number];
|
|
97
|
+
rotation?: [number, number, number] | { pitch: number, yaw: number, roll: number };
|
|
57
98
|
castShadows?: boolean;
|
|
58
99
|
temperature?: number;
|
|
100
|
+
useAsAtmosphereSunLight?: boolean;
|
|
101
|
+
properties?: Record<string, any>;
|
|
59
102
|
}) {
|
|
60
103
|
const name = this.normalizeName(params.name);
|
|
61
|
-
|
|
62
|
-
|
|
104
|
+
if (!this.automationBridge) {
|
|
105
|
+
throw new Error('Automation Bridge required for light spawning');
|
|
106
|
+
}
|
|
107
|
+
|
|
63
108
|
// Validate numeric parameters
|
|
64
109
|
if (params.intensity !== undefined) {
|
|
65
110
|
if (typeof params.intensity !== 'number' || !isFinite(params.intensity)) {
|
|
@@ -69,13 +114,13 @@ export class LightingTools {
|
|
|
69
114
|
throw new Error('Invalid intensity: must be non-negative');
|
|
70
115
|
}
|
|
71
116
|
}
|
|
72
|
-
|
|
117
|
+
|
|
73
118
|
if (params.temperature !== undefined) {
|
|
74
119
|
if (typeof params.temperature !== 'number' || !isFinite(params.temperature)) {
|
|
75
120
|
throw new Error(`Invalid temperature value: ${params.temperature}`);
|
|
76
121
|
}
|
|
77
122
|
}
|
|
78
|
-
|
|
123
|
+
|
|
79
124
|
// Validate arrays
|
|
80
125
|
if (params.color !== undefined) {
|
|
81
126
|
if (!Array.isArray(params.color) || params.color.length !== 3) {
|
|
@@ -87,77 +132,53 @@ export class LightingTools {
|
|
|
87
132
|
}
|
|
88
133
|
}
|
|
89
134
|
}
|
|
90
|
-
|
|
135
|
+
|
|
91
136
|
if (params.rotation !== undefined) {
|
|
92
|
-
if (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
137
|
+
if (Array.isArray(params.rotation)) {
|
|
138
|
+
if (params.rotation.length !== 3) {
|
|
139
|
+
throw new Error('Invalid rotation: must be an array [pitch,yaw,roll]');
|
|
140
|
+
}
|
|
141
|
+
for (const r of params.rotation) {
|
|
142
|
+
if (typeof r !== 'number' || !isFinite(r)) {
|
|
143
|
+
throw new Error('Invalid rotation component: must be finite numbers');
|
|
144
|
+
}
|
|
98
145
|
}
|
|
99
146
|
}
|
|
100
147
|
}
|
|
101
|
-
|
|
148
|
+
|
|
102
149
|
const rot = params.rotation || [0, 0, 0];
|
|
103
|
-
|
|
104
|
-
// Build
|
|
105
|
-
const
|
|
150
|
+
|
|
151
|
+
// Build properties for the light
|
|
152
|
+
const properties: Record<string, any> = params.properties || {};
|
|
106
153
|
if (params.intensity !== undefined) {
|
|
107
|
-
|
|
154
|
+
properties.intensity = params.intensity;
|
|
108
155
|
}
|
|
109
156
|
if (params.color) {
|
|
110
|
-
|
|
157
|
+
properties.color = { r: params.color[0], g: params.color[1], b: params.color[2], a: 1.0 };
|
|
111
158
|
}
|
|
112
159
|
if (params.castShadows !== undefined) {
|
|
113
|
-
|
|
160
|
+
properties.castShadows = params.castShadows;
|
|
114
161
|
}
|
|
115
162
|
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` };
|
|
163
|
+
properties.temperature = params.temperature;
|
|
164
|
+
}
|
|
165
|
+
if (params.useAsAtmosphereSunLight !== undefined) {
|
|
166
|
+
properties.useAsAtmosphereSunLight = params.useAsAtmosphereSunLight;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
await this.spawnLightViaAutomation('DirectionalLight', {
|
|
171
|
+
name,
|
|
172
|
+
location: [0, 0, 500],
|
|
173
|
+
rotation: rot,
|
|
174
|
+
properties
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
return { success: true, message: `Directional light '${name}' spawned` };
|
|
178
|
+
} catch (e: any) {
|
|
179
|
+
// Don't mask errors as "not implemented" - report the actual error from the bridge
|
|
180
|
+
return { success: false, error: `Failed to create directional light: ${e?.message ?? e}` } as any;
|
|
181
|
+
}
|
|
161
182
|
}
|
|
162
183
|
|
|
163
184
|
// Create point light
|
|
@@ -169,25 +190,27 @@ else:
|
|
|
169
190
|
color?: [number, number, number];
|
|
170
191
|
falloffExponent?: number;
|
|
171
192
|
castShadows?: boolean;
|
|
193
|
+
rotation?: [number, number, number] | { pitch: number, yaw: number, roll: number };
|
|
172
194
|
}) {
|
|
173
195
|
const name = this.normalizeName(params.name);
|
|
174
|
-
|
|
175
|
-
|
|
196
|
+
if (!this.automationBridge) {
|
|
197
|
+
throw new Error('Automation Bridge required for light spawning');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Validate location array
|
|
176
201
|
// Validate location array
|
|
177
202
|
if (params.location !== undefined) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
throw new Error('Invalid location component: must be finite numbers');
|
|
184
|
-
}
|
|
203
|
+
// Ensure location is valid array [x,y,z]
|
|
204
|
+
try {
|
|
205
|
+
params.location = ensureVector3(params.location, 'location');
|
|
206
|
+
} catch (e) {
|
|
207
|
+
throw new Error(`Invalid location: ${e instanceof Error ? e.message : String(e)}`);
|
|
185
208
|
}
|
|
186
209
|
}
|
|
187
|
-
|
|
210
|
+
|
|
188
211
|
// Default location if not provided
|
|
189
212
|
const location = params.location || [0, 0, 0];
|
|
190
|
-
|
|
213
|
+
|
|
191
214
|
// Validate numeric parameters
|
|
192
215
|
if (params.intensity !== undefined) {
|
|
193
216
|
if (typeof params.intensity !== 'number' || !isFinite(params.intensity)) {
|
|
@@ -210,7 +233,7 @@ else:
|
|
|
210
233
|
throw new Error(`Invalid falloffExponent value: ${params.falloffExponent}`);
|
|
211
234
|
}
|
|
212
235
|
}
|
|
213
|
-
|
|
236
|
+
|
|
214
237
|
// Validate color array
|
|
215
238
|
if (params.color !== undefined) {
|
|
216
239
|
if (!Array.isArray(params.color) || params.color.length !== 3) {
|
|
@@ -222,74 +245,45 @@ else:
|
|
|
222
245
|
}
|
|
223
246
|
}
|
|
224
247
|
}
|
|
225
|
-
|
|
226
|
-
// Build
|
|
227
|
-
const
|
|
248
|
+
|
|
249
|
+
// Build properties for the light
|
|
250
|
+
const properties: Record<string, any> = {};
|
|
228
251
|
if (params.intensity !== undefined) {
|
|
229
|
-
|
|
252
|
+
properties.intensity = params.intensity;
|
|
230
253
|
}
|
|
231
254
|
if (params.radius !== undefined) {
|
|
232
|
-
|
|
255
|
+
properties.attenuationRadius = params.radius;
|
|
233
256
|
}
|
|
234
257
|
if (params.color) {
|
|
235
|
-
|
|
258
|
+
properties.color = { r: params.color[0], g: params.color[1], b: params.color[2], a: 1.0 };
|
|
236
259
|
}
|
|
237
260
|
if (params.castShadows !== undefined) {
|
|
238
|
-
|
|
261
|
+
properties.castShadows = params.castShadows;
|
|
239
262
|
}
|
|
240
263
|
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(', ')}` };
|
|
264
|
+
properties.lightFalloffExponent = params.falloffExponent;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
await this.spawnLightViaAutomation('PointLight', {
|
|
269
|
+
name,
|
|
270
|
+
location,
|
|
271
|
+
rotation: params.rotation,
|
|
272
|
+
properties
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
return { success: true, message: `Point light '${name}' spawned at ${location.join(', ')}` };
|
|
276
|
+
} catch (e: any) {
|
|
277
|
+
// Don't mask errors as "not implemented" - report the actual error from the bridge
|
|
278
|
+
return { success: false, error: `Failed to create point light: ${e?.message ?? e}` } as any;
|
|
279
|
+
}
|
|
286
280
|
}
|
|
287
281
|
|
|
288
282
|
// Create spot light
|
|
289
283
|
async createSpotLight(params: {
|
|
290
284
|
name: string;
|
|
291
285
|
location: [number, number, number];
|
|
292
|
-
rotation: [number, number, number];
|
|
286
|
+
rotation: [number, number, number] | { pitch: number, yaw: number, roll: number };
|
|
293
287
|
intensity?: number;
|
|
294
288
|
innerCone?: number;
|
|
295
289
|
outerCone?: number;
|
|
@@ -298,8 +292,10 @@ else:
|
|
|
298
292
|
castShadows?: boolean;
|
|
299
293
|
}) {
|
|
300
294
|
const name = this.normalizeName(params.name);
|
|
301
|
-
|
|
302
|
-
|
|
295
|
+
if (!this.automationBridge) {
|
|
296
|
+
throw new Error('Automation Bridge required for light spawning');
|
|
297
|
+
}
|
|
298
|
+
|
|
303
299
|
// Validate required location and rotation arrays
|
|
304
300
|
if (!params.location || !Array.isArray(params.location) || params.location.length !== 3) {
|
|
305
301
|
throw new Error('Invalid location: must be an array [x,y,z]');
|
|
@@ -309,16 +305,21 @@ else:
|
|
|
309
305
|
throw new Error('Invalid location component: must be finite numbers');
|
|
310
306
|
}
|
|
311
307
|
}
|
|
312
|
-
|
|
313
|
-
if (!params.rotation
|
|
314
|
-
throw new Error('
|
|
308
|
+
|
|
309
|
+
if (!params.rotation) {
|
|
310
|
+
throw new Error('Rotation is required');
|
|
315
311
|
}
|
|
316
|
-
|
|
317
|
-
if (
|
|
318
|
-
throw new Error('Invalid rotation
|
|
312
|
+
if (Array.isArray(params.rotation)) {
|
|
313
|
+
if (params.rotation.length !== 3) {
|
|
314
|
+
throw new Error('Invalid rotation: must be an array [pitch,yaw,roll]');
|
|
315
|
+
}
|
|
316
|
+
for (const r of params.rotation) {
|
|
317
|
+
if (typeof r !== 'number' || !isFinite(r)) {
|
|
318
|
+
throw new Error('Invalid rotation component: must be finite numbers');
|
|
319
|
+
}
|
|
319
320
|
}
|
|
320
321
|
}
|
|
321
|
-
|
|
322
|
+
|
|
322
323
|
// Validate optional numeric parameters
|
|
323
324
|
if (params.intensity !== undefined) {
|
|
324
325
|
if (typeof params.intensity !== 'number' || !isFinite(params.intensity)) {
|
|
@@ -328,7 +329,7 @@ else:
|
|
|
328
329
|
throw new Error('Invalid intensity: must be non-negative');
|
|
329
330
|
}
|
|
330
331
|
}
|
|
331
|
-
|
|
332
|
+
|
|
332
333
|
if (params.innerCone !== undefined) {
|
|
333
334
|
if (typeof params.innerCone !== 'number' || !isFinite(params.innerCone)) {
|
|
334
335
|
throw new Error(`Invalid innerCone value: ${params.innerCone}`);
|
|
@@ -337,7 +338,7 @@ else:
|
|
|
337
338
|
throw new Error('Invalid innerCone: must be between 0 and 180 degrees');
|
|
338
339
|
}
|
|
339
340
|
}
|
|
340
|
-
|
|
341
|
+
|
|
341
342
|
if (params.outerCone !== undefined) {
|
|
342
343
|
if (typeof params.outerCone !== 'number' || !isFinite(params.outerCone)) {
|
|
343
344
|
throw new Error(`Invalid outerCone value: ${params.outerCone}`);
|
|
@@ -346,7 +347,7 @@ else:
|
|
|
346
347
|
throw new Error('Invalid outerCone: must be between 0 and 180 degrees');
|
|
347
348
|
}
|
|
348
349
|
}
|
|
349
|
-
|
|
350
|
+
|
|
350
351
|
if (params.radius !== undefined) {
|
|
351
352
|
if (typeof params.radius !== 'number' || !isFinite(params.radius)) {
|
|
352
353
|
throw new Error(`Invalid radius value: ${params.radius}`);
|
|
@@ -355,7 +356,7 @@ else:
|
|
|
355
356
|
throw new Error('Invalid radius: must be non-negative');
|
|
356
357
|
}
|
|
357
358
|
}
|
|
358
|
-
|
|
359
|
+
|
|
359
360
|
// Validate color array
|
|
360
361
|
if (params.color !== undefined) {
|
|
361
362
|
if (!Array.isArray(params.color) || params.color.length !== 3) {
|
|
@@ -367,83 +368,58 @@ else:
|
|
|
367
368
|
}
|
|
368
369
|
}
|
|
369
370
|
}
|
|
370
|
-
// Build
|
|
371
|
-
const
|
|
371
|
+
// Build properties for the light
|
|
372
|
+
const properties: Record<string, any> = {};
|
|
372
373
|
if (params.intensity !== undefined) {
|
|
373
|
-
|
|
374
|
+
properties.intensity = params.intensity;
|
|
374
375
|
}
|
|
375
376
|
if (params.innerCone !== undefined) {
|
|
376
|
-
|
|
377
|
+
properties.innerConeAngle = params.innerCone;
|
|
377
378
|
}
|
|
378
379
|
if (params.outerCone !== undefined) {
|
|
379
|
-
|
|
380
|
+
properties.outerConeAngle = params.outerCone;
|
|
380
381
|
}
|
|
381
382
|
if (params.radius !== undefined) {
|
|
382
|
-
|
|
383
|
+
properties.attenuationRadius = params.radius;
|
|
383
384
|
}
|
|
384
385
|
if (params.color) {
|
|
385
|
-
|
|
386
|
+
properties.color = { r: params.color[0], g: params.color[1], b: params.color[2], a: 1.0 };
|
|
386
387
|
}
|
|
387
388
|
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(', ')}` };
|
|
389
|
+
properties.castShadows = params.castShadows;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
try {
|
|
393
|
+
await this.spawnLightViaAutomation('SpotLight', {
|
|
394
|
+
name,
|
|
395
|
+
location: params.location,
|
|
396
|
+
rotation: params.rotation,
|
|
397
|
+
properties
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
return { success: true, message: `Spot light '${name}' spawned at ${params.location.join(', ')}` };
|
|
401
|
+
} catch (e: any) {
|
|
402
|
+
// Don't mask errors as "not implemented" - report the actual error from the bridge
|
|
403
|
+
return { success: false, error: `Failed to create spot light: ${e?.message ?? e}` } as any;
|
|
404
|
+
}
|
|
433
405
|
}
|
|
434
406
|
|
|
435
407
|
// Create rect light
|
|
436
408
|
async createRectLight(params: {
|
|
437
409
|
name: string;
|
|
438
410
|
location: [number, number, number];
|
|
439
|
-
rotation: [number, number, number];
|
|
411
|
+
rotation: [number, number, number] | { pitch: number, yaw: number, roll: number };
|
|
440
412
|
width?: number;
|
|
441
413
|
height?: number;
|
|
442
414
|
intensity?: number;
|
|
443
415
|
color?: [number, number, number];
|
|
416
|
+
castShadows?: boolean;
|
|
444
417
|
}) {
|
|
418
|
+
|
|
445
419
|
const name = this.normalizeName(params.name);
|
|
446
|
-
|
|
420
|
+
if (!this.automationBridge) {
|
|
421
|
+
throw new Error('Automation Bridge required for light spawning');
|
|
422
|
+
}
|
|
447
423
|
|
|
448
424
|
// Validate required location and rotation arrays
|
|
449
425
|
if (!params.location || !Array.isArray(params.location) || params.location.length !== 3) {
|
|
@@ -454,16 +430,21 @@ else:
|
|
|
454
430
|
throw new Error('Invalid location component: must be finite numbers');
|
|
455
431
|
}
|
|
456
432
|
}
|
|
457
|
-
|
|
458
|
-
if (!params.rotation
|
|
459
|
-
throw new Error('
|
|
433
|
+
|
|
434
|
+
if (!params.rotation) {
|
|
435
|
+
throw new Error('Rotation is required');
|
|
460
436
|
}
|
|
461
|
-
|
|
462
|
-
if (
|
|
463
|
-
throw new Error('Invalid rotation
|
|
437
|
+
if (Array.isArray(params.rotation)) {
|
|
438
|
+
if (params.rotation.length !== 3) {
|
|
439
|
+
throw new Error('Invalid rotation: must be an array [pitch,yaw,roll]');
|
|
440
|
+
}
|
|
441
|
+
for (const r of params.rotation) {
|
|
442
|
+
if (typeof r !== 'number' || !isFinite(r)) {
|
|
443
|
+
throw new Error('Invalid rotation component: must be finite numbers');
|
|
444
|
+
}
|
|
464
445
|
}
|
|
465
446
|
}
|
|
466
|
-
|
|
447
|
+
|
|
467
448
|
// Validate optional numeric parameters
|
|
468
449
|
if (params.width !== undefined) {
|
|
469
450
|
if (typeof params.width !== 'number' || !isFinite(params.width)) {
|
|
@@ -473,7 +454,7 @@ else:
|
|
|
473
454
|
throw new Error('Invalid width: must be positive');
|
|
474
455
|
}
|
|
475
456
|
}
|
|
476
|
-
|
|
457
|
+
|
|
477
458
|
if (params.height !== undefined) {
|
|
478
459
|
if (typeof params.height !== 'number' || !isFinite(params.height)) {
|
|
479
460
|
throw new Error(`Invalid height value: ${params.height}`);
|
|
@@ -482,7 +463,7 @@ else:
|
|
|
482
463
|
throw new Error('Invalid height: must be positive');
|
|
483
464
|
}
|
|
484
465
|
}
|
|
485
|
-
|
|
466
|
+
|
|
486
467
|
if (params.intensity !== undefined) {
|
|
487
468
|
if (typeof params.intensity !== 'number' || !isFinite(params.intensity)) {
|
|
488
469
|
throw new Error(`Invalid intensity value: ${params.intensity}`);
|
|
@@ -491,7 +472,7 @@ else:
|
|
|
491
472
|
throw new Error('Invalid intensity: must be non-negative');
|
|
492
473
|
}
|
|
493
474
|
}
|
|
494
|
-
|
|
475
|
+
|
|
495
476
|
// Validate color array
|
|
496
477
|
if (params.color !== undefined) {
|
|
497
478
|
if (!Array.isArray(params.color) || params.color.length !== 3) {
|
|
@@ -503,63 +484,77 @@ else:
|
|
|
503
484
|
}
|
|
504
485
|
}
|
|
505
486
|
}
|
|
506
|
-
// Build
|
|
507
|
-
const
|
|
487
|
+
// Build properties for the light
|
|
488
|
+
const properties: Record<string, any> = {};
|
|
508
489
|
if (params.intensity !== undefined) {
|
|
509
|
-
|
|
490
|
+
properties.intensity = params.intensity;
|
|
510
491
|
}
|
|
511
492
|
if (params.color) {
|
|
512
|
-
|
|
493
|
+
properties.color = { r: params.color[0], g: params.color[1], b: params.color[2], a: 1.0 };
|
|
513
494
|
}
|
|
514
495
|
if (params.width !== undefined) {
|
|
515
|
-
|
|
496
|
+
properties.sourceWidth = params.width;
|
|
516
497
|
}
|
|
517
498
|
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
|
-
|
|
499
|
+
properties.sourceHeight = params.height;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
try {
|
|
503
|
+
await this.spawnLightViaAutomation('RectLight', {
|
|
504
|
+
name,
|
|
505
|
+
location: params.location,
|
|
506
|
+
rotation: params.rotation,
|
|
507
|
+
properties
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
return { success: true, message: `Rect light '${name}' spawned at ${params.location.join(', ')}` };
|
|
511
|
+
} catch (e: any) {
|
|
512
|
+
// Don't mask errors as "not implemented" - report the actual error from the bridge
|
|
513
|
+
return { success: false, error: `Failed to create rect light: ${e?.message ?? e}` } as any;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Create dynamic light
|
|
519
|
+
*/
|
|
520
|
+
async createDynamicLight(params: {
|
|
521
|
+
name?: string;
|
|
522
|
+
lightType?: 'Point' | 'Spot' | 'Directional' | 'Rect' | string;
|
|
523
|
+
location?: [number, number, number] | { x: number; y: number; z: number };
|
|
524
|
+
rotation?: [number, number, number];
|
|
525
|
+
intensity?: number;
|
|
526
|
+
color?: [number, number, number, number] | { r: number; g: number; b: number; a?: number };
|
|
527
|
+
pulse?: { enabled?: boolean; frequency?: number };
|
|
528
|
+
}) {
|
|
529
|
+
try {
|
|
530
|
+
const name = typeof params.name === 'string' && params.name.trim().length > 0 ? params.name.trim() : `DynamicLight_${Date.now() % 10000}`;
|
|
531
|
+
const lightTypeRaw = typeof params.lightType === 'string' && params.lightType.trim().length > 0 ? params.lightType.trim() : 'Point';
|
|
532
|
+
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 });
|
|
533
|
+
|
|
534
|
+
// C++ plugin does not strictly implement 'create_dynamic_light' action; it supports 'spawn_light'.
|
|
535
|
+
// However, we rely on the specific helper methods below which correctly map to 'spawn_light'
|
|
536
|
+
// with the appropriate class and properties.
|
|
537
|
+
|
|
538
|
+
const toArray3 = (loc: any): [number, number, number] => Array.isArray(loc)
|
|
539
|
+
? [Number(loc[0]) || 0, Number(loc[1]) || 0, Number(loc[2]) || 0]
|
|
540
|
+
: [Number(loc?.x) || 0, Number(loc?.y) || 0, Number(loc?.z) || 0];
|
|
541
|
+
const locArr = toArray3(location);
|
|
542
|
+
const typeNorm = (lightTypeRaw || 'Point').toLowerCase();
|
|
543
|
+
|
|
544
|
+
switch (typeNorm) {
|
|
545
|
+
case 'directional': case 'directionallight':
|
|
546
|
+
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 });
|
|
547
|
+
case 'spot': case 'spotlight':
|
|
548
|
+
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) });
|
|
549
|
+
case 'rect': case 'rectlight':
|
|
550
|
+
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) });
|
|
551
|
+
case 'point': default:
|
|
552
|
+
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 });
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
} catch (err) {
|
|
556
|
+
return { success: false, error: `Failed to create dynamic light: ${err}` };
|
|
557
|
+
}
|
|
563
558
|
}
|
|
564
559
|
|
|
565
560
|
// Create sky light
|
|
@@ -569,210 +564,107 @@ else:
|
|
|
569
564
|
cubemapPath?: string;
|
|
570
565
|
intensity?: number;
|
|
571
566
|
recapture?: boolean;
|
|
567
|
+
location?: [number, number, number];
|
|
568
|
+
rotation?: [number, number, number] | { pitch: number, yaw: number, roll: number };
|
|
569
|
+
realTimeCapture?: boolean;
|
|
570
|
+
castShadows?: boolean;
|
|
571
|
+
color?: [number, number, number];
|
|
572
572
|
}) {
|
|
573
573
|
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)) {
|
|
574
|
+
if (params.sourceType === 'SpecifiedCubemap' && (!params.cubemapPath || params.cubemapPath.trim().length === 0)) {
|
|
586
575
|
const message = 'cubemapPath is required when sourceType is SpecifiedCubemap';
|
|
587
576
|
return { success: false, error: message, message };
|
|
588
577
|
}
|
|
589
|
-
const escapedCubemapPath = cubemapPath ? escapePythonString(cubemapPath) : '';
|
|
590
|
-
const python = `
|
|
591
|
-
import unreal
|
|
592
|
-
import json
|
|
593
578
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
}
|
|
579
|
+
if (!this.automationBridge) {
|
|
580
|
+
throw new Error('Automation Bridge required for sky light creation');
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
try {
|
|
584
|
+
const properties: Record<string, any> = {};
|
|
585
|
+
if (params.intensity !== undefined) properties.Intensity = params.intensity;
|
|
586
|
+
if (params.castShadows !== undefined) properties.CastShadows = params.castShadows;
|
|
587
|
+
if (params.realTimeCapture !== undefined) properties.RealTimeCapture = params.realTimeCapture;
|
|
588
|
+
if (params.color) properties.LightColor = { r: params.color[0], g: params.color[1], b: params.color[2], a: 1.0 };
|
|
589
|
+
|
|
590
|
+
const payload: Record<string, any> = {
|
|
591
|
+
name,
|
|
592
|
+
sourceType: params.sourceType || 'CapturedScene',
|
|
593
|
+
location: params.location,
|
|
594
|
+
rotation: params.rotation,
|
|
595
|
+
properties
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
if (params.cubemapPath) {
|
|
599
|
+
payload.cubemapPath = params.cubemapPath;
|
|
600
|
+
}
|
|
601
|
+
if (params.intensity !== undefined) {
|
|
602
|
+
payload.intensity = params.intensity;
|
|
603
|
+
}
|
|
604
|
+
if (params.recapture) {
|
|
605
|
+
payload.recapture = params.recapture;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const response = await this.automationBridge.sendAutomationRequest('spawn_sky_light', payload, {
|
|
609
|
+
timeoutMs: 60000
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
if (response.success === false) {
|
|
613
|
+
return {
|
|
614
|
+
success: false,
|
|
615
|
+
error: response.error || response.message || 'Failed to create sky light'
|
|
616
|
+
};
|
|
617
|
+
}
|
|
600
618
|
|
|
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
619
|
return {
|
|
735
620
|
success: true,
|
|
736
|
-
message:
|
|
737
|
-
|
|
621
|
+
message: response.message || 'Sky light created',
|
|
622
|
+
...(response.result || {})
|
|
623
|
+
};
|
|
624
|
+
} catch (error) {
|
|
625
|
+
return {
|
|
626
|
+
success: false,
|
|
627
|
+
error: `Failed to create sky light: ${error instanceof Error ? error.message : String(error)}`
|
|
738
628
|
};
|
|
739
629
|
}
|
|
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
630
|
}
|
|
748
631
|
|
|
749
632
|
// Remove duplicate SkyLights and keep only one (named target label)
|
|
750
633
|
async ensureSingleSkyLight(params?: { name?: string; recapture?: boolean }) {
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
const escapedName = escapePythonString(name);
|
|
634
|
+
const defaultName = 'MCP_Test_Sky';
|
|
635
|
+
const name = this.normalizeName(params?.name, defaultName);
|
|
754
636
|
const recapture = !!params?.recapture;
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
}
|
|
773
|
-
}
|
|
637
|
+
|
|
638
|
+
if (!this.automationBridge) {
|
|
639
|
+
throw new Error('Automation Bridge required for sky light management');
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
try {
|
|
643
|
+
const response = await this.automationBridge.sendAutomationRequest('ensure_single_sky_light', {
|
|
644
|
+
name,
|
|
645
|
+
recapture
|
|
646
|
+
}, {
|
|
647
|
+
timeoutMs: 60000
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
if (response.success === false) {
|
|
651
|
+
return {
|
|
652
|
+
success: false,
|
|
653
|
+
error: response.error || response.message || 'Failed to ensure single sky light'
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
return {
|
|
658
|
+
success: true,
|
|
659
|
+
message: response.message || `Ensured single SkyLight (removed ${(response.result as any)?.removed || 0})`,
|
|
660
|
+
...(response.result || {})
|
|
661
|
+
};
|
|
662
|
+
} catch (error) {
|
|
663
|
+
return {
|
|
664
|
+
success: false,
|
|
665
|
+
error: `Failed to ensure single sky light: ${error instanceof Error ? error.message : String(error)}`
|
|
666
|
+
};
|
|
774
667
|
}
|
|
775
|
-
return { success: true, message: 'Ensured single SkyLight' };
|
|
776
668
|
}
|
|
777
669
|
|
|
778
670
|
// Setup global illumination
|
|
@@ -782,8 +674,22 @@ except Exception as run_err:
|
|
|
782
674
|
indirectLightingIntensity?: number;
|
|
783
675
|
bounces?: number;
|
|
784
676
|
}) {
|
|
677
|
+
if (this.automationBridge) {
|
|
678
|
+
try {
|
|
679
|
+
const response = await this.automationBridge.sendAutomationRequest('setup_global_illumination', {
|
|
680
|
+
method: params.method,
|
|
681
|
+
quality: params.quality,
|
|
682
|
+
indirectLightingIntensity: params.indirectLightingIntensity,
|
|
683
|
+
bounces: params.bounces
|
|
684
|
+
});
|
|
685
|
+
if (response.success) return { success: true, message: 'Global illumination configured via bridge', ...(response.result || {}) };
|
|
686
|
+
} catch (_e) {
|
|
687
|
+
// Fallback to console commands
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
785
691
|
const commands = [];
|
|
786
|
-
|
|
692
|
+
|
|
787
693
|
switch (params.method) {
|
|
788
694
|
case 'Lightmass':
|
|
789
695
|
commands.push('r.DynamicGlobalIlluminationMethod 0');
|
|
@@ -798,25 +704,25 @@ except Exception as run_err:
|
|
|
798
704
|
commands.push('r.DynamicGlobalIlluminationMethod 3');
|
|
799
705
|
break;
|
|
800
706
|
}
|
|
801
|
-
|
|
707
|
+
|
|
802
708
|
if (params.quality) {
|
|
803
709
|
const qualityMap = { 'Low': 0, 'Medium': 1, 'High': 2, 'Epic': 3 };
|
|
804
710
|
commands.push(`r.Lumen.Quality ${qualityMap[params.quality]}`);
|
|
805
711
|
}
|
|
806
|
-
|
|
712
|
+
|
|
807
713
|
if (params.indirectLightingIntensity !== undefined) {
|
|
808
714
|
commands.push(`r.IndirectLightingIntensity ${params.indirectLightingIntensity}`);
|
|
809
715
|
}
|
|
810
|
-
|
|
716
|
+
|
|
811
717
|
if (params.bounces !== undefined) {
|
|
812
718
|
commands.push(`r.Lumen.MaxReflectionBounces ${params.bounces}`);
|
|
813
719
|
}
|
|
814
|
-
|
|
720
|
+
|
|
815
721
|
for (const cmd of commands) {
|
|
816
722
|
await this.bridge.executeConsoleCommand(cmd);
|
|
817
723
|
}
|
|
818
|
-
|
|
819
|
-
return { success: true, message: 'Global illumination configured' };
|
|
724
|
+
|
|
725
|
+
return { success: true, message: 'Global illumination configured (console)' };
|
|
820
726
|
}
|
|
821
727
|
|
|
822
728
|
// Configure shadows
|
|
@@ -827,529 +733,174 @@ except Exception as run_err:
|
|
|
827
733
|
contactShadows?: boolean;
|
|
828
734
|
rayTracedShadows?: boolean;
|
|
829
735
|
}) {
|
|
736
|
+
if (this.automationBridge) {
|
|
737
|
+
try {
|
|
738
|
+
const response = await this.automationBridge.sendAutomationRequest('configure_shadows', {
|
|
739
|
+
shadowQuality: params.shadowQuality,
|
|
740
|
+
cascadedShadows: params.cascadedShadows,
|
|
741
|
+
shadowDistance: params.shadowDistance,
|
|
742
|
+
contactShadows: params.contactShadows,
|
|
743
|
+
rayTracedShadows: params.rayTracedShadows,
|
|
744
|
+
virtualShadowMaps: params.rayTracedShadows // Map to VSM for C++ handler
|
|
745
|
+
});
|
|
746
|
+
if (response.success) return { success: true, message: 'Shadow settings configured via bridge', ...(response.result || {}) };
|
|
747
|
+
} catch (_e) {
|
|
748
|
+
// Fallback
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
830
752
|
const commands = [];
|
|
831
|
-
|
|
753
|
+
|
|
832
754
|
if (params.shadowQuality) {
|
|
833
755
|
const qualityMap = { 'Low': 0, 'Medium': 1, 'High': 2, 'Epic': 3 };
|
|
834
756
|
commands.push(`r.ShadowQuality ${qualityMap[params.shadowQuality]}`);
|
|
835
757
|
}
|
|
836
|
-
|
|
758
|
+
|
|
837
759
|
if (params.cascadedShadows !== undefined) {
|
|
838
760
|
commands.push(`r.Shadow.CSM.MaxCascades ${params.cascadedShadows ? 4 : 1}`);
|
|
839
761
|
}
|
|
840
|
-
|
|
762
|
+
|
|
841
763
|
if (params.shadowDistance !== undefined) {
|
|
842
764
|
commands.push(`r.Shadow.DistanceScale ${params.shadowDistance}`);
|
|
843
765
|
}
|
|
844
|
-
|
|
766
|
+
|
|
845
767
|
if (params.contactShadows !== undefined) {
|
|
846
768
|
commands.push(`r.ContactShadows ${params.contactShadows ? 1 : 0}`);
|
|
847
769
|
}
|
|
848
|
-
|
|
770
|
+
|
|
849
771
|
if (params.rayTracedShadows !== undefined) {
|
|
850
772
|
commands.push(`r.RayTracing.Shadows ${params.rayTracedShadows ? 1 : 0}`);
|
|
851
773
|
}
|
|
852
|
-
|
|
774
|
+
|
|
853
775
|
for (const cmd of commands) {
|
|
854
776
|
await this.bridge.executeConsoleCommand(cmd);
|
|
855
777
|
}
|
|
856
|
-
|
|
857
|
-
return { success: true, message: 'Shadow settings configured' };
|
|
778
|
+
|
|
779
|
+
return { success: true, message: 'Shadow settings configured (console)' };
|
|
858
780
|
}
|
|
859
781
|
|
|
860
|
-
// Build lighting
|
|
782
|
+
// Build lighting
|
|
861
783
|
async buildLighting(params: {
|
|
862
784
|
quality?: 'Preview' | 'Medium' | 'High' | 'Production';
|
|
863
|
-
buildOnlySelected?: boolean;
|
|
785
|
+
buildOnlySelected?: boolean;
|
|
864
786
|
buildReflectionCaptures?: boolean;
|
|
787
|
+
levelPath?: string;
|
|
865
788
|
}) {
|
|
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);
|
|
789
|
+
if (!this.automationBridge) {
|
|
790
|
+
throw new Error('Automation Bridge required for lighting build');
|
|
791
|
+
}
|
|
792
|
+
|
|
1016
793
|
try {
|
|
1017
|
-
const
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
} as any;
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
} catch {}
|
|
794
|
+
const response = await this.automationBridge.sendAutomationRequest('bake_lightmap', {
|
|
795
|
+
quality: params.quality || 'High',
|
|
796
|
+
buildOnlySelected: params.buildOnlySelected || false,
|
|
797
|
+
buildReflectionCaptures: params.buildReflectionCaptures !== false,
|
|
798
|
+
levelPath: params.levelPath
|
|
799
|
+
}, {
|
|
800
|
+
timeoutMs: 300000 // 5 minutes for lighting builds
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
if (response.success === false) {
|
|
804
|
+
return {
|
|
805
|
+
success: false,
|
|
806
|
+
error: response.error || response.message || 'Failed to build lighting'
|
|
807
|
+
};
|
|
1035
808
|
}
|
|
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' };
|
|
809
|
+
|
|
810
|
+
return {
|
|
811
|
+
success: true,
|
|
812
|
+
message: response.message || 'Lighting build started',
|
|
813
|
+
...(response.result || {})
|
|
814
|
+
} as any;
|
|
815
|
+
} catch (error) {
|
|
816
|
+
return {
|
|
817
|
+
success: false,
|
|
818
|
+
error: `Failed to build lighting: ${error instanceof Error ? error.message : String(error)}`
|
|
819
|
+
} as any;
|
|
820
|
+
}
|
|
1068
821
|
}
|
|
1069
822
|
|
|
1070
|
-
// Create a new level with proper lighting settings
|
|
823
|
+
// Create a new level with proper lighting settings
|
|
1071
824
|
async createLightingEnabledLevel(params?: {
|
|
1072
825
|
levelName?: string;
|
|
1073
826
|
copyActors?: boolean;
|
|
1074
827
|
useTemplate?: boolean;
|
|
1075
828
|
}) {
|
|
1076
829
|
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
|
-
|
|
830
|
+
|
|
831
|
+
if (!this.automationBridge) {
|
|
832
|
+
throw new Error('Automation Bridge not available. Level creation requires plugin support.');
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
try {
|
|
836
|
+
const response = await this.automationBridge.sendAutomationRequest('create_lighting_enabled_level', {
|
|
837
|
+
levelName,
|
|
838
|
+
copyActors: params?.copyActors === true,
|
|
839
|
+
useTemplate: params?.useTemplate === true,
|
|
840
|
+
path: params?.levelName ? `/Game/Maps/${params.levelName}` : undefined // Ensure path is sent
|
|
841
|
+
}, {
|
|
842
|
+
timeoutMs: 120000 // 2 minutes for level creation
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
if (response.success === false) {
|
|
1296
846
|
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' };
|
|
847
|
+
success: false,
|
|
848
|
+
error: response.error || response.message || 'Failed to create level'
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
return {
|
|
853
|
+
success: true,
|
|
854
|
+
message: response.message || `Created new level "${levelName}" with lighting enabled`,
|
|
855
|
+
...(response.result || {})
|
|
856
|
+
};
|
|
857
|
+
} catch (error) {
|
|
858
|
+
return {
|
|
859
|
+
success: false,
|
|
860
|
+
error: `Failed to create lighting-enabled level: ${error}`
|
|
861
|
+
};
|
|
862
|
+
}
|
|
1319
863
|
}
|
|
1320
864
|
|
|
1321
|
-
// Create lightmass importance volume
|
|
865
|
+
// Create lightmass importance volume
|
|
1322
866
|
async createLightmassVolume(params: {
|
|
1323
867
|
name: string;
|
|
1324
868
|
location: [number, number, number];
|
|
1325
869
|
size: [number, number, number];
|
|
1326
870
|
}) {
|
|
1327
871
|
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
|
-
|
|
872
|
+
|
|
873
|
+
if (!this.automationBridge) {
|
|
874
|
+
throw new Error('Automation Bridge not available. Lightmass volume creation requires plugin support.');
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
try {
|
|
878
|
+
const response = await this.automationBridge.sendAutomationRequest('create_lightmass_volume', {
|
|
879
|
+
name,
|
|
880
|
+
location: { x: params.location[0], y: params.location[1], z: params.location[2] },
|
|
881
|
+
size: { x: params.size[0], y: params.size[1], z: params.size[2] }
|
|
882
|
+
}, {
|
|
883
|
+
timeoutMs: 60000
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
if (response.success === false) {
|
|
887
|
+
return {
|
|
888
|
+
success: false,
|
|
889
|
+
error: response.error || response.message || 'Failed to create lightmass volume'
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
return {
|
|
894
|
+
success: true,
|
|
895
|
+
message: `LightmassImportanceVolume '${name}' created`,
|
|
896
|
+
...(response.result || {})
|
|
897
|
+
};
|
|
898
|
+
} catch (error) {
|
|
899
|
+
return {
|
|
900
|
+
success: false,
|
|
901
|
+
error: `Failed to create lightmass volume: ${error}`
|
|
902
|
+
};
|
|
903
|
+
}
|
|
1353
904
|
}
|
|
1354
905
|
|
|
1355
906
|
// Set exposure
|
|
@@ -1359,27 +910,41 @@ else:
|
|
|
1359
910
|
minBrightness?: number;
|
|
1360
911
|
maxBrightness?: number;
|
|
1361
912
|
}) {
|
|
913
|
+
if (this.automationBridge) {
|
|
914
|
+
try {
|
|
915
|
+
const response = await this.automationBridge.sendAutomationRequest('set_exposure', {
|
|
916
|
+
method: params.method,
|
|
917
|
+
compensationValue: params.compensationValue,
|
|
918
|
+
minBrightness: params.minBrightness,
|
|
919
|
+
maxBrightness: params.maxBrightness
|
|
920
|
+
});
|
|
921
|
+
if (response.success) return { success: true, message: 'Exposure settings updated via bridge', ...(response.result || {}) };
|
|
922
|
+
} catch (_e) {
|
|
923
|
+
// Fallback
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
1362
927
|
const commands = [];
|
|
1363
|
-
|
|
928
|
+
|
|
1364
929
|
commands.push(`r.EyeAdaptation.ExposureMethod ${params.method === 'Manual' ? 0 : 1}`);
|
|
1365
|
-
|
|
930
|
+
|
|
1366
931
|
if (params.compensationValue !== undefined) {
|
|
1367
932
|
commands.push(`r.EyeAdaptation.ExposureCompensation ${params.compensationValue}`);
|
|
1368
933
|
}
|
|
1369
|
-
|
|
934
|
+
|
|
1370
935
|
if (params.minBrightness !== undefined) {
|
|
1371
936
|
commands.push(`r.EyeAdaptation.MinBrightness ${params.minBrightness}`);
|
|
1372
937
|
}
|
|
1373
|
-
|
|
938
|
+
|
|
1374
939
|
if (params.maxBrightness !== undefined) {
|
|
1375
940
|
commands.push(`r.EyeAdaptation.MaxBrightness ${params.maxBrightness}`);
|
|
1376
941
|
}
|
|
1377
|
-
|
|
942
|
+
|
|
1378
943
|
for (const cmd of commands) {
|
|
1379
944
|
await this.bridge.executeConsoleCommand(cmd);
|
|
1380
945
|
}
|
|
1381
|
-
|
|
1382
|
-
return { success: true, message: 'Exposure settings updated' };
|
|
946
|
+
|
|
947
|
+
return { success: true, message: 'Exposure settings updated (console)' };
|
|
1383
948
|
}
|
|
1384
949
|
|
|
1385
950
|
// Set ambient occlusion
|
|
@@ -1389,31 +954,45 @@ else:
|
|
|
1389
954
|
radius?: number;
|
|
1390
955
|
quality?: 'Low' | 'Medium' | 'High';
|
|
1391
956
|
}) {
|
|
957
|
+
if (this.automationBridge) {
|
|
958
|
+
try {
|
|
959
|
+
const response = await this.automationBridge.sendAutomationRequest('set_ambient_occlusion', {
|
|
960
|
+
enabled: params.enabled,
|
|
961
|
+
intensity: params.intensity,
|
|
962
|
+
radius: params.radius,
|
|
963
|
+
quality: params.quality
|
|
964
|
+
});
|
|
965
|
+
if (response.success) return { success: true, message: 'Ambient occlusion configured via bridge', ...(response.result || {}) };
|
|
966
|
+
} catch (_e) {
|
|
967
|
+
// Fallback
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
1392
971
|
const commands = [];
|
|
1393
|
-
|
|
972
|
+
|
|
1394
973
|
commands.push(`r.AmbientOcclusion.Enabled ${params.enabled ? 1 : 0}`);
|
|
1395
|
-
|
|
974
|
+
|
|
1396
975
|
if (params.intensity !== undefined) {
|
|
1397
976
|
commands.push(`r.AmbientOcclusion.Intensity ${params.intensity}`);
|
|
1398
977
|
}
|
|
1399
|
-
|
|
978
|
+
|
|
1400
979
|
if (params.radius !== undefined) {
|
|
1401
980
|
commands.push(`r.AmbientOcclusion.Radius ${params.radius}`);
|
|
1402
981
|
}
|
|
1403
|
-
|
|
982
|
+
|
|
1404
983
|
if (params.quality) {
|
|
1405
984
|
const qualityMap = { 'Low': 0, 'Medium': 1, 'High': 2 };
|
|
1406
985
|
commands.push(`r.AmbientOcclusion.Quality ${qualityMap[params.quality]}`);
|
|
1407
986
|
}
|
|
1408
|
-
|
|
987
|
+
|
|
1409
988
|
for (const cmd of commands) {
|
|
1410
989
|
await this.bridge.executeConsoleCommand(cmd);
|
|
1411
990
|
}
|
|
1412
|
-
|
|
1413
|
-
return { success: true, message: 'Ambient occlusion configured' };
|
|
991
|
+
|
|
992
|
+
return { success: true, message: 'Ambient occlusion configured (console)' };
|
|
1414
993
|
}
|
|
1415
994
|
|
|
1416
|
-
// Setup volumetric fog
|
|
995
|
+
// Setup volumetric fog
|
|
1417
996
|
async setupVolumetricFog(params: {
|
|
1418
997
|
enabled: boolean;
|
|
1419
998
|
density?: number;
|
|
@@ -1423,15 +1002,40 @@ else:
|
|
|
1423
1002
|
// Enable/disable global volumetric fog via CVar
|
|
1424
1003
|
await this.bridge.executeConsoleCommand(`r.VolumetricFog ${params.enabled ? 1 : 0}`);
|
|
1425
1004
|
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1005
|
+
if (!this.automationBridge) {
|
|
1006
|
+
return {
|
|
1007
|
+
success: true,
|
|
1008
|
+
message: 'Volumetric fog console setting applied (plugin required for fog actor adjustment)'
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
try {
|
|
1013
|
+
const response = await this.automationBridge.sendAutomationRequest('setup_volumetric_fog', {
|
|
1014
|
+
enabled: params.enabled,
|
|
1015
|
+
density: params.density,
|
|
1016
|
+
scatteringIntensity: params.scatteringIntensity,
|
|
1017
|
+
fogHeight: params.fogHeight
|
|
1018
|
+
}, {
|
|
1019
|
+
timeoutMs: 60000
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
if (response.success === false) {
|
|
1023
|
+
return {
|
|
1024
|
+
success: false,
|
|
1025
|
+
error: response.error || response.message || 'Failed to configure volumetric fog'
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1430
1028
|
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1029
|
+
return {
|
|
1030
|
+
success: true,
|
|
1031
|
+
message: 'Volumetric fog configured',
|
|
1032
|
+
...(response.result || {})
|
|
1033
|
+
};
|
|
1034
|
+
} catch (error) {
|
|
1035
|
+
return {
|
|
1036
|
+
success: false,
|
|
1037
|
+
error: `Failed to setup volumetric fog: ${error}`
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1436
1040
|
}
|
|
1437
1041
|
}
|