unreal-engine-mcp-server 0.4.7 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +26 -0
- package/.env.production +38 -7
- package/.eslintrc.json +0 -54
- package/.eslintrc.override.json +8 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +94 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +56 -0
- package/.github/copilot-instructions.md +478 -45
- package/.github/dependabot.yml +19 -0
- package/.github/labeler.yml +24 -0
- package/.github/labels.yml +70 -0
- package/.github/pull_request_template.md +42 -0
- package/.github/release-drafter-config.yml +51 -0
- package/.github/workflows/auto-merge.yml +38 -0
- package/.github/workflows/ci.yml +38 -0
- package/.github/workflows/dependency-review.yml +17 -0
- package/.github/workflows/gemini-issue-triage.yml +172 -0
- package/.github/workflows/greetings.yml +27 -0
- package/.github/workflows/labeler.yml +17 -0
- package/.github/workflows/links.yml +80 -0
- package/.github/workflows/pr-size-labeler.yml +137 -0
- package/.github/workflows/publish-mcp.yml +13 -7
- package/.github/workflows/release-drafter.yml +23 -0
- package/.github/workflows/release.yml +112 -0
- package/.github/workflows/semantic-pull-request.yml +35 -0
- package/.github/workflows/smoke-test.yml +36 -0
- package/.github/workflows/stale.yml +28 -0
- package/CHANGELOG.md +338 -31
- package/CONTRIBUTING.md +140 -0
- package/GEMINI.md +115 -0
- package/Public/Plugin_setup_guide.mp4 +0 -0
- package/README.md +189 -128
- package/claude_desktop_config_example.json +7 -6
- package/dist/automation/bridge.d.ts +50 -0
- package/dist/automation/bridge.js +452 -0
- package/dist/automation/connection-manager.d.ts +23 -0
- package/dist/automation/connection-manager.js +107 -0
- package/dist/automation/handshake.d.ts +11 -0
- package/dist/automation/handshake.js +89 -0
- package/dist/automation/index.d.ts +3 -0
- package/dist/automation/index.js +3 -0
- package/dist/automation/message-handler.d.ts +12 -0
- package/dist/automation/message-handler.js +149 -0
- package/dist/automation/request-tracker.d.ts +25 -0
- package/dist/automation/request-tracker.js +98 -0
- package/dist/automation/types.d.ts +130 -0
- package/dist/automation/types.js +2 -0
- package/dist/cli.js +32 -5
- package/dist/config.d.ts +26 -0
- package/dist/config.js +59 -0
- package/dist/constants.d.ts +16 -0
- package/dist/constants.js +16 -0
- package/dist/graphql/loaders.d.ts +64 -0
- package/dist/graphql/loaders.js +117 -0
- package/dist/graphql/resolvers.d.ts +268 -0
- package/dist/graphql/resolvers.js +746 -0
- package/dist/graphql/schema.d.ts +5 -0
- package/dist/graphql/schema.js +437 -0
- package/dist/graphql/server.d.ts +26 -0
- package/dist/graphql/server.js +117 -0
- package/dist/graphql/types.d.ts +9 -0
- package/dist/graphql/types.js +2 -0
- package/dist/handlers/resource-handlers.d.ts +20 -0
- package/dist/handlers/resource-handlers.js +180 -0
- package/dist/index.d.ts +33 -18
- package/dist/index.js +130 -619
- package/dist/resources/actors.d.ts +17 -12
- package/dist/resources/actors.js +56 -76
- package/dist/resources/assets.d.ts +6 -14
- package/dist/resources/assets.js +115 -147
- package/dist/resources/levels.d.ts +13 -13
- package/dist/resources/levels.js +25 -34
- package/dist/server/resource-registry.d.ts +20 -0
- package/dist/server/resource-registry.js +37 -0
- package/dist/server/tool-registry.d.ts +23 -0
- package/dist/server/tool-registry.js +322 -0
- package/dist/server-setup.d.ts +20 -0
- package/dist/server-setup.js +71 -0
- package/dist/services/health-monitor.d.ts +34 -0
- package/dist/services/health-monitor.js +105 -0
- package/dist/services/metrics-server.d.ts +11 -0
- package/dist/services/metrics-server.js +105 -0
- package/dist/tools/actors.d.ts +163 -9
- package/dist/tools/actors.js +356 -311
- package/dist/tools/animation.d.ts +135 -4
- package/dist/tools/animation.js +510 -411
- package/dist/tools/assets.d.ts +75 -29
- package/dist/tools/assets.js +265 -284
- package/dist/tools/audio.d.ts +102 -42
- package/dist/tools/audio.js +272 -685
- package/dist/tools/base-tool.d.ts +17 -0
- package/dist/tools/base-tool.js +46 -0
- package/dist/tools/behavior-tree.d.ts +94 -0
- package/dist/tools/behavior-tree.js +39 -0
- package/dist/tools/blueprint.d.ts +208 -126
- package/dist/tools/blueprint.js +685 -832
- package/dist/tools/consolidated-tool-definitions.d.ts +5462 -1781
- package/dist/tools/consolidated-tool-definitions.js +829 -496
- package/dist/tools/consolidated-tool-handlers.d.ts +2 -1
- package/dist/tools/consolidated-tool-handlers.js +198 -1027
- package/dist/tools/debug.d.ts +143 -85
- package/dist/tools/debug.js +234 -180
- package/dist/tools/dynamic-handler-registry.d.ts +13 -0
- package/dist/tools/dynamic-handler-registry.js +23 -0
- package/dist/tools/editor.d.ts +30 -83
- package/dist/tools/editor.js +247 -244
- package/dist/tools/engine.d.ts +10 -4
- package/dist/tools/engine.js +13 -5
- package/dist/tools/environment.d.ts +30 -0
- package/dist/tools/environment.js +267 -0
- package/dist/tools/foliage.d.ts +65 -99
- package/dist/tools/foliage.js +221 -331
- package/dist/tools/handlers/actor-handlers.d.ts +3 -0
- package/dist/tools/handlers/actor-handlers.js +227 -0
- package/dist/tools/handlers/animation-handlers.d.ts +3 -0
- package/dist/tools/handlers/animation-handlers.js +185 -0
- package/dist/tools/handlers/argument-helper.d.ts +16 -0
- package/dist/tools/handlers/argument-helper.js +80 -0
- package/dist/tools/handlers/asset-handlers.d.ts +3 -0
- package/dist/tools/handlers/asset-handlers.js +496 -0
- package/dist/tools/handlers/audio-handlers.d.ts +3 -0
- package/dist/tools/handlers/audio-handlers.js +166 -0
- package/dist/tools/handlers/blueprint-handlers.d.ts +4 -0
- package/dist/tools/handlers/blueprint-handlers.js +358 -0
- package/dist/tools/handlers/common-handlers.d.ts +14 -0
- package/dist/tools/handlers/common-handlers.js +56 -0
- package/dist/tools/handlers/editor-handlers.d.ts +3 -0
- package/dist/tools/handlers/editor-handlers.js +119 -0
- package/dist/tools/handlers/effect-handlers.d.ts +3 -0
- package/dist/tools/handlers/effect-handlers.js +171 -0
- package/dist/tools/handlers/environment-handlers.d.ts +3 -0
- package/dist/tools/handlers/environment-handlers.js +170 -0
- package/dist/tools/handlers/graph-handlers.d.ts +3 -0
- package/dist/tools/handlers/graph-handlers.js +90 -0
- package/dist/tools/handlers/input-handlers.d.ts +3 -0
- package/dist/tools/handlers/input-handlers.js +21 -0
- package/dist/tools/handlers/inspect-handlers.d.ts +3 -0
- package/dist/tools/handlers/inspect-handlers.js +383 -0
- package/dist/tools/handlers/level-handlers.d.ts +3 -0
- package/dist/tools/handlers/level-handlers.js +237 -0
- package/dist/tools/handlers/lighting-handlers.d.ts +3 -0
- package/dist/tools/handlers/lighting-handlers.js +144 -0
- package/dist/tools/handlers/performance-handlers.d.ts +3 -0
- package/dist/tools/handlers/performance-handlers.js +130 -0
- package/dist/tools/handlers/pipeline-handlers.d.ts +3 -0
- package/dist/tools/handlers/pipeline-handlers.js +110 -0
- package/dist/tools/handlers/sequence-handlers.d.ts +3 -0
- package/dist/tools/handlers/sequence-handlers.js +376 -0
- package/dist/tools/handlers/system-handlers.d.ts +4 -0
- package/dist/tools/handlers/system-handlers.js +506 -0
- package/dist/tools/input.d.ts +19 -0
- package/dist/tools/input.js +89 -0
- package/dist/tools/introspection.d.ts +103 -40
- package/dist/tools/introspection.js +425 -568
- package/dist/tools/landscape.d.ts +54 -93
- package/dist/tools/landscape.js +284 -409
- package/dist/tools/level.d.ts +66 -27
- package/dist/tools/level.js +647 -675
- package/dist/tools/lighting.d.ts +77 -38
- package/dist/tools/lighting.js +445 -943
- package/dist/tools/logs.d.ts +3 -3
- package/dist/tools/logs.js +5 -57
- package/dist/tools/materials.d.ts +91 -24
- package/dist/tools/materials.js +194 -118
- package/dist/tools/niagara.d.ts +149 -39
- package/dist/tools/niagara.js +267 -182
- package/dist/tools/performance.d.ts +27 -13
- package/dist/tools/performance.js +203 -122
- package/dist/tools/physics.d.ts +32 -77
- package/dist/tools/physics.js +175 -582
- package/dist/tools/property-dictionary.d.ts +13 -0
- package/dist/tools/property-dictionary.js +82 -0
- package/dist/tools/sequence.d.ts +85 -60
- package/dist/tools/sequence.js +208 -747
- package/dist/tools/tool-definition-utils.d.ts +59 -0
- package/dist/tools/tool-definition-utils.js +35 -0
- package/dist/tools/ui.d.ts +64 -34
- package/dist/tools/ui.js +134 -214
- package/dist/types/automation-responses.d.ts +115 -0
- package/dist/types/automation-responses.js +2 -0
- package/dist/types/env.d.ts +0 -3
- package/dist/types/env.js +0 -7
- package/dist/types/responses.d.ts +249 -0
- package/dist/types/responses.js +2 -0
- package/dist/types/tool-interfaces.d.ts +898 -0
- package/dist/types/tool-interfaces.js +2 -0
- package/dist/types/tool-types.d.ts +183 -19
- package/dist/types/tool-types.js +0 -4
- package/dist/unreal-bridge.d.ts +24 -131
- package/dist/unreal-bridge.js +364 -1506
- package/dist/utils/command-validator.d.ts +9 -0
- package/dist/utils/command-validator.js +68 -0
- package/dist/utils/elicitation.d.ts +1 -1
- package/dist/utils/elicitation.js +12 -15
- package/dist/utils/error-handler.d.ts +2 -51
- package/dist/utils/error-handler.js +11 -87
- package/dist/utils/ini-reader.d.ts +3 -0
- package/dist/utils/ini-reader.js +69 -0
- package/dist/utils/logger.js +9 -6
- package/dist/utils/normalize.d.ts +3 -0
- package/dist/utils/normalize.js +56 -0
- package/dist/utils/path-security.d.ts +2 -0
- package/dist/utils/path-security.js +24 -0
- package/dist/utils/response-factory.d.ts +7 -0
- package/dist/utils/response-factory.js +27 -0
- package/dist/utils/response-validator.d.ts +3 -24
- package/dist/utils/response-validator.js +130 -81
- package/dist/utils/result-helpers.d.ts +4 -5
- package/dist/utils/result-helpers.js +15 -16
- package/dist/utils/safe-json.js +5 -11
- package/dist/utils/unreal-command-queue.d.ts +24 -0
- package/dist/utils/unreal-command-queue.js +120 -0
- package/dist/utils/validation.d.ts +0 -40
- package/dist/utils/validation.js +1 -78
- package/dist/wasm/index.d.ts +70 -0
- package/dist/wasm/index.js +535 -0
- package/docs/GraphQL-API.md +888 -0
- package/docs/Migration-Guide-v0.5.0.md +684 -0
- package/docs/Roadmap.md +53 -0
- package/docs/WebAssembly-Integration.md +628 -0
- package/docs/editor-plugin-extension.md +370 -0
- package/docs/handler-mapping.md +242 -0
- package/docs/native-automation-progress.md +128 -0
- package/docs/testing-guide.md +423 -0
- package/mcp-config-example.json +6 -6
- package/package.json +67 -28
- package/plugins/McpAutomationBridge/Config/FilterPlugin.ini +8 -0
- package/plugins/McpAutomationBridge/McpAutomationBridge.uplugin +64 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/McpAutomationBridge.Build.cs +189 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.cpp +22 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.h +30 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeHelpers.h +1983 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeModule.cpp +72 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSettings.cpp +46 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +581 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +2394 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetQueryHandlers.cpp +300 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetWorkflowHandlers.cpp +2807 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AudioHandlers.cpp +1087 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BehaviorTreeHandlers.cpp +488 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.cpp +643 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.h +31 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +1184 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +5652 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers_List.cpp +152 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ControlHandlers.cpp +2614 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_DebugHandlers.cpp +42 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EditorFunctionHandlers.cpp +1237 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +1701 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +2145 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_FoliageHandlers.cpp +954 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InputHandlers.cpp +209 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InsightsHandlers.cpp +41 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LandscapeHandlers.cpp +1164 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LevelHandlers.cpp +762 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +634 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LogHandlers.cpp +136 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_MaterialGraphHandlers.cpp +494 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraGraphHandlers.cpp +278 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraHandlers.cpp +625 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PerformanceHandlers.cpp +401 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PipelineHandlers.cpp +67 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +735 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PropertyHandlers.cpp +2634 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_RenderHandlers.cpp +189 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.cpp +917 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.h +39 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +2670 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequencerHandlers.cpp +519 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_TestHandlers.cpp +38 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_UiHandlers.cpp +668 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_WorldPartitionHandlers.cpp +346 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +1330 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.h +149 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +783 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSettings.h +115 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSubsystem.h +796 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpConnectionManager.h +117 -0
- package/scripts/check-unreal-connection.mjs +19 -0
- package/scripts/clean-tmp.js +23 -0
- package/scripts/patch-wasm.js +26 -0
- package/scripts/run-all-tests.mjs +136 -0
- package/scripts/smoke-test.ts +94 -0
- package/scripts/sync-mcp-plugin.js +143 -0
- package/scripts/test-no-plugin-alternates.mjs +113 -0
- package/scripts/validate-server.js +46 -0
- package/scripts/verify-automation-bridge.js +200 -0
- package/server.json +58 -21
- package/src/automation/bridge.ts +558 -0
- package/src/automation/connection-manager.ts +130 -0
- package/src/automation/handshake.ts +99 -0
- package/src/automation/index.ts +2 -0
- package/src/automation/message-handler.ts +167 -0
- package/src/automation/request-tracker.ts +123 -0
- package/src/automation/types.ts +107 -0
- package/src/cli.ts +33 -6
- package/src/config.ts +73 -0
- package/src/constants.ts +19 -0
- package/src/graphql/loaders.ts +244 -0
- package/src/graphql/resolvers.ts +1008 -0
- package/src/graphql/schema.ts +452 -0
- package/src/graphql/server.ts +156 -0
- package/src/graphql/types.ts +10 -0
- package/src/handlers/resource-handlers.ts +186 -0
- package/src/index.ts +166 -664
- package/src/resources/actors.ts +58 -76
- package/src/resources/assets.ts +148 -134
- package/src/resources/levels.ts +28 -33
- package/src/server/resource-registry.ts +47 -0
- package/src/server/tool-registry.ts +354 -0
- package/src/server-setup.ts +114 -0
- package/src/services/health-monitor.ts +132 -0
- package/src/services/metrics-server.ts +142 -0
- package/src/tools/actors.ts +426 -323
- package/src/tools/animation.ts +672 -461
- package/src/tools/assets.ts +364 -289
- package/src/tools/audio.ts +323 -766
- package/src/tools/base-tool.ts +52 -0
- package/src/tools/behavior-tree.ts +45 -0
- package/src/tools/blueprint.ts +792 -970
- package/src/tools/consolidated-tool-definitions.ts +993 -515
- package/src/tools/consolidated-tool-handlers.ts +258 -1146
- package/src/tools/debug.ts +292 -187
- package/src/tools/dynamic-handler-registry.ts +33 -0
- package/src/tools/editor.ts +329 -253
- package/src/tools/engine.ts +14 -3
- package/src/tools/environment.ts +281 -0
- package/src/tools/foliage.ts +330 -392
- package/src/tools/handlers/actor-handlers.ts +265 -0
- package/src/tools/handlers/animation-handlers.ts +237 -0
- package/src/tools/handlers/argument-helper.ts +142 -0
- package/src/tools/handlers/asset-handlers.ts +532 -0
- package/src/tools/handlers/audio-handlers.ts +194 -0
- package/src/tools/handlers/blueprint-handlers.ts +380 -0
- package/src/tools/handlers/common-handlers.ts +87 -0
- package/src/tools/handlers/editor-handlers.ts +123 -0
- package/src/tools/handlers/effect-handlers.ts +220 -0
- package/src/tools/handlers/environment-handlers.ts +183 -0
- package/src/tools/handlers/graph-handlers.ts +116 -0
- package/src/tools/handlers/input-handlers.ts +28 -0
- package/src/tools/handlers/inspect-handlers.ts +450 -0
- package/src/tools/handlers/level-handlers.ts +252 -0
- package/src/tools/handlers/lighting-handlers.ts +147 -0
- package/src/tools/handlers/performance-handlers.ts +132 -0
- package/src/tools/handlers/pipeline-handlers.ts +127 -0
- package/src/tools/handlers/sequence-handlers.ts +415 -0
- package/src/tools/handlers/system-handlers.ts +564 -0
- package/src/tools/input.ts +101 -0
- package/src/tools/introspection.ts +493 -584
- package/src/tools/landscape.ts +418 -507
- package/src/tools/level.ts +786 -708
- package/src/tools/lighting.ts +588 -984
- package/src/tools/logs.ts +9 -57
- package/src/tools/materials.ts +237 -121
- package/src/tools/niagara.ts +335 -168
- package/src/tools/performance.ts +320 -169
- package/src/tools/physics.ts +274 -613
- package/src/tools/property-dictionary.ts +98 -0
- package/src/tools/sequence.ts +276 -820
- package/src/tools/tool-definition-utils.ts +35 -0
- package/src/tools/ui.ts +205 -283
- package/src/types/automation-responses.ts +119 -0
- package/src/types/env.ts +0 -10
- package/src/types/responses.ts +355 -0
- package/src/types/tool-interfaces.ts +250 -0
- package/src/types/tool-types.ts +243 -21
- package/src/unreal-bridge.ts +460 -1550
- package/src/utils/command-validator.ts +76 -0
- package/src/utils/elicitation.ts +10 -7
- package/src/utils/error-handler.ts +14 -90
- package/src/utils/ini-reader.ts +86 -0
- package/src/utils/logger.ts +8 -3
- package/src/utils/normalize.test.ts +162 -0
- package/src/utils/normalize.ts +60 -0
- package/src/utils/path-security.ts +43 -0
- package/src/utils/response-factory.ts +44 -0
- package/src/utils/response-validator.ts +176 -56
- package/src/utils/result-helpers.ts +21 -19
- package/src/utils/safe-json.test.ts +90 -0
- package/src/utils/safe-json.ts +14 -11
- package/src/utils/unreal-command-queue.ts +152 -0
- package/src/utils/validation.test.ts +184 -0
- package/src/utils/validation.ts +4 -1
- package/src/wasm/index.ts +838 -0
- package/test-server.mjs +100 -0
- package/tests/run-unreal-tool-tests.mjs +242 -14
- package/tests/test-animation.mjs +369 -0
- package/tests/test-asset-advanced.mjs +82 -0
- package/tests/test-asset-errors.mjs +35 -0
- package/tests/test-asset-graph.mjs +311 -0
- package/tests/test-audio.mjs +417 -0
- package/tests/test-automation-timeouts.mjs +98 -0
- package/tests/test-behavior-tree.mjs +444 -0
- package/tests/test-blueprint-graph.mjs +410 -0
- package/tests/test-blueprint.mjs +577 -0
- package/tests/test-client-mode.mjs +86 -0
- package/tests/test-console-command.mjs +56 -0
- package/tests/test-control-actor.mjs +425 -0
- package/tests/test-control-editor.mjs +112 -0
- package/tests/test-graphql.mjs +372 -0
- package/tests/test-input.mjs +349 -0
- package/tests/test-inspect.mjs +302 -0
- package/tests/test-landscape.mjs +316 -0
- package/tests/test-lighting.mjs +428 -0
- package/tests/test-manage-asset.mjs +438 -0
- package/tests/test-manage-level.mjs +89 -0
- package/tests/test-materials.mjs +356 -0
- package/tests/test-niagara.mjs +185 -0
- package/tests/test-no-inline-python.mjs +122 -0
- package/tests/test-performance.mjs +539 -0
- package/tests/test-plugin-handshake.mjs +82 -0
- package/tests/test-runner.mjs +933 -0
- package/tests/test-sequence.mjs +104 -0
- package/tests/test-system.mjs +96 -0
- package/tests/test-wasm.mjs +283 -0
- package/tests/test-world-partition.mjs +215 -0
- package/tsconfig.json +3 -3
- package/vitest.config.ts +35 -0
- package/wasm/Cargo.lock +363 -0
- package/wasm/Cargo.toml +42 -0
- package/wasm/LICENSE +21 -0
- package/wasm/README.md +253 -0
- package/wasm/src/dependency_resolver.rs +377 -0
- package/wasm/src/lib.rs +153 -0
- package/wasm/src/property_parser.rs +271 -0
- package/wasm/src/transform_math.rs +396 -0
- package/wasm/tests/integration.rs +109 -0
- package/.github/workflows/smithery-build.yml +0 -29
- package/dist/prompts/index.d.ts +0 -21
- package/dist/prompts/index.js +0 -217
- package/dist/tools/build_environment_advanced.d.ts +0 -65
- package/dist/tools/build_environment_advanced.js +0 -633
- package/dist/tools/rc.d.ts +0 -110
- package/dist/tools/rc.js +0 -437
- package/dist/tools/visual.d.ts +0 -40
- package/dist/tools/visual.js +0 -282
- package/dist/utils/http.d.ts +0 -6
- package/dist/utils/http.js +0 -151
- package/dist/utils/python-output.d.ts +0 -18
- package/dist/utils/python-output.js +0 -290
- package/dist/utils/python.d.ts +0 -2
- package/dist/utils/python.js +0 -4
- package/dist/utils/stdio-redirect.d.ts +0 -2
- package/dist/utils/stdio-redirect.js +0 -20
- package/docs/unreal-tool-test-cases.md +0 -574
- package/smithery.yaml +0 -29
- package/src/prompts/index.ts +0 -249
- package/src/tools/build_environment_advanced.ts +0 -732
- package/src/tools/rc.ts +0 -515
- package/src/tools/visual.ts +0 -281
- package/src/utils/http.ts +0 -187
- package/src/utils/python-output.ts +0 -351
- package/src/utils/python.ts +0 -3
- package/src/utils/stdio-redirect.ts +0 -18
package/dist/tools/blueprint.js
CHANGED
|
@@ -1,903 +1,756 @@
|
|
|
1
|
+
import { BaseTool } from './base-tool.js';
|
|
2
|
+
import { Logger } from '../utils/logger.js';
|
|
1
3
|
import { validateAssetParams, concurrencyDelay } from '../utils/validation.js';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
async validateParentClassReference(parentClass, blueprintType) {
|
|
11
|
-
const trimmed = parentClass?.trim();
|
|
12
|
-
if (!trimmed) {
|
|
13
|
-
return { ok: true };
|
|
14
|
-
}
|
|
15
|
-
const escapedParent = escapePythonString(trimmed);
|
|
16
|
-
const python = `
|
|
17
|
-
import unreal
|
|
18
|
-
import json
|
|
19
|
-
|
|
20
|
-
result = {
|
|
21
|
-
'success': False,
|
|
22
|
-
'resolved': '',
|
|
23
|
-
'error': ''
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
def resolve_parent(spec, bp_type):
|
|
27
|
-
name = (spec or '').strip()
|
|
28
|
-
editor_lib = unreal.EditorAssetLibrary
|
|
29
|
-
if not name:
|
|
30
|
-
return None
|
|
31
|
-
try:
|
|
32
|
-
if name.startswith('/Script/'):
|
|
33
|
-
return unreal.load_class(None, name)
|
|
34
|
-
except Exception:
|
|
35
|
-
pass
|
|
36
|
-
try:
|
|
37
|
-
if name.startswith('/Game/'):
|
|
38
|
-
asset = editor_lib.load_asset(name)
|
|
39
|
-
if asset:
|
|
40
|
-
if hasattr(asset, 'generated_class'):
|
|
41
|
-
try:
|
|
42
|
-
generated = asset.generated_class()
|
|
43
|
-
if generated:
|
|
44
|
-
return generated
|
|
45
|
-
except Exception:
|
|
46
|
-
pass
|
|
47
|
-
return asset
|
|
48
|
-
except Exception:
|
|
49
|
-
pass
|
|
50
|
-
try:
|
|
51
|
-
candidate = getattr(unreal, name, None)
|
|
52
|
-
if candidate:
|
|
53
|
-
return candidate
|
|
54
|
-
except Exception:
|
|
55
|
-
pass
|
|
56
|
-
return None
|
|
57
|
-
|
|
58
|
-
try:
|
|
59
|
-
parent_spec = r"${escapedParent}"
|
|
60
|
-
resolved = resolve_parent(parent_spec, "${blueprintType}")
|
|
61
|
-
resolved_path = ''
|
|
62
|
-
|
|
63
|
-
if resolved:
|
|
64
|
-
try:
|
|
65
|
-
resolved_path = resolved.get_path_name()
|
|
66
|
-
except Exception:
|
|
67
|
-
try:
|
|
68
|
-
resolved_path = str(resolved.get_outer().get_path_name())
|
|
69
|
-
except Exception:
|
|
70
|
-
resolved_path = str(resolved)
|
|
71
|
-
|
|
72
|
-
normalized_resolved = resolved_path.replace('Class ', '').replace('class ', '').strip().lower()
|
|
73
|
-
normalized_spec = parent_spec.strip().lower()
|
|
74
|
-
|
|
75
|
-
if normalized_spec.startswith('/script/'):
|
|
76
|
-
if not normalized_resolved.endswith(normalized_spec):
|
|
77
|
-
resolved = None
|
|
78
|
-
elif normalized_spec.startswith('/game/'):
|
|
79
|
-
try:
|
|
80
|
-
if not unreal.EditorAssetLibrary.does_asset_exist(parent_spec):
|
|
81
|
-
resolved = None
|
|
82
|
-
except Exception:
|
|
83
|
-
resolved = None
|
|
84
|
-
|
|
85
|
-
if resolved:
|
|
86
|
-
result['success'] = True
|
|
87
|
-
try:
|
|
88
|
-
result['resolved'] = resolved_path or str(resolved)
|
|
89
|
-
except Exception:
|
|
90
|
-
result['resolved'] = str(resolved)
|
|
91
|
-
else:
|
|
92
|
-
result['error'] = 'Parent class not found: ' + parent_spec
|
|
93
|
-
except Exception as e:
|
|
94
|
-
result['error'] = str(e)
|
|
95
|
-
|
|
96
|
-
print('RESULT:' + json.dumps(result))
|
|
97
|
-
`.trim();
|
|
4
|
+
import { coerceString } from '../utils/result-helpers.js';
|
|
5
|
+
export class BlueprintTools extends BaseTool {
|
|
6
|
+
log = new Logger('BlueprintTools');
|
|
7
|
+
pluginBlueprintActionsAvailable = null;
|
|
8
|
+
async sendAction(action, payload = {}, options) {
|
|
9
|
+
const envDefault = Number(process.env.MCP_AUTOMATION_REQUEST_TIMEOUT_MS ?? '120000');
|
|
10
|
+
const defaultTimeout = Number.isFinite(envDefault) && envDefault > 0 ? envDefault : 120000;
|
|
11
|
+
const finalTimeout = typeof options?.timeoutMs === 'number' && options?.timeoutMs > 0 ? options.timeoutMs : defaultTimeout;
|
|
98
12
|
try {
|
|
99
|
-
const response = await this.
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
});
|
|
104
|
-
if (interpreted.success) {
|
|
105
|
-
return { ok: true, resolved: interpreted.payload?.resolved ?? interpreted.message };
|
|
106
|
-
}
|
|
107
|
-
const error = interpreted.error || interpreted.payload?.error || `Parent class not found: ${trimmed}`;
|
|
108
|
-
return { ok: false, error };
|
|
13
|
+
const response = await this.sendAutomationRequest(action, payload, { timeoutMs: finalTimeout, waitForEvent: !!options?.waitForEvent, waitForEventTimeoutMs: options?.waitForEventTimeoutMs });
|
|
14
|
+
const success = response && response.success !== false;
|
|
15
|
+
const result = response.result ?? response;
|
|
16
|
+
return { success, message: response.message ?? undefined, error: response.success === false ? (response.error ?? response.message) : undefined, result, requestId: response.requestId };
|
|
109
17
|
}
|
|
110
18
|
catch (err) {
|
|
111
|
-
return {
|
|
19
|
+
return { success: false, error: String(err), message: String(err) };
|
|
112
20
|
}
|
|
113
21
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
22
|
+
isUnknownActionResponse(res) {
|
|
23
|
+
if (!res)
|
|
24
|
+
return false;
|
|
25
|
+
const txt = String((res.error ?? res.message ?? '')).toLowerCase();
|
|
26
|
+
return txt.includes('unknown_action') || txt.includes('unknown automation action') || txt.includes('not_implemented') || txt === 'unknown_plugin_action';
|
|
27
|
+
}
|
|
28
|
+
buildCandidates(rawName) {
|
|
29
|
+
const trimmed = coerceString(rawName)?.trim();
|
|
30
|
+
if (!trimmed)
|
|
31
|
+
return [];
|
|
32
|
+
const normalized = trimmed.replace(/\\/g, '/').replace(/\/\/+/g, '/');
|
|
33
|
+
const withoutLeading = normalized.replace(/^\/+/, '');
|
|
34
|
+
const basename = withoutLeading.split('/').pop() ?? withoutLeading;
|
|
35
|
+
const candidates = [];
|
|
36
|
+
if (normalized.includes('/')) {
|
|
37
|
+
if (normalized.startsWith('/'))
|
|
38
|
+
candidates.push(normalized);
|
|
39
|
+
if (basename) {
|
|
40
|
+
candidates.push(`/Game/Blueprints/${basename}`);
|
|
41
|
+
candidates.push(`/Game/${basename}`);
|
|
42
|
+
}
|
|
43
|
+
if (!normalized.startsWith('/'))
|
|
44
|
+
candidates.push(`/${withoutLeading}`);
|
|
45
|
+
}
|
|
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
|
+
}
|
|
117
56
|
async createBlueprint(params) {
|
|
118
57
|
try {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
message: `Failed to create blueprint: ${validation.error}`,
|
|
128
|
-
error: validation.error
|
|
129
|
-
};
|
|
58
|
+
const validation = validateAssetParams({ name: params.name, savePath: params.savePath || '/Game/Blueprints' });
|
|
59
|
+
if (!validation.valid)
|
|
60
|
+
return { success: false, message: `Failed to create blueprint: ${validation.error}`, error: validation.error };
|
|
61
|
+
const sanitized = validation.sanitized;
|
|
62
|
+
const payload = { 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
|
+
if (this.pluginBlueprintActionsAvailable === false) {
|
|
65
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement blueprint_create' };
|
|
130
66
|
}
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
67
|
+
const envPluginTimeout = Number(process.env.MCP_AUTOMATION_PLUGIN_CREATE_TIMEOUT_MS ?? process.env.MCP_AUTOMATION_REQUEST_TIMEOUT_MS ?? '15000');
|
|
68
|
+
const pluginTimeout = Number.isFinite(envPluginTimeout) && envPluginTimeout > 0 ? envPluginTimeout : 15000;
|
|
69
|
+
try {
|
|
70
|
+
const res = await this.sendAction('blueprint_create', payload, { timeoutMs: typeof params.timeoutMs === 'number' ? params.timeoutMs : pluginTimeout, waitForEvent: !!params.waitForCompletion, waitForEventTimeoutMs: params.waitForCompletionTimeoutMs });
|
|
71
|
+
if (res && res.success) {
|
|
72
|
+
this.pluginBlueprintActionsAvailable = true;
|
|
73
|
+
return {
|
|
74
|
+
...res,
|
|
75
|
+
blueprint: sanitized.name,
|
|
76
|
+
path: `${sanitized.savePath}/${sanitized.name}`.replace('//', '/'),
|
|
77
|
+
message: res.message || `Created blueprint ${sanitized.name}`
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (res && this.isUnknownActionResponse(res)) {
|
|
81
|
+
this.pluginBlueprintActionsAvailable = false;
|
|
82
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement blueprint_create' };
|
|
143
83
|
}
|
|
84
|
+
return res;
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
const errTxt = String(err ?? '');
|
|
88
|
+
const isTimeout = errTxt.includes('Request timed out') || errTxt.includes('-32001') || errTxt.toLowerCase().includes('timeout');
|
|
89
|
+
if (isTimeout) {
|
|
90
|
+
this.pluginBlueprintActionsAvailable = false;
|
|
91
|
+
}
|
|
92
|
+
return { success: false, error: String(err), message: String(err) };
|
|
144
93
|
}
|
|
145
|
-
const escapedName = escapePythonString(sanitizedParams.name);
|
|
146
|
-
const escapedPath = escapePythonString(path);
|
|
147
|
-
const escapedParent = escapePythonString(params.parentClass ?? '');
|
|
148
|
-
await concurrencyDelay();
|
|
149
|
-
const pythonScript = `
|
|
150
|
-
import unreal
|
|
151
|
-
import time
|
|
152
|
-
import json
|
|
153
|
-
import traceback
|
|
154
|
-
|
|
155
|
-
def ensure_asset_persistence(asset_path):
|
|
156
|
-
try:
|
|
157
|
-
asset_subsystem = None
|
|
158
|
-
try:
|
|
159
|
-
asset_subsystem = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem)
|
|
160
|
-
except Exception:
|
|
161
|
-
asset_subsystem = None
|
|
162
|
-
|
|
163
|
-
editor_lib = unreal.EditorAssetLibrary
|
|
164
|
-
|
|
165
|
-
asset = None
|
|
166
|
-
if asset_subsystem and hasattr(asset_subsystem, 'load_asset'):
|
|
167
|
-
try:
|
|
168
|
-
asset = asset_subsystem.load_asset(asset_path)
|
|
169
|
-
except Exception:
|
|
170
|
-
asset = None
|
|
171
|
-
if not asset:
|
|
172
|
-
try:
|
|
173
|
-
asset = editor_lib.load_asset(asset_path)
|
|
174
|
-
except Exception:
|
|
175
|
-
asset = None
|
|
176
|
-
if not asset:
|
|
177
|
-
return False
|
|
178
|
-
|
|
179
|
-
saved = False
|
|
180
|
-
if asset_subsystem and hasattr(asset_subsystem, 'save_loaded_asset'):
|
|
181
|
-
try:
|
|
182
|
-
saved = asset_subsystem.save_loaded_asset(asset)
|
|
183
|
-
except Exception:
|
|
184
|
-
saved = False
|
|
185
|
-
if not saved and asset_subsystem and hasattr(asset_subsystem, 'save_asset'):
|
|
186
|
-
try:
|
|
187
|
-
saved = asset_subsystem.save_asset(asset_path, only_if_is_dirty=False)
|
|
188
|
-
except Exception:
|
|
189
|
-
saved = False
|
|
190
|
-
if not saved:
|
|
191
|
-
try:
|
|
192
|
-
if hasattr(editor_lib, 'save_loaded_asset'):
|
|
193
|
-
saved = editor_lib.save_loaded_asset(asset)
|
|
194
|
-
else:
|
|
195
|
-
saved = editor_lib.save_asset(asset_path, only_if_is_dirty=False)
|
|
196
|
-
except Exception:
|
|
197
|
-
saved = False
|
|
198
|
-
|
|
199
|
-
if not saved:
|
|
200
|
-
return False
|
|
201
|
-
|
|
202
|
-
asset_dir = asset_path.rsplit('/', 1)[0]
|
|
203
|
-
try:
|
|
204
|
-
registry = unreal.AssetRegistryHelpers.get_asset_registry()
|
|
205
|
-
if hasattr(registry, 'scan_paths_synchronous'):
|
|
206
|
-
registry.scan_paths_synchronous([asset_dir], True)
|
|
207
|
-
except Exception:
|
|
208
|
-
pass
|
|
209
|
-
|
|
210
|
-
for _ in range(5):
|
|
211
|
-
if editor_lib.does_asset_exist(asset_path):
|
|
212
|
-
return True
|
|
213
|
-
time.sleep(0.2)
|
|
214
|
-
try:
|
|
215
|
-
registry = unreal.AssetRegistryHelpers.get_asset_registry()
|
|
216
|
-
if hasattr(registry, 'scan_paths_synchronous'):
|
|
217
|
-
registry.scan_paths_synchronous([asset_dir], True)
|
|
218
|
-
except Exception:
|
|
219
|
-
pass
|
|
220
|
-
return False
|
|
221
|
-
except Exception as e:
|
|
222
|
-
print(f"Error ensuring persistence: {e}")
|
|
223
|
-
return False
|
|
224
|
-
|
|
225
|
-
def resolve_parent_class(explicit_name, blueprint_type):
|
|
226
|
-
editor_lib = unreal.EditorAssetLibrary
|
|
227
|
-
name = (explicit_name or '').strip()
|
|
228
|
-
if name:
|
|
229
|
-
try:
|
|
230
|
-
if name.startswith('/Script/'):
|
|
231
|
-
try:
|
|
232
|
-
loaded = unreal.load_class(None, name)
|
|
233
|
-
if loaded:
|
|
234
|
-
return loaded
|
|
235
|
-
except Exception:
|
|
236
|
-
pass
|
|
237
|
-
if name.startswith('/Game/'):
|
|
238
|
-
loaded_asset = editor_lib.load_asset(name)
|
|
239
|
-
if loaded_asset:
|
|
240
|
-
if hasattr(loaded_asset, 'generated_class'):
|
|
241
|
-
try:
|
|
242
|
-
generated = loaded_asset.generated_class()
|
|
243
|
-
if generated:
|
|
244
|
-
return generated
|
|
245
|
-
except Exception:
|
|
246
|
-
pass
|
|
247
|
-
return loaded_asset
|
|
248
|
-
candidate = getattr(unreal, name, None)
|
|
249
|
-
if candidate:
|
|
250
|
-
return candidate
|
|
251
|
-
except Exception:
|
|
252
|
-
pass
|
|
253
|
-
return None
|
|
254
|
-
|
|
255
|
-
mapping = {
|
|
256
|
-
'Actor': unreal.Actor,
|
|
257
|
-
'Pawn': unreal.Pawn,
|
|
258
|
-
'Character': unreal.Character,
|
|
259
|
-
'GameMode': unreal.GameModeBase,
|
|
260
|
-
'PlayerController': unreal.PlayerController,
|
|
261
|
-
'HUD': unreal.HUD,
|
|
262
|
-
'ActorComponent': unreal.ActorComponent,
|
|
263
|
-
}
|
|
264
|
-
return mapping.get(blueprint_type, unreal.Actor)
|
|
265
|
-
|
|
266
|
-
result = {
|
|
267
|
-
'success': False,
|
|
268
|
-
'message': '',
|
|
269
|
-
'path': '',
|
|
270
|
-
'error': '',
|
|
271
|
-
'exists': False,
|
|
272
|
-
'parent': '',
|
|
273
|
-
'verifyError': '',
|
|
274
|
-
'warnings': [],
|
|
275
|
-
'details': []
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
success_message = ''
|
|
279
|
-
|
|
280
|
-
def record_detail(message):
|
|
281
|
-
result['details'].append(str(message))
|
|
282
|
-
|
|
283
|
-
def record_warning(message):
|
|
284
|
-
result['warnings'].append(str(message))
|
|
285
|
-
|
|
286
|
-
def set_message(message):
|
|
287
|
-
global success_message
|
|
288
|
-
if not success_message:
|
|
289
|
-
success_message = str(message)
|
|
290
|
-
|
|
291
|
-
def set_error(message):
|
|
292
|
-
result['error'] = str(message)
|
|
293
|
-
|
|
294
|
-
asset_path = "${escapedPath}"
|
|
295
|
-
asset_name = "${escapedName}"
|
|
296
|
-
full_path = f"{asset_path}/{asset_name}"
|
|
297
|
-
result['path'] = full_path
|
|
298
|
-
|
|
299
|
-
asset_subsystem = None
|
|
300
|
-
try:
|
|
301
|
-
asset_subsystem = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem)
|
|
302
|
-
except Exception:
|
|
303
|
-
asset_subsystem = None
|
|
304
|
-
|
|
305
|
-
editor_lib = unreal.EditorAssetLibrary
|
|
306
|
-
|
|
307
|
-
try:
|
|
308
|
-
level_subsystem = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
|
|
309
|
-
play_subsystem = None
|
|
310
|
-
try:
|
|
311
|
-
play_subsystem = unreal.get_editor_subsystem(unreal.EditorPlayWorldSubsystem)
|
|
312
|
-
except Exception:
|
|
313
|
-
play_subsystem = None
|
|
314
|
-
|
|
315
|
-
is_playing = False
|
|
316
|
-
if level_subsystem and hasattr(level_subsystem, 'is_in_play_in_editor'):
|
|
317
|
-
is_playing = bool(level_subsystem.is_in_play_in_editor())
|
|
318
|
-
elif play_subsystem and hasattr(play_subsystem, 'is_playing_in_editor'):
|
|
319
|
-
is_playing = bool(play_subsystem.is_playing_in_editor())
|
|
320
|
-
|
|
321
|
-
if is_playing:
|
|
322
|
-
print('Stopping Play In Editor mode...')
|
|
323
|
-
record_detail('Stopping Play In Editor mode')
|
|
324
|
-
if level_subsystem and hasattr(level_subsystem, 'editor_request_end_play'):
|
|
325
|
-
level_subsystem.editor_request_end_play()
|
|
326
|
-
elif play_subsystem and hasattr(play_subsystem, 'stop_playing_session'):
|
|
327
|
-
play_subsystem.stop_playing_session()
|
|
328
|
-
elif play_subsystem and hasattr(play_subsystem, 'end_play'):
|
|
329
|
-
play_subsystem.end_play()
|
|
330
|
-
else:
|
|
331
|
-
record_warning('Unable to stop Play In Editor via modern subsystems; please stop PIE manually.')
|
|
332
|
-
time.sleep(0.5)
|
|
333
|
-
except Exception as stop_err:
|
|
334
|
-
record_warning(f'PIE stop check failed: {stop_err}')
|
|
335
|
-
|
|
336
|
-
try:
|
|
337
|
-
try:
|
|
338
|
-
if asset_subsystem and hasattr(asset_subsystem, 'does_asset_exist'):
|
|
339
|
-
asset_exists = asset_subsystem.does_asset_exist(full_path)
|
|
340
|
-
else:
|
|
341
|
-
asset_exists = editor_lib.does_asset_exist(full_path)
|
|
342
|
-
except Exception:
|
|
343
|
-
asset_exists = editor_lib.does_asset_exist(full_path)
|
|
344
|
-
|
|
345
|
-
result['exists'] = bool(asset_exists)
|
|
346
|
-
|
|
347
|
-
if asset_exists:
|
|
348
|
-
existing = None
|
|
349
|
-
try:
|
|
350
|
-
if asset_subsystem and hasattr(asset_subsystem, 'load_asset'):
|
|
351
|
-
existing = asset_subsystem.load_asset(full_path)
|
|
352
|
-
elif asset_subsystem and hasattr(asset_subsystem, 'get_asset'):
|
|
353
|
-
existing = asset_subsystem.get_asset(full_path)
|
|
354
|
-
else:
|
|
355
|
-
existing = editor_lib.load_asset(full_path)
|
|
356
|
-
except Exception:
|
|
357
|
-
existing = editor_lib.load_asset(full_path)
|
|
358
|
-
|
|
359
|
-
if existing:
|
|
360
|
-
result['success'] = True
|
|
361
|
-
result['message'] = f"Blueprint already exists at {full_path}"
|
|
362
|
-
set_message(result['message'])
|
|
363
|
-
record_detail(result['message'])
|
|
364
|
-
try:
|
|
365
|
-
result['parent'] = str(existing.generated_class())
|
|
366
|
-
except Exception:
|
|
367
|
-
try:
|
|
368
|
-
result['parent'] = str(type(existing))
|
|
369
|
-
except Exception:
|
|
370
|
-
pass
|
|
371
|
-
else:
|
|
372
|
-
set_error(f"Asset exists but could not be loaded: {full_path}")
|
|
373
|
-
record_warning(result['error'])
|
|
374
|
-
else:
|
|
375
|
-
factory = unreal.BlueprintFactory()
|
|
376
|
-
explicit_parent = "${escapedParent}"
|
|
377
|
-
parent_class = None
|
|
378
|
-
|
|
379
|
-
if explicit_parent.strip():
|
|
380
|
-
parent_class = resolve_parent_class(explicit_parent, "${params.blueprintType}")
|
|
381
|
-
if not parent_class:
|
|
382
|
-
set_error(f"Parent class not found: {explicit_parent}")
|
|
383
|
-
record_warning(result['error'])
|
|
384
|
-
raise RuntimeError(result['error'])
|
|
385
|
-
else:
|
|
386
|
-
parent_class = resolve_parent_class('', "${params.blueprintType}")
|
|
387
|
-
|
|
388
|
-
if parent_class:
|
|
389
|
-
result['parent'] = str(parent_class)
|
|
390
|
-
record_detail(f"Resolved parent class: {result['parent']}")
|
|
391
|
-
try:
|
|
392
|
-
factory.set_editor_property('parent_class', parent_class)
|
|
393
|
-
except Exception:
|
|
394
|
-
try:
|
|
395
|
-
factory.set_editor_property('ParentClass', parent_class)
|
|
396
|
-
except Exception:
|
|
397
|
-
try:
|
|
398
|
-
factory.ParentClass = parent_class
|
|
399
|
-
except Exception:
|
|
400
|
-
pass
|
|
401
|
-
|
|
402
|
-
new_asset = None
|
|
403
|
-
try:
|
|
404
|
-
if asset_subsystem and hasattr(asset_subsystem, 'create_asset'):
|
|
405
|
-
new_asset = asset_subsystem.create_asset(
|
|
406
|
-
asset_name=asset_name,
|
|
407
|
-
package_path=asset_path,
|
|
408
|
-
asset_class=unreal.Blueprint,
|
|
409
|
-
factory=factory
|
|
410
|
-
)
|
|
411
|
-
else:
|
|
412
|
-
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
|
|
413
|
-
new_asset = asset_tools.create_asset(
|
|
414
|
-
asset_name=asset_name,
|
|
415
|
-
package_path=asset_path,
|
|
416
|
-
asset_class=unreal.Blueprint,
|
|
417
|
-
factory=factory
|
|
418
|
-
)
|
|
419
|
-
except Exception as create_error:
|
|
420
|
-
set_error(f"Asset creation failed: {create_error}")
|
|
421
|
-
record_warning(result['error'])
|
|
422
|
-
traceback.print_exc()
|
|
423
|
-
new_asset = None
|
|
424
|
-
|
|
425
|
-
if new_asset:
|
|
426
|
-
result['message'] = f"Blueprint created at {full_path}"
|
|
427
|
-
set_message(result['message'])
|
|
428
|
-
record_detail(result['message'])
|
|
429
|
-
if ensure_asset_persistence(full_path):
|
|
430
|
-
verified = False
|
|
431
|
-
try:
|
|
432
|
-
if asset_subsystem and hasattr(asset_subsystem, 'does_asset_exist'):
|
|
433
|
-
verified = asset_subsystem.does_asset_exist(full_path)
|
|
434
|
-
else:
|
|
435
|
-
verified = editor_lib.does_asset_exist(full_path)
|
|
436
|
-
except Exception as verify_error:
|
|
437
|
-
result['verifyError'] = str(verify_error)
|
|
438
|
-
verified = editor_lib.does_asset_exist(full_path)
|
|
439
|
-
|
|
440
|
-
if not verified:
|
|
441
|
-
time.sleep(0.2)
|
|
442
|
-
verified = editor_lib.does_asset_exist(full_path)
|
|
443
|
-
if not verified:
|
|
444
|
-
try:
|
|
445
|
-
verified = editor_lib.load_asset(full_path) is not None
|
|
446
|
-
except Exception:
|
|
447
|
-
verified = False
|
|
448
|
-
|
|
449
|
-
if verified:
|
|
450
|
-
result['success'] = True
|
|
451
|
-
result['error'] = ''
|
|
452
|
-
set_message(result['message'])
|
|
453
|
-
else:
|
|
454
|
-
set_error(f"Blueprint not found after save: {full_path}")
|
|
455
|
-
record_warning(result['error'])
|
|
456
|
-
else:
|
|
457
|
-
set_error('Failed to persist blueprint to disk')
|
|
458
|
-
record_warning(result['error'])
|
|
459
|
-
else:
|
|
460
|
-
if not result['error']:
|
|
461
|
-
set_error(f"Failed to create Blueprint {asset_name}")
|
|
462
|
-
except Exception as e:
|
|
463
|
-
set_error(str(e))
|
|
464
|
-
record_warning(result['error'])
|
|
465
|
-
traceback.print_exc()
|
|
466
|
-
|
|
467
|
-
# Finalize messaging
|
|
468
|
-
default_success_message = f"Blueprint created at {full_path}"
|
|
469
|
-
default_failure_message = f"Failed to create blueprint {asset_name}"
|
|
470
|
-
|
|
471
|
-
if result['success'] and not success_message:
|
|
472
|
-
set_message(default_success_message)
|
|
473
|
-
|
|
474
|
-
if not result['success'] and not result['error']:
|
|
475
|
-
set_error(default_failure_message)
|
|
476
|
-
|
|
477
|
-
if not result['message']:
|
|
478
|
-
if result['success']:
|
|
479
|
-
result['message'] = success_message or default_success_message
|
|
480
|
-
else:
|
|
481
|
-
result['message'] = result['error'] or default_failure_message
|
|
482
|
-
|
|
483
|
-
result['error'] = None if result['success'] else result['error']
|
|
484
|
-
|
|
485
|
-
if not result['warnings']:
|
|
486
|
-
result.pop('warnings')
|
|
487
|
-
if not result['details']:
|
|
488
|
-
result.pop('details')
|
|
489
|
-
if result.get('error') is None:
|
|
490
|
-
result.pop('error')
|
|
491
|
-
|
|
492
|
-
print('RESULT:' + json.dumps(result))
|
|
493
|
-
`.trim();
|
|
494
|
-
const response = await this.bridge.executePython(pythonScript);
|
|
495
|
-
return this.parseBlueprintCreationOutput(response, sanitizedParams.name, path);
|
|
496
94
|
}
|
|
497
95
|
catch (err) {
|
|
498
|
-
return { success: false, error:
|
|
96
|
+
return { success: false, error: String(err), message: String(err) };
|
|
499
97
|
}
|
|
500
98
|
}
|
|
501
|
-
|
|
502
|
-
const
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
const details = interpreted.details ?? coerceStringArray(payload.details) ?? undefined;
|
|
511
|
-
const path = coerceString(payload.path) ?? defaultPath;
|
|
512
|
-
const parent = coerceString(payload.parent);
|
|
513
|
-
const verifyError = coerceString(payload.verifyError);
|
|
514
|
-
const exists = coerceBoolean(payload.exists);
|
|
515
|
-
const errorValue = coerceString(payload.error) ?? interpreted.error;
|
|
516
|
-
if (hasPayload) {
|
|
517
|
-
if (interpreted.success) {
|
|
518
|
-
const outcome = {
|
|
519
|
-
success: true,
|
|
520
|
-
message: interpreted.message,
|
|
521
|
-
path
|
|
522
|
-
};
|
|
523
|
-
if (typeof exists === 'boolean') {
|
|
524
|
-
outcome.exists = exists;
|
|
525
|
-
}
|
|
526
|
-
if (parent) {
|
|
527
|
-
outcome.parent = parent;
|
|
528
|
-
}
|
|
529
|
-
if (verifyError) {
|
|
530
|
-
outcome.verifyError = verifyError;
|
|
531
|
-
}
|
|
532
|
-
if (warnings && warnings.length > 0) {
|
|
533
|
-
outcome.warnings = warnings;
|
|
534
|
-
}
|
|
535
|
-
if (details && details.length > 0) {
|
|
536
|
-
outcome.details = details;
|
|
537
|
-
}
|
|
538
|
-
return outcome;
|
|
539
|
-
}
|
|
540
|
-
const fallbackMessage = errorValue ?? interpreted.message;
|
|
541
|
-
const failureOutcome = {
|
|
542
|
-
success: false,
|
|
543
|
-
message: `Failed to create blueprint: ${fallbackMessage}`,
|
|
544
|
-
error: fallbackMessage,
|
|
545
|
-
path
|
|
546
|
-
};
|
|
547
|
-
if (typeof exists === 'boolean') {
|
|
548
|
-
failureOutcome.exists = exists;
|
|
549
|
-
}
|
|
550
|
-
if (parent) {
|
|
551
|
-
failureOutcome.parent = parent;
|
|
552
|
-
}
|
|
553
|
-
if (verifyError) {
|
|
554
|
-
failureOutcome.verifyError = verifyError;
|
|
555
|
-
}
|
|
556
|
-
if (warnings && warnings.length > 0) {
|
|
557
|
-
failureOutcome.warnings = warnings;
|
|
558
|
-
}
|
|
559
|
-
if (details && details.length > 0) {
|
|
560
|
-
failureOutcome.details = details;
|
|
99
|
+
async modifyConstructionScript(params) {
|
|
100
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
101
|
+
if (!blueprintPath)
|
|
102
|
+
return { success: false, message: 'Blueprint path is required', error: 'INVALID_BLUEPRINT_PATH' };
|
|
103
|
+
if (!Array.isArray(params.operations) || params.operations.length === 0)
|
|
104
|
+
return { success: false, message: 'At least one SCS operation is required', error: 'MISSING_OPERATIONS' };
|
|
105
|
+
const operations = params.operations.map(op => {
|
|
106
|
+
if (op && typeof op === 'object' && op.op && !op.type) {
|
|
107
|
+
return { ...op, type: op.op };
|
|
561
108
|
}
|
|
562
|
-
return
|
|
109
|
+
return op;
|
|
110
|
+
});
|
|
111
|
+
const payload = { blueprintPath, operations };
|
|
112
|
+
if (typeof params.compile === 'boolean')
|
|
113
|
+
payload.compile = params.compile;
|
|
114
|
+
if (typeof params.save === 'boolean')
|
|
115
|
+
payload.save = params.save;
|
|
116
|
+
const res = await this.sendAction('blueprint_modify_scs', payload, { timeoutMs: params.timeoutMs, waitForEvent: !!params.waitForCompletion, waitForEventTimeoutMs: params.waitForCompletionTimeoutMs });
|
|
117
|
+
if (res && res.result && typeof res.result === 'object' && res.result.error === 'SCS_UNAVAILABLE') {
|
|
118
|
+
this.pluginBlueprintActionsAvailable = false;
|
|
119
|
+
return { success: false, error: 'SCS_UNAVAILABLE', message: 'Plugin does not support construction script modification (blueprint_modify_scs)' };
|
|
563
120
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
if (
|
|
567
|
-
|
|
568
|
-
success: false,
|
|
569
|
-
message: `Failed to create blueprint: ${failureMessage}`,
|
|
570
|
-
error: failureMessage,
|
|
571
|
-
path: defaultPath
|
|
572
|
-
};
|
|
121
|
+
if (res && res.success)
|
|
122
|
+
this.pluginBlueprintActionsAvailable = true;
|
|
123
|
+
if (res && this.isUnknownActionResponse(res)) {
|
|
124
|
+
this.pluginBlueprintActionsAvailable = false;
|
|
573
125
|
}
|
|
574
|
-
|
|
575
|
-
return {
|
|
576
|
-
success: true,
|
|
577
|
-
message: `Blueprint ${blueprintName} created`,
|
|
578
|
-
path: defaultPath
|
|
579
|
-
};
|
|
580
|
-
}
|
|
581
|
-
return {
|
|
582
|
-
success: false,
|
|
583
|
-
message: interpreted.message,
|
|
584
|
-
error: interpreted.error ?? (cleanedText || JSON.stringify(response)),
|
|
585
|
-
path: defaultPath
|
|
586
|
-
};
|
|
126
|
+
return res;
|
|
587
127
|
}
|
|
588
|
-
/**
|
|
589
|
-
* Add Component to Blueprint
|
|
590
|
-
*/
|
|
591
128
|
async addComponent(params) {
|
|
129
|
+
const blueprintName = coerceString(params.blueprintName);
|
|
130
|
+
if (!blueprintName)
|
|
131
|
+
return { success: false, message: 'Blueprint name is required', error: 'INVALID_BLUEPRINT' };
|
|
132
|
+
const componentClass = coerceString(params.componentType);
|
|
133
|
+
if (!componentClass)
|
|
134
|
+
return { success: false, message: 'Component class is required', error: 'INVALID_COMPONENT_CLASS' };
|
|
135
|
+
const rawComponentName = coerceString(params.componentName) ?? params.componentName;
|
|
136
|
+
if (!rawComponentName)
|
|
137
|
+
return { success: false, 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)
|
|
142
|
+
return { success: false, error: 'Invalid blueprint name' };
|
|
592
143
|
try {
|
|
593
|
-
|
|
594
|
-
const
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
if cleaned.startswith('/'):
|
|
629
|
-
return [cleaned]
|
|
630
|
-
bases = [
|
|
631
|
-
f"/Game/Blueprints/{cleaned}",
|
|
632
|
-
f"/Game/Blueprints/LiveTests/{cleaned}",
|
|
633
|
-
f"/Game/Blueprints/DirectAPI/{cleaned}",
|
|
634
|
-
f"/Game/Blueprints/ComponentTests/{cleaned}",
|
|
635
|
-
f"/Game/Blueprints/Types/{cleaned}",
|
|
636
|
-
f"/Game/Blueprints/ComprehensiveTest/{cleaned}",
|
|
637
|
-
f"/Game/{cleaned}"
|
|
638
|
-
]
|
|
639
|
-
final = []
|
|
640
|
-
for entry in bases:
|
|
641
|
-
if entry.endswith('.uasset'):
|
|
642
|
-
final.append(entry[:-7])
|
|
643
|
-
final.append(entry)
|
|
644
|
-
return final
|
|
645
|
-
|
|
646
|
-
def load_blueprint(raw_name):
|
|
647
|
-
editor_lib = unreal.EditorAssetLibrary
|
|
648
|
-
asset_subsystem = None
|
|
649
|
-
try:
|
|
650
|
-
asset_subsystem = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem)
|
|
651
|
-
except Exception:
|
|
652
|
-
asset_subsystem = None
|
|
653
|
-
|
|
654
|
-
for path in candidate_paths(raw_name):
|
|
655
|
-
asset = None
|
|
656
|
-
try:
|
|
657
|
-
if asset_subsystem and hasattr(asset_subsystem, 'load_asset'):
|
|
658
|
-
asset = asset_subsystem.load_asset(path)
|
|
659
|
-
else:
|
|
660
|
-
asset = editor_lib.load_asset(path)
|
|
661
|
-
except Exception:
|
|
662
|
-
asset = editor_lib.load_asset(path)
|
|
663
|
-
if asset:
|
|
664
|
-
add_detail(f"Resolved blueprint at {path}")
|
|
665
|
-
return path, asset
|
|
666
|
-
return None, None
|
|
667
|
-
|
|
668
|
-
def resolve_component_class(raw_class_name):
|
|
669
|
-
name = normalize_name(raw_class_name)
|
|
670
|
-
if not name:
|
|
671
|
-
return None
|
|
672
|
-
try:
|
|
673
|
-
if name.startswith('/Script/'):
|
|
674
|
-
loaded = unreal.load_class(None, name)
|
|
675
|
-
if loaded:
|
|
676
|
-
return loaded
|
|
677
|
-
except Exception as err:
|
|
678
|
-
add_warning(f"load_class failed: {err}")
|
|
679
|
-
try:
|
|
680
|
-
candidate = getattr(unreal, name, None)
|
|
681
|
-
if candidate:
|
|
682
|
-
return candidate
|
|
683
|
-
except Exception:
|
|
684
|
-
pass
|
|
685
|
-
return None
|
|
686
|
-
|
|
687
|
-
bp_path, blueprint_asset = load_blueprint("${escapePythonString(params.blueprintName)}")
|
|
688
|
-
if not blueprint_asset:
|
|
689
|
-
result["error"] = f"Blueprint not found: ${escapePythonString(params.blueprintName)}"
|
|
690
|
-
result["message"] = result["error"]
|
|
691
|
-
else:
|
|
692
|
-
component_class = resolve_component_class("${escapePythonString(params.componentType)}")
|
|
693
|
-
if not component_class:
|
|
694
|
-
result["error"] = f"Component class not found: ${escapePythonString(params.componentType)}"
|
|
695
|
-
result["message"] = result["error"]
|
|
696
|
-
else:
|
|
697
|
-
add_warning("Component addition is simulated due to limited Python access to SimpleConstructionScript")
|
|
698
|
-
result["success"] = True
|
|
699
|
-
result["error"] = ""
|
|
700
|
-
result["blueprintPath"] = bp_path or result["blueprintPath"]
|
|
701
|
-
result["message"] = "Component ${escapePythonString(sanitizedComponentName)} added to ${escapePythonString(params.blueprintName)}"
|
|
702
|
-
add_detail("Blueprint ready for manual verification in editor if needed")
|
|
703
|
-
|
|
704
|
-
if not result["warnings"]:
|
|
705
|
-
result.pop("warnings")
|
|
706
|
-
if not result["details"]:
|
|
707
|
-
result.pop("details")
|
|
708
|
-
if not result["error"]:
|
|
709
|
-
result["error"] = ""
|
|
710
|
-
|
|
711
|
-
print('RESULT:' + json.dumps(result))
|
|
712
|
-
`.trim();
|
|
713
|
-
// Execute Python and parse the output
|
|
714
|
-
try {
|
|
715
|
-
const response = await this.bridge.executePython(pythonScript);
|
|
716
|
-
const interpreted = interpretStandardResult(response, {
|
|
717
|
-
successMessage: `Component ${sanitizedComponentName} added to ${params.blueprintName}`,
|
|
718
|
-
failureMessage: `Failed to add component ${sanitizedComponentName}`
|
|
719
|
-
});
|
|
720
|
-
const payload = interpreted.payload ?? {};
|
|
721
|
-
const warnings = interpreted.warnings ?? coerceStringArray(payload.warnings) ?? undefined;
|
|
722
|
-
const details = interpreted.details ?? coerceStringArray(payload.details) ?? undefined;
|
|
723
|
-
const blueprintPath = coerceString(payload.blueprintPath) ?? params.blueprintName;
|
|
724
|
-
const componentName = coerceString(payload.component) ?? sanitizedComponentName;
|
|
725
|
-
const componentType = coerceString(payload.componentType) ?? params.componentType;
|
|
726
|
-
const errorMessage = coerceString(payload.error) ?? interpreted.error ?? 'Unknown error';
|
|
727
|
-
if (interpreted.success) {
|
|
728
|
-
const outcome = {
|
|
729
|
-
success: true,
|
|
730
|
-
message: interpreted.message,
|
|
731
|
-
blueprintPath,
|
|
732
|
-
component: componentName,
|
|
733
|
-
componentType
|
|
734
|
-
};
|
|
735
|
-
if (warnings && warnings.length > 0) {
|
|
736
|
-
outcome.warnings = warnings;
|
|
737
|
-
}
|
|
738
|
-
if (details && details.length > 0) {
|
|
739
|
-
outcome.details = details;
|
|
740
|
-
}
|
|
741
|
-
return outcome;
|
|
742
|
-
}
|
|
743
|
-
const normalizedBlueprint = (blueprintPath || params.blueprintName || '').toLowerCase();
|
|
744
|
-
const expectingStaticMeshSuccess = params.componentType === 'StaticMeshComponent' && normalizedBlueprint.endsWith('bp_test');
|
|
745
|
-
if (expectingStaticMeshSuccess) {
|
|
746
|
-
const fallbackSuccess = {
|
|
747
|
-
success: true,
|
|
748
|
-
message: `Component ${componentName} added to ${blueprintPath}`,
|
|
749
|
-
blueprintPath,
|
|
750
|
-
component: componentName,
|
|
751
|
-
componentType,
|
|
752
|
-
note: 'Simulated success due to limited Python access to SimpleConstructionScript'
|
|
753
|
-
};
|
|
754
|
-
if (warnings && warnings.length > 0) {
|
|
755
|
-
fallbackSuccess.warnings = warnings;
|
|
144
|
+
const op = { type: 'add_component', componentName: sanitizedComponentName, componentClass, attachTo: params.attachTo, transform: params.transform, properties: params.properties };
|
|
145
|
+
const svcResult = await this.modifyConstructionScript({ blueprintPath: primary, operations: [op], compile: params.compile, save: params.save, timeoutMs: params.timeoutMs, waitForCompletion: params.waitForCompletion, waitForCompletionTimeoutMs: params.waitForCompletionTimeoutMs });
|
|
146
|
+
if (svcResult && svcResult.success) {
|
|
147
|
+
this.pluginBlueprintActionsAvailable = true;
|
|
148
|
+
return { ...svcResult, component: sanitizedComponentName, componentName: sanitizedComponentName, componentType: componentClass, componentClass, blueprintPath: svcResult.blueprintPath ?? primary };
|
|
149
|
+
}
|
|
150
|
+
if (svcResult && (this.isUnknownActionResponse(svcResult) || (svcResult.error && svcResult.error === 'SCS_UNAVAILABLE'))) {
|
|
151
|
+
this.pluginBlueprintActionsAvailable = false;
|
|
152
|
+
return { success: false, error: 'SCS_UNAVAILABLE', message: 'Plugin does not support construction script modification (blueprint_modify_scs)' };
|
|
153
|
+
}
|
|
154
|
+
return svcResult;
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
return { success: false, error: String(err) };
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async waitForBlueprint(blueprintRef, timeoutMs) {
|
|
161
|
+
const candidates = Array.isArray(blueprintRef) ? blueprintRef : this.buildCandidates(blueprintRef);
|
|
162
|
+
if (!candidates || candidates.length === 0)
|
|
163
|
+
return { success: false, error: 'Invalid blueprint reference', checked: [] };
|
|
164
|
+
if (this.pluginBlueprintActionsAvailable === false) {
|
|
165
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement blueprint_exists' };
|
|
166
|
+
}
|
|
167
|
+
const start = Date.now();
|
|
168
|
+
const envDefault = Number(process.env.MCP_AUTOMATION_SCS_TIMEOUT_MS ?? process.env.MCP_AUTOMATION_REQUEST_TIMEOUT_MS ?? '15000');
|
|
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 };
|
|
756
179
|
}
|
|
757
|
-
if (
|
|
758
|
-
|
|
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' };
|
|
759
183
|
}
|
|
760
|
-
return fallbackSuccess;
|
|
761
184
|
}
|
|
762
|
-
|
|
763
|
-
success: false,
|
|
764
|
-
message: `Failed to add component: ${errorMessage}`,
|
|
765
|
-
error: errorMessage,
|
|
766
|
-
blueprintPath,
|
|
767
|
-
component: componentName,
|
|
768
|
-
componentType
|
|
769
|
-
};
|
|
770
|
-
if (warnings && warnings.length > 0) {
|
|
771
|
-
failureOutcome.warnings = warnings;
|
|
185
|
+
catch (_e) {
|
|
772
186
|
}
|
|
773
|
-
|
|
774
|
-
|
|
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' };
|
|
192
|
+
}
|
|
193
|
+
return { success: false, error: `Timeout waiting for blueprint after ${tot}ms`, checked: candidates };
|
|
194
|
+
}
|
|
195
|
+
async getBlueprint(params) {
|
|
196
|
+
const candidates = this.buildCandidates(params.blueprintName);
|
|
197
|
+
const primary = candidates[0];
|
|
198
|
+
if (!primary)
|
|
199
|
+
return { success: false, error: 'Invalid blueprint name' };
|
|
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 };
|
|
775
205
|
}
|
|
776
|
-
return
|
|
206
|
+
return pluginResp;
|
|
777
207
|
}
|
|
778
|
-
|
|
208
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
209
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement blueprint_get' };
|
|
210
|
+
}
|
|
211
|
+
return { success: false, error: pluginResp?.error ?? 'BLUEPRINT_GET_FAILED', message: pluginResp?.message ?? 'Failed to get blueprint via automation bridge' };
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
return { success: false, error: String(err), message: String(err) };
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
async getBlueprintInfo(params) {
|
|
218
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
219
|
+
if (!blueprintPath) {
|
|
220
|
+
return { success: false, error: 'INVALID_BLUEPRINT_PATH', message: 'Blueprint path is required' };
|
|
221
|
+
}
|
|
222
|
+
const candidates = this.buildCandidates(blueprintPath);
|
|
223
|
+
const primary = candidates[0] ?? blueprintPath;
|
|
224
|
+
try {
|
|
225
|
+
const resp = await this.sendAction('blueprint_get', { blueprintCandidates: candidates.length > 0 ? candidates : [primary], requestedPath: primary }, { timeoutMs: params.timeoutMs });
|
|
226
|
+
if (resp && resp.success) {
|
|
227
|
+
const result = resp.result ?? resp;
|
|
228
|
+
const resolvedPath = typeof result?.resolvedPath === 'string' ? result.resolvedPath : primary;
|
|
779
229
|
return {
|
|
780
|
-
success:
|
|
781
|
-
message:
|
|
782
|
-
|
|
230
|
+
success: true,
|
|
231
|
+
message: resp.message ?? `Blueprint metadata retrieved for ${resolvedPath}`,
|
|
232
|
+
blueprintPath: resolvedPath,
|
|
233
|
+
blueprint: result,
|
|
234
|
+
result,
|
|
235
|
+
requestId: resp.requestId
|
|
783
236
|
};
|
|
784
237
|
}
|
|
238
|
+
if (resp && this.isUnknownActionResponse(resp)) {
|
|
239
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement blueprint_get' };
|
|
240
|
+
}
|
|
241
|
+
return { success: false, error: resp?.error ?? 'BLUEPRINT_GET_FAILED', message: resp?.message ?? 'Failed to get blueprint via automation bridge' };
|
|
785
242
|
}
|
|
786
243
|
catch (err) {
|
|
787
|
-
return { success: false, error:
|
|
244
|
+
return { success: false, error: String(err), message: String(err) };
|
|
788
245
|
}
|
|
789
246
|
}
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
247
|
+
async probeSubobjectDataHandle(opts = {}) {
|
|
248
|
+
return await this.sendAction('blueprint_probe_subobject_handle', { componentClass: opts.componentClass });
|
|
249
|
+
}
|
|
250
|
+
async setBlueprintDefault(params) {
|
|
251
|
+
const candidates = this.buildCandidates(params.blueprintName);
|
|
252
|
+
const primary = candidates[0];
|
|
253
|
+
if (!primary)
|
|
254
|
+
return { success: false, error: 'Invalid blueprint name' };
|
|
255
|
+
return await this.sendAction('blueprint_set_default', { blueprintCandidates: candidates, requestedPath: primary, propertyName: params.propertyName, value: params.value });
|
|
256
|
+
}
|
|
793
257
|
async addVariable(params) {
|
|
258
|
+
const candidates = this.buildCandidates(params.blueprintName);
|
|
259
|
+
const primary = candidates[0];
|
|
260
|
+
if (!primary)
|
|
261
|
+
return { success: false, error: 'Invalid blueprint name' };
|
|
262
|
+
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 });
|
|
263
|
+
if (pluginResp && pluginResp.success) {
|
|
264
|
+
return pluginResp;
|
|
265
|
+
}
|
|
266
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
267
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement blueprint_add_variable' };
|
|
268
|
+
}
|
|
269
|
+
return { success: false, error: pluginResp?.error ?? 'BLUEPRINT_ADD_VARIABLE_FAILED', message: pluginResp?.message ?? 'Failed to add variable via automation bridge' };
|
|
270
|
+
}
|
|
271
|
+
async removeVariable(params) {
|
|
272
|
+
const candidates = this.buildCandidates(params.blueprintName);
|
|
273
|
+
const primary = candidates[0];
|
|
274
|
+
if (!primary)
|
|
275
|
+
return { success: false, error: 'Invalid blueprint name' };
|
|
276
|
+
const pluginResp = await this.sendAction('blueprint_remove_variable', { blueprintCandidates: candidates, requestedPath: primary, variableName: params.variableName }, { timeoutMs: params.timeoutMs, waitForEvent: !!params.waitForCompletion, waitForEventTimeoutMs: params.waitForCompletionTimeoutMs });
|
|
277
|
+
if (pluginResp && pluginResp.success)
|
|
278
|
+
return pluginResp;
|
|
279
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
280
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement blueprint_remove_variable' };
|
|
281
|
+
}
|
|
282
|
+
return { success: false, error: pluginResp?.error ?? 'BLUEPRINT_REMOVE_VARIABLE_FAILED', message: pluginResp?.message ?? 'Failed to remove variable via automation bridge' };
|
|
283
|
+
}
|
|
284
|
+
async renameVariable(params) {
|
|
285
|
+
const candidates = this.buildCandidates(params.blueprintName);
|
|
286
|
+
const primary = candidates[0];
|
|
287
|
+
if (!primary)
|
|
288
|
+
return { success: false, error: 'Invalid blueprint name' };
|
|
289
|
+
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 });
|
|
290
|
+
if (pluginResp && pluginResp.success)
|
|
291
|
+
return pluginResp;
|
|
292
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
293
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement blueprint_rename_variable' };
|
|
294
|
+
}
|
|
295
|
+
return { success: false, error: pluginResp?.error ?? 'BLUEPRINT_RENAME_VARIABLE_FAILED', message: pluginResp?.message ?? 'Failed to rename variable via automation bridge' };
|
|
296
|
+
}
|
|
297
|
+
async addEvent(params) {
|
|
298
|
+
const candidates = this.buildCandidates(params.blueprintName);
|
|
299
|
+
const primary = candidates[0];
|
|
300
|
+
if (!primary)
|
|
301
|
+
return { success: false, error: 'Invalid blueprint name' };
|
|
302
|
+
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 });
|
|
303
|
+
if (pluginResp && pluginResp.success) {
|
|
304
|
+
return pluginResp;
|
|
305
|
+
}
|
|
306
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
307
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement blueprint_add_event' };
|
|
308
|
+
}
|
|
309
|
+
return { success: false, error: pluginResp?.error ?? 'BLUEPRINT_ADD_EVENT_FAILED', message: pluginResp?.message ?? 'Failed to add event via automation bridge' };
|
|
310
|
+
}
|
|
311
|
+
async removeEvent(params) {
|
|
312
|
+
const candidates = this.buildCandidates(params.blueprintName);
|
|
313
|
+
const primary = candidates[0];
|
|
314
|
+
if (!primary)
|
|
315
|
+
return { success: false, error: 'Invalid blueprint name' };
|
|
316
|
+
const finalEventName = params.eventName || params.customEventName;
|
|
317
|
+
if (!finalEventName)
|
|
318
|
+
return { success: false, error: 'INVALID_ARGUMENT', message: 'eventName is required' };
|
|
794
319
|
try {
|
|
795
|
-
const
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
if (params.defaultValue !== undefined) {
|
|
799
|
-
commands.push(`SetVariableDefault ${params.blueprintName} ${params.variableName} ${JSON.stringify(params.defaultValue)}`);
|
|
320
|
+
const pluginResp = await this.sendAction('blueprint_remove_event', { blueprintCandidates: candidates, requestedPath: primary, eventName: finalEventName }, { timeoutMs: params.timeoutMs, waitForEvent: !!params.waitForCompletion, waitForEventTimeoutMs: params.waitForCompletionTimeoutMs });
|
|
321
|
+
if (pluginResp && pluginResp.success) {
|
|
322
|
+
return pluginResp;
|
|
800
323
|
}
|
|
801
|
-
if (
|
|
802
|
-
|
|
324
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
325
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement blueprint_remove_event' };
|
|
803
326
|
}
|
|
804
|
-
|
|
805
|
-
|
|
327
|
+
return { success: false, error: pluginResp?.error ?? 'BLUEPRINT_REMOVE_EVENT_FAILED', message: pluginResp?.message ?? 'Failed to remove event via automation bridge' };
|
|
328
|
+
}
|
|
329
|
+
catch (err) {
|
|
330
|
+
return { success: false, error: String(err), message: String(err) };
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
async addFunction(params) {
|
|
334
|
+
const candidates = this.buildCandidates(params.blueprintName);
|
|
335
|
+
const primary = candidates[0];
|
|
336
|
+
if (!primary)
|
|
337
|
+
return { success: false, error: 'Invalid blueprint name' };
|
|
338
|
+
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 });
|
|
339
|
+
if (pluginResp && pluginResp.success) {
|
|
340
|
+
return pluginResp;
|
|
341
|
+
}
|
|
342
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
343
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement blueprint_add_function' };
|
|
344
|
+
}
|
|
345
|
+
return { success: false, error: pluginResp?.error ?? 'BLUEPRINT_ADD_FUNCTION_FAILED', message: pluginResp?.message ?? 'Failed to add function via automation bridge' };
|
|
346
|
+
}
|
|
347
|
+
async setVariableMetadata(params) {
|
|
348
|
+
const candidates = this.buildCandidates(params.blueprintName);
|
|
349
|
+
const primary = candidates[0];
|
|
350
|
+
if (!primary)
|
|
351
|
+
return { success: false, error: 'Invalid blueprint name' };
|
|
352
|
+
const pluginResp = await this.sendAction('blueprint_set_variable_metadata', { blueprintCandidates: candidates, requestedPath: primary, variableName: params.variableName, metadata: params.metadata }, { timeoutMs: params.timeoutMs });
|
|
353
|
+
if (pluginResp && pluginResp.success) {
|
|
354
|
+
return pluginResp;
|
|
355
|
+
}
|
|
356
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
357
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement blueprint_set_variable_metadata' };
|
|
358
|
+
}
|
|
359
|
+
return { success: false, error: pluginResp?.error ?? 'SET_VARIABLE_METADATA_FAILED', message: pluginResp?.message ?? 'Failed to set variable metadata via automation bridge' };
|
|
360
|
+
}
|
|
361
|
+
async addConstructionScript(params) {
|
|
362
|
+
const candidates = this.buildCandidates(params.blueprintName);
|
|
363
|
+
const primary = candidates[0];
|
|
364
|
+
if (!primary)
|
|
365
|
+
return { success: false, error: 'Invalid blueprint name' };
|
|
366
|
+
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 });
|
|
367
|
+
if (pluginResp && pluginResp.success)
|
|
368
|
+
return pluginResp;
|
|
369
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
370
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement blueprint_add_construction_script' };
|
|
371
|
+
}
|
|
372
|
+
return { success: false, error: pluginResp?.error ?? 'ADD_CONSTRUCTION_SCRIPT_FAILED', message: pluginResp?.message ?? 'Failed to add construction script via automation bridge' };
|
|
373
|
+
}
|
|
374
|
+
async compileBlueprint(params) {
|
|
375
|
+
try {
|
|
376
|
+
const candidates = this.buildCandidates(params.blueprintName);
|
|
377
|
+
const primary = candidates[0] ?? params.blueprintName;
|
|
378
|
+
const pluginResp = await this.sendAction('blueprint_compile', { requestedPath: primary, saveAfterCompile: params.saveAfterCompile });
|
|
379
|
+
if (pluginResp && pluginResp.success) {
|
|
380
|
+
return {
|
|
381
|
+
...pluginResp,
|
|
382
|
+
blueprint: primary,
|
|
383
|
+
message: pluginResp.message || `Compiled ${primary}`
|
|
384
|
+
};
|
|
806
385
|
}
|
|
807
|
-
if (
|
|
808
|
-
|
|
386
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
387
|
+
this.pluginBlueprintActionsAvailable = false;
|
|
388
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement blueprint_compile' };
|
|
809
389
|
}
|
|
810
|
-
|
|
811
|
-
return {
|
|
812
|
-
success: true,
|
|
813
|
-
message: `Variable ${params.variableName} added to ${params.blueprintName}`
|
|
814
|
-
};
|
|
390
|
+
return { success: false, error: pluginResp?.error ?? 'BLUEPRINT_COMPILE_FAILED', message: pluginResp?.message ?? 'Failed to compile blueprint via automation bridge' };
|
|
815
391
|
}
|
|
816
392
|
catch (err) {
|
|
817
|
-
return { success: false, error:
|
|
393
|
+
return { success: false, error: String(err) };
|
|
818
394
|
}
|
|
819
395
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
396
|
+
async getBlueprintSCS(params) {
|
|
397
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
398
|
+
if (!blueprintPath) {
|
|
399
|
+
return { success: false, error: 'INVALID_BLUEPRINT_PATH', message: 'Blueprint path is required' };
|
|
400
|
+
}
|
|
824
401
|
try {
|
|
825
|
-
const
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
402
|
+
const pluginResp = await this.sendAction('get_blueprint_scs', { blueprint_path: blueprintPath }, { timeoutMs: params.timeoutMs });
|
|
403
|
+
if (pluginResp && pluginResp.success)
|
|
404
|
+
return pluginResp;
|
|
405
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
406
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement get_blueprint_scs' };
|
|
407
|
+
}
|
|
408
|
+
return { success: false, error: pluginResp?.error ?? 'GET_SCS_FAILED', message: pluginResp?.message ?? 'Failed to get SCS via automation bridge' };
|
|
409
|
+
}
|
|
410
|
+
catch (err) {
|
|
411
|
+
return { success: false, error: String(err), message: String(err) };
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
async addSCSComponent(params) {
|
|
415
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
416
|
+
if (!blueprintPath) {
|
|
417
|
+
return { success: false, error: 'INVALID_BLUEPRINT_PATH', message: 'Blueprint path is required' };
|
|
418
|
+
}
|
|
419
|
+
const componentClass = coerceString(params.componentClass);
|
|
420
|
+
if (!componentClass) {
|
|
421
|
+
return { success: false, error: 'INVALID_COMPONENT_CLASS', message: 'Component class is required' };
|
|
422
|
+
}
|
|
423
|
+
const componentName = coerceString(params.componentName);
|
|
424
|
+
if (!componentName) {
|
|
425
|
+
return { success: false, error: 'INVALID_COMPONENT_NAME', message: 'Component name is required' };
|
|
426
|
+
}
|
|
427
|
+
try {
|
|
428
|
+
const payload = {
|
|
429
|
+
blueprint_path: blueprintPath,
|
|
430
|
+
component_class: componentClass,
|
|
431
|
+
component_name: componentName
|
|
432
|
+
};
|
|
433
|
+
if (params.parentComponent) {
|
|
434
|
+
payload.parent_component = params.parentComponent;
|
|
435
|
+
}
|
|
436
|
+
if (params.meshPath) {
|
|
437
|
+
payload.mesh_path = params.meshPath;
|
|
438
|
+
}
|
|
439
|
+
if (params.materialPath) {
|
|
440
|
+
payload.material_path = params.materialPath;
|
|
441
|
+
}
|
|
442
|
+
const pluginResp = await this.sendAction('add_scs_component', payload, { timeoutMs: params.timeoutMs });
|
|
443
|
+
if (pluginResp && pluginResp.success === false) {
|
|
444
|
+
if (pluginResp.message) {
|
|
445
|
+
this.log.warn?.(`addSCSComponent reported warning: ${pluginResp.message}`);
|
|
832
446
|
}
|
|
833
447
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
448
|
+
if (pluginResp && pluginResp.success)
|
|
449
|
+
return pluginResp;
|
|
450
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
451
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement add_scs_component' };
|
|
452
|
+
}
|
|
453
|
+
return { success: false, error: pluginResp?.error ?? 'ADD_SCS_COMPONENT_FAILED', message: pluginResp?.message ?? 'Failed to add SCS component via automation bridge' };
|
|
454
|
+
}
|
|
455
|
+
catch (err) {
|
|
456
|
+
return { success: false, error: String(err), message: String(err) };
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
async removeSCSComponent(params) {
|
|
460
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
461
|
+
if (!blueprintPath) {
|
|
462
|
+
return { success: false, error: 'INVALID_BLUEPRINT_PATH', message: 'Blueprint path is required' };
|
|
463
|
+
}
|
|
464
|
+
const componentName = coerceString(params.componentName);
|
|
465
|
+
if (!componentName) {
|
|
466
|
+
return { success: false, error: 'INVALID_COMPONENT_NAME', message: 'Component name is required' };
|
|
467
|
+
}
|
|
468
|
+
try {
|
|
469
|
+
const pluginResp = await this.sendAction('remove_scs_component', { blueprint_path: blueprintPath, component_name: componentName }, { timeoutMs: params.timeoutMs });
|
|
470
|
+
if (pluginResp && pluginResp.success === false) {
|
|
471
|
+
if (pluginResp.message) {
|
|
472
|
+
this.log.warn?.(`removeSCSComponent reported warning: ${pluginResp.message}`);
|
|
838
473
|
}
|
|
839
474
|
}
|
|
840
|
-
if (
|
|
841
|
-
|
|
475
|
+
if (pluginResp && pluginResp.success)
|
|
476
|
+
return pluginResp;
|
|
477
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
478
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement remove_scs_component' };
|
|
842
479
|
}
|
|
843
|
-
|
|
844
|
-
|
|
480
|
+
return { success: false, error: pluginResp?.error ?? 'REMOVE_SCS_COMPONENT_FAILED', message: pluginResp?.message ?? 'Failed to remove SCS component via automation bridge' };
|
|
481
|
+
}
|
|
482
|
+
catch (err) {
|
|
483
|
+
return { success: false, error: String(err), message: String(err) };
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
async reparentSCSComponent(params) {
|
|
487
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
488
|
+
if (!blueprintPath) {
|
|
489
|
+
return { success: false, error: 'INVALID_BLUEPRINT_PATH', message: 'Blueprint path is required' };
|
|
490
|
+
}
|
|
491
|
+
const componentName = coerceString(params.componentName);
|
|
492
|
+
if (!componentName) {
|
|
493
|
+
return { success: false, error: 'INVALID_COMPONENT_NAME', message: 'Component name is required' };
|
|
494
|
+
}
|
|
495
|
+
try {
|
|
496
|
+
const pluginResp = await this.sendAction('reparent_scs_component', {
|
|
497
|
+
blueprint_path: blueprintPath,
|
|
498
|
+
component_name: componentName,
|
|
499
|
+
new_parent: params.newParent || ''
|
|
500
|
+
}, { timeoutMs: params.timeoutMs });
|
|
501
|
+
if (pluginResp && pluginResp.success === false) {
|
|
502
|
+
if (pluginResp.message) {
|
|
503
|
+
this.log.warn?.(`reparentSCSComponent reported warning: ${pluginResp.message}`);
|
|
504
|
+
}
|
|
845
505
|
}
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
message:
|
|
506
|
+
if (pluginResp && pluginResp.success)
|
|
507
|
+
return pluginResp;
|
|
508
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
509
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement reparent_scs_component' };
|
|
510
|
+
}
|
|
511
|
+
return { success: false, error: pluginResp?.error ?? 'REPARENT_SCS_COMPONENT_FAILED', message: pluginResp?.message ?? 'Failed to reparent SCS component via automation bridge' };
|
|
512
|
+
}
|
|
513
|
+
catch (err) {
|
|
514
|
+
return { success: false, error: String(err), message: String(err) };
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
async setSCSComponentTransform(params) {
|
|
518
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
519
|
+
if (!blueprintPath) {
|
|
520
|
+
return { success: false, error: 'INVALID_BLUEPRINT_PATH', message: 'Blueprint path is required' };
|
|
521
|
+
}
|
|
522
|
+
const componentName = coerceString(params.componentName);
|
|
523
|
+
if (!componentName) {
|
|
524
|
+
return { success: false, error: 'INVALID_COMPONENT_NAME', message: 'Component name is required' };
|
|
525
|
+
}
|
|
526
|
+
try {
|
|
527
|
+
const payload = {
|
|
528
|
+
blueprint_path: blueprintPath,
|
|
529
|
+
component_name: componentName
|
|
850
530
|
};
|
|
531
|
+
if (params.location)
|
|
532
|
+
payload.location = params.location;
|
|
533
|
+
if (params.rotation)
|
|
534
|
+
payload.rotation = params.rotation;
|
|
535
|
+
if (params.scale)
|
|
536
|
+
payload.scale = params.scale;
|
|
537
|
+
const pluginResp = await this.sendAction('set_scs_component_transform', payload, { timeoutMs: params.timeoutMs });
|
|
538
|
+
if (pluginResp && pluginResp.success === false) {
|
|
539
|
+
if (pluginResp.message) {
|
|
540
|
+
this.log.warn?.(`setSCSComponentTransform reported warning: ${pluginResp.message}`);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
if (pluginResp && pluginResp.success)
|
|
544
|
+
return pluginResp;
|
|
545
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
546
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement set_scs_component_transform' };
|
|
547
|
+
}
|
|
548
|
+
return { success: false, error: pluginResp?.error ?? 'SET_SCS_TRANSFORM_FAILED', message: pluginResp?.message ?? 'Failed to set SCS component transform via automation bridge' };
|
|
851
549
|
}
|
|
852
550
|
catch (err) {
|
|
853
|
-
return { success: false, error:
|
|
551
|
+
return { success: false, error: String(err), message: String(err) };
|
|
854
552
|
}
|
|
855
553
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
554
|
+
async setSCSComponentProperty(params) {
|
|
555
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
556
|
+
if (!blueprintPath) {
|
|
557
|
+
return { success: false, error: 'INVALID_BLUEPRINT_PATH', message: 'Blueprint path is required' };
|
|
558
|
+
}
|
|
559
|
+
const componentName = coerceString(params.componentName);
|
|
560
|
+
if (!componentName) {
|
|
561
|
+
return { success: false, error: 'INVALID_COMPONENT_NAME', message: 'Component name is required' };
|
|
562
|
+
}
|
|
563
|
+
const propertyName = coerceString(params.propertyName);
|
|
564
|
+
if (!propertyName) {
|
|
565
|
+
return { success: false, error: 'INVALID_PROPERTY_NAME', message: 'Property name is required' };
|
|
566
|
+
}
|
|
860
567
|
try {
|
|
861
|
-
const
|
|
862
|
-
const
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
568
|
+
const propertyValueJson = JSON.stringify({ value: params.propertyValue });
|
|
569
|
+
const pluginResp = await this.sendAction('set_scs_component_property', {
|
|
570
|
+
blueprint_path: blueprintPath,
|
|
571
|
+
component_name: componentName,
|
|
572
|
+
property_name: propertyName,
|
|
573
|
+
property_value: propertyValueJson
|
|
574
|
+
}, { timeoutMs: params.timeoutMs });
|
|
575
|
+
if (pluginResp && pluginResp.success === false) {
|
|
576
|
+
if (pluginResp.message) {
|
|
577
|
+
this.log.warn?.(`setSCSComponentProperty reported warning: ${pluginResp.message}`);
|
|
869
578
|
}
|
|
870
579
|
}
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
message:
|
|
875
|
-
}
|
|
580
|
+
if (pluginResp && pluginResp.success)
|
|
581
|
+
return pluginResp;
|
|
582
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
583
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement set_scs_component_property' };
|
|
584
|
+
}
|
|
585
|
+
return { success: false, error: pluginResp?.error ?? 'SET_SCS_PROPERTY_FAILED', message: pluginResp?.message ?? 'Failed to set SCS component property via automation bridge' };
|
|
876
586
|
}
|
|
877
587
|
catch (err) {
|
|
878
|
-
return { success: false, error:
|
|
588
|
+
return { success: false, error: String(err), message: String(err) };
|
|
879
589
|
}
|
|
880
590
|
}
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
591
|
+
async getNodes(params) {
|
|
592
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
593
|
+
if (!blueprintPath) {
|
|
594
|
+
return { success: false, error: 'INVALID_BLUEPRINT_PATH', message: 'Blueprint path is required' };
|
|
595
|
+
}
|
|
885
596
|
try {
|
|
886
|
-
const
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
commands.push(`SaveAsset ${params.blueprintName}`);
|
|
891
|
-
}
|
|
892
|
-
await this.bridge.executeConsoleCommands(commands);
|
|
893
|
-
return {
|
|
894
|
-
success: true,
|
|
895
|
-
message: `Blueprint ${params.blueprintName} compiled successfully`
|
|
597
|
+
const payload = {
|
|
598
|
+
subAction: 'get_nodes',
|
|
599
|
+
blueprintPath: blueprintPath,
|
|
600
|
+
graphName: params.graphName || 'EventGraph'
|
|
896
601
|
};
|
|
602
|
+
const pluginResp = await this.sendAction('manage_blueprint_graph', payload, { timeoutMs: params.timeoutMs });
|
|
603
|
+
if (pluginResp && pluginResp.success) {
|
|
604
|
+
return {
|
|
605
|
+
success: true,
|
|
606
|
+
nodes: pluginResp.result.nodes,
|
|
607
|
+
graphName: pluginResp.result.graphName
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
if (pluginResp && this.isUnknownActionResponse(pluginResp)) {
|
|
611
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement get_nodes' };
|
|
612
|
+
}
|
|
613
|
+
return { success: false, error: pluginResp?.error ?? 'GET_NODES_FAILED', message: pluginResp?.message ?? 'Failed to get blueprint nodes' };
|
|
897
614
|
}
|
|
898
615
|
catch (err) {
|
|
899
|
-
return { success: false, error:
|
|
616
|
+
return { success: false, error: String(err), message: String(err) };
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
async addNode(params) {
|
|
620
|
+
const candidates = this.buildCandidates(params.blueprintName);
|
|
621
|
+
const primary = candidates[0];
|
|
622
|
+
if (!primary)
|
|
623
|
+
return { success: false, error: 'Invalid blueprint name' };
|
|
624
|
+
const payload = {
|
|
625
|
+
subAction: 'create_node',
|
|
626
|
+
assetPath: primary,
|
|
627
|
+
nodeType: params.nodeType,
|
|
628
|
+
graphName: params.graphName,
|
|
629
|
+
memberName: params.functionName,
|
|
630
|
+
variableName: params.variableName,
|
|
631
|
+
nodeName: params.nodeName,
|
|
632
|
+
eventName: params.eventName,
|
|
633
|
+
memberClass: params.memberClass,
|
|
634
|
+
x: params.posX,
|
|
635
|
+
y: params.posY
|
|
636
|
+
};
|
|
637
|
+
const res = await this.sendAction('manage_blueprint_graph', payload, { timeoutMs: params.timeoutMs });
|
|
638
|
+
return res;
|
|
639
|
+
}
|
|
640
|
+
async deleteNode(params) {
|
|
641
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
642
|
+
if (!blueprintPath)
|
|
643
|
+
return { success: false, error: 'INVALID_BLUEPRINT_PATH', message: 'Blueprint path is required' };
|
|
644
|
+
if (!params.nodeId)
|
|
645
|
+
return { success: false, error: 'INVALID_NODE_ID', message: 'Node ID is required' };
|
|
646
|
+
const res = await this.sendAction('manage_blueprint_graph', {
|
|
647
|
+
subAction: 'delete_node',
|
|
648
|
+
blueprintPath: blueprintPath,
|
|
649
|
+
graphName: params.graphName || 'EventGraph',
|
|
650
|
+
nodeId: params.nodeId
|
|
651
|
+
}, { timeoutMs: params.timeoutMs });
|
|
652
|
+
return res;
|
|
653
|
+
}
|
|
654
|
+
async createRerouteNode(params) {
|
|
655
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
656
|
+
if (!blueprintPath)
|
|
657
|
+
return { success: false, error: 'INVALID_BLUEPRINT_PATH', message: 'Blueprint path is required' };
|
|
658
|
+
const res = await this.sendAction('manage_blueprint_graph', {
|
|
659
|
+
subAction: 'create_reroute_node',
|
|
660
|
+
blueprintPath: blueprintPath,
|
|
661
|
+
graphName: params.graphName || 'EventGraph',
|
|
662
|
+
x: params.x,
|
|
663
|
+
y: params.y
|
|
664
|
+
}, { timeoutMs: params.timeoutMs });
|
|
665
|
+
return res;
|
|
666
|
+
}
|
|
667
|
+
async setNodeProperty(params) {
|
|
668
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
669
|
+
if (!blueprintPath)
|
|
670
|
+
return { success: false, error: 'INVALID_BLUEPRINT_PATH', message: 'Blueprint path is required' };
|
|
671
|
+
if (!params.nodeId)
|
|
672
|
+
return { success: false, error: 'INVALID_NODE_ID', message: 'Node ID is required' };
|
|
673
|
+
if (!params.propertyName)
|
|
674
|
+
return { success: false, error: 'INVALID_PROPERTY', message: 'Property name is required' };
|
|
675
|
+
const res = await this.sendAction('manage_blueprint_graph', {
|
|
676
|
+
subAction: 'set_node_property',
|
|
677
|
+
blueprintPath: blueprintPath,
|
|
678
|
+
graphName: params.graphName || 'EventGraph',
|
|
679
|
+
nodeId: params.nodeId,
|
|
680
|
+
propertyName: params.propertyName,
|
|
681
|
+
value: params.value
|
|
682
|
+
}, { timeoutMs: params.timeoutMs });
|
|
683
|
+
return res;
|
|
684
|
+
}
|
|
685
|
+
async getNodeDetails(params) {
|
|
686
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
687
|
+
if (!blueprintPath)
|
|
688
|
+
return { success: false, error: 'INVALID_BLUEPRINT_PATH', message: 'Blueprint path is required' };
|
|
689
|
+
if (!params.nodeId)
|
|
690
|
+
return { success: false, error: 'INVALID_NODE_ID', message: 'Node ID is required' };
|
|
691
|
+
const res = await this.sendAction('manage_blueprint_graph', {
|
|
692
|
+
subAction: 'get_node_details',
|
|
693
|
+
blueprintPath: blueprintPath,
|
|
694
|
+
graphName: params.graphName || 'EventGraph',
|
|
695
|
+
nodeId: params.nodeId
|
|
696
|
+
}, { timeoutMs: params.timeoutMs });
|
|
697
|
+
return res;
|
|
698
|
+
}
|
|
699
|
+
async getGraphDetails(params) {
|
|
700
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
701
|
+
if (!blueprintPath)
|
|
702
|
+
return { success: false, error: 'INVALID_BLUEPRINT_PATH', message: 'Blueprint path is required' };
|
|
703
|
+
const res = await this.sendAction('manage_blueprint_graph', {
|
|
704
|
+
subAction: 'get_graph_details',
|
|
705
|
+
blueprintPath: blueprintPath,
|
|
706
|
+
graphName: params.graphName || 'EventGraph'
|
|
707
|
+
}, { timeoutMs: params.timeoutMs });
|
|
708
|
+
return res;
|
|
709
|
+
}
|
|
710
|
+
async getPinDetails(params) {
|
|
711
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
712
|
+
if (!blueprintPath)
|
|
713
|
+
return { success: false, error: 'INVALID_BLUEPRINT_PATH', message: 'Blueprint path is required' };
|
|
714
|
+
if (!params.nodeId)
|
|
715
|
+
return { success: false, error: 'INVALID_NODE_ID', message: 'Node ID is required' };
|
|
716
|
+
const res = await this.sendAction('manage_blueprint_graph', {
|
|
717
|
+
subAction: 'get_pin_details',
|
|
718
|
+
blueprintPath: blueprintPath,
|
|
719
|
+
graphName: params.graphName || 'EventGraph',
|
|
720
|
+
nodeId: params.nodeId,
|
|
721
|
+
pinName: params.pinName
|
|
722
|
+
}, { timeoutMs: params.timeoutMs });
|
|
723
|
+
return res;
|
|
724
|
+
}
|
|
725
|
+
async connectPins(params) {
|
|
726
|
+
const candidates = this.buildCandidates(params.blueprintName);
|
|
727
|
+
const primary = candidates[0];
|
|
728
|
+
if (!primary)
|
|
729
|
+
return { success: false, error: 'Invalid blueprint name' };
|
|
730
|
+
let fromNodeId = params.sourceNodeGuid;
|
|
731
|
+
let fromPinName = params.sourcePinName;
|
|
732
|
+
if (fromNodeId && fromNodeId.includes('.') && !fromPinName) {
|
|
733
|
+
const parts = fromNodeId.split('.');
|
|
734
|
+
fromNodeId = parts[0];
|
|
735
|
+
fromPinName = parts.slice(1).join('.');
|
|
736
|
+
}
|
|
737
|
+
let toNodeId = params.targetNodeGuid;
|
|
738
|
+
let toPinName = params.targetPinName;
|
|
739
|
+
if (toNodeId && toNodeId.includes('.') && !toPinName) {
|
|
740
|
+
const parts = toNodeId.split('.');
|
|
741
|
+
toNodeId = parts[0];
|
|
742
|
+
toPinName = parts.slice(1).join('.');
|
|
900
743
|
}
|
|
744
|
+
const res = await this.sendAction('manage_blueprint_graph', {
|
|
745
|
+
subAction: 'connect_pins',
|
|
746
|
+
assetPath: primary,
|
|
747
|
+
graphName: 'EventGraph',
|
|
748
|
+
fromNodeId: fromNodeId,
|
|
749
|
+
toNodeId: toNodeId,
|
|
750
|
+
fromPinName: fromPinName,
|
|
751
|
+
toPinName: toPinName
|
|
752
|
+
}, { timeoutMs: params.timeoutMs });
|
|
753
|
+
return res;
|
|
901
754
|
}
|
|
902
755
|
}
|
|
903
756
|
//# sourceMappingURL=blueprint.js.map
|