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/src/tools/landscape.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// Landscape tools for Unreal Engine with UE 5.6 World Partition support
|
|
2
2
|
import { UnrealBridge } from '../unreal-bridge.js';
|
|
3
|
-
import {
|
|
3
|
+
import { AutomationBridge } from '../automation/index.js';
|
|
4
4
|
import { ensureVector3 } from '../utils/validation.js';
|
|
5
|
-
import { escapePythonString } from '../utils/python.js';
|
|
6
5
|
|
|
7
6
|
export class LandscapeTools {
|
|
8
|
-
constructor(private bridge: UnrealBridge) {}
|
|
7
|
+
constructor(private bridge: UnrealBridge, private automationBridge?: AutomationBridge) { }
|
|
8
|
+
|
|
9
|
+
setAutomationBridge(automationBridge?: AutomationBridge) { this.automationBridge = automationBridge; }
|
|
9
10
|
|
|
10
11
|
// Create landscape with World Partition support (UE 5.6)
|
|
11
12
|
async createLandscape(params: {
|
|
@@ -40,281 +41,59 @@ export class LandscapeTools {
|
|
|
40
41
|
};
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
if (!this.automationBridge) {
|
|
45
|
+
throw new Error('Automation Bridge not available. Landscape operations require plugin support.');
|
|
46
|
+
}
|
|
47
|
+
|
|
43
48
|
const [locX, locY, locZ] = ensureVector3(params.location ?? [0, 0, 0], 'landscape location');
|
|
44
49
|
const sectionsPerComponent = Math.max(1, Math.floor(params.sectionsPerComponent ?? 1));
|
|
45
50
|
const quadsPerSection = Math.max(1, Math.floor(params.quadsPerSection ?? 63));
|
|
46
|
-
const componentCount = Math.max(1, Math.floor(params.componentCount ?? 1));
|
|
47
|
-
|
|
48
|
-
const defaultSize = 1000;
|
|
49
|
-
const scaleX = params.sizeX ? Math.max(0.1, params.sizeX / defaultSize) : 1;
|
|
50
|
-
const scaleY = params.sizeY ? Math.max(0.1, params.sizeY / defaultSize) : 1;
|
|
51
|
-
|
|
52
|
-
const escapedName = escapePythonString(name);
|
|
53
|
-
const escapedMaterial =
|
|
54
|
-
params.materialPath && params.materialPath.trim().length > 0
|
|
55
|
-
? escapePythonString(params.materialPath.trim())
|
|
56
|
-
: '';
|
|
57
|
-
|
|
58
|
-
const runtimeGridFlag = params.runtimeGrid ? 'True' : 'False';
|
|
59
|
-
const spatiallyLoadedFlag = params.isSpatiallyLoaded ? 'True' : 'False';
|
|
60
|
-
const runtimeGridValue = params.runtimeGrid ? escapePythonString(params.runtimeGrid.trim()) : '';
|
|
61
|
-
const dataLayerNames = Array.isArray(params.dataLayers)
|
|
62
|
-
? params.dataLayers
|
|
63
|
-
.map(layer => layer?.trim())
|
|
64
|
-
.filter((layer): layer is string => Boolean(layer))
|
|
65
|
-
.map(layer => escapePythonString(layer))
|
|
66
|
-
: [];
|
|
67
|
-
|
|
68
|
-
const pythonScript = `
|
|
69
|
-
import unreal
|
|
70
|
-
import json
|
|
71
|
-
|
|
72
|
-
result = {
|
|
73
|
-
"success": False,
|
|
74
|
-
"message": "",
|
|
75
|
-
"error": "",
|
|
76
|
-
"warnings": [],
|
|
77
|
-
"details": [],
|
|
78
|
-
"landscapeName": "",
|
|
79
|
-
"landscapeActor": "",
|
|
80
|
-
"worldPartition": False,
|
|
81
|
-
"runtimeGridRequested": ${runtimeGridFlag},
|
|
82
|
-
"spatiallyLoaded": ${spatiallyLoadedFlag}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
try:
|
|
86
|
-
editor_subsystem = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
|
|
87
|
-
world = editor_subsystem.get_editor_world() if editor_subsystem and hasattr(editor_subsystem, 'get_editor_world') else None
|
|
88
|
-
data_layer_manager = None
|
|
89
|
-
world_partition = None
|
|
90
|
-
if world:
|
|
91
|
-
# Try multiple methods to access World Partition (UE 5.6+)
|
|
92
|
-
try:
|
|
93
|
-
# Method 1: Try get_world_partition() if it exists
|
|
94
|
-
if hasattr(world, 'get_world_partition'):
|
|
95
|
-
world_partition = world.get_world_partition()
|
|
96
|
-
except (AttributeError, Exception):
|
|
97
|
-
pass
|
|
98
|
-
|
|
99
|
-
if not world_partition:
|
|
100
|
-
try:
|
|
101
|
-
# Method 2: Try WorldPartitionSubsystem
|
|
102
|
-
wp_subsystem = unreal.get_editor_subsystem(unreal.WorldPartitionSubsystem)
|
|
103
|
-
if wp_subsystem:
|
|
104
|
-
world_partition = wp_subsystem.get_world_partition(world)
|
|
105
|
-
except (AttributeError, Exception):
|
|
106
|
-
pass
|
|
107
|
-
|
|
108
|
-
if not world_partition:
|
|
109
|
-
try:
|
|
110
|
-
# Method 3: Check if world has world_partition property
|
|
111
|
-
if hasattr(world, 'world_partition'):
|
|
112
|
-
world_partition = world.world_partition
|
|
113
|
-
except (AttributeError, Exception):
|
|
114
|
-
pass
|
|
115
|
-
|
|
116
|
-
result["worldPartition"] = world_partition is not None
|
|
117
|
-
|
|
118
|
-
if result["worldPartition"] and hasattr(unreal, "WorldPartitionBlueprintLibrary"):
|
|
119
|
-
try:
|
|
120
|
-
data_layer_manager = unreal.WorldPartitionBlueprintLibrary.get_data_layer_manager(world)
|
|
121
|
-
except Exception as dlm_error:
|
|
122
|
-
result["warnings"].append(f"Data layer manager unavailable: {dlm_error}")
|
|
123
|
-
|
|
124
|
-
actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
|
|
125
|
-
if not actor_subsystem:
|
|
126
|
-
result["error"] = "EditorActorSubsystem unavailable"
|
|
127
|
-
else:
|
|
128
|
-
existing = None
|
|
129
|
-
try:
|
|
130
|
-
for actor in actor_subsystem.get_all_level_actors():
|
|
131
|
-
if actor and actor.get_actor_label() == "${escapedName}":
|
|
132
|
-
existing = actor
|
|
133
|
-
break
|
|
134
|
-
except Exception as scan_error:
|
|
135
|
-
result["warnings"].append(f"Actor scan failed: {scan_error}")
|
|
136
|
-
|
|
137
|
-
if existing:
|
|
138
|
-
result["success"] = True
|
|
139
|
-
result["message"] = "Landscape already exists"
|
|
140
|
-
result["landscapeName"] = existing.get_actor_label()
|
|
141
|
-
try:
|
|
142
|
-
result["landscapeActor"] = existing.get_path_name()
|
|
143
|
-
except Exception:
|
|
144
|
-
pass
|
|
145
|
-
else:
|
|
146
|
-
landscape_class = getattr(unreal, "Landscape", None)
|
|
147
|
-
if not landscape_class:
|
|
148
|
-
result["error"] = "Landscape class unavailable"
|
|
149
|
-
else:
|
|
150
|
-
location = unreal.Vector(${locX}, ${locY}, ${locZ})
|
|
151
|
-
rotation = unreal.Rotator(0.0, 0.0, 0.0)
|
|
152
|
-
landscape_actor = actor_subsystem.spawn_actor_from_class(landscape_class, location, rotation)
|
|
153
|
-
if not landscape_actor:
|
|
154
|
-
result["error"] = "Failed to spawn landscape actor"
|
|
155
|
-
else:
|
|
156
|
-
# Set label first
|
|
157
|
-
try:
|
|
158
|
-
landscape_actor.set_actor_label("${escapedName}", True)
|
|
159
|
-
except TypeError:
|
|
160
|
-
landscape_actor.set_actor_label("${escapedName}")
|
|
161
|
-
except Exception as label_error:
|
|
162
|
-
result["warnings"].append(f"Failed to set landscape label: {label_error}")
|
|
163
|
-
|
|
164
|
-
# Fix component registration by forcing re-registration
|
|
165
|
-
# This addresses the "RegisterComponentWithWorld: Trying to register component with IsValid() == false" warning
|
|
166
|
-
try:
|
|
167
|
-
# Get landscape components and re-register them
|
|
168
|
-
landscape_components = landscape_actor.get_components_by_class(unreal.LandscapeComponent)
|
|
169
|
-
if landscape_components:
|
|
170
|
-
for component in landscape_components:
|
|
171
|
-
if hasattr(component, 'register_component'):
|
|
172
|
-
try:
|
|
173
|
-
component.register_component()
|
|
174
|
-
except Exception:
|
|
175
|
-
pass
|
|
176
|
-
else:
|
|
177
|
-
# If no components yet, this is expected for LandscapePlaceholder
|
|
178
|
-
# The landscape needs to be "finalized" via editor tools or console commands
|
|
179
|
-
result["details"].append("Landscape placeholder created - finalize via editor for full functionality")
|
|
180
|
-
except Exception as comp_error:
|
|
181
|
-
# Component registration is best-effort; not critical
|
|
182
|
-
result["details"].append(f"Component registration attempted (editor finalization may be needed)")
|
|
183
|
-
|
|
184
|
-
try:
|
|
185
|
-
landscape_actor.set_actor_scale3d(unreal.Vector(${scaleX.toFixed(4)}, ${scaleY.toFixed(4)}, 1.0))
|
|
186
|
-
result["details"].append(f"Actor scale set to (${scaleX.toFixed(2)}, ${scaleY.toFixed(2)}, 1.0)")
|
|
187
|
-
except Exception as scale_error:
|
|
188
|
-
result["warnings"].append(f"Failed to set landscape scale: {scale_error}")
|
|
189
|
-
|
|
190
|
-
# Workaround for LandscapeEditorSubsystem Python API limitation
|
|
191
|
-
# Use direct property manipulation instead
|
|
192
|
-
landscape_configured = False
|
|
193
|
-
try:
|
|
194
|
-
# Try LandscapeEditorSubsystem if available (may not be in Python API)
|
|
195
|
-
landscape_editor = unreal.get_editor_subsystem(unreal.LandscapeEditorSubsystem)
|
|
196
|
-
if landscape_editor:
|
|
197
|
-
try:
|
|
198
|
-
landscape_editor.set_component_size(${sectionsPerComponent}, ${quadsPerSection})
|
|
199
|
-
landscape_editor.set_component_count(${componentCount}, ${componentCount})
|
|
200
|
-
result["details"].append(f"Component size ${sectionsPerComponent}x${quadsPerSection}, count ${componentCount}x${componentCount}")
|
|
201
|
-
landscape_configured = True
|
|
202
|
-
except Exception as config_error:
|
|
203
|
-
result["details"].append(f"LandscapeEditorSubsystem method limited: {config_error}")
|
|
204
|
-
except (AttributeError, Exception):
|
|
205
|
-
# Expected - LandscapeEditorSubsystem not available in Python API
|
|
206
|
-
pass
|
|
207
|
-
|
|
208
|
-
# Fallback: Configure via properties if subsystem not available
|
|
209
|
-
if not landscape_configured:
|
|
210
|
-
try:
|
|
211
|
-
# Set component properties directly
|
|
212
|
-
if hasattr(landscape_actor, 'set_editor_property'):
|
|
213
|
-
# Note: These properties may not be directly editable post-spawn
|
|
214
|
-
# This is documented UE limitation - landscape config is best done via editor tools
|
|
215
|
-
result["details"].append(f"Landscape spawned (config via editor tools recommended for ${sectionsPerComponent}x${quadsPerSection} components)")
|
|
216
|
-
except Exception:
|
|
217
|
-
pass
|
|
218
|
-
|
|
219
|
-
${escapedMaterial ? `try:
|
|
220
|
-
material = unreal.EditorAssetLibrary.load_asset("${escapedMaterial}")
|
|
221
|
-
if material:
|
|
222
|
-
try:
|
|
223
|
-
landscape_actor.set_landscape_material(material)
|
|
224
|
-
except Exception:
|
|
225
|
-
landscape_actor.editor_set_landscape_material(material)
|
|
226
|
-
result["details"].append("Landscape material applied")
|
|
227
|
-
else:
|
|
228
|
-
result["warnings"].append("Landscape material asset not found: ${escapedMaterial}")
|
|
229
|
-
except Exception as material_error:
|
|
230
|
-
result["warnings"].append(f"Failed to apply landscape material: {material_error}")
|
|
231
|
-
` : ''}
|
|
232
|
-
${runtimeGridValue ? `if result["worldPartition"] and hasattr(unreal, "WorldPartitionBlueprintLibrary"):
|
|
233
|
-
try:
|
|
234
|
-
unreal.WorldPartitionBlueprintLibrary.set_actor_runtime_grid(landscape_actor, "${runtimeGridValue}")
|
|
235
|
-
result["details"].append("Runtime grid assigned: ${runtimeGridValue}")
|
|
236
|
-
except Exception as grid_error:
|
|
237
|
-
result["warnings"].append(f"Failed to assign runtime grid: {grid_error}")
|
|
238
|
-
` : ''}
|
|
239
|
-
${params.isSpatiallyLoaded ? `if result["worldPartition"] and hasattr(unreal, "WorldPartitionBlueprintLibrary"):
|
|
240
|
-
try:
|
|
241
|
-
unreal.WorldPartitionBlueprintLibrary.set_actor_spatially_loaded(landscape_actor, True)
|
|
242
|
-
result["details"].append("Actor marked as spatially loaded")
|
|
243
|
-
except Exception as spatial_error:
|
|
244
|
-
result["warnings"].append(f"Failed to mark as spatially loaded: {spatial_error}")
|
|
245
|
-
` : ''}
|
|
246
|
-
${dataLayerNames.length ? `if result["worldPartition"] and data_layer_manager:
|
|
247
|
-
for layer_name in ${JSON.stringify(dataLayerNames)}:
|
|
248
|
-
try:
|
|
249
|
-
data_layer = data_layer_manager.get_data_layer(layer_name)
|
|
250
|
-
if data_layer:
|
|
251
|
-
unreal.WorldPartitionBlueprintLibrary.add_actor_to_data_layer(landscape_actor, data_layer)
|
|
252
|
-
result["details"].append(f"Added to data layer {layer_name}")
|
|
253
|
-
else:
|
|
254
|
-
result["warnings"].append(f"Data layer not found: {layer_name}")
|
|
255
|
-
except Exception as data_layer_error:
|
|
256
|
-
result["warnings"].append(f"Failed to assign data layer {layer_name}: {data_layer_error}")
|
|
257
|
-
` : ''}
|
|
258
|
-
|
|
259
|
-
try:
|
|
260
|
-
result["landscapeName"] = landscape_actor.get_actor_label()
|
|
261
|
-
result["landscapeActor"] = landscape_actor.get_path_name()
|
|
262
|
-
except Exception:
|
|
263
|
-
pass
|
|
264
|
-
|
|
265
|
-
result["success"] = True
|
|
266
|
-
result["message"] = "Landscape actor created"
|
|
267
|
-
except Exception as e:
|
|
268
|
-
result["error"] = str(e)
|
|
269
|
-
|
|
270
|
-
if result.get("success"):
|
|
271
|
-
result.pop("error", None)
|
|
272
|
-
else:
|
|
273
|
-
if not result.get("error"):
|
|
274
|
-
result["error"] = "Failed to create landscape actor"
|
|
275
|
-
if not result.get("message"):
|
|
276
|
-
result["message"] = result["error"]
|
|
277
|
-
|
|
278
|
-
if not result.get("warnings"):
|
|
279
|
-
result.pop("warnings", None)
|
|
280
|
-
if not result.get("details"):
|
|
281
|
-
result.pop("details", None)
|
|
282
|
-
|
|
283
|
-
print("RESULT:" + json.dumps(result))
|
|
284
|
-
`.trim();
|
|
285
51
|
|
|
286
52
|
try {
|
|
287
|
-
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
53
|
+
// Map to plugin-native payload shape
|
|
54
|
+
const componentsX = Math.max(1, Math.floor((params.componentCount ?? Math.max(1, Math.floor((params.sizeX ?? 1000) / 1000)))));
|
|
55
|
+
const componentsY = Math.max(1, Math.floor((params.componentCount ?? Math.max(1, Math.floor((params.sizeY ?? 1000) / 1000)))));
|
|
56
|
+
const quadsPerComponent = quadsPerSection; // Plugin uses quadsPerComponent
|
|
57
|
+
|
|
58
|
+
const payload: Record<string, unknown> = {
|
|
59
|
+
name,
|
|
60
|
+
x: locX,
|
|
61
|
+
y: locY,
|
|
62
|
+
z: locZ,
|
|
63
|
+
componentsX,
|
|
64
|
+
componentsY,
|
|
65
|
+
quadsPerComponent,
|
|
66
|
+
sectionsPerComponent,
|
|
67
|
+
materialPath: params.materialPath || ''
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const response = await this.automationBridge.sendAutomationRequest('create_landscape', payload, {
|
|
71
|
+
timeoutMs: 60000
|
|
291
72
|
});
|
|
292
73
|
|
|
293
|
-
if (
|
|
74
|
+
if (response.success === false) {
|
|
294
75
|
return {
|
|
295
76
|
success: false,
|
|
296
|
-
error:
|
|
77
|
+
error: response.error || response.message || 'Failed to create landscape actor'
|
|
297
78
|
};
|
|
298
79
|
}
|
|
299
80
|
|
|
300
81
|
const result: Record<string, unknown> = {
|
|
301
82
|
success: true,
|
|
302
|
-
message:
|
|
303
|
-
landscapeName:
|
|
304
|
-
worldPartition:
|
|
83
|
+
message: response.message || 'Landscape actor created',
|
|
84
|
+
landscapeName: response.landscapeName || name,
|
|
85
|
+
worldPartition: response.worldPartition ?? params.enableWorldPartition ?? false
|
|
305
86
|
};
|
|
306
87
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
result.landscapeActor = actorPath;
|
|
88
|
+
if (response.landscapeActor) {
|
|
89
|
+
result.landscapeActor = response.landscapeActor;
|
|
310
90
|
}
|
|
311
|
-
if (
|
|
312
|
-
result.warnings =
|
|
91
|
+
if (response.warnings) {
|
|
92
|
+
result.warnings = response.warnings;
|
|
313
93
|
}
|
|
314
|
-
if (
|
|
315
|
-
result.details =
|
|
94
|
+
if (response.details) {
|
|
95
|
+
result.details = response.details;
|
|
316
96
|
}
|
|
317
|
-
|
|
318
97
|
if (params.runtimeGrid) {
|
|
319
98
|
result.runtimeGrid = params.runtimeGrid;
|
|
320
99
|
}
|
|
@@ -326,141 +105,274 @@ print("RESULT:" + json.dumps(result))
|
|
|
326
105
|
} catch (error) {
|
|
327
106
|
return {
|
|
328
107
|
success: false,
|
|
329
|
-
error: `Failed to create landscape actor: ${error}`
|
|
108
|
+
error: `Failed to create landscape actor: ${error instanceof Error ? error.message : String(error)}`
|
|
330
109
|
};
|
|
331
110
|
}
|
|
332
111
|
}
|
|
333
112
|
|
|
113
|
+
|
|
334
114
|
// Sculpt landscape
|
|
335
|
-
async sculptLandscape(
|
|
115
|
+
async sculptLandscape(params: {
|
|
336
116
|
landscapeName: string;
|
|
337
|
-
tool:
|
|
117
|
+
tool: string;
|
|
338
118
|
brushSize?: number;
|
|
339
119
|
brushFalloff?: number;
|
|
340
120
|
strength?: number;
|
|
341
|
-
|
|
121
|
+
location?: [number, number, number];
|
|
122
|
+
radius?: number;
|
|
342
123
|
}) {
|
|
343
|
-
|
|
124
|
+
const [x, y, z] = ensureVector3(params.location ?? [0, 0, 0], 'sculpt location');
|
|
125
|
+
|
|
126
|
+
const tool = (params.tool || '').trim();
|
|
127
|
+
const lowerTool = tool.toLowerCase();
|
|
128
|
+
const validTools = new Set(['sculpt', 'smooth', 'flatten', 'ramp', 'erosion', 'hydro', 'noise', 'raise', 'lower']);
|
|
129
|
+
const isValidTool = lowerTool.length > 0 && validTools.has(lowerTool);
|
|
130
|
+
|
|
131
|
+
if (!isValidTool) {
|
|
132
|
+
return {
|
|
133
|
+
success: false,
|
|
134
|
+
error: `Invalid sculpt tool: ${params.tool}`
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!this.automationBridge) {
|
|
139
|
+
throw new Error('Automation Bridge not available. Landscape operations require plugin support.');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const payload = {
|
|
143
|
+
landscapeName: params.landscapeName?.trim(),
|
|
144
|
+
toolMode: tool, // Map 'tool' to 'toolMode'
|
|
145
|
+
brushRadius: params.brushSize ?? params.radius ?? 1000,
|
|
146
|
+
brushFalloff: params.brushFalloff ?? 0.5,
|
|
147
|
+
strength: params.strength ?? 0.1,
|
|
148
|
+
location: { x, y, z }
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const response = await this.automationBridge.sendAutomationRequest('sculpt_landscape', payload);
|
|
152
|
+
|
|
153
|
+
if (!response.success) {
|
|
154
|
+
return {
|
|
155
|
+
success: false,
|
|
156
|
+
error: response.error || 'Failed to sculpt landscape'
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
success: true,
|
|
162
|
+
message: `Sculpting applied to ${params.landscapeName}`,
|
|
163
|
+
details: response
|
|
164
|
+
};
|
|
344
165
|
}
|
|
345
166
|
|
|
346
167
|
// Paint landscape
|
|
347
|
-
async paintLandscape(
|
|
168
|
+
async paintLandscape(params: {
|
|
348
169
|
landscapeName: string;
|
|
349
170
|
layerName: string;
|
|
350
171
|
position: [number, number, number];
|
|
351
172
|
brushSize?: number;
|
|
352
173
|
strength?: number;
|
|
353
174
|
targetValue?: number;
|
|
175
|
+
radius?: number;
|
|
176
|
+
density?: number;
|
|
354
177
|
}) {
|
|
355
|
-
|
|
356
|
-
|
|
178
|
+
if (!this.automationBridge) {
|
|
179
|
+
throw new Error('Automation Bridge not available.');
|
|
180
|
+
}
|
|
357
181
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
landscapeName: string;
|
|
361
|
-
layerName: string;
|
|
362
|
-
weightMapPath?: string;
|
|
363
|
-
blendMode?: 'Weight' | 'Alpha';
|
|
364
|
-
}) {
|
|
365
|
-
const commands: string[] = [];
|
|
366
|
-
|
|
367
|
-
commands.push(`AddLandscapeLayer ${params.landscapeName} ${params.layerName}`);
|
|
368
|
-
|
|
369
|
-
if (params.weightMapPath) {
|
|
370
|
-
commands.push(`SetLayerWeightMap ${params.layerName} ${params.weightMapPath}`);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
if (params.blendMode) {
|
|
374
|
-
commands.push(`SetLayerBlendMode ${params.layerName} ${params.blendMode}`);
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
await this.bridge.executeConsoleCommands(commands);
|
|
378
|
-
|
|
379
|
-
return { success: true, message: `Layer ${params.layerName} added to landscape` };
|
|
380
|
-
}
|
|
182
|
+
const [x, y] = ensureVector3(params.position, 'paint position');
|
|
183
|
+
const radius = params.brushSize ?? params.radius ?? 1000;
|
|
381
184
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
await this.bridge.executeConsoleCommands(commands);
|
|
412
|
-
|
|
413
|
-
return { success: true, message: `Landscape spline ${params.splineName} created` };
|
|
185
|
+
// Map brush to a square region for now as C++ only supports region fill
|
|
186
|
+
const minX = Math.floor(x - radius);
|
|
187
|
+
const maxX = Math.floor(x + radius);
|
|
188
|
+
const minY = Math.floor(y - radius);
|
|
189
|
+
const maxY = Math.floor(y + radius);
|
|
190
|
+
|
|
191
|
+
const payload = {
|
|
192
|
+
landscapeName: params.landscapeName?.trim(),
|
|
193
|
+
layerName: params.layerName?.trim(),
|
|
194
|
+
region: { minX, minY, maxX, maxY },
|
|
195
|
+
strength: params.strength ?? 1.0
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const response = await this.automationBridge.sendAutomationRequest('paint_landscape_layer', payload);
|
|
199
|
+
|
|
200
|
+
if (!response.success) {
|
|
201
|
+
return {
|
|
202
|
+
success: false,
|
|
203
|
+
error: response.error || 'Failed to paint landscape layer'
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
success: true,
|
|
209
|
+
message: `Painted layer ${params.layerName}`,
|
|
210
|
+
details: response
|
|
211
|
+
};
|
|
414
212
|
}
|
|
415
213
|
|
|
416
|
-
//
|
|
417
|
-
async
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
214
|
+
// Create procedural terrain using ProceduralMeshComponent
|
|
215
|
+
async createProceduralTerrain(params: {
|
|
216
|
+
name: string;
|
|
217
|
+
location?: [number, number, number];
|
|
218
|
+
sizeX?: number;
|
|
219
|
+
sizeY?: number;
|
|
220
|
+
subdivisions?: number;
|
|
221
|
+
heightFunction?: string; // Expression for height calculation
|
|
222
|
+
material?: string;
|
|
223
|
+
settings?: Record<string, unknown>;
|
|
421
224
|
}) {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
225
|
+
if (!this.automationBridge) {
|
|
226
|
+
throw new Error('Automation Bridge not available. Procedural terrain creation requires plugin support.');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
// Combine specific params with generic settings
|
|
231
|
+
const payload = {
|
|
232
|
+
name: params.name,
|
|
233
|
+
location: params.location || [0, 0, 0],
|
|
234
|
+
sizeX: params.sizeX || 2000,
|
|
235
|
+
sizeY: params.sizeY || 2000,
|
|
236
|
+
subdivisions: params.subdivisions || 50,
|
|
237
|
+
heightFunction: params.heightFunction || 'math.sin(x/100) * 50 + math.cos(y/100) * 30',
|
|
238
|
+
material: params.material,
|
|
239
|
+
...params.settings
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const response = await this.automationBridge.sendAutomationRequest('create_procedural_terrain', payload, {
|
|
243
|
+
timeoutMs: 120000 // 2 minutes for mesh generation
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
if (response.success === false) {
|
|
247
|
+
return {
|
|
248
|
+
success: false,
|
|
249
|
+
error: response.error || response.message || 'Failed to create procedural terrain',
|
|
250
|
+
message: response.message || 'Failed to create procedural terrain'
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const result = response.result as any;
|
|
255
|
+
return {
|
|
256
|
+
success: true,
|
|
257
|
+
message: response.message || `Created procedural terrain '${params.name}'`,
|
|
258
|
+
actorName: result?.actor_name,
|
|
259
|
+
vertices: result?.vertices,
|
|
260
|
+
triangles: result?.triangles,
|
|
261
|
+
size: result?.size,
|
|
262
|
+
subdivisions: result?.subdivisions,
|
|
263
|
+
details: result
|
|
264
|
+
};
|
|
265
|
+
} catch (error) {
|
|
266
|
+
return {
|
|
267
|
+
success: false,
|
|
268
|
+
error: `Failed to create procedural terrain: ${error instanceof Error ? error.message : String(error)}`
|
|
269
|
+
};
|
|
270
|
+
}
|
|
426
271
|
}
|
|
427
272
|
|
|
428
|
-
//
|
|
429
|
-
async
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
273
|
+
// Create a LandscapeGrassType asset via AutomationBridge
|
|
274
|
+
async createLandscapeGrassType(params: {
|
|
275
|
+
name: string;
|
|
276
|
+
meshPath: string; // Normalized parameter name (was path/staticMesh/meshPath)
|
|
277
|
+
density?: number;
|
|
278
|
+
minScale?: number;
|
|
279
|
+
maxScale?: number;
|
|
280
|
+
path?: string; // Legacy support
|
|
281
|
+
staticMesh?: string; // Legacy support
|
|
282
|
+
}): Promise<any> {
|
|
283
|
+
if (!this.automationBridge) {
|
|
284
|
+
throw new Error('Automation Bridge not available. Landscape operations require plugin support.');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const name = typeof params.name === 'string' ? params.name.trim() : '';
|
|
288
|
+
if (!name) {
|
|
289
|
+
return { success: false, error: 'Grass type name is required' };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Accept mesh path from multiple fields for compatibility
|
|
293
|
+
const meshPathRaw = typeof params.meshPath === 'string' && params.meshPath.trim().length > 0
|
|
294
|
+
? params.meshPath.trim()
|
|
295
|
+
: (typeof params.path === 'string' && params.path.trim().length > 0
|
|
296
|
+
? params.path.trim()
|
|
297
|
+
: (typeof params.staticMesh === 'string' && params.staticMesh.trim().length > 0
|
|
298
|
+
? params.staticMesh.trim()
|
|
299
|
+
: ''));
|
|
300
|
+
|
|
301
|
+
if (!meshPathRaw) {
|
|
302
|
+
return { success: false, error: 'meshPath is required to create a landscape grass type' };
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
const response: any = await this.automationBridge.sendAutomationRequest('create_landscape_grass_type', {
|
|
307
|
+
name,
|
|
308
|
+
meshPath: meshPathRaw,
|
|
309
|
+
density: params.density || 1.0,
|
|
310
|
+
minScale: params.minScale || 0.8,
|
|
311
|
+
maxScale: params.maxScale || 1.2
|
|
312
|
+
}, { timeoutMs: 90000 });
|
|
313
|
+
|
|
314
|
+
if (response && response.success === false) {
|
|
315
|
+
return {
|
|
316
|
+
success: false,
|
|
317
|
+
error: response.error || response.message || 'Failed to create landscape grass type'
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const result = response.result as any;
|
|
322
|
+
return {
|
|
323
|
+
success: true,
|
|
324
|
+
message: response?.message || `Landscape grass type '${name}' created`,
|
|
325
|
+
assetPath: result?.asset_path || response?.assetPath || response?.asset_path
|
|
326
|
+
};
|
|
327
|
+
} catch (error) {
|
|
328
|
+
return {
|
|
329
|
+
success: false,
|
|
330
|
+
error: `Failed to create landscape grass type: ${error instanceof Error ? error.message : String(error)}`
|
|
331
|
+
};
|
|
332
|
+
}
|
|
438
333
|
}
|
|
439
334
|
|
|
440
|
-
// Set landscape
|
|
441
|
-
async
|
|
442
|
-
landscapeName:
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
const commands: string[] = [];
|
|
448
|
-
|
|
449
|
-
if (params.lodBias !== undefined) {
|
|
450
|
-
commands.push(`SetLandscapeLODBias ${params.landscapeName} ${params.lodBias}`);
|
|
335
|
+
// Set the material used by an existing landscape actor
|
|
336
|
+
async setLandscapeMaterial(params: { landscapeName: string; materialPath: string }): Promise<any> {
|
|
337
|
+
const landscapeName = typeof params.landscapeName === 'string' ? params.landscapeName.trim() : '';
|
|
338
|
+
const materialPath = typeof params.materialPath === 'string' ? params.materialPath.trim() : '';
|
|
339
|
+
|
|
340
|
+
if (!landscapeName) {
|
|
341
|
+
return { success: false, error: 'Landscape name is required' };
|
|
451
342
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
commands.push(`SetLandscapeForcedLOD ${params.landscapeName} ${params.forcedLOD}`);
|
|
343
|
+
if (!materialPath) {
|
|
344
|
+
return { success: false, error: 'materialPath is required' };
|
|
455
345
|
}
|
|
456
|
-
|
|
457
|
-
if (
|
|
458
|
-
|
|
346
|
+
|
|
347
|
+
if (!this.automationBridge) {
|
|
348
|
+
throw new Error('Automation Bridge not available. Landscape operations require plugin support.');
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
try {
|
|
352
|
+
const response: any = await this.automationBridge.sendAutomationRequest('set_landscape_material', {
|
|
353
|
+
landscapeName,
|
|
354
|
+
materialPath
|
|
355
|
+
}, { timeoutMs: 60000 });
|
|
356
|
+
|
|
357
|
+
if (response && response.success === false) {
|
|
358
|
+
return {
|
|
359
|
+
success: false,
|
|
360
|
+
error: response.error || response.message || 'Failed to set landscape material'
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
success: true,
|
|
366
|
+
message: response?.message || `Landscape material set on '${landscapeName}'`,
|
|
367
|
+
landscapeName: response?.landscapeName || landscapeName,
|
|
368
|
+
materialPath: response?.materialPath || materialPath
|
|
369
|
+
};
|
|
370
|
+
} catch (error) {
|
|
371
|
+
return {
|
|
372
|
+
success: false,
|
|
373
|
+
error: `Failed to set landscape material: ${error instanceof Error ? error.message : String(error)}`
|
|
374
|
+
};
|
|
459
375
|
}
|
|
460
|
-
|
|
461
|
-
await this.bridge.executeConsoleCommands(commands);
|
|
462
|
-
|
|
463
|
-
return { success: true, message: 'Landscape LOD settings updated' };
|
|
464
376
|
}
|
|
465
377
|
|
|
466
378
|
// Create landscape grass
|
|
@@ -472,24 +384,24 @@ print("RESULT:" + json.dumps(result))
|
|
|
472
384
|
maxScale?: number;
|
|
473
385
|
randomRotation?: boolean;
|
|
474
386
|
}) {
|
|
475
|
-
|
|
476
|
-
|
|
387
|
+
const commands: string[] = [];
|
|
388
|
+
|
|
477
389
|
commands.push(`CreateLandscapeGrass ${params.landscapeName} ${params.grassType}`);
|
|
478
|
-
|
|
390
|
+
|
|
479
391
|
if (params.density !== undefined) {
|
|
480
392
|
commands.push(`SetGrassDensity ${params.grassType} ${params.density}`);
|
|
481
393
|
}
|
|
482
|
-
|
|
394
|
+
|
|
483
395
|
if (params.minScale !== undefined && params.maxScale !== undefined) {
|
|
484
396
|
commands.push(`SetGrassScale ${params.grassType} ${params.minScale} ${params.maxScale}`);
|
|
485
397
|
}
|
|
486
|
-
|
|
398
|
+
|
|
487
399
|
if (params.randomRotation !== undefined) {
|
|
488
400
|
commands.push(`SetGrassRandomRotation ${params.grassType} ${params.randomRotation}`);
|
|
489
401
|
}
|
|
490
|
-
|
|
402
|
+
|
|
491
403
|
await this.bridge.executeConsoleCommands(commands);
|
|
492
|
-
|
|
404
|
+
|
|
493
405
|
return { success: true, message: `Grass type ${params.grassType} created on landscape` };
|
|
494
406
|
}
|
|
495
407
|
|
|
@@ -499,20 +411,20 @@ print("RESULT:" + json.dumps(result))
|
|
|
499
411
|
collisionMipLevel?: number;
|
|
500
412
|
simpleCollision?: boolean;
|
|
501
413
|
}) {
|
|
502
|
-
|
|
503
|
-
|
|
414
|
+
const commands: string[] = [];
|
|
415
|
+
|
|
504
416
|
if (params.collisionMipLevel !== undefined) {
|
|
505
417
|
commands.push(`SetLandscapeCollisionMipLevel ${params.landscapeName} ${params.collisionMipLevel}`);
|
|
506
418
|
}
|
|
507
|
-
|
|
419
|
+
|
|
508
420
|
if (params.simpleCollision !== undefined) {
|
|
509
421
|
commands.push(`SetLandscapeSimpleCollision ${params.landscapeName} ${params.simpleCollision}`);
|
|
510
422
|
}
|
|
511
|
-
|
|
423
|
+
|
|
512
424
|
commands.push(`UpdateLandscapeCollision ${params.landscapeName}`);
|
|
513
|
-
|
|
425
|
+
|
|
514
426
|
await this.bridge.executeConsoleCommands(commands);
|
|
515
|
-
|
|
427
|
+
|
|
516
428
|
return { success: true, message: 'Landscape collision updated' };
|
|
517
429
|
}
|
|
518
430
|
|
|
@@ -522,20 +434,20 @@ print("RESULT:" + json.dumps(result))
|
|
|
522
434
|
targetTriangleCount?: number;
|
|
523
435
|
preserveDetails?: boolean;
|
|
524
436
|
}) {
|
|
525
|
-
|
|
526
|
-
|
|
437
|
+
const commands: string[] = [];
|
|
438
|
+
|
|
527
439
|
if (params.targetTriangleCount !== undefined) {
|
|
528
440
|
commands.push(`SetRetopologizeTarget ${params.targetTriangleCount}`);
|
|
529
441
|
}
|
|
530
|
-
|
|
442
|
+
|
|
531
443
|
if (params.preserveDetails !== undefined) {
|
|
532
444
|
commands.push(`SetRetopologizePreserveDetails ${params.preserveDetails}`);
|
|
533
445
|
}
|
|
534
|
-
|
|
446
|
+
|
|
535
447
|
commands.push(`RetopologizeLandscape ${params.landscapeName}`);
|
|
536
|
-
|
|
448
|
+
|
|
537
449
|
await this.bridge.executeConsoleCommands(commands);
|
|
538
|
-
|
|
450
|
+
|
|
539
451
|
return { success: true, message: 'Landscape retopologized' };
|
|
540
452
|
}
|
|
541
453
|
|
|
@@ -550,9 +462,9 @@ print("RESULT:" + json.dumps(result))
|
|
|
550
462
|
const loc = params.location || [0, 0, 0];
|
|
551
463
|
const size = params.size || [1000, 1000];
|
|
552
464
|
const depth = params.depth || 100;
|
|
553
|
-
|
|
465
|
+
|
|
554
466
|
const command = `CreateWaterBody ${params.type} ${params.name} ${loc.join(' ')} ${size.join(' ')} ${depth}`;
|
|
555
|
-
|
|
467
|
+
|
|
556
468
|
return this.bridge.executeConsoleCommand(command);
|
|
557
469
|
}
|
|
558
470
|
|
|
@@ -564,104 +476,35 @@ print("RESULT:" + json.dumps(result))
|
|
|
564
476
|
dataLayers?: string[];
|
|
565
477
|
streamingDistance?: number;
|
|
566
478
|
}) {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
import json
|
|
571
|
-
|
|
572
|
-
result = {'success': False, 'error': 'Landscape not found'}
|
|
573
|
-
|
|
574
|
-
try:
|
|
575
|
-
# Get the landscape actor using modern EditorActorSubsystem
|
|
576
|
-
actors = []
|
|
577
|
-
try:
|
|
578
|
-
actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
|
|
579
|
-
if actor_subsystem and hasattr(actor_subsystem, 'get_all_level_actors'):
|
|
580
|
-
actors = actor_subsystem.get_all_level_actors()
|
|
581
|
-
except Exception:
|
|
582
|
-
actors = []
|
|
583
|
-
landscape = None
|
|
584
|
-
|
|
585
|
-
for actor in actors:
|
|
586
|
-
if actor.get_name() == "${params.landscapeName}" or actor.get_actor_label() == "${params.landscapeName}":
|
|
587
|
-
if isinstance(actor, unreal.LandscapeProxy) or isinstance(actor, unreal.Landscape):
|
|
588
|
-
landscape = actor
|
|
589
|
-
break
|
|
590
|
-
|
|
591
|
-
if landscape:
|
|
592
|
-
changes_made = []
|
|
593
|
-
|
|
594
|
-
# Configure spatial loading (UE 5.6)
|
|
595
|
-
if ${params.enableSpatialLoading !== undefined ? 'True' : 'False'}:
|
|
596
|
-
try:
|
|
597
|
-
landscape.set_editor_property('is_spatially_loaded', ${params.enableSpatialLoading || false})
|
|
598
|
-
changes_made.append("Spatial loading: ${params.enableSpatialLoading}")
|
|
599
|
-
except:
|
|
600
|
-
pass
|
|
601
|
-
|
|
602
|
-
# Set runtime grid (UE 5.6 World Partition)
|
|
603
|
-
if "${params.runtimeGrid || ''}":
|
|
604
|
-
try:
|
|
605
|
-
landscape.set_editor_property('runtime_grid', unreal.Name("${params.runtimeGrid}"))
|
|
606
|
-
changes_made.append("Runtime grid: ${params.runtimeGrid}")
|
|
607
|
-
except:
|
|
608
|
-
pass
|
|
609
|
-
|
|
610
|
-
# Configure data layers (UE 5.6)
|
|
611
|
-
if ${params.dataLayers ? 'True' : 'False'}:
|
|
612
|
-
try:
|
|
613
|
-
# Try modern subsystem first
|
|
614
|
-
try:
|
|
615
|
-
world = None
|
|
616
|
-
editor_subsystem = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
|
|
617
|
-
if editor_subsystem and hasattr(editor_subsystem, 'get_editor_world'):
|
|
618
|
-
world = editor_subsystem.get_editor_world()
|
|
619
|
-
if world is None:
|
|
620
|
-
world = unreal.EditorSubsystemLibrary.get_editor_world()
|
|
621
|
-
except Exception:
|
|
622
|
-
world = unreal.EditorSubsystemLibrary.get_editor_world()
|
|
623
|
-
data_layer_manager = unreal.WorldPartitionBlueprintLibrary.get_data_layer_manager(world)
|
|
624
|
-
if data_layer_manager:
|
|
625
|
-
# Note: Full data layer API requires additional setup
|
|
626
|
-
changes_made.append("Data layers: Requires manual configuration")
|
|
627
|
-
except:
|
|
628
|
-
pass
|
|
629
|
-
|
|
630
|
-
if changes_made:
|
|
631
|
-
result = {
|
|
632
|
-
'success': True,
|
|
633
|
-
'message': 'World Partition configured',
|
|
634
|
-
'changes': changes_made
|
|
635
|
-
}
|
|
636
|
-
else:
|
|
637
|
-
result = {
|
|
638
|
-
'success': False,
|
|
639
|
-
'error': 'No World Partition changes applied'
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
except Exception as e:
|
|
643
|
-
result = {'success': False, 'error': str(e)}
|
|
644
|
-
|
|
645
|
-
print('RESULT:' + json.dumps(result))
|
|
646
|
-
`.trim();
|
|
479
|
+
if (!this.automationBridge) {
|
|
480
|
+
throw new Error('Automation Bridge not available. World Partition operations require plugin support.');
|
|
481
|
+
}
|
|
647
482
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
483
|
+
try {
|
|
484
|
+
const response = await this.automationBridge.sendAutomationRequest('configure_landscape_world_partition', {
|
|
485
|
+
landscapeName: params.landscapeName,
|
|
486
|
+
enableSpatialLoading: params.enableSpatialLoading,
|
|
487
|
+
runtimeGrid: params.runtimeGrid || '',
|
|
488
|
+
dataLayers: params.dataLayers || [],
|
|
489
|
+
streamingDistance: params.streamingDistance
|
|
490
|
+
}, {
|
|
491
|
+
timeoutMs: 60000
|
|
492
|
+
});
|
|
653
493
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
494
|
+
if (response.success === false) {
|
|
495
|
+
return {
|
|
496
|
+
success: false,
|
|
497
|
+
error: response.error || response.message || 'World Partition configuration failed'
|
|
498
|
+
};
|
|
499
|
+
}
|
|
657
500
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
501
|
+
return {
|
|
502
|
+
success: true,
|
|
503
|
+
message: response.message || 'World Partition configured',
|
|
504
|
+
changes: response.changes
|
|
505
|
+
};
|
|
663
506
|
} catch (err) {
|
|
664
|
-
return { success: false, error: `Failed to configure World Partition: ${err}` };
|
|
507
|
+
return { success: false, error: `Failed to configure World Partition: ${err instanceof Error ? err.message : String(err)}` };
|
|
665
508
|
}
|
|
666
509
|
}
|
|
667
510
|
|
|
@@ -673,7 +516,7 @@ print('RESULT:' + json.dumps(result))
|
|
|
673
516
|
}) {
|
|
674
517
|
try {
|
|
675
518
|
const commands = [];
|
|
676
|
-
|
|
519
|
+
|
|
677
520
|
// Use console commands for data layer management
|
|
678
521
|
if (params.operation === 'set' || params.operation === 'add') {
|
|
679
522
|
for (const layerName of params.dataLayerNames) {
|
|
@@ -684,12 +527,12 @@ print('RESULT:' + json.dumps(result))
|
|
|
684
527
|
commands.push(`wp.Runtime.SetDataLayerRuntimeState Unloaded ${layerName}`);
|
|
685
528
|
}
|
|
686
529
|
}
|
|
687
|
-
|
|
530
|
+
|
|
688
531
|
// Execute commands
|
|
689
532
|
await this.bridge.executeConsoleCommands(commands);
|
|
690
|
-
|
|
691
|
-
return {
|
|
692
|
-
success: true,
|
|
533
|
+
|
|
534
|
+
return {
|
|
535
|
+
success: true,
|
|
693
536
|
message: `Data layers ${params.operation === 'add' ? 'added' : params.operation === 'remove' ? 'removed' : 'set'} for landscape`,
|
|
694
537
|
layers: params.dataLayerNames
|
|
695
538
|
};
|
|
@@ -706,24 +549,24 @@ print('RESULT:' + json.dumps(result))
|
|
|
706
549
|
enableHLOD?: boolean;
|
|
707
550
|
}) {
|
|
708
551
|
const commands = [];
|
|
709
|
-
|
|
552
|
+
|
|
710
553
|
// World Partition runtime commands
|
|
711
554
|
if (params.loadingRange !== undefined) {
|
|
712
555
|
commands.push(`wp.Runtime.OverrideRuntimeSpatialHashLoadingRange -grid=0 -range=${params.loadingRange}`);
|
|
713
556
|
}
|
|
714
|
-
|
|
557
|
+
|
|
715
558
|
if (params.enableHLOD !== undefined) {
|
|
716
559
|
commands.push(`wp.Runtime.HLOD ${params.enableHLOD ? '1' : '0'}`);
|
|
717
560
|
}
|
|
718
|
-
|
|
561
|
+
|
|
719
562
|
// Debug visualization commands
|
|
720
563
|
commands.push('wp.Runtime.ToggleDrawRuntimeHash2D'); // Show 2D grid
|
|
721
|
-
|
|
564
|
+
|
|
722
565
|
try {
|
|
723
566
|
await this.bridge.executeConsoleCommands(commands);
|
|
724
|
-
|
|
725
|
-
return {
|
|
726
|
-
success: true,
|
|
567
|
+
|
|
568
|
+
return {
|
|
569
|
+
success: true,
|
|
727
570
|
message: 'Streaming cells configured for World Partition',
|
|
728
571
|
settings: {
|
|
729
572
|
cellSize: params.cellSize,
|
|
@@ -735,4 +578,66 @@ print('RESULT:' + json.dumps(result))
|
|
|
735
578
|
return { success: false, error: `Failed to configure streaming cells: ${err}` };
|
|
736
579
|
}
|
|
737
580
|
}
|
|
581
|
+
|
|
582
|
+
// Modify landscape heightmap
|
|
583
|
+
async modifyHeightmap(params: {
|
|
584
|
+
landscapeName: string;
|
|
585
|
+
heightData: number[];
|
|
586
|
+
minX: number;
|
|
587
|
+
minY: number;
|
|
588
|
+
maxX: number;
|
|
589
|
+
maxY: number;
|
|
590
|
+
updateNormals?: boolean;
|
|
591
|
+
}) {
|
|
592
|
+
if (!this.automationBridge) {
|
|
593
|
+
throw new Error('Automation Bridge not available. Landscape operations require plugin support.');
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const { landscapeName, heightData, minX, minY, maxX, maxY } = params;
|
|
597
|
+
|
|
598
|
+
if (!landscapeName) {
|
|
599
|
+
return { success: false, error: 'Landscape name is required' };
|
|
600
|
+
}
|
|
601
|
+
if (!heightData || !Array.isArray(heightData) || heightData.length === 0) {
|
|
602
|
+
return { success: false, error: 'heightData array is required' };
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const width = maxX - minX + 1;
|
|
606
|
+
const height = maxY - minY + 1;
|
|
607
|
+
if (heightData.length !== width * height) {
|
|
608
|
+
return {
|
|
609
|
+
success: false,
|
|
610
|
+
error: `Height data length (${heightData.length}) does not match region dimensions (${width}x${height} = ${width * height})`
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
try {
|
|
615
|
+
const response = await this.automationBridge.sendAutomationRequest('modify_heightmap', {
|
|
616
|
+
landscapeName,
|
|
617
|
+
heightData,
|
|
618
|
+
minX,
|
|
619
|
+
minY,
|
|
620
|
+
maxX,
|
|
621
|
+
maxY,
|
|
622
|
+
updateNormals: params.updateNormals ?? true
|
|
623
|
+
}, {
|
|
624
|
+
timeoutMs: 60000
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
if (response.success === false) {
|
|
628
|
+
return {
|
|
629
|
+
success: false,
|
|
630
|
+
error: response.error || response.message || 'Failed to modify heightmap'
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
return {
|
|
635
|
+
success: true,
|
|
636
|
+
message: response.message || 'Heightmap modified successfully'
|
|
637
|
+
};
|
|
638
|
+
} catch (err) {
|
|
639
|
+
return { success: false, error: `Failed to modify heightmap: ${err instanceof Error ? err.message : String(err)}` };
|
|
640
|
+
}
|
|
641
|
+
}
|
|
738
642
|
}
|
|
643
|
+
|