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/blueprint.ts
CHANGED
|
@@ -1,1052 +1,874 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BaseTool } from './base-tool.js';
|
|
2
|
+
import { IBlueprintTools } from '../types/tool-interfaces.js';
|
|
3
|
+
import { Logger } from '../utils/logger.js';
|
|
2
4
|
import { validateAssetParams, concurrencyDelay } from '../utils/validation.js';
|
|
3
|
-
import {
|
|
4
|
-
import { interpretStandardResult, coerceBoolean, coerceString, coerceStringArray, bestEffortInterpretedText } from '../utils/result-helpers.js';
|
|
5
|
-
import { escapePythonString } from '../utils/python.js';
|
|
5
|
+
import { coerceString } from '../utils/result-helpers.js';
|
|
6
6
|
|
|
7
|
-
export class BlueprintTools {
|
|
8
|
-
|
|
7
|
+
export class BlueprintTools extends BaseTool implements IBlueprintTools {
|
|
8
|
+
private log = new Logger('BlueprintTools');
|
|
9
|
+
private pluginBlueprintActionsAvailable: boolean | null = null;
|
|
9
10
|
|
|
10
|
-
private async
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
private async sendAction(action: string, payload: Record<string, unknown> = {}, options?: { timeoutMs?: number; waitForEvent?: boolean; waitForEventTimeoutMs?: number }) {
|
|
12
|
+
const envDefault = Number(process.env.MCP_AUTOMATION_REQUEST_TIMEOUT_MS ?? '120000');
|
|
13
|
+
const defaultTimeout = Number.isFinite(envDefault) && envDefault > 0 ? envDefault : 120000;
|
|
14
|
+
const finalTimeout = typeof options?.timeoutMs === 'number' && options?.timeoutMs > 0 ? options.timeoutMs : defaultTimeout;
|
|
15
|
+
try {
|
|
16
|
+
const response: any = await this.sendAutomationRequest(action, payload, { timeoutMs: finalTimeout, waitForEvent: !!options?.waitForEvent, waitForEventTimeoutMs: options?.waitForEventTimeoutMs });
|
|
17
|
+
const success = response && response.success !== false;
|
|
18
|
+
const result = response.result ?? response;
|
|
19
|
+
return { success, message: response.message ?? undefined, error: response.success === false ? (response.error ?? response.message) : undefined, result, requestId: response.requestId } as any;
|
|
20
|
+
} catch (err: any) {
|
|
21
|
+
return { success: false, error: String(err), message: String(err) } as const;
|
|
14
22
|
}
|
|
23
|
+
}
|
|
15
24
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
25
|
+
private isUnknownActionResponse(res: any): boolean {
|
|
26
|
+
if (!res) return false;
|
|
27
|
+
const txt = String((res.error ?? res.message ?? '')).toLowerCase();
|
|
28
|
+
// Only treat specific error codes as "not implemented"
|
|
29
|
+
return txt.includes('unknown_action') || txt.includes('unknown automation action') || txt.includes('not_implemented') || txt === 'unknown_plugin_action';
|
|
30
|
+
}
|
|
20
31
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
32
|
+
private buildCandidates(rawName: string | undefined): string[] {
|
|
33
|
+
const trimmed = coerceString(rawName)?.trim();
|
|
34
|
+
if (!trimmed) return [];
|
|
35
|
+
const normalized = trimmed.replace(/\\/g, '/').replace(/\/\/+/g, '/');
|
|
36
|
+
const withoutLeading = normalized.replace(/^\/+/, '');
|
|
37
|
+
const basename = withoutLeading.split('/').pop() ?? withoutLeading;
|
|
38
|
+
const candidates: string[] = [];
|
|
39
|
+
if (normalized.includes('/')) {
|
|
40
|
+
if (normalized.startsWith('/')) candidates.push(normalized);
|
|
41
|
+
if (basename) {
|
|
42
|
+
candidates.push(`/Game/Blueprints/${basename}`);
|
|
43
|
+
candidates.push(`/Game/${basename}`);
|
|
44
|
+
}
|
|
45
|
+
if (!normalized.startsWith('/')) candidates.push(`/${withoutLeading}`);
|
|
46
|
+
} else {
|
|
47
|
+
if (basename) {
|
|
48
|
+
candidates.push(`/Game/Blueprints/${basename}`);
|
|
49
|
+
candidates.push(`/Game/${basename}`);
|
|
50
|
+
}
|
|
51
|
+
candidates.push(normalized);
|
|
52
|
+
candidates.push(`/${withoutLeading}`);
|
|
53
|
+
}
|
|
54
|
+
return candidates.filter(Boolean);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async createBlueprint(params: { name: string; blueprintType?: string; savePath?: string; parentClass?: string; properties?: Record<string, unknown>; timeoutMs?: number; waitForCompletion?: boolean; waitForCompletionTimeoutMs?: number }) {
|
|
58
|
+
try {
|
|
59
|
+
const validation = validateAssetParams({ name: params.name, savePath: params.savePath || '/Game/Blueprints' });
|
|
60
|
+
if (!validation.valid) return { success: false, message: `Failed to create blueprint: ${validation.error}`, error: validation.error };
|
|
61
|
+
const sanitized = validation.sanitized;
|
|
62
|
+
const payload: Record<string, unknown> = { name: sanitized.name, blueprintType: params.blueprintType ?? 'Actor', savePath: sanitized.savePath ?? '/Game/Blueprints', parentClass: params.parentClass, properties: params.properties, waitForCompletion: !!params.waitForCompletion };
|
|
63
|
+
await concurrencyDelay();
|
|
64
|
+
|
|
65
|
+
if (this.pluginBlueprintActionsAvailable === false) {
|
|
66
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement blueprint_create' } as const;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const envPluginTimeout = Number(process.env.MCP_AUTOMATION_PLUGIN_CREATE_TIMEOUT_MS ?? process.env.MCP_AUTOMATION_REQUEST_TIMEOUT_MS ?? '15000');
|
|
70
|
+
const pluginTimeout = Number.isFinite(envPluginTimeout) && envPluginTimeout > 0 ? envPluginTimeout : 15000;
|
|
71
|
+
try {
|
|
72
|
+
const res = await this.sendAction('blueprint_create', payload, { timeoutMs: typeof params.timeoutMs === 'number' ? params.timeoutMs : pluginTimeout, waitForEvent: !!params.waitForCompletion, waitForEventTimeoutMs: params.waitForCompletionTimeoutMs });
|
|
73
|
+
if (res && res.success) {
|
|
74
|
+
this.pluginBlueprintActionsAvailable = true;
|
|
75
|
+
// Enrich response for Validator
|
|
76
|
+
return {
|
|
77
|
+
...res,
|
|
78
|
+
blueprint: sanitized.name,
|
|
79
|
+
path: `${sanitized.savePath}/${sanitized.name}`.replace('//', '/'),
|
|
80
|
+
message: res.message || `Created blueprint ${sanitized.name}`
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (res && this.isUnknownActionResponse(res)) {
|
|
84
|
+
this.pluginBlueprintActionsAvailable = false;
|
|
85
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement blueprint_create' } as const;
|
|
86
|
+
}
|
|
87
|
+
return res as any;
|
|
88
|
+
} catch (err: any) {
|
|
89
|
+
// ... (unchanged catch block)
|
|
90
|
+
const errTxt = String(err ?? '');
|
|
91
|
+
const isTimeout = errTxt.includes('Request timed out') || errTxt.includes('-32001') || errTxt.toLowerCase().includes('timeout');
|
|
92
|
+
if (isTimeout) {
|
|
93
|
+
this.pluginBlueprintActionsAvailable = false;
|
|
94
|
+
}
|
|
95
|
+
return { success: false, error: String(err), message: String(err) } as const;
|
|
96
|
+
}
|
|
97
|
+
} catch (err: any) {
|
|
98
|
+
return { success: false, error: String(err), message: String(err) };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async modifyConstructionScript(params: { blueprintPath: string; operations: any[]; compile?: boolean; save?: boolean; timeoutMs?: number; waitForCompletion?: boolean; waitForCompletionTimeoutMs?: number }) {
|
|
103
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
104
|
+
if (!blueprintPath) return { success: false, message: 'Blueprint path is required', error: 'INVALID_BLUEPRINT_PATH' };
|
|
105
|
+
if (!Array.isArray(params.operations) || params.operations.length === 0) return { success: false, message: 'At least one SCS operation is required', error: 'MISSING_OPERATIONS' };
|
|
26
106
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return unreal.load_class(None, name)
|
|
35
|
-
except Exception:
|
|
36
|
-
pass
|
|
37
|
-
try:
|
|
38
|
-
if name.startswith('/Game/'):
|
|
39
|
-
asset = editor_lib.load_asset(name)
|
|
40
|
-
if asset:
|
|
41
|
-
if hasattr(asset, 'generated_class'):
|
|
42
|
-
try:
|
|
43
|
-
generated = asset.generated_class()
|
|
44
|
-
if generated:
|
|
45
|
-
return generated
|
|
46
|
-
except Exception:
|
|
47
|
-
pass
|
|
48
|
-
return asset
|
|
49
|
-
except Exception:
|
|
50
|
-
pass
|
|
51
|
-
try:
|
|
52
|
-
candidate = getattr(unreal, name, None)
|
|
53
|
-
if candidate:
|
|
54
|
-
return candidate
|
|
55
|
-
except Exception:
|
|
56
|
-
pass
|
|
57
|
-
return None
|
|
58
|
-
|
|
59
|
-
try:
|
|
60
|
-
parent_spec = r"${escapedParent}"
|
|
61
|
-
resolved = resolve_parent(parent_spec, "${blueprintType}")
|
|
62
|
-
resolved_path = ''
|
|
63
|
-
|
|
64
|
-
if resolved:
|
|
65
|
-
try:
|
|
66
|
-
resolved_path = resolved.get_path_name()
|
|
67
|
-
except Exception:
|
|
68
|
-
try:
|
|
69
|
-
resolved_path = str(resolved.get_outer().get_path_name())
|
|
70
|
-
except Exception:
|
|
71
|
-
resolved_path = str(resolved)
|
|
72
|
-
|
|
73
|
-
normalized_resolved = resolved_path.replace('Class ', '').replace('class ', '').strip().lower()
|
|
74
|
-
normalized_spec = parent_spec.strip().lower()
|
|
75
|
-
|
|
76
|
-
if normalized_spec.startswith('/script/'):
|
|
77
|
-
if not normalized_resolved.endswith(normalized_spec):
|
|
78
|
-
resolved = None
|
|
79
|
-
elif normalized_spec.startswith('/game/'):
|
|
80
|
-
try:
|
|
81
|
-
if not unreal.EditorAssetLibrary.does_asset_exist(parent_spec):
|
|
82
|
-
resolved = None
|
|
83
|
-
except Exception:
|
|
84
|
-
resolved = None
|
|
85
|
-
|
|
86
|
-
if resolved:
|
|
87
|
-
result['success'] = True
|
|
88
|
-
try:
|
|
89
|
-
result['resolved'] = resolved_path or str(resolved)
|
|
90
|
-
except Exception:
|
|
91
|
-
result['resolved'] = str(resolved)
|
|
92
|
-
else:
|
|
93
|
-
result['error'] = 'Parent class not found: ' + parent_spec
|
|
94
|
-
except Exception as e:
|
|
95
|
-
result['error'] = str(e)
|
|
96
|
-
|
|
97
|
-
print('RESULT:' + json.dumps(result))
|
|
98
|
-
`.trim();
|
|
107
|
+
// Fix: Map 'op' to 'type' if missing, for backward compatibility or user convenience
|
|
108
|
+
const operations = params.operations.map(op => {
|
|
109
|
+
if (op && typeof op === 'object' && op.op && !op.type) {
|
|
110
|
+
return { ...op, type: op.op };
|
|
111
|
+
}
|
|
112
|
+
return op;
|
|
113
|
+
});
|
|
99
114
|
|
|
115
|
+
const payload: any = { blueprintPath, operations };
|
|
116
|
+
if (typeof params.compile === 'boolean') payload.compile = params.compile;
|
|
117
|
+
if (typeof params.save === 'boolean') payload.save = params.save;
|
|
118
|
+
const res = await this.sendAction('blueprint_modify_scs', payload, { timeoutMs: params.timeoutMs, waitForEvent: !!params.waitForCompletion, waitForEventTimeoutMs: params.waitForCompletionTimeoutMs });
|
|
119
|
+
|
|
120
|
+
if (res && res.result && typeof res.result === 'object' && (res.result as any).error === 'SCS_UNAVAILABLE') {
|
|
121
|
+
this.pluginBlueprintActionsAvailable = false;
|
|
122
|
+
return { success: false, error: 'SCS_UNAVAILABLE', message: 'Plugin does not support construction script modification (blueprint_modify_scs)' } as const;
|
|
123
|
+
}
|
|
124
|
+
if (res && res.success) this.pluginBlueprintActionsAvailable = true;
|
|
125
|
+
if (res && this.isUnknownActionResponse(res)) {
|
|
126
|
+
this.pluginBlueprintActionsAvailable = false;
|
|
127
|
+
}
|
|
128
|
+
return res;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async addComponent(params: { blueprintName: string; componentType: string; componentName: string; attachTo?: string; transform?: Record<string, unknown>; properties?: Record<string, unknown>; compile?: boolean; save?: boolean; timeoutMs?: number; waitForCompletion?: boolean; waitForCompletionTimeoutMs?: number }) {
|
|
132
|
+
const blueprintName = coerceString(params.blueprintName);
|
|
133
|
+
if (!blueprintName) return { success: false as const, message: 'Blueprint name is required', error: 'INVALID_BLUEPRINT' };
|
|
134
|
+
const componentClass = coerceString(params.componentType);
|
|
135
|
+
if (!componentClass) return { success: false as const, message: 'Component class is required', error: 'INVALID_COMPONENT_CLASS' };
|
|
136
|
+
const rawComponentName = coerceString(params.componentName) ?? params.componentName;
|
|
137
|
+
if (!rawComponentName) return { success: false as const, message: 'Component name is required', error: 'INVALID_COMPONENT_NAME' };
|
|
138
|
+
const sanitizedComponentName = rawComponentName.replace(/[^A-Za-z0-9_]/g, '_');
|
|
139
|
+
const candidates = this.buildCandidates(blueprintName);
|
|
140
|
+
const primary = candidates[0];
|
|
141
|
+
if (!primary) return { success: false as const, error: 'Invalid blueprint name' };
|
|
100
142
|
try {
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (interpreted.success) {
|
|
108
|
-
return { ok: true, resolved: (interpreted.payload as any)?.resolved ?? interpreted.message };
|
|
143
|
+
const op = { type: 'add_component', componentName: sanitizedComponentName, componentClass, attachTo: params.attachTo, transform: params.transform, properties: params.properties };
|
|
144
|
+
const svcResult = await this.modifyConstructionScript({ blueprintPath: primary, operations: [op], compile: params.compile, save: params.save, timeoutMs: params.timeoutMs, waitForCompletion: params.waitForCompletion, waitForCompletionTimeoutMs: params.waitForCompletionTimeoutMs });
|
|
145
|
+
if (svcResult && svcResult.success) {
|
|
146
|
+
this.pluginBlueprintActionsAvailable = true;
|
|
147
|
+
return { ...(svcResult as any), component: sanitizedComponentName, componentName: sanitizedComponentName, componentType: componentClass, componentClass, blueprintPath: svcResult.blueprintPath ?? primary } as const;
|
|
109
148
|
}
|
|
149
|
+
if (svcResult && (this.isUnknownActionResponse(svcResult) || (svcResult.error && svcResult.error === 'SCS_UNAVAILABLE'))) {
|
|
150
|
+
this.pluginBlueprintActionsAvailable = false;
|
|
151
|
+
return { success: false, error: 'SCS_UNAVAILABLE', message: 'Plugin does not support construction script modification (blueprint_modify_scs)' } as const;
|
|
152
|
+
}
|
|
153
|
+
return svcResult as any;
|
|
154
|
+
} catch (err: any) {
|
|
155
|
+
return { success: false, error: String(err) };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async waitForBlueprint(blueprintRef: string | string[], timeoutMs?: number) {
|
|
160
|
+
const candidates = Array.isArray(blueprintRef) ? blueprintRef : this.buildCandidates(blueprintRef as string | undefined);
|
|
161
|
+
if (!candidates || candidates.length === 0) return { success: false, error: 'Invalid blueprint reference', checked: [] } as any;
|
|
162
|
+
if (this.pluginBlueprintActionsAvailable === false) {
|
|
163
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement blueprint_exists' } as any;
|
|
164
|
+
}
|
|
110
165
|
|
|
111
|
-
|
|
112
|
-
|
|
166
|
+
const start = Date.now();
|
|
167
|
+
const envDefault = Number(process.env.MCP_AUTOMATION_SCS_TIMEOUT_MS ?? process.env.MCP_AUTOMATION_REQUEST_TIMEOUT_MS ?? '15000');
|
|
168
|
+
// Default to 15s (15000ms) instead of 120s to avoid long hangs on non-existent assets
|
|
169
|
+
const defaultTimeout = Number.isFinite(envDefault) && envDefault > 0 ? envDefault : 15000;
|
|
170
|
+
const tot = typeof timeoutMs === 'number' && timeoutMs > 0 ? timeoutMs : defaultTimeout;
|
|
171
|
+
const perCheck = Math.min(5000, Math.max(1000, Math.floor(tot / 6)));
|
|
172
|
+
while (Date.now() - start < tot) {
|
|
173
|
+
for (const candidate of candidates) {
|
|
174
|
+
try {
|
|
175
|
+
const r = await this.sendAction('blueprint_exists', { blueprintCandidates: [candidate], requestedPath: candidate }, { timeoutMs: Math.min(perCheck, tot) });
|
|
176
|
+
if (r && r.success && r.result && (r.result.exists === true || r.result.found)) {
|
|
177
|
+
this.pluginBlueprintActionsAvailable = true;
|
|
178
|
+
return { success: true, found: r.result.found ?? candidate } as any;
|
|
179
|
+
}
|
|
180
|
+
if (r && r.success === false && this.isUnknownActionResponse(r)) {
|
|
181
|
+
this.pluginBlueprintActionsAvailable = false;
|
|
182
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement blueprint_exists' } as any;
|
|
183
|
+
}
|
|
184
|
+
} catch (_e) {
|
|
185
|
+
// ignore and try next candidate
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
189
|
+
}
|
|
190
|
+
if (this.pluginBlueprintActionsAvailable === null) {
|
|
191
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin availability unknown; blueprint_exists not implemented by plugin' } as any;
|
|
192
|
+
}
|
|
193
|
+
return { success: false, error: `Timeout waiting for blueprint after ${tot}ms`, checked: candidates } as any;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async getBlueprint(params: { blueprintName: string; timeoutMs?: number; }) {
|
|
197
|
+
const candidates = this.buildCandidates(params.blueprintName);
|
|
198
|
+
const primary = candidates[0];
|
|
199
|
+
if (!primary) return { success: false, error: 'Invalid blueprint name' } as const;
|
|
200
|
+
try {
|
|
201
|
+
const pluginResp = await this.sendAction('blueprint_get', { blueprintCandidates: candidates, requestedPath: primary }, { timeoutMs: params.timeoutMs });
|
|
202
|
+
if (pluginResp && pluginResp.success) {
|
|
203
|
+
if (pluginResp && typeof pluginResp === 'object') {
|
|
204
|
+
return { ...pluginResp, blueprint: pluginResp.result, blueprintPath: primary } as any;
|
|
205
|
+
}
|
|
206
|
+
return pluginResp;
|
|
207
|
+
}
|
|
208
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
209
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement blueprint_get' } as const;
|
|
210
|
+
}
|
|
211
|
+
return { success: false, error: pluginResp?.error ?? 'BLUEPRINT_GET_FAILED', message: pluginResp?.message ?? 'Failed to get blueprint via automation bridge' } as const;
|
|
113
212
|
} catch (err: any) {
|
|
114
|
-
return {
|
|
213
|
+
return { success: false, error: String(err), message: String(err) } as const;
|
|
115
214
|
}
|
|
116
215
|
}
|
|
117
216
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
217
|
+
async getBlueprintInfo(params: { blueprintPath: string; timeoutMs?: number }) {
|
|
218
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
219
|
+
if (!blueprintPath) {
|
|
220
|
+
return { success: false, error: 'INVALID_BLUEPRINT_PATH', message: 'Blueprint path is required' } as const;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const candidates = this.buildCandidates(blueprintPath);
|
|
224
|
+
const primary = candidates[0] ?? blueprintPath;
|
|
225
|
+
|
|
127
226
|
try {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
if (!validation.valid) {
|
|
227
|
+
const resp = await this.sendAction('blueprint_get', { blueprintCandidates: candidates.length > 0 ? candidates : [primary], requestedPath: primary }, { timeoutMs: params.timeoutMs });
|
|
228
|
+
if (resp && resp.success) {
|
|
229
|
+
const result = resp.result ?? resp;
|
|
230
|
+
const resolvedPath = typeof result?.resolvedPath === 'string' ? result.resolvedPath : primary;
|
|
135
231
|
return {
|
|
136
|
-
success:
|
|
137
|
-
message: `
|
|
138
|
-
|
|
232
|
+
success: true,
|
|
233
|
+
message: resp.message ?? `Blueprint metadata retrieved for ${resolvedPath}`,
|
|
234
|
+
blueprintPath: resolvedPath,
|
|
235
|
+
blueprint: result,
|
|
236
|
+
result,
|
|
237
|
+
requestId: resp.requestId
|
|
139
238
|
};
|
|
140
239
|
}
|
|
141
|
-
const sanitizedParams = validation.sanitized;
|
|
142
|
-
const path = sanitizedParams.savePath || '/Game/Blueprints';
|
|
143
240
|
|
|
144
|
-
if (
|
|
145
|
-
|
|
146
|
-
return { success: false, message, error: message };
|
|
241
|
+
if (resp && this.isUnknownActionResponse(resp)) {
|
|
242
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement blueprint_get' } as const;
|
|
147
243
|
}
|
|
148
|
-
if (params.parentClass && params.parentClass.trim()) {
|
|
149
|
-
const parentValidation = await this.validateParentClassReference(params.parentClass, params.blueprintType);
|
|
150
|
-
if (!parentValidation.ok) {
|
|
151
|
-
const error = parentValidation.error || `Parent class not found: ${params.parentClass}`;
|
|
152
|
-
const message = `Failed to create blueprint: ${error}`;
|
|
153
|
-
return { success: false, message, error };
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
const escapedName = escapePythonString(sanitizedParams.name);
|
|
157
|
-
const escapedPath = escapePythonString(path);
|
|
158
|
-
const escapedParent = escapePythonString(params.parentClass ?? '');
|
|
159
244
|
|
|
160
|
-
|
|
245
|
+
return { success: false, error: resp?.error ?? 'BLUEPRINT_GET_FAILED', message: resp?.message ?? 'Failed to get blueprint via automation bridge' } as const;
|
|
246
|
+
} catch (err: any) {
|
|
247
|
+
return { success: false, error: String(err), message: String(err) } as const;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
161
250
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
try:
|
|
172
|
-
asset_subsystem = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem)
|
|
173
|
-
except Exception:
|
|
174
|
-
asset_subsystem = None
|
|
175
|
-
|
|
176
|
-
editor_lib = unreal.EditorAssetLibrary
|
|
177
|
-
|
|
178
|
-
asset = None
|
|
179
|
-
if asset_subsystem and hasattr(asset_subsystem, 'load_asset'):
|
|
180
|
-
try:
|
|
181
|
-
asset = asset_subsystem.load_asset(asset_path)
|
|
182
|
-
except Exception:
|
|
183
|
-
asset = None
|
|
184
|
-
if not asset:
|
|
185
|
-
try:
|
|
186
|
-
asset = editor_lib.load_asset(asset_path)
|
|
187
|
-
except Exception:
|
|
188
|
-
asset = None
|
|
189
|
-
if not asset:
|
|
190
|
-
return False
|
|
191
|
-
|
|
192
|
-
saved = False
|
|
193
|
-
if asset_subsystem and hasattr(asset_subsystem, 'save_loaded_asset'):
|
|
194
|
-
try:
|
|
195
|
-
saved = asset_subsystem.save_loaded_asset(asset)
|
|
196
|
-
except Exception:
|
|
197
|
-
saved = False
|
|
198
|
-
if not saved and asset_subsystem and hasattr(asset_subsystem, 'save_asset'):
|
|
199
|
-
try:
|
|
200
|
-
saved = asset_subsystem.save_asset(asset_path, only_if_is_dirty=False)
|
|
201
|
-
except Exception:
|
|
202
|
-
saved = False
|
|
203
|
-
if not saved:
|
|
204
|
-
try:
|
|
205
|
-
if hasattr(editor_lib, 'save_loaded_asset'):
|
|
206
|
-
saved = editor_lib.save_loaded_asset(asset)
|
|
207
|
-
else:
|
|
208
|
-
saved = editor_lib.save_asset(asset_path, only_if_is_dirty=False)
|
|
209
|
-
except Exception:
|
|
210
|
-
saved = False
|
|
211
|
-
|
|
212
|
-
if not saved:
|
|
213
|
-
return False
|
|
214
|
-
|
|
215
|
-
asset_dir = asset_path.rsplit('/', 1)[0]
|
|
216
|
-
try:
|
|
217
|
-
registry = unreal.AssetRegistryHelpers.get_asset_registry()
|
|
218
|
-
if hasattr(registry, 'scan_paths_synchronous'):
|
|
219
|
-
registry.scan_paths_synchronous([asset_dir], True)
|
|
220
|
-
except Exception:
|
|
221
|
-
pass
|
|
222
|
-
|
|
223
|
-
for _ in range(5):
|
|
224
|
-
if editor_lib.does_asset_exist(asset_path):
|
|
225
|
-
return True
|
|
226
|
-
time.sleep(0.2)
|
|
227
|
-
try:
|
|
228
|
-
registry = unreal.AssetRegistryHelpers.get_asset_registry()
|
|
229
|
-
if hasattr(registry, 'scan_paths_synchronous'):
|
|
230
|
-
registry.scan_paths_synchronous([asset_dir], True)
|
|
231
|
-
except Exception:
|
|
232
|
-
pass
|
|
233
|
-
return False
|
|
234
|
-
except Exception as e:
|
|
235
|
-
print(f"Error ensuring persistence: {e}")
|
|
236
|
-
return False
|
|
237
|
-
|
|
238
|
-
def resolve_parent_class(explicit_name, blueprint_type):
|
|
239
|
-
editor_lib = unreal.EditorAssetLibrary
|
|
240
|
-
name = (explicit_name or '').strip()
|
|
241
|
-
if name:
|
|
242
|
-
try:
|
|
243
|
-
if name.startswith('/Script/'):
|
|
244
|
-
try:
|
|
245
|
-
loaded = unreal.load_class(None, name)
|
|
246
|
-
if loaded:
|
|
247
|
-
return loaded
|
|
248
|
-
except Exception:
|
|
249
|
-
pass
|
|
250
|
-
if name.startswith('/Game/'):
|
|
251
|
-
loaded_asset = editor_lib.load_asset(name)
|
|
252
|
-
if loaded_asset:
|
|
253
|
-
if hasattr(loaded_asset, 'generated_class'):
|
|
254
|
-
try:
|
|
255
|
-
generated = loaded_asset.generated_class()
|
|
256
|
-
if generated:
|
|
257
|
-
return generated
|
|
258
|
-
except Exception:
|
|
259
|
-
pass
|
|
260
|
-
return loaded_asset
|
|
261
|
-
candidate = getattr(unreal, name, None)
|
|
262
|
-
if candidate:
|
|
263
|
-
return candidate
|
|
264
|
-
except Exception:
|
|
265
|
-
pass
|
|
266
|
-
return None
|
|
267
|
-
|
|
268
|
-
mapping = {
|
|
269
|
-
'Actor': unreal.Actor,
|
|
270
|
-
'Pawn': unreal.Pawn,
|
|
271
|
-
'Character': unreal.Character,
|
|
272
|
-
'GameMode': unreal.GameModeBase,
|
|
273
|
-
'PlayerController': unreal.PlayerController,
|
|
274
|
-
'HUD': unreal.HUD,
|
|
275
|
-
'ActorComponent': unreal.ActorComponent,
|
|
251
|
+
async probeSubobjectDataHandle(opts: { componentClass?: string } = {}) {
|
|
252
|
+
return await this.sendAction('blueprint_probe_subobject_handle', { componentClass: opts.componentClass });
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async setBlueprintDefault(params: { blueprintName: string; propertyName: string; value: unknown }) {
|
|
256
|
+
const candidates = this.buildCandidates(params.blueprintName);
|
|
257
|
+
const primary = candidates[0];
|
|
258
|
+
if (!primary) return { success: false as const, error: 'Invalid blueprint name' };
|
|
259
|
+
return await this.sendAction('blueprint_set_default', { blueprintCandidates: candidates, requestedPath: primary, propertyName: params.propertyName, value: params.value });
|
|
276
260
|
}
|
|
277
|
-
return mapping.get(blueprint_type, unreal.Actor)
|
|
278
|
-
|
|
279
|
-
result = {
|
|
280
|
-
'success': False,
|
|
281
|
-
'message': '',
|
|
282
|
-
'path': '',
|
|
283
|
-
'error': '',
|
|
284
|
-
'exists': False,
|
|
285
|
-
'parent': '',
|
|
286
|
-
'verifyError': '',
|
|
287
|
-
'warnings': [],
|
|
288
|
-
'details': []
|
|
289
|
-
}
|
|
290
261
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
if not success_message:
|
|
302
|
-
success_message = str(message)
|
|
303
|
-
|
|
304
|
-
def set_error(message):
|
|
305
|
-
result['error'] = str(message)
|
|
306
|
-
|
|
307
|
-
asset_path = "${escapedPath}"
|
|
308
|
-
asset_name = "${escapedName}"
|
|
309
|
-
full_path = f"{asset_path}/{asset_name}"
|
|
310
|
-
result['path'] = full_path
|
|
311
|
-
|
|
312
|
-
asset_subsystem = None
|
|
313
|
-
try:
|
|
314
|
-
asset_subsystem = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem)
|
|
315
|
-
except Exception:
|
|
316
|
-
asset_subsystem = None
|
|
317
|
-
|
|
318
|
-
editor_lib = unreal.EditorAssetLibrary
|
|
319
|
-
|
|
320
|
-
try:
|
|
321
|
-
level_subsystem = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
|
|
322
|
-
play_subsystem = None
|
|
323
|
-
try:
|
|
324
|
-
play_subsystem = unreal.get_editor_subsystem(unreal.EditorPlayWorldSubsystem)
|
|
325
|
-
except Exception:
|
|
326
|
-
play_subsystem = None
|
|
327
|
-
|
|
328
|
-
is_playing = False
|
|
329
|
-
if level_subsystem and hasattr(level_subsystem, 'is_in_play_in_editor'):
|
|
330
|
-
is_playing = bool(level_subsystem.is_in_play_in_editor())
|
|
331
|
-
elif play_subsystem and hasattr(play_subsystem, 'is_playing_in_editor'):
|
|
332
|
-
is_playing = bool(play_subsystem.is_playing_in_editor())
|
|
333
|
-
|
|
334
|
-
if is_playing:
|
|
335
|
-
print('Stopping Play In Editor mode...')
|
|
336
|
-
record_detail('Stopping Play In Editor mode')
|
|
337
|
-
if level_subsystem and hasattr(level_subsystem, 'editor_request_end_play'):
|
|
338
|
-
level_subsystem.editor_request_end_play()
|
|
339
|
-
elif play_subsystem and hasattr(play_subsystem, 'stop_playing_session'):
|
|
340
|
-
play_subsystem.stop_playing_session()
|
|
341
|
-
elif play_subsystem and hasattr(play_subsystem, 'end_play'):
|
|
342
|
-
play_subsystem.end_play()
|
|
343
|
-
else:
|
|
344
|
-
record_warning('Unable to stop Play In Editor via modern subsystems; please stop PIE manually.')
|
|
345
|
-
time.sleep(0.5)
|
|
346
|
-
except Exception as stop_err:
|
|
347
|
-
record_warning(f'PIE stop check failed: {stop_err}')
|
|
348
|
-
|
|
349
|
-
try:
|
|
350
|
-
try:
|
|
351
|
-
if asset_subsystem and hasattr(asset_subsystem, 'does_asset_exist'):
|
|
352
|
-
asset_exists = asset_subsystem.does_asset_exist(full_path)
|
|
353
|
-
else:
|
|
354
|
-
asset_exists = editor_lib.does_asset_exist(full_path)
|
|
355
|
-
except Exception:
|
|
356
|
-
asset_exists = editor_lib.does_asset_exist(full_path)
|
|
357
|
-
|
|
358
|
-
result['exists'] = bool(asset_exists)
|
|
359
|
-
|
|
360
|
-
if asset_exists:
|
|
361
|
-
existing = None
|
|
362
|
-
try:
|
|
363
|
-
if asset_subsystem and hasattr(asset_subsystem, 'load_asset'):
|
|
364
|
-
existing = asset_subsystem.load_asset(full_path)
|
|
365
|
-
elif asset_subsystem and hasattr(asset_subsystem, 'get_asset'):
|
|
366
|
-
existing = asset_subsystem.get_asset(full_path)
|
|
367
|
-
else:
|
|
368
|
-
existing = editor_lib.load_asset(full_path)
|
|
369
|
-
except Exception:
|
|
370
|
-
existing = editor_lib.load_asset(full_path)
|
|
371
|
-
|
|
372
|
-
if existing:
|
|
373
|
-
result['success'] = True
|
|
374
|
-
result['message'] = f"Blueprint already exists at {full_path}"
|
|
375
|
-
set_message(result['message'])
|
|
376
|
-
record_detail(result['message'])
|
|
377
|
-
try:
|
|
378
|
-
result['parent'] = str(existing.generated_class())
|
|
379
|
-
except Exception:
|
|
380
|
-
try:
|
|
381
|
-
result['parent'] = str(type(existing))
|
|
382
|
-
except Exception:
|
|
383
|
-
pass
|
|
384
|
-
else:
|
|
385
|
-
set_error(f"Asset exists but could not be loaded: {full_path}")
|
|
386
|
-
record_warning(result['error'])
|
|
387
|
-
else:
|
|
388
|
-
factory = unreal.BlueprintFactory()
|
|
389
|
-
explicit_parent = "${escapedParent}"
|
|
390
|
-
parent_class = None
|
|
391
|
-
|
|
392
|
-
if explicit_parent.strip():
|
|
393
|
-
parent_class = resolve_parent_class(explicit_parent, "${params.blueprintType}")
|
|
394
|
-
if not parent_class:
|
|
395
|
-
set_error(f"Parent class not found: {explicit_parent}")
|
|
396
|
-
record_warning(result['error'])
|
|
397
|
-
raise RuntimeError(result['error'])
|
|
398
|
-
else:
|
|
399
|
-
parent_class = resolve_parent_class('', "${params.blueprintType}")
|
|
400
|
-
|
|
401
|
-
if parent_class:
|
|
402
|
-
result['parent'] = str(parent_class)
|
|
403
|
-
record_detail(f"Resolved parent class: {result['parent']}")
|
|
404
|
-
try:
|
|
405
|
-
factory.set_editor_property('parent_class', parent_class)
|
|
406
|
-
except Exception:
|
|
407
|
-
try:
|
|
408
|
-
factory.set_editor_property('ParentClass', parent_class)
|
|
409
|
-
except Exception:
|
|
410
|
-
try:
|
|
411
|
-
factory.ParentClass = parent_class
|
|
412
|
-
except Exception:
|
|
413
|
-
pass
|
|
414
|
-
|
|
415
|
-
new_asset = None
|
|
416
|
-
try:
|
|
417
|
-
if asset_subsystem and hasattr(asset_subsystem, 'create_asset'):
|
|
418
|
-
new_asset = asset_subsystem.create_asset(
|
|
419
|
-
asset_name=asset_name,
|
|
420
|
-
package_path=asset_path,
|
|
421
|
-
asset_class=unreal.Blueprint,
|
|
422
|
-
factory=factory
|
|
423
|
-
)
|
|
424
|
-
else:
|
|
425
|
-
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
|
|
426
|
-
new_asset = asset_tools.create_asset(
|
|
427
|
-
asset_name=asset_name,
|
|
428
|
-
package_path=asset_path,
|
|
429
|
-
asset_class=unreal.Blueprint,
|
|
430
|
-
factory=factory
|
|
431
|
-
)
|
|
432
|
-
except Exception as create_error:
|
|
433
|
-
set_error(f"Asset creation failed: {create_error}")
|
|
434
|
-
record_warning(result['error'])
|
|
435
|
-
traceback.print_exc()
|
|
436
|
-
new_asset = None
|
|
437
|
-
|
|
438
|
-
if new_asset:
|
|
439
|
-
result['message'] = f"Blueprint created at {full_path}"
|
|
440
|
-
set_message(result['message'])
|
|
441
|
-
record_detail(result['message'])
|
|
442
|
-
if ensure_asset_persistence(full_path):
|
|
443
|
-
verified = False
|
|
444
|
-
try:
|
|
445
|
-
if asset_subsystem and hasattr(asset_subsystem, 'does_asset_exist'):
|
|
446
|
-
verified = asset_subsystem.does_asset_exist(full_path)
|
|
447
|
-
else:
|
|
448
|
-
verified = editor_lib.does_asset_exist(full_path)
|
|
449
|
-
except Exception as verify_error:
|
|
450
|
-
result['verifyError'] = str(verify_error)
|
|
451
|
-
verified = editor_lib.does_asset_exist(full_path)
|
|
452
|
-
|
|
453
|
-
if not verified:
|
|
454
|
-
time.sleep(0.2)
|
|
455
|
-
verified = editor_lib.does_asset_exist(full_path)
|
|
456
|
-
if not verified:
|
|
457
|
-
try:
|
|
458
|
-
verified = editor_lib.load_asset(full_path) is not None
|
|
459
|
-
except Exception:
|
|
460
|
-
verified = False
|
|
461
|
-
|
|
462
|
-
if verified:
|
|
463
|
-
result['success'] = True
|
|
464
|
-
result['error'] = ''
|
|
465
|
-
set_message(result['message'])
|
|
466
|
-
else:
|
|
467
|
-
set_error(f"Blueprint not found after save: {full_path}")
|
|
468
|
-
record_warning(result['error'])
|
|
469
|
-
else:
|
|
470
|
-
set_error('Failed to persist blueprint to disk')
|
|
471
|
-
record_warning(result['error'])
|
|
472
|
-
else:
|
|
473
|
-
if not result['error']:
|
|
474
|
-
set_error(f"Failed to create Blueprint {asset_name}")
|
|
475
|
-
except Exception as e:
|
|
476
|
-
set_error(str(e))
|
|
477
|
-
record_warning(result['error'])
|
|
478
|
-
traceback.print_exc()
|
|
479
|
-
|
|
480
|
-
# Finalize messaging
|
|
481
|
-
default_success_message = f"Blueprint created at {full_path}"
|
|
482
|
-
default_failure_message = f"Failed to create blueprint {asset_name}"
|
|
483
|
-
|
|
484
|
-
if result['success'] and not success_message:
|
|
485
|
-
set_message(default_success_message)
|
|
486
|
-
|
|
487
|
-
if not result['success'] and not result['error']:
|
|
488
|
-
set_error(default_failure_message)
|
|
489
|
-
|
|
490
|
-
if not result['message']:
|
|
491
|
-
if result['success']:
|
|
492
|
-
result['message'] = success_message or default_success_message
|
|
493
|
-
else:
|
|
494
|
-
result['message'] = result['error'] or default_failure_message
|
|
495
|
-
|
|
496
|
-
result['error'] = None if result['success'] else result['error']
|
|
497
|
-
|
|
498
|
-
if not result['warnings']:
|
|
499
|
-
result.pop('warnings')
|
|
500
|
-
if not result['details']:
|
|
501
|
-
result.pop('details')
|
|
502
|
-
if result.get('error') is None:
|
|
503
|
-
result.pop('error')
|
|
504
|
-
|
|
505
|
-
print('RESULT:' + json.dumps(result))
|
|
506
|
-
`.trim();
|
|
507
|
-
|
|
508
|
-
const response = await this.bridge.executePython(pythonScript);
|
|
509
|
-
return this.parseBlueprintCreationOutput(response, sanitizedParams.name, path);
|
|
510
|
-
} catch (err) {
|
|
511
|
-
return { success: false, error: `Failed to create blueprint: ${err}` };
|
|
262
|
+
async addVariable(params: { blueprintName: string; variableName: string; variableType: string; defaultValue?: any; category?: string; isReplicated?: boolean; isPublic?: boolean; variablePinType?: Record<string, unknown>; timeoutMs?: number; waitForCompletion?: boolean; waitForCompletionTimeoutMs?: number }) {
|
|
263
|
+
const candidates = this.buildCandidates(params.blueprintName);
|
|
264
|
+
const primary = candidates[0];
|
|
265
|
+
if (!primary) return { success: false as const, error: 'Invalid blueprint name' };
|
|
266
|
+
const pluginResp = await this.sendAction('blueprint_add_variable', { blueprintCandidates: candidates, requestedPath: primary, variableName: params.variableName, variableType: params.variableType, defaultValue: params.defaultValue, category: params.category, isReplicated: params.isReplicated, isPublic: params.isPublic, variablePinType: params.variablePinType }, { timeoutMs: params.timeoutMs, waitForEvent: !!params.waitForCompletion, waitForEventTimeoutMs: params.waitForCompletionTimeoutMs });
|
|
267
|
+
if (pluginResp && pluginResp.success) {
|
|
268
|
+
return pluginResp;
|
|
269
|
+
}
|
|
270
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
271
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement blueprint_add_variable' } as const;
|
|
512
272
|
}
|
|
273
|
+
return { success: false, error: pluginResp?.error ?? 'BLUEPRINT_ADD_VARIABLE_FAILED', message: pluginResp?.message ?? 'Failed to add variable via automation bridge' } as const;
|
|
513
274
|
}
|
|
514
275
|
|
|
515
|
-
|
|
516
|
-
const
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
276
|
+
async removeVariable(params: { blueprintName: string; variableName: string; timeoutMs?: number; waitForCompletion?: boolean; waitForCompletionTimeoutMs?: number }) {
|
|
277
|
+
const candidates = this.buildCandidates(params.blueprintName);
|
|
278
|
+
const primary = candidates[0];
|
|
279
|
+
if (!primary) return { success: false as const, error: 'Invalid blueprint name' };
|
|
280
|
+
const pluginResp = await this.sendAction('blueprint_remove_variable', { blueprintCandidates: candidates, requestedPath: primary, variableName: params.variableName }, { timeoutMs: params.timeoutMs, waitForEvent: !!params.waitForCompletion, waitForEventTimeoutMs: params.waitForCompletionTimeoutMs });
|
|
281
|
+
if (pluginResp && pluginResp.success) return pluginResp;
|
|
282
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
283
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement blueprint_remove_variable' } as const;
|
|
284
|
+
}
|
|
285
|
+
return { success: false, error: pluginResp?.error ?? 'BLUEPRINT_REMOVE_VARIABLE_FAILED', message: pluginResp?.message ?? 'Failed to remove variable via automation bridge' } as const;
|
|
286
|
+
}
|
|
521
287
|
|
|
522
|
-
|
|
523
|
-
const
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
const
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
if (interpreted.success) {
|
|
534
|
-
const outcome: {
|
|
535
|
-
success: true;
|
|
536
|
-
message: string;
|
|
537
|
-
path: string;
|
|
538
|
-
exists?: boolean;
|
|
539
|
-
parent?: string;
|
|
540
|
-
verifyError?: string;
|
|
541
|
-
warnings?: string[];
|
|
542
|
-
details?: string[];
|
|
543
|
-
} = {
|
|
544
|
-
success: true,
|
|
545
|
-
message: interpreted.message,
|
|
546
|
-
path
|
|
547
|
-
};
|
|
288
|
+
async renameVariable(params: { blueprintName: string; oldName: string; newName: string; timeoutMs?: number; waitForCompletion?: boolean; waitForCompletionTimeoutMs?: number }) {
|
|
289
|
+
const candidates = this.buildCandidates(params.blueprintName);
|
|
290
|
+
const primary = candidates[0];
|
|
291
|
+
if (!primary) return { success: false as const, error: 'Invalid blueprint name' };
|
|
292
|
+
const pluginResp = await this.sendAction('blueprint_rename_variable', { blueprintCandidates: candidates, requestedPath: primary, oldName: params.oldName, newName: params.newName }, { timeoutMs: params.timeoutMs, waitForEvent: !!params.waitForCompletion, waitForEventTimeoutMs: params.waitForCompletionTimeoutMs });
|
|
293
|
+
if (pluginResp && pluginResp.success) return pluginResp;
|
|
294
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
295
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement blueprint_rename_variable' } as const;
|
|
296
|
+
}
|
|
297
|
+
return { success: false, error: pluginResp?.error ?? 'BLUEPRINT_RENAME_VARIABLE_FAILED', message: pluginResp?.message ?? 'Failed to rename variable via automation bridge' } as const;
|
|
298
|
+
}
|
|
548
299
|
|
|
549
|
-
if (typeof exists === 'boolean') {
|
|
550
|
-
outcome.exists = exists;
|
|
551
|
-
}
|
|
552
|
-
if (parent) {
|
|
553
|
-
outcome.parent = parent;
|
|
554
|
-
}
|
|
555
|
-
if (verifyError) {
|
|
556
|
-
outcome.verifyError = verifyError;
|
|
557
|
-
}
|
|
558
|
-
if (warnings && warnings.length > 0) {
|
|
559
|
-
outcome.warnings = warnings;
|
|
560
|
-
}
|
|
561
|
-
if (details && details.length > 0) {
|
|
562
|
-
outcome.details = details;
|
|
563
|
-
}
|
|
564
300
|
|
|
565
|
-
return outcome;
|
|
566
|
-
}
|
|
567
301
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
success: false,
|
|
582
|
-
message: `Failed to create blueprint: ${fallbackMessage}`,
|
|
583
|
-
error: fallbackMessage,
|
|
584
|
-
path
|
|
585
|
-
};
|
|
302
|
+
async addEvent(params: { blueprintName: string; eventType: string; customEventName?: string; parameters?: Array<{ name: string; type: string }>; timeoutMs?: number; waitForCompletion?: boolean; waitForCompletionTimeoutMs?: number }) {
|
|
303
|
+
const candidates = this.buildCandidates(params.blueprintName);
|
|
304
|
+
const primary = candidates[0];
|
|
305
|
+
if (!primary) return { success: false as const, error: 'Invalid blueprint name' };
|
|
306
|
+
const pluginResp = await this.sendAction('blueprint_add_event', { blueprintCandidates: candidates, requestedPath: primary, eventType: params.eventType, customEventName: params.customEventName, parameters: params.parameters }, { timeoutMs: params.timeoutMs, waitForEvent: !!params.waitForCompletion, waitForEventTimeoutMs: params.waitForCompletionTimeoutMs });
|
|
307
|
+
if (pluginResp && pluginResp.success) {
|
|
308
|
+
return pluginResp;
|
|
309
|
+
}
|
|
310
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
311
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement blueprint_add_event' } as const;
|
|
312
|
+
}
|
|
313
|
+
return { success: false, error: pluginResp?.error ?? 'BLUEPRINT_ADD_EVENT_FAILED', message: pluginResp?.message ?? 'Failed to add event via automation bridge' } as const;
|
|
314
|
+
}
|
|
586
315
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
316
|
+
async removeEvent(params: { blueprintName: string; eventName: string; customEventName?: string; timeoutMs?: number; waitForCompletion?: boolean; waitForCompletionTimeoutMs?: number }) {
|
|
317
|
+
const candidates = this.buildCandidates(params.blueprintName);
|
|
318
|
+
const primary = candidates[0];
|
|
319
|
+
if (!primary) return { success: false as const, error: 'Invalid blueprint name' };
|
|
320
|
+
|
|
321
|
+
// Fix: Allow customEventName as alias for eventName
|
|
322
|
+
const finalEventName = params.eventName || params.customEventName;
|
|
323
|
+
if (!finalEventName) return { success: false, error: 'INVALID_ARGUMENT', message: 'eventName is required' } as const;
|
|
324
|
+
|
|
325
|
+
try {
|
|
326
|
+
const pluginResp = await this.sendAction('blueprint_remove_event', { blueprintCandidates: candidates, requestedPath: primary, eventName: finalEventName }, { timeoutMs: params.timeoutMs, waitForEvent: !!params.waitForCompletion, waitForEventTimeoutMs: params.waitForCompletionTimeoutMs });
|
|
327
|
+
if (pluginResp && pluginResp.success) {
|
|
328
|
+
return pluginResp;
|
|
598
329
|
}
|
|
599
|
-
if (
|
|
600
|
-
|
|
330
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
331
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement blueprint_remove_event' } as const;
|
|
601
332
|
}
|
|
333
|
+
return { success: false, error: pluginResp?.error ?? 'BLUEPRINT_REMOVE_EVENT_FAILED', message: pluginResp?.message ?? 'Failed to remove event via automation bridge' } as const;
|
|
334
|
+
} catch (err: any) {
|
|
335
|
+
return { success: false, error: String(err), message: String(err) } as const;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
602
338
|
|
|
603
|
-
|
|
339
|
+
async addFunction(params: { blueprintName: string; functionName: string; inputs?: Array<{ name: string; type: string }>; outputs?: Array<{ name: string; type: string }>; isPublic?: boolean; category?: string; timeoutMs?: number; waitForCompletion?: boolean; waitForCompletionTimeoutMs?: number }) {
|
|
340
|
+
const candidates = this.buildCandidates(params.blueprintName);
|
|
341
|
+
const primary = candidates[0];
|
|
342
|
+
if (!primary) return { success: false as const, error: 'Invalid blueprint name' };
|
|
343
|
+
const pluginResp = await this.sendAction('blueprint_add_function', { blueprintCandidates: candidates, requestedPath: primary, functionName: params.functionName, inputs: params.inputs, outputs: params.outputs, isPublic: params.isPublic, category: params.category }, { timeoutMs: params.timeoutMs, waitForEvent: !!params.waitForCompletion, waitForEventTimeoutMs: params.waitForCompletionTimeoutMs });
|
|
344
|
+
if (pluginResp && pluginResp.success) {
|
|
345
|
+
return pluginResp;
|
|
346
|
+
}
|
|
347
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
348
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement blueprint_add_function' } as const;
|
|
604
349
|
}
|
|
350
|
+
return { success: false, error: pluginResp?.error ?? 'BLUEPRINT_ADD_FUNCTION_FAILED', message: pluginResp?.message ?? 'Failed to add function via automation bridge' } as const;
|
|
351
|
+
}
|
|
605
352
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
path: defaultPath
|
|
614
|
-
};
|
|
353
|
+
async setVariableMetadata(params: { blueprintName: string; variableName: string; metadata: Record<string, unknown>; timeoutMs?: number }) {
|
|
354
|
+
const candidates = this.buildCandidates(params.blueprintName);
|
|
355
|
+
const primary = candidates[0];
|
|
356
|
+
if (!primary) return { success: false as const, error: 'Invalid blueprint name' };
|
|
357
|
+
const pluginResp = await this.sendAction('blueprint_set_variable_metadata', { blueprintCandidates: candidates, requestedPath: primary, variableName: params.variableName, metadata: params.metadata }, { timeoutMs: params.timeoutMs });
|
|
358
|
+
if (pluginResp && pluginResp.success) {
|
|
359
|
+
return pluginResp;
|
|
615
360
|
}
|
|
361
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
362
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement blueprint_set_variable_metadata' } as const;
|
|
363
|
+
}
|
|
364
|
+
return { success: false, error: pluginResp?.error ?? 'SET_VARIABLE_METADATA_FAILED', message: pluginResp?.message ?? 'Failed to set variable metadata via automation bridge' } as const;
|
|
365
|
+
}
|
|
616
366
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
367
|
+
async addConstructionScript(params: { blueprintName: string; scriptName: string; timeoutMs?: number; waitForCompletion?: boolean; waitForCompletionTimeoutMs?: number }) {
|
|
368
|
+
const candidates = this.buildCandidates(params.blueprintName);
|
|
369
|
+
const primary = candidates[0];
|
|
370
|
+
if (!primary) return { success: false as const, error: 'Invalid blueprint name' };
|
|
371
|
+
const pluginResp = await this.sendAction('blueprint_add_construction_script', { blueprintCandidates: candidates, requestedPath: primary, scriptName: params.scriptName }, { timeoutMs: params.timeoutMs, waitForEvent: !!params.waitForCompletion, waitForEventTimeoutMs: params.waitForCompletionTimeoutMs });
|
|
372
|
+
if (pluginResp && pluginResp.success) return pluginResp;
|
|
373
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
374
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement blueprint_add_construction_script' } as const;
|
|
623
375
|
}
|
|
376
|
+
return { success: false, error: pluginResp?.error ?? 'ADD_CONSTRUCTION_SCRIPT_FAILED', message: pluginResp?.message ?? 'Failed to add construction script via automation bridge' } as const;
|
|
377
|
+
}
|
|
624
378
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
379
|
+
async compileBlueprint(params: { blueprintName: string; saveAfterCompile?: boolean; }) {
|
|
380
|
+
try {
|
|
381
|
+
const candidates = this.buildCandidates(params.blueprintName);
|
|
382
|
+
const primary = candidates[0] ?? params.blueprintName;
|
|
383
|
+
const pluginResp = await this.sendAction('blueprint_compile', { requestedPath: primary, saveAfterCompile: params.saveAfterCompile });
|
|
384
|
+
if (pluginResp && pluginResp.success) {
|
|
385
|
+
return {
|
|
386
|
+
...pluginResp,
|
|
387
|
+
blueprint: primary,
|
|
388
|
+
message: pluginResp.message || `Compiled ${primary}`
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
392
|
+
this.pluginBlueprintActionsAvailable = false;
|
|
393
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement blueprint_compile' } as const;
|
|
394
|
+
}
|
|
395
|
+
return { success: false, error: pluginResp?.error ?? 'BLUEPRINT_COMPILE_FAILED', message: pluginResp?.message ?? 'Failed to compile blueprint via automation bridge' } as const;
|
|
396
|
+
} catch (err: any) {
|
|
397
|
+
return { success: false, error: String(err) };
|
|
398
|
+
}
|
|
631
399
|
}
|
|
632
400
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
401
|
+
async getBlueprintSCS(params: { blueprintPath: string; timeoutMs?: number }) {
|
|
402
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
403
|
+
if (!blueprintPath) {
|
|
404
|
+
return { success: false, error: 'INVALID_BLUEPRINT_PATH', message: 'Blueprint path is required' } as const;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
try {
|
|
408
|
+
const pluginResp = await this.sendAction('get_blueprint_scs',
|
|
409
|
+
{ blueprint_path: blueprintPath },
|
|
410
|
+
{ timeoutMs: params.timeoutMs });
|
|
411
|
+
|
|
412
|
+
if (pluginResp && pluginResp.success) return pluginResp;
|
|
413
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
414
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement get_blueprint_scs' } as const;
|
|
415
|
+
}
|
|
416
|
+
return { success: false, error: pluginResp?.error ?? 'GET_SCS_FAILED', message: pluginResp?.message ?? 'Failed to get SCS via automation bridge' } as const;
|
|
417
|
+
} catch (err: any) {
|
|
418
|
+
return { success: false, error: String(err), message: String(err) } as const;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async addSCSComponent(params: {
|
|
423
|
+
blueprintPath: string;
|
|
424
|
+
componentClass: string;
|
|
639
425
|
componentName: string;
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
scale?: [number, number, number];
|
|
645
|
-
};
|
|
426
|
+
parentComponent?: string;
|
|
427
|
+
meshPath?: string;
|
|
428
|
+
materialPath?: string;
|
|
429
|
+
timeoutMs?: number;
|
|
646
430
|
}) {
|
|
431
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
432
|
+
if (!blueprintPath) {
|
|
433
|
+
return { success: false, error: 'INVALID_BLUEPRINT_PATH', message: 'Blueprint path is required' } as const;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const componentClass = coerceString(params.componentClass);
|
|
437
|
+
if (!componentClass) {
|
|
438
|
+
return { success: false, error: 'INVALID_COMPONENT_CLASS', message: 'Component class is required' } as const;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const componentName = coerceString(params.componentName);
|
|
442
|
+
if (!componentName) {
|
|
443
|
+
return { success: false, error: 'INVALID_COMPONENT_NAME', message: 'Component name is required' } as const;
|
|
444
|
+
}
|
|
445
|
+
|
|
647
446
|
try {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
// Add component using Python API
|
|
655
|
-
const pythonScript = `
|
|
656
|
-
import unreal
|
|
657
|
-
import json
|
|
658
|
-
|
|
659
|
-
result = {
|
|
660
|
-
"success": False,
|
|
661
|
-
"message": "",
|
|
662
|
-
"error": "",
|
|
663
|
-
"blueprintPath": "${escapePythonString(params.blueprintName)}",
|
|
664
|
-
"component": "${escapePythonString(sanitizedComponentName)}",
|
|
665
|
-
"componentType": "${escapePythonString(params.componentType)}",
|
|
666
|
-
"warnings": [],
|
|
667
|
-
"details": []
|
|
668
|
-
}
|
|
447
|
+
const payload: Record<string, unknown> = {
|
|
448
|
+
blueprint_path: blueprintPath,
|
|
449
|
+
component_class: componentClass,
|
|
450
|
+
component_name: componentName
|
|
451
|
+
};
|
|
669
452
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
return (name or "").strip()
|
|
680
|
-
|
|
681
|
-
def candidate_paths(raw_name):
|
|
682
|
-
cleaned = normalize_name(raw_name)
|
|
683
|
-
if not cleaned:
|
|
684
|
-
return []
|
|
685
|
-
if cleaned.startswith('/'):
|
|
686
|
-
return [cleaned]
|
|
687
|
-
bases = [
|
|
688
|
-
f"/Game/Blueprints/{cleaned}",
|
|
689
|
-
f"/Game/Blueprints/LiveTests/{cleaned}",
|
|
690
|
-
f"/Game/Blueprints/DirectAPI/{cleaned}",
|
|
691
|
-
f"/Game/Blueprints/ComponentTests/{cleaned}",
|
|
692
|
-
f"/Game/Blueprints/Types/{cleaned}",
|
|
693
|
-
f"/Game/Blueprints/ComprehensiveTest/{cleaned}",
|
|
694
|
-
f"/Game/{cleaned}"
|
|
695
|
-
]
|
|
696
|
-
final = []
|
|
697
|
-
for entry in bases:
|
|
698
|
-
if entry.endswith('.uasset'):
|
|
699
|
-
final.append(entry[:-7])
|
|
700
|
-
final.append(entry)
|
|
701
|
-
return final
|
|
702
|
-
|
|
703
|
-
def load_blueprint(raw_name):
|
|
704
|
-
editor_lib = unreal.EditorAssetLibrary
|
|
705
|
-
asset_subsystem = None
|
|
706
|
-
try:
|
|
707
|
-
asset_subsystem = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem)
|
|
708
|
-
except Exception:
|
|
709
|
-
asset_subsystem = None
|
|
710
|
-
|
|
711
|
-
for path in candidate_paths(raw_name):
|
|
712
|
-
asset = None
|
|
713
|
-
try:
|
|
714
|
-
if asset_subsystem and hasattr(asset_subsystem, 'load_asset'):
|
|
715
|
-
asset = asset_subsystem.load_asset(path)
|
|
716
|
-
else:
|
|
717
|
-
asset = editor_lib.load_asset(path)
|
|
718
|
-
except Exception:
|
|
719
|
-
asset = editor_lib.load_asset(path)
|
|
720
|
-
if asset:
|
|
721
|
-
add_detail(f"Resolved blueprint at {path}")
|
|
722
|
-
return path, asset
|
|
723
|
-
return None, None
|
|
724
|
-
|
|
725
|
-
def resolve_component_class(raw_class_name):
|
|
726
|
-
name = normalize_name(raw_class_name)
|
|
727
|
-
if not name:
|
|
728
|
-
return None
|
|
729
|
-
try:
|
|
730
|
-
if name.startswith('/Script/'):
|
|
731
|
-
loaded = unreal.load_class(None, name)
|
|
732
|
-
if loaded:
|
|
733
|
-
return loaded
|
|
734
|
-
except Exception as err:
|
|
735
|
-
add_warning(f"load_class failed: {err}")
|
|
736
|
-
try:
|
|
737
|
-
candidate = getattr(unreal, name, None)
|
|
738
|
-
if candidate:
|
|
739
|
-
return candidate
|
|
740
|
-
except Exception:
|
|
741
|
-
pass
|
|
742
|
-
return None
|
|
743
|
-
|
|
744
|
-
bp_path, blueprint_asset = load_blueprint("${escapePythonString(params.blueprintName)}")
|
|
745
|
-
if not blueprint_asset:
|
|
746
|
-
result["error"] = f"Blueprint not found: ${escapePythonString(params.blueprintName)}"
|
|
747
|
-
result["message"] = result["error"]
|
|
748
|
-
else:
|
|
749
|
-
component_class = resolve_component_class("${escapePythonString(params.componentType)}")
|
|
750
|
-
if not component_class:
|
|
751
|
-
result["error"] = f"Component class not found: ${escapePythonString(params.componentType)}"
|
|
752
|
-
result["message"] = result["error"]
|
|
753
|
-
else:
|
|
754
|
-
add_warning("Component addition is simulated due to limited Python access to SimpleConstructionScript")
|
|
755
|
-
result["success"] = True
|
|
756
|
-
result["error"] = ""
|
|
757
|
-
result["blueprintPath"] = bp_path or result["blueprintPath"]
|
|
758
|
-
result["message"] = "Component ${escapePythonString(sanitizedComponentName)} added to ${escapePythonString(params.blueprintName)}"
|
|
759
|
-
add_detail("Blueprint ready for manual verification in editor if needed")
|
|
760
|
-
|
|
761
|
-
if not result["warnings"]:
|
|
762
|
-
result.pop("warnings")
|
|
763
|
-
if not result["details"]:
|
|
764
|
-
result.pop("details")
|
|
765
|
-
if not result["error"]:
|
|
766
|
-
result["error"] = ""
|
|
767
|
-
|
|
768
|
-
print('RESULT:' + json.dumps(result))
|
|
769
|
-
`.trim();
|
|
770
|
-
// Execute Python and parse the output
|
|
771
|
-
try {
|
|
772
|
-
const response = await this.bridge.executePython(pythonScript);
|
|
773
|
-
const interpreted = interpretStandardResult(response, {
|
|
774
|
-
successMessage: `Component ${sanitizedComponentName} added to ${params.blueprintName}`,
|
|
775
|
-
failureMessage: `Failed to add component ${sanitizedComponentName}`
|
|
776
|
-
});
|
|
777
|
-
|
|
778
|
-
const payload = interpreted.payload ?? {};
|
|
779
|
-
const warnings = interpreted.warnings ?? coerceStringArray((payload as any).warnings) ?? undefined;
|
|
780
|
-
const details = interpreted.details ?? coerceStringArray((payload as any).details) ?? undefined;
|
|
781
|
-
const blueprintPath = coerceString((payload as any).blueprintPath) ?? params.blueprintName;
|
|
782
|
-
const componentName = coerceString((payload as any).component) ?? sanitizedComponentName;
|
|
783
|
-
const componentType = coerceString((payload as any).componentType) ?? params.componentType;
|
|
784
|
-
const errorMessage = coerceString((payload as any).error) ?? interpreted.error ?? 'Unknown error';
|
|
785
|
-
|
|
786
|
-
if (interpreted.success) {
|
|
787
|
-
const outcome: {
|
|
788
|
-
success: true;
|
|
789
|
-
message: string;
|
|
790
|
-
blueprintPath: string;
|
|
791
|
-
component: string;
|
|
792
|
-
componentType: string;
|
|
793
|
-
warnings?: string[];
|
|
794
|
-
details?: string[];
|
|
795
|
-
} = {
|
|
796
|
-
success: true,
|
|
797
|
-
message: interpreted.message,
|
|
798
|
-
blueprintPath,
|
|
799
|
-
component: componentName,
|
|
800
|
-
componentType
|
|
801
|
-
};
|
|
453
|
+
if (params.parentComponent) {
|
|
454
|
+
payload.parent_component = params.parentComponent;
|
|
455
|
+
}
|
|
456
|
+
if (params.meshPath) {
|
|
457
|
+
payload.mesh_path = params.meshPath;
|
|
458
|
+
}
|
|
459
|
+
if (params.materialPath) {
|
|
460
|
+
payload.material_path = params.materialPath;
|
|
461
|
+
}
|
|
802
462
|
|
|
803
|
-
|
|
804
|
-
outcome.warnings = warnings;
|
|
805
|
-
}
|
|
806
|
-
if (details && details.length > 0) {
|
|
807
|
-
outcome.details = details;
|
|
808
|
-
}
|
|
463
|
+
const pluginResp = await this.sendAction('add_scs_component', payload, { timeoutMs: params.timeoutMs });
|
|
809
464
|
|
|
810
|
-
|
|
465
|
+
if (pluginResp && pluginResp.success === false) {
|
|
466
|
+
if ((pluginResp as any).message) {
|
|
467
|
+
this.log.warn?.(`addSCSComponent reported warning: ${(pluginResp as any).message}`);
|
|
811
468
|
}
|
|
469
|
+
}
|
|
470
|
+
if (pluginResp && pluginResp.success) return pluginResp;
|
|
471
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
472
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement add_scs_component' } as const;
|
|
473
|
+
}
|
|
474
|
+
return { success: false, error: pluginResp?.error ?? 'ADD_SCS_COMPONENT_FAILED', message: pluginResp?.message ?? 'Failed to add SCS component via automation bridge' } as const;
|
|
475
|
+
} catch (err: any) {
|
|
476
|
+
return { success: false, error: String(err), message: String(err) } as const;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
812
479
|
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
message: string;
|
|
819
|
-
blueprintPath: string;
|
|
820
|
-
component: string;
|
|
821
|
-
componentType: string;
|
|
822
|
-
warnings?: string[];
|
|
823
|
-
details?: string[];
|
|
824
|
-
note?: string;
|
|
825
|
-
} = {
|
|
826
|
-
success: true,
|
|
827
|
-
message: `Component ${componentName} added to ${blueprintPath}`,
|
|
828
|
-
blueprintPath,
|
|
829
|
-
component: componentName,
|
|
830
|
-
componentType,
|
|
831
|
-
note: 'Simulated success due to limited Python access to SimpleConstructionScript'
|
|
832
|
-
};
|
|
833
|
-
if (warnings && warnings.length > 0) {
|
|
834
|
-
fallbackSuccess.warnings = warnings;
|
|
835
|
-
}
|
|
836
|
-
if (details && details.length > 0) {
|
|
837
|
-
fallbackSuccess.details = details;
|
|
838
|
-
}
|
|
839
|
-
return fallbackSuccess;
|
|
840
|
-
}
|
|
480
|
+
async removeSCSComponent(params: { blueprintPath: string; componentName: string; timeoutMs?: number }) {
|
|
481
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
482
|
+
if (!blueprintPath) {
|
|
483
|
+
return { success: false, error: 'INVALID_BLUEPRINT_PATH', message: 'Blueprint path is required' } as const;
|
|
484
|
+
}
|
|
841
485
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
blueprintPath: string;
|
|
847
|
-
component: string;
|
|
848
|
-
componentType: string;
|
|
849
|
-
warnings?: string[];
|
|
850
|
-
details?: string[];
|
|
851
|
-
} = {
|
|
852
|
-
success: false,
|
|
853
|
-
message: `Failed to add component: ${errorMessage}`,
|
|
854
|
-
error: errorMessage,
|
|
855
|
-
blueprintPath,
|
|
856
|
-
component: componentName,
|
|
857
|
-
componentType
|
|
858
|
-
};
|
|
486
|
+
const componentName = coerceString(params.componentName);
|
|
487
|
+
if (!componentName) {
|
|
488
|
+
return { success: false, error: 'INVALID_COMPONENT_NAME', message: 'Component name is required' } as const;
|
|
489
|
+
}
|
|
859
490
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
failureOutcome.details = details;
|
|
865
|
-
}
|
|
491
|
+
try {
|
|
492
|
+
const pluginResp = await this.sendAction('remove_scs_component',
|
|
493
|
+
{ blueprint_path: blueprintPath, component_name: componentName },
|
|
494
|
+
{ timeoutMs: params.timeoutMs });
|
|
866
495
|
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
message: 'Failed to add component',
|
|
872
|
-
error: String(error)
|
|
873
|
-
};
|
|
496
|
+
if (pluginResp && pluginResp.success === false) {
|
|
497
|
+
if ((pluginResp as any).message) {
|
|
498
|
+
this.log.warn?.(`removeSCSComponent reported warning: ${(pluginResp as any).message}`);
|
|
499
|
+
}
|
|
874
500
|
}
|
|
875
|
-
|
|
876
|
-
|
|
501
|
+
if (pluginResp && pluginResp.success) return pluginResp;
|
|
502
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
503
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement remove_scs_component' } as const;
|
|
504
|
+
}
|
|
505
|
+
return { success: false, error: pluginResp?.error ?? 'REMOVE_SCS_COMPONENT_FAILED', message: pluginResp?.message ?? 'Failed to remove SCS component via automation bridge' } as const;
|
|
506
|
+
} catch (err: any) {
|
|
507
|
+
return { success: false, error: String(err), message: String(err) } as const;
|
|
877
508
|
}
|
|
878
|
-
|
|
879
509
|
}
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
variableType: string;
|
|
887
|
-
defaultValue?: any;
|
|
888
|
-
category?: string;
|
|
889
|
-
isReplicated?: boolean;
|
|
890
|
-
isPublic?: boolean;
|
|
510
|
+
|
|
511
|
+
async reparentSCSComponent(params: {
|
|
512
|
+
blueprintPath: string;
|
|
513
|
+
componentName: string;
|
|
514
|
+
newParent: string;
|
|
515
|
+
timeoutMs?: number;
|
|
891
516
|
}) {
|
|
517
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
518
|
+
if (!blueprintPath) {
|
|
519
|
+
return { success: false, error: 'INVALID_BLUEPRINT_PATH', message: 'Blueprint path is required' } as const;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const componentName = coerceString(params.componentName);
|
|
523
|
+
if (!componentName) {
|
|
524
|
+
return { success: false, error: 'INVALID_COMPONENT_NAME', message: 'Component name is required' } as const;
|
|
525
|
+
}
|
|
526
|
+
|
|
892
527
|
try {
|
|
893
|
-
const
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
`SetVariableCategory ${params.blueprintName} ${params.variableName} ${params.category}`
|
|
906
|
-
);
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
if (params.isReplicated) {
|
|
910
|
-
commands.push(
|
|
911
|
-
`SetVariableReplicated ${params.blueprintName} ${params.variableName} true`
|
|
912
|
-
);
|
|
528
|
+
const pluginResp = await this.sendAction('reparent_scs_component',
|
|
529
|
+
{
|
|
530
|
+
blueprint_path: blueprintPath,
|
|
531
|
+
component_name: componentName,
|
|
532
|
+
new_parent: params.newParent || ''
|
|
533
|
+
},
|
|
534
|
+
{ timeoutMs: params.timeoutMs });
|
|
535
|
+
|
|
536
|
+
if (pluginResp && pluginResp.success === false) {
|
|
537
|
+
if ((pluginResp as any).message) {
|
|
538
|
+
this.log.warn?.(`reparentSCSComponent reported warning: ${(pluginResp as any).message}`);
|
|
539
|
+
}
|
|
913
540
|
}
|
|
914
|
-
|
|
915
|
-
if (
|
|
916
|
-
|
|
917
|
-
`SetVariablePublic ${params.blueprintName} ${params.variableName} ${params.isPublic}`
|
|
918
|
-
);
|
|
541
|
+
if (pluginResp && pluginResp.success) return pluginResp;
|
|
542
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
543
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement reparent_scs_component' } as const;
|
|
919
544
|
}
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
return {
|
|
924
|
-
success: true,
|
|
925
|
-
message: `Variable ${params.variableName} added to ${params.blueprintName}`
|
|
926
|
-
};
|
|
927
|
-
} catch (err) {
|
|
928
|
-
return { success: false, error: `Failed to add variable: ${err}` };
|
|
545
|
+
return { success: false, error: pluginResp?.error ?? 'REPARENT_SCS_COMPONENT_FAILED', message: pluginResp?.message ?? 'Failed to reparent SCS component via automation bridge' } as const;
|
|
546
|
+
} catch (err: any) {
|
|
547
|
+
return { success: false, error: String(err), message: String(err) } as const;
|
|
929
548
|
}
|
|
930
549
|
}
|
|
931
550
|
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
outputs?: Array<{ name: string; type: string }>;
|
|
940
|
-
isPublic?: boolean;
|
|
941
|
-
category?: string;
|
|
551
|
+
async setSCSComponentTransform(params: {
|
|
552
|
+
blueprintPath: string;
|
|
553
|
+
componentName: string;
|
|
554
|
+
location?: [number, number, number];
|
|
555
|
+
rotation?: [number, number, number];
|
|
556
|
+
scale?: [number, number, number];
|
|
557
|
+
timeoutMs?: number;
|
|
942
558
|
}) {
|
|
559
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
560
|
+
if (!blueprintPath) {
|
|
561
|
+
return { success: false, error: 'INVALID_BLUEPRINT_PATH', message: 'Blueprint path is required' } as const;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const componentName = coerceString(params.componentName);
|
|
565
|
+
if (!componentName) {
|
|
566
|
+
return { success: false, error: 'INVALID_COMPONENT_NAME', message: 'Component name is required' } as const;
|
|
567
|
+
}
|
|
568
|
+
|
|
943
569
|
try {
|
|
944
|
-
const
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
if (params.
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
if (params.outputs) {
|
|
959
|
-
for (const output of params.outputs) {
|
|
960
|
-
commands.push(
|
|
961
|
-
`AddFunctionOutput ${params.blueprintName} ${params.functionName} ${output.name} ${output.type}`
|
|
962
|
-
);
|
|
570
|
+
const payload: Record<string, unknown> = {
|
|
571
|
+
blueprint_path: blueprintPath,
|
|
572
|
+
component_name: componentName
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
if (params.location) payload.location = params.location;
|
|
576
|
+
if (params.rotation) payload.rotation = params.rotation;
|
|
577
|
+
if (params.scale) payload.scale = params.scale;
|
|
578
|
+
|
|
579
|
+
const pluginResp = await this.sendAction('set_scs_component_transform', payload, { timeoutMs: params.timeoutMs });
|
|
580
|
+
|
|
581
|
+
if (pluginResp && pluginResp.success === false) {
|
|
582
|
+
if ((pluginResp as any).message) {
|
|
583
|
+
this.log.warn?.(`setSCSComponentTransform reported warning: ${(pluginResp as any).message}`);
|
|
963
584
|
}
|
|
964
585
|
}
|
|
965
|
-
|
|
966
|
-
if (
|
|
967
|
-
|
|
968
|
-
`SetFunctionPublic ${params.blueprintName} ${params.functionName} ${params.isPublic}`
|
|
969
|
-
);
|
|
586
|
+
if (pluginResp && pluginResp.success) return pluginResp;
|
|
587
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
588
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement set_scs_component_transform' } as const;
|
|
970
589
|
}
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
`SetFunctionCategory ${params.blueprintName} ${params.functionName} ${params.category}`
|
|
975
|
-
);
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
await this.bridge.executeConsoleCommands(commands);
|
|
979
|
-
|
|
980
|
-
return {
|
|
981
|
-
success: true,
|
|
982
|
-
message: `Function ${params.functionName} added to ${params.blueprintName}`
|
|
983
|
-
};
|
|
984
|
-
} catch (err) {
|
|
985
|
-
return { success: false, error: `Failed to add function: ${err}` };
|
|
590
|
+
return { success: false, error: pluginResp?.error ?? 'SET_SCS_TRANSFORM_FAILED', message: pluginResp?.message ?? 'Failed to set SCS component transform via automation bridge' } as const;
|
|
591
|
+
} catch (err: any) {
|
|
592
|
+
return { success: false, error: String(err), message: String(err) } as const;
|
|
986
593
|
}
|
|
987
594
|
}
|
|
988
595
|
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
customEventName?: string;
|
|
996
|
-
parameters?: Array<{ name: string; type: string }>;
|
|
596
|
+
async setSCSComponentProperty(params: {
|
|
597
|
+
blueprintPath: string;
|
|
598
|
+
componentName: string;
|
|
599
|
+
propertyName: string;
|
|
600
|
+
propertyValue: any;
|
|
601
|
+
timeoutMs?: number;
|
|
997
602
|
}) {
|
|
603
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
604
|
+
if (!blueprintPath) {
|
|
605
|
+
return { success: false, error: 'INVALID_BLUEPRINT_PATH', message: 'Blueprint path is required' } as const;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const componentName = coerceString(params.componentName);
|
|
609
|
+
if (!componentName) {
|
|
610
|
+
return { success: false, error: 'INVALID_COMPONENT_NAME', message: 'Component name is required' } as const;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const propertyName = coerceString(params.propertyName);
|
|
614
|
+
if (!propertyName) {
|
|
615
|
+
return { success: false, error: 'INVALID_PROPERTY_NAME', message: 'Property name is required' } as const;
|
|
616
|
+
}
|
|
617
|
+
|
|
998
618
|
try {
|
|
999
|
-
const
|
|
1000
|
-
|
|
1001
|
-
const
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
619
|
+
const propertyValueJson = JSON.stringify({ value: params.propertyValue });
|
|
620
|
+
|
|
621
|
+
const pluginResp = await this.sendAction('set_scs_component_property',
|
|
622
|
+
{
|
|
623
|
+
blueprint_path: blueprintPath,
|
|
624
|
+
component_name: componentName,
|
|
625
|
+
property_name: propertyName,
|
|
626
|
+
property_value: propertyValueJson
|
|
627
|
+
},
|
|
628
|
+
{ timeoutMs: params.timeoutMs });
|
|
629
|
+
|
|
630
|
+
if (pluginResp && pluginResp.success === false) {
|
|
631
|
+
if ((pluginResp as any).message) {
|
|
632
|
+
this.log.warn?.(`setSCSComponentProperty reported warning: ${(pluginResp as any).message}`);
|
|
1011
633
|
}
|
|
1012
634
|
}
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
};
|
|
1020
|
-
} catch (err) {
|
|
1021
|
-
return { success: false, error: `Failed to add event: ${err}` };
|
|
635
|
+
if (pluginResp && pluginResp.success) return pluginResp;
|
|
636
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
637
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement set_scs_component_property' } as const;
|
|
638
|
+
}
|
|
639
|
+
return { success: false, error: pluginResp?.error ?? 'SET_SCS_PROPERTY_FAILED', message: pluginResp?.message ?? 'Failed to set SCS component property via automation bridge' } as const;
|
|
640
|
+
} catch (err: any) {
|
|
641
|
+
return { success: false, error: String(err), message: String(err) } as const;
|
|
1022
642
|
}
|
|
1023
643
|
}
|
|
1024
644
|
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
blueprintName: string;
|
|
1030
|
-
saveAfterCompile?: boolean;
|
|
645
|
+
async getNodes(params: {
|
|
646
|
+
blueprintPath: string;
|
|
647
|
+
graphName?: string;
|
|
648
|
+
timeoutMs?: number;
|
|
1031
649
|
}) {
|
|
650
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
651
|
+
if (!blueprintPath) {
|
|
652
|
+
return { success: false, error: 'INVALID_BLUEPRINT_PATH', message: 'Blueprint path is required' } as const;
|
|
653
|
+
}
|
|
654
|
+
|
|
1032
655
|
try {
|
|
1033
|
-
const
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
if (params.saveAfterCompile) {
|
|
1038
|
-
commands.push(`SaveAsset ${params.blueprintName}`);
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
await this.bridge.executeConsoleCommands(commands);
|
|
1042
|
-
|
|
1043
|
-
return {
|
|
1044
|
-
success: true,
|
|
1045
|
-
message: `Blueprint ${params.blueprintName} compiled successfully`
|
|
656
|
+
const payload: Record<string, unknown> = {
|
|
657
|
+
subAction: 'get_nodes',
|
|
658
|
+
blueprintPath: blueprintPath,
|
|
659
|
+
graphName: params.graphName || 'EventGraph'
|
|
1046
660
|
};
|
|
1047
|
-
|
|
1048
|
-
|
|
661
|
+
const pluginResp = await this.sendAction('manage_blueprint_graph', payload, { timeoutMs: params.timeoutMs });
|
|
662
|
+
if (pluginResp && pluginResp.success) {
|
|
663
|
+
return {
|
|
664
|
+
success: true,
|
|
665
|
+
nodes: (pluginResp.result as any).nodes,
|
|
666
|
+
graphName: (pluginResp.result as any).graphName
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
670
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement get_nodes' } as const;
|
|
671
|
+
}
|
|
672
|
+
return { success: false, error: pluginResp?.error ?? 'GET_NODES_FAILED', message: pluginResp?.message ?? 'Failed to get blueprint nodes' } as const;
|
|
673
|
+
} catch (err: any) {
|
|
674
|
+
return { success: false, error: String(err), message: String(err) } as const;
|
|
1049
675
|
}
|
|
1050
676
|
}
|
|
1051
677
|
|
|
678
|
+
|
|
679
|
+
async addNode(params: {
|
|
680
|
+
blueprintName: string;
|
|
681
|
+
nodeType: string;
|
|
682
|
+
graphName?: string;
|
|
683
|
+
functionName?: string;
|
|
684
|
+
variableName?: string;
|
|
685
|
+
nodeName?: string;
|
|
686
|
+
eventName?: string;
|
|
687
|
+
memberClass?: string;
|
|
688
|
+
posX?: number;
|
|
689
|
+
posY?: number;
|
|
690
|
+
timeoutMs?: number;
|
|
691
|
+
}) {
|
|
692
|
+
const candidates = this.buildCandidates(params.blueprintName);
|
|
693
|
+
const primary = candidates[0];
|
|
694
|
+
if (!primary) return { success: false as const, error: 'Invalid blueprint name' };
|
|
695
|
+
|
|
696
|
+
// Fix: C++ expects 'manage_blueprint_graph' with 'subAction' = 'create_node'
|
|
697
|
+
const payload: any = {
|
|
698
|
+
subAction: 'create_node',
|
|
699
|
+
assetPath: primary, // C++ expects 'assetPath' or 'blueprintPath'
|
|
700
|
+
nodeType: params.nodeType,
|
|
701
|
+
graphName: params.graphName,
|
|
702
|
+
memberName: params.functionName, // C++ maps 'memberName' to FunctionName
|
|
703
|
+
variableName: params.variableName,
|
|
704
|
+
nodeName: params.nodeName,
|
|
705
|
+
eventName: params.eventName,
|
|
706
|
+
memberClass: params.memberClass,
|
|
707
|
+
x: params.posX,
|
|
708
|
+
y: params.posY
|
|
709
|
+
};
|
|
710
|
+
const res = await this.sendAction('manage_blueprint_graph', payload, { timeoutMs: params.timeoutMs });
|
|
711
|
+
return res;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
async deleteNode(params: {
|
|
715
|
+
blueprintPath: string;
|
|
716
|
+
nodeId: string;
|
|
717
|
+
graphName?: string;
|
|
718
|
+
timeoutMs?: number;
|
|
719
|
+
}) {
|
|
720
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
721
|
+
if (!blueprintPath) return { success: false, error: 'INVALID_BLUEPRINT_PATH', message: 'Blueprint path is required' } as const;
|
|
722
|
+
if (!params.nodeId) return { success: false, error: 'INVALID_NODE_ID', message: 'Node ID is required' } as const;
|
|
723
|
+
|
|
724
|
+
const res = await this.sendAction('manage_blueprint_graph', {
|
|
725
|
+
subAction: 'delete_node',
|
|
726
|
+
blueprintPath: blueprintPath,
|
|
727
|
+
graphName: params.graphName || 'EventGraph',
|
|
728
|
+
nodeId: params.nodeId
|
|
729
|
+
}, { timeoutMs: params.timeoutMs });
|
|
730
|
+
return res;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
async createRerouteNode(params: {
|
|
734
|
+
blueprintPath: string;
|
|
735
|
+
graphName?: string;
|
|
736
|
+
x: number;
|
|
737
|
+
y: number;
|
|
738
|
+
timeoutMs?: number;
|
|
739
|
+
}) {
|
|
740
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
741
|
+
if (!blueprintPath) return { success: false, error: 'INVALID_BLUEPRINT_PATH', message: 'Blueprint path is required' } as const;
|
|
742
|
+
|
|
743
|
+
const res = await this.sendAction('manage_blueprint_graph', {
|
|
744
|
+
subAction: 'create_reroute_node',
|
|
745
|
+
blueprintPath: blueprintPath,
|
|
746
|
+
graphName: params.graphName || 'EventGraph',
|
|
747
|
+
x: params.x,
|
|
748
|
+
y: params.y
|
|
749
|
+
}, { timeoutMs: params.timeoutMs });
|
|
750
|
+
return res;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
async setNodeProperty(params: {
|
|
754
|
+
blueprintPath: string;
|
|
755
|
+
nodeId: string;
|
|
756
|
+
propertyName: string;
|
|
757
|
+
value: string;
|
|
758
|
+
graphName?: string;
|
|
759
|
+
timeoutMs?: number;
|
|
760
|
+
}) {
|
|
761
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
762
|
+
if (!blueprintPath) return { success: false, error: 'INVALID_BLUEPRINT_PATH', message: 'Blueprint path is required' } as const;
|
|
763
|
+
if (!params.nodeId) return { success: false, error: 'INVALID_NODE_ID', message: 'Node ID is required' } as const;
|
|
764
|
+
if (!params.propertyName) return { success: false, error: 'INVALID_PROPERTY', message: 'Property name is required' } as const;
|
|
765
|
+
|
|
766
|
+
const res = await this.sendAction('manage_blueprint_graph', {
|
|
767
|
+
subAction: 'set_node_property',
|
|
768
|
+
blueprintPath: blueprintPath,
|
|
769
|
+
graphName: params.graphName || 'EventGraph',
|
|
770
|
+
nodeId: params.nodeId,
|
|
771
|
+
propertyName: params.propertyName,
|
|
772
|
+
value: params.value
|
|
773
|
+
}, { timeoutMs: params.timeoutMs });
|
|
774
|
+
return res;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
async getNodeDetails(params: {
|
|
778
|
+
blueprintPath: string;
|
|
779
|
+
nodeId: string;
|
|
780
|
+
graphName?: string;
|
|
781
|
+
timeoutMs?: number;
|
|
782
|
+
}) {
|
|
783
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
784
|
+
if (!blueprintPath) return { success: false, error: 'INVALID_BLUEPRINT_PATH', message: 'Blueprint path is required' } as const;
|
|
785
|
+
if (!params.nodeId) return { success: false, error: 'INVALID_NODE_ID', message: 'Node ID is required' } as const;
|
|
786
|
+
|
|
787
|
+
const res = await this.sendAction('manage_blueprint_graph', {
|
|
788
|
+
subAction: 'get_node_details',
|
|
789
|
+
blueprintPath: blueprintPath,
|
|
790
|
+
graphName: params.graphName || 'EventGraph',
|
|
791
|
+
nodeId: params.nodeId
|
|
792
|
+
}, { timeoutMs: params.timeoutMs });
|
|
793
|
+
return res;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
async getGraphDetails(params: {
|
|
797
|
+
blueprintPath: string;
|
|
798
|
+
graphName?: string;
|
|
799
|
+
timeoutMs?: number;
|
|
800
|
+
}) {
|
|
801
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
802
|
+
if (!blueprintPath) return { success: false, error: 'INVALID_BLUEPRINT_PATH', message: 'Blueprint path is required' } as const;
|
|
803
|
+
|
|
804
|
+
const res = await this.sendAction('manage_blueprint_graph', {
|
|
805
|
+
subAction: 'get_graph_details',
|
|
806
|
+
blueprintPath: blueprintPath,
|
|
807
|
+
graphName: params.graphName || 'EventGraph'
|
|
808
|
+
}, { timeoutMs: params.timeoutMs });
|
|
809
|
+
return res;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
async getPinDetails(params: {
|
|
813
|
+
blueprintPath: string;
|
|
814
|
+
nodeId: string;
|
|
815
|
+
pinName?: string;
|
|
816
|
+
graphName?: string;
|
|
817
|
+
timeoutMs?: number;
|
|
818
|
+
}) {
|
|
819
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
820
|
+
if (!blueprintPath) return { success: false, error: 'INVALID_BLUEPRINT_PATH', message: 'Blueprint path is required' } as const;
|
|
821
|
+
if (!params.nodeId) return { success: false, error: 'INVALID_NODE_ID', message: 'Node ID is required' } as const;
|
|
822
|
+
|
|
823
|
+
const res = await this.sendAction('manage_blueprint_graph', {
|
|
824
|
+
subAction: 'get_pin_details',
|
|
825
|
+
blueprintPath: blueprintPath,
|
|
826
|
+
graphName: params.graphName || 'EventGraph',
|
|
827
|
+
nodeId: params.nodeId,
|
|
828
|
+
pinName: params.pinName
|
|
829
|
+
}, { timeoutMs: params.timeoutMs });
|
|
830
|
+
return res;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
async connectPins(params: {
|
|
835
|
+
blueprintName: string;
|
|
836
|
+
sourceNodeGuid: string;
|
|
837
|
+
targetNodeGuid: string;
|
|
838
|
+
sourcePinName?: string;
|
|
839
|
+
targetPinName?: string;
|
|
840
|
+
timeoutMs?: number;
|
|
841
|
+
}) {
|
|
842
|
+
const candidates = this.buildCandidates(params.blueprintName);
|
|
843
|
+
const primary = candidates[0];
|
|
844
|
+
if (!primary) return { success: false as const, error: 'Invalid blueprint name' };
|
|
845
|
+
|
|
846
|
+
// Fix: C++ expects 'manage_blueprint_graph' with 'subAction' = 'connect_pins'
|
|
847
|
+
let fromNodeId = params.sourceNodeGuid;
|
|
848
|
+
let fromPinName = params.sourcePinName;
|
|
849
|
+
if (fromNodeId && fromNodeId.includes('.') && !fromPinName) {
|
|
850
|
+
const parts = fromNodeId.split('.');
|
|
851
|
+
fromNodeId = parts[0];
|
|
852
|
+
fromPinName = parts.slice(1).join('.');
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
let toNodeId = params.targetNodeGuid;
|
|
856
|
+
let toPinName = params.targetPinName;
|
|
857
|
+
if (toNodeId && toNodeId.includes('.') && !toPinName) {
|
|
858
|
+
const parts = toNodeId.split('.');
|
|
859
|
+
toNodeId = parts[0];
|
|
860
|
+
toPinName = parts.slice(1).join('.');
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
const res = await this.sendAction('manage_blueprint_graph', {
|
|
864
|
+
subAction: 'connect_pins',
|
|
865
|
+
assetPath: primary,
|
|
866
|
+
graphName: 'EventGraph',
|
|
867
|
+
fromNodeId: fromNodeId,
|
|
868
|
+
toNodeId: toNodeId,
|
|
869
|
+
fromPinName: fromPinName,
|
|
870
|
+
toPinName: toPinName
|
|
871
|
+
}, { timeoutMs: params.timeoutMs });
|
|
872
|
+
return res;
|
|
873
|
+
}
|
|
1052
874
|
}
|