unreal-engine-mcp-server 0.4.7 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +26 -0
- package/.env.production +38 -7
- package/.eslintrc.json +0 -54
- package/.eslintrc.override.json +8 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +94 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +56 -0
- package/.github/copilot-instructions.md +478 -45
- package/.github/dependabot.yml +19 -0
- package/.github/labeler.yml +24 -0
- package/.github/labels.yml +70 -0
- package/.github/pull_request_template.md +42 -0
- package/.github/release-drafter-config.yml +51 -0
- package/.github/workflows/auto-merge.yml +38 -0
- package/.github/workflows/ci.yml +38 -0
- package/.github/workflows/dependency-review.yml +17 -0
- package/.github/workflows/gemini-issue-triage.yml +172 -0
- package/.github/workflows/greetings.yml +27 -0
- package/.github/workflows/labeler.yml +17 -0
- package/.github/workflows/links.yml +80 -0
- package/.github/workflows/pr-size-labeler.yml +137 -0
- package/.github/workflows/publish-mcp.yml +13 -7
- package/.github/workflows/release-drafter.yml +23 -0
- package/.github/workflows/release.yml +112 -0
- package/.github/workflows/semantic-pull-request.yml +35 -0
- package/.github/workflows/smoke-test.yml +36 -0
- package/.github/workflows/stale.yml +28 -0
- package/CHANGELOG.md +338 -31
- package/CONTRIBUTING.md +140 -0
- package/GEMINI.md +115 -0
- package/Public/Plugin_setup_guide.mp4 +0 -0
- package/README.md +189 -128
- package/claude_desktop_config_example.json +7 -6
- package/dist/automation/bridge.d.ts +50 -0
- package/dist/automation/bridge.js +452 -0
- package/dist/automation/connection-manager.d.ts +23 -0
- package/dist/automation/connection-manager.js +107 -0
- package/dist/automation/handshake.d.ts +11 -0
- package/dist/automation/handshake.js +89 -0
- package/dist/automation/index.d.ts +3 -0
- package/dist/automation/index.js +3 -0
- package/dist/automation/message-handler.d.ts +12 -0
- package/dist/automation/message-handler.js +149 -0
- package/dist/automation/request-tracker.d.ts +25 -0
- package/dist/automation/request-tracker.js +98 -0
- package/dist/automation/types.d.ts +130 -0
- package/dist/automation/types.js +2 -0
- package/dist/cli.js +32 -5
- package/dist/config.d.ts +26 -0
- package/dist/config.js +59 -0
- package/dist/constants.d.ts +16 -0
- package/dist/constants.js +16 -0
- package/dist/graphql/loaders.d.ts +64 -0
- package/dist/graphql/loaders.js +117 -0
- package/dist/graphql/resolvers.d.ts +268 -0
- package/dist/graphql/resolvers.js +746 -0
- package/dist/graphql/schema.d.ts +5 -0
- package/dist/graphql/schema.js +437 -0
- package/dist/graphql/server.d.ts +26 -0
- package/dist/graphql/server.js +117 -0
- package/dist/graphql/types.d.ts +9 -0
- package/dist/graphql/types.js +2 -0
- package/dist/handlers/resource-handlers.d.ts +20 -0
- package/dist/handlers/resource-handlers.js +180 -0
- package/dist/index.d.ts +33 -18
- package/dist/index.js +130 -619
- package/dist/resources/actors.d.ts +17 -12
- package/dist/resources/actors.js +56 -76
- package/dist/resources/assets.d.ts +6 -14
- package/dist/resources/assets.js +115 -147
- package/dist/resources/levels.d.ts +13 -13
- package/dist/resources/levels.js +25 -34
- package/dist/server/resource-registry.d.ts +20 -0
- package/dist/server/resource-registry.js +37 -0
- package/dist/server/tool-registry.d.ts +23 -0
- package/dist/server/tool-registry.js +322 -0
- package/dist/server-setup.d.ts +20 -0
- package/dist/server-setup.js +71 -0
- package/dist/services/health-monitor.d.ts +34 -0
- package/dist/services/health-monitor.js +105 -0
- package/dist/services/metrics-server.d.ts +11 -0
- package/dist/services/metrics-server.js +105 -0
- package/dist/tools/actors.d.ts +163 -9
- package/dist/tools/actors.js +356 -311
- package/dist/tools/animation.d.ts +135 -4
- package/dist/tools/animation.js +510 -411
- package/dist/tools/assets.d.ts +75 -29
- package/dist/tools/assets.js +265 -284
- package/dist/tools/audio.d.ts +102 -42
- package/dist/tools/audio.js +272 -685
- package/dist/tools/base-tool.d.ts +17 -0
- package/dist/tools/base-tool.js +46 -0
- package/dist/tools/behavior-tree.d.ts +94 -0
- package/dist/tools/behavior-tree.js +39 -0
- package/dist/tools/blueprint.d.ts +208 -126
- package/dist/tools/blueprint.js +685 -832
- package/dist/tools/consolidated-tool-definitions.d.ts +5462 -1781
- package/dist/tools/consolidated-tool-definitions.js +829 -496
- package/dist/tools/consolidated-tool-handlers.d.ts +2 -1
- package/dist/tools/consolidated-tool-handlers.js +198 -1027
- package/dist/tools/debug.d.ts +143 -85
- package/dist/tools/debug.js +234 -180
- package/dist/tools/dynamic-handler-registry.d.ts +13 -0
- package/dist/tools/dynamic-handler-registry.js +23 -0
- package/dist/tools/editor.d.ts +30 -83
- package/dist/tools/editor.js +247 -244
- package/dist/tools/engine.d.ts +10 -4
- package/dist/tools/engine.js +13 -5
- package/dist/tools/environment.d.ts +30 -0
- package/dist/tools/environment.js +267 -0
- package/dist/tools/foliage.d.ts +65 -99
- package/dist/tools/foliage.js +221 -331
- package/dist/tools/handlers/actor-handlers.d.ts +3 -0
- package/dist/tools/handlers/actor-handlers.js +227 -0
- package/dist/tools/handlers/animation-handlers.d.ts +3 -0
- package/dist/tools/handlers/animation-handlers.js +185 -0
- package/dist/tools/handlers/argument-helper.d.ts +16 -0
- package/dist/tools/handlers/argument-helper.js +80 -0
- package/dist/tools/handlers/asset-handlers.d.ts +3 -0
- package/dist/tools/handlers/asset-handlers.js +496 -0
- package/dist/tools/handlers/audio-handlers.d.ts +3 -0
- package/dist/tools/handlers/audio-handlers.js +166 -0
- package/dist/tools/handlers/blueprint-handlers.d.ts +4 -0
- package/dist/tools/handlers/blueprint-handlers.js +358 -0
- package/dist/tools/handlers/common-handlers.d.ts +14 -0
- package/dist/tools/handlers/common-handlers.js +56 -0
- package/dist/tools/handlers/editor-handlers.d.ts +3 -0
- package/dist/tools/handlers/editor-handlers.js +119 -0
- package/dist/tools/handlers/effect-handlers.d.ts +3 -0
- package/dist/tools/handlers/effect-handlers.js +171 -0
- package/dist/tools/handlers/environment-handlers.d.ts +3 -0
- package/dist/tools/handlers/environment-handlers.js +170 -0
- package/dist/tools/handlers/graph-handlers.d.ts +3 -0
- package/dist/tools/handlers/graph-handlers.js +90 -0
- package/dist/tools/handlers/input-handlers.d.ts +3 -0
- package/dist/tools/handlers/input-handlers.js +21 -0
- package/dist/tools/handlers/inspect-handlers.d.ts +3 -0
- package/dist/tools/handlers/inspect-handlers.js +383 -0
- package/dist/tools/handlers/level-handlers.d.ts +3 -0
- package/dist/tools/handlers/level-handlers.js +237 -0
- package/dist/tools/handlers/lighting-handlers.d.ts +3 -0
- package/dist/tools/handlers/lighting-handlers.js +144 -0
- package/dist/tools/handlers/performance-handlers.d.ts +3 -0
- package/dist/tools/handlers/performance-handlers.js +130 -0
- package/dist/tools/handlers/pipeline-handlers.d.ts +3 -0
- package/dist/tools/handlers/pipeline-handlers.js +110 -0
- package/dist/tools/handlers/sequence-handlers.d.ts +3 -0
- package/dist/tools/handlers/sequence-handlers.js +376 -0
- package/dist/tools/handlers/system-handlers.d.ts +4 -0
- package/dist/tools/handlers/system-handlers.js +506 -0
- package/dist/tools/input.d.ts +19 -0
- package/dist/tools/input.js +89 -0
- package/dist/tools/introspection.d.ts +103 -40
- package/dist/tools/introspection.js +425 -568
- package/dist/tools/landscape.d.ts +54 -93
- package/dist/tools/landscape.js +284 -409
- package/dist/tools/level.d.ts +66 -27
- package/dist/tools/level.js +647 -675
- package/dist/tools/lighting.d.ts +77 -38
- package/dist/tools/lighting.js +445 -943
- package/dist/tools/logs.d.ts +3 -3
- package/dist/tools/logs.js +5 -57
- package/dist/tools/materials.d.ts +91 -24
- package/dist/tools/materials.js +194 -118
- package/dist/tools/niagara.d.ts +149 -39
- package/dist/tools/niagara.js +267 -182
- package/dist/tools/performance.d.ts +27 -13
- package/dist/tools/performance.js +203 -122
- package/dist/tools/physics.d.ts +32 -77
- package/dist/tools/physics.js +175 -582
- package/dist/tools/property-dictionary.d.ts +13 -0
- package/dist/tools/property-dictionary.js +82 -0
- package/dist/tools/sequence.d.ts +85 -60
- package/dist/tools/sequence.js +208 -747
- package/dist/tools/tool-definition-utils.d.ts +59 -0
- package/dist/tools/tool-definition-utils.js +35 -0
- package/dist/tools/ui.d.ts +64 -34
- package/dist/tools/ui.js +134 -214
- package/dist/types/automation-responses.d.ts +115 -0
- package/dist/types/automation-responses.js +2 -0
- package/dist/types/env.d.ts +0 -3
- package/dist/types/env.js +0 -7
- package/dist/types/responses.d.ts +249 -0
- package/dist/types/responses.js +2 -0
- package/dist/types/tool-interfaces.d.ts +898 -0
- package/dist/types/tool-interfaces.js +2 -0
- package/dist/types/tool-types.d.ts +183 -19
- package/dist/types/tool-types.js +0 -4
- package/dist/unreal-bridge.d.ts +24 -131
- package/dist/unreal-bridge.js +364 -1506
- package/dist/utils/command-validator.d.ts +9 -0
- package/dist/utils/command-validator.js +68 -0
- package/dist/utils/elicitation.d.ts +1 -1
- package/dist/utils/elicitation.js +12 -15
- package/dist/utils/error-handler.d.ts +2 -51
- package/dist/utils/error-handler.js +11 -87
- package/dist/utils/ini-reader.d.ts +3 -0
- package/dist/utils/ini-reader.js +69 -0
- package/dist/utils/logger.js +9 -6
- package/dist/utils/normalize.d.ts +3 -0
- package/dist/utils/normalize.js +56 -0
- package/dist/utils/path-security.d.ts +2 -0
- package/dist/utils/path-security.js +24 -0
- package/dist/utils/response-factory.d.ts +7 -0
- package/dist/utils/response-factory.js +27 -0
- package/dist/utils/response-validator.d.ts +3 -24
- package/dist/utils/response-validator.js +130 -81
- package/dist/utils/result-helpers.d.ts +4 -5
- package/dist/utils/result-helpers.js +15 -16
- package/dist/utils/safe-json.js +5 -11
- package/dist/utils/unreal-command-queue.d.ts +24 -0
- package/dist/utils/unreal-command-queue.js +120 -0
- package/dist/utils/validation.d.ts +0 -40
- package/dist/utils/validation.js +1 -78
- package/dist/wasm/index.d.ts +70 -0
- package/dist/wasm/index.js +535 -0
- package/docs/GraphQL-API.md +888 -0
- package/docs/Migration-Guide-v0.5.0.md +684 -0
- package/docs/Roadmap.md +53 -0
- package/docs/WebAssembly-Integration.md +628 -0
- package/docs/editor-plugin-extension.md +370 -0
- package/docs/handler-mapping.md +242 -0
- package/docs/native-automation-progress.md +128 -0
- package/docs/testing-guide.md +423 -0
- package/mcp-config-example.json +6 -6
- package/package.json +67 -28
- package/plugins/McpAutomationBridge/Config/FilterPlugin.ini +8 -0
- package/plugins/McpAutomationBridge/McpAutomationBridge.uplugin +64 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/McpAutomationBridge.Build.cs +189 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.cpp +22 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.h +30 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeHelpers.h +1983 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeModule.cpp +72 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSettings.cpp +46 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +581 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +2394 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetQueryHandlers.cpp +300 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetWorkflowHandlers.cpp +2807 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AudioHandlers.cpp +1087 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BehaviorTreeHandlers.cpp +488 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.cpp +643 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.h +31 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +1184 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +5652 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers_List.cpp +152 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ControlHandlers.cpp +2614 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_DebugHandlers.cpp +42 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EditorFunctionHandlers.cpp +1237 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +1701 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +2145 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_FoliageHandlers.cpp +954 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InputHandlers.cpp +209 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InsightsHandlers.cpp +41 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LandscapeHandlers.cpp +1164 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LevelHandlers.cpp +762 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +634 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LogHandlers.cpp +136 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_MaterialGraphHandlers.cpp +494 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraGraphHandlers.cpp +278 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraHandlers.cpp +625 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PerformanceHandlers.cpp +401 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PipelineHandlers.cpp +67 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +735 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PropertyHandlers.cpp +2634 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_RenderHandlers.cpp +189 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.cpp +917 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.h +39 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +2670 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequencerHandlers.cpp +519 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_TestHandlers.cpp +38 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_UiHandlers.cpp +668 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_WorldPartitionHandlers.cpp +346 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +1330 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.h +149 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +783 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSettings.h +115 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSubsystem.h +796 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpConnectionManager.h +117 -0
- package/scripts/check-unreal-connection.mjs +19 -0
- package/scripts/clean-tmp.js +23 -0
- package/scripts/patch-wasm.js +26 -0
- package/scripts/run-all-tests.mjs +136 -0
- package/scripts/smoke-test.ts +94 -0
- package/scripts/sync-mcp-plugin.js +143 -0
- package/scripts/test-no-plugin-alternates.mjs +113 -0
- package/scripts/validate-server.js +46 -0
- package/scripts/verify-automation-bridge.js +200 -0
- package/server.json +58 -21
- package/src/automation/bridge.ts +558 -0
- package/src/automation/connection-manager.ts +130 -0
- package/src/automation/handshake.ts +99 -0
- package/src/automation/index.ts +2 -0
- package/src/automation/message-handler.ts +167 -0
- package/src/automation/request-tracker.ts +123 -0
- package/src/automation/types.ts +107 -0
- package/src/cli.ts +33 -6
- package/src/config.ts +73 -0
- package/src/constants.ts +19 -0
- package/src/graphql/loaders.ts +244 -0
- package/src/graphql/resolvers.ts +1008 -0
- package/src/graphql/schema.ts +452 -0
- package/src/graphql/server.ts +156 -0
- package/src/graphql/types.ts +10 -0
- package/src/handlers/resource-handlers.ts +186 -0
- package/src/index.ts +166 -664
- package/src/resources/actors.ts +58 -76
- package/src/resources/assets.ts +148 -134
- package/src/resources/levels.ts +28 -33
- package/src/server/resource-registry.ts +47 -0
- package/src/server/tool-registry.ts +354 -0
- package/src/server-setup.ts +114 -0
- package/src/services/health-monitor.ts +132 -0
- package/src/services/metrics-server.ts +142 -0
- package/src/tools/actors.ts +426 -323
- package/src/tools/animation.ts +672 -461
- package/src/tools/assets.ts +364 -289
- package/src/tools/audio.ts +323 -766
- package/src/tools/base-tool.ts +52 -0
- package/src/tools/behavior-tree.ts +45 -0
- package/src/tools/blueprint.ts +792 -970
- package/src/tools/consolidated-tool-definitions.ts +993 -515
- package/src/tools/consolidated-tool-handlers.ts +258 -1146
- package/src/tools/debug.ts +292 -187
- package/src/tools/dynamic-handler-registry.ts +33 -0
- package/src/tools/editor.ts +329 -253
- package/src/tools/engine.ts +14 -3
- package/src/tools/environment.ts +281 -0
- package/src/tools/foliage.ts +330 -392
- package/src/tools/handlers/actor-handlers.ts +265 -0
- package/src/tools/handlers/animation-handlers.ts +237 -0
- package/src/tools/handlers/argument-helper.ts +142 -0
- package/src/tools/handlers/asset-handlers.ts +532 -0
- package/src/tools/handlers/audio-handlers.ts +194 -0
- package/src/tools/handlers/blueprint-handlers.ts +380 -0
- package/src/tools/handlers/common-handlers.ts +87 -0
- package/src/tools/handlers/editor-handlers.ts +123 -0
- package/src/tools/handlers/effect-handlers.ts +220 -0
- package/src/tools/handlers/environment-handlers.ts +183 -0
- package/src/tools/handlers/graph-handlers.ts +116 -0
- package/src/tools/handlers/input-handlers.ts +28 -0
- package/src/tools/handlers/inspect-handlers.ts +450 -0
- package/src/tools/handlers/level-handlers.ts +252 -0
- package/src/tools/handlers/lighting-handlers.ts +147 -0
- package/src/tools/handlers/performance-handlers.ts +132 -0
- package/src/tools/handlers/pipeline-handlers.ts +127 -0
- package/src/tools/handlers/sequence-handlers.ts +415 -0
- package/src/tools/handlers/system-handlers.ts +564 -0
- package/src/tools/input.ts +101 -0
- package/src/tools/introspection.ts +493 -584
- package/src/tools/landscape.ts +418 -507
- package/src/tools/level.ts +786 -708
- package/src/tools/lighting.ts +588 -984
- package/src/tools/logs.ts +9 -57
- package/src/tools/materials.ts +237 -121
- package/src/tools/niagara.ts +335 -168
- package/src/tools/performance.ts +320 -169
- package/src/tools/physics.ts +274 -613
- package/src/tools/property-dictionary.ts +98 -0
- package/src/tools/sequence.ts +276 -820
- package/src/tools/tool-definition-utils.ts +35 -0
- package/src/tools/ui.ts +205 -283
- package/src/types/automation-responses.ts +119 -0
- package/src/types/env.ts +0 -10
- package/src/types/responses.ts +355 -0
- package/src/types/tool-interfaces.ts +250 -0
- package/src/types/tool-types.ts +243 -21
- package/src/unreal-bridge.ts +460 -1550
- package/src/utils/command-validator.ts +76 -0
- package/src/utils/elicitation.ts +10 -7
- package/src/utils/error-handler.ts +14 -90
- package/src/utils/ini-reader.ts +86 -0
- package/src/utils/logger.ts +8 -3
- package/src/utils/normalize.test.ts +162 -0
- package/src/utils/normalize.ts +60 -0
- package/src/utils/path-security.ts +43 -0
- package/src/utils/response-factory.ts +44 -0
- package/src/utils/response-validator.ts +176 -56
- package/src/utils/result-helpers.ts +21 -19
- package/src/utils/safe-json.test.ts +90 -0
- package/src/utils/safe-json.ts +14 -11
- package/src/utils/unreal-command-queue.ts +152 -0
- package/src/utils/validation.test.ts +184 -0
- package/src/utils/validation.ts +4 -1
- package/src/wasm/index.ts +838 -0
- package/test-server.mjs +100 -0
- package/tests/run-unreal-tool-tests.mjs +242 -14
- package/tests/test-animation.mjs +369 -0
- package/tests/test-asset-advanced.mjs +82 -0
- package/tests/test-asset-errors.mjs +35 -0
- package/tests/test-asset-graph.mjs +311 -0
- package/tests/test-audio.mjs +417 -0
- package/tests/test-automation-timeouts.mjs +98 -0
- package/tests/test-behavior-tree.mjs +444 -0
- package/tests/test-blueprint-graph.mjs +410 -0
- package/tests/test-blueprint.mjs +577 -0
- package/tests/test-client-mode.mjs +86 -0
- package/tests/test-console-command.mjs +56 -0
- package/tests/test-control-actor.mjs +425 -0
- package/tests/test-control-editor.mjs +112 -0
- package/tests/test-graphql.mjs +372 -0
- package/tests/test-input.mjs +349 -0
- package/tests/test-inspect.mjs +302 -0
- package/tests/test-landscape.mjs +316 -0
- package/tests/test-lighting.mjs +428 -0
- package/tests/test-manage-asset.mjs +438 -0
- package/tests/test-manage-level.mjs +89 -0
- package/tests/test-materials.mjs +356 -0
- package/tests/test-niagara.mjs +185 -0
- package/tests/test-no-inline-python.mjs +122 -0
- package/tests/test-performance.mjs +539 -0
- package/tests/test-plugin-handshake.mjs +82 -0
- package/tests/test-runner.mjs +933 -0
- package/tests/test-sequence.mjs +104 -0
- package/tests/test-system.mjs +96 -0
- package/tests/test-wasm.mjs +283 -0
- package/tests/test-world-partition.mjs +215 -0
- package/tsconfig.json +3 -3
- package/vitest.config.ts +35 -0
- package/wasm/Cargo.lock +363 -0
- package/wasm/Cargo.toml +42 -0
- package/wasm/LICENSE +21 -0
- package/wasm/README.md +253 -0
- package/wasm/src/dependency_resolver.rs +377 -0
- package/wasm/src/lib.rs +153 -0
- package/wasm/src/property_parser.rs +271 -0
- package/wasm/src/transform_math.rs +396 -0
- package/wasm/tests/integration.rs +109 -0
- package/.github/workflows/smithery-build.yml +0 -29
- package/dist/prompts/index.d.ts +0 -21
- package/dist/prompts/index.js +0 -217
- package/dist/tools/build_environment_advanced.d.ts +0 -65
- package/dist/tools/build_environment_advanced.js +0 -633
- package/dist/tools/rc.d.ts +0 -110
- package/dist/tools/rc.js +0 -437
- package/dist/tools/visual.d.ts +0 -40
- package/dist/tools/visual.js +0 -282
- package/dist/utils/http.d.ts +0 -6
- package/dist/utils/http.js +0 -151
- package/dist/utils/python-output.d.ts +0 -18
- package/dist/utils/python-output.js +0 -290
- package/dist/utils/python.d.ts +0 -2
- package/dist/utils/python.js +0 -4
- package/dist/utils/stdio-redirect.d.ts +0 -2
- package/dist/utils/stdio-redirect.js +0 -20
- package/docs/unreal-tool-test-cases.md +0 -574
- package/smithery.yaml +0 -29
- package/src/prompts/index.ts +0 -249
- package/src/tools/build_environment_advanced.ts +0 -732
- package/src/tools/rc.ts +0 -515
- package/src/tools/visual.ts +0 -281
- package/src/utils/http.ts +0 -187
- package/src/utils/python-output.ts +0 -351
- package/src/utils/python.ts +0 -3
- package/src/utils/stdio-redirect.ts +0 -18
package/src/tools/editor.ts
CHANGED
|
@@ -1,29 +1,33 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BaseTool } from './base-tool.js';
|
|
2
|
+
import { IEditorTools, StandardActionResponse } from '../types/tool-interfaces.js';
|
|
2
3
|
import { toVec3Object, toRotObject } from '../utils/normalize.js';
|
|
3
|
-
import {
|
|
4
|
+
import { DEFAULT_SCREENSHOT_RESOLUTION } from '../constants.js';
|
|
5
|
+
import { EditorResponse } from '../types/automation-responses.js';
|
|
6
|
+
import { wasmIntegration } from '../wasm/index.js';
|
|
7
|
+
|
|
8
|
+
export class EditorTools extends BaseTool implements IEditorTools {
|
|
9
|
+
private cameraBookmarks = new Map<string, { location: [number, number, number]; rotation: [number, number, number]; savedAt: number }>();
|
|
10
|
+
private editorPreferences = new Map<string, Record<string, unknown>>();
|
|
11
|
+
private activeRecording?: { name?: string; options?: Record<string, unknown>; startedAt: number };
|
|
4
12
|
|
|
5
|
-
export class EditorTools {
|
|
6
|
-
constructor(private bridge: UnrealBridge) {}
|
|
7
|
-
|
|
8
13
|
async isInPIE(): Promise<boolean> {
|
|
9
14
|
try {
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return out.includes('PIE_STATE:True');
|
|
15
|
+
const response = await this.sendAutomationRequest<EditorResponse>(
|
|
16
|
+
'check_pie_state',
|
|
17
|
+
{},
|
|
18
|
+
{ timeoutMs: 5000 }
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
if (response && response.success !== false) {
|
|
22
|
+
return response.isInPIE === true || (response.result as any)?.isInPIE === true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return false;
|
|
22
26
|
} catch {
|
|
23
27
|
return false;
|
|
24
28
|
}
|
|
25
29
|
}
|
|
26
|
-
|
|
30
|
+
|
|
27
31
|
async ensureNotInPIE(): Promise<void> {
|
|
28
32
|
if (await this.isInPIE()) {
|
|
29
33
|
await this.stopPlayInEditor();
|
|
@@ -32,108 +36,56 @@ else:
|
|
|
32
36
|
}
|
|
33
37
|
}
|
|
34
38
|
|
|
35
|
-
async playInEditor() {
|
|
39
|
+
async playInEditor(timeoutMs: number = 30000): Promise<StandardActionResponse> {
|
|
36
40
|
try {
|
|
37
|
-
// Set tick rate to match UI play (60 fps for game mode)
|
|
38
|
-
await this.bridge.executeConsoleCommand('t.MaxFPS 60');
|
|
39
|
-
|
|
40
|
-
// Try Python first using the modern LevelEditorSubsystem
|
|
41
41
|
try {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if
|
|
48
|
-
|
|
49
|
-
was_playing = les.is_in_play_in_editor()
|
|
50
|
-
|
|
51
|
-
# Request PIE in the current viewport
|
|
52
|
-
les.editor_play_simulate()
|
|
53
|
-
|
|
54
|
-
# Wait for PIE to start with multiple checks
|
|
55
|
-
max_attempts = 10
|
|
56
|
-
for i in range(max_attempts):
|
|
57
|
-
time.sleep(0.2) # Wait 200ms between checks
|
|
58
|
-
is_playing = les.is_in_play_in_editor()
|
|
59
|
-
if is_playing and not was_playing:
|
|
60
|
-
# PIE has started
|
|
61
|
-
print('RESULT:' + json.dumps({'success': True, 'method': 'LevelEditorSubsystem'}))
|
|
62
|
-
break
|
|
63
|
-
else:
|
|
64
|
-
# If we've waited 2 seconds total and PIE hasn't started,
|
|
65
|
-
# but the command was sent, assume it will start
|
|
66
|
-
print('RESULT:' + json.dumps({'success': True, 'method': 'LevelEditorSubsystem'}))
|
|
67
|
-
else:
|
|
68
|
-
# If subsystem not available, report error
|
|
69
|
-
print('RESULT:' + json.dumps({'success': False, 'error': 'LevelEditorSubsystem not available'}))
|
|
70
|
-
`.trim();
|
|
71
|
-
|
|
72
|
-
const resp: any = await this.bridge.executePython(pythonCmd);
|
|
73
|
-
const interpreted = interpretStandardResult(resp, {
|
|
74
|
-
successMessage: 'PIE started',
|
|
75
|
-
failureMessage: 'Failed to start PIE'
|
|
76
|
-
});
|
|
77
|
-
if (interpreted.success) {
|
|
78
|
-
const method = coerceString(interpreted.payload.method) ?? 'LevelEditorSubsystem';
|
|
79
|
-
return { success: true, message: `PIE started (via ${method})` };
|
|
42
|
+
const response = await this.sendAutomationRequest<EditorResponse>(
|
|
43
|
+
'control_editor',
|
|
44
|
+
{ action: 'play' },
|
|
45
|
+
{ timeoutMs }
|
|
46
|
+
);
|
|
47
|
+
if (response && response.success === true) {
|
|
48
|
+
return { success: true, message: response.message || 'PIE started' };
|
|
80
49
|
}
|
|
81
|
-
|
|
82
|
-
} catch (err) {
|
|
83
|
-
//
|
|
84
|
-
|
|
50
|
+
return { success: false, error: response?.error || response?.message || 'Failed to start PIE' };
|
|
51
|
+
} catch (err: any) {
|
|
52
|
+
// If it's a timeout, return error instead of falling back
|
|
53
|
+
if (err.message && /time.*out/i.test(err.message)) {
|
|
54
|
+
return { success: false, error: `Timeout waiting for PIE to start: ${err.message}` };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Fallback to console commands if automation bridge is unavailable or fails (non-timeout)
|
|
58
|
+
await this.bridge.executeConsoleCommand('t.MaxFPS 60');
|
|
59
|
+
await this.bridge.executeConsoleCommand('PlayInViewport');
|
|
60
|
+
return { success: true, message: 'PIE start command sent' };
|
|
85
61
|
}
|
|
86
|
-
// Fallback to console command which is more reliable
|
|
87
|
-
await this.bridge.executeConsoleCommand('PlayInViewport');
|
|
88
|
-
|
|
89
|
-
// Wait a moment and verify PIE started
|
|
90
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
91
|
-
|
|
92
|
-
// Check if PIE is now active
|
|
93
|
-
const isPlaying = await this.isInPIE();
|
|
94
|
-
|
|
95
|
-
return {
|
|
96
|
-
success: true,
|
|
97
|
-
message: isPlaying ? 'PIE started successfully' : 'PIE start command sent (may take a moment)'
|
|
98
|
-
};
|
|
99
62
|
} catch (err) {
|
|
100
63
|
return { success: false, error: `Failed to start PIE: ${err}` };
|
|
101
64
|
}
|
|
102
65
|
}
|
|
103
66
|
|
|
104
|
-
async stopPlayInEditor() {
|
|
67
|
+
async stopPlayInEditor(): Promise<StandardActionResponse> {
|
|
105
68
|
try {
|
|
106
|
-
// Try Python first using the modern LevelEditorSubsystem
|
|
107
69
|
try {
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
les.editor_request_end_play() # Modern API method
|
|
114
|
-
print('RESULT:' + json.dumps({'success': True, 'method': 'LevelEditorSubsystem'}))
|
|
115
|
-
else:
|
|
116
|
-
# If subsystem not available, report error
|
|
117
|
-
print('RESULT:' + json.dumps({'success': False, 'error': 'LevelEditorSubsystem not available'}))
|
|
118
|
-
`.trim();
|
|
119
|
-
const resp: any = await this.bridge.executePython(pythonCmd);
|
|
120
|
-
const interpreted = interpretStandardResult(resp, {
|
|
121
|
-
successMessage: 'PIE stopped successfully',
|
|
122
|
-
failureMessage: 'Failed to stop PIE'
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
if (interpreted.success) {
|
|
126
|
-
const method = coerceString(interpreted.payload.method) ?? 'LevelEditorSubsystem';
|
|
127
|
-
return { success: true, message: `PIE stopped via ${method}` };
|
|
128
|
-
}
|
|
70
|
+
const response = await this.sendAutomationRequest<EditorResponse>(
|
|
71
|
+
'control_editor',
|
|
72
|
+
{ action: 'stop' },
|
|
73
|
+
{ timeoutMs: 30000 }
|
|
74
|
+
);
|
|
129
75
|
|
|
130
|
-
if (
|
|
131
|
-
return {
|
|
76
|
+
if (response.success !== false) {
|
|
77
|
+
return {
|
|
78
|
+
success: true,
|
|
79
|
+
message: response.message || 'PIE stopped successfully'
|
|
80
|
+
};
|
|
132
81
|
}
|
|
133
82
|
|
|
134
|
-
return {
|
|
135
|
-
|
|
136
|
-
|
|
83
|
+
return {
|
|
84
|
+
success: false,
|
|
85
|
+
error: response.error || response.message || 'Failed to stop PIE'
|
|
86
|
+
};
|
|
87
|
+
} catch (_pluginErr) {
|
|
88
|
+
// Fallback to console command if plugin fails
|
|
137
89
|
await this.bridge.executeConsoleCommand('stop');
|
|
138
90
|
return { success: true, message: 'PIE stopped via console command' };
|
|
139
91
|
}
|
|
@@ -141,71 +93,60 @@ else:
|
|
|
141
93
|
return { success: false, error: `Failed to stop PIE: ${err}` };
|
|
142
94
|
}
|
|
143
95
|
}
|
|
144
|
-
|
|
145
|
-
async pausePlayInEditor() {
|
|
96
|
+
|
|
97
|
+
async pausePlayInEditor(): Promise<StandardActionResponse> {
|
|
146
98
|
try {
|
|
147
99
|
// Pause/Resume PIE
|
|
148
|
-
await this.bridge.
|
|
149
|
-
objectPath: '/Script/Engine.Default__KismetSystemLibrary',
|
|
150
|
-
functionName: 'ExecuteConsoleCommand',
|
|
151
|
-
parameters: {
|
|
152
|
-
WorldContextObject: null,
|
|
153
|
-
Command: 'pause',
|
|
154
|
-
SpecificPlayer: null
|
|
155
|
-
},
|
|
156
|
-
generateTransaction: false
|
|
157
|
-
});
|
|
100
|
+
await this.bridge.executeConsoleCommand('pause');
|
|
158
101
|
return { success: true, message: 'PIE paused/resumed' };
|
|
159
102
|
} catch (err) {
|
|
160
103
|
return { success: false, error: `Failed to pause PIE: ${err}` };
|
|
161
104
|
}
|
|
162
105
|
}
|
|
163
|
-
|
|
106
|
+
|
|
164
107
|
// Alias for consistency with naming convention
|
|
165
|
-
async pauseInEditor() {
|
|
108
|
+
async pauseInEditor(): Promise<StandardActionResponse> {
|
|
166
109
|
return this.pausePlayInEditor();
|
|
167
110
|
}
|
|
168
111
|
|
|
169
|
-
async buildLighting() {
|
|
112
|
+
async buildLighting(): Promise<StandardActionResponse> {
|
|
170
113
|
try {
|
|
171
|
-
// Use
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
import json
|
|
175
|
-
try:
|
|
176
|
-
# Use modern LevelEditorSubsystem API
|
|
177
|
-
les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
|
|
178
|
-
if les:
|
|
179
|
-
# build_light_maps(quality, with_reflection_captures)
|
|
180
|
-
les.build_light_maps(unreal.LightingBuildQuality.QUALITY_HIGH, True)
|
|
181
|
-
print('RESULT:' + json.dumps({'success': True, 'message': 'Lighting build started via LevelEditorSubsystem'}))
|
|
182
|
-
else:
|
|
183
|
-
# If subsystem not available, report error
|
|
184
|
-
print('RESULT:' + json.dumps({'success': False, 'error': 'LevelEditorSubsystem not available'}))
|
|
185
|
-
except Exception as e:
|
|
186
|
-
print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
|
|
187
|
-
`.trim();
|
|
188
|
-
const resp: any = await this.bridge.executePython(py);
|
|
189
|
-
const interpreted = interpretStandardResult(resp, {
|
|
190
|
-
successMessage: 'Lighting build started',
|
|
191
|
-
failureMessage: 'Failed to build lighting'
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
if (interpreted.success) {
|
|
195
|
-
return { success: true, message: interpreted.message };
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return {
|
|
199
|
-
success: false,
|
|
200
|
-
error: interpreted.error ?? 'Failed to build lighting',
|
|
201
|
-
details: bestEffortInterpretedText(interpreted)
|
|
202
|
-
};
|
|
114
|
+
// Use console command to build lighting
|
|
115
|
+
await this.bridge.executeConsoleCommand('BuildLighting');
|
|
116
|
+
return { success: true, message: 'Lighting build started' };
|
|
203
117
|
} catch (err) {
|
|
204
118
|
return { success: false, error: `Failed to build lighting: ${err}` };
|
|
205
119
|
}
|
|
206
120
|
}
|
|
207
121
|
|
|
208
|
-
async
|
|
122
|
+
private async getViewportCameraInfo(): Promise<{
|
|
123
|
+
success: boolean;
|
|
124
|
+
location?: [number, number, number];
|
|
125
|
+
rotation?: [number, number, number];
|
|
126
|
+
error?: string;
|
|
127
|
+
message?: string;
|
|
128
|
+
}> {
|
|
129
|
+
try {
|
|
130
|
+
const resp = await this.sendAutomationRequest<EditorResponse>(
|
|
131
|
+
'control_editor',
|
|
132
|
+
{ action: 'get_camera' },
|
|
133
|
+
{ timeoutMs: 3000 }
|
|
134
|
+
);
|
|
135
|
+
const result: any = resp?.result ?? resp;
|
|
136
|
+
const loc = result?.location ?? result?.camera?.location;
|
|
137
|
+
const rot = result?.rotation ?? result?.camera?.rotation;
|
|
138
|
+
const locArr: [number, number, number] | undefined = Array.isArray(loc) && loc.length === 3 ? [Number(loc[0]) || 0, Number(loc[1]) || 0, Number(loc[2]) || 0] : undefined;
|
|
139
|
+
const rotArr: [number, number, number] | undefined = Array.isArray(rot) && rot.length === 3 ? [Number(rot[0]) || 0, Number(rot[1]) || 0, Number(rot[2]) || 0] : undefined;
|
|
140
|
+
if (resp && resp.success !== false && locArr && rotArr) {
|
|
141
|
+
return { success: true, location: locArr, rotation: rotArr };
|
|
142
|
+
}
|
|
143
|
+
return { success: false, error: 'Failed to get camera information' };
|
|
144
|
+
} catch (err) {
|
|
145
|
+
return { success: false, error: `Camera query failed: ${err}` };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async setViewportCamera(location?: { x: number; y: number; z: number } | [number, number, number] | null | undefined, rotation?: { pitch: number; yaw: number; roll: number } | [number, number, number] | null | undefined): Promise<StandardActionResponse> {
|
|
209
150
|
// Special handling for when both location and rotation are missing/invalid
|
|
210
151
|
// Allow rotation-only updates
|
|
211
152
|
if (location === null) {
|
|
@@ -224,7 +165,7 @@ except Exception as e:
|
|
|
224
165
|
locObj.z = Math.max(-MAX_COORD, Math.min(MAX_COORD, locObj.z));
|
|
225
166
|
location = locObj as any;
|
|
226
167
|
}
|
|
227
|
-
|
|
168
|
+
|
|
228
169
|
// Validate rotation if provided
|
|
229
170
|
if (rotation !== undefined) {
|
|
230
171
|
if (rotation === null) {
|
|
@@ -240,119 +181,254 @@ except Exception as e:
|
|
|
240
181
|
rotObj.roll = ((rotObj.roll % 360) + 360) % 360;
|
|
241
182
|
rotation = rotObj as any;
|
|
242
183
|
}
|
|
243
|
-
|
|
184
|
+
|
|
185
|
+
// Use native control_editor.set_camera when available
|
|
244
186
|
try {
|
|
245
|
-
//
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
except Exception:
|
|
264
|
-
pass
|
|
265
|
-
`.trim();
|
|
266
|
-
await this.bridge.executePython(pythonCmd);
|
|
267
|
-
return {
|
|
268
|
-
success: true,
|
|
269
|
-
message: 'Viewport camera positioned via UnrealEditorSubsystem'
|
|
270
|
-
};
|
|
271
|
-
} catch {
|
|
272
|
-
// Fallback to camera speed control
|
|
273
|
-
await this.bridge.executeConsoleCommand('camspeed 4');
|
|
274
|
-
return {
|
|
275
|
-
success: true,
|
|
276
|
-
message: 'Camera speed set. Use debug camera (toggledebugcamera) for manual positioning'
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
} else if (rotation) {
|
|
280
|
-
// Only rotation provided, try to set just rotation
|
|
281
|
-
try {
|
|
282
|
-
const pythonCmd = `
|
|
283
|
-
import unreal
|
|
284
|
-
# Use UnrealEditorSubsystem to read/write viewport camera
|
|
285
|
-
ues = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
|
|
286
|
-
les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
|
|
287
|
-
rotation = unreal.Rotator(${(rotation as any).pitch}, ${(rotation as any).yaw}, ${(rotation as any).roll})
|
|
288
|
-
if ues:
|
|
289
|
-
info = ues.get_level_viewport_camera_info()
|
|
290
|
-
if info is not None:
|
|
291
|
-
current_location, _ = info
|
|
292
|
-
ues.set_level_viewport_camera_info(current_location, rotation)
|
|
293
|
-
try:
|
|
294
|
-
if les:
|
|
295
|
-
les.editor_invalidate_viewports()
|
|
296
|
-
except Exception:
|
|
297
|
-
pass
|
|
298
|
-
`.trim();
|
|
299
|
-
await this.bridge.executePython(pythonCmd);
|
|
300
|
-
return {
|
|
301
|
-
success: true,
|
|
302
|
-
message: 'Viewport camera rotation set via UnrealEditorSubsystem'
|
|
303
|
-
};
|
|
304
|
-
} catch {
|
|
305
|
-
// Fallback
|
|
306
|
-
return {
|
|
307
|
-
success: true,
|
|
308
|
-
message: 'Camera rotation update attempted'
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
} else {
|
|
312
|
-
// Neither location nor rotation provided - this is valid, just no-op
|
|
313
|
-
return {
|
|
314
|
-
success: true,
|
|
315
|
-
message: 'No camera changes requested'
|
|
316
|
-
};
|
|
187
|
+
// Use WASM composeTransform for camera transform calculation
|
|
188
|
+
const locArray: [number, number, number] = location
|
|
189
|
+
? [((location as any).x ?? (location as any)[0] ?? 0), ((location as any).y ?? (location as any)[1] ?? 0), ((location as any).z ?? (location as any)[2] ?? 0)]
|
|
190
|
+
: [0, 0, 0];
|
|
191
|
+
const rotArray: [number, number, number] = rotation
|
|
192
|
+
? [((rotation as any).pitch ?? (rotation as any)[0] ?? 0), ((rotation as any).yaw ?? (rotation as any)[1] ?? 0), ((rotation as any).roll ?? (rotation as any)[2] ?? 0)]
|
|
193
|
+
: [0, 0, 0];
|
|
194
|
+
// Compose transform to validate and process camera positioning via WASM
|
|
195
|
+
wasmIntegration.composeTransform(locArray, rotArray, [1, 1, 1]);
|
|
196
|
+
// console.error('[WASM] Using composeTransform for camera positioning');
|
|
197
|
+
|
|
198
|
+
const resp = await this.sendAutomationRequest<EditorResponse>('control_editor', {
|
|
199
|
+
action: 'set_camera',
|
|
200
|
+
location: location as any,
|
|
201
|
+
rotation: rotation as any
|
|
202
|
+
}, { timeoutMs: 10000 });
|
|
203
|
+
if (resp && resp.success === true) {
|
|
204
|
+
return { success: true, message: resp.message || 'Camera set', location, rotation };
|
|
317
205
|
}
|
|
206
|
+
return { success: false, error: resp?.error || resp?.message || 'Failed to set camera' };
|
|
318
207
|
} catch (err) {
|
|
319
|
-
return { success: false, error: `
|
|
208
|
+
return { success: false, error: `Camera control failed: ${err}` };
|
|
320
209
|
}
|
|
321
210
|
}
|
|
322
|
-
|
|
323
|
-
async setCameraSpeed(speed: number) {
|
|
211
|
+
|
|
212
|
+
async setCameraSpeed(speed: number): Promise<StandardActionResponse> {
|
|
324
213
|
try {
|
|
325
|
-
await this.bridge.
|
|
326
|
-
objectPath: '/Script/Engine.Default__KismetSystemLibrary',
|
|
327
|
-
functionName: 'ExecuteConsoleCommand',
|
|
328
|
-
parameters: {
|
|
329
|
-
WorldContextObject: null,
|
|
330
|
-
Command: `camspeed ${speed}`,
|
|
331
|
-
SpecificPlayer: null
|
|
332
|
-
},
|
|
333
|
-
generateTransaction: false
|
|
334
|
-
});
|
|
214
|
+
await this.bridge.executeConsoleCommand(`camspeed ${speed}`);
|
|
335
215
|
return { success: true, message: `Camera speed set to ${speed}` };
|
|
336
216
|
} catch (err) {
|
|
337
217
|
return { success: false, error: `Failed to set camera speed: ${err}` };
|
|
338
218
|
}
|
|
339
219
|
}
|
|
340
|
-
|
|
341
|
-
async setFOV(fov: number) {
|
|
220
|
+
|
|
221
|
+
async setFOV(fov: number): Promise<StandardActionResponse> {
|
|
342
222
|
try {
|
|
343
|
-
await this.bridge.
|
|
344
|
-
objectPath: '/Script/Engine.Default__KismetSystemLibrary',
|
|
345
|
-
functionName: 'ExecuteConsoleCommand',
|
|
346
|
-
parameters: {
|
|
347
|
-
WorldContextObject: null,
|
|
348
|
-
Command: `fov ${fov}`,
|
|
349
|
-
SpecificPlayer: null
|
|
350
|
-
},
|
|
351
|
-
generateTransaction: false
|
|
352
|
-
});
|
|
223
|
+
await this.bridge.executeConsoleCommand(`fov ${fov}`);
|
|
353
224
|
return { success: true, message: `FOV set to ${fov}` };
|
|
354
225
|
} catch (err) {
|
|
355
226
|
return { success: false, error: `Failed to set FOV: ${err}` };
|
|
356
227
|
}
|
|
357
228
|
}
|
|
229
|
+
|
|
230
|
+
async takeScreenshot(filename?: string, resolution?: string): Promise<StandardActionResponse> {
|
|
231
|
+
try {
|
|
232
|
+
if (resolution && !/^\d+x\d+$/.test(resolution)) {
|
|
233
|
+
return { success: false, error: 'Invalid resolution format. Use WxH (e.g. 1920x1080)' };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const sanitizedFilename = filename ? filename.replace(/[<>:*?"|]/g, '_') : `Screenshot_${Date.now()}`;
|
|
237
|
+
const resString = resolution || DEFAULT_SCREENSHOT_RESOLUTION;
|
|
238
|
+
const command = filename ? `highresshot ${resString} filename="${sanitizedFilename}"` : 'shot';
|
|
239
|
+
|
|
240
|
+
await this.bridge.executeConsoleCommand(command);
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
success: true,
|
|
244
|
+
message: `Screenshot captured: ${sanitizedFilename}`,
|
|
245
|
+
filename: sanitizedFilename,
|
|
246
|
+
command
|
|
247
|
+
};
|
|
248
|
+
} catch (err) {
|
|
249
|
+
return { success: false, error: `Failed to take screenshot: ${err}` };
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async resumePlayInEditor(): Promise<StandardActionResponse> {
|
|
254
|
+
try {
|
|
255
|
+
// Use console command to toggle pause (resumes if paused)
|
|
256
|
+
await this.bridge.executeConsoleCommand('pause');
|
|
257
|
+
return {
|
|
258
|
+
success: true,
|
|
259
|
+
message: 'PIE resume toggled via pause command'
|
|
260
|
+
};
|
|
261
|
+
} catch (err) {
|
|
262
|
+
return { success: false, error: `Failed to resume PIE: ${err}` };
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async stepPIEFrame(steps: number = 1): Promise<StandardActionResponse> {
|
|
267
|
+
const clampedSteps = Number.isFinite(steps) ? Math.max(1, Math.floor(steps)) : 1;
|
|
268
|
+
try {
|
|
269
|
+
// Use console command to step frames
|
|
270
|
+
for (let index = 0; index < clampedSteps; index += 1) {
|
|
271
|
+
await this.bridge.executeConsoleCommand('Step=1');
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
success: true,
|
|
275
|
+
message: `Advanced PIE by ${clampedSteps} frame(s)`,
|
|
276
|
+
steps: clampedSteps
|
|
277
|
+
};
|
|
278
|
+
} catch (err) {
|
|
279
|
+
return { success: false, error: `Failed to step PIE: ${err}` };
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async startRecording(options?: { filename?: string; frameRate?: number; durationSeconds?: number; metadata?: Record<string, unknown> }): Promise<StandardActionResponse> {
|
|
284
|
+
const startedAt = Date.now();
|
|
285
|
+
this.activeRecording = {
|
|
286
|
+
name: typeof options?.filename === 'string' ? options.filename.trim() : undefined,
|
|
287
|
+
options: options ? { ...options } : undefined,
|
|
288
|
+
startedAt
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
success: true as const,
|
|
293
|
+
message: 'Recording session started',
|
|
294
|
+
recording: {
|
|
295
|
+
name: this.activeRecording.name,
|
|
296
|
+
startedAt,
|
|
297
|
+
options: this.activeRecording.options
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async stopRecording(): Promise<StandardActionResponse> {
|
|
303
|
+
if (!this.activeRecording) {
|
|
304
|
+
return {
|
|
305
|
+
success: true as const,
|
|
306
|
+
message: 'No active recording session to stop'
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const stoppedRecording = this.activeRecording;
|
|
311
|
+
this.activeRecording = undefined;
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
success: true as const,
|
|
315
|
+
message: 'Recording session stopped',
|
|
316
|
+
recording: stoppedRecording
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async createCameraBookmark(name: string): Promise<StandardActionResponse> {
|
|
321
|
+
const trimmedName = name.trim();
|
|
322
|
+
if (!trimmedName) {
|
|
323
|
+
return { success: false as const, error: 'bookmarkName is required' };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const cameraInfo = await this.getViewportCameraInfo();
|
|
327
|
+
if (!cameraInfo.success || !cameraInfo.location || !cameraInfo.rotation) {
|
|
328
|
+
return {
|
|
329
|
+
success: false as const,
|
|
330
|
+
error: cameraInfo.error || 'Failed to capture viewport camera'
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
this.cameraBookmarks.set(trimmedName, {
|
|
335
|
+
location: cameraInfo.location,
|
|
336
|
+
rotation: cameraInfo.rotation,
|
|
337
|
+
savedAt: Date.now()
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
success: true as const,
|
|
342
|
+
message: `Bookmark '${trimmedName}' saved`,
|
|
343
|
+
bookmark: {
|
|
344
|
+
name: trimmedName,
|
|
345
|
+
location: cameraInfo.location,
|
|
346
|
+
rotation: cameraInfo.rotation
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async jumpToCameraBookmark(name: string): Promise<StandardActionResponse> {
|
|
352
|
+
const trimmedName = name.trim();
|
|
353
|
+
if (!trimmedName) {
|
|
354
|
+
return { success: false as const, error: 'bookmarkName is required' };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const bookmark = this.cameraBookmarks.get(trimmedName);
|
|
358
|
+
if (!bookmark) {
|
|
359
|
+
return {
|
|
360
|
+
success: false as const,
|
|
361
|
+
error: `Bookmark '${trimmedName}' not found`
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
await this.setViewportCamera(
|
|
366
|
+
{ x: bookmark.location[0], y: bookmark.location[1], z: bookmark.location[2] },
|
|
367
|
+
{ pitch: bookmark.rotation[0], yaw: bookmark.rotation[1], roll: bookmark.rotation[2] }
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
return {
|
|
371
|
+
success: true as const,
|
|
372
|
+
message: `Jumped to bookmark '${trimmedName}'`
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
async setEditorPreferences(category: string | undefined, preferences: Record<string, unknown>): Promise<StandardActionResponse> {
|
|
377
|
+
const resolvedCategory = typeof category === 'string' && category.trim().length > 0 ? category.trim() : 'General';
|
|
378
|
+
const existing = this.editorPreferences.get(resolvedCategory) ?? {};
|
|
379
|
+
this.editorPreferences.set(resolvedCategory, { ...existing, ...preferences });
|
|
380
|
+
|
|
381
|
+
return {
|
|
382
|
+
success: true as const,
|
|
383
|
+
message: `Preferences stored for ${resolvedCategory}`,
|
|
384
|
+
preferences: this.editorPreferences.get(resolvedCategory)
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
async setViewportResolution(width: number, height: number): Promise<StandardActionResponse> {
|
|
389
|
+
try {
|
|
390
|
+
// Clamp to reasonable limits
|
|
391
|
+
const clampedWidth = Math.max(320, Math.min(7680, width));
|
|
392
|
+
const clampedHeight = Math.max(240, Math.min(4320, height));
|
|
393
|
+
|
|
394
|
+
// Use console command directly instead of Python
|
|
395
|
+
const command = `r.SetRes ${clampedWidth}x${clampedHeight}`;
|
|
396
|
+
await this.bridge.executeConsoleCommand(command);
|
|
397
|
+
|
|
398
|
+
return {
|
|
399
|
+
success: true,
|
|
400
|
+
message: `Viewport resolution set to ${clampedWidth}x${clampedHeight}`,
|
|
401
|
+
width: clampedWidth,
|
|
402
|
+
height: clampedHeight
|
|
403
|
+
};
|
|
404
|
+
} catch (err) {
|
|
405
|
+
return { success: false, error: `Failed to set viewport resolution: ${err}` };
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
async executeConsoleCommand(command: string): Promise<StandardActionResponse> {
|
|
410
|
+
try {
|
|
411
|
+
// Sanitize and validate command
|
|
412
|
+
if (!command || typeof command !== 'string') {
|
|
413
|
+
return { success: false, error: 'Invalid command: must be a non-empty string' };
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (command.length > 1000) {
|
|
417
|
+
return {
|
|
418
|
+
success: false,
|
|
419
|
+
error: `Command too long (${command.length} chars). Maximum is 1000 characters.`
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const res = await this.bridge.executeConsoleCommand(command);
|
|
424
|
+
|
|
425
|
+
return {
|
|
426
|
+
success: true,
|
|
427
|
+
message: `Console command executed: ${command}`,
|
|
428
|
+
output: res
|
|
429
|
+
};
|
|
430
|
+
} catch (err) {
|
|
431
|
+
return { success: false, error: `Failed to execute console command: ${err}` };
|
|
432
|
+
}
|
|
433
|
+
}
|
|
358
434
|
}
|