unreal-engine-mcp-server 0.4.7 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +26 -0
- package/.env.production +38 -7
- package/.eslintrc.json +0 -54
- package/.eslintrc.override.json +8 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +94 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +56 -0
- package/.github/copilot-instructions.md +478 -45
- package/.github/dependabot.yml +19 -0
- package/.github/labeler.yml +24 -0
- package/.github/labels.yml +70 -0
- package/.github/pull_request_template.md +42 -0
- package/.github/release-drafter.yml +148 -0
- package/.github/workflows/auto-merge.yml +38 -0
- package/.github/workflows/ci.yml +38 -0
- package/.github/workflows/dependency-review.yml +17 -0
- package/.github/workflows/gemini-issue-triage.yml +172 -0
- package/.github/workflows/greetings.yml +23 -0
- package/.github/workflows/labeler.yml +16 -0
- package/.github/workflows/links.yml +80 -0
- package/.github/workflows/pr-size-labeler.yml +137 -0
- package/.github/workflows/publish-mcp.yml +12 -7
- package/.github/workflows/release-drafter.yml +23 -0
- package/.github/workflows/release.yml +112 -0
- package/.github/workflows/semantic-pull-request.yml +35 -0
- package/.github/workflows/smoke-test.yml +36 -0
- package/.github/workflows/stale.yml +28 -0
- package/CHANGELOG.md +267 -31
- package/CONTRIBUTING.md +140 -0
- package/README.md +166 -71
- package/claude_desktop_config_example.json +7 -6
- package/dist/automation/bridge.d.ts +50 -0
- package/dist/automation/bridge.js +452 -0
- package/dist/automation/connection-manager.d.ts +23 -0
- package/dist/automation/connection-manager.js +107 -0
- package/dist/automation/handshake.d.ts +11 -0
- package/dist/automation/handshake.js +89 -0
- package/dist/automation/index.d.ts +3 -0
- package/dist/automation/index.js +3 -0
- package/dist/automation/message-handler.d.ts +12 -0
- package/dist/automation/message-handler.js +149 -0
- package/dist/automation/request-tracker.d.ts +25 -0
- package/dist/automation/request-tracker.js +98 -0
- package/dist/automation/types.d.ts +130 -0
- package/dist/automation/types.js +2 -0
- package/dist/cli.js +32 -5
- package/dist/config.d.ts +27 -0
- package/dist/config.js +60 -0
- package/dist/constants.d.ts +12 -0
- package/dist/constants.js +12 -0
- package/dist/graphql/resolvers.d.ts +268 -0
- package/dist/graphql/resolvers.js +743 -0
- package/dist/graphql/schema.d.ts +5 -0
- package/dist/graphql/schema.js +437 -0
- package/dist/graphql/server.d.ts +26 -0
- package/dist/graphql/server.js +115 -0
- package/dist/graphql/types.d.ts +7 -0
- package/dist/graphql/types.js +2 -0
- package/dist/handlers/resource-handlers.d.ts +20 -0
- package/dist/handlers/resource-handlers.js +180 -0
- package/dist/index.d.ts +31 -18
- package/dist/index.js +119 -619
- package/dist/prompts/index.js +4 -4
- package/dist/resources/actors.d.ts +17 -12
- package/dist/resources/actors.js +56 -76
- package/dist/resources/assets.d.ts +6 -14
- package/dist/resources/assets.js +115 -147
- package/dist/resources/levels.d.ts +13 -13
- package/dist/resources/levels.js +25 -34
- package/dist/server/resource-registry.d.ts +20 -0
- package/dist/server/resource-registry.js +37 -0
- package/dist/server/tool-registry.d.ts +23 -0
- package/dist/server/tool-registry.js +322 -0
- package/dist/server-setup.d.ts +21 -0
- package/dist/server-setup.js +111 -0
- package/dist/services/health-monitor.d.ts +34 -0
- package/dist/services/health-monitor.js +105 -0
- package/dist/services/metrics-server.d.ts +11 -0
- package/dist/services/metrics-server.js +105 -0
- package/dist/tools/actors.d.ts +147 -9
- package/dist/tools/actors.js +350 -311
- package/dist/tools/animation.d.ts +135 -4
- package/dist/tools/animation.js +510 -411
- package/dist/tools/assets.d.ts +117 -19
- package/dist/tools/assets.js +259 -284
- package/dist/tools/audio.d.ts +102 -42
- package/dist/tools/audio.js +272 -685
- package/dist/tools/base-tool.d.ts +17 -0
- package/dist/tools/base-tool.js +46 -0
- package/dist/tools/behavior-tree.d.ts +94 -0
- package/dist/tools/behavior-tree.js +39 -0
- package/dist/tools/blueprint/helpers.d.ts +29 -0
- package/dist/tools/blueprint/helpers.js +182 -0
- package/dist/tools/blueprint.d.ts +228 -118
- package/dist/tools/blueprint.js +685 -832
- package/dist/tools/consolidated-tool-definitions.d.ts +5462 -1781
- package/dist/tools/consolidated-tool-definitions.js +829 -496
- package/dist/tools/consolidated-tool-handlers.d.ts +2 -1
- package/dist/tools/consolidated-tool-handlers.js +211 -1026
- package/dist/tools/debug.d.ts +143 -85
- package/dist/tools/debug.js +234 -180
- package/dist/tools/dynamic-handler-registry.d.ts +11 -0
- package/dist/tools/dynamic-handler-registry.js +101 -0
- package/dist/tools/editor.d.ts +139 -18
- package/dist/tools/editor.js +239 -244
- package/dist/tools/engine.d.ts +10 -4
- package/dist/tools/engine.js +13 -5
- package/dist/tools/environment.d.ts +36 -0
- package/dist/tools/environment.js +267 -0
- package/dist/tools/foliage.d.ts +105 -14
- package/dist/tools/foliage.js +219 -331
- package/dist/tools/handlers/actor-handlers.d.ts +3 -0
- package/dist/tools/handlers/actor-handlers.js +232 -0
- package/dist/tools/handlers/animation-handlers.d.ts +3 -0
- package/dist/tools/handlers/animation-handlers.js +185 -0
- package/dist/tools/handlers/argument-helper.d.ts +16 -0
- package/dist/tools/handlers/argument-helper.js +80 -0
- package/dist/tools/handlers/asset-handlers.d.ts +3 -0
- package/dist/tools/handlers/asset-handlers.js +496 -0
- package/dist/tools/handlers/audio-handlers.d.ts +3 -0
- package/dist/tools/handlers/audio-handlers.js +166 -0
- package/dist/tools/handlers/blueprint-handlers.d.ts +4 -0
- package/dist/tools/handlers/blueprint-handlers.js +358 -0
- package/dist/tools/handlers/common-handlers.d.ts +14 -0
- package/dist/tools/handlers/common-handlers.js +56 -0
- package/dist/tools/handlers/editor-handlers.d.ts +3 -0
- package/dist/tools/handlers/editor-handlers.js +119 -0
- package/dist/tools/handlers/effect-handlers.d.ts +3 -0
- package/dist/tools/handlers/effect-handlers.js +171 -0
- package/dist/tools/handlers/environment-handlers.d.ts +3 -0
- package/dist/tools/handlers/environment-handlers.js +170 -0
- package/dist/tools/handlers/graph-handlers.d.ts +3 -0
- package/dist/tools/handlers/graph-handlers.js +90 -0
- package/dist/tools/handlers/input-handlers.d.ts +3 -0
- package/dist/tools/handlers/input-handlers.js +21 -0
- package/dist/tools/handlers/inspect-handlers.d.ts +3 -0
- package/dist/tools/handlers/inspect-handlers.js +383 -0
- package/dist/tools/handlers/level-handlers.d.ts +3 -0
- package/dist/tools/handlers/level-handlers.js +237 -0
- package/dist/tools/handlers/lighting-handlers.d.ts +3 -0
- package/dist/tools/handlers/lighting-handlers.js +144 -0
- package/dist/tools/handlers/performance-handlers.d.ts +3 -0
- package/dist/tools/handlers/performance-handlers.js +130 -0
- package/dist/tools/handlers/pipeline-handlers.d.ts +3 -0
- package/dist/tools/handlers/pipeline-handlers.js +110 -0
- package/dist/tools/handlers/sequence-handlers.d.ts +3 -0
- package/dist/tools/handlers/sequence-handlers.js +376 -0
- package/dist/tools/handlers/system-handlers.d.ts +4 -0
- package/dist/tools/handlers/system-handlers.js +506 -0
- package/dist/tools/input.d.ts +19 -0
- package/dist/tools/input.js +89 -0
- package/dist/tools/introspection.d.ts +103 -40
- package/dist/tools/introspection.js +425 -568
- package/dist/tools/landscape.d.ts +97 -36
- package/dist/tools/landscape.js +280 -409
- package/dist/tools/level.d.ts +130 -10
- package/dist/tools/level.js +639 -675
- package/dist/tools/lighting.d.ts +77 -38
- package/dist/tools/lighting.js +441 -943
- package/dist/tools/logs.d.ts +3 -3
- package/dist/tools/logs.js +5 -57
- package/dist/tools/materials.d.ts +91 -24
- package/dist/tools/materials.js +190 -118
- package/dist/tools/niagara.d.ts +149 -39
- package/dist/tools/niagara.js +232 -182
- package/dist/tools/performance.d.ts +27 -12
- package/dist/tools/performance.js +204 -122
- package/dist/tools/physics.d.ts +32 -77
- package/dist/tools/physics.js +171 -582
- package/dist/tools/property-dictionary.d.ts +13 -0
- package/dist/tools/property-dictionary.js +82 -0
- package/dist/tools/sequence.d.ts +73 -48
- package/dist/tools/sequence.js +196 -748
- package/dist/tools/tool-definition-utils.d.ts +59 -0
- package/dist/tools/tool-definition-utils.js +35 -0
- package/dist/tools/ui.d.ts +66 -34
- package/dist/tools/ui.js +134 -214
- package/dist/types/env.d.ts +0 -3
- package/dist/types/env.js +0 -7
- package/dist/types/tool-interfaces.d.ts +898 -0
- package/dist/types/tool-interfaces.js +2 -0
- package/dist/types/tool-types.d.ts +183 -19
- package/dist/types/tool-types.js +0 -4
- package/dist/unreal-bridge.d.ts +24 -131
- package/dist/unreal-bridge.js +364 -1506
- package/dist/utils/command-validator.d.ts +9 -0
- package/dist/utils/command-validator.js +67 -0
- package/dist/utils/elicitation.d.ts +1 -1
- package/dist/utils/elicitation.js +12 -15
- package/dist/utils/error-handler.d.ts +2 -51
- package/dist/utils/error-handler.js +11 -87
- package/dist/utils/ini-reader.d.ts +3 -0
- package/dist/utils/ini-reader.js +69 -0
- package/dist/utils/logger.js +9 -6
- package/dist/utils/normalize.d.ts +3 -0
- package/dist/utils/normalize.js +56 -0
- package/dist/utils/response-factory.d.ts +7 -0
- package/dist/utils/response-factory.js +33 -0
- package/dist/utils/response-validator.d.ts +3 -24
- package/dist/utils/response-validator.js +130 -81
- package/dist/utils/result-helpers.d.ts +4 -5
- package/dist/utils/result-helpers.js +15 -16
- package/dist/utils/safe-json.js +5 -11
- package/dist/utils/unreal-command-queue.d.ts +24 -0
- package/dist/utils/unreal-command-queue.js +120 -0
- package/dist/utils/validation.d.ts +0 -40
- package/dist/utils/validation.js +1 -78
- package/dist/wasm/index.d.ts +70 -0
- package/dist/wasm/index.js +535 -0
- package/docs/GraphQL-API.md +888 -0
- package/docs/Migration-Guide-v0.5.0.md +692 -0
- package/docs/Roadmap.md +53 -0
- package/docs/WebAssembly-Integration.md +628 -0
- package/docs/editor-plugin-extension.md +370 -0
- package/docs/handler-mapping.md +242 -0
- package/docs/native-automation-progress.md +128 -0
- package/docs/testing-guide.md +423 -0
- package/mcp-config-example.json +6 -6
- package/package.json +60 -27
- package/plugins/McpAutomationBridge/Config/FilterPlugin.ini +8 -0
- package/plugins/McpAutomationBridge/McpAutomationBridge.uplugin +64 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/McpAutomationBridge.Build.cs +189 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.cpp +22 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.h +30 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeHelpers.h +1983 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeModule.cpp +72 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSettings.cpp +46 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +581 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +2394 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetQueryHandlers.cpp +300 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetWorkflowHandlers.cpp +2807 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AudioHandlers.cpp +1087 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BehaviorTreeHandlers.cpp +488 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.cpp +643 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.h +31 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +1184 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +5652 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers_List.cpp +152 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ControlHandlers.cpp +2614 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_DebugHandlers.cpp +42 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EditorFunctionHandlers.cpp +1237 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +1701 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +2145 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_FoliageHandlers.cpp +954 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InputHandlers.cpp +209 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InsightsHandlers.cpp +41 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LandscapeHandlers.cpp +1164 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LevelHandlers.cpp +762 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +634 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LogHandlers.cpp +136 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_MaterialGraphHandlers.cpp +494 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraGraphHandlers.cpp +278 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraHandlers.cpp +625 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PerformanceHandlers.cpp +401 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PipelineHandlers.cpp +67 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +735 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PropertyHandlers.cpp +2634 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_RenderHandlers.cpp +189 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.cpp +917 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.h +39 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +2670 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequencerHandlers.cpp +519 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_TestHandlers.cpp +38 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_UiHandlers.cpp +668 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_WorldPartitionHandlers.cpp +346 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +1330 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.h +149 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +783 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSettings.h +115 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSubsystem.h +796 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpConnectionManager.h +117 -0
- package/scripts/check-unreal-connection.mjs +19 -0
- package/scripts/clean-tmp.js +23 -0
- package/scripts/patch-wasm.js +26 -0
- package/scripts/run-all-tests.mjs +131 -0
- package/scripts/smoke-test.ts +94 -0
- package/scripts/sync-mcp-plugin.js +143 -0
- package/scripts/test-no-plugin-alternates.mjs +113 -0
- package/scripts/validate-server.js +46 -0
- package/scripts/verify-automation-bridge.js +200 -0
- package/server.json +57 -21
- package/src/automation/bridge.ts +558 -0
- package/src/automation/connection-manager.ts +130 -0
- package/src/automation/handshake.ts +99 -0
- package/src/automation/index.ts +2 -0
- package/src/automation/message-handler.ts +167 -0
- package/src/automation/request-tracker.ts +123 -0
- package/src/automation/types.ts +107 -0
- package/src/cli.ts +33 -6
- package/src/config.ts +73 -0
- package/src/constants.ts +12 -0
- package/src/graphql/resolvers.ts +1010 -0
- package/src/graphql/schema.ts +452 -0
- package/src/graphql/server.ts +154 -0
- package/src/graphql/types.ts +7 -0
- package/src/handlers/resource-handlers.ts +186 -0
- package/src/index.ts +152 -663
- package/src/prompts/index.ts +4 -4
- package/src/resources/actors.ts +58 -76
- package/src/resources/assets.ts +147 -134
- package/src/resources/levels.ts +28 -33
- package/src/server/resource-registry.ts +47 -0
- package/src/server/tool-registry.ts +354 -0
- package/src/server-setup.ts +148 -0
- package/src/services/health-monitor.ts +132 -0
- package/src/services/metrics-server.ts +142 -0
- package/src/tools/actors.ts +417 -322
- package/src/tools/animation.ts +671 -461
- package/src/tools/assets.ts +353 -289
- package/src/tools/audio.ts +323 -766
- package/src/tools/base-tool.ts +52 -0
- package/src/tools/behavior-tree.ts +45 -0
- package/src/tools/blueprint/helpers.ts +189 -0
- package/src/tools/blueprint.ts +787 -965
- package/src/tools/consolidated-tool-definitions.ts +993 -515
- package/src/tools/consolidated-tool-handlers.ts +272 -1139
- package/src/tools/debug.ts +292 -187
- package/src/tools/dynamic-handler-registry.ts +151 -0
- package/src/tools/editor.ts +309 -246
- package/src/tools/engine.ts +14 -3
- package/src/tools/environment.ts +287 -0
- package/src/tools/foliage.ts +314 -379
- package/src/tools/handlers/actor-handlers.ts +271 -0
- package/src/tools/handlers/animation-handlers.ts +237 -0
- package/src/tools/handlers/argument-helper.ts +142 -0
- package/src/tools/handlers/asset-handlers.ts +532 -0
- package/src/tools/handlers/audio-handlers.ts +194 -0
- package/src/tools/handlers/blueprint-handlers.ts +380 -0
- package/src/tools/handlers/common-handlers.ts +87 -0
- package/src/tools/handlers/editor-handlers.ts +123 -0
- package/src/tools/handlers/effect-handlers.ts +220 -0
- package/src/tools/handlers/environment-handlers.ts +183 -0
- package/src/tools/handlers/graph-handlers.ts +116 -0
- package/src/tools/handlers/input-handlers.ts +28 -0
- package/src/tools/handlers/inspect-handlers.ts +450 -0
- package/src/tools/handlers/level-handlers.ts +252 -0
- package/src/tools/handlers/lighting-handlers.ts +147 -0
- package/src/tools/handlers/performance-handlers.ts +132 -0
- package/src/tools/handlers/pipeline-handlers.ts +127 -0
- package/src/tools/handlers/sequence-handlers.ts +415 -0
- package/src/tools/handlers/system-handlers.ts +564 -0
- package/src/tools/input.ts +101 -0
- package/src/tools/introspection.ts +493 -584
- package/src/tools/landscape.ts +394 -489
- package/src/tools/level.ts +752 -694
- package/src/tools/lighting.ts +583 -984
- package/src/tools/logs.ts +9 -57
- package/src/tools/materials.ts +231 -121
- package/src/tools/niagara.ts +293 -168
- package/src/tools/performance.ts +320 -168
- package/src/tools/physics.ts +268 -613
- package/src/tools/property-dictionary.ts +98 -0
- package/src/tools/sequence.ts +255 -815
- package/src/tools/tool-definition-utils.ts +35 -0
- package/src/tools/ui.ts +207 -283
- package/src/types/env.ts +0 -10
- package/src/types/tool-interfaces.ts +250 -0
- package/src/types/tool-types.ts +243 -21
- package/src/unreal-bridge.ts +460 -1550
- package/src/utils/command-validator.ts +75 -0
- package/src/utils/elicitation.ts +10 -7
- package/src/utils/error-handler.ts +14 -90
- package/src/utils/ini-reader.ts +86 -0
- package/src/utils/logger.ts +8 -3
- package/src/utils/normalize.ts +60 -0
- package/src/utils/response-factory.ts +39 -0
- package/src/utils/response-validator.ts +176 -56
- package/src/utils/result-helpers.ts +21 -19
- package/src/utils/safe-json.ts +14 -11
- package/src/utils/unreal-command-queue.ts +152 -0
- package/src/utils/validation.ts +4 -1
- package/src/wasm/index.ts +838 -0
- package/test-server.mjs +100 -0
- package/tests/run-unreal-tool-tests.mjs +242 -14
- package/tests/test-animation.mjs +44 -0
- package/tests/test-asset-advanced.mjs +82 -0
- package/tests/test-asset-errors.mjs +35 -0
- package/tests/test-audio.mjs +219 -0
- package/tests/test-automation-timeouts.mjs +98 -0
- package/tests/test-behavior-tree.mjs +261 -0
- package/tests/test-blueprint-events.mjs +35 -0
- package/tests/test-blueprint-graph.mjs +79 -0
- package/tests/test-blueprint.mjs +577 -0
- package/tests/test-client-mode.mjs +86 -0
- package/tests/test-console-command.mjs +56 -0
- package/tests/test-control-actor.mjs +425 -0
- package/tests/test-control-editor.mjs +80 -0
- package/tests/test-extra-tools.mjs +38 -0
- package/tests/test-graphql.mjs +322 -0
- package/tests/test-inspect.mjs +72 -0
- package/tests/test-landscape.mjs +60 -0
- package/tests/test-manage-asset.mjs +438 -0
- package/tests/test-manage-level.mjs +70 -0
- package/tests/test-materials.mjs +356 -0
- package/tests/test-niagara.mjs +185 -0
- package/tests/test-no-inline-python.mjs +122 -0
- package/tests/test-plugin-handshake.mjs +82 -0
- package/tests/test-render.mjs +33 -0
- package/tests/test-runner.mjs +933 -0
- package/tests/test-search-assets.mjs +66 -0
- package/tests/test-sequence.mjs +68 -0
- package/tests/test-system.mjs +57 -0
- package/tests/test-wasm.mjs +193 -0
- package/tests/test-world-partition.mjs +215 -0
- package/tsconfig.json +3 -3
- package/wasm/Cargo.lock +363 -0
- package/wasm/Cargo.toml +42 -0
- package/wasm/LICENSE +21 -0
- package/wasm/README.md +253 -0
- package/wasm/src/dependency_resolver.rs +377 -0
- package/wasm/src/lib.rs +153 -0
- package/wasm/src/property_parser.rs +271 -0
- package/wasm/src/transform_math.rs +396 -0
- package/wasm/tests/integration.rs +109 -0
- package/.github/workflows/smithery-build.yml +0 -29
- package/dist/tools/build_environment_advanced.d.ts +0 -65
- package/dist/tools/build_environment_advanced.js +0 -633
- package/dist/tools/rc.d.ts +0 -110
- package/dist/tools/rc.js +0 -437
- package/dist/tools/visual.d.ts +0 -40
- package/dist/tools/visual.js +0 -282
- package/dist/utils/http.d.ts +0 -6
- package/dist/utils/http.js +0 -151
- package/dist/utils/python-output.d.ts +0 -18
- package/dist/utils/python-output.js +0 -290
- package/dist/utils/python.d.ts +0 -2
- package/dist/utils/python.js +0 -4
- package/dist/utils/stdio-redirect.d.ts +0 -2
- package/dist/utils/stdio-redirect.js +0 -20
- package/docs/unreal-tool-test-cases.md +0 -574
- package/smithery.yaml +0 -29
- package/src/tools/build_environment_advanced.ts +0 -732
- package/src/tools/rc.ts +0 -515
- package/src/tools/visual.ts +0 -281
- package/src/utils/http.ts +0 -187
- package/src/utils/python-output.ts +0 -351
- package/src/utils/python.ts +0 -3
- package/src/utils/stdio-redirect.ts +0 -18
package/src/tools/physics.ts
CHANGED
|
@@ -1,90 +1,54 @@
|
|
|
1
|
+
// Physics tools for Unreal Engine using Automation Bridge
|
|
1
2
|
import { UnrealBridge } from '../unreal-bridge.js';
|
|
3
|
+
import { AutomationBridge } from '../automation/index.js';
|
|
2
4
|
import { validateAssetParams, resolveSkeletalMeshPath, concurrencyDelay } from '../utils/validation.js';
|
|
3
|
-
import {
|
|
5
|
+
import { coerceString, coerceStringArray } from '../utils/result-helpers.js';
|
|
4
6
|
|
|
5
7
|
export class PhysicsTools {
|
|
6
|
-
constructor(private bridge: UnrealBridge) {}
|
|
7
|
-
|
|
8
|
+
constructor(private bridge: UnrealBridge, private automationBridge?: AutomationBridge) { }
|
|
9
|
+
|
|
10
|
+
setAutomationBridge(automationBridge?: AutomationBridge) { this.automationBridge = automationBridge; }
|
|
11
|
+
|
|
8
12
|
/**
|
|
9
13
|
* Helper to find a valid skeletal mesh in the project
|
|
10
14
|
*/
|
|
11
15
|
private async findValidSkeletalMesh(): Promise<string | null> {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
result = {
|
|
17
|
-
'success': False,
|
|
18
|
-
'meshPath': None,
|
|
19
|
-
'source': None
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
common_paths = [
|
|
23
|
-
'/Game/Characters/Mannequins/Meshes/SKM_Manny',
|
|
24
|
-
'/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
|
|
25
|
-
'/Game/Characters/Mannequins/Meshes/SKM_Manny_Complex',
|
|
26
|
-
'/Game/Characters/Mannequins/Meshes/SKM_Quinn',
|
|
27
|
-
'/Game/Characters/Mannequins/Meshes/SKM_Quinn_Simple',
|
|
28
|
-
'/Game/Characters/Mannequins/Meshes/SKM_Quinn_Complex'
|
|
29
|
-
]
|
|
30
|
-
|
|
31
|
-
for candidate in common_paths:
|
|
32
|
-
if unreal.EditorAssetLibrary.does_asset_exist(candidate):
|
|
33
|
-
mesh = unreal.EditorAssetLibrary.load_asset(candidate)
|
|
34
|
-
if mesh and isinstance(mesh, unreal.SkeletalMesh):
|
|
35
|
-
result['success'] = True
|
|
36
|
-
result['meshPath'] = candidate
|
|
37
|
-
result['source'] = 'common'
|
|
38
|
-
break
|
|
39
|
-
|
|
40
|
-
if not result['success']:
|
|
41
|
-
asset_registry = unreal.AssetRegistryHelpers.get_asset_registry()
|
|
42
|
-
assets = asset_registry.get_assets_by_class('SkeletalMesh', search_sub_classes=False)
|
|
43
|
-
if assets:
|
|
44
|
-
first_mesh = assets[0]
|
|
45
|
-
obj_path = first_mesh.get_editor_property('object_path') if hasattr(first_mesh, 'get_editor_property') else None
|
|
46
|
-
if not obj_path and hasattr(first_mesh, 'object_path'):
|
|
47
|
-
obj_path = first_mesh.object_path
|
|
48
|
-
if obj_path:
|
|
49
|
-
result['success'] = True
|
|
50
|
-
result['meshPath'] = str(obj_path).split('.')[0]
|
|
51
|
-
result['source'] = 'registry'
|
|
52
|
-
if hasattr(first_mesh, 'asset_name'):
|
|
53
|
-
result['assetName'] = str(first_mesh.asset_name)
|
|
54
|
-
|
|
55
|
-
if not result['success']:
|
|
56
|
-
result['fallback'] = '/Engine/EngineMeshes/SkeletalCube'
|
|
57
|
-
|
|
58
|
-
print('RESULT:' + json.dumps(result))
|
|
59
|
-
`;
|
|
16
|
+
if (!this.automationBridge) {
|
|
17
|
+
// Return common fallback paths without plugin
|
|
18
|
+
return '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple';
|
|
19
|
+
}
|
|
60
20
|
|
|
61
21
|
try {
|
|
62
|
-
const response = await this.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
22
|
+
const response = await this.automationBridge.sendAutomationRequest('find_skeletal_mesh', {
|
|
23
|
+
commonPaths: [
|
|
24
|
+
'/Game/Characters/Mannequins/Meshes/SKM_Manny',
|
|
25
|
+
'/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
|
|
26
|
+
'/Game/Characters/Mannequins/Meshes/SKM_Manny_Complex',
|
|
27
|
+
'/Game/Characters/Mannequins/Meshes/SKM_Quinn',
|
|
28
|
+
'/Game/Characters/Mannequins/Meshes/SKM_Quinn_Simple',
|
|
29
|
+
'/Game/Characters/Mannequins/Meshes/SKM_Quinn_Complex'
|
|
30
|
+
],
|
|
31
|
+
fallback: '/Engine/EngineMeshes/SkeletalCube'
|
|
32
|
+
}, {
|
|
33
|
+
timeoutMs: 30000
|
|
66
34
|
});
|
|
67
35
|
|
|
68
|
-
if (
|
|
69
|
-
const meshPath = coerceString(
|
|
36
|
+
if (response.success !== false && response.result) {
|
|
37
|
+
const meshPath = coerceString((response.result as any).meshPath);
|
|
70
38
|
if (meshPath) {
|
|
71
39
|
return meshPath;
|
|
72
40
|
}
|
|
73
41
|
}
|
|
74
42
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const detail = bestEffortInterpretedText(interpreted);
|
|
81
|
-
if (detail) {
|
|
82
|
-
console.error('Failed to parse skeletal mesh discovery:', detail);
|
|
43
|
+
// Fallback to alternate path
|
|
44
|
+
const alternate = coerceString((response.result as any)?.alternate);
|
|
45
|
+
if (alternate) {
|
|
46
|
+
return alternate;
|
|
83
47
|
}
|
|
84
48
|
} catch (error) {
|
|
85
49
|
console.error('Failed to find skeletal mesh:', error);
|
|
86
50
|
}
|
|
87
|
-
|
|
51
|
+
|
|
88
52
|
return '/Engine/EngineMeshes/SkeletalCube';
|
|
89
53
|
}
|
|
90
54
|
|
|
@@ -119,33 +83,33 @@ print('RESULT:' + json.dumps(result))
|
|
|
119
83
|
error: 'Name cannot be empty'
|
|
120
84
|
};
|
|
121
85
|
}
|
|
122
|
-
|
|
86
|
+
|
|
123
87
|
// Check for invalid characters in name
|
|
124
|
-
if (params.physicsAssetName.includes('@') || params.physicsAssetName.includes('#') ||
|
|
125
|
-
|
|
88
|
+
if (params.physicsAssetName.includes('@') || params.physicsAssetName.includes('#') ||
|
|
89
|
+
params.physicsAssetName.includes('$') || params.physicsAssetName.includes('%')) {
|
|
126
90
|
return {
|
|
127
91
|
success: false,
|
|
128
92
|
message: 'Failed to setup ragdoll: Name contains invalid characters',
|
|
129
93
|
error: 'Name contains invalid characters'
|
|
130
94
|
};
|
|
131
95
|
}
|
|
132
|
-
|
|
96
|
+
|
|
133
97
|
// Check if skeleton path is provided instead of skeletal mesh
|
|
134
|
-
if (params.skeletonPath && (params.skeletonPath.includes('_Skeleton') ||
|
|
135
|
-
|
|
98
|
+
if (params.skeletonPath && (params.skeletonPath.includes('_Skeleton') ||
|
|
99
|
+
params.skeletonPath.includes('SK_Mannequin') && !params.skeletonPath.includes('SKM_'))) {
|
|
136
100
|
return {
|
|
137
101
|
success: false,
|
|
138
102
|
message: 'Failed to setup ragdoll: Must specify a valid skeletal mesh',
|
|
139
103
|
error: 'Must specify a valid skeletal mesh, not a skeleton'
|
|
140
104
|
};
|
|
141
105
|
}
|
|
142
|
-
|
|
106
|
+
|
|
143
107
|
// Validate and sanitize parameters
|
|
144
108
|
const validation = validateAssetParams({
|
|
145
109
|
name: params.physicsAssetName,
|
|
146
110
|
savePath: params.savePath || '/Game/Physics'
|
|
147
111
|
});
|
|
148
|
-
|
|
112
|
+
|
|
149
113
|
if (!validation.valid) {
|
|
150
114
|
return {
|
|
151
115
|
success: false,
|
|
@@ -153,20 +117,20 @@ print('RESULT:' + json.dumps(result))
|
|
|
153
117
|
error: validation.error
|
|
154
118
|
};
|
|
155
119
|
}
|
|
156
|
-
|
|
120
|
+
|
|
157
121
|
const sanitizedParams = validation.sanitized;
|
|
158
122
|
const path = sanitizedParams.savePath || '/Game/Physics';
|
|
159
|
-
|
|
123
|
+
|
|
160
124
|
// Resolve skeletal mesh path
|
|
161
125
|
let meshPath = params.skeletonPath;
|
|
162
|
-
|
|
126
|
+
|
|
163
127
|
// Try to resolve skeleton to mesh mapping
|
|
164
128
|
const resolvedPath = resolveSkeletalMeshPath(meshPath);
|
|
165
129
|
if (resolvedPath && resolvedPath !== meshPath) {
|
|
166
130
|
console.error(`Auto-correcting path from ${meshPath} to ${resolvedPath}`);
|
|
167
131
|
meshPath = resolvedPath;
|
|
168
132
|
}
|
|
169
|
-
|
|
133
|
+
|
|
170
134
|
// Auto-resolve if it looks like a skeleton path or is empty
|
|
171
135
|
if (!meshPath || meshPath.includes('_Skeleton') || meshPath === 'None' || meshPath === '') {
|
|
172
136
|
console.error('Resolving skeletal mesh path...');
|
|
@@ -176,15 +140,15 @@ print('RESULT:' + json.dumps(result))
|
|
|
176
140
|
console.error(`Using resolved skeletal mesh: ${meshPath}`);
|
|
177
141
|
}
|
|
178
142
|
}
|
|
179
|
-
|
|
143
|
+
|
|
180
144
|
// Add concurrency delay to prevent race conditions
|
|
181
145
|
await concurrencyDelay();
|
|
182
|
-
|
|
146
|
+
|
|
183
147
|
// IMPORTANT: Physics assets require a SKELETAL MESH, not a skeleton
|
|
184
148
|
// UE5 uses: /Game/Characters/Mannequins/Meshes/SKM_Manny_Simple or SKM_Quinn_Simple
|
|
185
149
|
// UE4 used: /Game/Mannequin/Character/Mesh/SK_Mannequin (which no longer exists)
|
|
186
|
-
//
|
|
187
|
-
|
|
150
|
+
// Alternate path: /Engine/EngineMeshes/SkeletalCube
|
|
151
|
+
|
|
188
152
|
// Common skeleton paths that should be replaced with actual skeletal mesh paths
|
|
189
153
|
const skeletonToMeshMap: { [key: string]: string } = {
|
|
190
154
|
'/Game/Mannequin/Character/Mesh/UE4_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
|
|
@@ -193,356 +157,50 @@ print('RESULT:' + json.dumps(result))
|
|
|
193
157
|
'/Game/Characters/Mannequins/Skeletons/UE5_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
|
|
194
158
|
'/Game/Characters/Mannequins/Skeletons/UE5_Female_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Quinn_Simple'
|
|
195
159
|
};
|
|
196
|
-
|
|
160
|
+
|
|
197
161
|
// Auto-fix common incorrect paths
|
|
198
162
|
let actualSkeletonPath = params.skeletonPath;
|
|
199
163
|
if (actualSkeletonPath && skeletonToMeshMap[actualSkeletonPath]) {
|
|
200
164
|
console.error(`Auto-correcting path from ${actualSkeletonPath} to ${skeletonToMeshMap[actualSkeletonPath]}`);
|
|
201
165
|
actualSkeletonPath = skeletonToMeshMap[actualSkeletonPath];
|
|
202
166
|
}
|
|
203
|
-
|
|
167
|
+
|
|
204
168
|
if (actualSkeletonPath && (actualSkeletonPath.includes('_Skeleton') || actualSkeletonPath.includes('SK_Mannequin'))) {
|
|
205
169
|
// This is likely a skeleton path, not a skeletal mesh
|
|
206
170
|
console.error('Warning: Path appears to be a skeleton, not a skeletal mesh. Auto-correcting to SKM_Manny_Simple.');
|
|
207
171
|
}
|
|
208
|
-
|
|
209
|
-
// Build Python script with resolved mesh path
|
|
210
|
-
const pythonScript = `
|
|
211
|
-
import unreal
|
|
212
|
-
import time
|
|
213
|
-
import json
|
|
214
|
-
|
|
215
|
-
result = {
|
|
216
|
-
"success": False,
|
|
217
|
-
"path": None,
|
|
218
|
-
"message": "",
|
|
219
|
-
"error": None,
|
|
220
|
-
"warnings": [],
|
|
221
|
-
"details": [],
|
|
222
|
-
"existingAsset": False,
|
|
223
|
-
"meshPath": "${meshPath}"
|
|
224
|
-
}
|
|
225
172
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
def record_error(message):
|
|
233
|
-
result["error"] = message
|
|
234
|
-
|
|
235
|
-
# Helper function to ensure asset persistence
|
|
236
|
-
def ensure_asset_persistence(asset_path):
|
|
237
|
-
try:
|
|
238
|
-
asset = unreal.EditorAssetLibrary.load_asset(asset_path)
|
|
239
|
-
if not asset:
|
|
240
|
-
record_warning(f"Asset persistence check failed: {asset_path} not loaded")
|
|
241
|
-
return False
|
|
242
|
-
|
|
243
|
-
# Save the asset
|
|
244
|
-
saved = unreal.EditorAssetLibrary.save_asset(asset_path, only_if_is_dirty=False)
|
|
245
|
-
if saved:
|
|
246
|
-
print(f"Asset saved: {asset_path}")
|
|
247
|
-
record_detail(f"Asset saved: {asset_path}")
|
|
248
|
-
|
|
249
|
-
# Refresh the asset registry minimally for the asset's directory
|
|
250
|
-
try:
|
|
251
|
-
asset_dir = asset_path.rsplit('/', 1)[0]
|
|
252
|
-
unreal.AssetRegistryHelpers.get_asset_registry().scan_paths_synchronous([asset_dir], True)
|
|
253
|
-
except Exception as _reg_e:
|
|
254
|
-
record_warning(f"Asset registry refresh warning: {_reg_e}")
|
|
255
|
-
|
|
256
|
-
# Small delay to ensure filesystem sync
|
|
257
|
-
time.sleep(0.1)
|
|
258
|
-
|
|
259
|
-
return saved
|
|
260
|
-
except Exception as e:
|
|
261
|
-
print(f"Error ensuring persistence: {e}")
|
|
262
|
-
record_error(f"Error ensuring persistence: {e}")
|
|
263
|
-
return False
|
|
264
|
-
|
|
265
|
-
# Stop PIE if running using modern subsystems
|
|
266
|
-
try:
|
|
267
|
-
level_subsystem = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
|
|
268
|
-
play_subsystem = None
|
|
269
|
-
try:
|
|
270
|
-
play_subsystem = unreal.get_editor_subsystem(unreal.EditorPlayWorldSubsystem)
|
|
271
|
-
except Exception:
|
|
272
|
-
play_subsystem = None
|
|
273
|
-
|
|
274
|
-
is_playing = False
|
|
275
|
-
if level_subsystem and hasattr(level_subsystem, 'is_in_play_in_editor'):
|
|
276
|
-
is_playing = level_subsystem.is_in_play_in_editor()
|
|
277
|
-
elif play_subsystem and hasattr(play_subsystem, 'is_playing_in_editor'): # type: ignore[attr-defined]
|
|
278
|
-
is_playing = play_subsystem.is_playing_in_editor() # type: ignore[attr-defined]
|
|
279
|
-
|
|
280
|
-
if is_playing:
|
|
281
|
-
print("Stopping Play In Editor mode...")
|
|
282
|
-
record_detail("Stopping Play In Editor mode")
|
|
283
|
-
if level_subsystem and hasattr(level_subsystem, 'editor_request_end_play'):
|
|
284
|
-
level_subsystem.editor_request_end_play()
|
|
285
|
-
elif play_subsystem and hasattr(play_subsystem, 'stop_playing_session'): # type: ignore[attr-defined]
|
|
286
|
-
play_subsystem.stop_playing_session() # type: ignore[attr-defined]
|
|
287
|
-
elif play_subsystem and hasattr(play_subsystem, 'end_play'): # type: ignore[attr-defined]
|
|
288
|
-
play_subsystem.end_play() # type: ignore[attr-defined]
|
|
289
|
-
else:
|
|
290
|
-
record_warning('Unable to stop Play In Editor via modern subsystems; please stop PIE manually.')
|
|
291
|
-
time.sleep(0.5)
|
|
292
|
-
except Exception as pie_error:
|
|
293
|
-
record_warning(f"PIE stop check failed: {pie_error}")
|
|
294
|
-
|
|
295
|
-
# Main execution
|
|
296
|
-
success = False
|
|
297
|
-
error_msg = ""
|
|
298
|
-
new_asset = None
|
|
299
|
-
|
|
300
|
-
# Log the attempt
|
|
301
|
-
print("Setting up ragdoll for ${meshPath}")
|
|
302
|
-
record_detail("Setting up ragdoll for ${meshPath}")
|
|
303
|
-
|
|
304
|
-
asset_path = "${path}"
|
|
305
|
-
asset_name = "${sanitizedParams.name}"
|
|
306
|
-
full_path = f"{asset_path}/{asset_name}"
|
|
307
|
-
|
|
308
|
-
try:
|
|
309
|
-
# Check if already exists
|
|
310
|
-
if unreal.EditorAssetLibrary.does_asset_exist(full_path):
|
|
311
|
-
print(f"Physics asset already exists at {full_path}")
|
|
312
|
-
record_detail(f"Physics asset already exists at {full_path}")
|
|
313
|
-
existing = unreal.EditorAssetLibrary.load_asset(full_path)
|
|
314
|
-
if existing:
|
|
315
|
-
print(f"Loaded existing PhysicsAsset: {full_path}")
|
|
316
|
-
record_detail(f"Loaded existing PhysicsAsset: {full_path}")
|
|
317
|
-
success = True
|
|
318
|
-
result["existingAsset"] = True
|
|
319
|
-
result["message"] = f"Physics asset already exists at {full_path}"
|
|
320
|
-
else:
|
|
321
|
-
# Try to load skeletal mesh first - it's required
|
|
322
|
-
skeletal_mesh_path = "${meshPath}"
|
|
323
|
-
skeletal_mesh = None
|
|
324
|
-
|
|
325
|
-
if skeletal_mesh_path and skeletal_mesh_path != "None":
|
|
326
|
-
if unreal.EditorAssetLibrary.does_asset_exist(skeletal_mesh_path):
|
|
327
|
-
asset = unreal.EditorAssetLibrary.load_asset(skeletal_mesh_path)
|
|
328
|
-
if asset:
|
|
329
|
-
if isinstance(asset, unreal.SkeletalMesh):
|
|
330
|
-
skeletal_mesh = asset
|
|
331
|
-
print(f"Loaded skeletal mesh: {skeletal_mesh_path}")
|
|
332
|
-
record_detail(f"Loaded skeletal mesh: {skeletal_mesh_path}")
|
|
333
|
-
elif isinstance(asset, unreal.Skeleton):
|
|
334
|
-
error_msg = f"Provided path is a skeleton, not a skeletal mesh: {skeletal_mesh_path}"
|
|
335
|
-
print(f"Error: {error_msg}")
|
|
336
|
-
record_error(error_msg)
|
|
337
|
-
result["message"] = error_msg
|
|
338
|
-
print("Error: Physics assets require a skeletal mesh, not just a skeleton")
|
|
339
|
-
record_warning("Physics assets require a skeletal mesh, not just a skeleton")
|
|
340
|
-
else:
|
|
341
|
-
error_msg = f"Asset is not a skeletal mesh: {skeletal_mesh_path}"
|
|
342
|
-
print(f"Warning: {error_msg}")
|
|
343
|
-
record_warning(error_msg)
|
|
344
|
-
else:
|
|
345
|
-
error_msg = f"Skeletal mesh not found at {skeletal_mesh_path}"
|
|
346
|
-
print(f"Error: {error_msg}")
|
|
347
|
-
record_error(error_msg)
|
|
348
|
-
result["message"] = error_msg
|
|
349
|
-
|
|
350
|
-
if not skeletal_mesh:
|
|
351
|
-
if not error_msg:
|
|
352
|
-
error_msg = "Cannot create physics asset without a valid skeletal mesh"
|
|
353
|
-
print(f"Error: {error_msg}")
|
|
354
|
-
record_error(error_msg)
|
|
355
|
-
if not result["message"]:
|
|
356
|
-
result["message"] = error_msg
|
|
357
|
-
else:
|
|
358
|
-
# Create physics asset using a different approach
|
|
359
|
-
# Method 1: Direct creation with initialized factory
|
|
360
|
-
try:
|
|
361
|
-
factory = unreal.PhysicsAssetFactory()
|
|
362
|
-
|
|
363
|
-
# Ensure the directory exists
|
|
364
|
-
if not unreal.EditorAssetLibrary.does_directory_exist(asset_path):
|
|
365
|
-
unreal.EditorAssetLibrary.make_directory(asset_path)
|
|
366
|
-
|
|
367
|
-
# Alternative approach: Create physics asset from skeletal mesh
|
|
368
|
-
# This is the proper way in UE5
|
|
369
|
-
try:
|
|
370
|
-
# Try modern physics asset creation methods first
|
|
371
|
-
try:
|
|
372
|
-
# Method 1: Try using SkeletalMesh editor utilities if available
|
|
373
|
-
if hasattr(unreal, 'SkeletalMeshEditorSubsystem'):
|
|
374
|
-
skel_subsystem = unreal.get_editor_subsystem(unreal.SkeletalMeshEditorSubsystem)
|
|
375
|
-
if hasattr(skel_subsystem, 'create_physics_asset'):
|
|
376
|
-
physics_asset = skel_subsystem.create_physics_asset(skeletal_mesh)
|
|
377
|
-
else:
|
|
378
|
-
# Fallback to deprecated EditorSkeletalMeshLibrary
|
|
379
|
-
physics_asset = unreal.EditorSkeletalMeshLibrary.create_physics_asset(skeletal_mesh)
|
|
380
|
-
else:
|
|
381
|
-
physics_asset = unreal.EditorSkeletalMeshLibrary.create_physics_asset(skeletal_mesh)
|
|
382
|
-
except Exception as method1_modern_error:
|
|
383
|
-
record_warning(f"Modern creation path fallback: {method1_modern_error}")
|
|
384
|
-
# Final fallback to deprecated API
|
|
385
|
-
physics_asset = unreal.EditorSkeletalMeshLibrary.create_physics_asset(skeletal_mesh)
|
|
386
|
-
except Exception as e:
|
|
387
|
-
print(f"Physics asset creation failed: {str(e)}")
|
|
388
|
-
record_error(f"Physics asset creation failed: {str(e)}")
|
|
389
|
-
physics_asset = None
|
|
390
|
-
|
|
391
|
-
if physics_asset:
|
|
392
|
-
# Move/rename the physics asset to desired location
|
|
393
|
-
source_path = physics_asset.get_path_name()
|
|
394
|
-
if unreal.EditorAssetLibrary.rename_asset(source_path, full_path):
|
|
395
|
-
print(f"Successfully created and moved PhysicsAsset to {full_path}")
|
|
396
|
-
record_detail(f"Successfully created and moved PhysicsAsset to {full_path}")
|
|
397
|
-
new_asset = physics_asset
|
|
398
|
-
|
|
399
|
-
# Ensure persistence
|
|
400
|
-
if ensure_asset_persistence(full_path):
|
|
401
|
-
# Verify it was saved
|
|
402
|
-
if unreal.EditorAssetLibrary.does_asset_exist(full_path):
|
|
403
|
-
print(f"Verified PhysicsAsset exists after save: {full_path}")
|
|
404
|
-
record_detail(f"Verified PhysicsAsset exists after save: {full_path}")
|
|
405
|
-
success = True
|
|
406
|
-
result["message"] = f"Ragdoll physics setup completed for {asset_name}"
|
|
407
|
-
else:
|
|
408
|
-
error_msg = f"PhysicsAsset not found after save: {full_path}"
|
|
409
|
-
print(f"Warning: {error_msg}")
|
|
410
|
-
record_warning(error_msg)
|
|
411
|
-
else:
|
|
412
|
-
error_msg = "Failed to persist physics asset"
|
|
413
|
-
print(f"Warning: {error_msg}")
|
|
414
|
-
record_warning(error_msg)
|
|
415
|
-
else:
|
|
416
|
-
print(f"Created PhysicsAsset but couldn't move to {full_path}")
|
|
417
|
-
record_warning(f"Created PhysicsAsset but couldn't move to {full_path}")
|
|
418
|
-
# Still consider it a success if we created it
|
|
419
|
-
new_asset = physics_asset
|
|
420
|
-
success = True
|
|
421
|
-
result["message"] = f"Physics asset created but not moved to {full_path}"
|
|
422
|
-
else:
|
|
423
|
-
error_msg = "Failed to create PhysicsAsset from skeletal mesh"
|
|
424
|
-
print(error_msg)
|
|
425
|
-
record_error(error_msg)
|
|
426
|
-
new_asset = None
|
|
427
|
-
|
|
428
|
-
successMessage: \`Skeletal mesh discovery complete\`,
|
|
429
|
-
failureMessage: \`Failed to discover skeletal mesh\`
|
|
430
|
-
record_warning(f"Method 1 failed: {str(e)}")
|
|
431
|
-
|
|
432
|
-
# Method 2: Try older approach
|
|
433
|
-
try:
|
|
434
|
-
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
|
|
435
|
-
factory = unreal.PhysicsAssetFactory()
|
|
436
|
-
|
|
437
|
-
# Try to initialize factory with the skeletal mesh
|
|
438
|
-
factory.create_physics_asset_from_skeletal_mesh = skeletal_mesh
|
|
439
|
-
|
|
440
|
-
new_asset = asset_tools.create_asset(
|
|
441
|
-
asset_name=asset_name,
|
|
442
|
-
package_path=asset_path,
|
|
443
|
-
asset_class=unreal.PhysicsAsset,
|
|
444
|
-
factory=factory
|
|
445
|
-
)
|
|
446
|
-
|
|
447
|
-
if new_asset:
|
|
448
|
-
print(f"Successfully created PhysicsAsset at {full_path} (Method 2)")
|
|
449
|
-
record_detail(f"Successfully created PhysicsAsset at {full_path} (Method 2)")
|
|
450
|
-
# Ensure persistence
|
|
451
|
-
if ensure_asset_persistence(full_path):
|
|
452
|
-
success = True
|
|
453
|
-
result["message"] = f"Ragdoll physics setup completed for {asset_name}"
|
|
454
|
-
else:
|
|
455
|
-
record_warning("Persistence check failed after Method 2 creation")
|
|
456
|
-
except Exception as e2:
|
|
457
|
-
error_msg = f"Method 2 also failed: {str(e2)}"
|
|
458
|
-
print(error_msg)
|
|
459
|
-
record_error(error_msg)
|
|
460
|
-
new_asset = None
|
|
461
|
-
|
|
462
|
-
# Final check
|
|
463
|
-
if new_asset and not success:
|
|
464
|
-
# Try one more save
|
|
465
|
-
if ensure_asset_persistence(full_path):
|
|
466
|
-
if unreal.EditorAssetLibrary.does_asset_exist(full_path):
|
|
467
|
-
success = True
|
|
468
|
-
result["message"] = f"Ragdoll physics setup completed for {asset_name}"
|
|
469
|
-
else:
|
|
470
|
-
record_warning(f"Final existence check failed for {full_path}")
|
|
471
|
-
|
|
472
|
-
except Exception as e:
|
|
473
|
-
error_msg = str(e)
|
|
474
|
-
print(f"Error: {error_msg}")
|
|
475
|
-
record_error(error_msg)
|
|
476
|
-
import traceback
|
|
477
|
-
traceback.print_exc()
|
|
478
|
-
|
|
479
|
-
# Finalize result
|
|
480
|
-
result["success"] = bool(success)
|
|
481
|
-
result["path"] = full_path if success else None
|
|
482
|
-
|
|
483
|
-
if not result["message"]:
|
|
484
|
-
if success:
|
|
485
|
-
result["message"] = f"Ragdoll physics setup completed for {asset_name}"
|
|
486
|
-
elif error_msg:
|
|
487
|
-
result["message"] = error_msg
|
|
488
|
-
else:
|
|
489
|
-
result["message"] = "Failed to setup ragdoll"
|
|
490
|
-
|
|
491
|
-
if not success:
|
|
492
|
-
if not result["error"]:
|
|
493
|
-
result["error"] = error_msg or "Unknown error"
|
|
494
|
-
|
|
495
|
-
print('RESULT:' + json.dumps(result))
|
|
496
|
-
`;
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
// Execute Python and interpret response
|
|
173
|
+
// Use Automation Bridge for physics asset creation
|
|
174
|
+
if (!this.automationBridge) {
|
|
175
|
+
throw new Error('Automation Bridge not available. Physics asset creation requires plugin support.');
|
|
176
|
+
}
|
|
177
|
+
|
|
500
178
|
try {
|
|
501
|
-
const response = await this.
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
179
|
+
const response = await this.automationBridge.sendAutomationRequest('setup_ragdoll', {
|
|
180
|
+
meshPath,
|
|
181
|
+
physicsAssetName: sanitizedParams.name,
|
|
182
|
+
savePath: path,
|
|
183
|
+
blendWeight: params.blendWeight,
|
|
184
|
+
constraints: params.constraints
|
|
185
|
+
}, {
|
|
186
|
+
timeoutMs: 120000 // 2 minutes for complex physics asset creation
|
|
505
187
|
});
|
|
506
188
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
success: true;
|
|
513
|
-
message: string;
|
|
514
|
-
path: string;
|
|
515
|
-
existingAsset?: boolean;
|
|
516
|
-
warnings?: string[];
|
|
517
|
-
details?: string[];
|
|
518
|
-
} = {
|
|
519
|
-
success: true,
|
|
520
|
-
message: interpreted.message,
|
|
521
|
-
path: coerceString(interpreted.payload.path) ?? `${path}/${sanitizedParams.name}`
|
|
189
|
+
if (response.success === false) {
|
|
190
|
+
return {
|
|
191
|
+
success: false,
|
|
192
|
+
message: response.error || response.message || `Failed to setup ragdoll for ${sanitizedParams.name}`,
|
|
193
|
+
error: response.error || response.message || 'Failed to setup ragdoll'
|
|
522
194
|
};
|
|
523
|
-
|
|
524
|
-
if (interpreted.payload.existingAsset === true) {
|
|
525
|
-
successPayload.existingAsset = true;
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
if (warnings.length > 0) {
|
|
529
|
-
successPayload.warnings = warnings;
|
|
530
|
-
}
|
|
531
|
-
if (details.length > 0) {
|
|
532
|
-
successPayload.details = details;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
return successPayload;
|
|
536
195
|
}
|
|
537
196
|
|
|
538
|
-
const
|
|
539
|
-
|
|
197
|
+
const result = response.result as any;
|
|
540
198
|
return {
|
|
541
|
-
success:
|
|
542
|
-
message:
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
199
|
+
success: true,
|
|
200
|
+
message: response.message || `Ragdoll physics setup completed for ${sanitizedParams.name}`,
|
|
201
|
+
path: coerceString(result?.path) ?? coerceString(result?.physicsAssetPath) ?? `${path}/${sanitizedParams.name}`,
|
|
202
|
+
existingAsset: result?.existingAsset,
|
|
203
|
+
...(result || {})
|
|
546
204
|
};
|
|
547
205
|
} catch (error) {
|
|
548
206
|
return {
|
|
@@ -556,6 +214,7 @@ print('RESULT:' + json.dumps(result))
|
|
|
556
214
|
}
|
|
557
215
|
}
|
|
558
216
|
|
|
217
|
+
|
|
559
218
|
/**
|
|
560
219
|
* Create Physics Constraint
|
|
561
220
|
*/
|
|
@@ -577,17 +236,17 @@ print('RESULT:' + json.dumps(result))
|
|
|
577
236
|
// Spawn constraint actor
|
|
578
237
|
const spawnCmd = `spawnactor /Script/Engine.PhysicsConstraintActor ${params.location[0]} ${params.location[1]} ${params.location[2]}`;
|
|
579
238
|
await this.bridge.executeConsoleCommand(spawnCmd);
|
|
580
|
-
|
|
239
|
+
|
|
581
240
|
// Configure constraint
|
|
582
241
|
const commands = [
|
|
583
242
|
`SetConstraintActors ${params.name} ${params.actor1} ${params.actor2}`,
|
|
584
243
|
`SetConstraintType ${params.name} ${params.constraintType}`
|
|
585
244
|
];
|
|
586
|
-
|
|
245
|
+
|
|
587
246
|
if (params.breakThreshold) {
|
|
588
247
|
commands.push(`SetConstraintBreakThreshold ${params.name} ${params.breakThreshold}`);
|
|
589
248
|
}
|
|
590
|
-
|
|
249
|
+
|
|
591
250
|
if (params.limits) {
|
|
592
251
|
const limits = params.limits;
|
|
593
252
|
if (limits.swing1 !== undefined) {
|
|
@@ -603,12 +262,12 @@ print('RESULT:' + json.dumps(result))
|
|
|
603
262
|
commands.push(`SetConstraintLinear ${params.name} ${limits.linear}`);
|
|
604
263
|
}
|
|
605
264
|
}
|
|
606
|
-
|
|
265
|
+
|
|
607
266
|
await this.bridge.executeConsoleCommands(commands);
|
|
608
|
-
|
|
609
|
-
return {
|
|
610
|
-
success: true,
|
|
611
|
-
message: `Physics constraint ${params.name} created between ${params.actor1} and ${params.actor2}`
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
success: true,
|
|
270
|
+
message: `Physics constraint ${params.name} created between ${params.actor1} and ${params.actor2}`
|
|
612
271
|
};
|
|
613
272
|
} catch (err) {
|
|
614
273
|
return { success: false, error: `Failed to create constraint: ${err}` };
|
|
@@ -632,11 +291,11 @@ print('RESULT:' + json.dumps(result))
|
|
|
632
291
|
}) {
|
|
633
292
|
try {
|
|
634
293
|
const path = params.savePath || '/Game/Destruction';
|
|
635
|
-
|
|
294
|
+
|
|
636
295
|
const commands = [
|
|
637
296
|
`CreateGeometryCollection ${params.destructionName} ${params.meshPath} ${path}`
|
|
638
297
|
];
|
|
639
|
-
|
|
298
|
+
|
|
640
299
|
// Configure fracture
|
|
641
300
|
if (params.fractureSettings) {
|
|
642
301
|
const settings = params.fractureSettings;
|
|
@@ -644,21 +303,21 @@ print('RESULT:' + json.dumps(result))
|
|
|
644
303
|
`FractureGeometry ${params.destructionName} ${settings.cellCount} ${settings.minimumVolumeSize} ${settings.seed}`
|
|
645
304
|
);
|
|
646
305
|
}
|
|
647
|
-
|
|
306
|
+
|
|
648
307
|
// Set damage threshold
|
|
649
308
|
if (params.damageThreshold) {
|
|
650
309
|
commands.push(`SetDamageThreshold ${params.destructionName} ${params.damageThreshold}`);
|
|
651
310
|
}
|
|
652
|
-
|
|
311
|
+
|
|
653
312
|
// Set debris lifetime
|
|
654
313
|
if (params.debrisLifetime) {
|
|
655
314
|
commands.push(`SetDebrisLifetime ${params.destructionName} ${params.debrisLifetime}`);
|
|
656
315
|
}
|
|
657
|
-
|
|
316
|
+
|
|
658
317
|
await this.bridge.executeConsoleCommands(commands);
|
|
659
|
-
|
|
660
|
-
return {
|
|
661
|
-
success: true,
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
success: true,
|
|
662
321
|
message: `Chaos destruction ${params.destructionName} created`,
|
|
663
322
|
path: `${path}/${params.destructionName}`
|
|
664
323
|
};
|
|
@@ -689,58 +348,126 @@ print('RESULT:' + json.dumps(result))
|
|
|
689
348
|
gears: number[];
|
|
690
349
|
finalDriveRatio: number;
|
|
691
350
|
};
|
|
351
|
+
pluginDependencies?: string[];
|
|
692
352
|
}) {
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
353
|
+
// Plugin check removed as ensurePluginsEnabled is deprecated.
|
|
354
|
+
// Users should ensure required plugins are enabled in the editor.
|
|
355
|
+
|
|
356
|
+
const rawParams: any = params as any;
|
|
357
|
+
|
|
358
|
+
const pluginDeps: string[] | undefined = Array.isArray(params.pluginDependencies) && params.pluginDependencies.length > 0
|
|
359
|
+
? params.pluginDependencies
|
|
360
|
+
: (Array.isArray(rawParams.plugins) && rawParams.plugins.length > 0 ? rawParams.plugins : undefined);
|
|
361
|
+
|
|
362
|
+
if (pluginDeps && pluginDeps.length > 0) {
|
|
363
|
+
return {
|
|
364
|
+
success: false,
|
|
365
|
+
error: 'MISSING_ENGINE_PLUGINS',
|
|
366
|
+
missingPlugins: pluginDeps,
|
|
367
|
+
message: `Required engine plugins not enabled: ${pluginDeps.join(', ')}`
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const warnings: string[] = [];
|
|
372
|
+
|
|
373
|
+
const hasExplicitEmptyWheels = Array.isArray(params.wheels) && params.wheels.length === 0;
|
|
374
|
+
|
|
375
|
+
const effectiveVehicleType = typeof params.vehicleType === 'string' && params.vehicleType.trim().length > 0
|
|
376
|
+
? params.vehicleType
|
|
377
|
+
: 'Car';
|
|
378
|
+
|
|
379
|
+
const commands = [
|
|
380
|
+
`CreateVehicle ${params.vehicleName} ${effectiveVehicleType}`
|
|
381
|
+
];
|
|
382
|
+
|
|
383
|
+
// Configure wheels when provided
|
|
384
|
+
if (Array.isArray(params.wheels) && params.wheels.length > 0) {
|
|
385
|
+
for (const wheel of params.wheels) {
|
|
386
|
+
commands.push(
|
|
387
|
+
`AddVehicleWheel ${params.vehicleName} ${wheel.name} ${wheel.radius} ${wheel.width} ${wheel.mass}`
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
if (wheel.isSteering) {
|
|
391
|
+
commands.push(`SetWheelSteering ${params.vehicleName} ${wheel.name} true`);
|
|
711
392
|
}
|
|
393
|
+
if (wheel.isDriving) {
|
|
394
|
+
commands.push(`SetWheelDriving ${params.vehicleName} ${wheel.name} true`);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Configure engine (optional). Clamp negative RPMs and tolerate missing torqueCurve.
|
|
400
|
+
const effectiveEngine = params.engine ?? ((typeof rawParams.maxRPM === 'number' || Array.isArray(rawParams.torqueCurve))
|
|
401
|
+
? { maxRPM: rawParams.maxRPM, torqueCurve: rawParams.torqueCurve }
|
|
402
|
+
: undefined);
|
|
403
|
+
|
|
404
|
+
if (effectiveEngine) {
|
|
405
|
+
let maxRPM = typeof effectiveEngine.maxRPM === 'number' ? effectiveEngine.maxRPM : 0;
|
|
406
|
+
if (maxRPM < 0) {
|
|
407
|
+
maxRPM = 0;
|
|
408
|
+
warnings.push('Engine maxRPM was negative and has been clamped to 0.');
|
|
712
409
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
410
|
+
commands.push(`SetEngineMaxRPM ${params.vehicleName} ${maxRPM}`);
|
|
411
|
+
|
|
412
|
+
const rawCurve = Array.isArray(effectiveEngine.torqueCurve) ? effectiveEngine.torqueCurve : [];
|
|
413
|
+
for (const point of rawCurve) {
|
|
414
|
+
let rpm: number | undefined;
|
|
415
|
+
let torque: number | undefined;
|
|
416
|
+
|
|
417
|
+
if (Array.isArray(point) && point.length >= 2) {
|
|
418
|
+
rpm = Number(point[0]);
|
|
419
|
+
torque = Number(point[1]);
|
|
420
|
+
} else if (point && typeof point === 'object') {
|
|
421
|
+
const anyPoint: any = point;
|
|
422
|
+
rpm = typeof anyPoint.rpm === 'number' ? anyPoint.rpm : undefined;
|
|
423
|
+
torque = typeof anyPoint.torque === 'number' ? anyPoint.torque : undefined;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (typeof rpm === 'number' && typeof torque === 'number') {
|
|
719
427
|
commands.push(`AddTorqueCurvePoint ${params.vehicleName} ${rpm} ${torque}`);
|
|
720
428
|
}
|
|
721
429
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Configure transmission
|
|
433
|
+
if (params.transmission) {
|
|
434
|
+
if (Array.isArray(params.transmission.gears)) {
|
|
725
435
|
for (let i = 0; i < params.transmission.gears.length; i++) {
|
|
726
436
|
commands.push(
|
|
727
437
|
`SetGearRatio ${params.vehicleName} ${i} ${params.transmission.gears[i]}`
|
|
728
438
|
);
|
|
729
439
|
}
|
|
440
|
+
}
|
|
441
|
+
if (typeof params.transmission.finalDriveRatio === 'number') {
|
|
730
442
|
commands.push(
|
|
731
443
|
`SetFinalDriveRatio ${params.vehicleName} ${params.transmission.finalDriveRatio}`
|
|
732
444
|
);
|
|
733
445
|
}
|
|
734
|
-
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
try {
|
|
735
449
|
await this.bridge.executeConsoleCommands(commands);
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
450
|
+
} catch (_error) {
|
|
451
|
+
// If vehicle console commands fail (e.g., `Command not executed`), treat this as
|
|
452
|
+
// a best-effort configuration that falls back to engine defaults.
|
|
453
|
+
if (warnings.length === 0) {
|
|
454
|
+
warnings.push('Vehicle configuration commands could not be executed; using engine defaults.');
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (hasExplicitEmptyWheels) {
|
|
459
|
+
warnings.push('No wheels specified; using default wheels from vehicle preset.');
|
|
743
460
|
}
|
|
461
|
+
|
|
462
|
+
if (warnings.length === 0) {
|
|
463
|
+
warnings.push('Verify wheel class assignments and offsets in the vehicle movement component to ensure they match your project defaults.');
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return {
|
|
467
|
+
success: true,
|
|
468
|
+
message: `Vehicle ${params.vehicleName} configured`,
|
|
469
|
+
warnings
|
|
470
|
+
};
|
|
744
471
|
}
|
|
745
472
|
|
|
746
473
|
/**
|
|
@@ -753,148 +480,37 @@ print('RESULT:' + json.dumps(result))
|
|
|
753
480
|
boneName?: string;
|
|
754
481
|
isLocal?: boolean;
|
|
755
482
|
}) {
|
|
483
|
+
if (!this.automationBridge) {
|
|
484
|
+
throw new Error('Automation Bridge not available. Physics force application requires plugin support.');
|
|
485
|
+
}
|
|
486
|
+
|
|
756
487
|
try {
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
try:
|
|
766
|
-
les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
|
|
767
|
-
if les and les.is_in_play_in_editor():
|
|
768
|
-
result["message"] = "Cannot apply physics while in Play In Editor mode. Please stop PIE first."
|
|
769
|
-
print(f"RESULT:{json.dumps(result)}")
|
|
770
|
-
# Exit early from this script
|
|
771
|
-
raise SystemExit(0)
|
|
772
|
-
except SystemExit:
|
|
773
|
-
# Re-raise the SystemExit to exit properly
|
|
774
|
-
raise
|
|
775
|
-
except:
|
|
776
|
-
pass # Continue if we can't check PIE state
|
|
777
|
-
|
|
778
|
-
try:
|
|
779
|
-
actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
|
|
780
|
-
actors = actor_subsystem.get_all_level_actors()
|
|
781
|
-
search_name = "${params.actorName}"
|
|
782
|
-
|
|
783
|
-
for actor in actors:
|
|
784
|
-
if actor:
|
|
785
|
-
# Check both actor name and label with case-insensitive partial matching
|
|
786
|
-
actor_name = actor.get_name()
|
|
787
|
-
actor_label = actor.get_actor_label()
|
|
788
|
-
|
|
789
|
-
if (search_name.lower() in actor_label.lower() or
|
|
790
|
-
actor_label.lower().startswith(search_name.lower() + "_") or
|
|
791
|
-
actor_label.lower() == search_name.lower() or
|
|
792
|
-
actor_name.lower() == search_name.lower()):
|
|
793
|
-
|
|
794
|
-
result["actor_found"] = True
|
|
795
|
-
# Get the primitive component if it exists
|
|
796
|
-
root = actor.get_editor_property('root_component')
|
|
797
|
-
|
|
798
|
-
if root and isinstance(root, unreal.PrimitiveComponent):
|
|
799
|
-
# Check if the component is static or movable
|
|
800
|
-
mobility = root.get_editor_property('mobility')
|
|
801
|
-
if mobility == unreal.ComponentMobility.STATIC:
|
|
802
|
-
# Try to set to movable first
|
|
803
|
-
try:
|
|
804
|
-
root.set_editor_property('mobility', unreal.ComponentMobility.MOVABLE)
|
|
805
|
-
except:
|
|
806
|
-
result["message"] = f"Actor {actor_label} has static mobility and cannot simulate physics"
|
|
807
|
-
break
|
|
808
|
-
|
|
809
|
-
# Ensure physics is enabled
|
|
810
|
-
try:
|
|
811
|
-
root.set_simulate_physics(True)
|
|
812
|
-
result["physics_enabled"] = True
|
|
813
|
-
except Exception as physics_err:
|
|
814
|
-
# If we can't enable physics, try applying force anyway (some actors respond without physics sim)
|
|
815
|
-
result["physics_enabled"] = False
|
|
816
|
-
|
|
817
|
-
force = unreal.Vector(${params.vector[0]}, ${params.vector[1]}, ${params.vector[2]})
|
|
818
|
-
|
|
819
|
-
if "${params.forceType}" == "Force":
|
|
820
|
-
root.add_force(force, 'None', False)
|
|
821
|
-
result["success"] = True
|
|
822
|
-
result["message"] = f"Applied Force to {actor_label}: {force}"
|
|
823
|
-
elif "${params.forceType}" == "Impulse":
|
|
824
|
-
root.add_impulse(force, 'None', False)
|
|
825
|
-
result["success"] = True
|
|
826
|
-
result["message"] = f"Applied Impulse to {actor_label}: {force}"
|
|
827
|
-
elif "${params.forceType}" == "Velocity":
|
|
828
|
-
root.set_physics_linear_velocity(force)
|
|
829
|
-
result["success"] = True
|
|
830
|
-
result["message"] = f"Set Velocity on {actor_label}: {force}"
|
|
831
|
-
elif "${params.forceType}" == "Torque":
|
|
832
|
-
root.add_torque_in_radians(force, 'None', False)
|
|
833
|
-
result["success"] = True
|
|
834
|
-
result["message"] = f"Applied Torque to {actor_label}: {force}"
|
|
835
|
-
else:
|
|
836
|
-
result["message"] = f"Actor {actor_label} doesn't have a physics-enabled component"
|
|
837
|
-
break
|
|
838
|
-
|
|
839
|
-
if not result["actor_found"]:
|
|
840
|
-
result["message"] = f"Actor not found: {search_name}"
|
|
841
|
-
# List actors with physics enabled for debugging
|
|
842
|
-
physics_actors = []
|
|
843
|
-
for actor in actors[:20]:
|
|
844
|
-
if actor:
|
|
845
|
-
label = actor.get_actor_label()
|
|
846
|
-
if "mesh" in label.lower() or "cube" in label.lower() or "static" in label.lower():
|
|
847
|
-
physics_actors.append(label)
|
|
848
|
-
if physics_actors:
|
|
849
|
-
result["available_actors"] = physics_actors
|
|
850
|
-
|
|
851
|
-
except Exception as e:
|
|
852
|
-
result["message"] = f"Error applying force: {e}"
|
|
853
|
-
|
|
854
|
-
print(f"RESULT:{json.dumps(result)}")
|
|
855
|
-
`.trim();
|
|
856
|
-
|
|
857
|
-
const response = await this.bridge.executePython(pythonCode);
|
|
858
|
-
const interpreted = interpretStandardResult(response, {
|
|
859
|
-
successMessage: `Applied ${params.forceType} to ${params.actorName}`,
|
|
860
|
-
failureMessage: 'Force application failed'
|
|
488
|
+
const response = await this.automationBridge.sendAutomationRequest('apply_force', {
|
|
489
|
+
actorName: params.actorName,
|
|
490
|
+
forceType: params.forceType,
|
|
491
|
+
vector: params.vector,
|
|
492
|
+
boneName: params.boneName,
|
|
493
|
+
isLocal: params.isLocal
|
|
494
|
+
}, {
|
|
495
|
+
timeoutMs: 30000
|
|
861
496
|
});
|
|
862
497
|
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
if (interpreted.success) {
|
|
866
|
-
return {
|
|
867
|
-
success: true,
|
|
868
|
-
message: interpreted.message,
|
|
869
|
-
availableActors,
|
|
870
|
-
details: interpreted.details
|
|
871
|
-
};
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
const fallbackText = bestEffortInterpretedText(interpreted) ?? '';
|
|
875
|
-
if (/Applied/i.test(fallbackText)) {
|
|
876
|
-
return {
|
|
877
|
-
success: true,
|
|
878
|
-
message: fallbackText || interpreted.message,
|
|
879
|
-
availableActors,
|
|
880
|
-
details: interpreted.details
|
|
881
|
-
};
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
if (/not found/i.test(fallbackText) || /error/i.test(fallbackText)) {
|
|
498
|
+
if (response.success === false) {
|
|
499
|
+
const result = response.result as any;
|
|
885
500
|
return {
|
|
886
501
|
success: false,
|
|
887
|
-
error:
|
|
888
|
-
availableActors,
|
|
889
|
-
details:
|
|
502
|
+
error: response.error || response.message || 'Force application failed',
|
|
503
|
+
availableActors: result?.available_actors ? coerceStringArray(result.available_actors) : undefined,
|
|
504
|
+
details: result?.details
|
|
890
505
|
};
|
|
891
506
|
}
|
|
892
507
|
|
|
508
|
+
const result = response.result as any;
|
|
893
509
|
return {
|
|
894
|
-
success:
|
|
895
|
-
|
|
896
|
-
availableActors,
|
|
897
|
-
|
|
510
|
+
success: true,
|
|
511
|
+
message: response.message || `Applied ${params.forceType} to ${params.actorName}`,
|
|
512
|
+
availableActors: result?.available_actors ? coerceStringArray(result.available_actors) : undefined,
|
|
513
|
+
...(result || {})
|
|
898
514
|
};
|
|
899
515
|
} catch (err) {
|
|
900
516
|
return { success: false, error: `Failed to apply force: ${err}` };
|
|
@@ -921,10 +537,10 @@ print(f"RESULT:{json.dumps(result)}")
|
|
|
921
537
|
`EnableClothSimulation ${params.meshName}`,
|
|
922
538
|
`SetClothPreset ${params.meshName} ${params.clothPreset}`
|
|
923
539
|
];
|
|
924
|
-
|
|
540
|
+
|
|
925
541
|
if (params.clothPreset === 'Custom' && params.customSettings) {
|
|
926
542
|
const settings = params.customSettings;
|
|
927
|
-
|
|
543
|
+
|
|
928
544
|
if (settings.stiffness !== undefined) {
|
|
929
545
|
commands.push(`SetClothStiffness ${params.meshName} ${settings.stiffness}`);
|
|
930
546
|
}
|
|
@@ -945,12 +561,12 @@ print(f"RESULT:{json.dumps(result)}")
|
|
|
945
561
|
commands.push(`SetClothWind ${params.meshName} ${wind[0]} ${wind[1]} ${wind[2]}`);
|
|
946
562
|
}
|
|
947
563
|
}
|
|
948
|
-
|
|
564
|
+
|
|
949
565
|
await this.bridge.executeConsoleCommands(commands);
|
|
950
|
-
|
|
951
|
-
return {
|
|
952
|
-
success: true,
|
|
953
|
-
message: `Cloth simulation enabled for ${params.meshName}`
|
|
566
|
+
|
|
567
|
+
return {
|
|
568
|
+
success: true,
|
|
569
|
+
message: `Cloth simulation enabled for ${params.meshName}`
|
|
954
570
|
};
|
|
955
571
|
} catch (err) {
|
|
956
572
|
return { success: false, error: `Failed to setup cloth: ${err}` };
|
|
@@ -976,14 +592,14 @@ print(f"RESULT:{json.dumps(result)}")
|
|
|
976
592
|
try {
|
|
977
593
|
const locStr = `${params.location[0]} ${params.location[1]} ${params.location[2]}`;
|
|
978
594
|
const volStr = `${params.volume[0]} ${params.volume[1]} ${params.volume[2]}`;
|
|
979
|
-
|
|
595
|
+
|
|
980
596
|
const commands = [
|
|
981
597
|
`CreateFluidSimulation ${params.name} ${params.fluidType} ${locStr} ${volStr}`
|
|
982
598
|
];
|
|
983
|
-
|
|
599
|
+
|
|
984
600
|
if (params.customSettings) {
|
|
985
601
|
const settings = params.customSettings;
|
|
986
|
-
|
|
602
|
+
|
|
987
603
|
if (settings.viscosity !== undefined) {
|
|
988
604
|
commands.push(`SetFluidViscosity ${params.name} ${settings.viscosity}`);
|
|
989
605
|
}
|
|
@@ -1003,16 +619,55 @@ print(f"RESULT:{json.dumps(result)}")
|
|
|
1003
619
|
);
|
|
1004
620
|
}
|
|
1005
621
|
}
|
|
1006
|
-
|
|
622
|
+
|
|
1007
623
|
await this.bridge.executeConsoleCommands(commands);
|
|
1008
|
-
|
|
1009
|
-
return {
|
|
1010
|
-
success: true,
|
|
1011
|
-
message: `Fluid simulation ${params.name} created`
|
|
624
|
+
|
|
625
|
+
return {
|
|
626
|
+
success: true,
|
|
627
|
+
message: `Fluid simulation ${params.name} created`
|
|
1012
628
|
};
|
|
1013
629
|
} catch (err) {
|
|
1014
630
|
return { success: false, error: `Failed to create fluid simulation: ${err}` };
|
|
1015
631
|
}
|
|
1016
632
|
}
|
|
1017
633
|
|
|
634
|
+
/**
|
|
635
|
+
* Setup Physics Simulation (Create Physics Asset)
|
|
636
|
+
*/
|
|
637
|
+
async setupPhysicsSimulation(params: {
|
|
638
|
+
meshPath?: string;
|
|
639
|
+
skeletonPath?: string;
|
|
640
|
+
physicsAssetName?: string;
|
|
641
|
+
savePath?: string;
|
|
642
|
+
}) {
|
|
643
|
+
if (!this.automationBridge) {
|
|
644
|
+
throw new Error('Automation Bridge not available. Physics asset creation requires plugin support.');
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
try {
|
|
648
|
+
const response = await this.automationBridge.sendAutomationRequest('animation_physics', {
|
|
649
|
+
action: 'setup_physics_simulation',
|
|
650
|
+
...params
|
|
651
|
+
}, {
|
|
652
|
+
timeoutMs: 60000
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
if (response.success === false) {
|
|
656
|
+
return {
|
|
657
|
+
success: false,
|
|
658
|
+
message: response.error || response.message || 'Failed to setup physics simulation',
|
|
659
|
+
error: response.error || response.message
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
return {
|
|
664
|
+
success: true,
|
|
665
|
+
message: response.message || 'Physics simulation setup completed',
|
|
666
|
+
...(response.result || {})
|
|
667
|
+
};
|
|
668
|
+
} catch (err) {
|
|
669
|
+
return { success: false, error: `Failed to setup physics simulation: ${err}` };
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
1018
673
|
}
|