unreal-engine-mcp-server 0.4.6 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +26 -0
- package/.env.production +38 -7
- package/.eslintrc.json +0 -54
- package/.eslintrc.override.json +8 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +94 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +56 -0
- package/.github/copilot-instructions.md +478 -45
- package/.github/dependabot.yml +19 -0
- package/.github/labeler.yml +24 -0
- package/.github/labels.yml +70 -0
- package/.github/pull_request_template.md +42 -0
- package/.github/release-drafter.yml +148 -0
- package/.github/workflows/auto-merge.yml +38 -0
- package/.github/workflows/ci.yml +38 -0
- package/.github/workflows/dependency-review.yml +17 -0
- package/.github/workflows/gemini-issue-triage.yml +172 -0
- package/.github/workflows/greetings.yml +23 -0
- package/.github/workflows/labeler.yml +16 -0
- package/.github/workflows/links.yml +80 -0
- package/.github/workflows/pr-size-labeler.yml +137 -0
- package/.github/workflows/publish-mcp.yml +12 -7
- package/.github/workflows/release-drafter.yml +23 -0
- package/.github/workflows/release.yml +112 -0
- package/.github/workflows/semantic-pull-request.yml +35 -0
- package/.github/workflows/smoke-test.yml +36 -0
- package/.github/workflows/stale.yml +28 -0
- package/CHANGELOG.md +269 -22
- package/CONTRIBUTING.md +140 -0
- package/README.md +166 -72
- package/claude_desktop_config_example.json +7 -6
- package/dist/automation/bridge.d.ts +50 -0
- package/dist/automation/bridge.js +452 -0
- package/dist/automation/connection-manager.d.ts +23 -0
- package/dist/automation/connection-manager.js +107 -0
- package/dist/automation/handshake.d.ts +11 -0
- package/dist/automation/handshake.js +89 -0
- package/dist/automation/index.d.ts +3 -0
- package/dist/automation/index.js +3 -0
- package/dist/automation/message-handler.d.ts +12 -0
- package/dist/automation/message-handler.js +149 -0
- package/dist/automation/request-tracker.d.ts +25 -0
- package/dist/automation/request-tracker.js +98 -0
- package/dist/automation/types.d.ts +130 -0
- package/dist/automation/types.js +2 -0
- package/dist/cli.js +32 -5
- package/dist/config.d.ts +27 -0
- package/dist/config.js +60 -0
- package/dist/constants.d.ts +12 -0
- package/dist/constants.js +12 -0
- package/dist/graphql/resolvers.d.ts +268 -0
- package/dist/graphql/resolvers.js +743 -0
- package/dist/graphql/schema.d.ts +5 -0
- package/dist/graphql/schema.js +437 -0
- package/dist/graphql/server.d.ts +26 -0
- package/dist/graphql/server.js +115 -0
- package/dist/graphql/types.d.ts +7 -0
- package/dist/graphql/types.js +2 -0
- package/dist/handlers/resource-handlers.d.ts +20 -0
- package/dist/handlers/resource-handlers.js +180 -0
- package/dist/index.d.ts +31 -18
- package/dist/index.js +119 -604
- package/dist/prompts/index.js +4 -4
- package/dist/resources/actors.d.ts +17 -12
- package/dist/resources/actors.js +56 -76
- package/dist/resources/assets.d.ts +6 -14
- package/dist/resources/assets.js +115 -147
- package/dist/resources/levels.d.ts +13 -13
- package/dist/resources/levels.js +25 -34
- package/dist/server/resource-registry.d.ts +20 -0
- package/dist/server/resource-registry.js +37 -0
- package/dist/server/tool-registry.d.ts +23 -0
- package/dist/server/tool-registry.js +322 -0
- package/dist/server-setup.d.ts +21 -0
- package/dist/server-setup.js +111 -0
- package/dist/services/health-monitor.d.ts +34 -0
- package/dist/services/health-monitor.js +105 -0
- package/dist/services/metrics-server.d.ts +11 -0
- package/dist/services/metrics-server.js +105 -0
- package/dist/tools/actors.d.ts +147 -9
- package/dist/tools/actors.js +350 -311
- package/dist/tools/animation.d.ts +135 -4
- package/dist/tools/animation.js +510 -411
- package/dist/tools/assets.d.ts +117 -19
- package/dist/tools/assets.js +259 -284
- package/dist/tools/audio.d.ts +102 -42
- package/dist/tools/audio.js +272 -685
- package/dist/tools/base-tool.d.ts +17 -0
- package/dist/tools/base-tool.js +46 -0
- package/dist/tools/behavior-tree.d.ts +94 -0
- package/dist/tools/behavior-tree.js +39 -0
- package/dist/tools/blueprint/helpers.d.ts +29 -0
- package/dist/tools/blueprint/helpers.js +182 -0
- package/dist/tools/blueprint.d.ts +228 -118
- package/dist/tools/blueprint.js +685 -832
- package/dist/tools/consolidated-tool-definitions.d.ts +5475 -1627
- package/dist/tools/consolidated-tool-definitions.js +829 -482
- package/dist/tools/consolidated-tool-handlers.d.ts +2 -1
- package/dist/tools/consolidated-tool-handlers.js +211 -1009
- package/dist/tools/debug.d.ts +143 -85
- package/dist/tools/debug.js +234 -180
- package/dist/tools/dynamic-handler-registry.d.ts +11 -0
- package/dist/tools/dynamic-handler-registry.js +101 -0
- package/dist/tools/editor.d.ts +139 -18
- package/dist/tools/editor.js +239 -244
- package/dist/tools/engine.d.ts +10 -4
- package/dist/tools/engine.js +13 -5
- package/dist/tools/environment.d.ts +36 -0
- package/dist/tools/environment.js +267 -0
- package/dist/tools/foliage.d.ts +105 -14
- package/dist/tools/foliage.js +219 -331
- package/dist/tools/handlers/actor-handlers.d.ts +3 -0
- package/dist/tools/handlers/actor-handlers.js +232 -0
- package/dist/tools/handlers/animation-handlers.d.ts +3 -0
- package/dist/tools/handlers/animation-handlers.js +185 -0
- package/dist/tools/handlers/argument-helper.d.ts +16 -0
- package/dist/tools/handlers/argument-helper.js +80 -0
- package/dist/tools/handlers/asset-handlers.d.ts +3 -0
- package/dist/tools/handlers/asset-handlers.js +496 -0
- package/dist/tools/handlers/audio-handlers.d.ts +3 -0
- package/dist/tools/handlers/audio-handlers.js +166 -0
- package/dist/tools/handlers/blueprint-handlers.d.ts +4 -0
- package/dist/tools/handlers/blueprint-handlers.js +358 -0
- package/dist/tools/handlers/common-handlers.d.ts +14 -0
- package/dist/tools/handlers/common-handlers.js +56 -0
- package/dist/tools/handlers/editor-handlers.d.ts +3 -0
- package/dist/tools/handlers/editor-handlers.js +119 -0
- package/dist/tools/handlers/effect-handlers.d.ts +3 -0
- package/dist/tools/handlers/effect-handlers.js +171 -0
- package/dist/tools/handlers/environment-handlers.d.ts +3 -0
- package/dist/tools/handlers/environment-handlers.js +170 -0
- package/dist/tools/handlers/graph-handlers.d.ts +3 -0
- package/dist/tools/handlers/graph-handlers.js +90 -0
- package/dist/tools/handlers/input-handlers.d.ts +3 -0
- package/dist/tools/handlers/input-handlers.js +21 -0
- package/dist/tools/handlers/inspect-handlers.d.ts +3 -0
- package/dist/tools/handlers/inspect-handlers.js +383 -0
- package/dist/tools/handlers/level-handlers.d.ts +3 -0
- package/dist/tools/handlers/level-handlers.js +237 -0
- package/dist/tools/handlers/lighting-handlers.d.ts +3 -0
- package/dist/tools/handlers/lighting-handlers.js +144 -0
- package/dist/tools/handlers/performance-handlers.d.ts +3 -0
- package/dist/tools/handlers/performance-handlers.js +130 -0
- package/dist/tools/handlers/pipeline-handlers.d.ts +3 -0
- package/dist/tools/handlers/pipeline-handlers.js +110 -0
- package/dist/tools/handlers/sequence-handlers.d.ts +3 -0
- package/dist/tools/handlers/sequence-handlers.js +376 -0
- package/dist/tools/handlers/system-handlers.d.ts +4 -0
- package/dist/tools/handlers/system-handlers.js +506 -0
- package/dist/tools/input.d.ts +19 -0
- package/dist/tools/input.js +89 -0
- package/dist/tools/introspection.d.ts +103 -40
- package/dist/tools/introspection.js +425 -568
- package/dist/tools/landscape.d.ts +97 -36
- package/dist/tools/landscape.js +280 -409
- package/dist/tools/level.d.ts +130 -10
- package/dist/tools/level.js +639 -675
- package/dist/tools/lighting.d.ts +77 -38
- package/dist/tools/lighting.js +441 -943
- package/dist/tools/logs.d.ts +45 -0
- package/dist/tools/logs.js +210 -0
- package/dist/tools/materials.d.ts +91 -24
- package/dist/tools/materials.js +190 -118
- package/dist/tools/niagara.d.ts +149 -39
- package/dist/tools/niagara.js +232 -182
- package/dist/tools/performance.d.ts +27 -12
- package/dist/tools/performance.js +204 -122
- package/dist/tools/physics.d.ts +32 -77
- package/dist/tools/physics.js +171 -582
- package/dist/tools/property-dictionary.d.ts +13 -0
- package/dist/tools/property-dictionary.js +82 -0
- package/dist/tools/sequence.d.ts +73 -48
- package/dist/tools/sequence.js +196 -748
- package/dist/tools/tool-definition-utils.d.ts +59 -0
- package/dist/tools/tool-definition-utils.js +35 -0
- package/dist/tools/ui.d.ts +66 -34
- package/dist/tools/ui.js +134 -214
- package/dist/types/env.d.ts +0 -3
- package/dist/types/env.js +0 -7
- package/dist/types/tool-interfaces.d.ts +898 -0
- package/dist/types/tool-interfaces.js +2 -0
- package/dist/types/tool-types.d.ts +195 -11
- package/dist/types/tool-types.js +0 -4
- package/dist/unreal-bridge.d.ts +24 -131
- package/dist/unreal-bridge.js +364 -1506
- package/dist/utils/command-validator.d.ts +9 -0
- package/dist/utils/command-validator.js +67 -0
- package/dist/utils/elicitation.d.ts +1 -1
- package/dist/utils/elicitation.js +12 -15
- package/dist/utils/error-handler.d.ts +2 -51
- package/dist/utils/error-handler.js +11 -87
- package/dist/utils/ini-reader.d.ts +3 -0
- package/dist/utils/ini-reader.js +69 -0
- package/dist/utils/logger.js +9 -6
- package/dist/utils/normalize.d.ts +3 -0
- package/dist/utils/normalize.js +56 -0
- package/dist/utils/response-factory.d.ts +7 -0
- package/dist/utils/response-factory.js +33 -0
- package/dist/utils/response-validator.d.ts +3 -24
- package/dist/utils/response-validator.js +130 -81
- package/dist/utils/result-helpers.d.ts +4 -5
- package/dist/utils/result-helpers.js +15 -16
- package/dist/utils/safe-json.js +5 -11
- package/dist/utils/unreal-command-queue.d.ts +24 -0
- package/dist/utils/unreal-command-queue.js +120 -0
- package/dist/utils/validation.d.ts +0 -40
- package/dist/utils/validation.js +1 -78
- package/dist/wasm/index.d.ts +70 -0
- package/dist/wasm/index.js +535 -0
- package/docs/GraphQL-API.md +888 -0
- package/docs/Migration-Guide-v0.5.0.md +692 -0
- package/docs/Roadmap.md +53 -0
- package/docs/WebAssembly-Integration.md +628 -0
- package/docs/editor-plugin-extension.md +370 -0
- package/docs/handler-mapping.md +242 -0
- package/docs/native-automation-progress.md +128 -0
- package/docs/testing-guide.md +423 -0
- package/mcp-config-example.json +6 -6
- package/package.json +60 -27
- package/plugins/McpAutomationBridge/Config/FilterPlugin.ini +8 -0
- package/plugins/McpAutomationBridge/McpAutomationBridge.uplugin +64 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/McpAutomationBridge.Build.cs +189 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.cpp +22 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.h +30 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeHelpers.h +1983 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeModule.cpp +72 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSettings.cpp +46 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +581 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +2394 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetQueryHandlers.cpp +300 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetWorkflowHandlers.cpp +2807 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AudioHandlers.cpp +1087 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BehaviorTreeHandlers.cpp +488 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.cpp +643 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.h +31 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +1184 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +5652 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers_List.cpp +152 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ControlHandlers.cpp +2614 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_DebugHandlers.cpp +42 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EditorFunctionHandlers.cpp +1237 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +1701 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +2145 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_FoliageHandlers.cpp +954 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InputHandlers.cpp +209 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InsightsHandlers.cpp +41 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LandscapeHandlers.cpp +1164 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LevelHandlers.cpp +762 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +634 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LogHandlers.cpp +136 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_MaterialGraphHandlers.cpp +494 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraGraphHandlers.cpp +278 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraHandlers.cpp +625 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PerformanceHandlers.cpp +401 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PipelineHandlers.cpp +67 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +735 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PropertyHandlers.cpp +2634 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_RenderHandlers.cpp +189 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.cpp +917 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.h +39 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +2670 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequencerHandlers.cpp +519 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_TestHandlers.cpp +38 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_UiHandlers.cpp +668 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_WorldPartitionHandlers.cpp +346 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +1330 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.h +149 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +783 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSettings.h +115 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSubsystem.h +796 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpConnectionManager.h +117 -0
- package/scripts/check-unreal-connection.mjs +19 -0
- package/scripts/clean-tmp.js +23 -0
- package/scripts/patch-wasm.js +26 -0
- package/scripts/run-all-tests.mjs +131 -0
- package/scripts/smoke-test.ts +94 -0
- package/scripts/sync-mcp-plugin.js +143 -0
- package/scripts/test-no-plugin-alternates.mjs +113 -0
- package/scripts/validate-server.js +46 -0
- package/scripts/verify-automation-bridge.js +200 -0
- package/server.json +57 -21
- package/src/automation/bridge.ts +558 -0
- package/src/automation/connection-manager.ts +130 -0
- package/src/automation/handshake.ts +99 -0
- package/src/automation/index.ts +2 -0
- package/src/automation/message-handler.ts +167 -0
- package/src/automation/request-tracker.ts +123 -0
- package/src/automation/types.ts +107 -0
- package/src/cli.ts +33 -6
- package/src/config.ts +73 -0
- package/src/constants.ts +12 -0
- package/src/graphql/resolvers.ts +1010 -0
- package/src/graphql/schema.ts +452 -0
- package/src/graphql/server.ts +154 -0
- package/src/graphql/types.ts +7 -0
- package/src/handlers/resource-handlers.ts +186 -0
- package/src/index.ts +152 -649
- package/src/prompts/index.ts +4 -4
- package/src/resources/actors.ts +58 -76
- package/src/resources/assets.ts +147 -134
- package/src/resources/levels.ts +28 -33
- package/src/server/resource-registry.ts +47 -0
- package/src/server/tool-registry.ts +354 -0
- package/src/server-setup.ts +148 -0
- package/src/services/health-monitor.ts +132 -0
- package/src/services/metrics-server.ts +142 -0
- package/src/tools/actors.ts +417 -322
- package/src/tools/animation.ts +671 -461
- package/src/tools/assets.ts +353 -289
- package/src/tools/audio.ts +323 -766
- package/src/tools/base-tool.ts +52 -0
- package/src/tools/behavior-tree.ts +45 -0
- package/src/tools/blueprint/helpers.ts +189 -0
- package/src/tools/blueprint.ts +787 -965
- package/src/tools/consolidated-tool-definitions.ts +993 -500
- package/src/tools/consolidated-tool-handlers.ts +272 -1122
- package/src/tools/debug.ts +292 -187
- package/src/tools/dynamic-handler-registry.ts +151 -0
- package/src/tools/editor.ts +309 -246
- package/src/tools/engine.ts +14 -3
- package/src/tools/environment.ts +287 -0
- package/src/tools/foliage.ts +314 -379
- package/src/tools/handlers/actor-handlers.ts +271 -0
- package/src/tools/handlers/animation-handlers.ts +237 -0
- package/src/tools/handlers/argument-helper.ts +142 -0
- package/src/tools/handlers/asset-handlers.ts +532 -0
- package/src/tools/handlers/audio-handlers.ts +194 -0
- package/src/tools/handlers/blueprint-handlers.ts +380 -0
- package/src/tools/handlers/common-handlers.ts +87 -0
- package/src/tools/handlers/editor-handlers.ts +123 -0
- package/src/tools/handlers/effect-handlers.ts +220 -0
- package/src/tools/handlers/environment-handlers.ts +183 -0
- package/src/tools/handlers/graph-handlers.ts +116 -0
- package/src/tools/handlers/input-handlers.ts +28 -0
- package/src/tools/handlers/inspect-handlers.ts +450 -0
- package/src/tools/handlers/level-handlers.ts +252 -0
- package/src/tools/handlers/lighting-handlers.ts +147 -0
- package/src/tools/handlers/performance-handlers.ts +132 -0
- package/src/tools/handlers/pipeline-handlers.ts +127 -0
- package/src/tools/handlers/sequence-handlers.ts +415 -0
- package/src/tools/handlers/system-handlers.ts +564 -0
- package/src/tools/input.ts +101 -0
- package/src/tools/introspection.ts +493 -584
- package/src/tools/landscape.ts +394 -489
- package/src/tools/level.ts +752 -694
- package/src/tools/lighting.ts +583 -984
- package/src/tools/logs.ts +219 -0
- package/src/tools/materials.ts +231 -121
- package/src/tools/niagara.ts +293 -168
- package/src/tools/performance.ts +320 -168
- package/src/tools/physics.ts +268 -613
- package/src/tools/property-dictionary.ts +98 -0
- package/src/tools/sequence.ts +255 -815
- package/src/tools/tool-definition-utils.ts +35 -0
- package/src/tools/ui.ts +207 -283
- package/src/types/env.ts +0 -10
- package/src/types/tool-interfaces.ts +250 -0
- package/src/types/tool-types.ts +250 -13
- package/src/unreal-bridge.ts +460 -1550
- package/src/utils/command-validator.ts +75 -0
- package/src/utils/elicitation.ts +10 -7
- package/src/utils/error-handler.ts +14 -90
- package/src/utils/ini-reader.ts +86 -0
- package/src/utils/logger.ts +8 -3
- package/src/utils/normalize.ts +60 -0
- package/src/utils/response-factory.ts +39 -0
- package/src/utils/response-validator.ts +176 -56
- package/src/utils/result-helpers.ts +21 -19
- package/src/utils/safe-json.ts +14 -11
- package/src/utils/unreal-command-queue.ts +152 -0
- package/src/utils/validation.ts +4 -1
- package/src/wasm/index.ts +838 -0
- package/test-server.mjs +100 -0
- package/tests/run-unreal-tool-tests.mjs +242 -14
- package/tests/test-animation.mjs +44 -0
- package/tests/test-asset-advanced.mjs +82 -0
- package/tests/test-asset-errors.mjs +35 -0
- package/tests/test-audio.mjs +219 -0
- package/tests/test-automation-timeouts.mjs +98 -0
- package/tests/test-behavior-tree.mjs +261 -0
- package/tests/test-blueprint-events.mjs +35 -0
- package/tests/test-blueprint-graph.mjs +79 -0
- package/tests/test-blueprint.mjs +577 -0
- package/tests/test-client-mode.mjs +86 -0
- package/tests/test-console-command.mjs +56 -0
- package/tests/test-control-actor.mjs +425 -0
- package/tests/test-control-editor.mjs +80 -0
- package/tests/test-extra-tools.mjs +38 -0
- package/tests/test-graphql.mjs +322 -0
- package/tests/test-inspect.mjs +72 -0
- package/tests/test-landscape.mjs +60 -0
- package/tests/test-manage-asset.mjs +438 -0
- package/tests/test-manage-level.mjs +70 -0
- package/tests/test-materials.mjs +356 -0
- package/tests/test-niagara.mjs +185 -0
- package/tests/test-no-inline-python.mjs +122 -0
- package/tests/test-plugin-handshake.mjs +82 -0
- package/tests/test-render.mjs +33 -0
- package/tests/test-runner.mjs +933 -0
- package/tests/test-search-assets.mjs +66 -0
- package/tests/test-sequence.mjs +68 -0
- package/tests/test-system.mjs +57 -0
- package/tests/test-wasm.mjs +193 -0
- package/tests/test-world-partition.mjs +215 -0
- package/tsconfig.json +3 -3
- package/wasm/Cargo.lock +363 -0
- package/wasm/Cargo.toml +42 -0
- package/wasm/LICENSE +21 -0
- package/wasm/README.md +253 -0
- package/wasm/src/dependency_resolver.rs +377 -0
- package/wasm/src/lib.rs +153 -0
- package/wasm/src/property_parser.rs +271 -0
- package/wasm/src/transform_math.rs +396 -0
- package/wasm/tests/integration.rs +109 -0
- package/.github/workflows/smithery-build.yml +0 -29
- package/dist/tools/build_environment_advanced.d.ts +0 -65
- package/dist/tools/build_environment_advanced.js +0 -633
- package/dist/tools/rc.d.ts +0 -110
- package/dist/tools/rc.js +0 -437
- package/dist/tools/visual.d.ts +0 -40
- package/dist/tools/visual.js +0 -282
- package/dist/utils/http.d.ts +0 -6
- package/dist/utils/http.js +0 -151
- package/dist/utils/python-output.d.ts +0 -18
- package/dist/utils/python-output.js +0 -290
- package/dist/utils/python.d.ts +0 -2
- package/dist/utils/python.js +0 -4
- package/dist/utils/stdio-redirect.d.ts +0 -2
- package/dist/utils/stdio-redirect.js +0 -20
- package/docs/unreal-tool-test-cases.md +0 -572
- package/smithery.yaml +0 -29
- package/src/tools/build_environment_advanced.ts +0 -732
- package/src/tools/rc.ts +0 -515
- package/src/tools/visual.ts +0 -281
- package/src/utils/http.ts +0 -187
- package/src/utils/python-output.ts +0 -351
- package/src/utils/python.ts +0 -3
- package/src/utils/stdio-redirect.ts +0 -18
package/src/tools/sequence.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { interpretStandardResult } from '../utils/result-helpers.js';
|
|
1
|
+
import { BaseTool } from './base-tool.js';
|
|
2
|
+
import { ISequenceTools } from '../types/tool-interfaces.js';
|
|
4
3
|
|
|
5
4
|
export interface LevelSequence {
|
|
6
5
|
path: string;
|
|
@@ -23,896 +22,337 @@ export interface SequenceTrack {
|
|
|
23
22
|
sections?: any[];
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
export class SequenceTools {
|
|
27
|
-
private log = new Logger('SequenceTools');
|
|
25
|
+
export class SequenceTools extends BaseTool implements ISequenceTools {
|
|
28
26
|
private sequenceCache = new Map<string, LevelSequence>();
|
|
29
|
-
private
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
private async ensureSequencerPrerequisites(operation: string): Promise<string[] | null> {
|
|
35
|
-
const missing = await this.bridge.ensurePluginsEnabled(['LevelSequenceEditor', 'Sequencer'], operation);
|
|
36
|
-
return missing.length ? missing : null;
|
|
27
|
+
private activeSequencePath?: string;
|
|
28
|
+
|
|
29
|
+
private resolveSequencePath(explicitPath?: unknown): string | undefined {
|
|
30
|
+
if (typeof explicitPath === 'string' && explicitPath.trim().length > 0) {
|
|
31
|
+
return explicitPath.trim();
|
|
37
32
|
}
|
|
33
|
+
return this.activeSequencePath;
|
|
34
|
+
}
|
|
38
35
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
setTimeout(resolve, this.retryDelay * attempt)
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
36
|
+
private async sendAction(action: string, payload: Record<string, unknown> = {}, timeoutMs?: number) {
|
|
37
|
+
const envDefault = Number(process.env.MCP_AUTOMATION_REQUEST_TIMEOUT_MS ?? '120000');
|
|
38
|
+
const defaultTimeout = Number.isFinite(envDefault) && envDefault > 0 ? envDefault : 120000;
|
|
39
|
+
const finalTimeout = typeof timeoutMs === 'number' && timeoutMs > 0 ? timeoutMs : defaultTimeout;
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const response = await this.sendAutomationRequest(
|
|
43
|
+
action,
|
|
44
|
+
payload,
|
|
45
|
+
{ timeoutMs: finalTimeout, waitForEvent: false }
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const success = response && response.success !== false;
|
|
49
|
+
const result = response.result ?? response;
|
|
50
|
+
|
|
51
|
+
return { success, message: response.message ?? undefined, error: response.success === false ? (response.error ?? response.message) : undefined, result, requestId: response.requestId } as any;
|
|
52
|
+
} catch (err: any) {
|
|
53
|
+
return { success: false, error: String(err), message: String(err) } as const;
|
|
61
54
|
}
|
|
62
|
-
|
|
63
|
-
throw lastError;
|
|
64
55
|
}
|
|
65
56
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
successMessage: `${operationName} succeeded`,
|
|
72
|
-
failureMessage: `${operationName} failed`
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
if (interpreted.success) {
|
|
76
|
-
return {
|
|
77
|
-
...interpreted.payload,
|
|
78
|
-
success: true
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const baseError = interpreted.error ?? `${operationName} did not return a valid result`;
|
|
83
|
-
const rawOutput = interpreted.rawText ?? '';
|
|
84
|
-
const cleanedOutput = interpreted.cleanText && interpreted.cleanText.trim().length > 0
|
|
85
|
-
? interpreted.cleanText.trim()
|
|
86
|
-
: baseError;
|
|
87
|
-
|
|
88
|
-
if (rawOutput.includes('ModuleNotFoundError')) {
|
|
89
|
-
return { success: false, error: 'Sequencer module not available. Ensure Sequencer is enabled.' };
|
|
90
|
-
}
|
|
91
|
-
if (rawOutput.includes('AttributeError')) {
|
|
92
|
-
return { success: false, error: 'Sequencer API method not found. Check Unreal Engine version compatibility.' };
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
this.log.error(`${operationName} returned no parsable result: ${cleanedOutput}`);
|
|
96
|
-
return {
|
|
97
|
-
success: false,
|
|
98
|
-
error: (() => {
|
|
99
|
-
const detail = cleanedOutput === baseError
|
|
100
|
-
? ''
|
|
101
|
-
: (cleanedOutput ?? '').substring(0, 200).trim();
|
|
102
|
-
return detail ? `${baseError}: ${detail}` : baseError;
|
|
103
|
-
})()
|
|
104
|
-
};
|
|
57
|
+
private isUnknownActionResponse(res: any): boolean {
|
|
58
|
+
if (!res) return false;
|
|
59
|
+
const txt = String((res.error ?? res.message ?? '')).toLowerCase();
|
|
60
|
+
// Only treat specific error codes as "not implemented"
|
|
61
|
+
return txt.includes('unknown_action') || txt.includes('unknown automation action') || txt.includes('not_implemented') || txt === 'unknown_plugin_action';
|
|
105
62
|
}
|
|
106
63
|
|
|
107
|
-
async create(params: { name: string; path?: string }) {
|
|
64
|
+
async create(params: { name: string; path?: string; timeoutMs?: number }) {
|
|
108
65
|
const name = params.name?.trim();
|
|
109
66
|
const base = (params.path || '/Game/Sequences').replace(/\/$/, '');
|
|
110
67
|
if (!name) return { success: false, error: 'name is required' };
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
message: 'Sequencer plugins disabled; reported simulated sequence creation.',
|
|
120
|
-
warnings: [`Sequence asset reported without creating on disk because required plugins are disabled: ${missingPlugins.join(', ')}`]
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
const py = `
|
|
124
|
-
import unreal, json
|
|
125
|
-
name = r"${name}"
|
|
126
|
-
base = r"${base}"
|
|
127
|
-
full = f"{base}/{name}"
|
|
128
|
-
try:
|
|
129
|
-
# Ensure directory exists
|
|
130
|
-
try:
|
|
131
|
-
if not unreal.EditorAssetLibrary.does_directory_exist(base):
|
|
132
|
-
unreal.EditorAssetLibrary.make_directory(base)
|
|
133
|
-
except Exception:
|
|
134
|
-
pass
|
|
135
|
-
|
|
136
|
-
if unreal.EditorAssetLibrary.does_asset_exist(full):
|
|
137
|
-
print('RESULT:' + json.dumps({'success': True, 'sequencePath': full, 'existing': True}))
|
|
138
|
-
else:
|
|
139
|
-
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
|
|
140
|
-
factory = unreal.LevelSequenceFactoryNew()
|
|
141
|
-
seq = asset_tools.create_asset(asset_name=name, package_path=base, asset_class=unreal.LevelSequence, factory=factory)
|
|
142
|
-
if seq:
|
|
143
|
-
unreal.EditorAssetLibrary.save_asset(full)
|
|
144
|
-
print('RESULT:' + json.dumps({'success': True, 'sequencePath': full}))
|
|
145
|
-
else:
|
|
146
|
-
print('RESULT:' + json.dumps({'success': False, 'error': 'Create returned None'}))
|
|
147
|
-
except Exception as e:
|
|
148
|
-
print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
|
|
149
|
-
`.trim();
|
|
150
|
-
const resp = await this.executeWithRetry(
|
|
151
|
-
() => this.bridge.executePython(py),
|
|
152
|
-
'createSequence'
|
|
153
|
-
);
|
|
154
|
-
|
|
155
|
-
const result = this.parsePythonResult(resp, 'createSequence');
|
|
156
|
-
|
|
157
|
-
// Cache the sequence if successful
|
|
158
|
-
if (result.success && result.sequencePath) {
|
|
159
|
-
const sequence: LevelSequence = {
|
|
160
|
-
path: result.sequencePath,
|
|
161
|
-
name: name
|
|
162
|
-
};
|
|
68
|
+
|
|
69
|
+
const payload = { name, path: base } as Record<string, unknown>;
|
|
70
|
+
const resp = await this.sendAction('sequence_create', payload, params.timeoutMs);
|
|
71
|
+
if (!resp.success && this.isUnknownActionResponse(resp)) {
|
|
72
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_create' } as const;
|
|
73
|
+
}
|
|
74
|
+
if (resp.success && resp.result && resp.result.sequencePath) {
|
|
75
|
+
const sequence: LevelSequence = { path: resp.result.sequencePath, name };
|
|
163
76
|
this.sequenceCache.set(sequence.path, sequence);
|
|
77
|
+
return { ...resp, sequence: resp.result.sequencePath };
|
|
164
78
|
}
|
|
165
|
-
|
|
166
|
-
return result;
|
|
79
|
+
return resp;
|
|
167
80
|
}
|
|
168
81
|
|
|
169
82
|
async open(params: { path: string }) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
path = r"${params.path}"
|
|
180
|
-
try:
|
|
181
|
-
seq = unreal.load_asset(path)
|
|
182
|
-
if not seq:
|
|
183
|
-
print('RESULT:' + json.dumps({'success': False, 'error': 'Sequence not found'}))
|
|
184
|
-
else:
|
|
185
|
-
unreal.LevelSequenceEditorBlueprintLibrary.open_level_sequence(seq)
|
|
186
|
-
print('RESULT:' + json.dumps({'success': True, 'sequencePath': path}))
|
|
187
|
-
except Exception as e:
|
|
188
|
-
print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
|
|
189
|
-
`.trim();
|
|
190
|
-
const resp = await this.executeWithRetry(
|
|
191
|
-
() => this.bridge.executePython(py),
|
|
192
|
-
'openSequence'
|
|
193
|
-
);
|
|
194
|
-
|
|
195
|
-
return this.parsePythonResult(resp, 'openSequence');
|
|
83
|
+
const path = params.path?.trim();
|
|
84
|
+
const resp = await this.sendAction('sequence_open', { path });
|
|
85
|
+
if (!resp.success && this.isUnknownActionResponse(resp)) {
|
|
86
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_open' } as const;
|
|
87
|
+
}
|
|
88
|
+
if (resp && resp.success !== false && path) {
|
|
89
|
+
this.activeSequencePath = path;
|
|
90
|
+
}
|
|
91
|
+
return resp;
|
|
196
92
|
}
|
|
197
93
|
|
|
198
|
-
async addCamera(params: { spawnable?: boolean }) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
cameraBindingId: 'simulated_camera',
|
|
206
|
-
cameraName: 'SimulatedCamera',
|
|
207
|
-
warnings: [`Camera binding simulated because required plugins are disabled: ${missingPlugins.join(', ')}`]
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
const py = `
|
|
211
|
-
import unreal, json
|
|
212
|
-
try:
|
|
213
|
-
ls = unreal.get_editor_subsystem(unreal.LevelSequenceEditorSubsystem)
|
|
214
|
-
if not ls:
|
|
215
|
-
print('RESULT:' + json.dumps({'success': False, 'error': 'LevelSequenceEditorSubsystem unavailable'}))
|
|
216
|
-
else:
|
|
217
|
-
# create_camera returns tuple: (binding_proxy, camera_actor)
|
|
218
|
-
result = ls.create_camera(spawnable=${params.spawnable !== false ? 'True' : 'False'})
|
|
219
|
-
binding_id = ''
|
|
220
|
-
camera_name = ''
|
|
221
|
-
|
|
222
|
-
if result and len(result) >= 2:
|
|
223
|
-
binding_proxy = result[0]
|
|
224
|
-
camera_actor = result[1]
|
|
225
|
-
|
|
226
|
-
# Get the current sequence
|
|
227
|
-
seq = unreal.LevelSequenceEditorBlueprintLibrary.get_focused_level_sequence()
|
|
228
|
-
|
|
229
|
-
if seq and binding_proxy:
|
|
230
|
-
try:
|
|
231
|
-
# Get GUID directly from binding proxy - this is more reliable
|
|
232
|
-
binding_guid = unreal.MovieSceneBindingExtensions.get_id(binding_proxy)
|
|
233
|
-
# The GUID itself is what we need
|
|
234
|
-
binding_id = str(binding_guid).replace('<Guid ', '').replace('>', '').split(' ')[0] if str(binding_guid).startswith('<') else str(binding_guid)
|
|
235
|
-
|
|
236
|
-
# If that didn't work, try the binding object
|
|
237
|
-
if binding_id.startswith('<') or not binding_id:
|
|
238
|
-
binding_obj = unreal.MovieSceneSequenceExtensions.get_binding_id(seq, binding_proxy)
|
|
239
|
-
# Try to extract GUID from the object representation
|
|
240
|
-
obj_str = str(binding_obj)
|
|
241
|
-
if 'guid=' in obj_str:
|
|
242
|
-
binding_id = obj_str.split('guid=')[1].split(',')[0].split('}')[0].strip()
|
|
243
|
-
elif hasattr(binding_obj, 'guid'):
|
|
244
|
-
binding_id = str(binding_obj.guid)
|
|
245
|
-
else:
|
|
246
|
-
# Use a hash of the binding for a consistent ID
|
|
247
|
-
import hashlib
|
|
248
|
-
binding_id = hashlib.md5(str(binding_proxy).encode()).hexdigest()[:8]
|
|
249
|
-
except Exception as e:
|
|
250
|
-
# Generate a unique ID based on camera
|
|
251
|
-
import hashlib
|
|
252
|
-
camera_str = camera_actor.get_name() if camera_actor else 'spawned'
|
|
253
|
-
binding_id = f'cam_{hashlib.md5(camera_str.encode()).hexdigest()[:8]}'
|
|
254
|
-
|
|
255
|
-
if camera_actor:
|
|
256
|
-
try:
|
|
257
|
-
camera_name = camera_actor.get_actor_label()
|
|
258
|
-
except:
|
|
259
|
-
camera_name = 'CineCamera'
|
|
260
|
-
|
|
261
|
-
print('RESULT:' + json.dumps({
|
|
262
|
-
'success': True,
|
|
263
|
-
'cameraBindingId': binding_id,
|
|
264
|
-
'cameraName': camera_name
|
|
265
|
-
}))
|
|
266
|
-
else:
|
|
267
|
-
# Even if result format is different, camera might still be created
|
|
268
|
-
print('RESULT:' + json.dumps({
|
|
269
|
-
'success': True,
|
|
270
|
-
'cameraBindingId': 'camera_created',
|
|
271
|
-
'warning': 'Camera created but binding format unexpected'
|
|
272
|
-
}))
|
|
273
|
-
except Exception as e:
|
|
274
|
-
print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
|
|
275
|
-
`.trim();
|
|
276
|
-
const resp = await this.executeWithRetry(
|
|
277
|
-
() => this.bridge.executePython(py),
|
|
278
|
-
'addCamera'
|
|
279
|
-
);
|
|
280
|
-
|
|
281
|
-
return this.parsePythonResult(resp, 'addCamera');
|
|
94
|
+
async addCamera(params: { spawnable?: boolean; path?: string }) {
|
|
95
|
+
const path = this.resolveSequencePath(params.path);
|
|
96
|
+
const resp = await this.sendAction('sequence_add_camera', { path, spawnable: params.spawnable !== false });
|
|
97
|
+
if (!resp.success && this.isUnknownActionResponse(resp)) {
|
|
98
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_add_camera' } as const;
|
|
99
|
+
}
|
|
100
|
+
return resp;
|
|
282
101
|
}
|
|
283
102
|
|
|
284
|
-
async addActor(params: { actorName: string; createBinding?: boolean }) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
}
|
|
292
|
-
const py = `
|
|
293
|
-
import unreal, json
|
|
294
|
-
try:
|
|
295
|
-
actor_sub = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
|
|
296
|
-
ls = unreal.get_editor_subsystem(unreal.LevelSequenceEditorSubsystem)
|
|
297
|
-
if not ls or not actor_sub:
|
|
298
|
-
print('RESULT:' + json.dumps({'success': False, 'error': 'Subsystem unavailable'}))
|
|
299
|
-
else:
|
|
300
|
-
target = None
|
|
301
|
-
actors = actor_sub.get_all_level_actors()
|
|
302
|
-
for a in actors:
|
|
303
|
-
if not a: continue
|
|
304
|
-
label = a.get_actor_label()
|
|
305
|
-
name = a.get_name()
|
|
306
|
-
# Check label, name, and partial matches
|
|
307
|
-
if label == r"${params.actorName}" or name == r"${params.actorName}" or label.startswith(r"${params.actorName}"):
|
|
308
|
-
target = a
|
|
309
|
-
break
|
|
310
|
-
|
|
311
|
-
if not target:
|
|
312
|
-
# Try to find any actors to debug
|
|
313
|
-
actor_info = []
|
|
314
|
-
for a in actors[:5]:
|
|
315
|
-
if a:
|
|
316
|
-
actor_info.append({'label': a.get_actor_label(), 'name': a.get_name()})
|
|
317
|
-
print('RESULT:' + json.dumps({'success': False, 'error': f'Actor "${params.actorName}" not found. Sample actors: {actor_info}'}))
|
|
318
|
-
else:
|
|
319
|
-
# Make sure we have a focused sequence
|
|
320
|
-
seq = unreal.LevelSequenceEditorBlueprintLibrary.get_focused_level_sequence()
|
|
321
|
-
if seq:
|
|
322
|
-
# Use add_actors method which returns binding proxies
|
|
323
|
-
bindings = ls.add_actors([target])
|
|
324
|
-
binding_info = []
|
|
325
|
-
|
|
326
|
-
# bindings might be a list or might be empty if actor already exists
|
|
327
|
-
if bindings and len(bindings) > 0:
|
|
328
|
-
for binding in bindings:
|
|
329
|
-
try:
|
|
330
|
-
# Get binding name and GUID
|
|
331
|
-
binding_name = unreal.MovieSceneBindingExtensions.get_name(binding)
|
|
332
|
-
binding_guid = unreal.MovieSceneBindingExtensions.get_id(binding)
|
|
333
|
-
|
|
334
|
-
# Extract clean GUID string
|
|
335
|
-
guid_str = str(binding_guid)
|
|
336
|
-
if guid_str.startswith('<Guid '):
|
|
337
|
-
# Extract the actual GUID value from <Guid 'XXXX-XXXX-XXXX-XXXX'>
|
|
338
|
-
guid_clean = guid_str.replace('<Guid ', '').replace('>', '').replace("'", '').split(' ')[0]
|
|
339
|
-
else:
|
|
340
|
-
guid_clean = guid_str
|
|
341
|
-
|
|
342
|
-
binding_info.append({
|
|
343
|
-
'id': guid_clean,
|
|
344
|
-
'guid': guid_clean,
|
|
345
|
-
'name': binding_name if binding_name else target.get_actor_label()
|
|
346
|
-
})
|
|
347
|
-
except Exception as e:
|
|
348
|
-
# If binding methods fail, still count it
|
|
349
|
-
binding_info.append({
|
|
350
|
-
'id': 'binding_' + str(len(binding_info)),
|
|
351
|
-
'name': target.get_actor_label(),
|
|
352
|
-
'error': str(e)
|
|
353
|
-
})
|
|
354
|
-
|
|
355
|
-
print('RESULT:' + json.dumps({
|
|
356
|
-
'success': True,
|
|
357
|
-
'count': len(bindings),
|
|
358
|
-
'actorAdded': target.get_actor_label(),
|
|
359
|
-
'bindings': binding_info
|
|
360
|
-
}))
|
|
361
|
-
else:
|
|
362
|
-
# Actor was likely added but no new binding returned (might already exist)
|
|
363
|
-
# Still report success since the actor is in the sequence
|
|
364
|
-
print('RESULT:' + json.dumps({
|
|
365
|
-
'success': True,
|
|
366
|
-
'count': 1,
|
|
367
|
-
'actorAdded': target.get_actor_label(),
|
|
368
|
-
'bindings': [{'name': target.get_actor_label(), 'note': 'Actor added to sequence'}],
|
|
369
|
-
'info': 'Actor processed successfully'
|
|
370
|
-
}))
|
|
371
|
-
else:
|
|
372
|
-
print('RESULT:' + json.dumps({'success': False, 'error': 'No sequence is currently focused'}))
|
|
373
|
-
except Exception as e:
|
|
374
|
-
print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
|
|
375
|
-
`.trim();
|
|
376
|
-
const resp = await this.executeWithRetry(
|
|
377
|
-
() => this.bridge.executePython(py),
|
|
378
|
-
'addActor'
|
|
379
|
-
);
|
|
380
|
-
|
|
381
|
-
return this.parsePythonResult(resp, 'addActor');
|
|
103
|
+
async addActor(params: { actorName: string; createBinding?: boolean; path?: string }) {
|
|
104
|
+
const path = this.resolveSequencePath(params.path);
|
|
105
|
+
const resp = await this.sendAction('sequence_add_actor', { path, actorName: params.actorName, createBinding: params.createBinding });
|
|
106
|
+
if (!resp.success && this.isUnknownActionResponse(resp)) {
|
|
107
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_add_actor' } as const;
|
|
108
|
+
}
|
|
109
|
+
return resp;
|
|
382
110
|
}
|
|
383
111
|
|
|
384
112
|
/**
|
|
385
113
|
* Play the current level sequence
|
|
386
114
|
*/
|
|
387
|
-
async play(params?: { startTime?: number; loopMode?: 'once' | 'loop' | 'pingpong' }) {
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
simulated: true,
|
|
395
|
-
playing: true,
|
|
396
|
-
loopMode: loop || 'default',
|
|
397
|
-
warnings: [`Playback simulated because required plugins are disabled: ${missingPlugins.join(', ')}`],
|
|
398
|
-
message: 'Sequencer plugins disabled; playback simulated.'
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
const py = `
|
|
402
|
-
import unreal, json
|
|
403
|
-
|
|
404
|
-
# Helper to resolve SequencerLoopMode from a friendly string
|
|
405
|
-
def _resolve_loop_mode(mode_str):
|
|
406
|
-
try:
|
|
407
|
-
m = str(mode_str).lower()
|
|
408
|
-
slm = unreal.SequencerLoopMode
|
|
409
|
-
if m in ('once','noloop','no_loop'):
|
|
410
|
-
return getattr(slm, 'SLM_NoLoop', getattr(slm, 'NoLoop'))
|
|
411
|
-
if m in ('loop',):
|
|
412
|
-
return getattr(slm, 'SLM_Loop', getattr(slm, 'Loop'))
|
|
413
|
-
if m in ('pingpong','ping_pong'):
|
|
414
|
-
return getattr(slm, 'SLM_PingPong', getattr(slm, 'PingPong'))
|
|
415
|
-
except Exception:
|
|
416
|
-
pass
|
|
417
|
-
return None
|
|
418
|
-
|
|
419
|
-
try:
|
|
420
|
-
unreal.LevelSequenceEditorBlueprintLibrary.play()
|
|
421
|
-
loop_mode = _resolve_loop_mode('${loop}')
|
|
422
|
-
if loop_mode is not None:
|
|
423
|
-
unreal.LevelSequenceEditorBlueprintLibrary.set_loop_mode(loop_mode)
|
|
424
|
-
print('RESULT:' + json.dumps({'success': True, 'playing': True, 'loopMode': '${loop || 'default'}'}))
|
|
425
|
-
except Exception as e:
|
|
426
|
-
print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
|
|
427
|
-
`.trim();
|
|
428
|
-
|
|
429
|
-
const resp = await this.executeWithRetry(
|
|
430
|
-
() => this.bridge.executePython(py),
|
|
431
|
-
'playSequence'
|
|
432
|
-
);
|
|
433
|
-
|
|
434
|
-
return this.parsePythonResult(resp, 'playSequence');
|
|
115
|
+
async play(params?: { path?: string; startTime?: number; loopMode?: 'once' | 'loop' | 'pingpong' }) {
|
|
116
|
+
const path = this.resolveSequencePath(params?.path);
|
|
117
|
+
const resp = await this.sendAction('sequence_play', { path, startTime: params?.startTime, loopMode: params?.loopMode });
|
|
118
|
+
if (!resp.success && this.isUnknownActionResponse(resp)) {
|
|
119
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_play' } as const;
|
|
120
|
+
}
|
|
121
|
+
return resp;
|
|
435
122
|
}
|
|
436
123
|
|
|
437
124
|
/**
|
|
438
125
|
* Pause the current level sequence
|
|
439
126
|
*/
|
|
440
|
-
async pause() {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
}
|
|
448
|
-
const py = `
|
|
449
|
-
import unreal, json
|
|
450
|
-
try:
|
|
451
|
-
unreal.LevelSequenceEditorBlueprintLibrary.pause()
|
|
452
|
-
print('RESULT:' + json.dumps({'success': True, 'paused': True}))
|
|
453
|
-
except Exception as e:
|
|
454
|
-
print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
|
|
455
|
-
`.trim();
|
|
456
|
-
|
|
457
|
-
const resp = await this.executeWithRetry(
|
|
458
|
-
() => this.bridge.executePython(py),
|
|
459
|
-
'pauseSequence'
|
|
460
|
-
);
|
|
461
|
-
|
|
462
|
-
return this.parsePythonResult(resp, 'pauseSequence');
|
|
127
|
+
async pause(params?: { path?: string }) {
|
|
128
|
+
const path = this.resolveSequencePath(params?.path);
|
|
129
|
+
const resp = await this.sendAction('sequence_pause', { path });
|
|
130
|
+
if (!resp.success && this.isUnknownActionResponse(resp)) {
|
|
131
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_pause' } as const;
|
|
132
|
+
}
|
|
133
|
+
return resp;
|
|
463
134
|
}
|
|
464
135
|
|
|
465
136
|
/**
|
|
466
137
|
* Stop/close the current level sequence
|
|
467
138
|
*/
|
|
468
|
-
async stop() {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
}
|
|
476
|
-
const py = `
|
|
477
|
-
import unreal, json
|
|
478
|
-
try:
|
|
479
|
-
unreal.LevelSequenceEditorBlueprintLibrary.close_level_sequence()
|
|
480
|
-
print('RESULT:' + json.dumps({'success': True, 'stopped': True}))
|
|
481
|
-
except Exception as e:
|
|
482
|
-
print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
|
|
483
|
-
`.trim();
|
|
484
|
-
|
|
485
|
-
const resp = await this.executeWithRetry(
|
|
486
|
-
() => this.bridge.executePython(py),
|
|
487
|
-
'stopSequence'
|
|
488
|
-
);
|
|
489
|
-
|
|
490
|
-
return this.parsePythonResult(resp, 'stopSequence');
|
|
139
|
+
async stop(params?: { path?: string }) {
|
|
140
|
+
const path = this.resolveSequencePath(params?.path);
|
|
141
|
+
const resp = await this.sendAction('sequence_stop', { path });
|
|
142
|
+
if (!resp.success && this.isUnknownActionResponse(resp)) {
|
|
143
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_stop' } as const;
|
|
144
|
+
}
|
|
145
|
+
return resp;
|
|
491
146
|
}
|
|
492
147
|
|
|
493
148
|
/**
|
|
494
149
|
* Set sequence properties including frame rate and length
|
|
495
150
|
*/
|
|
496
|
-
async setSequenceProperties(params: {
|
|
151
|
+
async setSequenceProperties(params: {
|
|
497
152
|
path?: string;
|
|
498
153
|
frameRate?: number;
|
|
499
154
|
lengthInFrames?: number;
|
|
500
155
|
playbackStart?: number;
|
|
501
156
|
playbackEnd?: number;
|
|
502
157
|
}) {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
finalProperties: {
|
|
527
|
-
frameRate: params.frameRate ? { numerator: params.frameRate, denominator: 1 } : undefined,
|
|
528
|
-
playbackStart: params.playbackStart,
|
|
529
|
-
playbackEnd: params.playbackEnd,
|
|
530
|
-
duration: params.lengthInFrames
|
|
531
|
-
}
|
|
532
|
-
};
|
|
533
|
-
}
|
|
534
|
-
const py = `
|
|
535
|
-
import unreal, json
|
|
536
|
-
try:
|
|
537
|
-
# Load the sequence
|
|
538
|
-
seq_path = r"${params.path || ''}"
|
|
539
|
-
if seq_path:
|
|
540
|
-
seq = unreal.load_asset(seq_path)
|
|
541
|
-
else:
|
|
542
|
-
# Try to get the currently open sequence
|
|
543
|
-
seq = unreal.LevelSequenceEditorBlueprintLibrary.get_focused_level_sequence()
|
|
544
|
-
|
|
545
|
-
if not seq:
|
|
546
|
-
print('RESULT:' + json.dumps({'success': False, 'error': 'No sequence found or loaded'}))
|
|
547
|
-
else:
|
|
548
|
-
result = {'success': True, 'changes': []}
|
|
549
|
-
|
|
550
|
-
# Set frame rate if provided
|
|
551
|
-
${params.frameRate ? `
|
|
552
|
-
frame_rate = unreal.FrameRate(numerator=${params.frameRate}, denominator=1)
|
|
553
|
-
unreal.MovieSceneSequenceExtensions.set_display_rate(seq, frame_rate)
|
|
554
|
-
result['changes'].append({'property': 'frameRate', 'value': ${params.frameRate}})
|
|
555
|
-
` : ''}
|
|
556
|
-
|
|
557
|
-
# Set playback range if provided
|
|
558
|
-
${(params.playbackStart !== undefined || params.playbackEnd !== undefined) ? `
|
|
559
|
-
current_range = unreal.MovieSceneSequenceExtensions.get_playback_range(seq)
|
|
560
|
-
start = ${params.playbackStart !== undefined ? params.playbackStart : 'current_range.get_start_frame()'}
|
|
561
|
-
end = ${params.playbackEnd !== undefined ? params.playbackEnd : 'current_range.get_end_frame()'}
|
|
562
|
-
# Use set_playback_start and set_playback_end instead
|
|
563
|
-
if ${params.playbackStart !== undefined}:
|
|
564
|
-
unreal.MovieSceneSequenceExtensions.set_playback_start(seq, ${params.playbackStart})
|
|
565
|
-
if ${params.playbackEnd !== undefined}:
|
|
566
|
-
unreal.MovieSceneSequenceExtensions.set_playback_end(seq, ${params.playbackEnd})
|
|
567
|
-
result['changes'].append({'property': 'playbackRange', 'start': start, 'end': end})
|
|
568
|
-
` : ''}
|
|
569
|
-
|
|
570
|
-
# Set total length in frames if provided
|
|
571
|
-
${params.lengthInFrames ? `
|
|
572
|
-
# This sets the playback end to match the desired length
|
|
573
|
-
start = unreal.MovieSceneSequenceExtensions.get_playback_start(seq)
|
|
574
|
-
end = start + ${params.lengthInFrames}
|
|
575
|
-
unreal.MovieSceneSequenceExtensions.set_playback_end(seq, end)
|
|
576
|
-
result['changes'].append({'property': 'lengthInFrames', 'value': ${params.lengthInFrames}})
|
|
577
|
-
` : ''}
|
|
578
|
-
|
|
579
|
-
# Get final properties for confirmation
|
|
580
|
-
final_rate = unreal.MovieSceneSequenceExtensions.get_display_rate(seq)
|
|
581
|
-
final_range = unreal.MovieSceneSequenceExtensions.get_playback_range(seq)
|
|
582
|
-
result['finalProperties'] = {
|
|
583
|
-
'frameRate': {'numerator': final_rate.numerator, 'denominator': final_rate.denominator},
|
|
584
|
-
'playbackStart': final_range.get_start_frame(),
|
|
585
|
-
'playbackEnd': final_range.get_end_frame(),
|
|
586
|
-
'duration': final_range.get_end_frame() - final_range.get_start_frame()
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
print('RESULT:' + json.dumps(result))
|
|
590
|
-
except Exception as e:
|
|
591
|
-
print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
|
|
592
|
-
`.trim();
|
|
593
|
-
|
|
594
|
-
const resp = await this.executeWithRetry(
|
|
595
|
-
() => this.bridge.executePython(py),
|
|
596
|
-
'setSequenceProperties'
|
|
597
|
-
);
|
|
598
|
-
|
|
599
|
-
return this.parsePythonResult(resp, 'setSequenceProperties');
|
|
158
|
+
const payload: Record<string, unknown> = {
|
|
159
|
+
path: params.path,
|
|
160
|
+
frameRate: params.frameRate,
|
|
161
|
+
lengthInFrames: params.lengthInFrames,
|
|
162
|
+
playbackStart: params.playbackStart,
|
|
163
|
+
playbackEnd: params.playbackEnd
|
|
164
|
+
};
|
|
165
|
+
const resp = await this.sendAction('sequence_set_properties', payload);
|
|
166
|
+
if (!resp.success && this.isUnknownActionResponse(resp)) {
|
|
167
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_set_properties' } as const;
|
|
168
|
+
}
|
|
169
|
+
return resp;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Set display rate (fps)
|
|
174
|
+
*/
|
|
175
|
+
async setDisplayRate(params: { path?: string; frameRate: string | number }) {
|
|
176
|
+
const resp = await this.sendAction('sequence_set_display_rate', { path: params.path, frameRate: params.frameRate });
|
|
177
|
+
if (!resp.success && this.isUnknownActionResponse(resp)) {
|
|
178
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_set_display_rate' } as const;
|
|
179
|
+
}
|
|
180
|
+
return resp;
|
|
600
181
|
}
|
|
601
182
|
|
|
602
183
|
/**
|
|
603
184
|
* Get sequence properties
|
|
604
185
|
*/
|
|
605
186
|
async getSequenceProperties(params: { path?: string }) {
|
|
606
|
-
const
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
if seq_path:
|
|
612
|
-
seq = unreal.load_asset(seq_path)
|
|
613
|
-
else:
|
|
614
|
-
# Try to get the currently open sequence
|
|
615
|
-
seq = unreal.LevelSequenceEditorBlueprintLibrary.get_focused_level_sequence()
|
|
616
|
-
|
|
617
|
-
if not seq:
|
|
618
|
-
print('RESULT:' + json.dumps({'success': False, 'error': 'No sequence found or loaded'}))
|
|
619
|
-
else:
|
|
620
|
-
# Get all properties
|
|
621
|
-
display_rate = unreal.MovieSceneSequenceExtensions.get_display_rate(seq)
|
|
622
|
-
playback_range = unreal.MovieSceneSequenceExtensions.get_playback_range(seq)
|
|
623
|
-
|
|
624
|
-
# Get marked frames if any
|
|
625
|
-
marked_frames = []
|
|
626
|
-
try:
|
|
627
|
-
frames = unreal.MovieSceneSequenceExtensions.get_marked_frames(seq)
|
|
628
|
-
marked_frames = [{'frame': f.frame_number.value, 'label': f.label} for f in frames]
|
|
629
|
-
except:
|
|
630
|
-
pass
|
|
631
|
-
|
|
632
|
-
result = {
|
|
633
|
-
'success': True,
|
|
634
|
-
'path': seq.get_path_name(),
|
|
635
|
-
'name': seq.get_name(),
|
|
636
|
-
'frameRate': {
|
|
637
|
-
'numerator': display_rate.numerator,
|
|
638
|
-
'denominator': display_rate.denominator,
|
|
639
|
-
'fps': float(display_rate.numerator) / float(display_rate.denominator) if display_rate.denominator > 0 else 0
|
|
640
|
-
},
|
|
641
|
-
'playbackStart': playback_range.get_start_frame(),
|
|
642
|
-
'playbackEnd': playback_range.get_end_frame(),
|
|
643
|
-
'duration': playback_range.get_end_frame() - playback_range.get_start_frame(),
|
|
644
|
-
'markedFrames': marked_frames
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
print('RESULT:' + json.dumps(result))
|
|
648
|
-
except Exception as e:
|
|
649
|
-
print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
|
|
650
|
-
`.trim();
|
|
651
|
-
|
|
652
|
-
const resp = await this.executeWithRetry(
|
|
653
|
-
() => this.bridge.executePython(py),
|
|
654
|
-
'getSequenceProperties'
|
|
655
|
-
);
|
|
656
|
-
|
|
657
|
-
return this.parsePythonResult(resp, 'getSequenceProperties');
|
|
187
|
+
const resp = await this.sendAction('sequence_get_properties', { path: params.path });
|
|
188
|
+
if (!resp.success && this.isUnknownActionResponse(resp)) {
|
|
189
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_get_properties' } as const;
|
|
190
|
+
}
|
|
191
|
+
return resp;
|
|
658
192
|
}
|
|
659
193
|
|
|
660
194
|
/**
|
|
661
195
|
* Set playback speed/rate
|
|
662
196
|
*/
|
|
663
|
-
async setPlaybackSpeed(params: { speed: number }) {
|
|
664
|
-
const
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
|
|
671
|
-
`.trim();
|
|
672
|
-
|
|
673
|
-
const resp = await this.executeWithRetry(
|
|
674
|
-
() => this.bridge.executePython(py),
|
|
675
|
-
'setPlaybackSpeed'
|
|
676
|
-
);
|
|
677
|
-
|
|
678
|
-
return this.parsePythonResult(resp, 'setPlaybackSpeed');
|
|
197
|
+
async setPlaybackSpeed(params: { speed: number; path?: string }) {
|
|
198
|
+
const path = this.resolveSequencePath(params.path);
|
|
199
|
+
const resp = await this.sendAction('sequence_set_playback_speed', { path, speed: params.speed });
|
|
200
|
+
if (!resp.success && this.isUnknownActionResponse(resp)) {
|
|
201
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_set_playback_speed' } as const;
|
|
202
|
+
}
|
|
203
|
+
return resp;
|
|
679
204
|
}
|
|
680
205
|
|
|
681
206
|
/**
|
|
682
207
|
* Get all bindings in the current sequence
|
|
683
208
|
*/
|
|
684
209
|
async getBindings(params?: { path?: string }) {
|
|
685
|
-
const
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
if seq_path:
|
|
691
|
-
seq = unreal.load_asset(seq_path)
|
|
692
|
-
else:
|
|
693
|
-
# Try to get the currently open sequence
|
|
694
|
-
seq = unreal.LevelSequenceEditorBlueprintLibrary.get_focused_level_sequence()
|
|
695
|
-
|
|
696
|
-
if not seq:
|
|
697
|
-
print('RESULT:' + json.dumps({'success': False, 'error': 'No sequence found or loaded'}))
|
|
698
|
-
else:
|
|
699
|
-
bindings = unreal.MovieSceneSequenceExtensions.get_bindings(seq)
|
|
700
|
-
binding_list = []
|
|
701
|
-
for binding in bindings:
|
|
702
|
-
try:
|
|
703
|
-
binding_name = unreal.MovieSceneBindingExtensions.get_name(binding)
|
|
704
|
-
binding_guid = unreal.MovieSceneBindingExtensions.get_id(binding)
|
|
705
|
-
|
|
706
|
-
# Extract clean GUID string
|
|
707
|
-
guid_str = str(binding_guid)
|
|
708
|
-
if guid_str.startswith('<Guid '):
|
|
709
|
-
# Extract the actual GUID value from <Guid 'XXXX-XXXX-XXXX-XXXX'>
|
|
710
|
-
guid_clean = guid_str.replace('<Guid ', '').replace('>', '').replace("'", '').split(' ')[0]
|
|
711
|
-
else:
|
|
712
|
-
guid_clean = guid_str
|
|
713
|
-
|
|
714
|
-
binding_list.append({
|
|
715
|
-
'id': guid_clean,
|
|
716
|
-
'name': binding_name,
|
|
717
|
-
'guid': guid_clean
|
|
718
|
-
})
|
|
719
|
-
except:
|
|
720
|
-
pass
|
|
721
|
-
|
|
722
|
-
print('RESULT:' + json.dumps({'success': True, 'bindings': binding_list, 'count': len(binding_list)}))
|
|
723
|
-
except Exception as e:
|
|
724
|
-
print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
|
|
725
|
-
`.trim();
|
|
726
|
-
|
|
727
|
-
const resp = await this.executeWithRetry(
|
|
728
|
-
() => this.bridge.executePython(py),
|
|
729
|
-
'getBindings'
|
|
730
|
-
);
|
|
731
|
-
|
|
732
|
-
return this.parsePythonResult(resp, 'getBindings');
|
|
210
|
+
const resp = await this.sendAction('sequence_get_bindings', { path: params?.path });
|
|
211
|
+
if (!resp.success && this.isUnknownActionResponse(resp)) {
|
|
212
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_get_bindings' } as const;
|
|
213
|
+
}
|
|
214
|
+
return resp;
|
|
733
215
|
}
|
|
734
216
|
|
|
735
217
|
/**
|
|
736
218
|
* Add multiple actors to sequence at once
|
|
737
219
|
*/
|
|
738
|
-
async addActors(params: { actorNames: string[] }) {
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
print('RESULT:' + json.dumps({'success': False, 'error': 'Subsystem unavailable'}))
|
|
746
|
-
else:
|
|
747
|
-
actor_names = ${JSON.stringify(params.actorNames)}
|
|
748
|
-
actors_to_add = []
|
|
749
|
-
not_found = []
|
|
750
|
-
|
|
751
|
-
all_actors = actor_sub.get_all_level_actors()
|
|
752
|
-
for name in actor_names:
|
|
753
|
-
found = False
|
|
754
|
-
for a in all_actors:
|
|
755
|
-
if not a: continue
|
|
756
|
-
label = a.get_actor_label()
|
|
757
|
-
actor_name = a.get_name()
|
|
758
|
-
if label == name or actor_name == name or label.startswith(name):
|
|
759
|
-
actors_to_add.append(a)
|
|
760
|
-
found = True
|
|
761
|
-
break
|
|
762
|
-
if not found:
|
|
763
|
-
not_found.append(name)
|
|
764
|
-
|
|
765
|
-
# Make sure we have a focused sequence
|
|
766
|
-
seq = unreal.LevelSequenceEditorBlueprintLibrary.get_focused_level_sequence()
|
|
767
|
-
if not seq:
|
|
768
|
-
print('RESULT:' + json.dumps({'success': False, 'error': 'No sequence is currently focused'}))
|
|
769
|
-
elif len(actors_to_add) == 0:
|
|
770
|
-
print('RESULT:' + json.dumps({'success': False, 'error': f'No actors found: {not_found}'}))
|
|
771
|
-
else:
|
|
772
|
-
# Add all actors at once
|
|
773
|
-
bindings = ls.add_actors(actors_to_add)
|
|
774
|
-
added_actors = [a.get_actor_label() for a in actors_to_add]
|
|
775
|
-
print('RESULT:' + json.dumps({
|
|
776
|
-
'success': True,
|
|
777
|
-
'count': len(bindings) if bindings else len(actors_to_add),
|
|
778
|
-
'actorsAdded': added_actors,
|
|
779
|
-
'notFound': not_found
|
|
780
|
-
}))
|
|
781
|
-
except Exception as e:
|
|
782
|
-
print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
|
|
783
|
-
`.trim();
|
|
784
|
-
|
|
785
|
-
const resp = await this.executeWithRetry(
|
|
786
|
-
() => this.bridge.executePython(py),
|
|
787
|
-
'addActors'
|
|
788
|
-
);
|
|
789
|
-
|
|
790
|
-
return this.parsePythonResult(resp, 'addActors');
|
|
220
|
+
async addActors(params: { actorNames: string[]; path?: string }) {
|
|
221
|
+
const path = this.resolveSequencePath(params.path);
|
|
222
|
+
const resp = await this.sendAction('sequence_add_actors', { path, actorNames: params.actorNames });
|
|
223
|
+
if (!resp.success && this.isUnknownActionResponse(resp)) {
|
|
224
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_add_actors' } as const;
|
|
225
|
+
}
|
|
226
|
+
return resp;
|
|
791
227
|
}
|
|
792
228
|
|
|
793
229
|
/**
|
|
794
230
|
* Remove actors from binding
|
|
795
231
|
*/
|
|
796
|
-
async removeActors(params: { actorNames: string[] }) {
|
|
797
|
-
const
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
if not ls or not actor_sub:
|
|
804
|
-
print('RESULT:' + json.dumps({'success': False, 'error': 'Subsystem unavailable'}))
|
|
805
|
-
else:
|
|
806
|
-
# Get current sequence
|
|
807
|
-
seq = unreal.LevelSequenceEditorBlueprintLibrary.get_focused_level_sequence()
|
|
808
|
-
if not seq:
|
|
809
|
-
print('RESULT:' + json.dumps({'success': False, 'error': 'No sequence is currently focused'}))
|
|
810
|
-
else:
|
|
811
|
-
actor_names = ${JSON.stringify(params.actorNames)}
|
|
812
|
-
actors_to_remove = []
|
|
813
|
-
|
|
814
|
-
all_actors = actor_sub.get_all_level_actors()
|
|
815
|
-
for name in actor_names:
|
|
816
|
-
for a in all_actors:
|
|
817
|
-
if not a: continue
|
|
818
|
-
label = a.get_actor_label()
|
|
819
|
-
actor_name = a.get_name()
|
|
820
|
-
if label == name or actor_name == name:
|
|
821
|
-
actors_to_remove.append(a)
|
|
822
|
-
break
|
|
823
|
-
|
|
824
|
-
# Get all bindings and remove matching actors
|
|
825
|
-
bindings = unreal.MovieSceneSequenceExtensions.get_bindings(seq)
|
|
826
|
-
removed_count = 0
|
|
827
|
-
for binding in bindings:
|
|
828
|
-
try:
|
|
829
|
-
ls.remove_actors_from_binding(actors_to_remove, binding)
|
|
830
|
-
removed_count += 1
|
|
831
|
-
except:
|
|
832
|
-
pass
|
|
833
|
-
|
|
834
|
-
print('RESULT:' + json.dumps({
|
|
835
|
-
'success': True,
|
|
836
|
-
'removedActors': [a.get_actor_label() for a in actors_to_remove],
|
|
837
|
-
'bindingsProcessed': removed_count
|
|
838
|
-
}))
|
|
839
|
-
except Exception as e:
|
|
840
|
-
print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
|
|
841
|
-
`.trim();
|
|
842
|
-
|
|
843
|
-
const resp = await this.executeWithRetry(
|
|
844
|
-
() => this.bridge.executePython(py),
|
|
845
|
-
'removeActors'
|
|
846
|
-
);
|
|
847
|
-
|
|
848
|
-
return this.parsePythonResult(resp, 'removeActors');
|
|
232
|
+
async removeActors(params: { actorNames: string[]; path?: string }) {
|
|
233
|
+
const path = this.resolveSequencePath(params.path);
|
|
234
|
+
const resp = await this.sendAction('sequence_remove_actors', { path, actorNames: params.actorNames });
|
|
235
|
+
if (!resp.success && this.isUnknownActionResponse(resp)) {
|
|
236
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_remove_actors' } as const;
|
|
237
|
+
}
|
|
238
|
+
return resp;
|
|
849
239
|
}
|
|
850
240
|
|
|
851
241
|
/**
|
|
852
242
|
* Create a spawnable from an actor class
|
|
853
243
|
*/
|
|
854
244
|
async addSpawnableFromClass(params: { className: string; path?: string }) {
|
|
855
|
-
const
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
if
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
245
|
+
const resp = await this.sendAction('sequence_add_spawnable_from_class', { className: params.className, path: params.path });
|
|
246
|
+
if (!resp.success && this.isUnknownActionResponse(resp)) {
|
|
247
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_add_spawnable_from_class' } as const;
|
|
248
|
+
}
|
|
249
|
+
return resp;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async list(params?: { path?: string }) {
|
|
253
|
+
const resp = await this.sendAction('sequence_list', { path: params?.path });
|
|
254
|
+
if (!resp.success && this.isUnknownActionResponse(resp)) {
|
|
255
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_list' } as const;
|
|
256
|
+
}
|
|
257
|
+
if (resp.success) {
|
|
258
|
+
const sequences = resp.sequences || resp.data || resp.result || [];
|
|
259
|
+
return {
|
|
260
|
+
...resp,
|
|
261
|
+
sequences,
|
|
262
|
+
count: Array.isArray(sequences) ? sequences.length : undefined
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
return resp;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async duplicate(params: { path: string; destinationPath: string }) {
|
|
269
|
+
const resp = await this.sendAction('sequence_duplicate', { path: params.path, destinationPath: params.destinationPath });
|
|
270
|
+
if (!resp.success && this.isUnknownActionResponse(resp)) {
|
|
271
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_duplicate' } as const;
|
|
272
|
+
}
|
|
273
|
+
return resp;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async rename(params: { path: string; newName: string }) {
|
|
277
|
+
const resp = await this.sendAction('sequence_rename', { path: params.path, newName: params.newName });
|
|
278
|
+
if (!resp.success && this.isUnknownActionResponse(resp)) {
|
|
279
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_rename' } as const;
|
|
280
|
+
}
|
|
281
|
+
return resp;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async deleteSequence(params: { path: string }) {
|
|
285
|
+
const resp = await this.sendAction('sequence_delete', { path: params.path });
|
|
286
|
+
if (!resp.success && this.isUnknownActionResponse(resp)) {
|
|
287
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_delete' } as const;
|
|
288
|
+
}
|
|
289
|
+
return resp;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async getMetadata(params: { path?: string }) {
|
|
293
|
+
const resp = await this.sendAction('sequence_get_metadata', { path: params.path });
|
|
294
|
+
if (!resp.success && this.isUnknownActionResponse(resp)) {
|
|
295
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_get_metadata' } as const;
|
|
296
|
+
}
|
|
297
|
+
return resp;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Add a keyframe to a sequence binding
|
|
302
|
+
*/
|
|
303
|
+
async addKeyframe(params: {
|
|
304
|
+
path?: string;
|
|
305
|
+
bindingId?: string;
|
|
306
|
+
actorName?: string;
|
|
307
|
+
property: 'Transform';
|
|
308
|
+
frame: number;
|
|
309
|
+
value: {
|
|
310
|
+
location?: { x: number; y: number; z: number };
|
|
311
|
+
rotation?: { roll: number; pitch: number; yaw: number };
|
|
312
|
+
scale?: { x: number; y: number; z: number };
|
|
313
|
+
};
|
|
314
|
+
}) {
|
|
315
|
+
const resp = await this.sendAction('sequence_add_keyframe', {
|
|
316
|
+
path: params.path,
|
|
317
|
+
bindingId: params.bindingId,
|
|
318
|
+
actorName: params.actorName,
|
|
319
|
+
property: params.property,
|
|
320
|
+
frame: params.frame,
|
|
321
|
+
value: params.value
|
|
322
|
+
});
|
|
323
|
+
if (!resp.success && this.isUnknownActionResponse(resp)) {
|
|
324
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_add_keyframe' } as const;
|
|
325
|
+
}
|
|
326
|
+
return resp;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* List tracks in a sequence
|
|
331
|
+
*/
|
|
332
|
+
async listTracks(params: { path: string }) {
|
|
333
|
+
const resp = await this.sendAction('sequence_list_tracks', { path: params.path });
|
|
334
|
+
if (!resp.success && this.isUnknownActionResponse(resp)) {
|
|
335
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_list_tracks' } as const;
|
|
336
|
+
}
|
|
337
|
+
if (resp.success) {
|
|
338
|
+
const tracks = resp.tracks || resp.data || resp.result || [];
|
|
339
|
+
return {
|
|
340
|
+
...resp,
|
|
341
|
+
tracks,
|
|
342
|
+
count: Array.isArray(tracks) ? tracks.length : undefined
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
return resp;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Set playback work range
|
|
350
|
+
*/
|
|
351
|
+
async setWorkRange(params: { path?: string; start: number; end: number }) {
|
|
352
|
+
const resp = await this.sendAction('sequence_set_work_range', { path: params.path, start: params.start, end: params.end });
|
|
353
|
+
if (!resp.success && this.isUnknownActionResponse(resp)) {
|
|
354
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_set_work_range' } as const;
|
|
355
|
+
}
|
|
356
|
+
return resp;
|
|
917
357
|
}
|
|
918
358
|
}
|