unreal-engine-mcp-server 0.4.7 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +26 -0
- package/.env.production +38 -7
- package/.eslintrc.json +0 -54
- package/.eslintrc.override.json +8 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +94 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +56 -0
- package/.github/copilot-instructions.md +478 -45
- package/.github/dependabot.yml +19 -0
- package/.github/labeler.yml +24 -0
- package/.github/labels.yml +70 -0
- package/.github/pull_request_template.md +42 -0
- package/.github/release-drafter.yml +148 -0
- package/.github/workflows/auto-merge.yml +38 -0
- package/.github/workflows/ci.yml +38 -0
- package/.github/workflows/dependency-review.yml +17 -0
- package/.github/workflows/gemini-issue-triage.yml +172 -0
- package/.github/workflows/greetings.yml +23 -0
- package/.github/workflows/labeler.yml +16 -0
- package/.github/workflows/links.yml +80 -0
- package/.github/workflows/pr-size-labeler.yml +137 -0
- package/.github/workflows/publish-mcp.yml +12 -7
- package/.github/workflows/release-drafter.yml +23 -0
- package/.github/workflows/release.yml +112 -0
- package/.github/workflows/semantic-pull-request.yml +35 -0
- package/.github/workflows/smoke-test.yml +36 -0
- package/.github/workflows/stale.yml +28 -0
- package/CHANGELOG.md +267 -31
- package/CONTRIBUTING.md +140 -0
- package/README.md +166 -71
- package/claude_desktop_config_example.json +7 -6
- package/dist/automation/bridge.d.ts +50 -0
- package/dist/automation/bridge.js +452 -0
- package/dist/automation/connection-manager.d.ts +23 -0
- package/dist/automation/connection-manager.js +107 -0
- package/dist/automation/handshake.d.ts +11 -0
- package/dist/automation/handshake.js +89 -0
- package/dist/automation/index.d.ts +3 -0
- package/dist/automation/index.js +3 -0
- package/dist/automation/message-handler.d.ts +12 -0
- package/dist/automation/message-handler.js +149 -0
- package/dist/automation/request-tracker.d.ts +25 -0
- package/dist/automation/request-tracker.js +98 -0
- package/dist/automation/types.d.ts +130 -0
- package/dist/automation/types.js +2 -0
- package/dist/cli.js +32 -5
- package/dist/config.d.ts +27 -0
- package/dist/config.js +60 -0
- package/dist/constants.d.ts +12 -0
- package/dist/constants.js +12 -0
- package/dist/graphql/resolvers.d.ts +268 -0
- package/dist/graphql/resolvers.js +743 -0
- package/dist/graphql/schema.d.ts +5 -0
- package/dist/graphql/schema.js +437 -0
- package/dist/graphql/server.d.ts +26 -0
- package/dist/graphql/server.js +115 -0
- package/dist/graphql/types.d.ts +7 -0
- package/dist/graphql/types.js +2 -0
- package/dist/handlers/resource-handlers.d.ts +20 -0
- package/dist/handlers/resource-handlers.js +180 -0
- package/dist/index.d.ts +31 -18
- package/dist/index.js +119 -619
- package/dist/prompts/index.js +4 -4
- package/dist/resources/actors.d.ts +17 -12
- package/dist/resources/actors.js +56 -76
- package/dist/resources/assets.d.ts +6 -14
- package/dist/resources/assets.js +115 -147
- package/dist/resources/levels.d.ts +13 -13
- package/dist/resources/levels.js +25 -34
- package/dist/server/resource-registry.d.ts +20 -0
- package/dist/server/resource-registry.js +37 -0
- package/dist/server/tool-registry.d.ts +23 -0
- package/dist/server/tool-registry.js +322 -0
- package/dist/server-setup.d.ts +21 -0
- package/dist/server-setup.js +111 -0
- package/dist/services/health-monitor.d.ts +34 -0
- package/dist/services/health-monitor.js +105 -0
- package/dist/services/metrics-server.d.ts +11 -0
- package/dist/services/metrics-server.js +105 -0
- package/dist/tools/actors.d.ts +147 -9
- package/dist/tools/actors.js +350 -311
- package/dist/tools/animation.d.ts +135 -4
- package/dist/tools/animation.js +510 -411
- package/dist/tools/assets.d.ts +117 -19
- package/dist/tools/assets.js +259 -284
- package/dist/tools/audio.d.ts +102 -42
- package/dist/tools/audio.js +272 -685
- package/dist/tools/base-tool.d.ts +17 -0
- package/dist/tools/base-tool.js +46 -0
- package/dist/tools/behavior-tree.d.ts +94 -0
- package/dist/tools/behavior-tree.js +39 -0
- package/dist/tools/blueprint/helpers.d.ts +29 -0
- package/dist/tools/blueprint/helpers.js +182 -0
- package/dist/tools/blueprint.d.ts +228 -118
- package/dist/tools/blueprint.js +685 -832
- package/dist/tools/consolidated-tool-definitions.d.ts +5462 -1781
- package/dist/tools/consolidated-tool-definitions.js +829 -496
- package/dist/tools/consolidated-tool-handlers.d.ts +2 -1
- package/dist/tools/consolidated-tool-handlers.js +211 -1026
- package/dist/tools/debug.d.ts +143 -85
- package/dist/tools/debug.js +234 -180
- package/dist/tools/dynamic-handler-registry.d.ts +11 -0
- package/dist/tools/dynamic-handler-registry.js +101 -0
- package/dist/tools/editor.d.ts +139 -18
- package/dist/tools/editor.js +239 -244
- package/dist/tools/engine.d.ts +10 -4
- package/dist/tools/engine.js +13 -5
- package/dist/tools/environment.d.ts +36 -0
- package/dist/tools/environment.js +267 -0
- package/dist/tools/foliage.d.ts +105 -14
- package/dist/tools/foliage.js +219 -331
- package/dist/tools/handlers/actor-handlers.d.ts +3 -0
- package/dist/tools/handlers/actor-handlers.js +232 -0
- package/dist/tools/handlers/animation-handlers.d.ts +3 -0
- package/dist/tools/handlers/animation-handlers.js +185 -0
- package/dist/tools/handlers/argument-helper.d.ts +16 -0
- package/dist/tools/handlers/argument-helper.js +80 -0
- package/dist/tools/handlers/asset-handlers.d.ts +3 -0
- package/dist/tools/handlers/asset-handlers.js +496 -0
- package/dist/tools/handlers/audio-handlers.d.ts +3 -0
- package/dist/tools/handlers/audio-handlers.js +166 -0
- package/dist/tools/handlers/blueprint-handlers.d.ts +4 -0
- package/dist/tools/handlers/blueprint-handlers.js +358 -0
- package/dist/tools/handlers/common-handlers.d.ts +14 -0
- package/dist/tools/handlers/common-handlers.js +56 -0
- package/dist/tools/handlers/editor-handlers.d.ts +3 -0
- package/dist/tools/handlers/editor-handlers.js +119 -0
- package/dist/tools/handlers/effect-handlers.d.ts +3 -0
- package/dist/tools/handlers/effect-handlers.js +171 -0
- package/dist/tools/handlers/environment-handlers.d.ts +3 -0
- package/dist/tools/handlers/environment-handlers.js +170 -0
- package/dist/tools/handlers/graph-handlers.d.ts +3 -0
- package/dist/tools/handlers/graph-handlers.js +90 -0
- package/dist/tools/handlers/input-handlers.d.ts +3 -0
- package/dist/tools/handlers/input-handlers.js +21 -0
- package/dist/tools/handlers/inspect-handlers.d.ts +3 -0
- package/dist/tools/handlers/inspect-handlers.js +383 -0
- package/dist/tools/handlers/level-handlers.d.ts +3 -0
- package/dist/tools/handlers/level-handlers.js +237 -0
- package/dist/tools/handlers/lighting-handlers.d.ts +3 -0
- package/dist/tools/handlers/lighting-handlers.js +144 -0
- package/dist/tools/handlers/performance-handlers.d.ts +3 -0
- package/dist/tools/handlers/performance-handlers.js +130 -0
- package/dist/tools/handlers/pipeline-handlers.d.ts +3 -0
- package/dist/tools/handlers/pipeline-handlers.js +110 -0
- package/dist/tools/handlers/sequence-handlers.d.ts +3 -0
- package/dist/tools/handlers/sequence-handlers.js +376 -0
- package/dist/tools/handlers/system-handlers.d.ts +4 -0
- package/dist/tools/handlers/system-handlers.js +506 -0
- package/dist/tools/input.d.ts +19 -0
- package/dist/tools/input.js +89 -0
- package/dist/tools/introspection.d.ts +103 -40
- package/dist/tools/introspection.js +425 -568
- package/dist/tools/landscape.d.ts +97 -36
- package/dist/tools/landscape.js +280 -409
- package/dist/tools/level.d.ts +130 -10
- package/dist/tools/level.js +639 -675
- package/dist/tools/lighting.d.ts +77 -38
- package/dist/tools/lighting.js +441 -943
- package/dist/tools/logs.d.ts +3 -3
- package/dist/tools/logs.js +5 -57
- package/dist/tools/materials.d.ts +91 -24
- package/dist/tools/materials.js +190 -118
- package/dist/tools/niagara.d.ts +149 -39
- package/dist/tools/niagara.js +232 -182
- package/dist/tools/performance.d.ts +27 -12
- package/dist/tools/performance.js +204 -122
- package/dist/tools/physics.d.ts +32 -77
- package/dist/tools/physics.js +171 -582
- package/dist/tools/property-dictionary.d.ts +13 -0
- package/dist/tools/property-dictionary.js +82 -0
- package/dist/tools/sequence.d.ts +73 -48
- package/dist/tools/sequence.js +196 -748
- package/dist/tools/tool-definition-utils.d.ts +59 -0
- package/dist/tools/tool-definition-utils.js +35 -0
- package/dist/tools/ui.d.ts +66 -34
- package/dist/tools/ui.js +134 -214
- package/dist/types/env.d.ts +0 -3
- package/dist/types/env.js +0 -7
- package/dist/types/tool-interfaces.d.ts +898 -0
- package/dist/types/tool-interfaces.js +2 -0
- package/dist/types/tool-types.d.ts +183 -19
- package/dist/types/tool-types.js +0 -4
- package/dist/unreal-bridge.d.ts +24 -131
- package/dist/unreal-bridge.js +364 -1506
- package/dist/utils/command-validator.d.ts +9 -0
- package/dist/utils/command-validator.js +67 -0
- package/dist/utils/elicitation.d.ts +1 -1
- package/dist/utils/elicitation.js +12 -15
- package/dist/utils/error-handler.d.ts +2 -51
- package/dist/utils/error-handler.js +11 -87
- package/dist/utils/ini-reader.d.ts +3 -0
- package/dist/utils/ini-reader.js +69 -0
- package/dist/utils/logger.js +9 -6
- package/dist/utils/normalize.d.ts +3 -0
- package/dist/utils/normalize.js +56 -0
- package/dist/utils/response-factory.d.ts +7 -0
- package/dist/utils/response-factory.js +33 -0
- package/dist/utils/response-validator.d.ts +3 -24
- package/dist/utils/response-validator.js +130 -81
- package/dist/utils/result-helpers.d.ts +4 -5
- package/dist/utils/result-helpers.js +15 -16
- package/dist/utils/safe-json.js +5 -11
- package/dist/utils/unreal-command-queue.d.ts +24 -0
- package/dist/utils/unreal-command-queue.js +120 -0
- package/dist/utils/validation.d.ts +0 -40
- package/dist/utils/validation.js +1 -78
- package/dist/wasm/index.d.ts +70 -0
- package/dist/wasm/index.js +535 -0
- package/docs/GraphQL-API.md +888 -0
- package/docs/Migration-Guide-v0.5.0.md +692 -0
- package/docs/Roadmap.md +53 -0
- package/docs/WebAssembly-Integration.md +628 -0
- package/docs/editor-plugin-extension.md +370 -0
- package/docs/handler-mapping.md +242 -0
- package/docs/native-automation-progress.md +128 -0
- package/docs/testing-guide.md +423 -0
- package/mcp-config-example.json +6 -6
- package/package.json +60 -27
- package/plugins/McpAutomationBridge/Config/FilterPlugin.ini +8 -0
- package/plugins/McpAutomationBridge/McpAutomationBridge.uplugin +64 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/McpAutomationBridge.Build.cs +189 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.cpp +22 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.h +30 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeHelpers.h +1983 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeModule.cpp +72 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSettings.cpp +46 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +581 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +2394 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetQueryHandlers.cpp +300 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetWorkflowHandlers.cpp +2807 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AudioHandlers.cpp +1087 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BehaviorTreeHandlers.cpp +488 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.cpp +643 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.h +31 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +1184 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +5652 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers_List.cpp +152 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ControlHandlers.cpp +2614 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_DebugHandlers.cpp +42 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EditorFunctionHandlers.cpp +1237 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +1701 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +2145 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_FoliageHandlers.cpp +954 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InputHandlers.cpp +209 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InsightsHandlers.cpp +41 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LandscapeHandlers.cpp +1164 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LevelHandlers.cpp +762 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +634 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LogHandlers.cpp +136 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_MaterialGraphHandlers.cpp +494 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraGraphHandlers.cpp +278 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraHandlers.cpp +625 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PerformanceHandlers.cpp +401 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PipelineHandlers.cpp +67 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +735 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PropertyHandlers.cpp +2634 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_RenderHandlers.cpp +189 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.cpp +917 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.h +39 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +2670 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequencerHandlers.cpp +519 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_TestHandlers.cpp +38 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_UiHandlers.cpp +668 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_WorldPartitionHandlers.cpp +346 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +1330 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.h +149 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +783 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSettings.h +115 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSubsystem.h +796 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpConnectionManager.h +117 -0
- package/scripts/check-unreal-connection.mjs +19 -0
- package/scripts/clean-tmp.js +23 -0
- package/scripts/patch-wasm.js +26 -0
- package/scripts/run-all-tests.mjs +131 -0
- package/scripts/smoke-test.ts +94 -0
- package/scripts/sync-mcp-plugin.js +143 -0
- package/scripts/test-no-plugin-alternates.mjs +113 -0
- package/scripts/validate-server.js +46 -0
- package/scripts/verify-automation-bridge.js +200 -0
- package/server.json +57 -21
- package/src/automation/bridge.ts +558 -0
- package/src/automation/connection-manager.ts +130 -0
- package/src/automation/handshake.ts +99 -0
- package/src/automation/index.ts +2 -0
- package/src/automation/message-handler.ts +167 -0
- package/src/automation/request-tracker.ts +123 -0
- package/src/automation/types.ts +107 -0
- package/src/cli.ts +33 -6
- package/src/config.ts +73 -0
- package/src/constants.ts +12 -0
- package/src/graphql/resolvers.ts +1010 -0
- package/src/graphql/schema.ts +452 -0
- package/src/graphql/server.ts +154 -0
- package/src/graphql/types.ts +7 -0
- package/src/handlers/resource-handlers.ts +186 -0
- package/src/index.ts +152 -663
- package/src/prompts/index.ts +4 -4
- package/src/resources/actors.ts +58 -76
- package/src/resources/assets.ts +147 -134
- package/src/resources/levels.ts +28 -33
- package/src/server/resource-registry.ts +47 -0
- package/src/server/tool-registry.ts +354 -0
- package/src/server-setup.ts +148 -0
- package/src/services/health-monitor.ts +132 -0
- package/src/services/metrics-server.ts +142 -0
- package/src/tools/actors.ts +417 -322
- package/src/tools/animation.ts +671 -461
- package/src/tools/assets.ts +353 -289
- package/src/tools/audio.ts +323 -766
- package/src/tools/base-tool.ts +52 -0
- package/src/tools/behavior-tree.ts +45 -0
- package/src/tools/blueprint/helpers.ts +189 -0
- package/src/tools/blueprint.ts +787 -965
- package/src/tools/consolidated-tool-definitions.ts +993 -515
- package/src/tools/consolidated-tool-handlers.ts +272 -1139
- package/src/tools/debug.ts +292 -187
- package/src/tools/dynamic-handler-registry.ts +151 -0
- package/src/tools/editor.ts +309 -246
- package/src/tools/engine.ts +14 -3
- package/src/tools/environment.ts +287 -0
- package/src/tools/foliage.ts +314 -379
- package/src/tools/handlers/actor-handlers.ts +271 -0
- package/src/tools/handlers/animation-handlers.ts +237 -0
- package/src/tools/handlers/argument-helper.ts +142 -0
- package/src/tools/handlers/asset-handlers.ts +532 -0
- package/src/tools/handlers/audio-handlers.ts +194 -0
- package/src/tools/handlers/blueprint-handlers.ts +380 -0
- package/src/tools/handlers/common-handlers.ts +87 -0
- package/src/tools/handlers/editor-handlers.ts +123 -0
- package/src/tools/handlers/effect-handlers.ts +220 -0
- package/src/tools/handlers/environment-handlers.ts +183 -0
- package/src/tools/handlers/graph-handlers.ts +116 -0
- package/src/tools/handlers/input-handlers.ts +28 -0
- package/src/tools/handlers/inspect-handlers.ts +450 -0
- package/src/tools/handlers/level-handlers.ts +252 -0
- package/src/tools/handlers/lighting-handlers.ts +147 -0
- package/src/tools/handlers/performance-handlers.ts +132 -0
- package/src/tools/handlers/pipeline-handlers.ts +127 -0
- package/src/tools/handlers/sequence-handlers.ts +415 -0
- package/src/tools/handlers/system-handlers.ts +564 -0
- package/src/tools/input.ts +101 -0
- package/src/tools/introspection.ts +493 -584
- package/src/tools/landscape.ts +394 -489
- package/src/tools/level.ts +752 -694
- package/src/tools/lighting.ts +583 -984
- package/src/tools/logs.ts +9 -57
- package/src/tools/materials.ts +231 -121
- package/src/tools/niagara.ts +293 -168
- package/src/tools/performance.ts +320 -168
- package/src/tools/physics.ts +268 -613
- package/src/tools/property-dictionary.ts +98 -0
- package/src/tools/sequence.ts +255 -815
- package/src/tools/tool-definition-utils.ts +35 -0
- package/src/tools/ui.ts +207 -283
- package/src/types/env.ts +0 -10
- package/src/types/tool-interfaces.ts +250 -0
- package/src/types/tool-types.ts +243 -21
- package/src/unreal-bridge.ts +460 -1550
- package/src/utils/command-validator.ts +75 -0
- package/src/utils/elicitation.ts +10 -7
- package/src/utils/error-handler.ts +14 -90
- package/src/utils/ini-reader.ts +86 -0
- package/src/utils/logger.ts +8 -3
- package/src/utils/normalize.ts +60 -0
- package/src/utils/response-factory.ts +39 -0
- package/src/utils/response-validator.ts +176 -56
- package/src/utils/result-helpers.ts +21 -19
- package/src/utils/safe-json.ts +14 -11
- package/src/utils/unreal-command-queue.ts +152 -0
- package/src/utils/validation.ts +4 -1
- package/src/wasm/index.ts +838 -0
- package/test-server.mjs +100 -0
- package/tests/run-unreal-tool-tests.mjs +242 -14
- package/tests/test-animation.mjs +44 -0
- package/tests/test-asset-advanced.mjs +82 -0
- package/tests/test-asset-errors.mjs +35 -0
- package/tests/test-audio.mjs +219 -0
- package/tests/test-automation-timeouts.mjs +98 -0
- package/tests/test-behavior-tree.mjs +261 -0
- package/tests/test-blueprint-events.mjs +35 -0
- package/tests/test-blueprint-graph.mjs +79 -0
- package/tests/test-blueprint.mjs +577 -0
- package/tests/test-client-mode.mjs +86 -0
- package/tests/test-console-command.mjs +56 -0
- package/tests/test-control-actor.mjs +425 -0
- package/tests/test-control-editor.mjs +80 -0
- package/tests/test-extra-tools.mjs +38 -0
- package/tests/test-graphql.mjs +322 -0
- package/tests/test-inspect.mjs +72 -0
- package/tests/test-landscape.mjs +60 -0
- package/tests/test-manage-asset.mjs +438 -0
- package/tests/test-manage-level.mjs +70 -0
- package/tests/test-materials.mjs +356 -0
- package/tests/test-niagara.mjs +185 -0
- package/tests/test-no-inline-python.mjs +122 -0
- package/tests/test-plugin-handshake.mjs +82 -0
- package/tests/test-render.mjs +33 -0
- package/tests/test-runner.mjs +933 -0
- package/tests/test-search-assets.mjs +66 -0
- package/tests/test-sequence.mjs +68 -0
- package/tests/test-system.mjs +57 -0
- package/tests/test-wasm.mjs +193 -0
- package/tests/test-world-partition.mjs +215 -0
- package/tsconfig.json +3 -3
- package/wasm/Cargo.lock +363 -0
- package/wasm/Cargo.toml +42 -0
- package/wasm/LICENSE +21 -0
- package/wasm/README.md +253 -0
- package/wasm/src/dependency_resolver.rs +377 -0
- package/wasm/src/lib.rs +153 -0
- package/wasm/src/property_parser.rs +271 -0
- package/wasm/src/transform_math.rs +396 -0
- package/wasm/tests/integration.rs +109 -0
- package/.github/workflows/smithery-build.yml +0 -29
- package/dist/tools/build_environment_advanced.d.ts +0 -65
- package/dist/tools/build_environment_advanced.js +0 -633
- package/dist/tools/rc.d.ts +0 -110
- package/dist/tools/rc.js +0 -437
- package/dist/tools/visual.d.ts +0 -40
- package/dist/tools/visual.js +0 -282
- package/dist/utils/http.d.ts +0 -6
- package/dist/utils/http.js +0 -151
- package/dist/utils/python-output.d.ts +0 -18
- package/dist/utils/python-output.js +0 -290
- package/dist/utils/python.d.ts +0 -2
- package/dist/utils/python.js +0 -4
- package/dist/utils/stdio-redirect.d.ts +0 -2
- package/dist/utils/stdio-redirect.js +0 -20
- package/docs/unreal-tool-test-cases.md +0 -574
- package/smithery.yaml +0 -29
- package/src/tools/build_environment_advanced.ts +0 -732
- package/src/tools/rc.ts +0 -515
- package/src/tools/visual.ts +0 -281
- package/src/utils/http.ts +0 -187
- package/src/utils/python-output.ts +0 -351
- package/src/utils/python.ts +0 -3
- package/src/utils/stdio-redirect.ts +0 -18
package/src/tools/animation.ts
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { UnrealBridge } from '../unreal-bridge.js';
|
|
2
|
+
import { AutomationBridge } from '../automation/index.js';
|
|
3
|
+
import { cleanObject } from '../utils/safe-json.js';
|
|
2
4
|
import { validateAssetParams } from '../utils/validation.js';
|
|
3
|
-
import {
|
|
4
|
-
interpretStandardResult,
|
|
5
|
-
coerceBoolean,
|
|
6
|
-
coerceString,
|
|
7
|
-
coerceStringArray
|
|
8
|
-
} from '../utils/result-helpers.js';
|
|
9
5
|
|
|
10
6
|
type CreateAnimationBlueprintSuccess = {
|
|
11
7
|
success: true;
|
|
@@ -51,7 +47,26 @@ type PlayAnimationFailure = {
|
|
|
51
47
|
};
|
|
52
48
|
|
|
53
49
|
export class AnimationTools {
|
|
54
|
-
|
|
50
|
+
private managedArtifacts = new Map<string, {
|
|
51
|
+
path?: string;
|
|
52
|
+
type: string;
|
|
53
|
+
metadata?: Record<string, unknown>;
|
|
54
|
+
createdAt: number;
|
|
55
|
+
}>();
|
|
56
|
+
|
|
57
|
+
private automationBridge?: AutomationBridge;
|
|
58
|
+
|
|
59
|
+
constructor(private bridge: UnrealBridge, automationBridge?: AutomationBridge) {
|
|
60
|
+
this.automationBridge = automationBridge;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
setAutomationBridge(automationBridge?: AutomationBridge) {
|
|
64
|
+
this.automationBridge = automationBridge;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private trackArtifact(key: string, info: { path?: string; type: string; metadata?: Record<string, unknown> }) {
|
|
68
|
+
this.managedArtifacts.set(key, { ...info, createdAt: Date.now() });
|
|
69
|
+
}
|
|
55
70
|
|
|
56
71
|
async createAnimationBlueprint(params: {
|
|
57
72
|
name: string;
|
|
@@ -69,14 +84,69 @@ export class AnimationTools {
|
|
|
69
84
|
const sanitized = validation.sanitized;
|
|
70
85
|
const assetName = sanitized.name;
|
|
71
86
|
const assetPath = sanitized.savePath ?? targetPath;
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
87
|
+
const fullPath = `${assetPath}/${assetName}`;
|
|
88
|
+
|
|
89
|
+
// Prefer native plugin support; surface real errors when creation fails.
|
|
90
|
+
if (this.automationBridge && typeof this.automationBridge.sendAutomationRequest === 'function') {
|
|
91
|
+
try {
|
|
92
|
+
const resp = await this.automationBridge.sendAutomationRequest('create_animation_blueprint', {
|
|
93
|
+
name: assetName,
|
|
94
|
+
skeletonPath: params.skeletonPath,
|
|
95
|
+
savePath: assetPath
|
|
96
|
+
}, { timeoutMs: 60000 });
|
|
97
|
+
|
|
98
|
+
const result = resp?.result ?? resp;
|
|
99
|
+
const resultObj = result && typeof result === 'object' ? result as Record<string, unknown> : undefined;
|
|
100
|
+
const warnings = Array.isArray(resultObj?.warnings) ? (resultObj.warnings as string[]) : undefined;
|
|
101
|
+
const details = Array.isArray(resultObj?.details) ? (resultObj.details as string[]) : undefined;
|
|
102
|
+
const isSuccess = resp && resp.success !== false && !!resultObj;
|
|
103
|
+
|
|
104
|
+
if (isSuccess && resultObj) {
|
|
105
|
+
const blueprintPath = typeof resultObj.blueprintPath === 'string' ? resultObj.blueprintPath : fullPath;
|
|
106
|
+
this.trackArtifact(assetName, { path: blueprintPath, type: 'AnimationBlueprint' });
|
|
107
|
+
return {
|
|
108
|
+
success: true,
|
|
109
|
+
message: resp.message || `Animation Blueprint created at ${blueprintPath}`,
|
|
110
|
+
path: blueprintPath,
|
|
111
|
+
skeleton: params.skeletonPath,
|
|
112
|
+
warnings,
|
|
113
|
+
details
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const message = typeof resp?.message === 'string'
|
|
118
|
+
? resp.message
|
|
119
|
+
: (typeof resp?.error === 'string' ? resp.error : 'Animation Blueprint creation failed');
|
|
120
|
+
const error = typeof resp?.error === 'string' ? resp.error : message;
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
success: false,
|
|
124
|
+
message,
|
|
125
|
+
error,
|
|
126
|
+
path: fullPath,
|
|
127
|
+
skeleton: params.skeletonPath,
|
|
128
|
+
warnings,
|
|
129
|
+
details
|
|
130
|
+
};
|
|
131
|
+
} catch (err) {
|
|
132
|
+
const error = String(err);
|
|
133
|
+
return {
|
|
134
|
+
success: false,
|
|
135
|
+
message: `Failed to create Animation Blueprint: ${error}`,
|
|
136
|
+
error,
|
|
137
|
+
path: fullPath,
|
|
138
|
+
skeleton: params.skeletonPath
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
77
142
|
|
|
78
|
-
|
|
79
|
-
|
|
143
|
+
return {
|
|
144
|
+
success: false,
|
|
145
|
+
message: 'Automation bridge not connected for Animation Blueprint creation',
|
|
146
|
+
error: 'AUTOMATION_BRIDGE_UNAVAILABLE',
|
|
147
|
+
path: fullPath,
|
|
148
|
+
skeleton: params.skeletonPath
|
|
149
|
+
};
|
|
80
150
|
} catch (err) {
|
|
81
151
|
const error = `Failed to create Animation Blueprint: ${err}`;
|
|
82
152
|
return { success: false, message: error, error: String(err) };
|
|
@@ -143,6 +213,95 @@ export class AnimationTools {
|
|
|
143
213
|
}
|
|
144
214
|
}
|
|
145
215
|
|
|
216
|
+
async createStateMachine(params: {
|
|
217
|
+
machineName?: string;
|
|
218
|
+
states?: Array<string | { name: string; animation?: string; isEntry?: boolean; isExit?: boolean }>;
|
|
219
|
+
transitions?: Array<{ sourceState: string; targetState: string; condition?: string }>;
|
|
220
|
+
blueprintPath?: string;
|
|
221
|
+
}): Promise<
|
|
222
|
+
| {
|
|
223
|
+
success: true;
|
|
224
|
+
message: string;
|
|
225
|
+
machineName: string;
|
|
226
|
+
blueprintPath?: string;
|
|
227
|
+
states?: Array<{ name: string; animation?: string; isEntry?: boolean; isExit?: boolean }>;
|
|
228
|
+
transitions?: Array<{ sourceState: string; targetState: string; condition?: string }>;
|
|
229
|
+
}
|
|
230
|
+
| { success: false; message: string; error: string }
|
|
231
|
+
> {
|
|
232
|
+
try {
|
|
233
|
+
const rawName = typeof params.machineName === 'string' ? params.machineName.trim() : '';
|
|
234
|
+
const machineName = rawName || 'StateMachine';
|
|
235
|
+
|
|
236
|
+
const normalizedStates: Array<{ name: string; animation?: string; isEntry?: boolean; isExit?: boolean }> =
|
|
237
|
+
Array.isArray(params.states)
|
|
238
|
+
? params.states
|
|
239
|
+
.map((s) => {
|
|
240
|
+
if (typeof s === 'string') {
|
|
241
|
+
const name = s.trim();
|
|
242
|
+
return name ? { name } : undefined;
|
|
243
|
+
}
|
|
244
|
+
if (s && typeof s === 'object' && typeof (s as any).name === 'string') {
|
|
245
|
+
const name = (s as any).name.trim();
|
|
246
|
+
if (!name) return undefined;
|
|
247
|
+
return s as { name: string; animation?: string; isEntry?: boolean; isExit?: boolean };
|
|
248
|
+
}
|
|
249
|
+
return undefined;
|
|
250
|
+
})
|
|
251
|
+
.filter((s): s is { name: string; animation?: string; isEntry?: boolean; isExit?: boolean } => !!s)
|
|
252
|
+
: [];
|
|
253
|
+
|
|
254
|
+
const normalizedTransitionsRaw = Array.isArray(params.transitions)
|
|
255
|
+
? params.transitions
|
|
256
|
+
.map((t) => {
|
|
257
|
+
if (!t || typeof t !== 'object') return undefined;
|
|
258
|
+
const src = (t.sourceState || '').trim();
|
|
259
|
+
const dst = (t.targetState || '').trim();
|
|
260
|
+
if (!src || !dst) return undefined;
|
|
261
|
+
return { sourceState: src, targetState: dst, condition: t.condition };
|
|
262
|
+
})
|
|
263
|
+
.filter((t) => !!t)
|
|
264
|
+
: [];
|
|
265
|
+
|
|
266
|
+
const normalizedTransitions = normalizedTransitionsRaw as Array<{
|
|
267
|
+
sourceState: string;
|
|
268
|
+
targetState: string;
|
|
269
|
+
condition?: string;
|
|
270
|
+
}>;
|
|
271
|
+
|
|
272
|
+
const blueprintPath = typeof params.blueprintPath === 'string' && params.blueprintPath.trim().length > 0
|
|
273
|
+
? params.blueprintPath.trim()
|
|
274
|
+
: undefined;
|
|
275
|
+
|
|
276
|
+
const key = `StateMachine:${machineName}`;
|
|
277
|
+
this.trackArtifact(key, {
|
|
278
|
+
path: blueprintPath,
|
|
279
|
+
type: 'AnimationStateMachine',
|
|
280
|
+
metadata: {
|
|
281
|
+
machineName,
|
|
282
|
+
states: normalizedStates,
|
|
283
|
+
transitions: normalizedTransitions
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
success: true,
|
|
289
|
+
message: `State machine '${machineName}' specification recorded`,
|
|
290
|
+
machineName,
|
|
291
|
+
blueprintPath,
|
|
292
|
+
states: normalizedStates.length ? normalizedStates : undefined,
|
|
293
|
+
transitions: normalizedTransitions.length ? normalizedTransitions : undefined
|
|
294
|
+
};
|
|
295
|
+
} catch (err) {
|
|
296
|
+
const error = String(err);
|
|
297
|
+
return {
|
|
298
|
+
success: false,
|
|
299
|
+
message: `Failed to record state machine specification: ${error}`,
|
|
300
|
+
error
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
146
305
|
async createBlendSpace(params: {
|
|
147
306
|
name: string;
|
|
148
307
|
savePath?: string;
|
|
@@ -151,7 +310,10 @@ export class AnimationTools {
|
|
|
151
310
|
horizontalAxis?: { name: string; minValue: number; maxValue: number };
|
|
152
311
|
verticalAxis?: { name: string; minValue: number; maxValue: number };
|
|
153
312
|
samples?: Array<{ animation: string; x: number; y?: number }>;
|
|
154
|
-
}): Promise<
|
|
313
|
+
}): Promise<
|
|
314
|
+
| { success: true; message: string; path: string; skeletonPath?: string; warnings?: string[]; details?: unknown }
|
|
315
|
+
| { success: false; error: string }
|
|
316
|
+
> {
|
|
155
317
|
try {
|
|
156
318
|
const targetPath = params.savePath ?? '/Game/Animations';
|
|
157
319
|
const validation = validateAssetParams({ name: params.name, savePath: targetPath });
|
|
@@ -163,42 +325,64 @@ export class AnimationTools {
|
|
|
163
325
|
const assetName = sanitized.name;
|
|
164
326
|
const assetPath = sanitized.savePath ?? targetPath;
|
|
165
327
|
const dimensions = params.dimensions === 2 ? 2 : 1;
|
|
166
|
-
const blendSpaceType = dimensions === 2 ? 'BlendSpace' : 'BlendSpace1D';
|
|
167
328
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
329
|
+
if (this.automationBridge && typeof this.automationBridge.sendAutomationRequest === 'function') {
|
|
330
|
+
try {
|
|
331
|
+
const payload: any = {
|
|
332
|
+
action: 'create_blend_space',
|
|
333
|
+
name: assetName,
|
|
334
|
+
savePath: assetPath,
|
|
335
|
+
skeletonPath: params.skeletonPath,
|
|
336
|
+
dimensions,
|
|
337
|
+
samples: params.samples
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
if (params.horizontalAxis) {
|
|
341
|
+
payload.minX = params.horizontalAxis.minValue;
|
|
342
|
+
payload.maxX = params.horizontalAxis.maxValue;
|
|
343
|
+
// gridX is not in params.horizontalAxis, maybe default or add to interface?
|
|
344
|
+
// The C++ code defaults GridX to 3.0.
|
|
345
|
+
}
|
|
172
346
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
347
|
+
if (params.verticalAxis) {
|
|
348
|
+
payload.minY = params.verticalAxis.minValue;
|
|
349
|
+
payload.maxY = params.verticalAxis.maxValue;
|
|
350
|
+
}
|
|
176
351
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
352
|
+
const resp = await this.automationBridge.sendAutomationRequest('animation_physics', cleanObject(payload));
|
|
353
|
+
const result = resp?.result ?? resp;
|
|
354
|
+
const resultObj = result && typeof result === 'object' ? result as Record<string, unknown> : undefined;
|
|
355
|
+
const isSuccess = resp && resp.success !== false && !!resultObj;
|
|
356
|
+
|
|
357
|
+
if (isSuccess && resultObj) {
|
|
358
|
+
const path = typeof resultObj.blendSpacePath === 'string'
|
|
359
|
+
? (resultObj.blendSpacePath as string)
|
|
360
|
+
: `${assetPath}/${assetName}`;
|
|
361
|
+
const warnings = Array.isArray(resultObj.warnings) ? (resultObj.warnings as string[]) : undefined;
|
|
362
|
+
const details = resultObj ? resultObj.details : undefined;
|
|
363
|
+
return {
|
|
364
|
+
success: true,
|
|
365
|
+
message: resp.message || `Blend Space ${assetName} created`,
|
|
366
|
+
path,
|
|
367
|
+
skeletonPath: params.skeletonPath,
|
|
368
|
+
details,
|
|
369
|
+
warnings
|
|
370
|
+
};
|
|
371
|
+
}
|
|
182
372
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
373
|
+
const message = typeof resp?.message === 'string'
|
|
374
|
+
? resp.message
|
|
375
|
+
: (typeof resp?.error === 'string' ? resp.error : 'Blend space creation failed');
|
|
376
|
+
const error = typeof resp?.error === 'string' ? resp.error : message;
|
|
188
377
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
|
|
378
|
+
return { success: false, error };
|
|
379
|
+
} catch (err) {
|
|
380
|
+
const error = String(err);
|
|
381
|
+
return { success: false, error: `Failed to create blend space: ${error}` };
|
|
193
382
|
}
|
|
194
383
|
}
|
|
195
384
|
|
|
196
|
-
|
|
197
|
-
return {
|
|
198
|
-
success: true,
|
|
199
|
-
message: `Blend Space ${assetName} created`,
|
|
200
|
-
path: `${assetPath}/${assetName}`
|
|
201
|
-
};
|
|
385
|
+
return { success: false, error: 'Automation bridge not connected for createBlendSpace' };
|
|
202
386
|
} catch (err) {
|
|
203
387
|
return { success: false, error: `Failed to create blend space: ${err}` };
|
|
204
388
|
}
|
|
@@ -214,7 +398,10 @@ export class AnimationTools {
|
|
|
214
398
|
bone?: string;
|
|
215
399
|
defaultValue?: unknown;
|
|
216
400
|
}>;
|
|
217
|
-
}): Promise<
|
|
401
|
+
}): Promise<
|
|
402
|
+
| { success: true; message: string; path: string; warnings?: string[]; details?: unknown }
|
|
403
|
+
| { success: false; error: string }
|
|
404
|
+
> {
|
|
218
405
|
try {
|
|
219
406
|
const targetPath = params.savePath ?? '/Game/Animations';
|
|
220
407
|
const validation = validateAssetParams({ name: params.name, savePath: targetPath });
|
|
@@ -227,32 +414,445 @@ export class AnimationTools {
|
|
|
227
414
|
const assetPath = sanitized.savePath ?? targetPath;
|
|
228
415
|
const fullPath = `${assetPath}/${assetName}`;
|
|
229
416
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
417
|
+
if (this.automationBridge && typeof this.automationBridge.sendAutomationRequest === 'function') {
|
|
418
|
+
try {
|
|
419
|
+
const resp = await this.automationBridge.sendAutomationRequest('animation_physics', cleanObject({
|
|
420
|
+
action: 'setup_ik',
|
|
421
|
+
name: assetName,
|
|
422
|
+
savePath: assetPath,
|
|
423
|
+
skeletonPath: params.skeletonPath,
|
|
424
|
+
controls: params.controls
|
|
425
|
+
}), { timeoutMs: 60000 });
|
|
426
|
+
const result = resp?.result ?? resp;
|
|
427
|
+
const resultObj = result && typeof result === 'object' ? result as Record<string, unknown> : undefined;
|
|
428
|
+
const isSuccess = resp && resp.success !== false && !!resultObj;
|
|
429
|
+
|
|
430
|
+
if (isSuccess && resultObj) {
|
|
431
|
+
const controlRigPath = typeof resultObj.controlRigPath === 'string'
|
|
432
|
+
? (resultObj.controlRigPath as string)
|
|
433
|
+
: fullPath;
|
|
434
|
+
const warnings = Array.isArray(resultObj.warnings) ? (resultObj.warnings as string[]) : undefined;
|
|
435
|
+
const details = resultObj ? resultObj.details : undefined;
|
|
436
|
+
this.trackArtifact(assetName, { path: controlRigPath, type: 'ControlRig' });
|
|
437
|
+
return {
|
|
438
|
+
success: true,
|
|
439
|
+
message: resp.message || `Control Rig ${assetName} created`,
|
|
440
|
+
path: controlRigPath,
|
|
441
|
+
warnings,
|
|
442
|
+
details
|
|
443
|
+
};
|
|
444
|
+
}
|
|
234
445
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
446
|
+
const message = typeof resp?.message === 'string'
|
|
447
|
+
? resp.message
|
|
448
|
+
: (typeof resp?.error === 'string' ? resp.error : 'Control Rig setup failed');
|
|
449
|
+
const error = typeof resp?.error === 'string' ? resp.error : message;
|
|
450
|
+
|
|
451
|
+
return { success: false, error };
|
|
452
|
+
} catch (err) {
|
|
453
|
+
const error = String(err);
|
|
454
|
+
return { success: false, error: `Failed to setup control rig: ${error}` };
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return { success: false, error: 'Automation bridge not connected for setupControlRig' };
|
|
459
|
+
} catch (err) {
|
|
460
|
+
return { success: false, error: `Failed to setup control rig: ${err}` };
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
async setupIK(params: {
|
|
465
|
+
actorName?: string;
|
|
466
|
+
ikBones?: string[];
|
|
467
|
+
enableFootPlacement?: boolean;
|
|
468
|
+
}): Promise<
|
|
469
|
+
| {
|
|
470
|
+
success: true;
|
|
471
|
+
message: string;
|
|
472
|
+
actorName: string;
|
|
473
|
+
ikBones?: string[];
|
|
474
|
+
enableFootPlacement?: boolean;
|
|
475
|
+
}
|
|
476
|
+
| { success: false; message: string; error: string }
|
|
477
|
+
> {
|
|
478
|
+
try {
|
|
479
|
+
const actorName = (params.actorName || 'Character').trim();
|
|
480
|
+
const ikBones = Array.isArray(params.ikBones)
|
|
481
|
+
? params.ikBones.map((b) => String(b)).filter((b) => b.trim().length > 0)
|
|
482
|
+
: [];
|
|
483
|
+
|
|
484
|
+
const key = `IK:${actorName}`;
|
|
485
|
+
this.trackArtifact(key, {
|
|
486
|
+
type: 'IKSetup',
|
|
487
|
+
metadata: {
|
|
488
|
+
actorName,
|
|
489
|
+
ikBones,
|
|
490
|
+
enableFootPlacement: params.enableFootPlacement === true
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
return {
|
|
495
|
+
success: true,
|
|
496
|
+
message: `IK setup specification recorded for actor '${actorName}'`,
|
|
497
|
+
actorName,
|
|
498
|
+
ikBones: ikBones.length ? ikBones : undefined,
|
|
499
|
+
enableFootPlacement: params.enableFootPlacement === true ? true : undefined
|
|
500
|
+
};
|
|
501
|
+
} catch (err) {
|
|
502
|
+
const error = String(err);
|
|
503
|
+
return {
|
|
504
|
+
success: false,
|
|
505
|
+
message: `Failed to record IK setup: ${error}`,
|
|
506
|
+
error
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
async createProceduralAnim(params: {
|
|
512
|
+
systemName?: string;
|
|
513
|
+
baseAnimation?: string;
|
|
514
|
+
modifiers?: any[];
|
|
515
|
+
savePath?: string;
|
|
516
|
+
}): Promise<
|
|
517
|
+
| {
|
|
518
|
+
success: true;
|
|
519
|
+
message: string;
|
|
520
|
+
path: string;
|
|
521
|
+
systemName: string;
|
|
522
|
+
}
|
|
523
|
+
| { success: false; message: string; error: string }
|
|
524
|
+
> {
|
|
525
|
+
try {
|
|
526
|
+
const baseName = (params.systemName || '').trim()
|
|
527
|
+
|| (params.baseAnimation ? params.baseAnimation.split('/').pop() || '' : '');
|
|
528
|
+
const systemName = baseName || 'ProceduralSystem';
|
|
529
|
+
const basePath = (params.savePath || '/Game/Animations').replace(/\/+$/, '');
|
|
530
|
+
const path = `${basePath || '/Game/Animations'}/${systemName}`;
|
|
531
|
+
|
|
532
|
+
this.trackArtifact(systemName, {
|
|
533
|
+
path,
|
|
534
|
+
type: 'ProceduralAnimation',
|
|
535
|
+
metadata: {
|
|
536
|
+
baseAnimation: params.baseAnimation,
|
|
537
|
+
modifiers: Array.isArray(params.modifiers) ? params.modifiers : []
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
return {
|
|
542
|
+
success: true,
|
|
543
|
+
message: `Procedural animation system '${systemName}' specification recorded at ${path}`,
|
|
544
|
+
path,
|
|
545
|
+
systemName
|
|
546
|
+
};
|
|
547
|
+
} catch (err) {
|
|
548
|
+
const error = String(err);
|
|
549
|
+
return {
|
|
550
|
+
success: false,
|
|
551
|
+
message: `Failed to record procedural animation system: ${error}`,
|
|
552
|
+
error
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
async createBlendTree(params: {
|
|
558
|
+
treeName?: string;
|
|
559
|
+
blendType?: string;
|
|
560
|
+
basePose?: string;
|
|
561
|
+
additiveAnimations?: any[];
|
|
562
|
+
savePath?: string;
|
|
563
|
+
}): Promise<
|
|
564
|
+
| {
|
|
565
|
+
success: true;
|
|
566
|
+
message: string;
|
|
567
|
+
path: string;
|
|
568
|
+
treeName: string;
|
|
569
|
+
}
|
|
570
|
+
| { success: false; message: string; error: string }
|
|
571
|
+
> {
|
|
572
|
+
try {
|
|
573
|
+
const rawName = (params.treeName || '').trim();
|
|
574
|
+
const treeName = rawName || 'BlendTree';
|
|
575
|
+
const basePath = (params.savePath || '/Game/Animations').replace(/\/+$/, '');
|
|
576
|
+
const path = `${basePath || '/Game/Animations'}/${treeName}`;
|
|
577
|
+
|
|
578
|
+
this.trackArtifact(treeName, {
|
|
579
|
+
path,
|
|
580
|
+
type: 'BlendTree',
|
|
581
|
+
metadata: {
|
|
582
|
+
blendType: params.blendType,
|
|
583
|
+
basePose: params.basePose,
|
|
584
|
+
additiveAnimations: Array.isArray(params.additiveAnimations) ? params.additiveAnimations : []
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
return {
|
|
589
|
+
success: true,
|
|
590
|
+
message: `Blend tree '${treeName}' specification recorded at ${path}`,
|
|
591
|
+
path,
|
|
592
|
+
treeName
|
|
593
|
+
};
|
|
594
|
+
} catch (err) {
|
|
595
|
+
const error = String(err);
|
|
596
|
+
return {
|
|
597
|
+
success: false,
|
|
598
|
+
message: `Failed to record blend tree specification: ${error}`,
|
|
599
|
+
error
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
async cleanup(artifacts?: string[]): Promise<
|
|
605
|
+
| {
|
|
606
|
+
success: boolean;
|
|
607
|
+
message: string;
|
|
608
|
+
removed?: string[];
|
|
609
|
+
missing?: string[];
|
|
610
|
+
}
|
|
611
|
+
| { success: false; message: string; error: string }
|
|
612
|
+
> {
|
|
613
|
+
try {
|
|
614
|
+
const pathsToDelete: string[] = [];
|
|
615
|
+
|
|
616
|
+
if (Array.isArray(artifacts) && artifacts.length > 0) {
|
|
617
|
+
pathsToDelete.push(...artifacts.map((a) => String(a).trim()).filter((a) => a.length > 0));
|
|
618
|
+
} else {
|
|
619
|
+
// If no specific artifacts provided, clear all managed ones
|
|
620
|
+
for (const [key, val] of this.managedArtifacts.entries()) {
|
|
621
|
+
if (val.path) pathsToDelete.push(val.path);
|
|
622
|
+
else pathsToDelete.push(key);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
if (pathsToDelete.length === 0) {
|
|
627
|
+
return {
|
|
628
|
+
success: true,
|
|
629
|
+
message: 'No artifacts to cleanup.'
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
let bridgeMessage = '';
|
|
634
|
+
if (this.automationBridge) {
|
|
635
|
+
try {
|
|
636
|
+
const response = await this.automationBridge.sendAutomationRequest('animation_physics', {
|
|
637
|
+
action: 'cleanup',
|
|
638
|
+
artifacts: pathsToDelete
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
if (!response.success) {
|
|
642
|
+
bridgeMessage = ` (Engine cleanup failed: ${response.message})`;
|
|
643
|
+
} else {
|
|
644
|
+
bridgeMessage = ' (Engine assets deleted)';
|
|
244
645
|
}
|
|
646
|
+
} catch (e) {
|
|
647
|
+
bridgeMessage = ` (Engine connection failed: ${e})`;
|
|
648
|
+
}
|
|
649
|
+
} else {
|
|
650
|
+
bridgeMessage = ' (No automation bridge available)';
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const removed: string[] = [];
|
|
654
|
+
|
|
655
|
+
// Clear local registry
|
|
656
|
+
const toRemoveKeys: string[] = [];
|
|
657
|
+
for (const [key, val] of this.managedArtifacts.entries()) {
|
|
658
|
+
if (pathsToDelete.includes(key) || (val.path && pathsToDelete.includes(val.path))) {
|
|
659
|
+
toRemoveKeys.push(key);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
for (const key of toRemoveKeys) {
|
|
664
|
+
this.managedArtifacts.delete(key);
|
|
665
|
+
removed.push(key);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// Add any explicit paths that were not in managed artifacts but requested
|
|
669
|
+
for (const path of pathsToDelete) {
|
|
670
|
+
if (!removed.includes(path)) {
|
|
671
|
+
// We don't have it locally, but we tried to delete it from engine
|
|
672
|
+
removed.push(path);
|
|
245
673
|
}
|
|
246
674
|
}
|
|
247
675
|
|
|
248
|
-
await this.bridge.executeConsoleCommands(commands);
|
|
249
676
|
return {
|
|
250
677
|
success: true,
|
|
251
|
-
message: `
|
|
252
|
-
|
|
678
|
+
message: `Cleanup attempt processed for ${pathsToDelete.length} artifacts${bridgeMessage}`,
|
|
679
|
+
removed
|
|
253
680
|
};
|
|
254
681
|
} catch (err) {
|
|
255
|
-
|
|
682
|
+
const error = String(err);
|
|
683
|
+
return {
|
|
684
|
+
success: false,
|
|
685
|
+
message: `Failed to cleanup animation artifacts: ${error}`,
|
|
686
|
+
error
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
async createAnimationAsset(params: {
|
|
692
|
+
name: string;
|
|
693
|
+
path?: string;
|
|
694
|
+
savePath?: string;
|
|
695
|
+
skeletonPath?: string;
|
|
696
|
+
assetType?: string;
|
|
697
|
+
}): Promise<
|
|
698
|
+
| {
|
|
699
|
+
success: true;
|
|
700
|
+
message: string;
|
|
701
|
+
path: string;
|
|
702
|
+
assetType?: string;
|
|
703
|
+
existingAsset?: boolean;
|
|
704
|
+
}
|
|
705
|
+
| { success: false; message: string; error: string }
|
|
706
|
+
> {
|
|
707
|
+
try {
|
|
708
|
+
const targetPath = (params.path || params.savePath) ?? '/Game/Animations';
|
|
709
|
+
const validation = validateAssetParams({ name: params.name, savePath: targetPath });
|
|
710
|
+
if (!validation.valid) {
|
|
711
|
+
const message = validation.error ?? 'Invalid asset parameters';
|
|
712
|
+
return { success: false, message, error: message };
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
const sanitized = validation.sanitized;
|
|
716
|
+
const assetName = sanitized.name;
|
|
717
|
+
const assetPath = sanitized.savePath ?? targetPath;
|
|
718
|
+
const fullPath = `${assetPath}/${assetName}`;
|
|
719
|
+
|
|
720
|
+
const normalizedType = (params.assetType || 'sequence').toLowerCase();
|
|
721
|
+
|
|
722
|
+
if (this.automationBridge && typeof this.automationBridge.sendAutomationRequest === 'function') {
|
|
723
|
+
try {
|
|
724
|
+
const payload: any = {
|
|
725
|
+
action: 'create_animation_asset',
|
|
726
|
+
name: assetName,
|
|
727
|
+
savePath: assetPath,
|
|
728
|
+
skeletonPath: params.skeletonPath,
|
|
729
|
+
assetType: normalizedType
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
const resp = await this.automationBridge.sendAutomationRequest('animation_physics', cleanObject(payload), { timeoutMs: 60000 });
|
|
733
|
+
const result = resp?.result ?? resp;
|
|
734
|
+
const resultObj = result && typeof result === 'object' ? (result as Record<string, unknown>) : undefined;
|
|
735
|
+
const isSuccess = resp && resp.success !== false && !!resultObj;
|
|
736
|
+
|
|
737
|
+
if (isSuccess && resultObj) {
|
|
738
|
+
const assetPathResult = typeof resultObj.assetPath === 'string' ? (resultObj.assetPath as string) : fullPath;
|
|
739
|
+
const assetTypeResult = typeof resultObj.assetType === 'string' ? (resultObj.assetType as string) : undefined;
|
|
740
|
+
const existingAsset = typeof resultObj.existingAsset === 'boolean' ? Boolean(resultObj.existingAsset) : false;
|
|
741
|
+
|
|
742
|
+
this.trackArtifact(assetName, { path: assetPathResult, type: 'AnimationAsset' });
|
|
743
|
+
|
|
744
|
+
return {
|
|
745
|
+
success: true,
|
|
746
|
+
message: resp.message || `Animation asset created at ${assetPathResult}`,
|
|
747
|
+
path: assetPathResult,
|
|
748
|
+
assetType: assetTypeResult,
|
|
749
|
+
existingAsset
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
const message = typeof resp?.message === 'string'
|
|
754
|
+
? resp.message
|
|
755
|
+
: (typeof resp?.error === 'string' ? resp.error : 'Animation asset creation failed');
|
|
756
|
+
const error = typeof resp?.error === 'string' ? resp.error : message;
|
|
757
|
+
|
|
758
|
+
return { success: false, message, error };
|
|
759
|
+
} catch (err) {
|
|
760
|
+
const error = String(err);
|
|
761
|
+
return {
|
|
762
|
+
success: false,
|
|
763
|
+
message: `Failed to create animation asset: ${error}`,
|
|
764
|
+
error
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
return {
|
|
770
|
+
success: false,
|
|
771
|
+
message: 'Automation bridge not connected for createAnimationAsset',
|
|
772
|
+
error: 'AUTOMATION_BRIDGE_UNAVAILABLE'
|
|
773
|
+
};
|
|
774
|
+
} catch (err) {
|
|
775
|
+
const error = String(err);
|
|
776
|
+
return {
|
|
777
|
+
success: false,
|
|
778
|
+
message: `Failed to create animation asset: ${error}`,
|
|
779
|
+
error
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
async addNotify(params: {
|
|
785
|
+
animationPath?: string;
|
|
786
|
+
assetPath?: string;
|
|
787
|
+
notifyName?: string;
|
|
788
|
+
time?: number;
|
|
789
|
+
}): Promise<
|
|
790
|
+
| {
|
|
791
|
+
success: true;
|
|
792
|
+
message: string;
|
|
793
|
+
assetPath: string;
|
|
794
|
+
notifyName: string;
|
|
795
|
+
time: number;
|
|
796
|
+
}
|
|
797
|
+
| { success: false; message: string; error: string }
|
|
798
|
+
> {
|
|
799
|
+
try {
|
|
800
|
+
const rawPath = (params.animationPath || params.assetPath || '').trim();
|
|
801
|
+
if (!rawPath) {
|
|
802
|
+
const error = 'animationPath or assetPath is required for addNotify';
|
|
803
|
+
return { success: false, message: error, error };
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
const notifyName = (params.notifyName || 'Notify').trim();
|
|
807
|
+
const time = typeof params.time === 'number' && params.time >= 0 ? params.time : 0;
|
|
808
|
+
|
|
809
|
+
if (this.automationBridge && typeof this.automationBridge.sendAutomationRequest === 'function') {
|
|
810
|
+
try {
|
|
811
|
+
const resp = await this.automationBridge.sendAutomationRequest(
|
|
812
|
+
'animation_physics',
|
|
813
|
+
cleanObject({
|
|
814
|
+
action: 'add_notify',
|
|
815
|
+
assetPath: rawPath,
|
|
816
|
+
notifyName,
|
|
817
|
+
time
|
|
818
|
+
}),
|
|
819
|
+
{ timeoutMs: 60000 }
|
|
820
|
+
);
|
|
821
|
+
|
|
822
|
+
const result = resp?.result ?? resp;
|
|
823
|
+
const resultObj = result && typeof result === 'object' ? (result as Record<string, unknown>) : undefined;
|
|
824
|
+
|
|
825
|
+
if (resp && resp.success !== false && resultObj) {
|
|
826
|
+
return {
|
|
827
|
+
success: true,
|
|
828
|
+
message: resp.message || `Notify '${notifyName}' added to ${rawPath} at time ${time}`,
|
|
829
|
+
assetPath: typeof resultObj.assetPath === 'string' ? (resultObj.assetPath as string) : rawPath,
|
|
830
|
+
notifyName,
|
|
831
|
+
time
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
} catch (err) {
|
|
835
|
+
const error = String(err);
|
|
836
|
+
return {
|
|
837
|
+
success: false,
|
|
838
|
+
message: `Failed to add notify: ${error}`,
|
|
839
|
+
error
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
return {
|
|
845
|
+
success: false,
|
|
846
|
+
message: 'Automation bridge not connected for addNotify',
|
|
847
|
+
error: 'AUTOMATION_BRIDGE_UNAVAILABLE'
|
|
848
|
+
};
|
|
849
|
+
} catch (err) {
|
|
850
|
+
const error = String(err);
|
|
851
|
+
return {
|
|
852
|
+
success: false,
|
|
853
|
+
message: `Failed to add notify: ${error}`,
|
|
854
|
+
error
|
|
855
|
+
};
|
|
256
856
|
}
|
|
257
857
|
}
|
|
258
858
|
|
|
@@ -318,413 +918,23 @@ export class AnimationTools {
|
|
|
318
918
|
blendOutTime?: number;
|
|
319
919
|
}): Promise<PlayAnimationSuccess | PlayAnimationFailure> {
|
|
320
920
|
try {
|
|
321
|
-
const
|
|
322
|
-
actorName
|
|
323
|
-
|
|
324
|
-
animationPath: params.animationPath,
|
|
325
|
-
playRate: params.playRate ?? 1.0,
|
|
326
|
-
loop: params.loop ?? false,
|
|
327
|
-
blendInTime: params.blendInTime ?? 0.25,
|
|
328
|
-
blendOutTime: params.blendOutTime ?? 0.25
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
const response = await this.bridge.executePython(script);
|
|
332
|
-
const interpreted = interpretStandardResult(response, {
|
|
333
|
-
successMessage: `Animation ${params.animationType} triggered on ${params.actorName}`,
|
|
334
|
-
failureMessage: `Failed to play animation on ${params.actorName}`
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
const payload = interpreted.payload ?? {};
|
|
338
|
-
const warnings = interpreted.warnings ?? coerceStringArray((payload as any).warnings) ?? undefined;
|
|
339
|
-
const details = interpreted.details ?? coerceStringArray((payload as any).details) ?? undefined;
|
|
340
|
-
const availableActors = coerceStringArray((payload as any).availableActors);
|
|
341
|
-
const actorName = coerceString((payload as any).actorName) ?? params.actorName;
|
|
342
|
-
const animationType = coerceString((payload as any).animationType) ?? params.animationType;
|
|
343
|
-
const assetPath = coerceString((payload as any).assetPath) ?? params.animationPath;
|
|
344
|
-
const errorMessage = coerceString((payload as any).error) ?? interpreted.error ?? `Animation playback failed for ${params.actorName}`;
|
|
345
|
-
|
|
346
|
-
if (interpreted.success) {
|
|
347
|
-
const result: PlayAnimationSuccess = {
|
|
348
|
-
success: true,
|
|
349
|
-
message: interpreted.message
|
|
350
|
-
};
|
|
351
|
-
|
|
352
|
-
if (warnings && warnings.length > 0) {
|
|
353
|
-
result.warnings = warnings;
|
|
354
|
-
}
|
|
355
|
-
if (details && details.length > 0) {
|
|
356
|
-
result.details = details;
|
|
357
|
-
}
|
|
358
|
-
if (actorName) {
|
|
359
|
-
result.actorName = actorName;
|
|
360
|
-
}
|
|
361
|
-
if (animationType) {
|
|
362
|
-
result.animationType = animationType;
|
|
363
|
-
}
|
|
364
|
-
if (assetPath) {
|
|
365
|
-
result.assetPath = assetPath;
|
|
366
|
-
}
|
|
921
|
+
const commands: string[] = [
|
|
922
|
+
`PlayAnimation ${params.actorName} ${params.animationType} ${params.animationPath} ${params.playRate ?? 1.0} ${params.loop ?? false} ${params.blendInTime ?? 0.25} ${params.blendOutTime ?? 0.25}`
|
|
923
|
+
];
|
|
367
924
|
|
|
368
|
-
|
|
369
|
-
}
|
|
925
|
+
await this.bridge.executeConsoleCommands(commands);
|
|
370
926
|
|
|
371
|
-
|
|
372
|
-
success:
|
|
373
|
-
message: `
|
|
374
|
-
|
|
927
|
+
return {
|
|
928
|
+
success: true,
|
|
929
|
+
message: `Animation ${params.animationType} triggered on ${params.actorName}`,
|
|
930
|
+
actorName: params.actorName,
|
|
931
|
+
animationType: params.animationType,
|
|
932
|
+
assetPath: params.animationPath
|
|
375
933
|
};
|
|
376
|
-
|
|
377
|
-
if (warnings && warnings.length > 0) {
|
|
378
|
-
failure.warnings = warnings;
|
|
379
|
-
}
|
|
380
|
-
if (details && details.length > 0) {
|
|
381
|
-
failure.details = details;
|
|
382
|
-
}
|
|
383
|
-
if (availableActors && availableActors.length > 0) {
|
|
384
|
-
failure.availableActors = availableActors;
|
|
385
|
-
}
|
|
386
|
-
if (actorName) {
|
|
387
|
-
failure.actorName = actorName;
|
|
388
|
-
}
|
|
389
|
-
if (animationType) {
|
|
390
|
-
failure.animationType = animationType;
|
|
391
|
-
}
|
|
392
|
-
if (assetPath) {
|
|
393
|
-
failure.assetPath = assetPath;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
return failure;
|
|
397
934
|
} catch (err) {
|
|
398
935
|
const error = `Failed to play animation: ${err}`;
|
|
399
936
|
return { success: false, message: error, error: String(err) };
|
|
400
937
|
}
|
|
401
938
|
}
|
|
402
939
|
|
|
403
|
-
private buildCreateAnimationBlueprintScript(args: {
|
|
404
|
-
name: string;
|
|
405
|
-
path: string;
|
|
406
|
-
skeletonPath: string;
|
|
407
|
-
}): string {
|
|
408
|
-
const payload = JSON.stringify(args);
|
|
409
|
-
return `
|
|
410
|
-
import unreal
|
|
411
|
-
import json
|
|
412
|
-
import traceback
|
|
413
|
-
|
|
414
|
-
params = json.loads(${JSON.stringify(payload)})
|
|
415
|
-
|
|
416
|
-
result = {
|
|
417
|
-
"success": False,
|
|
418
|
-
"message": "",
|
|
419
|
-
"error": "",
|
|
420
|
-
"warnings": [],
|
|
421
|
-
"details": [],
|
|
422
|
-
"exists": False,
|
|
423
|
-
"skeleton": params.get("skeletonPath") or ""
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
try:
|
|
427
|
-
asset_path = (params.get("path") or "/Game").rstrip('/')
|
|
428
|
-
asset_name = params.get("name") or ""
|
|
429
|
-
full_path = f"{asset_path}/{asset_name}"
|
|
430
|
-
result["path"] = full_path
|
|
431
|
-
|
|
432
|
-
editor_lib = unreal.EditorAssetLibrary
|
|
433
|
-
asset_subsystem = None
|
|
434
|
-
try:
|
|
435
|
-
asset_subsystem = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem)
|
|
436
|
-
except Exception:
|
|
437
|
-
asset_subsystem = None
|
|
438
|
-
|
|
439
|
-
skeleton_path = params.get("skeletonPath")
|
|
440
|
-
skeleton_asset = None
|
|
441
|
-
if skeleton_path:
|
|
442
|
-
if editor_lib.does_asset_exist(skeleton_path):
|
|
443
|
-
skeleton_asset = editor_lib.load_asset(skeleton_path)
|
|
444
|
-
if skeleton_asset and isinstance(skeleton_asset, unreal.Skeleton):
|
|
445
|
-
result["details"].append(f"Using skeleton: {skeleton_path}")
|
|
446
|
-
result["skeleton"] = skeleton_path
|
|
447
|
-
else:
|
|
448
|
-
result["error"] = f"Skeleton asset invalid at {skeleton_path}"
|
|
449
|
-
result["warnings"].append(result["error"])
|
|
450
|
-
skeleton_asset = None
|
|
451
|
-
else:
|
|
452
|
-
result["error"] = f"Skeleton not found at {skeleton_path}"
|
|
453
|
-
result["warnings"].append(result["error"])
|
|
454
|
-
|
|
455
|
-
if not skeleton_asset:
|
|
456
|
-
raise RuntimeError(result["error"] or f"Skeleton {skeleton_path} unavailable")
|
|
457
|
-
|
|
458
|
-
does_exist = False
|
|
459
|
-
try:
|
|
460
|
-
if asset_subsystem and hasattr(asset_subsystem, 'does_asset_exist'):
|
|
461
|
-
does_exist = asset_subsystem.does_asset_exist(full_path)
|
|
462
|
-
else:
|
|
463
|
-
does_exist = editor_lib.does_asset_exist(full_path)
|
|
464
|
-
except Exception:
|
|
465
|
-
does_exist = editor_lib.does_asset_exist(full_path)
|
|
466
|
-
|
|
467
|
-
if does_exist:
|
|
468
|
-
result["exists"] = True
|
|
469
|
-
loaded = editor_lib.load_asset(full_path)
|
|
470
|
-
if loaded:
|
|
471
|
-
result["success"] = True
|
|
472
|
-
result["message"] = f"Animation Blueprint already exists at {full_path}"
|
|
473
|
-
result["details"].append(result["message"])
|
|
474
|
-
else:
|
|
475
|
-
result["error"] = f"Asset exists but could not be loaded: {full_path}"
|
|
476
|
-
result["warnings"].append(result["error"])
|
|
477
|
-
else:
|
|
478
|
-
factory = unreal.AnimBlueprintFactory()
|
|
479
|
-
if skeleton_asset:
|
|
480
|
-
try:
|
|
481
|
-
factory.target_skeleton = skeleton_asset
|
|
482
|
-
except Exception as assign_error:
|
|
483
|
-
result["warnings"].append(f"Unable to assign skeleton {skeleton_path}: {assign_error}")
|
|
484
|
-
|
|
485
|
-
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
|
|
486
|
-
created = asset_tools.create_asset(
|
|
487
|
-
asset_name=asset_name,
|
|
488
|
-
package_path=asset_path,
|
|
489
|
-
asset_class=unreal.AnimBlueprint,
|
|
490
|
-
factory=factory
|
|
491
|
-
)
|
|
492
|
-
|
|
493
|
-
if created:
|
|
494
|
-
editor_lib.save_asset(full_path, only_if_is_dirty=False)
|
|
495
|
-
result["success"] = True
|
|
496
|
-
result["message"] = f"Animation Blueprint created at {full_path}"
|
|
497
|
-
result["details"].append(result["message"])
|
|
498
|
-
else:
|
|
499
|
-
result["error"] = f"Failed to create Animation Blueprint {asset_name}"
|
|
500
|
-
|
|
501
|
-
except Exception as exc:
|
|
502
|
-
result["error"] = str(exc)
|
|
503
|
-
result["warnings"].append(result["error"])
|
|
504
|
-
tb = traceback.format_exc()
|
|
505
|
-
if tb:
|
|
506
|
-
result.setdefault("details", []).append(tb)
|
|
507
|
-
|
|
508
|
-
if result["success"] and not result.get("message"):
|
|
509
|
-
result["message"] = f"Animation Blueprint created at {result.get('path')}"
|
|
510
|
-
|
|
511
|
-
if not result["success"] and not result.get("error"):
|
|
512
|
-
result["error"] = "Animation Blueprint creation failed"
|
|
513
|
-
|
|
514
|
-
if not result.get("warnings"):
|
|
515
|
-
result.pop("warnings", None)
|
|
516
|
-
if not result.get("details"):
|
|
517
|
-
result.pop("details", None)
|
|
518
|
-
if not result.get("error"):
|
|
519
|
-
result.pop("error", None)
|
|
520
|
-
|
|
521
|
-
print('RESULT:' + json.dumps(result))
|
|
522
|
-
`.trim();
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
private parseAnimationBlueprintResponse(
|
|
526
|
-
response: unknown,
|
|
527
|
-
assetName: string,
|
|
528
|
-
assetPath: string
|
|
529
|
-
): CreateAnimationBlueprintSuccess | CreateAnimationBlueprintFailure {
|
|
530
|
-
const interpreted = interpretStandardResult(response, {
|
|
531
|
-
successMessage: `Animation Blueprint ${assetName} created`,
|
|
532
|
-
failureMessage: `Failed to create Animation Blueprint ${assetName}`
|
|
533
|
-
});
|
|
534
|
-
|
|
535
|
-
const payload = interpreted.payload ?? {};
|
|
536
|
-
const path = coerceString((payload as any).path) ?? `${assetPath}/${assetName}`;
|
|
537
|
-
const exists = coerceBoolean((payload as any).exists);
|
|
538
|
-
const skeleton = coerceString((payload as any).skeleton);
|
|
539
|
-
const warnings = interpreted.warnings ?? coerceStringArray((payload as any).warnings) ?? undefined;
|
|
540
|
-
const details = interpreted.details ?? coerceStringArray((payload as any).details) ?? undefined;
|
|
541
|
-
|
|
542
|
-
if (interpreted.success) {
|
|
543
|
-
const result: CreateAnimationBlueprintSuccess = {
|
|
544
|
-
success: true,
|
|
545
|
-
message: interpreted.message,
|
|
546
|
-
path
|
|
547
|
-
};
|
|
548
|
-
|
|
549
|
-
if (typeof exists === 'boolean') {
|
|
550
|
-
result.exists = exists;
|
|
551
|
-
}
|
|
552
|
-
if (skeleton) {
|
|
553
|
-
result.skeleton = skeleton;
|
|
554
|
-
}
|
|
555
|
-
if (warnings && warnings.length > 0) {
|
|
556
|
-
result.warnings = warnings;
|
|
557
|
-
}
|
|
558
|
-
if (details && details.length > 0) {
|
|
559
|
-
result.details = details;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
return result;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
const errorMessage = coerceString((payload as any).error) ?? interpreted.error ?? interpreted.message;
|
|
566
|
-
|
|
567
|
-
const failure: CreateAnimationBlueprintFailure = {
|
|
568
|
-
success: false,
|
|
569
|
-
message: `Failed to create Animation Blueprint: ${errorMessage}`,
|
|
570
|
-
error: errorMessage,
|
|
571
|
-
path
|
|
572
|
-
};
|
|
573
|
-
|
|
574
|
-
if (typeof exists === 'boolean') {
|
|
575
|
-
failure.exists = exists;
|
|
576
|
-
}
|
|
577
|
-
if (skeleton) {
|
|
578
|
-
failure.skeleton = skeleton;
|
|
579
|
-
}
|
|
580
|
-
if (warnings && warnings.length > 0) {
|
|
581
|
-
failure.warnings = warnings;
|
|
582
|
-
}
|
|
583
|
-
if (details && details.length > 0) {
|
|
584
|
-
failure.details = details;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
return failure;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
private buildPlayAnimationScript(args: {
|
|
591
|
-
actorName: string;
|
|
592
|
-
animationType: string;
|
|
593
|
-
animationPath: string;
|
|
594
|
-
playRate: number;
|
|
595
|
-
loop: boolean;
|
|
596
|
-
blendInTime: number;
|
|
597
|
-
blendOutTime: number;
|
|
598
|
-
}): string {
|
|
599
|
-
const payload = JSON.stringify(args);
|
|
600
|
-
return `
|
|
601
|
-
import unreal
|
|
602
|
-
import json
|
|
603
|
-
import traceback
|
|
604
|
-
|
|
605
|
-
params = json.loads(${JSON.stringify(payload)})
|
|
606
|
-
|
|
607
|
-
result = {
|
|
608
|
-
"success": False,
|
|
609
|
-
"message": "",
|
|
610
|
-
"error": "",
|
|
611
|
-
"warnings": [],
|
|
612
|
-
"details": [],
|
|
613
|
-
"actorName": params.get("actorName"),
|
|
614
|
-
"animationType": params.get("animationType"),
|
|
615
|
-
"assetPath": params.get("animationPath"),
|
|
616
|
-
"availableActors": []
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
try:
|
|
620
|
-
actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
|
|
621
|
-
actors = actor_subsystem.get_all_level_actors() if actor_subsystem else []
|
|
622
|
-
target = None
|
|
623
|
-
search = params.get("actorName") or ""
|
|
624
|
-
search_lower = search.lower()
|
|
625
|
-
|
|
626
|
-
for actor in actors:
|
|
627
|
-
if not actor:
|
|
628
|
-
continue
|
|
629
|
-
name = (actor.get_name() or "").lower()
|
|
630
|
-
label = (actor.get_actor_label() or "").lower()
|
|
631
|
-
if search_lower and (search_lower == name or search_lower == label or search_lower in label):
|
|
632
|
-
target = actor
|
|
633
|
-
break
|
|
634
|
-
|
|
635
|
-
if not target:
|
|
636
|
-
result["error"] = f"Actor not found: {search}"
|
|
637
|
-
result["warnings"].append("Actor search yielded no results")
|
|
638
|
-
suggestions = []
|
|
639
|
-
for actor in actors[:20]:
|
|
640
|
-
try:
|
|
641
|
-
suggestions.append(actor.get_actor_label())
|
|
642
|
-
except Exception:
|
|
643
|
-
continue
|
|
644
|
-
if suggestions:
|
|
645
|
-
result["availableActors"] = suggestions
|
|
646
|
-
else:
|
|
647
|
-
try:
|
|
648
|
-
display_name = target.get_actor_label() or target.get_name()
|
|
649
|
-
if display_name:
|
|
650
|
-
result["actorName"] = display_name
|
|
651
|
-
except Exception:
|
|
652
|
-
pass
|
|
653
|
-
|
|
654
|
-
skeletal_component = target.get_component_by_class(unreal.SkeletalMeshComponent)
|
|
655
|
-
if not skeletal_component:
|
|
656
|
-
try:
|
|
657
|
-
skeletal_component = target.get_editor_property('mesh')
|
|
658
|
-
except Exception:
|
|
659
|
-
skeletal_component = None
|
|
660
|
-
|
|
661
|
-
if not skeletal_component:
|
|
662
|
-
result["error"] = "No SkeletalMeshComponent found on actor"
|
|
663
|
-
result["warnings"].append("Actor lacks SkeletalMeshComponent")
|
|
664
|
-
else:
|
|
665
|
-
asset_path = params.get("animationPath")
|
|
666
|
-
if not asset_path or not unreal.EditorAssetLibrary.does_asset_exist(asset_path):
|
|
667
|
-
result["error"] = f"Animation asset not found: {asset_path}"
|
|
668
|
-
result["warnings"].append("Animation asset missing")
|
|
669
|
-
else:
|
|
670
|
-
asset = unreal.EditorAssetLibrary.load_asset(asset_path)
|
|
671
|
-
anim_type = params.get("animationType") or ""
|
|
672
|
-
if anim_type == 'Montage':
|
|
673
|
-
anim_instance = skeletal_component.get_anim_instance()
|
|
674
|
-
if anim_instance:
|
|
675
|
-
try:
|
|
676
|
-
anim_instance.montage_play(asset, params.get("playRate", 1.0))
|
|
677
|
-
result["success"] = True
|
|
678
|
-
result["message"] = f"Montage playing on {result.get('actorName') or search}"
|
|
679
|
-
result["details"].append(result["message"])
|
|
680
|
-
except Exception as play_error:
|
|
681
|
-
result["error"] = f"Failed to play montage: {play_error}"
|
|
682
|
-
result["warnings"].append(result["error"])
|
|
683
|
-
else:
|
|
684
|
-
result["error"] = "AnimInstance not found on SkeletalMeshComponent"
|
|
685
|
-
result["warnings"].append(result["error"])
|
|
686
|
-
elif anim_type == 'Sequence':
|
|
687
|
-
try:
|
|
688
|
-
skeletal_component.play_animation(asset, bool(params.get("loop")))
|
|
689
|
-
try:
|
|
690
|
-
anim_instance = skeletal_component.get_anim_instance()
|
|
691
|
-
if anim_instance:
|
|
692
|
-
anim_instance.set_play_rate(params.get("playRate", 1.0))
|
|
693
|
-
except Exception:
|
|
694
|
-
pass
|
|
695
|
-
result["success"] = True
|
|
696
|
-
result["message"] = f"Sequence playing on {result.get('actorName') or search}"
|
|
697
|
-
result["details"].append(result["message"])
|
|
698
|
-
except Exception as play_error:
|
|
699
|
-
result["error"] = f"Failed to play sequence: {play_error}"
|
|
700
|
-
result["warnings"].append(result["error"])
|
|
701
|
-
else:
|
|
702
|
-
result["error"] = "BlendSpace playback requires Animation Blueprint support"
|
|
703
|
-
result["warnings"].append("Unsupported animation type for direct play")
|
|
704
|
-
|
|
705
|
-
except Exception as exc:
|
|
706
|
-
result["error"] = str(exc)
|
|
707
|
-
result["warnings"].append(result["error"])
|
|
708
|
-
tb = traceback.format_exc()
|
|
709
|
-
if tb:
|
|
710
|
-
result["details"].append(tb)
|
|
711
|
-
|
|
712
|
-
if result["success"] and not result.get("message"):
|
|
713
|
-
result["message"] = f"Animation {result.get('animationType')} triggered on {result.get('actorName') or params.get('actorName')}"
|
|
714
|
-
|
|
715
|
-
if not result["success"] and not result.get("error"):
|
|
716
|
-
result["error"] = "Animation playback failed"
|
|
717
|
-
|
|
718
|
-
if not result.get("warnings"):
|
|
719
|
-
result.pop("warnings", None)
|
|
720
|
-
if not result.get("details"):
|
|
721
|
-
result.pop("details", None)
|
|
722
|
-
if not result.get("availableActors"):
|
|
723
|
-
result.pop("availableActors", None)
|
|
724
|
-
if not result.get("error"):
|
|
725
|
-
result.pop("error", None)
|
|
726
|
-
|
|
727
|
-
print('RESULT:' + json.dumps(result))
|
|
728
|
-
`.trim();
|
|
729
|
-
}
|
|
730
940
|
}
|