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