unreal-engine-mcp-server 0.5.4 → 0.5.5
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/dist/automation/bridge.d.ts.map +1 -0
- package/dist/automation/bridge.js.map +1 -0
- package/dist/automation/connection-manager.d.ts.map +1 -0
- package/dist/automation/connection-manager.js.map +1 -0
- package/dist/automation/handshake.d.ts.map +1 -0
- package/dist/automation/handshake.js.map +1 -0
- package/dist/automation/index.d.ts.map +1 -0
- package/dist/automation/index.js.map +1 -0
- package/dist/automation/message-handler.d.ts.map +1 -0
- package/dist/automation/message-handler.js.map +1 -0
- package/dist/automation/request-tracker.d.ts.map +1 -0
- package/dist/automation/request-tracker.js.map +1 -0
- package/dist/automation/types.d.ts.map +1 -0
- package/dist/automation/types.js.map +1 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +4 -3
- package/dist/cli.js.map +1 -0
- package/dist/config/class-aliases.d.ts.map +1 -0
- package/dist/config/class-aliases.js.map +1 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js.map +1 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js.map +1 -0
- package/dist/graphql/loaders.d.ts.map +1 -0
- package/dist/graphql/loaders.js.map +1 -0
- package/dist/graphql/resolvers.d.ts.map +1 -0
- package/dist/graphql/resolvers.js +29 -29
- package/dist/graphql/resolvers.js.map +1 -0
- package/dist/graphql/schema.d.ts.map +1 -0
- package/dist/graphql/schema.js.map +1 -0
- package/dist/graphql/server.d.ts.map +1 -0
- package/dist/graphql/server.js.map +1 -0
- package/dist/graphql/types.d.ts.map +1 -0
- package/dist/graphql/types.js.map +1 -0
- package/dist/handlers/resource-handlers.d.ts.map +1 -0
- package/dist/handlers/resource-handlers.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +64 -7
- package/dist/index.js.map +1 -0
- package/dist/resources/actors.d.ts.map +1 -0
- package/dist/resources/actors.js.map +1 -0
- package/dist/resources/assets.d.ts.map +1 -0
- package/dist/resources/assets.js +6 -4
- package/dist/resources/assets.js.map +1 -0
- package/dist/resources/levels.d.ts.map +1 -0
- package/dist/resources/levels.js.map +1 -0
- package/dist/server/resource-registry.d.ts.map +1 -0
- package/dist/server/resource-registry.js.map +1 -0
- package/dist/server/tool-registry.d.ts.map +1 -0
- package/dist/server/tool-registry.js.map +1 -0
- package/dist/server-setup.d.ts.map +1 -0
- package/dist/server-setup.js.map +1 -0
- package/dist/services/health-monitor.d.ts.map +1 -0
- package/dist/services/health-monitor.js.map +1 -0
- package/dist/services/metrics-server.d.ts.map +1 -0
- package/dist/services/metrics-server.js.map +1 -0
- package/dist/tools/actors.d.ts.map +1 -0
- package/dist/tools/actors.js +3 -1
- package/dist/tools/actors.js.map +1 -0
- package/dist/tools/animation.d.ts.map +1 -0
- package/dist/tools/animation.js +2 -2
- package/dist/tools/animation.js.map +1 -0
- package/dist/tools/assets.d.ts.map +1 -0
- package/dist/tools/assets.js.map +1 -0
- package/dist/tools/audio.d.ts.map +1 -0
- package/dist/tools/audio.js.map +1 -0
- package/dist/tools/base-tool.d.ts.map +1 -0
- package/dist/tools/base-tool.js.map +1 -0
- package/dist/tools/behavior-tree.d.ts.map +1 -0
- package/dist/tools/behavior-tree.js.map +1 -0
- package/dist/tools/blueprint.d.ts.map +1 -0
- package/dist/tools/blueprint.js +4 -2
- package/dist/tools/blueprint.js.map +1 -0
- package/dist/tools/consolidated-tool-definitions.d.ts.map +1 -0
- package/dist/tools/consolidated-tool-definitions.js.map +1 -0
- package/dist/tools/consolidated-tool-handlers.d.ts.map +1 -0
- package/dist/tools/consolidated-tool-handlers.js.map +1 -0
- package/dist/tools/debug.d.ts.map +1 -0
- package/dist/tools/debug.js +3 -1
- package/dist/tools/debug.js.map +1 -0
- package/dist/tools/dynamic-handler-registry.d.ts.map +1 -0
- package/dist/tools/dynamic-handler-registry.js +3 -1
- package/dist/tools/dynamic-handler-registry.js.map +1 -0
- package/dist/tools/editor.d.ts.map +1 -0
- package/dist/tools/editor.js +1 -1
- package/dist/tools/editor.js.map +1 -0
- package/dist/tools/engine.d.ts.map +1 -0
- package/dist/tools/engine.js.map +1 -0
- package/dist/tools/environment.d.ts.map +1 -0
- package/dist/tools/environment.js +2 -2
- package/dist/tools/environment.js.map +1 -0
- package/dist/tools/foliage.d.ts.map +1 -0
- package/dist/tools/foliage.js.map +1 -0
- package/dist/tools/handlers/actor-handlers.d.ts +1 -1
- package/dist/tools/handlers/actor-handlers.d.ts.map +1 -0
- package/dist/tools/handlers/actor-handlers.js +6 -5
- package/dist/tools/handlers/actor-handlers.js.map +1 -0
- package/dist/tools/handlers/animation-handlers.d.ts.map +1 -0
- package/dist/tools/handlers/animation-handlers.js.map +1 -0
- package/dist/tools/handlers/argument-helper.d.ts.map +1 -0
- package/dist/tools/handlers/argument-helper.js.map +1 -0
- package/dist/tools/handlers/asset-handlers.d.ts.map +1 -0
- package/dist/tools/handlers/asset-handlers.js +5 -1
- package/dist/tools/handlers/asset-handlers.js.map +1 -0
- package/dist/tools/handlers/audio-handlers.d.ts.map +1 -0
- package/dist/tools/handlers/audio-handlers.js.map +1 -0
- package/dist/tools/handlers/blueprint-handlers.d.ts.map +1 -0
- package/dist/tools/handlers/blueprint-handlers.js +2 -1
- package/dist/tools/handlers/blueprint-handlers.js.map +1 -0
- package/dist/tools/handlers/common-handlers.d.ts.map +1 -0
- package/dist/tools/handlers/common-handlers.js.map +1 -0
- package/dist/tools/handlers/editor-handlers.d.ts.map +1 -0
- package/dist/tools/handlers/editor-handlers.js +12 -2
- package/dist/tools/handlers/editor-handlers.js.map +1 -0
- package/dist/tools/handlers/effect-handlers.d.ts.map +1 -0
- package/dist/tools/handlers/effect-handlers.js.map +1 -0
- package/dist/tools/handlers/environment-handlers.d.ts.map +1 -0
- package/dist/tools/handlers/environment-handlers.js.map +1 -0
- package/dist/tools/handlers/graph-handlers.d.ts.map +1 -0
- package/dist/tools/handlers/graph-handlers.js +61 -1
- package/dist/tools/handlers/graph-handlers.js.map +1 -0
- package/dist/tools/handlers/input-handlers.d.ts.map +1 -0
- package/dist/tools/handlers/input-handlers.js.map +1 -0
- package/dist/tools/handlers/inspect-handlers.d.ts.map +1 -0
- package/dist/tools/handlers/inspect-handlers.js.map +1 -0
- package/dist/tools/handlers/level-handlers.d.ts.map +1 -0
- package/dist/tools/handlers/level-handlers.js.map +1 -0
- package/dist/tools/handlers/lighting-handlers.d.ts.map +1 -0
- package/dist/tools/handlers/lighting-handlers.js +23 -1
- package/dist/tools/handlers/lighting-handlers.js.map +1 -0
- package/dist/tools/handlers/performance-handlers.d.ts.map +1 -0
- package/dist/tools/handlers/performance-handlers.js +15 -2
- package/dist/tools/handlers/performance-handlers.js.map +1 -0
- package/dist/tools/handlers/pipeline-handlers.d.ts.map +1 -0
- package/dist/tools/handlers/pipeline-handlers.js.map +1 -0
- package/dist/tools/handlers/sequence-handlers.d.ts.map +1 -0
- package/dist/tools/handlers/sequence-handlers.js.map +1 -0
- package/dist/tools/handlers/system-handlers.d.ts.map +1 -0
- package/dist/tools/handlers/system-handlers.js +16 -1
- package/dist/tools/handlers/system-handlers.js.map +1 -0
- package/dist/tools/input.d.ts.map +1 -0
- package/dist/tools/input.js +3 -1
- package/dist/tools/input.js.map +1 -0
- package/dist/tools/introspection.d.ts.map +1 -0
- package/dist/tools/introspection.js.map +1 -0
- package/dist/tools/landscape.d.ts.map +1 -0
- package/dist/tools/landscape.js +3 -1
- package/dist/tools/landscape.js.map +1 -0
- package/dist/tools/level.d.ts.map +1 -0
- package/dist/tools/level.js.map +1 -0
- package/dist/tools/lighting.d.ts.map +1 -0
- package/dist/tools/lighting.js +3 -1
- package/dist/tools/lighting.js.map +1 -0
- package/dist/tools/logs.d.ts.map +1 -0
- package/dist/tools/logs.js.map +1 -0
- package/dist/tools/materials.d.ts.map +1 -0
- package/dist/tools/materials.js +3 -1
- package/dist/tools/materials.js.map +1 -0
- package/dist/tools/niagara.d.ts.map +1 -0
- package/dist/tools/niagara.js +7 -5
- package/dist/tools/niagara.js.map +1 -0
- package/dist/tools/performance.d.ts.map +1 -0
- package/dist/tools/performance.js.map +1 -0
- package/dist/tools/physics.d.ts.map +1 -0
- package/dist/tools/physics.js +9 -7
- package/dist/tools/physics.js.map +1 -0
- package/dist/tools/property-dictionary.d.ts.map +1 -0
- package/dist/tools/property-dictionary.js.map +1 -0
- package/dist/tools/sequence.d.ts.map +1 -0
- package/dist/tools/sequence.js +3 -1
- package/dist/tools/sequence.js.map +1 -0
- package/dist/tools/tool-definition-utils.d.ts.map +1 -0
- package/dist/tools/tool-definition-utils.js.map +1 -0
- package/dist/tools/ui.d.ts.map +1 -0
- package/dist/tools/ui.js +3 -1
- package/dist/tools/ui.js.map +1 -0
- package/dist/types/automation-responses.d.ts.map +1 -0
- package/dist/types/automation-responses.js.map +1 -0
- package/dist/types/env.d.ts.map +1 -0
- package/dist/types/env.js.map +1 -0
- package/dist/types/handler-types.d.ts.map +1 -0
- package/dist/types/handler-types.js.map +1 -0
- package/dist/types/tool-interfaces.d.ts.map +1 -0
- package/dist/types/tool-interfaces.js.map +1 -0
- package/dist/types/tool-types.d.ts.map +1 -0
- package/dist/types/tool-types.js.map +1 -0
- package/dist/unreal-bridge.d.ts +1 -0
- package/dist/unreal-bridge.d.ts.map +1 -0
- package/dist/unreal-bridge.js +8 -0
- package/dist/unreal-bridge.js.map +1 -0
- package/dist/utils/command-validator.d.ts.map +1 -0
- package/dist/utils/command-validator.js.map +1 -0
- package/dist/utils/elicitation.d.ts.map +1 -0
- package/dist/utils/elicitation.js.map +1 -0
- package/dist/utils/error-handler.d.ts.map +1 -0
- package/dist/utils/error-handler.js.map +1 -0
- package/dist/utils/ini-reader.d.ts.map +1 -0
- package/dist/utils/ini-reader.js.map +1 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/normalize.d.ts.map +1 -0
- package/dist/utils/normalize.js.map +1 -0
- package/dist/utils/path-security.d.ts.map +1 -0
- package/dist/utils/path-security.js.map +1 -0
- package/dist/utils/response-factory.d.ts.map +1 -0
- package/dist/utils/response-factory.js +3 -1
- package/dist/utils/response-factory.js.map +1 -0
- package/dist/utils/response-validator.d.ts.map +1 -0
- package/dist/utils/response-validator.js.map +1 -0
- package/dist/utils/result-helpers.d.ts.map +1 -0
- package/dist/utils/result-helpers.js.map +1 -0
- package/dist/utils/safe-json.d.ts.map +1 -0
- package/dist/utils/safe-json.js.map +1 -0
- package/dist/utils/unreal-command-queue.d.ts.map +1 -0
- package/dist/utils/unreal-command-queue.js.map +1 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js.map +1 -0
- package/dist/wasm/index.d.ts.map +1 -0
- package/dist/wasm/index.js.map +1 -0
- package/package.json +12 -34
- package/server.json +2 -2
- package/.dockerignore +0 -57
- package/.env.example +0 -26
- package/.env.production +0 -61
- package/.eslintrc.json +0 -0
- package/.eslintrc.override.json +0 -8
- package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -94
- package/.github/ISSUE_TEMPLATE/config.yml +0 -8
- package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -56
- package/.github/copilot-instructions.md +0 -478
- package/.github/dependabot.yml +0 -19
- package/.github/labeler.yml +0 -24
- package/.github/labels.yml +0 -70
- package/.github/pull_request_template.md +0 -42
- package/.github/release-drafter-config.yml +0 -51
- package/.github/workflows/auto-merge.yml +0 -38
- package/.github/workflows/ci.yml +0 -38
- package/.github/workflows/dependency-review.yml +0 -17
- package/.github/workflows/gemini-issue-triage.yml +0 -172
- package/.github/workflows/greetings.yml +0 -27
- package/.github/workflows/labeler.yml +0 -17
- package/.github/workflows/links.yml +0 -80
- package/.github/workflows/pr-size-labeler.yml +0 -137
- package/.github/workflows/publish-mcp.yml +0 -79
- package/.github/workflows/release-drafter.yml +0 -24
- package/.github/workflows/release.yml +0 -112
- package/.github/workflows/semantic-pull-request.yml +0 -35
- package/.github/workflows/smoke-test.yml +0 -36
- package/.github/workflows/stale.yml +0 -28
- package/CONTRIBUTING.md +0 -140
- package/Dockerfile +0 -37
- package/GEMINI.md +0 -115
- package/Public/Plugin_setup_guide.mp4 +0 -0
- package/Public/icon.png +0 -0
- package/claude_desktop_config_example.json +0 -15
- package/dist/types/responses.d.ts +0 -249
- package/dist/types/responses.js +0 -2
- package/docs/GraphQL-API.md +0 -888
- package/docs/Migration-Guide-v0.5.0.md +0 -684
- package/docs/Roadmap.md +0 -53
- package/docs/WebAssembly-Integration.md +0 -628
- package/docs/editor-plugin-extension.md +0 -370
- package/docs/handler-mapping.md +0 -249
- package/docs/native-automation-progress.md +0 -128
- package/docs/testing-guide.md +0 -423
- package/eslint.config.mjs +0 -68
- package/mcp-config-example.json +0 -14
- package/plugins/McpAutomationBridge/Config/FilterPlugin.ini +0 -8
- package/plugins/McpAutomationBridge/McpAutomationBridge.uplugin +0 -64
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/McpAutomationBridge.Build.cs +0 -189
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.cpp +0 -22
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.h +0 -30
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeHelpers.h +0 -1983
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeModule.cpp +0 -72
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSettings.cpp +0 -46
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +0 -846
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +0 -2393
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetQueryHandlers.cpp +0 -300
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetWorkflowHandlers.cpp +0 -2807
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AudioHandlers.cpp +0 -1087
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BehaviorTreeHandlers.cpp +0 -488
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.cpp +0 -643
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.h +0 -31
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +0 -1094
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +0 -5750
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers_List.cpp +0 -152
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ControlHandlers.cpp +0 -2614
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_DebugHandlers.cpp +0 -42
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EditorFunctionHandlers.cpp +0 -1237
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +0 -1725
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +0 -2265
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_FoliageHandlers.cpp +0 -954
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InputHandlers.cpp +0 -209
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InsightsHandlers.cpp +0 -41
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LandscapeHandlers.cpp +0 -1164
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LevelHandlers.cpp +0 -762
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +0 -663
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LogHandlers.cpp +0 -136
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_MaterialGraphHandlers.cpp +0 -494
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraGraphHandlers.cpp +0 -278
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraHandlers.cpp +0 -625
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PerformanceHandlers.cpp +0 -401
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PipelineHandlers.cpp +0 -67
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +0 -472
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PropertyHandlers.cpp +0 -2634
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_RenderHandlers.cpp +0 -189
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.cpp +0 -917
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.h +0 -39
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +0 -2706
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequencerHandlers.cpp +0 -519
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_TestHandlers.cpp +0 -38
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_UiHandlers.cpp +0 -668
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_WorldPartitionHandlers.cpp +0 -346
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +0 -1345
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.h +0 -149
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +0 -782
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSettings.h +0 -115
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSubsystem.h +0 -796
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpConnectionManager.h +0 -117
- package/scripts/check-unreal-connection.mjs +0 -19
- package/scripts/clean-tmp.js +0 -23
- package/scripts/patch-wasm.js +0 -26
- package/scripts/run-all-tests.mjs +0 -136
- package/scripts/smoke-test.ts +0 -94
- package/scripts/sync-mcp-plugin.js +0 -143
- package/scripts/test-no-plugin-alternates.mjs +0 -113
- package/scripts/validate-server.js +0 -46
- package/scripts/verify-automation-bridge.js +0 -200
- package/src/automation/bridge.ts +0 -630
- package/src/automation/connection-manager.ts +0 -148
- package/src/automation/handshake.ts +0 -99
- package/src/automation/index.ts +0 -2
- package/src/automation/message-handler.ts +0 -192
- package/src/automation/request-tracker.ts +0 -155
- package/src/automation/types.ts +0 -108
- package/src/cli.ts +0 -34
- package/src/config/class-aliases.ts +0 -65
- package/src/config.ts +0 -73
- package/src/constants.ts +0 -29
- package/src/graphql/loaders.ts +0 -244
- package/src/graphql/resolvers.ts +0 -1008
- package/src/graphql/schema.ts +0 -452
- package/src/graphql/server.ts +0 -156
- package/src/graphql/types.ts +0 -10
- package/src/handlers/resource-handlers.ts +0 -186
- package/src/index.ts +0 -243
- package/src/resources/actors.ts +0 -127
- package/src/resources/assets.ts +0 -286
- package/src/resources/levels.ts +0 -68
- package/src/server/resource-registry.ts +0 -47
- package/src/server/tool-registry.ts +0 -354
- package/src/server-setup.ts +0 -114
- package/src/services/health-monitor.ts +0 -132
- package/src/services/metrics-server.ts +0 -176
- package/src/tools/actors.ts +0 -564
- package/src/tools/animation.ts +0 -941
- package/src/tools/assets.ts +0 -394
- package/src/tools/audio.ts +0 -499
- package/src/tools/base-tool.ts +0 -52
- package/src/tools/behavior-tree.ts +0 -45
- package/src/tools/blueprint.ts +0 -940
- package/src/tools/consolidated-tool-definitions.ts +0 -1256
- package/src/tools/consolidated-tool-handlers.ts +0 -302
- package/src/tools/debug.ts +0 -622
- package/src/tools/dynamic-handler-registry.ts +0 -33
- package/src/tools/editor.ts +0 -435
- package/src/tools/engine.ts +0 -43
- package/src/tools/environment.ts +0 -281
- package/src/tools/foliage.ts +0 -596
- package/src/tools/handlers/actor-handlers.ts +0 -244
- package/src/tools/handlers/animation-handlers.ts +0 -237
- package/src/tools/handlers/argument-helper.ts +0 -142
- package/src/tools/handlers/asset-handlers.ts +0 -550
- package/src/tools/handlers/audio-handlers.ts +0 -194
- package/src/tools/handlers/blueprint-handlers.ts +0 -380
- package/src/tools/handlers/common-handlers.ts +0 -108
- package/src/tools/handlers/editor-handlers.ts +0 -124
- package/src/tools/handlers/effect-handlers.ts +0 -224
- package/src/tools/handlers/environment-handlers.ts +0 -183
- package/src/tools/handlers/graph-handlers.ts +0 -117
- package/src/tools/handlers/input-handlers.ts +0 -28
- package/src/tools/handlers/inspect-handlers.ts +0 -450
- package/src/tools/handlers/level-handlers.ts +0 -253
- package/src/tools/handlers/lighting-handlers.ts +0 -151
- package/src/tools/handlers/performance-handlers.ts +0 -132
- package/src/tools/handlers/pipeline-handlers.ts +0 -194
- package/src/tools/handlers/sequence-handlers.ts +0 -438
- package/src/tools/handlers/system-handlers.ts +0 -564
- package/src/tools/input.ts +0 -160
- package/src/tools/introspection.ts +0 -689
- package/src/tools/landscape.ts +0 -649
- package/src/tools/level.ts +0 -989
- package/src/tools/lighting.ts +0 -1052
- package/src/tools/logs.ts +0 -219
- package/src/tools/materials.ts +0 -295
- package/src/tools/niagara.ts +0 -485
- package/src/tools/performance.ts +0 -661
- package/src/tools/physics.ts +0 -679
- package/src/tools/property-dictionary.ts +0 -98
- package/src/tools/sequence.ts +0 -385
- package/src/tools/tool-definition-utils.ts +0 -35
- package/src/tools/ui.ts +0 -452
- package/src/types/automation-responses.ts +0 -119
- package/src/types/env.ts +0 -17
- package/src/types/handler-types.ts +0 -442
- package/src/types/responses.ts +0 -355
- package/src/types/tool-interfaces.ts +0 -250
- package/src/types/tool-types.ts +0 -575
- package/src/unreal-bridge.ts +0 -693
- package/src/utils/command-validator.ts +0 -139
- package/src/utils/elicitation.ts +0 -132
- package/src/utils/error-handler.ts +0 -287
- package/src/utils/ini-reader.ts +0 -86
- package/src/utils/logger.ts +0 -35
- package/src/utils/normalize.test.ts +0 -162
- package/src/utils/normalize.ts +0 -146
- package/src/utils/path-security.ts +0 -43
- package/src/utils/response-factory.ts +0 -44
- package/src/utils/response-validator.ts +0 -395
- package/src/utils/result-helpers.ts +0 -195
- package/src/utils/safe-json.test.ts +0 -90
- package/src/utils/safe-json.ts +0 -70
- package/src/utils/unreal-command-queue.ts +0 -166
- package/src/utils/validation.test.ts +0 -184
- package/src/utils/validation.ts +0 -312
- package/src/wasm/index.ts +0 -838
- package/test-server.mjs +0 -100
- package/tests/test-animation.mjs +0 -369
- package/tests/test-asset-advanced.mjs +0 -82
- package/tests/test-asset-graph.mjs +0 -311
- package/tests/test-audio.mjs +0 -417
- package/tests/test-automation-timeouts.mjs +0 -98
- package/tests/test-behavior-tree.mjs +0 -444
- package/tests/test-blueprint-graph.mjs +0 -410
- package/tests/test-blueprint.mjs +0 -577
- package/tests/test-client-mode.mjs +0 -86
- package/tests/test-console-command.mjs +0 -56
- package/tests/test-control-actor.mjs +0 -425
- package/tests/test-control-editor.mjs +0 -112
- package/tests/test-graphql.mjs +0 -372
- package/tests/test-input.mjs +0 -349
- package/tests/test-inspect.mjs +0 -302
- package/tests/test-landscape.mjs +0 -316
- package/tests/test-lighting.mjs +0 -428
- package/tests/test-manage-asset.mjs +0 -438
- package/tests/test-manage-level.mjs +0 -89
- package/tests/test-materials.mjs +0 -356
- package/tests/test-niagara.mjs +0 -185
- package/tests/test-no-inline-python.mjs +0 -122
- package/tests/test-performance.mjs +0 -539
- package/tests/test-plugin-handshake.mjs +0 -82
- package/tests/test-runner.mjs +0 -993
- package/tests/test-sequence.mjs +0 -104
- package/tests/test-system.mjs +0 -96
- package/tests/test-wasm.mjs +0 -283
- package/tests/test-world-partition.mjs +0 -215
- package/tsconfig.json +0 -56
- package/vitest.config.ts +0 -35
- package/wasm/Cargo.lock +0 -363
- package/wasm/Cargo.toml +0 -42
- package/wasm/LICENSE +0 -21
- package/wasm/README.md +0 -253
- package/wasm/src/dependency_resolver.rs +0 -377
- package/wasm/src/lib.rs +0 -153
- package/wasm/src/property_parser.rs +0 -271
- package/wasm/src/transform_math.rs +0 -396
- package/wasm/tests/integration.rs +0 -109
|
@@ -1,2706 +0,0 @@
|
|
|
1
|
-
#include "LevelSequence.h"
|
|
2
|
-
#include "McpAutomationBridgeGlobals.h"
|
|
3
|
-
#include "McpAutomationBridgeHelpers.h"
|
|
4
|
-
#include "McpAutomationBridgeSubsystem.h"
|
|
5
|
-
#include "MovieScene.h"
|
|
6
|
-
#include "MovieSceneBinding.h"
|
|
7
|
-
#include "MovieSceneSection.h"
|
|
8
|
-
#include "MovieSceneSequence.h"
|
|
9
|
-
#include "MovieSceneTrack.h"
|
|
10
|
-
#include "UObject/UObjectIterator.h"
|
|
11
|
-
|
|
12
|
-
#if WITH_EDITOR
|
|
13
|
-
#include "Editor.h"
|
|
14
|
-
#include "EditorAssetLibrary.h"
|
|
15
|
-
#if __has_include("Subsystems/EditorActorSubsystem.h")
|
|
16
|
-
#include "Subsystems/EditorActorSubsystem.h"
|
|
17
|
-
#define MCP_HAS_EDITOR_ACTOR_SUBSYSTEM 1
|
|
18
|
-
#elif __has_include("EditorActorSubsystem.h")
|
|
19
|
-
#include "EditorActorSubsystem.h"
|
|
20
|
-
#define MCP_HAS_EDITOR_ACTOR_SUBSYSTEM 1
|
|
21
|
-
#else
|
|
22
|
-
#define MCP_HAS_EDITOR_ACTOR_SUBSYSTEM 0
|
|
23
|
-
#endif
|
|
24
|
-
|
|
25
|
-
#include "AssetRegistry/AssetRegistryModule.h"
|
|
26
|
-
#include "AssetToolsModule.h"
|
|
27
|
-
#include "Editor/EditorEngine.h"
|
|
28
|
-
#include "Engine/Selection.h"
|
|
29
|
-
#include "IAssetTools.h"
|
|
30
|
-
#include "LevelSequenceEditorBlueprintLibrary.h"
|
|
31
|
-
#include "Subsystems/AssetEditorSubsystem.h"
|
|
32
|
-
|
|
33
|
-
// Header checks removed causing issues with private headers
|
|
34
|
-
|
|
35
|
-
#if __has_include("LevelSequenceEditorSubsystem.h")
|
|
36
|
-
#include "LevelSequenceEditorSubsystem.h"
|
|
37
|
-
#define MCP_HAS_LEVELSEQUENCE_EDITOR_SUBSYSTEM 1
|
|
38
|
-
#else
|
|
39
|
-
#define MCP_HAS_LEVELSEQUENCE_EDITOR_SUBSYSTEM 0
|
|
40
|
-
#endif
|
|
41
|
-
|
|
42
|
-
#if __has_include("ILevelSequenceEditorToolkit.h")
|
|
43
|
-
#include "ILevelSequenceEditorToolkit.h"
|
|
44
|
-
#endif
|
|
45
|
-
|
|
46
|
-
#if __has_include("ISequencer.h")
|
|
47
|
-
#include "ISequencer.h"
|
|
48
|
-
#include "MovieSceneSequencePlayer.h"
|
|
49
|
-
#endif
|
|
50
|
-
|
|
51
|
-
#if __has_include("Tracks/MovieSceneFloatTrack.h")
|
|
52
|
-
#include "Sections/MovieSceneFloatSection.h"
|
|
53
|
-
#include "Tracks/MovieSceneFloatTrack.h"
|
|
54
|
-
|
|
55
|
-
#endif
|
|
56
|
-
|
|
57
|
-
#if __has_include("Tracks/MovieSceneBoolTrack.h")
|
|
58
|
-
#include "Sections/MovieSceneBoolSection.h"
|
|
59
|
-
#include "Tracks/MovieSceneBoolTrack.h"
|
|
60
|
-
|
|
61
|
-
#endif
|
|
62
|
-
|
|
63
|
-
#if __has_include("Tracks/MovieScene3DTransformTrack.h")
|
|
64
|
-
#include "Tracks/MovieScene3DTransformTrack.h"
|
|
65
|
-
#endif
|
|
66
|
-
|
|
67
|
-
#include "Tracks/MovieSceneAudioTrack.h"
|
|
68
|
-
#include "Tracks/MovieSceneEventTrack.h"
|
|
69
|
-
|
|
70
|
-
#if __has_include("Sections/MovieScene3DTransformSection.h")
|
|
71
|
-
#include "Sections/MovieScene3DTransformSection.h"
|
|
72
|
-
#endif
|
|
73
|
-
#if __has_include("Channels/MovieSceneDoubleChannel.h")
|
|
74
|
-
#include "Channels/MovieSceneDoubleChannel.h"
|
|
75
|
-
#endif
|
|
76
|
-
#if __has_include("Channels/MovieSceneChannelProxy.h")
|
|
77
|
-
#include "Channels/MovieSceneChannelProxy.h"
|
|
78
|
-
#endif
|
|
79
|
-
|
|
80
|
-
// Optional components check
|
|
81
|
-
#if __has_include("Misc/ScopedTransaction.h")
|
|
82
|
-
#include "Misc/ScopedTransaction.h"
|
|
83
|
-
#endif
|
|
84
|
-
#if __has_include("Camera/CameraActor.h")
|
|
85
|
-
#include "Camera/CameraActor.h"
|
|
86
|
-
#endif
|
|
87
|
-
#endif
|
|
88
|
-
|
|
89
|
-
FString UMcpAutomationBridgeSubsystem::ResolveSequencePath(
|
|
90
|
-
const TSharedPtr<FJsonObject> &Payload) {
|
|
91
|
-
FString Path;
|
|
92
|
-
if (Payload.IsValid() && Payload->TryGetStringField(TEXT("path"), Path) &&
|
|
93
|
-
!Path.IsEmpty()) {
|
|
94
|
-
#if WITH_EDITOR
|
|
95
|
-
// Check existence first to avoid error log spam
|
|
96
|
-
if (UEditorAssetLibrary::DoesAssetExist(Path)) {
|
|
97
|
-
UObject *Obj = UEditorAssetLibrary::LoadAsset(Path);
|
|
98
|
-
if (Obj) {
|
|
99
|
-
return Obj->GetPathName();
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
#endif
|
|
103
|
-
return Path;
|
|
104
|
-
}
|
|
105
|
-
if (!GCurrentSequencePath.IsEmpty())
|
|
106
|
-
return GCurrentSequencePath;
|
|
107
|
-
return FString();
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
TSharedPtr<FJsonObject>
|
|
111
|
-
UMcpAutomationBridgeSubsystem::EnsureSequenceEntry(const FString &SeqPath) {
|
|
112
|
-
if (SeqPath.IsEmpty())
|
|
113
|
-
return nullptr;
|
|
114
|
-
if (TSharedPtr<FJsonObject> *Found = GSequenceRegistry.Find(SeqPath))
|
|
115
|
-
return *Found;
|
|
116
|
-
TSharedPtr<FJsonObject> NewObj = MakeShared<FJsonObject>();
|
|
117
|
-
NewObj->SetStringField(TEXT("sequencePath"), SeqPath);
|
|
118
|
-
GSequenceRegistry.Add(SeqPath, NewObj);
|
|
119
|
-
return NewObj;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
bool UMcpAutomationBridgeSubsystem::HandleSequenceCreate(
|
|
123
|
-
const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
|
|
124
|
-
TSharedPtr<FMcpBridgeWebSocket> Socket) {
|
|
125
|
-
#if WITH_EDITOR
|
|
126
|
-
TSharedPtr<FJsonObject> LocalPayload =
|
|
127
|
-
Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
|
|
128
|
-
FString Name;
|
|
129
|
-
LocalPayload->TryGetStringField(TEXT("name"), Name);
|
|
130
|
-
FString Path;
|
|
131
|
-
LocalPayload->TryGetStringField(TEXT("path"), Path);
|
|
132
|
-
if (Name.IsEmpty()) {
|
|
133
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
134
|
-
TEXT("sequence_create requires name"), nullptr,
|
|
135
|
-
TEXT("INVALID_ARGUMENT"));
|
|
136
|
-
return true;
|
|
137
|
-
}
|
|
138
|
-
FString FullPath = Path.IsEmpty()
|
|
139
|
-
? FString::Printf(TEXT("/Game/%s"), *Name)
|
|
140
|
-
: FString::Printf(TEXT("%s/%s"), *Path, *Name);
|
|
141
|
-
|
|
142
|
-
FString DestFolder = Path.IsEmpty() ? TEXT("/Game") : Path;
|
|
143
|
-
if (DestFolder.StartsWith(TEXT("/Content"), ESearchCase::IgnoreCase)) {
|
|
144
|
-
DestFolder = FString::Printf(TEXT("/Game%s"), *DestFolder.RightChop(8));
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
TWeakObjectPtr<UMcpAutomationBridgeSubsystem> WeakSubsystem(this);
|
|
148
|
-
FString RequestIdArg = RequestId;
|
|
149
|
-
|
|
150
|
-
// Execute on Game Thread
|
|
151
|
-
UMcpAutomationBridgeSubsystem *Subsystem = this;
|
|
152
|
-
UE_LOG(LogMcpAutomationBridgeSubsystem, Warning,
|
|
153
|
-
TEXT("HandleSequenceCreate: Handing RequestID=%s Path=%s"),
|
|
154
|
-
*RequestIdArg, *FullPath);
|
|
155
|
-
|
|
156
|
-
// Check existence first to avoid error log spam
|
|
157
|
-
if (UEditorAssetLibrary::DoesAssetExist(FullPath)) {
|
|
158
|
-
TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
|
|
159
|
-
Resp->SetStringField(TEXT("sequencePath"), FullPath);
|
|
160
|
-
UE_LOG(LogMcpAutomationBridgeSubsystem, Warning,
|
|
161
|
-
TEXT("HandleSequenceCreate: Sequence exists, sending response for "
|
|
162
|
-
"RequestID=%s"),
|
|
163
|
-
*RequestIdArg);
|
|
164
|
-
Subsystem->SendAutomationResponse(Socket, RequestIdArg, true,
|
|
165
|
-
TEXT("Sequence already exists"), Resp,
|
|
166
|
-
FString());
|
|
167
|
-
return true;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Dynamic factory lookup
|
|
171
|
-
UClass *FactoryClass = FindObject<UClass>(
|
|
172
|
-
nullptr, TEXT("/Script/LevelSequenceEditor.LevelSequenceFactoryNew"));
|
|
173
|
-
if (!FactoryClass)
|
|
174
|
-
FactoryClass = LoadClass<UClass>(
|
|
175
|
-
nullptr, TEXT("/Script/LevelSequenceEditor.LevelSequenceFactoryNew"));
|
|
176
|
-
|
|
177
|
-
if (FactoryClass) {
|
|
178
|
-
UFactory *Factory =
|
|
179
|
-
NewObject<UFactory>(GetTransientPackage(), FactoryClass);
|
|
180
|
-
FAssetToolsModule &AssetToolsModule =
|
|
181
|
-
FModuleManager::LoadModuleChecked<FAssetToolsModule>(
|
|
182
|
-
TEXT("AssetTools"));
|
|
183
|
-
UObject *NewObj = AssetToolsModule.Get().CreateAsset(
|
|
184
|
-
Name, DestFolder, ULevelSequence::StaticClass(), Factory);
|
|
185
|
-
if (NewObj) {
|
|
186
|
-
UEditorAssetLibrary::SaveAsset(FullPath);
|
|
187
|
-
GCurrentSequencePath = FullPath;
|
|
188
|
-
TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
|
|
189
|
-
Resp->SetStringField(TEXT("sequencePath"), FullPath);
|
|
190
|
-
UE_LOG(LogMcpAutomationBridgeSubsystem, Warning,
|
|
191
|
-
TEXT("HandleSequenceCreate: Created sequence, sending response "
|
|
192
|
-
"for RequestID=%s"),
|
|
193
|
-
*RequestIdArg);
|
|
194
|
-
Subsystem->SendAutomationResponse(Socket, RequestIdArg, true,
|
|
195
|
-
TEXT("Sequence created"), Resp,
|
|
196
|
-
FString());
|
|
197
|
-
} else {
|
|
198
|
-
UE_LOG(
|
|
199
|
-
LogMcpAutomationBridgeSubsystem, Error,
|
|
200
|
-
TEXT("HandleSequenceCreate: Failed to create asset for RequestID=%s"),
|
|
201
|
-
*RequestIdArg);
|
|
202
|
-
Subsystem->SendAutomationResponse(Socket, RequestIdArg, false,
|
|
203
|
-
TEXT("Failed to create sequence asset"),
|
|
204
|
-
nullptr, TEXT("CREATE_ASSET_FAILED"));
|
|
205
|
-
}
|
|
206
|
-
} else {
|
|
207
|
-
UE_LOG(LogMcpAutomationBridgeSubsystem, Error,
|
|
208
|
-
TEXT("HandleSequenceCreate: Factory not found for RequestID=%s"),
|
|
209
|
-
*RequestIdArg);
|
|
210
|
-
Subsystem->SendAutomationResponse(
|
|
211
|
-
Socket, RequestIdArg, false,
|
|
212
|
-
TEXT("LevelSequenceFactoryNew class not found (Module not loaded?)"),
|
|
213
|
-
nullptr, TEXT("FACTORY_NOT_AVAILABLE"));
|
|
214
|
-
}
|
|
215
|
-
return true;
|
|
216
|
-
return true;
|
|
217
|
-
|
|
218
|
-
#else
|
|
219
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
220
|
-
TEXT("sequence_create requires editor build"), nullptr,
|
|
221
|
-
TEXT("NOT_AVAILABLE"));
|
|
222
|
-
return true;
|
|
223
|
-
#endif
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
bool UMcpAutomationBridgeSubsystem::HandleSequenceSetDisplayRate(
|
|
227
|
-
const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
|
|
228
|
-
TSharedPtr<FMcpBridgeWebSocket> Socket) {
|
|
229
|
-
#if WITH_EDITOR
|
|
230
|
-
TSharedPtr<FJsonObject> LocalPayload =
|
|
231
|
-
Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
|
|
232
|
-
FString SeqPath = ResolveSequencePath(LocalPayload);
|
|
233
|
-
if (SeqPath.IsEmpty()) {
|
|
234
|
-
SendAutomationResponse(
|
|
235
|
-
Socket, RequestId, false,
|
|
236
|
-
TEXT("sequence_set_display_rate requires a sequence path"), nullptr,
|
|
237
|
-
TEXT("INVALID_SEQUENCE"));
|
|
238
|
-
return true;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
UObject *SeqObj = UEditorAssetLibrary::LoadAsset(SeqPath);
|
|
242
|
-
if (!SeqObj) {
|
|
243
|
-
SendAutomationResponse(Socket, RequestId, false, TEXT("Sequence not found"),
|
|
244
|
-
nullptr, TEXT("INVALID_SEQUENCE"));
|
|
245
|
-
return true;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (ULevelSequence *LevelSeq = Cast<ULevelSequence>(SeqObj)) {
|
|
249
|
-
if (UMovieScene *MovieScene = LevelSeq->GetMovieScene()) {
|
|
250
|
-
FString FrameRateStr;
|
|
251
|
-
double FrameRateVal = 0.0;
|
|
252
|
-
|
|
253
|
-
FFrameRate NewRate;
|
|
254
|
-
bool bRateFound = false;
|
|
255
|
-
|
|
256
|
-
if (LocalPayload->TryGetStringField(TEXT("frameRate"), FrameRateStr)) {
|
|
257
|
-
// Parse "30fps", "24000/1001", etc.
|
|
258
|
-
// Simple parsing for standard rates
|
|
259
|
-
if (FrameRateStr.EndsWith(TEXT("fps"))) {
|
|
260
|
-
FrameRateStr.RemoveFromEnd(TEXT("fps"));
|
|
261
|
-
NewRate = FFrameRate(FCString::Atoi(*FrameRateStr), 1);
|
|
262
|
-
bRateFound = true;
|
|
263
|
-
} else if (FrameRateStr.Contains(TEXT("/"))) {
|
|
264
|
-
// Rational
|
|
265
|
-
FString NumStr, DenomStr;
|
|
266
|
-
if (FrameRateStr.Split(TEXT("/"), &NumStr, &DenomStr)) {
|
|
267
|
-
NewRate =
|
|
268
|
-
FFrameRate(FCString::Atoi(*NumStr), FCString::Atoi(*DenomStr));
|
|
269
|
-
bRateFound = true;
|
|
270
|
-
}
|
|
271
|
-
} else {
|
|
272
|
-
// Decimal string?
|
|
273
|
-
if (FrameRateStr.IsNumeric()) {
|
|
274
|
-
NewRate = FFrameRate(FCString::Atoi(*FrameRateStr), 1);
|
|
275
|
-
bRateFound = true;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
} else if (LocalPayload->TryGetNumberField(TEXT("frameRate"),
|
|
279
|
-
FrameRateVal)) {
|
|
280
|
-
NewRate = FFrameRate(FMath::RoundToInt(FrameRateVal), 1);
|
|
281
|
-
bRateFound = true;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
if (bRateFound) {
|
|
285
|
-
MovieScene->SetDisplayRate(NewRate);
|
|
286
|
-
MovieScene->Modify();
|
|
287
|
-
|
|
288
|
-
TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
|
|
289
|
-
Resp->SetBoolField(TEXT("success"), true);
|
|
290
|
-
Resp->SetStringField(TEXT("displayRate"),
|
|
291
|
-
NewRate.ToPrettyText().ToString());
|
|
292
|
-
SendAutomationResponse(Socket, RequestId, true,
|
|
293
|
-
TEXT("Display rate set"), Resp, FString());
|
|
294
|
-
return true;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
298
|
-
TEXT("Invalid frameRate format"), nullptr,
|
|
299
|
-
TEXT("INVALID_ARGUMENT"));
|
|
300
|
-
return true;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
305
|
-
TEXT("Invalid sequence type"), nullptr,
|
|
306
|
-
TEXT("INVALID_SEQUENCE"));
|
|
307
|
-
return true;
|
|
308
|
-
#else
|
|
309
|
-
SendAutomationResponse(
|
|
310
|
-
Socket, RequestId, false,
|
|
311
|
-
TEXT("sequence_set_display_rate requires editor build"), nullptr,
|
|
312
|
-
TEXT("NOT_IMPLEMENTED"));
|
|
313
|
-
return true;
|
|
314
|
-
#endif
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
bool UMcpAutomationBridgeSubsystem::HandleSequenceSetProperties(
|
|
318
|
-
const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
|
|
319
|
-
TSharedPtr<FMcpBridgeWebSocket> Socket) {
|
|
320
|
-
TSharedPtr<FJsonObject> LocalPayload =
|
|
321
|
-
Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
|
|
322
|
-
FString SeqPath = ResolveSequencePath(LocalPayload);
|
|
323
|
-
if (SeqPath.IsEmpty()) {
|
|
324
|
-
SendAutomationResponse(
|
|
325
|
-
Socket, RequestId, false,
|
|
326
|
-
TEXT("sequence_set_properties requires a sequence path"), nullptr,
|
|
327
|
-
TEXT("INVALID_SEQUENCE"));
|
|
328
|
-
return true;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
#if WITH_EDITOR
|
|
332
|
-
TWeakObjectPtr<UMcpAutomationBridgeSubsystem> WeakSubsystem(this);
|
|
333
|
-
FString RequestIdArg = RequestId;
|
|
334
|
-
|
|
335
|
-
// Capture simple types. For JsonObject, we need to capture the data, not the
|
|
336
|
-
// pointer if we want to be safe, but since we parsed it above, we should
|
|
337
|
-
// capture the parsed values. Parsing logic happens above. We'll capture the
|
|
338
|
-
// parsed variables. But wait, the parsing logic in the original code is
|
|
339
|
-
// INSIDE the block I'm replacing (lines 176-185). I need to include the
|
|
340
|
-
// parsing inside the Async task or move it out. I'll move the parsing INSIDE
|
|
341
|
-
// the Async task, but I need to capture LocalPayload. LocalPayload is a
|
|
342
|
-
// SharedPtr, so it's safe to capture.
|
|
343
|
-
|
|
344
|
-
UMcpAutomationBridgeSubsystem *Subsystem = this;
|
|
345
|
-
TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
|
|
346
|
-
UObject *SeqObj = UEditorAssetLibrary::LoadAsset(SeqPath);
|
|
347
|
-
if (!SeqObj) {
|
|
348
|
-
Subsystem->SendAutomationResponse(Socket, RequestIdArg, false,
|
|
349
|
-
TEXT("Sequence not found"), nullptr,
|
|
350
|
-
TEXT("INVALID_SEQUENCE"));
|
|
351
|
-
return true;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
if (ULevelSequence *LevelSeq = Cast<ULevelSequence>(SeqObj)) {
|
|
355
|
-
if (UMovieScene *MovieScene = LevelSeq->GetMovieScene()) {
|
|
356
|
-
bool bModified = false;
|
|
357
|
-
double FrameRateValue = 0.0;
|
|
358
|
-
double LengthInFramesValue = 0.0;
|
|
359
|
-
double PlaybackStartValue = 0.0;
|
|
360
|
-
double PlaybackEndValue = 0.0;
|
|
361
|
-
|
|
362
|
-
const bool bHasFrameRate =
|
|
363
|
-
LocalPayload->TryGetNumberField(TEXT("frameRate"), FrameRateValue);
|
|
364
|
-
const bool bHasLengthInFrames = LocalPayload->TryGetNumberField(
|
|
365
|
-
TEXT("lengthInFrames"), LengthInFramesValue);
|
|
366
|
-
const bool bHasPlaybackStart = LocalPayload->TryGetNumberField(
|
|
367
|
-
TEXT("playbackStart"), PlaybackStartValue);
|
|
368
|
-
const bool bHasPlaybackEnd = LocalPayload->TryGetNumberField(
|
|
369
|
-
TEXT("playbackEnd"), PlaybackEndValue);
|
|
370
|
-
|
|
371
|
-
if (bHasFrameRate) {
|
|
372
|
-
if (FrameRateValue <= 0.0) {
|
|
373
|
-
Subsystem->SendAutomationResponse(Socket, RequestIdArg, false,
|
|
374
|
-
TEXT("frameRate must be > 0"),
|
|
375
|
-
nullptr, TEXT("INVALID_ARGUMENT"));
|
|
376
|
-
return true;
|
|
377
|
-
}
|
|
378
|
-
const int32 Rounded =
|
|
379
|
-
FMath::Clamp<int32>(FMath::RoundToInt(FrameRateValue), 1, 960);
|
|
380
|
-
FFrameRate CurrentRate = MovieScene->GetDisplayRate();
|
|
381
|
-
FFrameRate NewRate(Rounded, 1);
|
|
382
|
-
if (NewRate != CurrentRate) {
|
|
383
|
-
MovieScene->SetDisplayRate(NewRate);
|
|
384
|
-
bModified = true;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
if (bHasPlaybackStart || bHasPlaybackEnd || bHasLengthInFrames) {
|
|
389
|
-
TRange<FFrameNumber> ExistingRange = MovieScene->GetPlaybackRange();
|
|
390
|
-
FFrameNumber StartFrame = ExistingRange.GetLowerBoundValue();
|
|
391
|
-
FFrameNumber EndFrame = ExistingRange.GetUpperBoundValue();
|
|
392
|
-
|
|
393
|
-
if (bHasPlaybackStart)
|
|
394
|
-
StartFrame = FFrameNumber(static_cast<int32>(PlaybackStartValue));
|
|
395
|
-
if (bHasPlaybackEnd)
|
|
396
|
-
EndFrame = FFrameNumber(static_cast<int32>(PlaybackEndValue));
|
|
397
|
-
else if (bHasLengthInFrames)
|
|
398
|
-
EndFrame =
|
|
399
|
-
StartFrame +
|
|
400
|
-
FMath::Max<int32>(0, static_cast<int32>(LengthInFramesValue));
|
|
401
|
-
|
|
402
|
-
if (EndFrame < StartFrame)
|
|
403
|
-
EndFrame = StartFrame;
|
|
404
|
-
MovieScene->SetPlaybackRange(
|
|
405
|
-
TRange<FFrameNumber>(StartFrame, EndFrame));
|
|
406
|
-
bModified = true;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
if (bModified)
|
|
410
|
-
MovieScene->Modify();
|
|
411
|
-
|
|
412
|
-
FFrameRate FR = MovieScene->GetDisplayRate();
|
|
413
|
-
TSharedPtr<FJsonObject> FrameRateObj = MakeShared<FJsonObject>();
|
|
414
|
-
FrameRateObj->SetNumberField(TEXT("numerator"), FR.Numerator);
|
|
415
|
-
FrameRateObj->SetNumberField(TEXT("denominator"), FR.Denominator);
|
|
416
|
-
Resp->SetObjectField(TEXT("frameRate"), FrameRateObj);
|
|
417
|
-
|
|
418
|
-
TRange<FFrameNumber> Range = MovieScene->GetPlaybackRange();
|
|
419
|
-
const double Start =
|
|
420
|
-
static_cast<double>(Range.GetLowerBoundValue().Value);
|
|
421
|
-
const double End = static_cast<double>(Range.GetUpperBoundValue().Value);
|
|
422
|
-
Resp->SetNumberField(TEXT("playbackStart"), Start);
|
|
423
|
-
Resp->SetNumberField(TEXT("playbackEnd"), End);
|
|
424
|
-
Resp->SetNumberField(TEXT("duration"), End - Start);
|
|
425
|
-
Resp->SetBoolField(TEXT("applied"), bModified);
|
|
426
|
-
|
|
427
|
-
Subsystem->SendAutomationResponse(Socket, RequestIdArg, true,
|
|
428
|
-
TEXT("properties updated"), Resp,
|
|
429
|
-
FString());
|
|
430
|
-
return true;
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
Resp->SetObjectField(TEXT("frameRate"), MakeShared<FJsonObject>());
|
|
434
|
-
Resp->SetNumberField(TEXT("playbackStart"), 0.0);
|
|
435
|
-
Resp->SetNumberField(TEXT("playbackEnd"), 0.0);
|
|
436
|
-
Resp->SetNumberField(TEXT("duration"), 0.0);
|
|
437
|
-
Resp->SetBoolField(TEXT("applied"), false);
|
|
438
|
-
Subsystem->SendAutomationResponse(
|
|
439
|
-
Socket, RequestIdArg, false,
|
|
440
|
-
TEXT("sequence_set_properties is not available in this editor build or "
|
|
441
|
-
"for this sequence type"),
|
|
442
|
-
Resp, TEXT("NOT_IMPLEMENTED"));
|
|
443
|
-
return true;
|
|
444
|
-
return true;
|
|
445
|
-
#else
|
|
446
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
447
|
-
TEXT("sequence_set_properties requires editor build."),
|
|
448
|
-
nullptr, TEXT("NOT_IMPLEMENTED"));
|
|
449
|
-
return true;
|
|
450
|
-
#endif
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
bool UMcpAutomationBridgeSubsystem::HandleSequenceOpen(
|
|
454
|
-
const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
|
|
455
|
-
TSharedPtr<FMcpBridgeWebSocket> Socket) {
|
|
456
|
-
TSharedPtr<FJsonObject> LocalPayload =
|
|
457
|
-
Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
|
|
458
|
-
FString SeqPath = ResolveSequencePath(LocalPayload);
|
|
459
|
-
if (SeqPath.IsEmpty()) {
|
|
460
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
461
|
-
TEXT("sequence_open requires a sequence path"),
|
|
462
|
-
nullptr, TEXT("INVALID_SEQUENCE"));
|
|
463
|
-
return true;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
#if WITH_EDITOR
|
|
467
|
-
TWeakObjectPtr<UMcpAutomationBridgeSubsystem> WeakSubsystem(this);
|
|
468
|
-
FString RequestIdArg = RequestId;
|
|
469
|
-
UMcpAutomationBridgeSubsystem *Subsystem = this;
|
|
470
|
-
UE_LOG(LogMcpAutomationBridgeSubsystem, Warning,
|
|
471
|
-
TEXT("HandleSequenceOpen: Opening sequence %s for RequestID=%s"),
|
|
472
|
-
*SeqPath, *RequestIdArg);
|
|
473
|
-
TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
|
|
474
|
-
UObject *SeqObj = UEditorAssetLibrary::LoadAsset(SeqPath);
|
|
475
|
-
if (!SeqObj) {
|
|
476
|
-
Subsystem->SendAutomationResponse(Socket, RequestIdArg, false,
|
|
477
|
-
TEXT("Sequence not found"), nullptr,
|
|
478
|
-
TEXT("INVALID_SEQUENCE"));
|
|
479
|
-
return true;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
if (ULevelSequence *LevelSeq = Cast<ULevelSequence>(SeqObj)) {
|
|
483
|
-
if (GEditor) {
|
|
484
|
-
if (ULevelSequenceEditorSubsystem *LSES =
|
|
485
|
-
GEditor->GetEditorSubsystem<ULevelSequenceEditorSubsystem>()) {
|
|
486
|
-
if (UAssetEditorSubsystem *AssetEditorSS =
|
|
487
|
-
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()) {
|
|
488
|
-
AssetEditorSS->OpenEditorForAsset(LevelSeq);
|
|
489
|
-
Resp->SetStringField(TEXT("sequencePath"), SeqPath);
|
|
490
|
-
Resp->SetStringField(TEXT("message"), TEXT("Sequence opened"));
|
|
491
|
-
UE_LOG(LogMcpAutomationBridgeSubsystem, Warning,
|
|
492
|
-
TEXT("HandleSequenceOpen: Successfully opened in LSES, "
|
|
493
|
-
"sending response for RequestID=%s"),
|
|
494
|
-
*RequestIdArg);
|
|
495
|
-
Subsystem->SendAutomationResponse(Socket, RequestIdArg, true,
|
|
496
|
-
TEXT("Sequence opened"), Resp,
|
|
497
|
-
FString());
|
|
498
|
-
return true;
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
if (GEditor) {
|
|
505
|
-
if (UAssetEditorSubsystem *AssetEditorSS =
|
|
506
|
-
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()) {
|
|
507
|
-
AssetEditorSS->OpenEditorForAsset(SeqObj);
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
Resp->SetStringField(TEXT("sequencePath"), SeqPath);
|
|
511
|
-
Resp->SetStringField(TEXT("message"), TEXT("Sequence opened (asset editor)"));
|
|
512
|
-
UE_LOG(LogMcpAutomationBridgeSubsystem, Warning,
|
|
513
|
-
TEXT("HandleSequenceOpen: Opened via AssetEditorSS, sending response "
|
|
514
|
-
"for RequestID=%s"),
|
|
515
|
-
*RequestIdArg);
|
|
516
|
-
Subsystem->SendAutomationResponse(Socket, RequestIdArg, true,
|
|
517
|
-
TEXT("Sequence opened"), Resp, FString());
|
|
518
|
-
return true;
|
|
519
|
-
return true;
|
|
520
|
-
#else
|
|
521
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
522
|
-
TEXT("sequence_open requires editor build."), nullptr,
|
|
523
|
-
TEXT("NOT_AVAILABLE"));
|
|
524
|
-
return true;
|
|
525
|
-
#endif
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
bool UMcpAutomationBridgeSubsystem::HandleSequenceAddCamera(
|
|
529
|
-
const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
|
|
530
|
-
TSharedPtr<FMcpBridgeWebSocket> Socket) {
|
|
531
|
-
TSharedPtr<FJsonObject> LocalPayload =
|
|
532
|
-
Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
|
|
533
|
-
FString SeqPath = ResolveSequencePath(LocalPayload);
|
|
534
|
-
if (SeqPath.IsEmpty()) {
|
|
535
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
536
|
-
TEXT("sequence_add_camera requires a sequence path"),
|
|
537
|
-
nullptr, TEXT("INVALID_SEQUENCE"));
|
|
538
|
-
return true;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
#if WITH_EDITOR
|
|
542
|
-
TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
|
|
543
|
-
UObject *SeqObj = UEditorAssetLibrary::LoadAsset(SeqPath);
|
|
544
|
-
if (!SeqObj) {
|
|
545
|
-
SendAutomationResponse(Socket, RequestId, false, TEXT("Sequence not found"),
|
|
546
|
-
nullptr, TEXT("INVALID_SEQUENCE"));
|
|
547
|
-
return true;
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
#if MCP_HAS_EDITOR_ACTOR_SUBSYSTEM
|
|
551
|
-
if (GEditor) {
|
|
552
|
-
UClass *CameraClass = ACameraActor::StaticClass();
|
|
553
|
-
AActor *Spawned = SpawnActorInActiveWorld<AActor>(
|
|
554
|
-
CameraClass, FVector::ZeroVector, FRotator::ZeroRotator,
|
|
555
|
-
TEXT("SequenceCamera"));
|
|
556
|
-
if (Spawned) {
|
|
557
|
-
// Fix for Issue #6: Auto-bind the camera to the sequence
|
|
558
|
-
if (ULevelSequence *LevelSeq = Cast<ULevelSequence>(SeqObj)) {
|
|
559
|
-
if (UMovieScene *MovieScene = LevelSeq->GetMovieScene()) {
|
|
560
|
-
FGuid BindingGuid = MovieScene->AddPossessable(
|
|
561
|
-
Spawned->GetActorLabel(), Spawned->GetClass());
|
|
562
|
-
if (MovieScene->FindPossessable(BindingGuid)) {
|
|
563
|
-
MovieScene->Modify();
|
|
564
|
-
Resp->SetStringField(TEXT("bindingGuid"), BindingGuid.ToString());
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
Resp->SetBoolField(TEXT("success"), true);
|
|
570
|
-
Resp->SetStringField(TEXT("actorLabel"), Spawned->GetActorLabel());
|
|
571
|
-
SendAutomationResponse(Socket, RequestId, true,
|
|
572
|
-
TEXT("Camera actor spawned and bound to sequence"),
|
|
573
|
-
Resp, FString());
|
|
574
|
-
return true;
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
SendAutomationResponse(Socket, RequestId, false, TEXT("Failed to add camera"),
|
|
578
|
-
nullptr, TEXT("ADD_CAMERA_FAILED"));
|
|
579
|
-
return true;
|
|
580
|
-
#else
|
|
581
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
582
|
-
TEXT("UEditorActorSubsystem not available"), nullptr,
|
|
583
|
-
TEXT("NOT_AVAILABLE"));
|
|
584
|
-
return true;
|
|
585
|
-
#endif
|
|
586
|
-
#else
|
|
587
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
588
|
-
TEXT("sequence_add_camera requires editor build."),
|
|
589
|
-
nullptr, TEXT("NOT_IMPLEMENTED"));
|
|
590
|
-
return true;
|
|
591
|
-
#endif
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
bool UMcpAutomationBridgeSubsystem::HandleSequencePlay(
|
|
595
|
-
const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
|
|
596
|
-
TSharedPtr<FMcpBridgeWebSocket> Socket) {
|
|
597
|
-
TSharedPtr<FJsonObject> LocalPayload =
|
|
598
|
-
Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
|
|
599
|
-
FString SeqPath = ResolveSequencePath(LocalPayload);
|
|
600
|
-
if (SeqPath.IsEmpty()) {
|
|
601
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
602
|
-
TEXT("No sequence selected or path provided"),
|
|
603
|
-
nullptr, TEXT("INVALID_SEQUENCE"));
|
|
604
|
-
return true;
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
#if WITH_EDITOR
|
|
608
|
-
TWeakObjectPtr<UMcpAutomationBridgeSubsystem> WeakSubsystem(this);
|
|
609
|
-
FString RequestIdArg = RequestId;
|
|
610
|
-
UMcpAutomationBridgeSubsystem *Subsystem = this;
|
|
611
|
-
ULevelSequence *LevelSeq =
|
|
612
|
-
Cast<ULevelSequence>(UEditorAssetLibrary::LoadAsset(SeqPath));
|
|
613
|
-
if (LevelSeq) {
|
|
614
|
-
if (ULevelSequenceEditorBlueprintLibrary::OpenLevelSequence(LevelSeq)) {
|
|
615
|
-
ULevelSequenceEditorBlueprintLibrary::Play();
|
|
616
|
-
Subsystem->SendAutomationResponse(Socket, RequestIdArg, true,
|
|
617
|
-
TEXT("Sequence playing"), nullptr);
|
|
618
|
-
return true;
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
Subsystem->SendAutomationResponse(Socket, RequestIdArg, false,
|
|
622
|
-
TEXT("Failed to open or play sequence"),
|
|
623
|
-
nullptr, TEXT("EXECUTION_ERROR"));
|
|
624
|
-
return true;
|
|
625
|
-
return true;
|
|
626
|
-
#else
|
|
627
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
628
|
-
TEXT("sequence_play requires editor build."), nullptr,
|
|
629
|
-
TEXT("NOT_AVAILABLE"));
|
|
630
|
-
return true;
|
|
631
|
-
#endif
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
bool UMcpAutomationBridgeSubsystem::HandleSequenceAddActor(
|
|
635
|
-
const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
|
|
636
|
-
TSharedPtr<FMcpBridgeWebSocket> Socket) {
|
|
637
|
-
TSharedPtr<FJsonObject> LocalPayload =
|
|
638
|
-
Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
|
|
639
|
-
FString ActorName;
|
|
640
|
-
LocalPayload->TryGetStringField(TEXT("actorName"), ActorName);
|
|
641
|
-
if (ActorName.IsEmpty()) {
|
|
642
|
-
SendAutomationResponse(Socket, RequestId, false, TEXT("actorName required"),
|
|
643
|
-
nullptr, TEXT("INVALID_ARGUMENT"));
|
|
644
|
-
return true;
|
|
645
|
-
}
|
|
646
|
-
FString SeqPath = ResolveSequencePath(LocalPayload);
|
|
647
|
-
if (SeqPath.IsEmpty()) {
|
|
648
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
649
|
-
TEXT("sequence_add_actor requires a sequence path"),
|
|
650
|
-
nullptr, TEXT("INVALID_SEQUENCE"));
|
|
651
|
-
return true;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
#if WITH_EDITOR
|
|
655
|
-
// Reuse multi-actor binding logic for a single actor by forwarding to
|
|
656
|
-
// HandleSequenceAddActors with a one-element actorNames array and the
|
|
657
|
-
// resolved sequence path. This ensures real LevelSequence bindings are
|
|
658
|
-
// applied when supported by the editor build.
|
|
659
|
-
TSharedPtr<FJsonObject> ForwardPayload = MakeShared<FJsonObject>();
|
|
660
|
-
ForwardPayload->SetStringField(TEXT("path"), SeqPath);
|
|
661
|
-
TArray<TSharedPtr<FJsonValue>> NamesArray;
|
|
662
|
-
NamesArray.Add(MakeShared<FJsonValueString>(ActorName));
|
|
663
|
-
ForwardPayload->SetArrayField(TEXT("actorNames"), NamesArray);
|
|
664
|
-
|
|
665
|
-
return HandleSequenceAddActors(RequestId, ForwardPayload, Socket);
|
|
666
|
-
#else
|
|
667
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
668
|
-
TEXT("sequence_add_actor requires editor build."),
|
|
669
|
-
nullptr, TEXT("NOT_IMPLEMENTED"));
|
|
670
|
-
return true;
|
|
671
|
-
#endif
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
bool UMcpAutomationBridgeSubsystem::HandleSequenceAddActors(
|
|
675
|
-
const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
|
|
676
|
-
TSharedPtr<FMcpBridgeWebSocket> Socket) {
|
|
677
|
-
TSharedPtr<FJsonObject> LocalPayload =
|
|
678
|
-
Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
|
|
679
|
-
const TArray<TSharedPtr<FJsonValue>> *Arr = nullptr;
|
|
680
|
-
LocalPayload->TryGetArrayField(TEXT("actorNames"), Arr);
|
|
681
|
-
if (!Arr || Arr->Num() == 0) {
|
|
682
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
683
|
-
TEXT("actorNames required"), nullptr,
|
|
684
|
-
TEXT("INVALID_ARGUMENT"));
|
|
685
|
-
return true;
|
|
686
|
-
}
|
|
687
|
-
FString SeqPath = ResolveSequencePath(LocalPayload);
|
|
688
|
-
if (SeqPath.IsEmpty()) {
|
|
689
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
690
|
-
TEXT("sequence_add_actors requires a sequence path"),
|
|
691
|
-
nullptr, TEXT("INVALID_SEQUENCE"));
|
|
692
|
-
return true;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
#if WITH_EDITOR
|
|
696
|
-
TArray<FString> Names;
|
|
697
|
-
Names.Reserve(Arr->Num());
|
|
698
|
-
for (const TSharedPtr<FJsonValue> &V : *Arr) {
|
|
699
|
-
if (V.IsValid() && V->Type == EJson::String)
|
|
700
|
-
Names.Add(V->AsString());
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
TWeakObjectPtr<UMcpAutomationBridgeSubsystem> WeakSubsystem(this);
|
|
704
|
-
FString RequestIdArg = RequestId;
|
|
705
|
-
UMcpAutomationBridgeSubsystem *Subsystem = this;
|
|
706
|
-
UObject *SeqObj = UEditorAssetLibrary::LoadAsset(SeqPath);
|
|
707
|
-
if (!SeqObj) {
|
|
708
|
-
Subsystem->SendAutomationResponse(Socket, RequestIdArg, false,
|
|
709
|
-
TEXT("Sequence not found"), nullptr,
|
|
710
|
-
TEXT("INVALID_SEQUENCE"));
|
|
711
|
-
return true;
|
|
712
|
-
}
|
|
713
|
-
if (!GEditor) {
|
|
714
|
-
Subsystem->SendAutomationResponse(Socket, RequestIdArg, false,
|
|
715
|
-
TEXT("Editor not available"), nullptr,
|
|
716
|
-
TEXT("EDITOR_NOT_AVAILABLE"));
|
|
717
|
-
return true;
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
#if MCP_HAS_EDITOR_ACTOR_SUBSYSTEM
|
|
721
|
-
if (UEditorActorSubsystem *ActorSS =
|
|
722
|
-
GEditor->GetEditorSubsystem<UEditorActorSubsystem>()) {
|
|
723
|
-
TArray<TSharedPtr<FJsonValue>> Results;
|
|
724
|
-
Results.Reserve(Names.Num());
|
|
725
|
-
for (const FString &Name : Names) {
|
|
726
|
-
TSharedPtr<FJsonObject> Item = MakeShared<FJsonObject>();
|
|
727
|
-
Item->SetStringField(TEXT("name"), Name);
|
|
728
|
-
// Use robust actor lookup that checks label, name, and UAID
|
|
729
|
-
AActor *Found = Subsystem->FindActorByName(Name);
|
|
730
|
-
|
|
731
|
-
if (!Found) {
|
|
732
|
-
Item->SetBoolField(TEXT("success"), false);
|
|
733
|
-
Item->SetStringField(TEXT("error"), TEXT("Actor not found"));
|
|
734
|
-
} else {
|
|
735
|
-
if (ULevelSequence *LevelSeq = Cast<ULevelSequence>(SeqObj)) {
|
|
736
|
-
UMovieScene *MovieScene = LevelSeq->GetMovieScene();
|
|
737
|
-
if (MovieScene) {
|
|
738
|
-
FGuid BindingGuid = MovieScene->AddPossessable(
|
|
739
|
-
Found->GetActorLabel(), Found->GetClass());
|
|
740
|
-
if (MovieScene->FindPossessable(BindingGuid)) {
|
|
741
|
-
Item->SetBoolField(TEXT("success"), true);
|
|
742
|
-
Item->SetStringField(TEXT("bindingGuid"), BindingGuid.ToString());
|
|
743
|
-
MovieScene->Modify();
|
|
744
|
-
} else {
|
|
745
|
-
Item->SetBoolField(TEXT("success"), false);
|
|
746
|
-
Item->SetStringField(
|
|
747
|
-
TEXT("error"), TEXT("Failed to create possessable binding"));
|
|
748
|
-
}
|
|
749
|
-
} else {
|
|
750
|
-
Item->SetBoolField(TEXT("success"), false);
|
|
751
|
-
Item->SetStringField(TEXT("error"),
|
|
752
|
-
TEXT("Sequence has no MovieScene"));
|
|
753
|
-
}
|
|
754
|
-
} else {
|
|
755
|
-
Item->SetBoolField(TEXT("success"), false);
|
|
756
|
-
Item->SetStringField(TEXT("error"),
|
|
757
|
-
TEXT("Sequence object is not a LevelSequence"));
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
Results.Add(MakeShared<FJsonValueObject>(Item));
|
|
761
|
-
}
|
|
762
|
-
TSharedPtr<FJsonObject> Out = MakeShared<FJsonObject>();
|
|
763
|
-
Out->SetArrayField(TEXT("results"), Results);
|
|
764
|
-
Subsystem->SendAutomationResponse(Socket, RequestIdArg, true,
|
|
765
|
-
TEXT("Actors processed"), Out, FString());
|
|
766
|
-
return true;
|
|
767
|
-
}
|
|
768
|
-
Subsystem->SendAutomationResponse(
|
|
769
|
-
Socket, RequestIdArg, false, TEXT("EditorActorSubsystem not available"),
|
|
770
|
-
nullptr, TEXT("EDITOR_ACTOR_SUBSYSTEM_MISSING"));
|
|
771
|
-
return true;
|
|
772
|
-
#else
|
|
773
|
-
Subsystem->SendAutomationResponse(Socket, RequestIdArg, false,
|
|
774
|
-
TEXT("UEditorActorSubsystem not available"),
|
|
775
|
-
nullptr, TEXT("NOT_AVAILABLE"));
|
|
776
|
-
#endif
|
|
777
|
-
return true;
|
|
778
|
-
#else
|
|
779
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
780
|
-
TEXT("sequence_add_actors requires editor build."),
|
|
781
|
-
nullptr, TEXT("NOT_IMPLEMENTED"));
|
|
782
|
-
return true;
|
|
783
|
-
#endif
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
bool UMcpAutomationBridgeSubsystem::HandleSequenceAddSpawnable(
|
|
787
|
-
const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
|
|
788
|
-
TSharedPtr<FMcpBridgeWebSocket> Socket) {
|
|
789
|
-
TSharedPtr<FJsonObject> LocalPayload =
|
|
790
|
-
Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
|
|
791
|
-
FString ClassName;
|
|
792
|
-
LocalPayload->TryGetStringField(TEXT("className"), ClassName);
|
|
793
|
-
if (ClassName.IsEmpty()) {
|
|
794
|
-
SendAutomationResponse(Socket, RequestId, false, TEXT("className required"),
|
|
795
|
-
nullptr, TEXT("INVALID_ARGUMENT"));
|
|
796
|
-
return true;
|
|
797
|
-
}
|
|
798
|
-
FString SeqPath = ResolveSequencePath(LocalPayload);
|
|
799
|
-
if (SeqPath.IsEmpty()) {
|
|
800
|
-
SendAutomationResponse(
|
|
801
|
-
Socket, RequestId, false,
|
|
802
|
-
TEXT("sequence_add_spawnable_from_class requires a sequence path"),
|
|
803
|
-
nullptr, TEXT("INVALID_SEQUENCE"));
|
|
804
|
-
return true;
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
#if WITH_EDITOR
|
|
808
|
-
UObject *SeqObj = UEditorAssetLibrary::LoadAsset(SeqPath);
|
|
809
|
-
if (!SeqObj) {
|
|
810
|
-
SendAutomationResponse(Socket, RequestId, false, TEXT("Sequence not found"),
|
|
811
|
-
nullptr, TEXT("INVALID_SEQUENCE"));
|
|
812
|
-
return true;
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
UClass *ResolvedClass = nullptr;
|
|
816
|
-
if (ClassName.StartsWith(TEXT("/")) || ClassName.Contains(TEXT("/"))) {
|
|
817
|
-
if (UObject *Loaded = UEditorAssetLibrary::LoadAsset(ClassName)) {
|
|
818
|
-
if (UBlueprint *BP = Cast<UBlueprint>(Loaded))
|
|
819
|
-
ResolvedClass = BP->GeneratedClass;
|
|
820
|
-
else if (UClass *C = Cast<UClass>(Loaded))
|
|
821
|
-
ResolvedClass = C;
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
if (!ResolvedClass)
|
|
825
|
-
ResolvedClass = ResolveClassByName(ClassName);
|
|
826
|
-
if (!ResolvedClass) {
|
|
827
|
-
SendAutomationResponse(Socket, RequestId, false, TEXT("Class not found"),
|
|
828
|
-
nullptr, TEXT("CLASS_NOT_FOUND"));
|
|
829
|
-
return true;
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
if (ULevelSequence *LevelSeq = Cast<ULevelSequence>(SeqObj)) {
|
|
833
|
-
UMovieScene *MovieScene = LevelSeq->GetMovieScene();
|
|
834
|
-
if (MovieScene) {
|
|
835
|
-
UObject *DefaultObject = ResolvedClass->GetDefaultObject();
|
|
836
|
-
if (DefaultObject) {
|
|
837
|
-
FGuid BindingGuid = MovieScene->AddSpawnable(ClassName, *DefaultObject);
|
|
838
|
-
if (MovieScene->FindSpawnable(BindingGuid)) {
|
|
839
|
-
MovieScene->Modify();
|
|
840
|
-
TSharedPtr<FJsonObject> SpawnableResp = MakeShared<FJsonObject>();
|
|
841
|
-
SpawnableResp->SetBoolField(TEXT("success"), true);
|
|
842
|
-
SpawnableResp->SetStringField(TEXT("className"), ClassName);
|
|
843
|
-
SpawnableResp->SetStringField(TEXT("bindingGuid"),
|
|
844
|
-
BindingGuid.ToString());
|
|
845
|
-
SendAutomationResponse(Socket, RequestId, true,
|
|
846
|
-
TEXT("Spawnable added to sequence"),
|
|
847
|
-
SpawnableResp, FString());
|
|
848
|
-
return true;
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
853
|
-
TEXT("Failed to create spawnable binding"), nullptr,
|
|
854
|
-
TEXT("SPAWNABLE_CREATION_FAILED"));
|
|
855
|
-
return true;
|
|
856
|
-
}
|
|
857
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
858
|
-
TEXT("Sequence object is not a LevelSequence"),
|
|
859
|
-
nullptr, TEXT("INVALID_SEQUENCE_TYPE"));
|
|
860
|
-
return true;
|
|
861
|
-
#else
|
|
862
|
-
SendAutomationResponse(
|
|
863
|
-
Socket, RequestId, false,
|
|
864
|
-
TEXT("sequence_add_spawnable_from_class requires editor build."), nullptr,
|
|
865
|
-
TEXT("NOT_IMPLEMENTED"));
|
|
866
|
-
return true;
|
|
867
|
-
#endif
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
bool UMcpAutomationBridgeSubsystem::HandleSequenceRemoveActors(
|
|
871
|
-
const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
|
|
872
|
-
TSharedPtr<FMcpBridgeWebSocket> Socket) {
|
|
873
|
-
TSharedPtr<FJsonObject> LocalPayload =
|
|
874
|
-
Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
|
|
875
|
-
const TArray<TSharedPtr<FJsonValue>> *Arr = nullptr;
|
|
876
|
-
LocalPayload->TryGetArrayField(TEXT("actorNames"), Arr);
|
|
877
|
-
if (!Arr || Arr->Num() == 0) {
|
|
878
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
879
|
-
TEXT("actorNames required"), nullptr,
|
|
880
|
-
TEXT("INVALID_ARGUMENT"));
|
|
881
|
-
return true;
|
|
882
|
-
}
|
|
883
|
-
FString SeqPath = ResolveSequencePath(LocalPayload);
|
|
884
|
-
if (SeqPath.IsEmpty()) {
|
|
885
|
-
SendAutomationResponse(
|
|
886
|
-
Socket, RequestId, false,
|
|
887
|
-
TEXT("sequence_remove_actors requires a sequence path"), nullptr,
|
|
888
|
-
TEXT("INVALID_SEQUENCE"));
|
|
889
|
-
return true;
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
#if WITH_EDITOR
|
|
893
|
-
UObject *SeqObj = UEditorAssetLibrary::LoadAsset(SeqPath);
|
|
894
|
-
if (!SeqObj) {
|
|
895
|
-
SendAutomationResponse(Socket, RequestId, false, TEXT("Sequence not found"),
|
|
896
|
-
nullptr, TEXT("INVALID_SEQUENCE"));
|
|
897
|
-
return true;
|
|
898
|
-
}
|
|
899
|
-
if (!GEditor) {
|
|
900
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
901
|
-
TEXT("Editor not available"), nullptr,
|
|
902
|
-
TEXT("EDITOR_NOT_AVAILABLE"));
|
|
903
|
-
return true;
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
#if MCP_HAS_EDITOR_ACTOR_SUBSYSTEM
|
|
907
|
-
if (UEditorActorSubsystem *ActorSS =
|
|
908
|
-
GEditor->GetEditorSubsystem<UEditorActorSubsystem>()) {
|
|
909
|
-
TArray<TSharedPtr<FJsonValue>> Removed;
|
|
910
|
-
int32 RemovedCount = 0;
|
|
911
|
-
for (const TSharedPtr<FJsonValue> &V : *Arr) {
|
|
912
|
-
if (!V.IsValid() || V->Type != EJson::String)
|
|
913
|
-
continue;
|
|
914
|
-
FString Name = V->AsString();
|
|
915
|
-
TSharedPtr<FJsonObject> Item = MakeShared<FJsonObject>();
|
|
916
|
-
Item->SetStringField(TEXT("name"), Name);
|
|
917
|
-
|
|
918
|
-
if (ULevelSequence *LevelSeq = Cast<ULevelSequence>(SeqObj)) {
|
|
919
|
-
UMovieScene *MovieScene = LevelSeq->GetMovieScene();
|
|
920
|
-
if (MovieScene) {
|
|
921
|
-
bool bRemoved = false;
|
|
922
|
-
for (const FMovieSceneBinding &Binding :
|
|
923
|
-
const_cast<const UMovieScene *>(MovieScene)->GetBindings()) {
|
|
924
|
-
FString BindingName;
|
|
925
|
-
if (FMovieScenePossessable *Possessable =
|
|
926
|
-
MovieScene->FindPossessable(Binding.GetObjectGuid())) {
|
|
927
|
-
BindingName = Possessable->GetName();
|
|
928
|
-
} else if (FMovieSceneSpawnable *Spawnable =
|
|
929
|
-
MovieScene->FindSpawnable(Binding.GetObjectGuid())) {
|
|
930
|
-
BindingName = Spawnable->GetName();
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
if (BindingName.Equals(Name, ESearchCase::IgnoreCase)) {
|
|
934
|
-
MovieScene->RemovePossessable(Binding.GetObjectGuid());
|
|
935
|
-
MovieScene->Modify();
|
|
936
|
-
bRemoved = true;
|
|
937
|
-
break;
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
if (bRemoved) {
|
|
941
|
-
Item->SetBoolField(TEXT("success"), true);
|
|
942
|
-
Item->SetStringField(TEXT("status"), TEXT("Actor removed"));
|
|
943
|
-
RemovedCount++;
|
|
944
|
-
} else {
|
|
945
|
-
Item->SetBoolField(TEXT("success"), false);
|
|
946
|
-
Item->SetStringField(TEXT("error"),
|
|
947
|
-
TEXT("Actor not found in sequence bindings"));
|
|
948
|
-
}
|
|
949
|
-
} else {
|
|
950
|
-
Item->SetBoolField(TEXT("success"), false);
|
|
951
|
-
Item->SetStringField(TEXT("error"),
|
|
952
|
-
TEXT("Sequence has no MovieScene"));
|
|
953
|
-
}
|
|
954
|
-
} else {
|
|
955
|
-
Item->SetBoolField(TEXT("success"), false);
|
|
956
|
-
Item->SetStringField(TEXT("error"),
|
|
957
|
-
TEXT("Sequence object is not a LevelSequence"));
|
|
958
|
-
}
|
|
959
|
-
Removed.Add(MakeShared<FJsonValueObject>(Item));
|
|
960
|
-
}
|
|
961
|
-
TSharedPtr<FJsonObject> Out = MakeShared<FJsonObject>();
|
|
962
|
-
Out->SetArrayField(TEXT("removedActors"), Removed);
|
|
963
|
-
Out->SetNumberField(TEXT("bindingsProcessed"), RemovedCount);
|
|
964
|
-
SendAutomationResponse(Socket, RequestId, true,
|
|
965
|
-
TEXT("Actors processed for removal"), Out,
|
|
966
|
-
FString());
|
|
967
|
-
return true;
|
|
968
|
-
}
|
|
969
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
970
|
-
TEXT("EditorActorSubsystem not available"), nullptr,
|
|
971
|
-
TEXT("EDITOR_ACTOR_SUBSYSTEM_MISSING"));
|
|
972
|
-
return true;
|
|
973
|
-
#else
|
|
974
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
975
|
-
TEXT("UEditorActorSubsystem not available"), nullptr,
|
|
976
|
-
TEXT("NOT_AVAILABLE"));
|
|
977
|
-
return true;
|
|
978
|
-
#endif
|
|
979
|
-
#else
|
|
980
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
981
|
-
TEXT("sequence_remove_actors requires editor build."),
|
|
982
|
-
nullptr, TEXT("NOT_IMPLEMENTED"));
|
|
983
|
-
return true;
|
|
984
|
-
#endif
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
bool UMcpAutomationBridgeSubsystem::HandleSequenceGetBindings(
|
|
988
|
-
const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
|
|
989
|
-
TSharedPtr<FMcpBridgeWebSocket> Socket) {
|
|
990
|
-
TSharedPtr<FJsonObject> LocalPayload =
|
|
991
|
-
Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
|
|
992
|
-
FString SeqPath = ResolveSequencePath(LocalPayload);
|
|
993
|
-
if (SeqPath.IsEmpty()) {
|
|
994
|
-
SendAutomationResponse(
|
|
995
|
-
Socket, RequestId, false,
|
|
996
|
-
TEXT("sequence_get_bindings requires a sequence path"), nullptr,
|
|
997
|
-
TEXT("INVALID_SEQUENCE"));
|
|
998
|
-
return true;
|
|
999
|
-
}
|
|
1000
|
-
#if WITH_EDITOR
|
|
1001
|
-
TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
|
|
1002
|
-
UObject *SeqObj = UEditorAssetLibrary::LoadAsset(SeqPath);
|
|
1003
|
-
if (!SeqObj) {
|
|
1004
|
-
SendAutomationResponse(Socket, RequestId, false, TEXT("Sequence not found"),
|
|
1005
|
-
nullptr, TEXT("INVALID_SEQUENCE"));
|
|
1006
|
-
return true;
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
if (ULevelSequence *LevelSeq = Cast<ULevelSequence>(SeqObj)) {
|
|
1010
|
-
if (UMovieScene *MovieScene = LevelSeq->GetMovieScene()) {
|
|
1011
|
-
TArray<TSharedPtr<FJsonValue>> BindingsArray;
|
|
1012
|
-
for (const FMovieSceneBinding &B :
|
|
1013
|
-
const_cast<const UMovieScene *>(MovieScene)->GetBindings()) {
|
|
1014
|
-
TSharedPtr<FJsonObject> Bobj = MakeShared<FJsonObject>();
|
|
1015
|
-
Bobj->SetStringField(TEXT("id"), B.GetObjectGuid().ToString());
|
|
1016
|
-
|
|
1017
|
-
FString BindingName;
|
|
1018
|
-
if (FMovieScenePossessable *Possessable =
|
|
1019
|
-
MovieScene->FindPossessable(B.GetObjectGuid())) {
|
|
1020
|
-
BindingName = Possessable->GetName();
|
|
1021
|
-
} else if (FMovieSceneSpawnable *Spawnable =
|
|
1022
|
-
MovieScene->FindSpawnable(B.GetObjectGuid())) {
|
|
1023
|
-
BindingName = Spawnable->GetName();
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
Bobj->SetStringField(TEXT("name"), BindingName);
|
|
1027
|
-
BindingsArray.Add(MakeShared<FJsonValueObject>(Bobj));
|
|
1028
|
-
}
|
|
1029
|
-
Resp->SetArrayField(TEXT("bindings"), BindingsArray);
|
|
1030
|
-
SendAutomationResponse(Socket, RequestId, true, TEXT("bindings listed"),
|
|
1031
|
-
Resp, FString());
|
|
1032
|
-
return true;
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
Resp->SetArrayField(TEXT("bindings"), TArray<TSharedPtr<FJsonValue>>());
|
|
1036
|
-
SendAutomationResponse(Socket, RequestId, true,
|
|
1037
|
-
TEXT("bindings listed (empty)"), Resp, FString());
|
|
1038
|
-
return true;
|
|
1039
|
-
#else
|
|
1040
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
1041
|
-
TEXT("sequence_get_bindings requires editor build."),
|
|
1042
|
-
nullptr, TEXT("NOT_IMPLEMENTED"));
|
|
1043
|
-
return true;
|
|
1044
|
-
#endif
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
bool UMcpAutomationBridgeSubsystem::HandleSequenceGetProperties(
|
|
1048
|
-
const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
|
|
1049
|
-
TSharedPtr<FMcpBridgeWebSocket> Socket) {
|
|
1050
|
-
TSharedPtr<FJsonObject> LocalPayload =
|
|
1051
|
-
Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
|
|
1052
|
-
FString SeqPath = ResolveSequencePath(LocalPayload);
|
|
1053
|
-
if (SeqPath.IsEmpty()) {
|
|
1054
|
-
SendAutomationResponse(
|
|
1055
|
-
Socket, RequestId, false,
|
|
1056
|
-
TEXT("sequence_get_properties requires a sequence path"), nullptr,
|
|
1057
|
-
TEXT("INVALID_SEQUENCE"));
|
|
1058
|
-
return true;
|
|
1059
|
-
}
|
|
1060
|
-
#if WITH_EDITOR
|
|
1061
|
-
TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
|
|
1062
|
-
UObject *SeqObj = UEditorAssetLibrary::LoadAsset(SeqPath);
|
|
1063
|
-
if (!SeqObj) {
|
|
1064
|
-
SendAutomationResponse(Socket, RequestId, false, TEXT("Sequence not found"),
|
|
1065
|
-
nullptr, TEXT("INVALID_SEQUENCE"));
|
|
1066
|
-
return true;
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
if (ULevelSequence *LevelSeq = Cast<ULevelSequence>(SeqObj)) {
|
|
1070
|
-
if (UMovieScene *MovieScene = LevelSeq->GetMovieScene()) {
|
|
1071
|
-
FFrameRate FR = MovieScene->GetDisplayRate();
|
|
1072
|
-
TSharedPtr<FJsonObject> FrameRateObj = MakeShared<FJsonObject>();
|
|
1073
|
-
FrameRateObj->SetNumberField(TEXT("numerator"), FR.Numerator);
|
|
1074
|
-
FrameRateObj->SetNumberField(TEXT("denominator"), FR.Denominator);
|
|
1075
|
-
Resp->SetObjectField(TEXT("frameRate"), FrameRateObj);
|
|
1076
|
-
TRange<FFrameNumber> Range = MovieScene->GetPlaybackRange();
|
|
1077
|
-
const double Start =
|
|
1078
|
-
static_cast<double>(Range.GetLowerBoundValue().Value);
|
|
1079
|
-
const double End = static_cast<double>(Range.GetUpperBoundValue().Value);
|
|
1080
|
-
Resp->SetNumberField(TEXT("playbackStart"), Start);
|
|
1081
|
-
Resp->SetNumberField(TEXT("playbackEnd"), End);
|
|
1082
|
-
Resp->SetNumberField(TEXT("duration"), End - Start);
|
|
1083
|
-
SendAutomationResponse(Socket, RequestId, true,
|
|
1084
|
-
TEXT("properties retrieved"), Resp, FString());
|
|
1085
|
-
return true;
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
Resp->SetObjectField(TEXT("frameRate"), MakeShared<FJsonObject>());
|
|
1089
|
-
Resp->SetNumberField(TEXT("playbackStart"), 0.0);
|
|
1090
|
-
Resp->SetNumberField(TEXT("playbackEnd"), 0.0);
|
|
1091
|
-
Resp->SetNumberField(TEXT("duration"), 0.0);
|
|
1092
|
-
SendAutomationResponse(Socket, RequestId, true, TEXT("properties retrieved"),
|
|
1093
|
-
Resp, FString());
|
|
1094
|
-
return true;
|
|
1095
|
-
#else
|
|
1096
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
1097
|
-
TEXT("sequence_get_properties requires editor build."),
|
|
1098
|
-
nullptr, TEXT("NOT_IMPLEMENTED"));
|
|
1099
|
-
return true;
|
|
1100
|
-
#endif
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
bool UMcpAutomationBridgeSubsystem::HandleSequenceSetPlaybackSpeed(
|
|
1104
|
-
const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
|
|
1105
|
-
TSharedPtr<FMcpBridgeWebSocket> Socket) {
|
|
1106
|
-
TSharedPtr<FJsonObject> LocalPayload =
|
|
1107
|
-
Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
|
|
1108
|
-
double Speed = 1.0;
|
|
1109
|
-
LocalPayload->TryGetNumberField(TEXT("speed"), Speed);
|
|
1110
|
-
if (Speed <= 0.0) {
|
|
1111
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
1112
|
-
TEXT("Invalid speed (must be > 0)"), nullptr,
|
|
1113
|
-
TEXT("INVALID_ARGUMENT"));
|
|
1114
|
-
return true;
|
|
1115
|
-
}
|
|
1116
|
-
FString SeqPath = ResolveSequencePath(LocalPayload);
|
|
1117
|
-
if (SeqPath.IsEmpty()) {
|
|
1118
|
-
SendAutomationResponse(
|
|
1119
|
-
Socket, RequestId, false,
|
|
1120
|
-
TEXT("sequence_set_playback_speed requires a sequence path"), nullptr,
|
|
1121
|
-
TEXT("INVALID_SEQUENCE"));
|
|
1122
|
-
return true;
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
#if WITH_EDITOR
|
|
1126
|
-
TWeakObjectPtr<UMcpAutomationBridgeSubsystem> WeakSubsystem(this);
|
|
1127
|
-
FString RequestIdArg = RequestId; // Capture
|
|
1128
|
-
|
|
1129
|
-
// Execute on Game Thread
|
|
1130
|
-
UMcpAutomationBridgeSubsystem *Subsystem = this;
|
|
1131
|
-
UObject *SeqObj = UEditorAssetLibrary::LoadAsset(SeqPath);
|
|
1132
|
-
if (!SeqObj) {
|
|
1133
|
-
Subsystem->SendAutomationResponse(Socket, RequestIdArg, false,
|
|
1134
|
-
TEXT("Sequence not found"), nullptr,
|
|
1135
|
-
TEXT("INVALID_SEQUENCE"));
|
|
1136
|
-
return true;
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
if (GEditor) {
|
|
1140
|
-
if (UAssetEditorSubsystem *AssetEditorSS =
|
|
1141
|
-
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()) {
|
|
1142
|
-
IAssetEditorInstance *Editor =
|
|
1143
|
-
AssetEditorSS->FindEditorForAsset(SeqObj, false);
|
|
1144
|
-
if (Editor && Editor->GetEditorName() == FName("LevelSequenceEditor")) {
|
|
1145
|
-
// We assume it implements ILevelSequenceEditorToolkit if the name
|
|
1146
|
-
// matches
|
|
1147
|
-
ILevelSequenceEditorToolkit *LSEditor =
|
|
1148
|
-
static_cast<ILevelSequenceEditorToolkit *>(Editor);
|
|
1149
|
-
if (LSEditor && LSEditor->GetSequencer().IsValid()) {
|
|
1150
|
-
UE_LOG(LogMcpAutomationBridgeSubsystem, Display,
|
|
1151
|
-
TEXT("HandleSequenceSetPlaybackSpeed: Setting speed to %.2f"),
|
|
1152
|
-
Speed);
|
|
1153
|
-
LSEditor->GetSequencer()->SetPlaybackSpeed(static_cast<float>(Speed));
|
|
1154
|
-
Subsystem->SendAutomationResponse(
|
|
1155
|
-
Socket, RequestIdArg, true,
|
|
1156
|
-
FString::Printf(TEXT("Playback speed set to %.2f"), Speed),
|
|
1157
|
-
nullptr);
|
|
1158
|
-
return true;
|
|
1159
|
-
} else {
|
|
1160
|
-
UE_LOG(LogMcpAutomationBridgeSubsystem, Error,
|
|
1161
|
-
TEXT("HandleSequenceSetPlaybackSpeed: Sequencer invalid for "
|
|
1162
|
-
"asset %s"),
|
|
1163
|
-
*SeqObj->GetName());
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
Subsystem->SendAutomationResponse(
|
|
1170
|
-
Socket, RequestIdArg, false,
|
|
1171
|
-
TEXT("Sequence editor not open or interface unavailable"), nullptr,
|
|
1172
|
-
TEXT("EDITOR_NOT_OPEN"));
|
|
1173
|
-
return true;
|
|
1174
|
-
return true;
|
|
1175
|
-
#else
|
|
1176
|
-
SendAutomationResponse(
|
|
1177
|
-
Socket, RequestId, false,
|
|
1178
|
-
TEXT("sequence_set_playback_speed requires editor build."), nullptr,
|
|
1179
|
-
TEXT("NOT_AVAILABLE"));
|
|
1180
|
-
return true;
|
|
1181
|
-
#endif
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
bool UMcpAutomationBridgeSubsystem::HandleSequencePause(
|
|
1185
|
-
const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
|
|
1186
|
-
TSharedPtr<FMcpBridgeWebSocket> Socket) {
|
|
1187
|
-
TSharedPtr<FJsonObject> LocalPayload =
|
|
1188
|
-
Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
|
|
1189
|
-
FString SeqPath = ResolveSequencePath(LocalPayload);
|
|
1190
|
-
if (SeqPath.IsEmpty()) {
|
|
1191
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
1192
|
-
TEXT("sequence_pause requires a sequence path"),
|
|
1193
|
-
nullptr, TEXT("INVALID_SEQUENCE"));
|
|
1194
|
-
return true;
|
|
1195
|
-
}
|
|
1196
|
-
#if WITH_EDITOR
|
|
1197
|
-
TWeakObjectPtr<UMcpAutomationBridgeSubsystem> WeakSubsystem(this);
|
|
1198
|
-
FString RequestIdArg = RequestId;
|
|
1199
|
-
UMcpAutomationBridgeSubsystem *Subsystem = this;
|
|
1200
|
-
|
|
1201
|
-
ULevelSequence *LevelSeq =
|
|
1202
|
-
Cast<ULevelSequence>(UEditorAssetLibrary::LoadAsset(SeqPath));
|
|
1203
|
-
if (LevelSeq) {
|
|
1204
|
-
// Ensure it's the active one
|
|
1205
|
-
if (ULevelSequenceEditorBlueprintLibrary::GetCurrentLevelSequence() ==
|
|
1206
|
-
LevelSeq) {
|
|
1207
|
-
ULevelSequenceEditorBlueprintLibrary::Pause();
|
|
1208
|
-
Subsystem->SendAutomationResponse(Socket, RequestIdArg, true,
|
|
1209
|
-
TEXT("Sequence paused"), nullptr);
|
|
1210
|
-
return true;
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
Subsystem->SendAutomationResponse(
|
|
1214
|
-
Socket, RequestIdArg, false,
|
|
1215
|
-
TEXT("Sequence not currently open in editor"), nullptr,
|
|
1216
|
-
TEXT("EXECUTION_ERROR"));
|
|
1217
|
-
return true;
|
|
1218
|
-
return true;
|
|
1219
|
-
#else
|
|
1220
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
1221
|
-
TEXT("sequence_pause requires editor build."), nullptr,
|
|
1222
|
-
TEXT("NOT_AVAILABLE"));
|
|
1223
|
-
return true;
|
|
1224
|
-
#endif
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
bool UMcpAutomationBridgeSubsystem::HandleSequenceStop(
|
|
1228
|
-
const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
|
|
1229
|
-
TSharedPtr<FMcpBridgeWebSocket> Socket) {
|
|
1230
|
-
TSharedPtr<FJsonObject> LocalPayload =
|
|
1231
|
-
Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
|
|
1232
|
-
FString SeqPath = ResolveSequencePath(LocalPayload);
|
|
1233
|
-
if (SeqPath.IsEmpty()) {
|
|
1234
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
1235
|
-
TEXT("sequence_stop requires a sequence path"),
|
|
1236
|
-
nullptr, TEXT("INVALID_SEQUENCE"));
|
|
1237
|
-
return true;
|
|
1238
|
-
}
|
|
1239
|
-
#if WITH_EDITOR
|
|
1240
|
-
TWeakObjectPtr<UMcpAutomationBridgeSubsystem> WeakSubsystem(this);
|
|
1241
|
-
FString RequestIdArg = RequestId;
|
|
1242
|
-
UMcpAutomationBridgeSubsystem *Subsystem = this;
|
|
1243
|
-
|
|
1244
|
-
ULevelSequence *LevelSeq =
|
|
1245
|
-
Cast<ULevelSequence>(UEditorAssetLibrary::LoadAsset(SeqPath));
|
|
1246
|
-
if (LevelSeq) {
|
|
1247
|
-
if (ULevelSequenceEditorBlueprintLibrary::GetCurrentLevelSequence() ==
|
|
1248
|
-
LevelSeq) {
|
|
1249
|
-
ULevelSequenceEditorBlueprintLibrary::Pause();
|
|
1250
|
-
|
|
1251
|
-
FMovieSceneSequencePlaybackParams PlaybackParams;
|
|
1252
|
-
PlaybackParams.Frame = FFrameTime(0);
|
|
1253
|
-
PlaybackParams.UpdateMethod = EUpdatePositionMethod::Scrub;
|
|
1254
|
-
ULevelSequenceEditorBlueprintLibrary::SetGlobalPosition(PlaybackParams);
|
|
1255
|
-
|
|
1256
|
-
Subsystem->SendAutomationResponse(
|
|
1257
|
-
Socket, RequestIdArg, true, TEXT("Sequence stopped (reset to start)"),
|
|
1258
|
-
nullptr);
|
|
1259
|
-
return true;
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
Subsystem->SendAutomationResponse(
|
|
1263
|
-
Socket, RequestIdArg, false,
|
|
1264
|
-
TEXT("Sequence not currently open in editor"), nullptr,
|
|
1265
|
-
TEXT("EXECUTION_ERROR"));
|
|
1266
|
-
return true;
|
|
1267
|
-
return true;
|
|
1268
|
-
#else
|
|
1269
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
1270
|
-
TEXT("sequence_stop requires editor build."), nullptr,
|
|
1271
|
-
TEXT("NOT_AVAILABLE"));
|
|
1272
|
-
return true;
|
|
1273
|
-
#endif
|
|
1274
|
-
}
|
|
1275
|
-
|
|
1276
|
-
bool UMcpAutomationBridgeSubsystem::HandleSequenceList(
|
|
1277
|
-
const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
|
|
1278
|
-
TSharedPtr<FMcpBridgeWebSocket> Socket) {
|
|
1279
|
-
#if WITH_EDITOR
|
|
1280
|
-
TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
|
|
1281
|
-
TArray<TSharedPtr<FJsonValue>> SequencesArray;
|
|
1282
|
-
|
|
1283
|
-
// Use Asset Registry to find all LevelSequence assets, not string matching
|
|
1284
|
-
FAssetRegistryModule &AssetRegistryModule =
|
|
1285
|
-
FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
|
|
1286
|
-
IAssetRegistry &AssetRegistry = AssetRegistryModule.Get();
|
|
1287
|
-
|
|
1288
|
-
FARFilter Filter;
|
|
1289
|
-
Filter.ClassPaths.Add(ULevelSequence::StaticClass()->GetClassPathName());
|
|
1290
|
-
Filter.bRecursiveClasses = true;
|
|
1291
|
-
Filter.bRecursivePaths = true;
|
|
1292
|
-
Filter.PackagePaths.Add(FName("/Game"));
|
|
1293
|
-
|
|
1294
|
-
TArray<FAssetData> AssetList;
|
|
1295
|
-
AssetRegistry.GetAssets(Filter, AssetList);
|
|
1296
|
-
|
|
1297
|
-
for (const FAssetData &Asset : AssetList) {
|
|
1298
|
-
TSharedPtr<FJsonObject> SeqObj = MakeShared<FJsonObject>();
|
|
1299
|
-
SeqObj->SetStringField(TEXT("path"), Asset.GetObjectPathString());
|
|
1300
|
-
SeqObj->SetStringField(TEXT("name"), Asset.AssetName.ToString());
|
|
1301
|
-
SequencesArray.Add(MakeShared<FJsonValueObject>(SeqObj));
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
Resp->SetArrayField(TEXT("sequences"), SequencesArray);
|
|
1305
|
-
Resp->SetNumberField(TEXT("count"), SequencesArray.Num());
|
|
1306
|
-
SendAutomationResponse(
|
|
1307
|
-
Socket, RequestId, true,
|
|
1308
|
-
FString::Printf(TEXT("Found %d sequences"), SequencesArray.Num()), Resp,
|
|
1309
|
-
FString());
|
|
1310
|
-
return true;
|
|
1311
|
-
#else
|
|
1312
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
1313
|
-
TEXT("sequence_list requires editor build."), nullptr,
|
|
1314
|
-
TEXT("NOT_AVAILABLE"));
|
|
1315
|
-
return true;
|
|
1316
|
-
#endif
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
bool UMcpAutomationBridgeSubsystem::HandleSequenceDuplicate(
|
|
1320
|
-
const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
|
|
1321
|
-
TSharedPtr<FMcpBridgeWebSocket> Socket) {
|
|
1322
|
-
TSharedPtr<FJsonObject> LocalPayload =
|
|
1323
|
-
Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
|
|
1324
|
-
FString SourcePath;
|
|
1325
|
-
LocalPayload->TryGetStringField(TEXT("path"), SourcePath);
|
|
1326
|
-
FString DestinationPath;
|
|
1327
|
-
LocalPayload->TryGetStringField(TEXT("destinationPath"), DestinationPath);
|
|
1328
|
-
if (SourcePath.IsEmpty() || DestinationPath.IsEmpty()) {
|
|
1329
|
-
SendAutomationResponse(
|
|
1330
|
-
Socket, RequestId, false,
|
|
1331
|
-
TEXT("sequence_duplicate requires path and destinationPath"), nullptr,
|
|
1332
|
-
TEXT("INVALID_ARGUMENT"));
|
|
1333
|
-
return true;
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
// Auto-resolve relative destination path (if just a name is provided)
|
|
1337
|
-
if (!DestinationPath.IsEmpty() && !DestinationPath.StartsWith(TEXT("/"))) {
|
|
1338
|
-
FString ParentPath = FPaths::GetPath(SourcePath);
|
|
1339
|
-
DestinationPath =
|
|
1340
|
-
FString::Printf(TEXT("%s/%s"), *ParentPath, *DestinationPath);
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
|
-
#if WITH_EDITOR
|
|
1344
|
-
UObject *SourceSeq = UEditorAssetLibrary::LoadAsset(SourcePath);
|
|
1345
|
-
if (!SourceSeq) {
|
|
1346
|
-
SendAutomationResponse(
|
|
1347
|
-
Socket, RequestId, false,
|
|
1348
|
-
FString::Printf(TEXT("Source sequence not found: %s"), *SourcePath),
|
|
1349
|
-
nullptr, TEXT("INVALID_SEQUENCE"));
|
|
1350
|
-
return true;
|
|
1351
|
-
}
|
|
1352
|
-
UObject *DuplicatedSeq =
|
|
1353
|
-
UEditorAssetLibrary::DuplicateAsset(SourcePath, DestinationPath);
|
|
1354
|
-
if (DuplicatedSeq) {
|
|
1355
|
-
TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
|
|
1356
|
-
Resp->SetStringField(TEXT("sourcePath"), SourcePath);
|
|
1357
|
-
Resp->SetStringField(TEXT("destinationPath"), DestinationPath);
|
|
1358
|
-
Resp->SetStringField(TEXT("duplicatedPath"), DuplicatedSeq->GetPathName());
|
|
1359
|
-
SendAutomationResponse(Socket, RequestId, true,
|
|
1360
|
-
TEXT("Sequence duplicated successfully"), Resp,
|
|
1361
|
-
FString());
|
|
1362
|
-
return true;
|
|
1363
|
-
}
|
|
1364
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
1365
|
-
TEXT("Failed to duplicate sequence"), nullptr,
|
|
1366
|
-
TEXT("OPERATION_FAILED"));
|
|
1367
|
-
return true;
|
|
1368
|
-
#else
|
|
1369
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
1370
|
-
TEXT("sequence_duplicate requires editor build."),
|
|
1371
|
-
nullptr, TEXT("NOT_AVAILABLE"));
|
|
1372
|
-
return true;
|
|
1373
|
-
#endif
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
bool UMcpAutomationBridgeSubsystem::HandleSequenceRename(
|
|
1377
|
-
const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
|
|
1378
|
-
TSharedPtr<FMcpBridgeWebSocket> Socket) {
|
|
1379
|
-
TSharedPtr<FJsonObject> LocalPayload =
|
|
1380
|
-
Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
|
|
1381
|
-
FString Path;
|
|
1382
|
-
LocalPayload->TryGetStringField(TEXT("path"), Path);
|
|
1383
|
-
FString NewName;
|
|
1384
|
-
LocalPayload->TryGetStringField(TEXT("newName"), NewName);
|
|
1385
|
-
if (Path.IsEmpty() || NewName.IsEmpty()) {
|
|
1386
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
1387
|
-
TEXT("sequence_rename requires path and newName"),
|
|
1388
|
-
nullptr, TEXT("INVALID_ARGUMENT"));
|
|
1389
|
-
return true;
|
|
1390
|
-
}
|
|
1391
|
-
|
|
1392
|
-
// Auto-resolve relative new name to full path
|
|
1393
|
-
if (!NewName.IsEmpty() && !NewName.StartsWith(TEXT("/"))) {
|
|
1394
|
-
FString ParentPath = FPaths::GetPath(Path);
|
|
1395
|
-
NewName = FString::Printf(TEXT("%s/%s"), *ParentPath, *NewName);
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
|
-
#if WITH_EDITOR
|
|
1399
|
-
if (UEditorAssetLibrary::RenameAsset(Path, NewName)) {
|
|
1400
|
-
TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
|
|
1401
|
-
Resp->SetStringField(TEXT("oldPath"), Path);
|
|
1402
|
-
Resp->SetStringField(TEXT("newName"), NewName);
|
|
1403
|
-
SendAutomationResponse(Socket, RequestId, true,
|
|
1404
|
-
TEXT("Sequence renamed successfully"), Resp,
|
|
1405
|
-
FString());
|
|
1406
|
-
return true;
|
|
1407
|
-
}
|
|
1408
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
1409
|
-
TEXT("Failed to rename sequence"), nullptr,
|
|
1410
|
-
TEXT("OPERATION_FAILED"));
|
|
1411
|
-
return true;
|
|
1412
|
-
#else
|
|
1413
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
1414
|
-
TEXT("sequence_rename requires editor build."),
|
|
1415
|
-
nullptr, TEXT("NOT_AVAILABLE"));
|
|
1416
|
-
return true;
|
|
1417
|
-
#endif
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1420
|
-
bool UMcpAutomationBridgeSubsystem::HandleSequenceDelete(
|
|
1421
|
-
const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
|
|
1422
|
-
TSharedPtr<FMcpBridgeWebSocket> Socket) {
|
|
1423
|
-
TSharedPtr<FJsonObject> LocalPayload =
|
|
1424
|
-
Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
|
|
1425
|
-
FString Path;
|
|
1426
|
-
LocalPayload->TryGetStringField(TEXT("path"), Path);
|
|
1427
|
-
if (Path.IsEmpty()) {
|
|
1428
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
1429
|
-
TEXT("sequence_delete requires path"), nullptr,
|
|
1430
|
-
TEXT("INVALID_ARGUMENT"));
|
|
1431
|
-
return true;
|
|
1432
|
-
}
|
|
1433
|
-
#if WITH_EDITOR
|
|
1434
|
-
if (!UEditorAssetLibrary::DoesAssetExist(Path)) {
|
|
1435
|
-
// Idempotent success - if it's already gone, good.
|
|
1436
|
-
TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
|
|
1437
|
-
Resp->SetStringField(TEXT("deletedPath"), Path);
|
|
1438
|
-
SendAutomationResponse(Socket, RequestId, true,
|
|
1439
|
-
TEXT("Sequence deleted (or did not exist)"), Resp,
|
|
1440
|
-
FString());
|
|
1441
|
-
return true;
|
|
1442
|
-
}
|
|
1443
|
-
|
|
1444
|
-
if (UEditorAssetLibrary::DeleteAsset(Path)) {
|
|
1445
|
-
TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
|
|
1446
|
-
Resp->SetStringField(TEXT("deletedPath"), Path);
|
|
1447
|
-
SendAutomationResponse(Socket, RequestId, true,
|
|
1448
|
-
TEXT("Sequence deleted successfully"), Resp,
|
|
1449
|
-
FString());
|
|
1450
|
-
return true;
|
|
1451
|
-
}
|
|
1452
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
1453
|
-
TEXT("Failed to delete sequence"), nullptr,
|
|
1454
|
-
TEXT("OPERATION_FAILED"));
|
|
1455
|
-
return true;
|
|
1456
|
-
#else
|
|
1457
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
1458
|
-
TEXT("sequence_delete requires editor build."),
|
|
1459
|
-
nullptr, TEXT("NOT_AVAILABLE"));
|
|
1460
|
-
return true;
|
|
1461
|
-
#endif
|
|
1462
|
-
}
|
|
1463
|
-
|
|
1464
|
-
bool UMcpAutomationBridgeSubsystem::HandleSequenceGetMetadata(
|
|
1465
|
-
const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
|
|
1466
|
-
TSharedPtr<FMcpBridgeWebSocket> Socket) {
|
|
1467
|
-
TSharedPtr<FJsonObject> LocalPayload =
|
|
1468
|
-
Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
|
|
1469
|
-
FString SeqPath = ResolveSequencePath(LocalPayload);
|
|
1470
|
-
if (SeqPath.IsEmpty()) {
|
|
1471
|
-
SendAutomationResponse(
|
|
1472
|
-
Socket, RequestId, false,
|
|
1473
|
-
TEXT("sequence_get_metadata requires a sequence path"), nullptr,
|
|
1474
|
-
TEXT("INVALID_SEQUENCE"));
|
|
1475
|
-
return true;
|
|
1476
|
-
}
|
|
1477
|
-
#if WITH_EDITOR
|
|
1478
|
-
UObject *SeqObj = UEditorAssetLibrary::LoadAsset(SeqPath);
|
|
1479
|
-
if (!SeqObj) {
|
|
1480
|
-
SendAutomationResponse(Socket, RequestId, false, TEXT("Sequence not found"),
|
|
1481
|
-
nullptr, TEXT("INVALID_SEQUENCE"));
|
|
1482
|
-
return true;
|
|
1483
|
-
}
|
|
1484
|
-
TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
|
|
1485
|
-
Resp->SetStringField(TEXT("path"), SeqPath);
|
|
1486
|
-
Resp->SetStringField(TEXT("name"), SeqObj->GetName());
|
|
1487
|
-
Resp->SetStringField(TEXT("class"), SeqObj->GetClass()->GetName());
|
|
1488
|
-
SendAutomationResponse(Socket, RequestId, true,
|
|
1489
|
-
TEXT("Sequence metadata retrieved"), Resp, FString());
|
|
1490
|
-
return true;
|
|
1491
|
-
#else
|
|
1492
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
1493
|
-
TEXT("sequence_get_metadata requires editor build."),
|
|
1494
|
-
nullptr, TEXT("NOT_AVAILABLE"));
|
|
1495
|
-
return true;
|
|
1496
|
-
#endif
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
bool UMcpAutomationBridgeSubsystem::HandleSequenceAddKeyframe(
|
|
1500
|
-
const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
|
|
1501
|
-
TSharedPtr<FMcpBridgeWebSocket> Socket) {
|
|
1502
|
-
TSharedPtr<FJsonObject> LocalPayload =
|
|
1503
|
-
Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
|
|
1504
|
-
FString SeqPath = ResolveSequencePath(LocalPayload);
|
|
1505
|
-
if (SeqPath.IsEmpty()) {
|
|
1506
|
-
SendAutomationResponse(
|
|
1507
|
-
Socket, RequestId, false,
|
|
1508
|
-
TEXT("sequence_add_keyframe requires a sequence path"), nullptr,
|
|
1509
|
-
TEXT("INVALID_SEQUENCE"));
|
|
1510
|
-
return true;
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
|
-
FString BindingIdStr;
|
|
1514
|
-
LocalPayload->TryGetStringField(TEXT("bindingId"), BindingIdStr);
|
|
1515
|
-
FString ActorName;
|
|
1516
|
-
LocalPayload->TryGetStringField(TEXT("actorName"), ActorName);
|
|
1517
|
-
FString PropertyName;
|
|
1518
|
-
LocalPayload->TryGetStringField(TEXT("property"), PropertyName);
|
|
1519
|
-
|
|
1520
|
-
if (BindingIdStr.IsEmpty() && ActorName.IsEmpty()) {
|
|
1521
|
-
SendAutomationResponse(
|
|
1522
|
-
Socket, RequestId, false,
|
|
1523
|
-
TEXT("Either bindingId or actorName must be provided. bindingId is the "
|
|
1524
|
-
"GUID from add_actor/get_bindings. actorName is the label of an "
|
|
1525
|
-
"actor already bound to the sequence. Example: {\"actorName\": "
|
|
1526
|
-
"\"MySphere\", \"property\": \"Location\", \"frame\": 0, "
|
|
1527
|
-
"\"value\": {\"x\":0,\"y\":0,\"z\":0}}"),
|
|
1528
|
-
nullptr, TEXT("INVALID_ARGUMENT"));
|
|
1529
|
-
return true;
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
double Frame = 0.0;
|
|
1533
|
-
if (!LocalPayload->TryGetNumberField(TEXT("frame"), Frame)) {
|
|
1534
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
1535
|
-
TEXT("frame number is required. Example: "
|
|
1536
|
-
"{\"frame\": 30} for keyframe at frame 30"),
|
|
1537
|
-
nullptr, TEXT("INVALID_ARGUMENT"));
|
|
1538
|
-
return true;
|
|
1539
|
-
}
|
|
1540
|
-
|
|
1541
|
-
#if WITH_EDITOR
|
|
1542
|
-
UObject *SeqObj = UEditorAssetLibrary::LoadAsset(SeqPath);
|
|
1543
|
-
if (!SeqObj) {
|
|
1544
|
-
SendAutomationResponse(Socket, RequestId, false, TEXT("Sequence not found"),
|
|
1545
|
-
nullptr, TEXT("INVALID_SEQUENCE"));
|
|
1546
|
-
return true;
|
|
1547
|
-
}
|
|
1548
|
-
|
|
1549
|
-
if (ULevelSequence *LevelSeq = Cast<ULevelSequence>(SeqObj)) {
|
|
1550
|
-
UMovieScene *MovieScene = LevelSeq->GetMovieScene();
|
|
1551
|
-
if (MovieScene) {
|
|
1552
|
-
FGuid BindingGuid;
|
|
1553
|
-
if (!BindingIdStr.IsEmpty()) {
|
|
1554
|
-
FGuid::Parse(BindingIdStr, BindingGuid);
|
|
1555
|
-
} else if (!ActorName.IsEmpty()) {
|
|
1556
|
-
for (const FMovieSceneBinding &Binding :
|
|
1557
|
-
const_cast<const UMovieScene *>(MovieScene)->GetBindings()) {
|
|
1558
|
-
FString BindingName;
|
|
1559
|
-
if (FMovieScenePossessable *Possessable =
|
|
1560
|
-
MovieScene->FindPossessable(Binding.GetObjectGuid())) {
|
|
1561
|
-
BindingName = Possessable->GetName();
|
|
1562
|
-
} else if (FMovieSceneSpawnable *Spawnable =
|
|
1563
|
-
MovieScene->FindSpawnable(Binding.GetObjectGuid())) {
|
|
1564
|
-
BindingName = Spawnable->GetName();
|
|
1565
|
-
}
|
|
1566
|
-
|
|
1567
|
-
if (BindingName.Equals(ActorName, ESearchCase::IgnoreCase)) {
|
|
1568
|
-
BindingGuid = Binding.GetObjectGuid();
|
|
1569
|
-
break;
|
|
1570
|
-
}
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
|
|
1574
|
-
if (!BindingGuid.IsValid()) {
|
|
1575
|
-
FString Target = !BindingIdStr.IsEmpty() ? BindingIdStr : ActorName;
|
|
1576
|
-
SendAutomationResponse(
|
|
1577
|
-
Socket, RequestId, false,
|
|
1578
|
-
FString::Printf(TEXT("Binding not found for '%s'. Ensure actor is "
|
|
1579
|
-
"bound to sequence."),
|
|
1580
|
-
*Target),
|
|
1581
|
-
nullptr, TEXT("BINDING_NOT_FOUND"));
|
|
1582
|
-
return true;
|
|
1583
|
-
}
|
|
1584
|
-
|
|
1585
|
-
FMovieSceneBinding *Binding = MovieScene->FindBinding(BindingGuid);
|
|
1586
|
-
if (!Binding) {
|
|
1587
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
1588
|
-
TEXT("Binding object not found in sequence"),
|
|
1589
|
-
nullptr, TEXT("BINDING_NOT_FOUND"));
|
|
1590
|
-
return true;
|
|
1591
|
-
}
|
|
1592
|
-
|
|
1593
|
-
if (PropertyName.Equals(TEXT("Transform"), ESearchCase::IgnoreCase)) {
|
|
1594
|
-
UMovieScene3DTransformTrack *Track =
|
|
1595
|
-
MovieScene->FindTrack<UMovieScene3DTransformTrack>(
|
|
1596
|
-
BindingGuid, FName("Transform"));
|
|
1597
|
-
if (!Track) {
|
|
1598
|
-
Track =
|
|
1599
|
-
MovieScene->AddTrack<UMovieScene3DTransformTrack>(BindingGuid);
|
|
1600
|
-
}
|
|
1601
|
-
|
|
1602
|
-
if (Track) {
|
|
1603
|
-
bool bSectionAdded = false;
|
|
1604
|
-
UMovieScene3DTransformSection *Section =
|
|
1605
|
-
Cast<UMovieScene3DTransformSection>(
|
|
1606
|
-
Track->FindOrAddSection(0, bSectionAdded));
|
|
1607
|
-
if (Section) {
|
|
1608
|
-
FFrameRate TickResolution = MovieScene->GetTickResolution();
|
|
1609
|
-
FFrameRate DisplayRate = MovieScene->GetDisplayRate();
|
|
1610
|
-
FFrameNumber FrameNum = FFrameNumber(static_cast<int32>(Frame));
|
|
1611
|
-
FFrameNumber TickFrame =
|
|
1612
|
-
FFrameRate::TransformTime(FFrameTime(FrameNum), DisplayRate,
|
|
1613
|
-
TickResolution)
|
|
1614
|
-
.FloorToFrame();
|
|
1615
|
-
|
|
1616
|
-
bool bModified = false;
|
|
1617
|
-
const TSharedPtr<FJsonObject> *ValueObj = nullptr;
|
|
1618
|
-
|
|
1619
|
-
FMovieSceneChannelProxy &Proxy = Section->GetChannelProxy();
|
|
1620
|
-
TArrayView<FMovieSceneDoubleChannel *> Channels =
|
|
1621
|
-
Proxy.GetChannels<FMovieSceneDoubleChannel>();
|
|
1622
|
-
|
|
1623
|
-
if (LocalPayload->TryGetObjectField(TEXT("value"), ValueObj) &&
|
|
1624
|
-
ValueObj && Channels.Num() >= 9) {
|
|
1625
|
-
const TSharedPtr<FJsonObject> *LocObj = nullptr;
|
|
1626
|
-
if ((*ValueObj)->TryGetObjectField(TEXT("location"), LocObj)) {
|
|
1627
|
-
double X, Y, Z;
|
|
1628
|
-
if ((*LocObj)->TryGetNumberField(TEXT("x"), X)) {
|
|
1629
|
-
Channels[0]->GetData().AddKey(TickFrame,
|
|
1630
|
-
FMovieSceneDoubleValue(X));
|
|
1631
|
-
bModified = true;
|
|
1632
|
-
}
|
|
1633
|
-
if ((*LocObj)->TryGetNumberField(TEXT("y"), Y)) {
|
|
1634
|
-
Channels[1]->GetData().AddKey(TickFrame,
|
|
1635
|
-
FMovieSceneDoubleValue(Y));
|
|
1636
|
-
bModified = true;
|
|
1637
|
-
}
|
|
1638
|
-
if ((*LocObj)->TryGetNumberField(TEXT("z"), Z)) {
|
|
1639
|
-
Channels[2]->GetData().AddKey(TickFrame,
|
|
1640
|
-
FMovieSceneDoubleValue(Z));
|
|
1641
|
-
bModified = true;
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
|
-
|
|
1645
|
-
const TSharedPtr<FJsonObject> *RotObj = nullptr;
|
|
1646
|
-
if ((*ValueObj)->TryGetObjectField(TEXT("rotation"), RotObj)) {
|
|
1647
|
-
double P, Yaw, R;
|
|
1648
|
-
// 0=Roll(X), 1=Pitch(Y), 2=Yaw(Z) in Transform Track channels
|
|
1649
|
-
// usually. Channels 3, 4, 5.
|
|
1650
|
-
if ((*RotObj)->TryGetNumberField(TEXT("roll"), R)) {
|
|
1651
|
-
Channels[3]->GetData().AddKey(TickFrame,
|
|
1652
|
-
FMovieSceneDoubleValue(R));
|
|
1653
|
-
bModified = true;
|
|
1654
|
-
}
|
|
1655
|
-
if ((*RotObj)->TryGetNumberField(TEXT("pitch"), P)) {
|
|
1656
|
-
Channels[4]->GetData().AddKey(TickFrame,
|
|
1657
|
-
FMovieSceneDoubleValue(P));
|
|
1658
|
-
bModified = true;
|
|
1659
|
-
}
|
|
1660
|
-
if ((*RotObj)->TryGetNumberField(TEXT("yaw"), Yaw)) {
|
|
1661
|
-
Channels[5]->GetData().AddKey(TickFrame,
|
|
1662
|
-
FMovieSceneDoubleValue(Yaw));
|
|
1663
|
-
bModified = true;
|
|
1664
|
-
}
|
|
1665
|
-
}
|
|
1666
|
-
|
|
1667
|
-
const TSharedPtr<FJsonObject> *ScaleObj = nullptr;
|
|
1668
|
-
if ((*ValueObj)->TryGetObjectField(TEXT("scale"), ScaleObj)) {
|
|
1669
|
-
double X, Y, Z;
|
|
1670
|
-
if ((*ScaleObj)->TryGetNumberField(TEXT("x"), X)) {
|
|
1671
|
-
Channels[6]->GetData().AddKey(TickFrame,
|
|
1672
|
-
FMovieSceneDoubleValue(X));
|
|
1673
|
-
bModified = true;
|
|
1674
|
-
}
|
|
1675
|
-
if ((*ScaleObj)->TryGetNumberField(TEXT("y"), Y)) {
|
|
1676
|
-
Channels[7]->GetData().AddKey(TickFrame,
|
|
1677
|
-
FMovieSceneDoubleValue(Y));
|
|
1678
|
-
bModified = true;
|
|
1679
|
-
}
|
|
1680
|
-
if ((*ScaleObj)->TryGetNumberField(TEXT("z"), Z)) {
|
|
1681
|
-
Channels[8]->GetData().AddKey(TickFrame,
|
|
1682
|
-
FMovieSceneDoubleValue(Z));
|
|
1683
|
-
bModified = true;
|
|
1684
|
-
}
|
|
1685
|
-
}
|
|
1686
|
-
}
|
|
1687
|
-
|
|
1688
|
-
if (bModified) {
|
|
1689
|
-
MovieScene->Modify();
|
|
1690
|
-
SendAutomationResponse(Socket, RequestId, true,
|
|
1691
|
-
TEXT("Keyframe added"), nullptr,
|
|
1692
|
-
FString());
|
|
1693
|
-
return true;
|
|
1694
|
-
}
|
|
1695
|
-
}
|
|
1696
|
-
}
|
|
1697
|
-
} else {
|
|
1698
|
-
// Try generic property tracks
|
|
1699
|
-
const TSharedPtr<FJsonValue> Val =
|
|
1700
|
-
LocalPayload->TryGetField(TEXT("value"));
|
|
1701
|
-
if (Val.IsValid() && Val->Type == EJson::Number) {
|
|
1702
|
-
UMovieSceneFloatTrack *Track =
|
|
1703
|
-
MovieScene->FindTrack<UMovieSceneFloatTrack>(
|
|
1704
|
-
BindingGuid, FName(*PropertyName));
|
|
1705
|
-
if (!Track) {
|
|
1706
|
-
Track = MovieScene->AddTrack<UMovieSceneFloatTrack>(BindingGuid);
|
|
1707
|
-
if (Track)
|
|
1708
|
-
Track->SetPropertyNameAndPath(FName(*PropertyName), PropertyName);
|
|
1709
|
-
}
|
|
1710
|
-
if (Track) {
|
|
1711
|
-
bool bSectionAdded = false;
|
|
1712
|
-
UMovieSceneFloatSection *Section = Cast<UMovieSceneFloatSection>(
|
|
1713
|
-
Track->FindOrAddSection(0, bSectionAdded));
|
|
1714
|
-
if (Section) {
|
|
1715
|
-
FFrameRate TickResolution = MovieScene->GetTickResolution();
|
|
1716
|
-
FFrameRate DisplayRate = MovieScene->GetDisplayRate();
|
|
1717
|
-
FFrameNumber FrameNum = FFrameNumber(static_cast<int32>(Frame));
|
|
1718
|
-
FFrameNumber TickFrame =
|
|
1719
|
-
FFrameRate::TransformTime(FFrameTime(FrameNum), DisplayRate,
|
|
1720
|
-
TickResolution)
|
|
1721
|
-
.FloorToFrame();
|
|
1722
|
-
|
|
1723
|
-
FMovieSceneFloatChannel *Channel =
|
|
1724
|
-
Section->GetChannelProxy()
|
|
1725
|
-
.GetChannel<FMovieSceneFloatChannel>(0);
|
|
1726
|
-
if (Channel) {
|
|
1727
|
-
Channel->GetData().UpdateOrAddKey(
|
|
1728
|
-
TickFrame, FMovieSceneFloatValue((float)Val->AsNumber()));
|
|
1729
|
-
MovieScene->Modify();
|
|
1730
|
-
SendAutomationResponse(Socket, RequestId, true,
|
|
1731
|
-
TEXT("Float Keyframe added"), nullptr);
|
|
1732
|
-
return true;
|
|
1733
|
-
}
|
|
1734
|
-
}
|
|
1735
|
-
}
|
|
1736
|
-
} else if (Val.IsValid() && Val->Type == EJson::Boolean) {
|
|
1737
|
-
UMovieSceneBoolTrack *Track =
|
|
1738
|
-
MovieScene->FindTrack<UMovieSceneBoolTrack>(BindingGuid,
|
|
1739
|
-
FName(*PropertyName));
|
|
1740
|
-
if (!Track) {
|
|
1741
|
-
Track = MovieScene->AddTrack<UMovieSceneBoolTrack>(BindingGuid);
|
|
1742
|
-
if (Track)
|
|
1743
|
-
Track->SetPropertyNameAndPath(FName(*PropertyName), PropertyName);
|
|
1744
|
-
}
|
|
1745
|
-
if (Track) {
|
|
1746
|
-
bool bSectionAdded = false;
|
|
1747
|
-
UMovieSceneBoolSection *Section = Cast<UMovieSceneBoolSection>(
|
|
1748
|
-
Track->FindOrAddSection(0, bSectionAdded));
|
|
1749
|
-
if (Section) {
|
|
1750
|
-
FFrameRate TickResolution = MovieScene->GetTickResolution();
|
|
1751
|
-
FFrameRate DisplayRate = MovieScene->GetDisplayRate();
|
|
1752
|
-
FFrameNumber FrameNum = FFrameNumber(static_cast<int32>(Frame));
|
|
1753
|
-
FFrameNumber TickFrame =
|
|
1754
|
-
FFrameRate::TransformTime(FFrameTime(FrameNum), DisplayRate,
|
|
1755
|
-
TickResolution)
|
|
1756
|
-
.FloorToFrame();
|
|
1757
|
-
|
|
1758
|
-
FMovieSceneBoolChannel *Channel =
|
|
1759
|
-
Section->GetChannelProxy().GetChannel<FMovieSceneBoolChannel>(
|
|
1760
|
-
0);
|
|
1761
|
-
if (Channel) {
|
|
1762
|
-
Channel->GetData().UpdateOrAddKey(TickFrame, Val->AsBool());
|
|
1763
|
-
MovieScene->Modify();
|
|
1764
|
-
SendAutomationResponse(Socket, RequestId, true,
|
|
1765
|
-
TEXT("Bool Keyframe added"), nullptr);
|
|
1766
|
-
return true;
|
|
1767
|
-
}
|
|
1768
|
-
}
|
|
1769
|
-
}
|
|
1770
|
-
}
|
|
1771
|
-
}
|
|
1772
|
-
|
|
1773
|
-
SendAutomationResponse(
|
|
1774
|
-
Socket, RequestId, false,
|
|
1775
|
-
TEXT("Unsupported property or failed to create track"), nullptr,
|
|
1776
|
-
TEXT("UNSUPPORTED_PROPERTY"));
|
|
1777
|
-
return true;
|
|
1778
|
-
}
|
|
1779
|
-
}
|
|
1780
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
1781
|
-
TEXT("Sequence object is not a LevelSequence"),
|
|
1782
|
-
nullptr, TEXT("INVALID_SEQUENCE_TYPE"));
|
|
1783
|
-
return true;
|
|
1784
|
-
#else
|
|
1785
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
1786
|
-
TEXT("sequence_add_keyframe requires editor build."),
|
|
1787
|
-
nullptr, TEXT("NOT_IMPLEMENTED"));
|
|
1788
|
-
return true;
|
|
1789
|
-
#endif
|
|
1790
|
-
}
|
|
1791
|
-
|
|
1792
|
-
bool UMcpAutomationBridgeSubsystem::HandleSequenceAddSection(
|
|
1793
|
-
const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
|
|
1794
|
-
TSharedPtr<FMcpBridgeWebSocket> Socket) {
|
|
1795
|
-
#if WITH_EDITOR
|
|
1796
|
-
FString SeqPath = ResolveSequencePath(Payload);
|
|
1797
|
-
if (SeqPath.IsEmpty()) {
|
|
1798
|
-
SendAutomationResponse(
|
|
1799
|
-
Socket, RequestId, false,
|
|
1800
|
-
TEXT("sequence_add_section requires a sequence path"), nullptr,
|
|
1801
|
-
TEXT("INVALID_SEQUENCE"));
|
|
1802
|
-
return true;
|
|
1803
|
-
}
|
|
1804
|
-
|
|
1805
|
-
FString TrackName;
|
|
1806
|
-
Payload->TryGetStringField(TEXT("trackName"), TrackName);
|
|
1807
|
-
FString ActorName;
|
|
1808
|
-
Payload->TryGetStringField(TEXT("actorName"), ActorName);
|
|
1809
|
-
double StartFrame = 0.0, EndFrame = 100.0;
|
|
1810
|
-
Payload->TryGetNumberField(TEXT("startFrame"), StartFrame);
|
|
1811
|
-
Payload->TryGetNumberField(TEXT("endFrame"), EndFrame);
|
|
1812
|
-
|
|
1813
|
-
ULevelSequence *Sequence = LoadObject<ULevelSequence>(nullptr, *SeqPath);
|
|
1814
|
-
if (!Sequence || !Sequence->GetMovieScene()) {
|
|
1815
|
-
SendAutomationResponse(Socket, RequestId, false, TEXT("Sequence not found"),
|
|
1816
|
-
nullptr, TEXT("SEQUENCE_NOT_FOUND"));
|
|
1817
|
-
return true;
|
|
1818
|
-
}
|
|
1819
|
-
|
|
1820
|
-
UMovieScene *MovieScene = Sequence->GetMovieScene();
|
|
1821
|
-
|
|
1822
|
-
// Find the track - either from master tracks or from actor binding
|
|
1823
|
-
UMovieSceneTrack *Track = nullptr;
|
|
1824
|
-
|
|
1825
|
-
// First check master tracks
|
|
1826
|
-
for (UMovieSceneTrack *MasterTrack : MovieScene->GetTracks()) {
|
|
1827
|
-
if (MasterTrack &&
|
|
1828
|
-
(MasterTrack->GetName().Contains(TrackName) ||
|
|
1829
|
-
MasterTrack->GetDisplayName().ToString().Contains(TrackName))) {
|
|
1830
|
-
Track = MasterTrack;
|
|
1831
|
-
break;
|
|
1832
|
-
}
|
|
1833
|
-
}
|
|
1834
|
-
|
|
1835
|
-
// If not found in master tracks, check bindings
|
|
1836
|
-
// Search all bindings if ActorName is empty, or filter by ActorName if
|
|
1837
|
-
// provided
|
|
1838
|
-
if (!Track) {
|
|
1839
|
-
for (const FMovieSceneBinding &Binding :
|
|
1840
|
-
const_cast<const UMovieScene *>(MovieScene)->GetBindings()) {
|
|
1841
|
-
FString BindingName;
|
|
1842
|
-
if (FMovieScenePossessable *Possessable =
|
|
1843
|
-
MovieScene->FindPossessable(Binding.GetObjectGuid())) {
|
|
1844
|
-
BindingName = Possessable->GetName();
|
|
1845
|
-
} else if (FMovieSceneSpawnable *Spawnable =
|
|
1846
|
-
MovieScene->FindSpawnable(Binding.GetObjectGuid())) {
|
|
1847
|
-
BindingName = Spawnable->GetName();
|
|
1848
|
-
}
|
|
1849
|
-
|
|
1850
|
-
// If ActorName is provided, filter by it; otherwise search all bindings
|
|
1851
|
-
if (ActorName.IsEmpty() || BindingName.Contains(ActorName)) {
|
|
1852
|
-
for (UMovieSceneTrack *BindingTrack : Binding.GetTracks()) {
|
|
1853
|
-
if (BindingTrack &&
|
|
1854
|
-
(BindingTrack->GetName().Contains(TrackName) ||
|
|
1855
|
-
BindingTrack->GetDisplayName().ToString().Contains(TrackName))) {
|
|
1856
|
-
Track = BindingTrack;
|
|
1857
|
-
break;
|
|
1858
|
-
}
|
|
1859
|
-
}
|
|
1860
|
-
if (Track)
|
|
1861
|
-
break;
|
|
1862
|
-
}
|
|
1863
|
-
}
|
|
1864
|
-
}
|
|
1865
|
-
|
|
1866
|
-
if (!Track) {
|
|
1867
|
-
SendAutomationResponse(Socket, RequestId, false, TEXT("Track not found"),
|
|
1868
|
-
nullptr, TEXT("TRACK_NOT_FOUND"));
|
|
1869
|
-
return true;
|
|
1870
|
-
}
|
|
1871
|
-
|
|
1872
|
-
// Create the section
|
|
1873
|
-
UMovieSceneSection *NewSection = Track->CreateNewSection();
|
|
1874
|
-
if (NewSection) {
|
|
1875
|
-
FFrameRate TickResolution = MovieScene->GetTickResolution();
|
|
1876
|
-
FFrameNumber Start((int32)FMath::RoundToInt(StartFrame));
|
|
1877
|
-
FFrameNumber End((int32)FMath::RoundToInt(EndFrame));
|
|
1878
|
-
NewSection->SetRange(TRange<FFrameNumber>(Start, End));
|
|
1879
|
-
Track->AddSection(*NewSection);
|
|
1880
|
-
MovieScene->Modify();
|
|
1881
|
-
|
|
1882
|
-
TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
|
|
1883
|
-
Resp->SetStringField(TEXT("trackName"), Track->GetName());
|
|
1884
|
-
Resp->SetNumberField(TEXT("startFrame"), StartFrame);
|
|
1885
|
-
Resp->SetNumberField(TEXT("endFrame"), EndFrame);
|
|
1886
|
-
SendAutomationResponse(Socket, RequestId, true,
|
|
1887
|
-
TEXT("Section added to track"), Resp);
|
|
1888
|
-
} else {
|
|
1889
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
1890
|
-
TEXT("Failed to create section"), nullptr,
|
|
1891
|
-
TEXT("SECTION_CREATION_FAILED"));
|
|
1892
|
-
}
|
|
1893
|
-
return true;
|
|
1894
|
-
#else
|
|
1895
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
1896
|
-
TEXT("sequence_add_section requires editor build"),
|
|
1897
|
-
nullptr, TEXT("EDITOR_ONLY"));
|
|
1898
|
-
return true;
|
|
1899
|
-
#endif
|
|
1900
|
-
}
|
|
1901
|
-
|
|
1902
|
-
bool UMcpAutomationBridgeSubsystem::HandleSequenceSetTickResolution(
|
|
1903
|
-
const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
|
|
1904
|
-
TSharedPtr<FMcpBridgeWebSocket> Socket) {
|
|
1905
|
-
#if WITH_EDITOR
|
|
1906
|
-
FString ResolutionStr;
|
|
1907
|
-
Payload->TryGetStringField(TEXT("resolution"), ResolutionStr);
|
|
1908
|
-
|
|
1909
|
-
FString SeqPath = ResolveSequencePath(Payload);
|
|
1910
|
-
if (SeqPath.IsEmpty()) {
|
|
1911
|
-
SendAutomationResponse(Socket, RequestId, false, TEXT("path required"),
|
|
1912
|
-
nullptr, TEXT("INVALID_ARGUMENT"));
|
|
1913
|
-
return true;
|
|
1914
|
-
}
|
|
1915
|
-
|
|
1916
|
-
ULevelSequence *Sequence = LoadObject<ULevelSequence>(nullptr, *SeqPath);
|
|
1917
|
-
if (Sequence && Sequence->GetMovieScene()) {
|
|
1918
|
-
FFrameRate TickResolution;
|
|
1919
|
-
// Simplified parsing
|
|
1920
|
-
if (ResolutionStr.Contains(TEXT("24000")))
|
|
1921
|
-
TickResolution = FFrameRate(24000, 1);
|
|
1922
|
-
else if (ResolutionStr.Contains(TEXT("60000")))
|
|
1923
|
-
TickResolution = FFrameRate(60000, 1);
|
|
1924
|
-
else
|
|
1925
|
-
TickResolution = FFrameRate(24000, 1); // Default
|
|
1926
|
-
|
|
1927
|
-
Sequence->GetMovieScene()->SetTickResolutionDirectly(TickResolution);
|
|
1928
|
-
Sequence->GetMovieScene()->Modify();
|
|
1929
|
-
SendAutomationResponse(Socket, RequestId, true, TEXT("Tick resolution set"),
|
|
1930
|
-
nullptr);
|
|
1931
|
-
} else {
|
|
1932
|
-
SendAutomationResponse(Socket, RequestId, false, TEXT("Sequence not found"),
|
|
1933
|
-
nullptr, TEXT("NOT_FOUND"));
|
|
1934
|
-
}
|
|
1935
|
-
return true;
|
|
1936
|
-
#else
|
|
1937
|
-
return false;
|
|
1938
|
-
#endif
|
|
1939
|
-
}
|
|
1940
|
-
|
|
1941
|
-
bool UMcpAutomationBridgeSubsystem::HandleSequenceSetViewRange(
|
|
1942
|
-
const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
|
|
1943
|
-
TSharedPtr<FMcpBridgeWebSocket> Socket) {
|
|
1944
|
-
#if WITH_EDITOR
|
|
1945
|
-
double Start = 0;
|
|
1946
|
-
double End = 10;
|
|
1947
|
-
Payload->TryGetNumberField(TEXT("start"), Start);
|
|
1948
|
-
Payload->TryGetNumberField(TEXT("end"), End);
|
|
1949
|
-
FString SeqPath = ResolveSequencePath(Payload);
|
|
1950
|
-
|
|
1951
|
-
if (SeqPath.IsEmpty()) {
|
|
1952
|
-
SendAutomationResponse(Socket, RequestId, false, TEXT("path required"),
|
|
1953
|
-
nullptr, TEXT("INVALID_ARGUMENT"));
|
|
1954
|
-
return true;
|
|
1955
|
-
}
|
|
1956
|
-
|
|
1957
|
-
ULevelSequence *Sequence = LoadObject<ULevelSequence>(nullptr, *SeqPath);
|
|
1958
|
-
if (Sequence && Sequence->GetMovieScene()) {
|
|
1959
|
-
Sequence->GetMovieScene()->SetViewRange(Start, End);
|
|
1960
|
-
Sequence->GetMovieScene()->Modify();
|
|
1961
|
-
SendAutomationResponse(Socket, RequestId, true, TEXT("View range set"),
|
|
1962
|
-
nullptr);
|
|
1963
|
-
} else {
|
|
1964
|
-
SendAutomationResponse(Socket, RequestId, false, TEXT("Sequence not found"),
|
|
1965
|
-
nullptr, TEXT("NOT_FOUND"));
|
|
1966
|
-
}
|
|
1967
|
-
return true;
|
|
1968
|
-
#else
|
|
1969
|
-
return false;
|
|
1970
|
-
#endif
|
|
1971
|
-
}
|
|
1972
|
-
|
|
1973
|
-
bool UMcpAutomationBridgeSubsystem::HandleSequenceSetTrackMuted(
|
|
1974
|
-
const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
|
|
1975
|
-
TSharedPtr<FMcpBridgeWebSocket> Socket) {
|
|
1976
|
-
#if WITH_EDITOR
|
|
1977
|
-
FString SeqPath = ResolveSequencePath(Payload);
|
|
1978
|
-
if (SeqPath.IsEmpty()) {
|
|
1979
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
1980
|
-
TEXT("sequence path required"), nullptr,
|
|
1981
|
-
TEXT("INVALID_SEQUENCE"));
|
|
1982
|
-
return true;
|
|
1983
|
-
}
|
|
1984
|
-
|
|
1985
|
-
FString TrackName;
|
|
1986
|
-
Payload->TryGetStringField(TEXT("trackName"), TrackName);
|
|
1987
|
-
bool bMuted = true;
|
|
1988
|
-
Payload->TryGetBoolField(TEXT("muted"), bMuted);
|
|
1989
|
-
|
|
1990
|
-
ULevelSequence *Sequence = LoadObject<ULevelSequence>(nullptr, *SeqPath);
|
|
1991
|
-
if (!Sequence || !Sequence->GetMovieScene()) {
|
|
1992
|
-
SendAutomationResponse(Socket, RequestId, false, TEXT("Sequence not found"),
|
|
1993
|
-
nullptr, TEXT("SEQUENCE_NOT_FOUND"));
|
|
1994
|
-
return true;
|
|
1995
|
-
}
|
|
1996
|
-
|
|
1997
|
-
UMovieScene *MovieScene = Sequence->GetMovieScene();
|
|
1998
|
-
UMovieSceneTrack *Track = nullptr;
|
|
1999
|
-
|
|
2000
|
-
// Search master tracks and binding tracks
|
|
2001
|
-
for (UMovieSceneTrack *MasterTrack : MovieScene->GetTracks()) {
|
|
2002
|
-
if (MasterTrack && MasterTrack->GetName().Contains(TrackName)) {
|
|
2003
|
-
Track = MasterTrack;
|
|
2004
|
-
break;
|
|
2005
|
-
}
|
|
2006
|
-
}
|
|
2007
|
-
|
|
2008
|
-
if (!Track) {
|
|
2009
|
-
for (const FMovieSceneBinding &Binding :
|
|
2010
|
-
const_cast<const UMovieScene *>(MovieScene)->GetBindings()) {
|
|
2011
|
-
for (UMovieSceneTrack *BindingTrack : Binding.GetTracks()) {
|
|
2012
|
-
if (BindingTrack && BindingTrack->GetName().Contains(TrackName)) {
|
|
2013
|
-
Track = BindingTrack;
|
|
2014
|
-
break;
|
|
2015
|
-
}
|
|
2016
|
-
}
|
|
2017
|
-
if (Track)
|
|
2018
|
-
break;
|
|
2019
|
-
}
|
|
2020
|
-
}
|
|
2021
|
-
|
|
2022
|
-
if (!Track) {
|
|
2023
|
-
SendAutomationResponse(Socket, RequestId, false, TEXT("Track not found"),
|
|
2024
|
-
nullptr, TEXT("TRACK_NOT_FOUND"));
|
|
2025
|
-
return true;
|
|
2026
|
-
}
|
|
2027
|
-
|
|
2028
|
-
// Set muted state via EvalOptions
|
|
2029
|
-
Track->SetEvalDisabled(bMuted);
|
|
2030
|
-
MovieScene->Modify();
|
|
2031
|
-
|
|
2032
|
-
TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
|
|
2033
|
-
Resp->SetStringField(TEXT("trackName"), Track->GetName());
|
|
2034
|
-
Resp->SetBoolField(TEXT("muted"), bMuted);
|
|
2035
|
-
SendAutomationResponse(Socket, RequestId, true,
|
|
2036
|
-
bMuted ? TEXT("Track muted") : TEXT("Track unmuted"),
|
|
2037
|
-
Resp);
|
|
2038
|
-
return true;
|
|
2039
|
-
#else
|
|
2040
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
2041
|
-
TEXT("sequence_set_track_muted requires editor build"),
|
|
2042
|
-
nullptr, TEXT("EDITOR_ONLY"));
|
|
2043
|
-
return true;
|
|
2044
|
-
#endif
|
|
2045
|
-
}
|
|
2046
|
-
|
|
2047
|
-
bool UMcpAutomationBridgeSubsystem::HandleSequenceSetTrackSolo(
|
|
2048
|
-
const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
|
|
2049
|
-
TSharedPtr<FMcpBridgeWebSocket> Socket) {
|
|
2050
|
-
#if WITH_EDITOR
|
|
2051
|
-
// Note: UE doesn't have a direct "solo" property on tracks, but we can
|
|
2052
|
-
// simulate it by muting all other tracks
|
|
2053
|
-
FString SeqPath = ResolveSequencePath(Payload);
|
|
2054
|
-
if (SeqPath.IsEmpty()) {
|
|
2055
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
2056
|
-
TEXT("sequence path required"), nullptr,
|
|
2057
|
-
TEXT("INVALID_SEQUENCE"));
|
|
2058
|
-
return true;
|
|
2059
|
-
}
|
|
2060
|
-
|
|
2061
|
-
FString TrackName;
|
|
2062
|
-
Payload->TryGetStringField(TEXT("trackName"), TrackName);
|
|
2063
|
-
bool bSolo = true;
|
|
2064
|
-
Payload->TryGetBoolField(TEXT("solo"), bSolo);
|
|
2065
|
-
|
|
2066
|
-
ULevelSequence *Sequence = LoadObject<ULevelSequence>(nullptr, *SeqPath);
|
|
2067
|
-
if (!Sequence || !Sequence->GetMovieScene()) {
|
|
2068
|
-
SendAutomationResponse(Socket, RequestId, false, TEXT("Sequence not found"),
|
|
2069
|
-
nullptr, TEXT("SEQUENCE_NOT_FOUND"));
|
|
2070
|
-
return true;
|
|
2071
|
-
}
|
|
2072
|
-
|
|
2073
|
-
UMovieScene *MovieScene = Sequence->GetMovieScene();
|
|
2074
|
-
UMovieSceneTrack *SoloTrack = nullptr;
|
|
2075
|
-
|
|
2076
|
-
// Find the track to solo
|
|
2077
|
-
TArray<UMovieSceneTrack *> AllTracks;
|
|
2078
|
-
for (UMovieSceneTrack *Track : MovieScene->GetTracks()) {
|
|
2079
|
-
if (Track) {
|
|
2080
|
-
AllTracks.Add(Track);
|
|
2081
|
-
if (Track->GetName().Contains(TrackName)) {
|
|
2082
|
-
SoloTrack = Track;
|
|
2083
|
-
}
|
|
2084
|
-
}
|
|
2085
|
-
}
|
|
2086
|
-
|
|
2087
|
-
for (const FMovieSceneBinding &Binding :
|
|
2088
|
-
const_cast<const UMovieScene *>(MovieScene)->GetBindings()) {
|
|
2089
|
-
for (UMovieSceneTrack *Track : Binding.GetTracks()) {
|
|
2090
|
-
if (Track) {
|
|
2091
|
-
AllTracks.Add(Track);
|
|
2092
|
-
if (Track->GetName().Contains(TrackName)) {
|
|
2093
|
-
SoloTrack = Track;
|
|
2094
|
-
}
|
|
2095
|
-
}
|
|
2096
|
-
}
|
|
2097
|
-
}
|
|
2098
|
-
|
|
2099
|
-
if (!SoloTrack) {
|
|
2100
|
-
SendAutomationResponse(Socket, RequestId, false, TEXT("Track not found"),
|
|
2101
|
-
nullptr, TEXT("TRACK_NOT_FOUND"));
|
|
2102
|
-
return true;
|
|
2103
|
-
}
|
|
2104
|
-
|
|
2105
|
-
// If enabling solo, mute all other tracks; if disabling, unmute all
|
|
2106
|
-
for (UMovieSceneTrack *Track : AllTracks) {
|
|
2107
|
-
if (bSolo) {
|
|
2108
|
-
Track->SetEvalDisabled(Track != SoloTrack);
|
|
2109
|
-
} else {
|
|
2110
|
-
Track->SetEvalDisabled(false);
|
|
2111
|
-
}
|
|
2112
|
-
}
|
|
2113
|
-
MovieScene->Modify();
|
|
2114
|
-
|
|
2115
|
-
TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
|
|
2116
|
-
Resp->SetStringField(TEXT("trackName"), SoloTrack->GetName());
|
|
2117
|
-
Resp->SetBoolField(TEXT("solo"), bSolo);
|
|
2118
|
-
SendAutomationResponse(
|
|
2119
|
-
Socket, RequestId, true,
|
|
2120
|
-
bSolo ? TEXT("Track solo enabled") : TEXT("Solo disabled"), Resp);
|
|
2121
|
-
return true;
|
|
2122
|
-
#else
|
|
2123
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
2124
|
-
TEXT("sequence_set_track_solo requires editor build"),
|
|
2125
|
-
nullptr, TEXT("EDITOR_ONLY"));
|
|
2126
|
-
return true;
|
|
2127
|
-
#endif
|
|
2128
|
-
}
|
|
2129
|
-
|
|
2130
|
-
bool UMcpAutomationBridgeSubsystem::HandleSequenceSetTrackLocked(
|
|
2131
|
-
const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
|
|
2132
|
-
TSharedPtr<FMcpBridgeWebSocket> Socket) {
|
|
2133
|
-
#if WITH_EDITOR
|
|
2134
|
-
FString SeqPath = ResolveSequencePath(Payload);
|
|
2135
|
-
if (SeqPath.IsEmpty()) {
|
|
2136
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
2137
|
-
TEXT("sequence path required"), nullptr,
|
|
2138
|
-
TEXT("INVALID_SEQUENCE"));
|
|
2139
|
-
return true;
|
|
2140
|
-
}
|
|
2141
|
-
|
|
2142
|
-
FString TrackName;
|
|
2143
|
-
Payload->TryGetStringField(TEXT("trackName"), TrackName);
|
|
2144
|
-
bool bLocked = true;
|
|
2145
|
-
Payload->TryGetBoolField(TEXT("locked"), bLocked);
|
|
2146
|
-
|
|
2147
|
-
ULevelSequence *Sequence = LoadObject<ULevelSequence>(nullptr, *SeqPath);
|
|
2148
|
-
if (!Sequence || !Sequence->GetMovieScene()) {
|
|
2149
|
-
SendAutomationResponse(Socket, RequestId, false, TEXT("Sequence not found"),
|
|
2150
|
-
nullptr, TEXT("SEQUENCE_NOT_FOUND"));
|
|
2151
|
-
return true;
|
|
2152
|
-
}
|
|
2153
|
-
|
|
2154
|
-
UMovieScene *MovieScene = Sequence->GetMovieScene();
|
|
2155
|
-
UMovieSceneTrack *Track = nullptr;
|
|
2156
|
-
|
|
2157
|
-
// Search for track
|
|
2158
|
-
for (UMovieSceneTrack *MasterTrack : MovieScene->GetTracks()) {
|
|
2159
|
-
if (MasterTrack && MasterTrack->GetName().Contains(TrackName)) {
|
|
2160
|
-
Track = MasterTrack;
|
|
2161
|
-
break;
|
|
2162
|
-
}
|
|
2163
|
-
}
|
|
2164
|
-
|
|
2165
|
-
if (!Track) {
|
|
2166
|
-
for (const FMovieSceneBinding &Binding :
|
|
2167
|
-
const_cast<const UMovieScene *>(MovieScene)->GetBindings()) {
|
|
2168
|
-
for (UMovieSceneTrack *BindingTrack : Binding.GetTracks()) {
|
|
2169
|
-
if (BindingTrack && BindingTrack->GetName().Contains(TrackName)) {
|
|
2170
|
-
Track = BindingTrack;
|
|
2171
|
-
break;
|
|
2172
|
-
}
|
|
2173
|
-
}
|
|
2174
|
-
if (Track)
|
|
2175
|
-
break;
|
|
2176
|
-
}
|
|
2177
|
-
}
|
|
2178
|
-
|
|
2179
|
-
if (!Track) {
|
|
2180
|
-
SendAutomationResponse(Socket, RequestId, false, TEXT("Track not found"),
|
|
2181
|
-
nullptr, TEXT("TRACK_NOT_FOUND"));
|
|
2182
|
-
return true;
|
|
2183
|
-
}
|
|
2184
|
-
|
|
2185
|
-
// Lock all sections in the track
|
|
2186
|
-
for (UMovieSceneSection *Section : Track->GetAllSections()) {
|
|
2187
|
-
if (Section) {
|
|
2188
|
-
Section->SetIsLocked(bLocked);
|
|
2189
|
-
}
|
|
2190
|
-
}
|
|
2191
|
-
MovieScene->Modify();
|
|
2192
|
-
|
|
2193
|
-
TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
|
|
2194
|
-
Resp->SetStringField(TEXT("trackName"), Track->GetName());
|
|
2195
|
-
Resp->SetBoolField(TEXT("locked"), bLocked);
|
|
2196
|
-
SendAutomationResponse(
|
|
2197
|
-
Socket, RequestId, true,
|
|
2198
|
-
bLocked ? TEXT("Track locked") : TEXT("Track unlocked"), Resp);
|
|
2199
|
-
return true;
|
|
2200
|
-
#else
|
|
2201
|
-
SendAutomationResponse(
|
|
2202
|
-
Socket, RequestId, false,
|
|
2203
|
-
TEXT("sequence_set_track_locked requires editor build"), nullptr,
|
|
2204
|
-
TEXT("EDITOR_ONLY"));
|
|
2205
|
-
return true;
|
|
2206
|
-
#endif
|
|
2207
|
-
}
|
|
2208
|
-
|
|
2209
|
-
bool UMcpAutomationBridgeSubsystem::HandleSequenceRemoveTrack(
|
|
2210
|
-
const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
|
|
2211
|
-
TSharedPtr<FMcpBridgeWebSocket> Socket) {
|
|
2212
|
-
#if WITH_EDITOR
|
|
2213
|
-
FString SeqPath = ResolveSequencePath(Payload);
|
|
2214
|
-
if (SeqPath.IsEmpty()) {
|
|
2215
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
2216
|
-
TEXT("sequence path required"), nullptr,
|
|
2217
|
-
TEXT("INVALID_SEQUENCE"));
|
|
2218
|
-
return true;
|
|
2219
|
-
}
|
|
2220
|
-
|
|
2221
|
-
FString TrackName;
|
|
2222
|
-
Payload->TryGetStringField(TEXT("trackName"), TrackName);
|
|
2223
|
-
|
|
2224
|
-
ULevelSequence *Sequence = LoadObject<ULevelSequence>(nullptr, *SeqPath);
|
|
2225
|
-
if (!Sequence || !Sequence->GetMovieScene()) {
|
|
2226
|
-
SendAutomationResponse(Socket, RequestId, false, TEXT("Sequence not found"),
|
|
2227
|
-
nullptr, TEXT("SEQUENCE_NOT_FOUND"));
|
|
2228
|
-
return true;
|
|
2229
|
-
}
|
|
2230
|
-
|
|
2231
|
-
UMovieScene *MovieScene = Sequence->GetMovieScene();
|
|
2232
|
-
bool bRemoved = false;
|
|
2233
|
-
FString RemovedTrackName;
|
|
2234
|
-
|
|
2235
|
-
// Try to remove from master tracks first
|
|
2236
|
-
for (UMovieSceneTrack *Track : MovieScene->GetTracks()) {
|
|
2237
|
-
if (Track && Track->GetName().Contains(TrackName)) {
|
|
2238
|
-
RemovedTrackName = Track->GetName();
|
|
2239
|
-
MovieScene->RemoveTrack(*Track);
|
|
2240
|
-
bRemoved = true;
|
|
2241
|
-
break;
|
|
2242
|
-
}
|
|
2243
|
-
}
|
|
2244
|
-
|
|
2245
|
-
// If not found, try binding tracks
|
|
2246
|
-
if (!bRemoved) {
|
|
2247
|
-
for (const FMovieSceneBinding &Binding :
|
|
2248
|
-
const_cast<const UMovieScene *>(MovieScene)->GetBindings()) {
|
|
2249
|
-
for (UMovieSceneTrack *Track : Binding.GetTracks()) {
|
|
2250
|
-
if (Track && Track->GetName().Contains(TrackName)) {
|
|
2251
|
-
RemovedTrackName = Track->GetName();
|
|
2252
|
-
MovieScene->RemoveTrack(*Track);
|
|
2253
|
-
bRemoved = true;
|
|
2254
|
-
break;
|
|
2255
|
-
}
|
|
2256
|
-
}
|
|
2257
|
-
if (bRemoved)
|
|
2258
|
-
break;
|
|
2259
|
-
}
|
|
2260
|
-
}
|
|
2261
|
-
|
|
2262
|
-
if (bRemoved) {
|
|
2263
|
-
MovieScene->Modify();
|
|
2264
|
-
TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
|
|
2265
|
-
Resp->SetStringField(TEXT("trackName"), RemovedTrackName);
|
|
2266
|
-
SendAutomationResponse(Socket, RequestId, true, TEXT("Track removed"),
|
|
2267
|
-
Resp);
|
|
2268
|
-
} else {
|
|
2269
|
-
SendAutomationResponse(Socket, RequestId, false, TEXT("Track not found"),
|
|
2270
|
-
nullptr, TEXT("TRACK_NOT_FOUND"));
|
|
2271
|
-
}
|
|
2272
|
-
return true;
|
|
2273
|
-
#else
|
|
2274
|
-
SendAutomationResponse(Socket, RequestId, false,
|
|
2275
|
-
TEXT("sequence_remove_track requires editor build"),
|
|
2276
|
-
nullptr, TEXT("EDITOR_ONLY"));
|
|
2277
|
-
return true;
|
|
2278
|
-
#endif
|
|
2279
|
-
}
|
|
2280
|
-
|
|
2281
|
-
bool UMcpAutomationBridgeSubsystem::HandleSequenceAction(
|
|
2282
|
-
const FString &RequestId, const FString &Action,
|
|
2283
|
-
const TSharedPtr<FJsonObject> &Payload,
|
|
2284
|
-
TSharedPtr<FMcpBridgeWebSocket> RequestingSocket) {
|
|
2285
|
-
const FString Lower = Action.ToLower();
|
|
2286
|
-
// Also handle manage_sequence which acts as a dispatcher for sub-actions
|
|
2287
|
-
if (!Lower.StartsWith(TEXT("sequence_")) &&
|
|
2288
|
-
!Lower.Equals(TEXT("manage_sequence")))
|
|
2289
|
-
return false;
|
|
2290
|
-
|
|
2291
|
-
TSharedPtr<FJsonObject> LocalPayload =
|
|
2292
|
-
Payload.IsValid() ? Payload : MakeShared<FJsonObject>();
|
|
2293
|
-
FString EffectiveAction = Lower;
|
|
2294
|
-
|
|
2295
|
-
// If generic manage_sequence, extract the sub-action to determine behavior
|
|
2296
|
-
if (Lower.Equals(TEXT("manage_sequence"))) {
|
|
2297
|
-
FString Sub;
|
|
2298
|
-
if (LocalPayload->TryGetStringField(TEXT("subAction"), Sub) &&
|
|
2299
|
-
!Sub.IsEmpty()) {
|
|
2300
|
-
EffectiveAction = Sub.ToLower();
|
|
2301
|
-
// If subAction is just "create", map to "sequence_create" for consistency
|
|
2302
|
-
if (EffectiveAction == TEXT("create"))
|
|
2303
|
-
EffectiveAction = TEXT("sequence_create");
|
|
2304
|
-
else if (!EffectiveAction.StartsWith(TEXT("sequence_")))
|
|
2305
|
-
EffectiveAction = TEXT("sequence_") + EffectiveAction;
|
|
2306
|
-
}
|
|
2307
|
-
}
|
|
2308
|
-
|
|
2309
|
-
if (EffectiveAction == TEXT("sequence_create"))
|
|
2310
|
-
return HandleSequenceCreate(RequestId, LocalPayload, RequestingSocket);
|
|
2311
|
-
if (EffectiveAction == TEXT("sequence_set_display_rate"))
|
|
2312
|
-
return HandleSequenceSetDisplayRate(RequestId, LocalPayload,
|
|
2313
|
-
RequestingSocket);
|
|
2314
|
-
if (EffectiveAction == TEXT("sequence_set_properties"))
|
|
2315
|
-
return HandleSequenceSetProperties(RequestId, LocalPayload,
|
|
2316
|
-
RequestingSocket);
|
|
2317
|
-
if (EffectiveAction == TEXT("sequence_open"))
|
|
2318
|
-
return HandleSequenceOpen(RequestId, LocalPayload, RequestingSocket);
|
|
2319
|
-
if (EffectiveAction == TEXT("sequence_add_camera"))
|
|
2320
|
-
return HandleSequenceAddCamera(RequestId, LocalPayload, RequestingSocket);
|
|
2321
|
-
if (EffectiveAction == TEXT("sequence_play"))
|
|
2322
|
-
return HandleSequencePlay(RequestId, LocalPayload, RequestingSocket);
|
|
2323
|
-
if (EffectiveAction == TEXT("sequence_add_actor"))
|
|
2324
|
-
return HandleSequenceAddActor(RequestId, LocalPayload, RequestingSocket);
|
|
2325
|
-
if (EffectiveAction == TEXT("sequence_add_actors"))
|
|
2326
|
-
return HandleSequenceAddActors(RequestId, LocalPayload, RequestingSocket);
|
|
2327
|
-
if (EffectiveAction == TEXT("sequence_add_spawnable_from_class"))
|
|
2328
|
-
return HandleSequenceAddSpawnable(RequestId, LocalPayload,
|
|
2329
|
-
RequestingSocket);
|
|
2330
|
-
if (EffectiveAction == TEXT("sequence_remove_actors"))
|
|
2331
|
-
return HandleSequenceRemoveActors(RequestId, LocalPayload,
|
|
2332
|
-
RequestingSocket);
|
|
2333
|
-
if (EffectiveAction == TEXT("sequence_get_bindings"))
|
|
2334
|
-
return HandleSequenceGetBindings(RequestId, LocalPayload, RequestingSocket);
|
|
2335
|
-
if (EffectiveAction == TEXT("sequence_get_properties"))
|
|
2336
|
-
return HandleSequenceGetProperties(RequestId, LocalPayload,
|
|
2337
|
-
RequestingSocket);
|
|
2338
|
-
if (EffectiveAction == TEXT("sequence_set_playback_speed"))
|
|
2339
|
-
return HandleSequenceSetPlaybackSpeed(RequestId, LocalPayload,
|
|
2340
|
-
RequestingSocket);
|
|
2341
|
-
if (EffectiveAction == TEXT("sequence_pause"))
|
|
2342
|
-
return HandleSequencePause(RequestId, LocalPayload, RequestingSocket);
|
|
2343
|
-
if (EffectiveAction == TEXT("sequence_stop"))
|
|
2344
|
-
return HandleSequenceStop(RequestId, LocalPayload, RequestingSocket);
|
|
2345
|
-
if (EffectiveAction == TEXT("sequence_list"))
|
|
2346
|
-
return HandleSequenceList(RequestId, LocalPayload, RequestingSocket);
|
|
2347
|
-
if (EffectiveAction == TEXT("sequence_duplicate"))
|
|
2348
|
-
return HandleSequenceDuplicate(RequestId, LocalPayload, RequestingSocket);
|
|
2349
|
-
if (EffectiveAction == TEXT("sequence_rename"))
|
|
2350
|
-
return HandleSequenceRename(RequestId, LocalPayload, RequestingSocket);
|
|
2351
|
-
if (EffectiveAction == TEXT("sequence_delete"))
|
|
2352
|
-
return HandleSequenceDelete(RequestId, LocalPayload, RequestingSocket);
|
|
2353
|
-
if (EffectiveAction == TEXT("sequence_get_metadata"))
|
|
2354
|
-
return HandleSequenceGetMetadata(RequestId, LocalPayload, RequestingSocket);
|
|
2355
|
-
if (EffectiveAction == TEXT("sequence_add_keyframe"))
|
|
2356
|
-
return HandleSequenceAddKeyframe(RequestId, LocalPayload, RequestingSocket);
|
|
2357
|
-
|
|
2358
|
-
// New handlers
|
|
2359
|
-
if (EffectiveAction == TEXT("sequence_add_section"))
|
|
2360
|
-
return HandleSequenceAddSection(RequestId, LocalPayload, RequestingSocket);
|
|
2361
|
-
if (EffectiveAction == TEXT("sequence_set_tick_resolution"))
|
|
2362
|
-
return HandleSequenceSetTickResolution(RequestId, LocalPayload,
|
|
2363
|
-
RequestingSocket);
|
|
2364
|
-
if (EffectiveAction == TEXT("sequence_set_view_range"))
|
|
2365
|
-
return HandleSequenceSetViewRange(RequestId, LocalPayload,
|
|
2366
|
-
RequestingSocket);
|
|
2367
|
-
if (EffectiveAction == TEXT("sequence_set_track_muted"))
|
|
2368
|
-
return HandleSequenceSetTrackMuted(RequestId, LocalPayload,
|
|
2369
|
-
RequestingSocket);
|
|
2370
|
-
if (EffectiveAction == TEXT("sequence_set_track_solo"))
|
|
2371
|
-
return HandleSequenceSetTrackSolo(RequestId, LocalPayload,
|
|
2372
|
-
RequestingSocket);
|
|
2373
|
-
if (EffectiveAction == TEXT("sequence_set_track_locked"))
|
|
2374
|
-
return HandleSequenceSetTrackLocked(RequestId, LocalPayload,
|
|
2375
|
-
RequestingSocket);
|
|
2376
|
-
if (EffectiveAction == TEXT("sequence_remove_track"))
|
|
2377
|
-
return HandleSequenceRemoveTrack(RequestId, LocalPayload, RequestingSocket);
|
|
2378
|
-
|
|
2379
|
-
if (EffectiveAction == TEXT("sequence_list_track_types")) {
|
|
2380
|
-
// Discovery: list available track types
|
|
2381
|
-
TArray<TSharedPtr<FJsonValue>> Types;
|
|
2382
|
-
// Add common shortcuts first
|
|
2383
|
-
Types.Add(MakeShared<FJsonValueString>(TEXT("transform")));
|
|
2384
|
-
Types.Add(MakeShared<FJsonValueString>(TEXT("3dtransform")));
|
|
2385
|
-
Types.Add(MakeShared<FJsonValueString>(TEXT("audio")));
|
|
2386
|
-
Types.Add(MakeShared<FJsonValueString>(TEXT("event")));
|
|
2387
|
-
|
|
2388
|
-
// Discover all UMovieSceneTrack subclasses via reflection
|
|
2389
|
-
TSet<FString> AddedNames;
|
|
2390
|
-
AddedNames.Add(TEXT("transform"));
|
|
2391
|
-
AddedNames.Add(TEXT("3dtransform"));
|
|
2392
|
-
AddedNames.Add(TEXT("audio"));
|
|
2393
|
-
AddedNames.Add(TEXT("event"));
|
|
2394
|
-
|
|
2395
|
-
for (TObjectIterator<UClass> It; It; ++It) {
|
|
2396
|
-
if (It->IsChildOf(UMovieSceneTrack::StaticClass()) &&
|
|
2397
|
-
!It->HasAnyClassFlags(CLASS_Abstract) &&
|
|
2398
|
-
!AddedNames.Contains(It->GetName())) {
|
|
2399
|
-
Types.Add(MakeShared<FJsonValueString>(It->GetName()));
|
|
2400
|
-
AddedNames.Add(It->GetName());
|
|
2401
|
-
}
|
|
2402
|
-
}
|
|
2403
|
-
|
|
2404
|
-
TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
|
|
2405
|
-
Resp->SetArrayField(TEXT("types"), Types);
|
|
2406
|
-
Resp->SetNumberField(TEXT("count"), Types.Num());
|
|
2407
|
-
SendAutomationResponse(RequestingSocket, RequestId, true,
|
|
2408
|
-
TEXT("Available track types"), Resp);
|
|
2409
|
-
return true;
|
|
2410
|
-
}
|
|
2411
|
-
|
|
2412
|
-
if (EffectiveAction == TEXT("sequence_add_track")) {
|
|
2413
|
-
// add_track action: Add a track to a binding in a level sequence
|
|
2414
|
-
FString SeqPath = ResolveSequencePath(LocalPayload);
|
|
2415
|
-
if (SeqPath.IsEmpty()) {
|
|
2416
|
-
SendAutomationResponse(
|
|
2417
|
-
RequestingSocket, RequestId, false,
|
|
2418
|
-
TEXT("sequence_add_track requires a sequence path"), nullptr,
|
|
2419
|
-
TEXT("INVALID_SEQUENCE"));
|
|
2420
|
-
return true;
|
|
2421
|
-
}
|
|
2422
|
-
|
|
2423
|
-
ULevelSequence *Sequence = LoadObject<ULevelSequence>(nullptr, *SeqPath);
|
|
2424
|
-
if (!Sequence) {
|
|
2425
|
-
SendAutomationResponse(RequestingSocket, RequestId, false,
|
|
2426
|
-
TEXT("Level sequence not found"), nullptr,
|
|
2427
|
-
TEXT("SEQUENCE_NOT_FOUND"));
|
|
2428
|
-
return true;
|
|
2429
|
-
}
|
|
2430
|
-
|
|
2431
|
-
UMovieScene *MovieScene = Sequence->GetMovieScene();
|
|
2432
|
-
if (!MovieScene) {
|
|
2433
|
-
SendAutomationResponse(RequestingSocket, RequestId, false,
|
|
2434
|
-
TEXT("MovieScene not available"), nullptr,
|
|
2435
|
-
TEXT("MOVIESCENE_UNAVAILABLE"));
|
|
2436
|
-
return true;
|
|
2437
|
-
}
|
|
2438
|
-
|
|
2439
|
-
FString TrackType;
|
|
2440
|
-
LocalPayload->TryGetStringField(TEXT("trackType"), TrackType);
|
|
2441
|
-
if (TrackType.IsEmpty()) {
|
|
2442
|
-
SendAutomationResponse(
|
|
2443
|
-
RequestingSocket, RequestId, false,
|
|
2444
|
-
TEXT("trackType required (e.g., Transform, Animation, Audio, Event)"),
|
|
2445
|
-
nullptr, TEXT("INVALID_ARGUMENT"));
|
|
2446
|
-
return true;
|
|
2447
|
-
}
|
|
2448
|
-
|
|
2449
|
-
FString TrackName;
|
|
2450
|
-
LocalPayload->TryGetStringField(TEXT("trackName"), TrackName);
|
|
2451
|
-
|
|
2452
|
-
FString ActorName;
|
|
2453
|
-
LocalPayload->TryGetStringField(TEXT("actorName"), ActorName);
|
|
2454
|
-
|
|
2455
|
-
// Find or use master track if no actor specified
|
|
2456
|
-
FGuid BindingGuid;
|
|
2457
|
-
if (!ActorName.IsEmpty()) {
|
|
2458
|
-
// Find binding by actor name
|
|
2459
|
-
// Use const interface to avoid deprecation warning
|
|
2460
|
-
const UMovieScene *ConstMovieScene = MovieScene;
|
|
2461
|
-
for (const FMovieSceneBinding &Binding : ConstMovieScene->GetBindings()) {
|
|
2462
|
-
FString BindingName;
|
|
2463
|
-
if (FMovieScenePossessable *Possessable =
|
|
2464
|
-
MovieScene->FindPossessable(Binding.GetObjectGuid())) {
|
|
2465
|
-
BindingName = Possessable->GetName();
|
|
2466
|
-
} else if (FMovieSceneSpawnable *Spawnable =
|
|
2467
|
-
MovieScene->FindSpawnable(Binding.GetObjectGuid())) {
|
|
2468
|
-
BindingName = Spawnable->GetName();
|
|
2469
|
-
}
|
|
2470
|
-
|
|
2471
|
-
if (BindingName.Contains(ActorName)) {
|
|
2472
|
-
BindingGuid = Binding.GetObjectGuid();
|
|
2473
|
-
break;
|
|
2474
|
-
}
|
|
2475
|
-
}
|
|
2476
|
-
if (!BindingGuid.IsValid()) {
|
|
2477
|
-
SendAutomationResponse(
|
|
2478
|
-
RequestingSocket, RequestId, false,
|
|
2479
|
-
FString::Printf(TEXT("Binding not found for actor: %s"),
|
|
2480
|
-
*ActorName),
|
|
2481
|
-
nullptr, TEXT("BINDING_NOT_FOUND"));
|
|
2482
|
-
return true;
|
|
2483
|
-
}
|
|
2484
|
-
}
|
|
2485
|
-
|
|
2486
|
-
// Add the track
|
|
2487
|
-
UMovieSceneTrack *NewTrack = nullptr;
|
|
2488
|
-
|
|
2489
|
-
// Dynamic resolution with heuristics
|
|
2490
|
-
UClass *TrackClass = ResolveUClass(TrackType);
|
|
2491
|
-
|
|
2492
|
-
// Try with common prefixes
|
|
2493
|
-
if (!TrackClass) {
|
|
2494
|
-
TrackClass = ResolveUClass(
|
|
2495
|
-
FString::Printf(TEXT("UMovieScene%sTrack"), *TrackType));
|
|
2496
|
-
}
|
|
2497
|
-
if (!TrackClass) {
|
|
2498
|
-
TrackClass =
|
|
2499
|
-
ResolveUClass(FString::Printf(TEXT("MovieScene%sTrack"), *TrackType));
|
|
2500
|
-
}
|
|
2501
|
-
// Try simple "U" prefix
|
|
2502
|
-
if (!TrackClass) {
|
|
2503
|
-
TrackClass = ResolveUClass(FString::Printf(TEXT("U%s"), *TrackType));
|
|
2504
|
-
}
|
|
2505
|
-
|
|
2506
|
-
// Validate it's actually a track class
|
|
2507
|
-
if (TrackClass && TrackClass->IsChildOf(UMovieSceneTrack::StaticClass())) {
|
|
2508
|
-
if (BindingGuid.IsValid()) {
|
|
2509
|
-
NewTrack = MovieScene->AddTrack(TrackClass, BindingGuid);
|
|
2510
|
-
} else {
|
|
2511
|
-
NewTrack = MovieScene->AddTrack(TrackClass);
|
|
2512
|
-
}
|
|
2513
|
-
} else if (TrackClass) {
|
|
2514
|
-
// Found a class but it's not a track
|
|
2515
|
-
SendAutomationError(
|
|
2516
|
-
RequestingSocket, RequestId,
|
|
2517
|
-
FString::Printf(TEXT("Class '%s' is not a UMovieSceneTrack"),
|
|
2518
|
-
*TrackClass->GetName()),
|
|
2519
|
-
TEXT("INVALID_CLASS_TYPE"));
|
|
2520
|
-
return true;
|
|
2521
|
-
}
|
|
2522
|
-
|
|
2523
|
-
if (NewTrack) {
|
|
2524
|
-
Sequence->MarkPackageDirty();
|
|
2525
|
-
|
|
2526
|
-
TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
|
|
2527
|
-
Resp->SetBoolField(TEXT("success"), true);
|
|
2528
|
-
Resp->SetStringField(TEXT("sequencePath"), SeqPath);
|
|
2529
|
-
Resp->SetStringField(TEXT("trackType"), TrackType);
|
|
2530
|
-
Resp->SetStringField(TEXT("trackName"),
|
|
2531
|
-
TrackName.IsEmpty() ? TrackType : TrackName);
|
|
2532
|
-
if (!ActorName.IsEmpty()) {
|
|
2533
|
-
Resp->SetStringField(TEXT("actorName"), ActorName);
|
|
2534
|
-
Resp->SetStringField(TEXT("bindingGuid"), BindingGuid.ToString());
|
|
2535
|
-
}
|
|
2536
|
-
SendAutomationResponse(RequestingSocket, RequestId, true,
|
|
2537
|
-
TEXT("Track added successfully"), Resp, FString());
|
|
2538
|
-
} else {
|
|
2539
|
-
SendAutomationResponse(
|
|
2540
|
-
RequestingSocket, RequestId, false,
|
|
2541
|
-
FString::Printf(TEXT("Failed to add track of type: %s"), *TrackType),
|
|
2542
|
-
nullptr, TEXT("TRACK_CREATION_FAILED"));
|
|
2543
|
-
}
|
|
2544
|
-
return true;
|
|
2545
|
-
}
|
|
2546
|
-
|
|
2547
|
-
// sequence_list_tracks: List all tracks for a sequence binding
|
|
2548
|
-
if (EffectiveAction == TEXT("sequence_list_tracks")) {
|
|
2549
|
-
FString SeqPath = ResolveSequencePath(LocalPayload);
|
|
2550
|
-
if (SeqPath.IsEmpty()) {
|
|
2551
|
-
SendAutomationResponse(
|
|
2552
|
-
RequestingSocket, RequestId, false,
|
|
2553
|
-
TEXT("sequence_list_tracks requires a sequence path"), nullptr,
|
|
2554
|
-
TEXT("INVALID_SEQUENCE"));
|
|
2555
|
-
return true;
|
|
2556
|
-
}
|
|
2557
|
-
|
|
2558
|
-
#if WITH_EDITOR
|
|
2559
|
-
ULevelSequence *Sequence = LoadObject<ULevelSequence>(nullptr, *SeqPath);
|
|
2560
|
-
if (!Sequence) {
|
|
2561
|
-
SendAutomationResponse(RequestingSocket, RequestId, false,
|
|
2562
|
-
TEXT("Level sequence not found"), nullptr,
|
|
2563
|
-
TEXT("SEQUENCE_NOT_FOUND"));
|
|
2564
|
-
return true;
|
|
2565
|
-
}
|
|
2566
|
-
|
|
2567
|
-
UMovieScene *MovieScene = Sequence->GetMovieScene();
|
|
2568
|
-
if (!MovieScene) {
|
|
2569
|
-
SendAutomationResponse(RequestingSocket, RequestId, false,
|
|
2570
|
-
TEXT("MovieScene not available"), nullptr,
|
|
2571
|
-
TEXT("MOVIESCENE_UNAVAILABLE"));
|
|
2572
|
-
return true;
|
|
2573
|
-
}
|
|
2574
|
-
|
|
2575
|
-
TArray<TSharedPtr<FJsonValue>> TracksArray;
|
|
2576
|
-
|
|
2577
|
-
// Get Tracks (formerly GetMasterTracks)
|
|
2578
|
-
for (UMovieSceneTrack *Track : MovieScene->GetTracks()) {
|
|
2579
|
-
if (!Track)
|
|
2580
|
-
continue;
|
|
2581
|
-
TSharedPtr<FJsonObject> TrackObj = MakeShared<FJsonObject>();
|
|
2582
|
-
TrackObj->SetStringField(TEXT("trackName"), Track->GetName());
|
|
2583
|
-
TrackObj->SetStringField(TEXT("trackType"), Track->GetClass()->GetName());
|
|
2584
|
-
TrackObj->SetStringField(TEXT("displayName"),
|
|
2585
|
-
Track->GetDisplayName().ToString());
|
|
2586
|
-
TrackObj->SetBoolField(TEXT("isMasterTrack"), true);
|
|
2587
|
-
TrackObj->SetNumberField(TEXT("sectionCount"),
|
|
2588
|
-
Track->GetAllSections().Num());
|
|
2589
|
-
TracksArray.Add(MakeShared<FJsonValueObject>(TrackObj));
|
|
2590
|
-
}
|
|
2591
|
-
|
|
2592
|
-
// Get tracks from bindings
|
|
2593
|
-
for (const FMovieSceneBinding &Binding :
|
|
2594
|
-
const_cast<const UMovieScene *>(MovieScene)->GetBindings()) {
|
|
2595
|
-
FString BindingName;
|
|
2596
|
-
if (FMovieScenePossessable *Possessable =
|
|
2597
|
-
MovieScene->FindPossessable(Binding.GetObjectGuid())) {
|
|
2598
|
-
BindingName = Possessable->GetName();
|
|
2599
|
-
} else if (FMovieSceneSpawnable *Spawnable =
|
|
2600
|
-
MovieScene->FindSpawnable(Binding.GetObjectGuid())) {
|
|
2601
|
-
BindingName = Spawnable->GetName();
|
|
2602
|
-
}
|
|
2603
|
-
|
|
2604
|
-
for (UMovieSceneTrack *Track : Binding.GetTracks()) {
|
|
2605
|
-
if (!Track)
|
|
2606
|
-
continue;
|
|
2607
|
-
TSharedPtr<FJsonObject> TrackObj = MakeShared<FJsonObject>();
|
|
2608
|
-
TrackObj->SetStringField(TEXT("trackName"), Track->GetName());
|
|
2609
|
-
TrackObj->SetStringField(TEXT("trackType"),
|
|
2610
|
-
Track->GetClass()->GetName());
|
|
2611
|
-
TrackObj->SetStringField(TEXT("displayName"),
|
|
2612
|
-
Track->GetDisplayName().ToString());
|
|
2613
|
-
TrackObj->SetBoolField(TEXT("isMasterTrack"), false);
|
|
2614
|
-
TrackObj->SetStringField(TEXT("bindingName"), BindingName);
|
|
2615
|
-
TrackObj->SetStringField(TEXT("bindingGuid"),
|
|
2616
|
-
Binding.GetObjectGuid().ToString());
|
|
2617
|
-
TrackObj->SetNumberField(TEXT("sectionCount"),
|
|
2618
|
-
Track->GetAllSections().Num());
|
|
2619
|
-
TracksArray.Add(MakeShared<FJsonValueObject>(TrackObj));
|
|
2620
|
-
}
|
|
2621
|
-
}
|
|
2622
|
-
|
|
2623
|
-
TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
|
|
2624
|
-
Resp->SetArrayField(TEXT("tracks"), TracksArray);
|
|
2625
|
-
Resp->SetNumberField(TEXT("trackCount"), TracksArray.Num());
|
|
2626
|
-
Resp->SetStringField(TEXT("sequencePath"), SeqPath);
|
|
2627
|
-
SendAutomationResponse(
|
|
2628
|
-
RequestingSocket, RequestId, true,
|
|
2629
|
-
FString::Printf(TEXT("Found %d tracks"), TracksArray.Num()), Resp,
|
|
2630
|
-
FString());
|
|
2631
|
-
return true;
|
|
2632
|
-
#else
|
|
2633
|
-
SendAutomationResponse(RequestingSocket, RequestId, false,
|
|
2634
|
-
TEXT("sequence_list_tracks requires editor build"),
|
|
2635
|
-
nullptr, TEXT("EDITOR_ONLY"));
|
|
2636
|
-
return true;
|
|
2637
|
-
#endif
|
|
2638
|
-
}
|
|
2639
|
-
|
|
2640
|
-
// sequence_set_work_range: Set the work range of a sequence
|
|
2641
|
-
if (EffectiveAction == TEXT("sequence_set_work_range")) {
|
|
2642
|
-
FString SeqPath = ResolveSequencePath(LocalPayload);
|
|
2643
|
-
if (SeqPath.IsEmpty()) {
|
|
2644
|
-
SendAutomationResponse(
|
|
2645
|
-
RequestingSocket, RequestId, false,
|
|
2646
|
-
TEXT("sequence_set_work_range requires a sequence path"), nullptr,
|
|
2647
|
-
TEXT("INVALID_SEQUENCE"));
|
|
2648
|
-
return true;
|
|
2649
|
-
}
|
|
2650
|
-
|
|
2651
|
-
#if WITH_EDITOR
|
|
2652
|
-
ULevelSequence *Sequence = LoadObject<ULevelSequence>(nullptr, *SeqPath);
|
|
2653
|
-
if (!Sequence) {
|
|
2654
|
-
SendAutomationResponse(RequestingSocket, RequestId, false,
|
|
2655
|
-
TEXT("Level sequence not found"), nullptr,
|
|
2656
|
-
TEXT("SEQUENCE_NOT_FOUND"));
|
|
2657
|
-
return true;
|
|
2658
|
-
}
|
|
2659
|
-
|
|
2660
|
-
UMovieScene *MovieScene = Sequence->GetMovieScene();
|
|
2661
|
-
if (!MovieScene) {
|
|
2662
|
-
SendAutomationResponse(RequestingSocket, RequestId, false,
|
|
2663
|
-
TEXT("MovieScene not available"), nullptr,
|
|
2664
|
-
TEXT("MOVIESCENE_UNAVAILABLE"));
|
|
2665
|
-
return true;
|
|
2666
|
-
}
|
|
2667
|
-
|
|
2668
|
-
double Start = 0.0, End = 0.0;
|
|
2669
|
-
LocalPayload->TryGetNumberField(TEXT("start"), Start);
|
|
2670
|
-
LocalPayload->TryGetNumberField(TEXT("end"), End);
|
|
2671
|
-
|
|
2672
|
-
FFrameRate TickResolution = MovieScene->GetTickResolution();
|
|
2673
|
-
// Round to int32 for FFrameNumber constructor
|
|
2674
|
-
FFrameNumber StartFrame(
|
|
2675
|
-
(int32)FMath::RoundToInt(Start * TickResolution.AsDecimal()));
|
|
2676
|
-
FFrameNumber EndFrame(
|
|
2677
|
-
(int32)FMath::RoundToInt(End * TickResolution.AsDecimal()));
|
|
2678
|
-
|
|
2679
|
-
// SetWorkingRange expects seconds (double)
|
|
2680
|
-
MovieScene->SetWorkingRange(Start, End);
|
|
2681
|
-
MovieScene->Modify();
|
|
2682
|
-
|
|
2683
|
-
TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
|
|
2684
|
-
Resp->SetNumberField(TEXT("startFrame"), StartFrame.Value);
|
|
2685
|
-
Resp->SetNumberField(TEXT("endFrame"), EndFrame.Value);
|
|
2686
|
-
Resp->SetStringField(TEXT("sequencePath"), SeqPath);
|
|
2687
|
-
SendAutomationResponse(RequestingSocket, RequestId, true,
|
|
2688
|
-
TEXT("Work range set successfully"), Resp,
|
|
2689
|
-
FString());
|
|
2690
|
-
return true;
|
|
2691
|
-
#else
|
|
2692
|
-
SendAutomationResponse(
|
|
2693
|
-
RequestingSocket, RequestId, false,
|
|
2694
|
-
TEXT("sequence_set_work_range requires editor build"), nullptr,
|
|
2695
|
-
TEXT("EDITOR_ONLY"));
|
|
2696
|
-
return true;
|
|
2697
|
-
#endif
|
|
2698
|
-
}
|
|
2699
|
-
|
|
2700
|
-
SendAutomationResponse(
|
|
2701
|
-
RequestingSocket, RequestId, false,
|
|
2702
|
-
FString::Printf(TEXT("Sequence action not implemented by plugin: %s"),
|
|
2703
|
-
*Action),
|
|
2704
|
-
nullptr, TEXT("NOT_IMPLEMENTED"));
|
|
2705
|
-
return true;
|
|
2706
|
-
}
|