unreal-engine-mcp-server 0.4.6 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +26 -0
- package/.env.production +38 -7
- package/.eslintrc.json +0 -54
- package/.eslintrc.override.json +8 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +94 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +56 -0
- package/.github/copilot-instructions.md +478 -45
- package/.github/dependabot.yml +19 -0
- package/.github/labeler.yml +24 -0
- package/.github/labels.yml +70 -0
- package/.github/pull_request_template.md +42 -0
- package/.github/release-drafter.yml +148 -0
- package/.github/workflows/auto-merge.yml +38 -0
- package/.github/workflows/ci.yml +38 -0
- package/.github/workflows/dependency-review.yml +17 -0
- package/.github/workflows/gemini-issue-triage.yml +172 -0
- package/.github/workflows/greetings.yml +23 -0
- package/.github/workflows/labeler.yml +16 -0
- package/.github/workflows/links.yml +80 -0
- package/.github/workflows/pr-size-labeler.yml +137 -0
- package/.github/workflows/publish-mcp.yml +12 -7
- package/.github/workflows/release-drafter.yml +23 -0
- package/.github/workflows/release.yml +112 -0
- package/.github/workflows/semantic-pull-request.yml +35 -0
- package/.github/workflows/smoke-test.yml +36 -0
- package/.github/workflows/stale.yml +28 -0
- package/CHANGELOG.md +269 -22
- package/CONTRIBUTING.md +140 -0
- package/README.md +166 -72
- package/claude_desktop_config_example.json +7 -6
- package/dist/automation/bridge.d.ts +50 -0
- package/dist/automation/bridge.js +452 -0
- package/dist/automation/connection-manager.d.ts +23 -0
- package/dist/automation/connection-manager.js +107 -0
- package/dist/automation/handshake.d.ts +11 -0
- package/dist/automation/handshake.js +89 -0
- package/dist/automation/index.d.ts +3 -0
- package/dist/automation/index.js +3 -0
- package/dist/automation/message-handler.d.ts +12 -0
- package/dist/automation/message-handler.js +149 -0
- package/dist/automation/request-tracker.d.ts +25 -0
- package/dist/automation/request-tracker.js +98 -0
- package/dist/automation/types.d.ts +130 -0
- package/dist/automation/types.js +2 -0
- package/dist/cli.js +32 -5
- package/dist/config.d.ts +27 -0
- package/dist/config.js +60 -0
- package/dist/constants.d.ts +12 -0
- package/dist/constants.js +12 -0
- package/dist/graphql/resolvers.d.ts +268 -0
- package/dist/graphql/resolvers.js +743 -0
- package/dist/graphql/schema.d.ts +5 -0
- package/dist/graphql/schema.js +437 -0
- package/dist/graphql/server.d.ts +26 -0
- package/dist/graphql/server.js +115 -0
- package/dist/graphql/types.d.ts +7 -0
- package/dist/graphql/types.js +2 -0
- package/dist/handlers/resource-handlers.d.ts +20 -0
- package/dist/handlers/resource-handlers.js +180 -0
- package/dist/index.d.ts +31 -18
- package/dist/index.js +119 -604
- package/dist/prompts/index.js +4 -4
- package/dist/resources/actors.d.ts +17 -12
- package/dist/resources/actors.js +56 -76
- package/dist/resources/assets.d.ts +6 -14
- package/dist/resources/assets.js +115 -147
- package/dist/resources/levels.d.ts +13 -13
- package/dist/resources/levels.js +25 -34
- package/dist/server/resource-registry.d.ts +20 -0
- package/dist/server/resource-registry.js +37 -0
- package/dist/server/tool-registry.d.ts +23 -0
- package/dist/server/tool-registry.js +322 -0
- package/dist/server-setup.d.ts +21 -0
- package/dist/server-setup.js +111 -0
- package/dist/services/health-monitor.d.ts +34 -0
- package/dist/services/health-monitor.js +105 -0
- package/dist/services/metrics-server.d.ts +11 -0
- package/dist/services/metrics-server.js +105 -0
- package/dist/tools/actors.d.ts +147 -9
- package/dist/tools/actors.js +350 -311
- package/dist/tools/animation.d.ts +135 -4
- package/dist/tools/animation.js +510 -411
- package/dist/tools/assets.d.ts +117 -19
- package/dist/tools/assets.js +259 -284
- package/dist/tools/audio.d.ts +102 -42
- package/dist/tools/audio.js +272 -685
- package/dist/tools/base-tool.d.ts +17 -0
- package/dist/tools/base-tool.js +46 -0
- package/dist/tools/behavior-tree.d.ts +94 -0
- package/dist/tools/behavior-tree.js +39 -0
- package/dist/tools/blueprint/helpers.d.ts +29 -0
- package/dist/tools/blueprint/helpers.js +182 -0
- package/dist/tools/blueprint.d.ts +228 -118
- package/dist/tools/blueprint.js +685 -832
- package/dist/tools/consolidated-tool-definitions.d.ts +5475 -1627
- package/dist/tools/consolidated-tool-definitions.js +829 -482
- package/dist/tools/consolidated-tool-handlers.d.ts +2 -1
- package/dist/tools/consolidated-tool-handlers.js +211 -1009
- package/dist/tools/debug.d.ts +143 -85
- package/dist/tools/debug.js +234 -180
- package/dist/tools/dynamic-handler-registry.d.ts +11 -0
- package/dist/tools/dynamic-handler-registry.js +101 -0
- package/dist/tools/editor.d.ts +139 -18
- package/dist/tools/editor.js +239 -244
- package/dist/tools/engine.d.ts +10 -4
- package/dist/tools/engine.js +13 -5
- package/dist/tools/environment.d.ts +36 -0
- package/dist/tools/environment.js +267 -0
- package/dist/tools/foliage.d.ts +105 -14
- package/dist/tools/foliage.js +219 -331
- package/dist/tools/handlers/actor-handlers.d.ts +3 -0
- package/dist/tools/handlers/actor-handlers.js +232 -0
- package/dist/tools/handlers/animation-handlers.d.ts +3 -0
- package/dist/tools/handlers/animation-handlers.js +185 -0
- package/dist/tools/handlers/argument-helper.d.ts +16 -0
- package/dist/tools/handlers/argument-helper.js +80 -0
- package/dist/tools/handlers/asset-handlers.d.ts +3 -0
- package/dist/tools/handlers/asset-handlers.js +496 -0
- package/dist/tools/handlers/audio-handlers.d.ts +3 -0
- package/dist/tools/handlers/audio-handlers.js +166 -0
- package/dist/tools/handlers/blueprint-handlers.d.ts +4 -0
- package/dist/tools/handlers/blueprint-handlers.js +358 -0
- package/dist/tools/handlers/common-handlers.d.ts +14 -0
- package/dist/tools/handlers/common-handlers.js +56 -0
- package/dist/tools/handlers/editor-handlers.d.ts +3 -0
- package/dist/tools/handlers/editor-handlers.js +119 -0
- package/dist/tools/handlers/effect-handlers.d.ts +3 -0
- package/dist/tools/handlers/effect-handlers.js +171 -0
- package/dist/tools/handlers/environment-handlers.d.ts +3 -0
- package/dist/tools/handlers/environment-handlers.js +170 -0
- package/dist/tools/handlers/graph-handlers.d.ts +3 -0
- package/dist/tools/handlers/graph-handlers.js +90 -0
- package/dist/tools/handlers/input-handlers.d.ts +3 -0
- package/dist/tools/handlers/input-handlers.js +21 -0
- package/dist/tools/handlers/inspect-handlers.d.ts +3 -0
- package/dist/tools/handlers/inspect-handlers.js +383 -0
- package/dist/tools/handlers/level-handlers.d.ts +3 -0
- package/dist/tools/handlers/level-handlers.js +237 -0
- package/dist/tools/handlers/lighting-handlers.d.ts +3 -0
- package/dist/tools/handlers/lighting-handlers.js +144 -0
- package/dist/tools/handlers/performance-handlers.d.ts +3 -0
- package/dist/tools/handlers/performance-handlers.js +130 -0
- package/dist/tools/handlers/pipeline-handlers.d.ts +3 -0
- package/dist/tools/handlers/pipeline-handlers.js +110 -0
- package/dist/tools/handlers/sequence-handlers.d.ts +3 -0
- package/dist/tools/handlers/sequence-handlers.js +376 -0
- package/dist/tools/handlers/system-handlers.d.ts +4 -0
- package/dist/tools/handlers/system-handlers.js +506 -0
- package/dist/tools/input.d.ts +19 -0
- package/dist/tools/input.js +89 -0
- package/dist/tools/introspection.d.ts +103 -40
- package/dist/tools/introspection.js +425 -568
- package/dist/tools/landscape.d.ts +97 -36
- package/dist/tools/landscape.js +280 -409
- package/dist/tools/level.d.ts +130 -10
- package/dist/tools/level.js +639 -675
- package/dist/tools/lighting.d.ts +77 -38
- package/dist/tools/lighting.js +441 -943
- package/dist/tools/logs.d.ts +45 -0
- package/dist/tools/logs.js +210 -0
- package/dist/tools/materials.d.ts +91 -24
- package/dist/tools/materials.js +190 -118
- package/dist/tools/niagara.d.ts +149 -39
- package/dist/tools/niagara.js +232 -182
- package/dist/tools/performance.d.ts +27 -12
- package/dist/tools/performance.js +204 -122
- package/dist/tools/physics.d.ts +32 -77
- package/dist/tools/physics.js +171 -582
- package/dist/tools/property-dictionary.d.ts +13 -0
- package/dist/tools/property-dictionary.js +82 -0
- package/dist/tools/sequence.d.ts +73 -48
- package/dist/tools/sequence.js +196 -748
- package/dist/tools/tool-definition-utils.d.ts +59 -0
- package/dist/tools/tool-definition-utils.js +35 -0
- package/dist/tools/ui.d.ts +66 -34
- package/dist/tools/ui.js +134 -214
- package/dist/types/env.d.ts +0 -3
- package/dist/types/env.js +0 -7
- package/dist/types/tool-interfaces.d.ts +898 -0
- package/dist/types/tool-interfaces.js +2 -0
- package/dist/types/tool-types.d.ts +195 -11
- package/dist/types/tool-types.js +0 -4
- package/dist/unreal-bridge.d.ts +24 -131
- package/dist/unreal-bridge.js +364 -1506
- package/dist/utils/command-validator.d.ts +9 -0
- package/dist/utils/command-validator.js +67 -0
- package/dist/utils/elicitation.d.ts +1 -1
- package/dist/utils/elicitation.js +12 -15
- package/dist/utils/error-handler.d.ts +2 -51
- package/dist/utils/error-handler.js +11 -87
- package/dist/utils/ini-reader.d.ts +3 -0
- package/dist/utils/ini-reader.js +69 -0
- package/dist/utils/logger.js +9 -6
- package/dist/utils/normalize.d.ts +3 -0
- package/dist/utils/normalize.js +56 -0
- package/dist/utils/response-factory.d.ts +7 -0
- package/dist/utils/response-factory.js +33 -0
- package/dist/utils/response-validator.d.ts +3 -24
- package/dist/utils/response-validator.js +130 -81
- package/dist/utils/result-helpers.d.ts +4 -5
- package/dist/utils/result-helpers.js +15 -16
- package/dist/utils/safe-json.js +5 -11
- package/dist/utils/unreal-command-queue.d.ts +24 -0
- package/dist/utils/unreal-command-queue.js +120 -0
- package/dist/utils/validation.d.ts +0 -40
- package/dist/utils/validation.js +1 -78
- package/dist/wasm/index.d.ts +70 -0
- package/dist/wasm/index.js +535 -0
- package/docs/GraphQL-API.md +888 -0
- package/docs/Migration-Guide-v0.5.0.md +692 -0
- package/docs/Roadmap.md +53 -0
- package/docs/WebAssembly-Integration.md +628 -0
- package/docs/editor-plugin-extension.md +370 -0
- package/docs/handler-mapping.md +242 -0
- package/docs/native-automation-progress.md +128 -0
- package/docs/testing-guide.md +423 -0
- package/mcp-config-example.json +6 -6
- package/package.json +60 -27
- package/plugins/McpAutomationBridge/Config/FilterPlugin.ini +8 -0
- package/plugins/McpAutomationBridge/McpAutomationBridge.uplugin +64 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/McpAutomationBridge.Build.cs +189 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.cpp +22 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.h +30 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeHelpers.h +1983 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeModule.cpp +72 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSettings.cpp +46 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +581 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +2394 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetQueryHandlers.cpp +300 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetWorkflowHandlers.cpp +2807 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AudioHandlers.cpp +1087 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BehaviorTreeHandlers.cpp +488 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.cpp +643 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.h +31 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +1184 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +5652 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers_List.cpp +152 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ControlHandlers.cpp +2614 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_DebugHandlers.cpp +42 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EditorFunctionHandlers.cpp +1237 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +1701 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +2145 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_FoliageHandlers.cpp +954 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InputHandlers.cpp +209 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InsightsHandlers.cpp +41 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LandscapeHandlers.cpp +1164 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LevelHandlers.cpp +762 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +634 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LogHandlers.cpp +136 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_MaterialGraphHandlers.cpp +494 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraGraphHandlers.cpp +278 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraHandlers.cpp +625 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PerformanceHandlers.cpp +401 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PipelineHandlers.cpp +67 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +735 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PropertyHandlers.cpp +2634 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_RenderHandlers.cpp +189 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.cpp +917 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.h +39 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +2670 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequencerHandlers.cpp +519 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_TestHandlers.cpp +38 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_UiHandlers.cpp +668 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_WorldPartitionHandlers.cpp +346 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +1330 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.h +149 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +783 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSettings.h +115 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSubsystem.h +796 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpConnectionManager.h +117 -0
- package/scripts/check-unreal-connection.mjs +19 -0
- package/scripts/clean-tmp.js +23 -0
- package/scripts/patch-wasm.js +26 -0
- package/scripts/run-all-tests.mjs +131 -0
- package/scripts/smoke-test.ts +94 -0
- package/scripts/sync-mcp-plugin.js +143 -0
- package/scripts/test-no-plugin-alternates.mjs +113 -0
- package/scripts/validate-server.js +46 -0
- package/scripts/verify-automation-bridge.js +200 -0
- package/server.json +57 -21
- package/src/automation/bridge.ts +558 -0
- package/src/automation/connection-manager.ts +130 -0
- package/src/automation/handshake.ts +99 -0
- package/src/automation/index.ts +2 -0
- package/src/automation/message-handler.ts +167 -0
- package/src/automation/request-tracker.ts +123 -0
- package/src/automation/types.ts +107 -0
- package/src/cli.ts +33 -6
- package/src/config.ts +73 -0
- package/src/constants.ts +12 -0
- package/src/graphql/resolvers.ts +1010 -0
- package/src/graphql/schema.ts +452 -0
- package/src/graphql/server.ts +154 -0
- package/src/graphql/types.ts +7 -0
- package/src/handlers/resource-handlers.ts +186 -0
- package/src/index.ts +152 -649
- package/src/prompts/index.ts +4 -4
- package/src/resources/actors.ts +58 -76
- package/src/resources/assets.ts +147 -134
- package/src/resources/levels.ts +28 -33
- package/src/server/resource-registry.ts +47 -0
- package/src/server/tool-registry.ts +354 -0
- package/src/server-setup.ts +148 -0
- package/src/services/health-monitor.ts +132 -0
- package/src/services/metrics-server.ts +142 -0
- package/src/tools/actors.ts +417 -322
- package/src/tools/animation.ts +671 -461
- package/src/tools/assets.ts +353 -289
- package/src/tools/audio.ts +323 -766
- package/src/tools/base-tool.ts +52 -0
- package/src/tools/behavior-tree.ts +45 -0
- package/src/tools/blueprint/helpers.ts +189 -0
- package/src/tools/blueprint.ts +787 -965
- package/src/tools/consolidated-tool-definitions.ts +993 -500
- package/src/tools/consolidated-tool-handlers.ts +272 -1122
- package/src/tools/debug.ts +292 -187
- package/src/tools/dynamic-handler-registry.ts +151 -0
- package/src/tools/editor.ts +309 -246
- package/src/tools/engine.ts +14 -3
- package/src/tools/environment.ts +287 -0
- package/src/tools/foliage.ts +314 -379
- package/src/tools/handlers/actor-handlers.ts +271 -0
- package/src/tools/handlers/animation-handlers.ts +237 -0
- package/src/tools/handlers/argument-helper.ts +142 -0
- package/src/tools/handlers/asset-handlers.ts +532 -0
- package/src/tools/handlers/audio-handlers.ts +194 -0
- package/src/tools/handlers/blueprint-handlers.ts +380 -0
- package/src/tools/handlers/common-handlers.ts +87 -0
- package/src/tools/handlers/editor-handlers.ts +123 -0
- package/src/tools/handlers/effect-handlers.ts +220 -0
- package/src/tools/handlers/environment-handlers.ts +183 -0
- package/src/tools/handlers/graph-handlers.ts +116 -0
- package/src/tools/handlers/input-handlers.ts +28 -0
- package/src/tools/handlers/inspect-handlers.ts +450 -0
- package/src/tools/handlers/level-handlers.ts +252 -0
- package/src/tools/handlers/lighting-handlers.ts +147 -0
- package/src/tools/handlers/performance-handlers.ts +132 -0
- package/src/tools/handlers/pipeline-handlers.ts +127 -0
- package/src/tools/handlers/sequence-handlers.ts +415 -0
- package/src/tools/handlers/system-handlers.ts +564 -0
- package/src/tools/input.ts +101 -0
- package/src/tools/introspection.ts +493 -584
- package/src/tools/landscape.ts +394 -489
- package/src/tools/level.ts +752 -694
- package/src/tools/lighting.ts +583 -984
- package/src/tools/logs.ts +219 -0
- package/src/tools/materials.ts +231 -121
- package/src/tools/niagara.ts +293 -168
- package/src/tools/performance.ts +320 -168
- package/src/tools/physics.ts +268 -613
- package/src/tools/property-dictionary.ts +98 -0
- package/src/tools/sequence.ts +255 -815
- package/src/tools/tool-definition-utils.ts +35 -0
- package/src/tools/ui.ts +207 -283
- package/src/types/env.ts +0 -10
- package/src/types/tool-interfaces.ts +250 -0
- package/src/types/tool-types.ts +250 -13
- package/src/unreal-bridge.ts +460 -1550
- package/src/utils/command-validator.ts +75 -0
- package/src/utils/elicitation.ts +10 -7
- package/src/utils/error-handler.ts +14 -90
- package/src/utils/ini-reader.ts +86 -0
- package/src/utils/logger.ts +8 -3
- package/src/utils/normalize.ts +60 -0
- package/src/utils/response-factory.ts +39 -0
- package/src/utils/response-validator.ts +176 -56
- package/src/utils/result-helpers.ts +21 -19
- package/src/utils/safe-json.ts +14 -11
- package/src/utils/unreal-command-queue.ts +152 -0
- package/src/utils/validation.ts +4 -1
- package/src/wasm/index.ts +838 -0
- package/test-server.mjs +100 -0
- package/tests/run-unreal-tool-tests.mjs +242 -14
- package/tests/test-animation.mjs +44 -0
- package/tests/test-asset-advanced.mjs +82 -0
- package/tests/test-asset-errors.mjs +35 -0
- package/tests/test-audio.mjs +219 -0
- package/tests/test-automation-timeouts.mjs +98 -0
- package/tests/test-behavior-tree.mjs +261 -0
- package/tests/test-blueprint-events.mjs +35 -0
- package/tests/test-blueprint-graph.mjs +79 -0
- package/tests/test-blueprint.mjs +577 -0
- package/tests/test-client-mode.mjs +86 -0
- package/tests/test-console-command.mjs +56 -0
- package/tests/test-control-actor.mjs +425 -0
- package/tests/test-control-editor.mjs +80 -0
- package/tests/test-extra-tools.mjs +38 -0
- package/tests/test-graphql.mjs +322 -0
- package/tests/test-inspect.mjs +72 -0
- package/tests/test-landscape.mjs +60 -0
- package/tests/test-manage-asset.mjs +438 -0
- package/tests/test-manage-level.mjs +70 -0
- package/tests/test-materials.mjs +356 -0
- package/tests/test-niagara.mjs +185 -0
- package/tests/test-no-inline-python.mjs +122 -0
- package/tests/test-plugin-handshake.mjs +82 -0
- package/tests/test-render.mjs +33 -0
- package/tests/test-runner.mjs +933 -0
- package/tests/test-search-assets.mjs +66 -0
- package/tests/test-sequence.mjs +68 -0
- package/tests/test-system.mjs +57 -0
- package/tests/test-wasm.mjs +193 -0
- package/tests/test-world-partition.mjs +215 -0
- package/tsconfig.json +3 -3
- package/wasm/Cargo.lock +363 -0
- package/wasm/Cargo.toml +42 -0
- package/wasm/LICENSE +21 -0
- package/wasm/README.md +253 -0
- package/wasm/src/dependency_resolver.rs +377 -0
- package/wasm/src/lib.rs +153 -0
- package/wasm/src/property_parser.rs +271 -0
- package/wasm/src/transform_math.rs +396 -0
- package/wasm/tests/integration.rs +109 -0
- package/.github/workflows/smithery-build.yml +0 -29
- package/dist/tools/build_environment_advanced.d.ts +0 -65
- package/dist/tools/build_environment_advanced.js +0 -633
- package/dist/tools/rc.d.ts +0 -110
- package/dist/tools/rc.js +0 -437
- package/dist/tools/visual.d.ts +0 -40
- package/dist/tools/visual.js +0 -282
- package/dist/utils/http.d.ts +0 -6
- package/dist/utils/http.js +0 -151
- package/dist/utils/python-output.d.ts +0 -18
- package/dist/utils/python-output.js +0 -290
- package/dist/utils/python.d.ts +0 -2
- package/dist/utils/python.js +0 -4
- package/dist/utils/stdio-redirect.d.ts +0 -2
- package/dist/utils/stdio-redirect.js +0 -20
- package/docs/unreal-tool-test-cases.md +0 -572
- package/smithery.yaml +0 -29
- package/src/tools/build_environment_advanced.ts +0 -732
- package/src/tools/rc.ts +0 -515
- package/src/tools/visual.ts +0 -281
- package/src/utils/http.ts +0 -187
- package/src/utils/python-output.ts +0 -351
- package/src/utils/python.ts +0 -3
- package/src/utils/stdio-redirect.ts +0 -18
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { UnrealBridge } from '../unreal-bridge.js';
|
|
2
|
+
import { loadEnv } from '../types/env.js';
|
|
3
|
+
import { Logger } from '../utils/logger.js';
|
|
4
|
+
import { promises as fs } from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
|
|
7
|
+
type ReadParams = {
|
|
8
|
+
filterCategory?: string[]
|
|
9
|
+
filterLevel?: 'Error' | 'Warning' | 'Log' | 'Verbose' | 'VeryVerbose' | 'All'
|
|
10
|
+
lines?: number
|
|
11
|
+
logPath?: string
|
|
12
|
+
includePrefixes?: string[]
|
|
13
|
+
excludeCategories?: string[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type Entry = {
|
|
17
|
+
timestamp?: string
|
|
18
|
+
category?: string
|
|
19
|
+
level?: string
|
|
20
|
+
message: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class LogTools {
|
|
24
|
+
private env = loadEnv();
|
|
25
|
+
private _log = new Logger('LogTools');
|
|
26
|
+
private cachedLogPath?: string;
|
|
27
|
+
constructor(private _bridge: UnrealBridge) { }
|
|
28
|
+
|
|
29
|
+
async readOutputLog(params: ReadParams) {
|
|
30
|
+
this._log.debug('Reading output log', { params, connected: this._bridge.isConnected });
|
|
31
|
+
const target = await this.resolveLogPath(params.logPath);
|
|
32
|
+
if (!target) {
|
|
33
|
+
return { success: false, error: 'Log file not found' };
|
|
34
|
+
}
|
|
35
|
+
const maxLines = typeof params.lines === 'number' && params.lines > 0 ? Math.min(params.lines, 2000) : 200;
|
|
36
|
+
let text = '';
|
|
37
|
+
try {
|
|
38
|
+
text = await this.tailFile(target, maxLines);
|
|
39
|
+
} catch (err: any) {
|
|
40
|
+
return { success: false, error: String(err?.message || err) };
|
|
41
|
+
}
|
|
42
|
+
const rawLines = text.split(/\r?\n/).filter(l => l.length > 0);
|
|
43
|
+
const parsed: Entry[] = rawLines.map(l => this.parseLine(l));
|
|
44
|
+
const mappedLevel = params.filterLevel || 'All';
|
|
45
|
+
const includeCats = Array.isArray(params.filterCategory) && params.filterCategory.length ? new Set(params.filterCategory) : undefined;
|
|
46
|
+
const includePrefixes = Array.isArray(params.includePrefixes) && params.includePrefixes.length ? params.includePrefixes : undefined;
|
|
47
|
+
const excludeCats = Array.isArray(params.excludeCategories) && params.excludeCategories.length ? new Set(params.excludeCategories) : undefined;
|
|
48
|
+
const filtered = parsed.filter(e => {
|
|
49
|
+
if (!e) return false;
|
|
50
|
+
if (mappedLevel && mappedLevel !== 'All') {
|
|
51
|
+
const lv = (e.level || 'Log');
|
|
52
|
+
if (lv === 'Display') {
|
|
53
|
+
if (mappedLevel !== 'Log') return false;
|
|
54
|
+
} else if (lv !== mappedLevel) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (includeCats && e.category && !includeCats.has(e.category)) return false;
|
|
59
|
+
if (includePrefixes && includePrefixes.length && e.category) {
|
|
60
|
+
if (!includePrefixes.some(p => (e.category ?? '').startsWith(p))) return false;
|
|
61
|
+
}
|
|
62
|
+
if (excludeCats && e.category && excludeCats.has(e.category)) return false;
|
|
63
|
+
return true;
|
|
64
|
+
});
|
|
65
|
+
const includeInternal = Boolean(
|
|
66
|
+
(includeCats && includeCats.has('LogPython')) ||
|
|
67
|
+
(includePrefixes && includePrefixes.some(p => 'LogPython'.startsWith(p)))
|
|
68
|
+
);
|
|
69
|
+
const sanitized = includeInternal ? filtered : filtered.filter(entry => !this.isInternalLogEntry(entry));
|
|
70
|
+
return { success: true, logPath: target.replace(/\\/g, '/'), entries: sanitized, filteredCount: sanitized.length };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private async resolveLogPath(override?: string): Promise<string | undefined> {
|
|
74
|
+
if (override && typeof override === 'string' && override.trim()) {
|
|
75
|
+
try {
|
|
76
|
+
const st = await fs.stat(override);
|
|
77
|
+
if (st.isFile()) {
|
|
78
|
+
return this.cacheLogPath(path.resolve(override));
|
|
79
|
+
}
|
|
80
|
+
} catch { }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (this.cachedLogPath && (await this.fileExists(this.cachedLogPath))) {
|
|
84
|
+
return this.cachedLogPath;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const envLog = await this.resolveFromProjectEnv();
|
|
88
|
+
if (envLog) {
|
|
89
|
+
return envLog;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// TODO: Restore Python path resolution once executePythonWithResult is available in UnrealBridge
|
|
93
|
+
// Currently relying on UE_PROJECT_PATH or cwd fallback
|
|
94
|
+
const fallback = await this.findLatestLogInDir(path.join(process.cwd(), 'Saved', 'Logs'));
|
|
95
|
+
if (fallback) {
|
|
96
|
+
return fallback;
|
|
97
|
+
}
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private async resolveFromProjectEnv(): Promise<string | undefined> {
|
|
102
|
+
const projectPath = this.env.UE_PROJECT_PATH;
|
|
103
|
+
if (projectPath && typeof projectPath === 'string' && projectPath.trim()) {
|
|
104
|
+
const projectDir = path.dirname(projectPath);
|
|
105
|
+
const logsDir = path.join(projectDir, 'Saved', 'Logs');
|
|
106
|
+
const envLog = await this.findLatestLogInDir(logsDir);
|
|
107
|
+
if (envLog) {
|
|
108
|
+
return envLog;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private async findLatestLogInDir(dir: string): Promise<string | undefined> {
|
|
115
|
+
if (!dir) return undefined;
|
|
116
|
+
try {
|
|
117
|
+
const entries = await fs.readdir(dir);
|
|
118
|
+
const candidates: { p: string; m: number }[] = [];
|
|
119
|
+
for (const name of entries) {
|
|
120
|
+
if (!name.toLowerCase().endsWith('.log')) continue;
|
|
121
|
+
const fp = path.join(dir, name);
|
|
122
|
+
try {
|
|
123
|
+
const st = await fs.stat(fp);
|
|
124
|
+
candidates.push({ p: fp, m: st.mtimeMs });
|
|
125
|
+
} catch { }
|
|
126
|
+
}
|
|
127
|
+
if (candidates.length) {
|
|
128
|
+
candidates.sort((a, b) => b.m - a.m);
|
|
129
|
+
return this.cacheLogPath(candidates[0].p);
|
|
130
|
+
}
|
|
131
|
+
} catch { }
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private async fileExists(filePath: string): Promise<boolean> {
|
|
136
|
+
try {
|
|
137
|
+
const st = await fs.stat(filePath);
|
|
138
|
+
return st.isFile();
|
|
139
|
+
} catch {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private cacheLogPath(p: string): string {
|
|
145
|
+
this.cachedLogPath = p;
|
|
146
|
+
return p;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private async tailFile(filePath: string, maxLines: number): Promise<string> {
|
|
150
|
+
const handle = await fs.open(filePath, 'r');
|
|
151
|
+
try {
|
|
152
|
+
const stat = await handle.stat();
|
|
153
|
+
const chunkSize = 128 * 1024;
|
|
154
|
+
let position = stat.size;
|
|
155
|
+
let remaining = '';
|
|
156
|
+
const lines: string[] = [];
|
|
157
|
+
while (position > 0 && lines.length < maxLines) {
|
|
158
|
+
const readSize = Math.min(chunkSize, position);
|
|
159
|
+
position -= readSize;
|
|
160
|
+
const buf = Buffer.alloc(readSize);
|
|
161
|
+
await handle.read(buf, 0, readSize, position);
|
|
162
|
+
remaining = buf.toString('utf8') + remaining;
|
|
163
|
+
const parts = remaining.split(/\r?\n/);
|
|
164
|
+
remaining = parts.shift() || '';
|
|
165
|
+
while (parts.length) {
|
|
166
|
+
const line = parts.pop() as string;
|
|
167
|
+
if (line === undefined) break;
|
|
168
|
+
if (line.length === 0) continue;
|
|
169
|
+
lines.unshift(line);
|
|
170
|
+
if (lines.length >= maxLines) break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (lines.length < maxLines && remaining) {
|
|
174
|
+
lines.unshift(remaining);
|
|
175
|
+
}
|
|
176
|
+
return lines.slice(0, maxLines).join('\n');
|
|
177
|
+
} finally {
|
|
178
|
+
try { await handle.close(); } catch { }
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private parseLine(line: string): Entry {
|
|
183
|
+
const m1 = line.match(/^\[?(\d{4}\.\d{2}\.\d{2}-\d{2}\.\d{2}\.\d{2}:\d+)\]?\s*\[(.*?)\]\s*(.*)$/);
|
|
184
|
+
if (m1) {
|
|
185
|
+
const rest = m1[3];
|
|
186
|
+
const m2 = rest.match(/^(\w+):\s*(Error|Warning|Display|Log|Verbose|VeryVerbose):\s*(.*)$/);
|
|
187
|
+
if (m2) {
|
|
188
|
+
return { timestamp: m1[1], category: m2[1], level: m2[2] === 'Display' ? 'Log' : m2[2], message: m2[3] };
|
|
189
|
+
}
|
|
190
|
+
const m3 = rest.match(/^(\w+):\s*(.*)$/);
|
|
191
|
+
if (m3) {
|
|
192
|
+
return { timestamp: m1[1], category: m3[1], level: 'Log', message: m3[2] };
|
|
193
|
+
}
|
|
194
|
+
return { timestamp: m1[1], message: rest };
|
|
195
|
+
}
|
|
196
|
+
const m = line.match(/^(\w+):\s*(Error|Warning|Display|Log|Verbose|VeryVerbose):\s*(.*)$/);
|
|
197
|
+
if (m) {
|
|
198
|
+
return { category: m[1], level: m[2] === 'Display' ? 'Log' : m[2], message: m[3] };
|
|
199
|
+
}
|
|
200
|
+
const mAlt = line.match(/^(\w+):\s*(.*)$/);
|
|
201
|
+
if (mAlt) {
|
|
202
|
+
return { category: mAlt[1], level: 'Log', message: mAlt[2] };
|
|
203
|
+
}
|
|
204
|
+
return { message: line };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private isInternalLogEntry(entry: Entry): boolean {
|
|
208
|
+
if (!entry) return false;
|
|
209
|
+
const category = entry.category?.toLowerCase() || '';
|
|
210
|
+
const message = entry.message?.trim() || '';
|
|
211
|
+
if (category === 'logpython' && message.startsWith('RESULT:')) {
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
if (!entry.category && message.startsWith('[') && message.includes('LogPython: RESULT:')) {
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
}
|
package/src/tools/materials.ts
CHANGED
|
@@ -1,33 +1,46 @@
|
|
|
1
1
|
import { UnrealBridge } from '../unreal-bridge.js';
|
|
2
|
-
import {
|
|
3
|
-
import { escapePythonString } from '../utils/python.js';
|
|
2
|
+
import { AutomationBridge } from '../automation/index.js';
|
|
4
3
|
|
|
5
4
|
export class MaterialTools {
|
|
6
|
-
constructor(private bridge: UnrealBridge) {}
|
|
5
|
+
constructor(private bridge: UnrealBridge, private automationBridge?: AutomationBridge) { }
|
|
6
|
+
|
|
7
|
+
setAutomationBridge(automationBridge?: AutomationBridge) {
|
|
8
|
+
this.automationBridge = automationBridge;
|
|
9
|
+
}
|
|
7
10
|
|
|
8
11
|
async createMaterial(name: string, path: string) {
|
|
9
12
|
try {
|
|
10
|
-
|
|
13
|
+
// Support unified assetPath in 'name' argument
|
|
14
|
+
let effectiveName = name;
|
|
15
|
+
let effectivePath = path;
|
|
16
|
+
|
|
17
|
+
if (name && name.includes('/')) {
|
|
18
|
+
const lastSlashIndex = name.lastIndexOf('/');
|
|
19
|
+
effectiveName = name.substring(lastSlashIndex + 1);
|
|
20
|
+
const derivedPath = name.substring(0, lastSlashIndex);
|
|
21
|
+
// If path was not provided or is just root, prefer the derived path
|
|
22
|
+
if (!path || path === '/Game' || path === '') {
|
|
23
|
+
effectivePath = derivedPath;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!effectiveName || effectiveName.trim() === '') {
|
|
11
28
|
return { success: false, error: 'Material name cannot be empty' };
|
|
12
29
|
}
|
|
13
30
|
|
|
14
|
-
if (
|
|
15
|
-
return { success: false, error: `Material name too long (${
|
|
31
|
+
if (effectiveName.length > 100) {
|
|
32
|
+
return { success: false, error: `Material name too long (${effectiveName.length} chars). Maximum is 100 characters.` };
|
|
16
33
|
}
|
|
17
34
|
|
|
18
35
|
const invalidChars = /[\s./<>|{}[\]()@#\\]/;
|
|
19
|
-
if (invalidChars.test(
|
|
20
|
-
const foundChars =
|
|
36
|
+
if (invalidChars.test(effectiveName)) {
|
|
37
|
+
const foundChars = effectiveName.match(invalidChars);
|
|
21
38
|
return { success: false, error: `Material name contains invalid characters: '${foundChars?.[0]}'. Avoid spaces, dots, slashes, backslashes, brackets, and special symbols.` };
|
|
22
39
|
}
|
|
23
40
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const trimmedPath = path.trim();
|
|
29
|
-
const effectivePath = trimmedPath.length === 0 ? '/Game' : trimmedPath;
|
|
30
|
-
const cleanPath = effectivePath.replace(/\/$/, '');
|
|
41
|
+
const trimmedPath = (effectivePath || '').trim();
|
|
42
|
+
const cleanPathStr = trimmedPath.length === 0 ? '/Game' : trimmedPath;
|
|
43
|
+
const cleanPath = cleanPathStr.replace(/\/$/, '');
|
|
31
44
|
|
|
32
45
|
if (!cleanPath.startsWith('/Game') && !cleanPath.startsWith('/Engine')) {
|
|
33
46
|
return { success: false, error: `Invalid path: must start with /Game or /Engine, got ${cleanPath}` };
|
|
@@ -40,109 +53,40 @@ export class MaterialTools {
|
|
|
40
53
|
return { success: false, error: errorMessage, message: errorMessage };
|
|
41
54
|
}
|
|
42
55
|
|
|
43
|
-
|
|
44
|
-
const payload = { name, cleanPath, materialPath };
|
|
45
|
-
const escapedName = escapePythonString(name);
|
|
46
|
-
const pythonCode = `
|
|
47
|
-
import unreal, json
|
|
48
|
-
|
|
49
|
-
payload = json.loads(r'''${JSON.stringify(payload)}''')
|
|
50
|
-
result = {
|
|
51
|
-
'success': False,
|
|
52
|
-
'message': '',
|
|
53
|
-
'error': '',
|
|
54
|
-
'warnings': [],
|
|
55
|
-
'details': [],
|
|
56
|
-
'name': payload.get('name') or "${escapedName}",
|
|
57
|
-
'path': payload.get('materialPath')
|
|
58
|
-
}
|
|
56
|
+
const materialPath = `${cleanPath}/${effectiveName}`;
|
|
59
57
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
result['message'] = f"Material already exists at {material_path}"
|
|
68
|
-
else:
|
|
69
|
-
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
|
|
70
|
-
factory = unreal.MaterialFactoryNew()
|
|
71
|
-
asset = asset_tools.create_asset(
|
|
72
|
-
asset_name=payload.get('name'),
|
|
73
|
-
package_path=clean_path,
|
|
74
|
-
asset_class=unreal.Material,
|
|
75
|
-
factory=factory
|
|
76
|
-
)
|
|
77
|
-
if asset:
|
|
78
|
-
unreal.EditorAssetLibrary.save_asset(material_path)
|
|
79
|
-
result['success'] = True
|
|
80
|
-
result['created'] = True
|
|
81
|
-
result['message'] = f"Material created at {material_path}"
|
|
82
|
-
else:
|
|
83
|
-
result['error'] = 'Failed to create material'
|
|
84
|
-
result['message'] = result['error']
|
|
85
|
-
except Exception as exc:
|
|
86
|
-
result['error'] = str(exc)
|
|
87
|
-
if not result['message']:
|
|
88
|
-
result['message'] = result['error']
|
|
89
|
-
|
|
90
|
-
print('RESULT:' + json.dumps(result))
|
|
91
|
-
`.trim();
|
|
92
|
-
|
|
93
|
-
const pyResult = await this.bridge.executePython(pythonCode);
|
|
94
|
-
const interpreted = interpretStandardResult(pyResult, {
|
|
95
|
-
successMessage: `Material ${name} processed`,
|
|
96
|
-
failureMessage: 'Failed to create material'
|
|
97
|
-
});
|
|
58
|
+
// Use Automation Bridge for material creation
|
|
59
|
+
if (this.automationBridge && typeof this.automationBridge.sendAutomationRequest === 'function') {
|
|
60
|
+
try {
|
|
61
|
+
const resp: any = await this.automationBridge.sendAutomationRequest('create_material', {
|
|
62
|
+
name: effectiveName,
|
|
63
|
+
destinationPath: cleanPath
|
|
64
|
+
});
|
|
98
65
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
return { success: true, path: materialPath, message: interpreted.message };
|
|
109
|
-
}
|
|
66
|
+
if (resp && resp.success !== false) {
|
|
67
|
+
return {
|
|
68
|
+
success: true,
|
|
69
|
+
path: resp.path || resp.result?.path || materialPath,
|
|
70
|
+
message: resp.message || `Material ${name} created at ${materialPath}`,
|
|
71
|
+
warnings: resp.warnings
|
|
72
|
+
};
|
|
73
|
+
}
|
|
110
74
|
|
|
111
|
-
if (interpreted.error) {
|
|
112
|
-
const exists = await this.assetExists(materialPath);
|
|
113
|
-
if (exists) {
|
|
114
75
|
return {
|
|
115
|
-
success:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
76
|
+
success: false,
|
|
77
|
+
error: resp?.error ?? resp?.message ?? 'Failed to create material'
|
|
78
|
+
};
|
|
79
|
+
} catch (err) {
|
|
80
|
+
return {
|
|
81
|
+
success: false,
|
|
82
|
+
error: `Failed to create material: ${err}`
|
|
120
83
|
};
|
|
121
84
|
}
|
|
122
|
-
return {
|
|
123
|
-
success: false,
|
|
124
|
-
error: interpreted.error,
|
|
125
|
-
warnings: interpreted.warnings,
|
|
126
|
-
details: interpreted.details
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const exists = await this.assetExists(materialPath);
|
|
131
|
-
if (exists) {
|
|
132
|
-
return {
|
|
133
|
-
success: true,
|
|
134
|
-
path: materialPath,
|
|
135
|
-
message: `Material ${name} created at ${materialPath}`,
|
|
136
|
-
warnings: interpreted.warnings,
|
|
137
|
-
details: interpreted.details
|
|
138
|
-
};
|
|
139
85
|
}
|
|
140
86
|
|
|
141
87
|
return {
|
|
142
88
|
success: false,
|
|
143
|
-
error:
|
|
144
|
-
warnings: interpreted.warnings,
|
|
145
|
-
details: interpreted.details
|
|
89
|
+
error: 'Material creation requires Automation Bridge connection'
|
|
146
90
|
};
|
|
147
91
|
} catch (err) {
|
|
148
92
|
return { success: false, error: `Failed to create material: ${err}` };
|
|
@@ -151,29 +95,195 @@ print('RESULT:' + json.dumps(result))
|
|
|
151
95
|
|
|
152
96
|
async applyMaterialToActor(actorPath: string, materialPath: string, slotIndex = 0) {
|
|
153
97
|
try {
|
|
154
|
-
await this.bridge.
|
|
98
|
+
const result = await this.bridge.setObjectProperty({
|
|
155
99
|
objectPath: actorPath,
|
|
156
100
|
propertyName: `StaticMeshComponent.Materials[${slotIndex}]`,
|
|
157
|
-
|
|
101
|
+
value: materialPath
|
|
158
102
|
});
|
|
159
|
-
|
|
103
|
+
|
|
104
|
+
if (result.success) {
|
|
105
|
+
return {
|
|
106
|
+
success: true,
|
|
107
|
+
message: 'Material applied',
|
|
108
|
+
transport: result.transport,
|
|
109
|
+
warnings: Array.isArray(result.warnings) ? result.warnings : undefined
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
success: false,
|
|
115
|
+
error: result.error ?? `Failed to apply material to ${actorPath}`,
|
|
116
|
+
transport: result.transport
|
|
117
|
+
};
|
|
160
118
|
} catch (err) {
|
|
161
119
|
return { success: false, error: `Failed to apply material: ${err}` };
|
|
162
120
|
}
|
|
163
121
|
}
|
|
164
122
|
|
|
165
|
-
|
|
123
|
+
async createMaterialInstance(name: string, path: string, parentMaterial: string, parameters?: Record<string, any>) {
|
|
166
124
|
try {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
125
|
+
if (!name || name.trim() === '') {
|
|
126
|
+
return { success: false, error: 'Material instance name cannot be empty' };
|
|
127
|
+
}
|
|
128
|
+
const cleanPath = (path || '/Game').replace(/\/$/, '');
|
|
129
|
+
if (!cleanPath.startsWith('/Game') && !cleanPath.startsWith('/Engine')) {
|
|
130
|
+
return { success: false, error: `Invalid path: must start with /Game or /Engine, got ${cleanPath}` };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Plugin-first attempt
|
|
134
|
+
if (this.automationBridge && typeof this.automationBridge.sendAutomationRequest === 'function') {
|
|
135
|
+
try {
|
|
136
|
+
const resp: any = await this.automationBridge.sendAutomationRequest('create_material_instance', {
|
|
137
|
+
name,
|
|
138
|
+
destinationPath: cleanPath,
|
|
139
|
+
parentMaterial,
|
|
140
|
+
parameters
|
|
141
|
+
});
|
|
142
|
+
if (resp && resp.success !== false) {
|
|
143
|
+
return { success: true, path: resp.path || resp.result?.path || `${cleanPath}/${name}`, message: resp.message || `Material instance ${name} created at ${cleanPath}/${name}`, warnings: resp.warnings } as any;
|
|
144
|
+
}
|
|
145
|
+
const errTxt = String(resp?.error ?? resp?.message ?? '');
|
|
146
|
+
if (!(errTxt.toLowerCase().includes('unknown') || errTxt.includes('UNKNOWN_PLUGIN_ACTION'))) {
|
|
147
|
+
return { success: false, error: resp?.error ?? resp?.message ?? 'CREATE_MATERIAL_INSTANCE_FAILED' } as any;
|
|
148
|
+
}
|
|
149
|
+
} catch (_e) {
|
|
150
|
+
// fall back to python path below
|
|
172
151
|
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Fallback: use the centralized editor function template for material instance
|
|
155
|
+
const createParams = {
|
|
156
|
+
asset_name: name,
|
|
157
|
+
package_path: cleanPath,
|
|
158
|
+
factory_class: 'MaterialInstanceConstantFactoryNew',
|
|
159
|
+
asset_class: 'MaterialInstanceConstant',
|
|
160
|
+
parent_material: parentMaterial,
|
|
161
|
+
parameters: parameters || {}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// Try a plugin-native asset creation path via executeEditorFunction
|
|
165
|
+
try {
|
|
166
|
+
const res = await this.bridge.executeEditorFunction('CREATE_ASSET', createParams);
|
|
167
|
+
if (res && res.success !== false) {
|
|
168
|
+
const createdPath = res.path || res.result?.path || `${cleanPath}/${name}`;
|
|
169
|
+
// Attempt to set parent via plugin-native setObjectProperty when available
|
|
170
|
+
if (parentMaterial) {
|
|
171
|
+
try {
|
|
172
|
+
await this.bridge.setObjectProperty({ objectPath: createdPath, propertyName: 'parent', value: parentMaterial, markDirty: true });
|
|
173
|
+
} catch (_) {
|
|
174
|
+
// Ignore failure to set parent - template may have already set it
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Save asset (plugin-first via SAVE_ASSET template)
|
|
178
|
+
try {
|
|
179
|
+
await this.bridge.executeEditorFunction('SAVE_ASSET', { path: createdPath });
|
|
180
|
+
} catch (_) { }
|
|
181
|
+
return { success: true, path: createdPath, message: `Material instance ${name} created at ${createdPath}` } as any;
|
|
182
|
+
}
|
|
183
|
+
// If plugin indicated unknown action and python fallback not allowed, surface explicit failure
|
|
184
|
+
const errTxt = String((res as any)?.error ?? (res as any)?.message ?? '');
|
|
185
|
+
if (errTxt.toLowerCase().includes('unknown') || errTxt.includes('UNKNOWN_PLUGIN_ACTION')) {
|
|
186
|
+
return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement create_material_instance' } as any;
|
|
187
|
+
}
|
|
188
|
+
return { success: false, error: (res as any)?.error ?? (res as any)?.message ?? 'CREATE_MATERIAL_INSTANCE_FAILED' } as any;
|
|
189
|
+
} catch (err) {
|
|
190
|
+
// If executeEditorFunction threw due to bridge not connected or python disabled,
|
|
191
|
+
// preserve previous behavior by returning structured failure.
|
|
192
|
+
return { success: false, error: String(err) || 'CREATE_MATERIAL_INSTANCE_FAILED' } as any;
|
|
193
|
+
}
|
|
194
|
+
} catch (err) {
|
|
195
|
+
return { success: false, error: `Failed to create material instance: ${err}` };
|
|
177
196
|
}
|
|
178
197
|
}
|
|
198
|
+
async addNode(params: {
|
|
199
|
+
materialPath: string;
|
|
200
|
+
nodeType: string;
|
|
201
|
+
x: number;
|
|
202
|
+
y: number;
|
|
203
|
+
name?: string; // For parameter nodes
|
|
204
|
+
timeoutMs?: number;
|
|
205
|
+
}) {
|
|
206
|
+
if (!params.materialPath) return { success: false, error: 'INVALID_MATERIAL_PATH', message: 'Material path is required' } as const;
|
|
207
|
+
if (!params.nodeType) return { success: false, error: 'INVALID_NODE_TYPE', message: 'Node type is required' } as const;
|
|
208
|
+
|
|
209
|
+
const res = await this.automationBridge?.sendAutomationRequest('manage_material_graph', {
|
|
210
|
+
subAction: 'add_node',
|
|
211
|
+
assetPath: params.materialPath,
|
|
212
|
+
nodeType: params.nodeType,
|
|
213
|
+
x: params.x,
|
|
214
|
+
y: params.y,
|
|
215
|
+
name: params.name
|
|
216
|
+
});
|
|
217
|
+
return res;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async removeNode(params: {
|
|
221
|
+
materialPath: string;
|
|
222
|
+
nodeId: string;
|
|
223
|
+
timeoutMs?: number;
|
|
224
|
+
}) {
|
|
225
|
+
if (!params.materialPath) return { success: false, error: 'INVALID_MATERIAL_PATH', message: 'Material path is required' } as const;
|
|
226
|
+
if (!params.nodeId) return { success: false, error: 'INVALID_NODE_ID', message: 'Node ID is required' } as const;
|
|
227
|
+
|
|
228
|
+
const res = await this.automationBridge?.sendAutomationRequest('manage_material_graph', {
|
|
229
|
+
subAction: 'remove_node',
|
|
230
|
+
assetPath: params.materialPath,
|
|
231
|
+
nodeId: params.nodeId
|
|
232
|
+
});
|
|
233
|
+
return res;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async connectNodes(params: {
|
|
237
|
+
materialPath: string;
|
|
238
|
+
sourceNodeId: string;
|
|
239
|
+
targetNodeId: string; // Use 'Main' for main material node
|
|
240
|
+
inputName: string; // Input pin name on target
|
|
241
|
+
timeoutMs?: number;
|
|
242
|
+
}) {
|
|
243
|
+
if (!params.materialPath) return { success: false, error: 'INVALID_MATERIAL_PATH', message: 'Material path is required' } as const;
|
|
244
|
+
if (!params.sourceNodeId) return { success: false, error: 'INVALID_SOURCE_NODE', message: 'Source node ID is required' } as const;
|
|
245
|
+
if (!params.inputName) return { success: false, error: 'INVALID_INPUT_NAME', message: 'Input name is required' } as const;
|
|
246
|
+
|
|
247
|
+
const res = await this.automationBridge?.sendAutomationRequest('manage_material_graph', {
|
|
248
|
+
subAction: 'connect_nodes',
|
|
249
|
+
assetPath: params.materialPath,
|
|
250
|
+
sourceNodeId: params.sourceNodeId,
|
|
251
|
+
targetNodeId: params.targetNodeId,
|
|
252
|
+
inputName: params.inputName
|
|
253
|
+
});
|
|
254
|
+
return res;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async breakConnections(params: {
|
|
258
|
+
materialPath: string;
|
|
259
|
+
nodeId: string;
|
|
260
|
+
pinName?: string;
|
|
261
|
+
timeoutMs?: number;
|
|
262
|
+
}) {
|
|
263
|
+
if (!params.materialPath) return { success: false, error: 'INVALID_MATERIAL_PATH', message: 'Material path is required' } as const;
|
|
264
|
+
if (!params.nodeId) return { success: false, error: 'INVALID_NODE_ID', message: 'Node ID is required' } as const;
|
|
265
|
+
|
|
266
|
+
const res = await this.automationBridge?.sendAutomationRequest('manage_material_graph', {
|
|
267
|
+
subAction: 'break_connections',
|
|
268
|
+
assetPath: params.materialPath,
|
|
269
|
+
nodeId: params.nodeId,
|
|
270
|
+
pinName: params.pinName
|
|
271
|
+
});
|
|
272
|
+
return res;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async getNodeDetails(params: {
|
|
276
|
+
materialPath: string;
|
|
277
|
+
nodeId?: string; // If omitted, lists all nodes
|
|
278
|
+
timeoutMs?: number;
|
|
279
|
+
}) {
|
|
280
|
+
if (!params.materialPath) return { success: false, error: 'INVALID_MATERIAL_PATH', message: 'Material path is required' } as const;
|
|
281
|
+
|
|
282
|
+
const res = await this.automationBridge?.sendAutomationRequest('manage_material_graph', {
|
|
283
|
+
subAction: 'get_node_details',
|
|
284
|
+
assetPath: params.materialPath,
|
|
285
|
+
nodeId: params.nodeId
|
|
286
|
+
});
|
|
287
|
+
return res;
|
|
288
|
+
}
|
|
179
289
|
}
|