unreal-engine-mcp-server 0.4.7 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +26 -0
- package/.env.production +38 -7
- package/.eslintrc.json +0 -54
- package/.eslintrc.override.json +8 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +94 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +56 -0
- package/.github/copilot-instructions.md +478 -45
- package/.github/dependabot.yml +19 -0
- package/.github/labeler.yml +24 -0
- package/.github/labels.yml +70 -0
- package/.github/pull_request_template.md +42 -0
- package/.github/release-drafter-config.yml +51 -0
- package/.github/workflows/auto-merge.yml +38 -0
- package/.github/workflows/ci.yml +38 -0
- package/.github/workflows/dependency-review.yml +17 -0
- package/.github/workflows/gemini-issue-triage.yml +172 -0
- package/.github/workflows/greetings.yml +27 -0
- package/.github/workflows/labeler.yml +17 -0
- package/.github/workflows/links.yml +80 -0
- package/.github/workflows/pr-size-labeler.yml +137 -0
- package/.github/workflows/publish-mcp.yml +13 -7
- package/.github/workflows/release-drafter.yml +23 -0
- package/.github/workflows/release.yml +112 -0
- package/.github/workflows/semantic-pull-request.yml +35 -0
- package/.github/workflows/smoke-test.yml +36 -0
- package/.github/workflows/stale.yml +28 -0
- package/CHANGELOG.md +338 -31
- package/CONTRIBUTING.md +140 -0
- package/GEMINI.md +115 -0
- package/Public/Plugin_setup_guide.mp4 +0 -0
- package/README.md +189 -128
- package/claude_desktop_config_example.json +7 -6
- package/dist/automation/bridge.d.ts +50 -0
- package/dist/automation/bridge.js +452 -0
- package/dist/automation/connection-manager.d.ts +23 -0
- package/dist/automation/connection-manager.js +107 -0
- package/dist/automation/handshake.d.ts +11 -0
- package/dist/automation/handshake.js +89 -0
- package/dist/automation/index.d.ts +3 -0
- package/dist/automation/index.js +3 -0
- package/dist/automation/message-handler.d.ts +12 -0
- package/dist/automation/message-handler.js +149 -0
- package/dist/automation/request-tracker.d.ts +25 -0
- package/dist/automation/request-tracker.js +98 -0
- package/dist/automation/types.d.ts +130 -0
- package/dist/automation/types.js +2 -0
- package/dist/cli.js +32 -5
- package/dist/config.d.ts +26 -0
- package/dist/config.js +59 -0
- package/dist/constants.d.ts +16 -0
- package/dist/constants.js +16 -0
- package/dist/graphql/loaders.d.ts +64 -0
- package/dist/graphql/loaders.js +117 -0
- package/dist/graphql/resolvers.d.ts +268 -0
- package/dist/graphql/resolvers.js +746 -0
- package/dist/graphql/schema.d.ts +5 -0
- package/dist/graphql/schema.js +437 -0
- package/dist/graphql/server.d.ts +26 -0
- package/dist/graphql/server.js +117 -0
- package/dist/graphql/types.d.ts +9 -0
- package/dist/graphql/types.js +2 -0
- package/dist/handlers/resource-handlers.d.ts +20 -0
- package/dist/handlers/resource-handlers.js +180 -0
- package/dist/index.d.ts +33 -18
- package/dist/index.js +130 -619
- package/dist/resources/actors.d.ts +17 -12
- package/dist/resources/actors.js +56 -76
- package/dist/resources/assets.d.ts +6 -14
- package/dist/resources/assets.js +115 -147
- package/dist/resources/levels.d.ts +13 -13
- package/dist/resources/levels.js +25 -34
- package/dist/server/resource-registry.d.ts +20 -0
- package/dist/server/resource-registry.js +37 -0
- package/dist/server/tool-registry.d.ts +23 -0
- package/dist/server/tool-registry.js +322 -0
- package/dist/server-setup.d.ts +20 -0
- package/dist/server-setup.js +71 -0
- package/dist/services/health-monitor.d.ts +34 -0
- package/dist/services/health-monitor.js +105 -0
- package/dist/services/metrics-server.d.ts +11 -0
- package/dist/services/metrics-server.js +105 -0
- package/dist/tools/actors.d.ts +163 -9
- package/dist/tools/actors.js +356 -311
- package/dist/tools/animation.d.ts +135 -4
- package/dist/tools/animation.js +510 -411
- package/dist/tools/assets.d.ts +75 -29
- package/dist/tools/assets.js +265 -284
- package/dist/tools/audio.d.ts +102 -42
- package/dist/tools/audio.js +272 -685
- package/dist/tools/base-tool.d.ts +17 -0
- package/dist/tools/base-tool.js +46 -0
- package/dist/tools/behavior-tree.d.ts +94 -0
- package/dist/tools/behavior-tree.js +39 -0
- package/dist/tools/blueprint.d.ts +208 -126
- package/dist/tools/blueprint.js +685 -832
- package/dist/tools/consolidated-tool-definitions.d.ts +5462 -1781
- package/dist/tools/consolidated-tool-definitions.js +829 -496
- package/dist/tools/consolidated-tool-handlers.d.ts +2 -1
- package/dist/tools/consolidated-tool-handlers.js +198 -1027
- package/dist/tools/debug.d.ts +143 -85
- package/dist/tools/debug.js +234 -180
- package/dist/tools/dynamic-handler-registry.d.ts +13 -0
- package/dist/tools/dynamic-handler-registry.js +23 -0
- package/dist/tools/editor.d.ts +30 -83
- package/dist/tools/editor.js +247 -244
- package/dist/tools/engine.d.ts +10 -4
- package/dist/tools/engine.js +13 -5
- package/dist/tools/environment.d.ts +30 -0
- package/dist/tools/environment.js +267 -0
- package/dist/tools/foliage.d.ts +65 -99
- package/dist/tools/foliage.js +221 -331
- package/dist/tools/handlers/actor-handlers.d.ts +3 -0
- package/dist/tools/handlers/actor-handlers.js +227 -0
- package/dist/tools/handlers/animation-handlers.d.ts +3 -0
- package/dist/tools/handlers/animation-handlers.js +185 -0
- package/dist/tools/handlers/argument-helper.d.ts +16 -0
- package/dist/tools/handlers/argument-helper.js +80 -0
- package/dist/tools/handlers/asset-handlers.d.ts +3 -0
- package/dist/tools/handlers/asset-handlers.js +496 -0
- package/dist/tools/handlers/audio-handlers.d.ts +3 -0
- package/dist/tools/handlers/audio-handlers.js +166 -0
- package/dist/tools/handlers/blueprint-handlers.d.ts +4 -0
- package/dist/tools/handlers/blueprint-handlers.js +358 -0
- package/dist/tools/handlers/common-handlers.d.ts +14 -0
- package/dist/tools/handlers/common-handlers.js +56 -0
- package/dist/tools/handlers/editor-handlers.d.ts +3 -0
- package/dist/tools/handlers/editor-handlers.js +119 -0
- package/dist/tools/handlers/effect-handlers.d.ts +3 -0
- package/dist/tools/handlers/effect-handlers.js +171 -0
- package/dist/tools/handlers/environment-handlers.d.ts +3 -0
- package/dist/tools/handlers/environment-handlers.js +170 -0
- package/dist/tools/handlers/graph-handlers.d.ts +3 -0
- package/dist/tools/handlers/graph-handlers.js +90 -0
- package/dist/tools/handlers/input-handlers.d.ts +3 -0
- package/dist/tools/handlers/input-handlers.js +21 -0
- package/dist/tools/handlers/inspect-handlers.d.ts +3 -0
- package/dist/tools/handlers/inspect-handlers.js +383 -0
- package/dist/tools/handlers/level-handlers.d.ts +3 -0
- package/dist/tools/handlers/level-handlers.js +237 -0
- package/dist/tools/handlers/lighting-handlers.d.ts +3 -0
- package/dist/tools/handlers/lighting-handlers.js +144 -0
- package/dist/tools/handlers/performance-handlers.d.ts +3 -0
- package/dist/tools/handlers/performance-handlers.js +130 -0
- package/dist/tools/handlers/pipeline-handlers.d.ts +3 -0
- package/dist/tools/handlers/pipeline-handlers.js +110 -0
- package/dist/tools/handlers/sequence-handlers.d.ts +3 -0
- package/dist/tools/handlers/sequence-handlers.js +376 -0
- package/dist/tools/handlers/system-handlers.d.ts +4 -0
- package/dist/tools/handlers/system-handlers.js +506 -0
- package/dist/tools/input.d.ts +19 -0
- package/dist/tools/input.js +89 -0
- package/dist/tools/introspection.d.ts +103 -40
- package/dist/tools/introspection.js +425 -568
- package/dist/tools/landscape.d.ts +54 -93
- package/dist/tools/landscape.js +284 -409
- package/dist/tools/level.d.ts +66 -27
- package/dist/tools/level.js +647 -675
- package/dist/tools/lighting.d.ts +77 -38
- package/dist/tools/lighting.js +445 -943
- package/dist/tools/logs.d.ts +3 -3
- package/dist/tools/logs.js +5 -57
- package/dist/tools/materials.d.ts +91 -24
- package/dist/tools/materials.js +194 -118
- package/dist/tools/niagara.d.ts +149 -39
- package/dist/tools/niagara.js +267 -182
- package/dist/tools/performance.d.ts +27 -13
- package/dist/tools/performance.js +203 -122
- package/dist/tools/physics.d.ts +32 -77
- package/dist/tools/physics.js +175 -582
- package/dist/tools/property-dictionary.d.ts +13 -0
- package/dist/tools/property-dictionary.js +82 -0
- package/dist/tools/sequence.d.ts +85 -60
- package/dist/tools/sequence.js +208 -747
- package/dist/tools/tool-definition-utils.d.ts +59 -0
- package/dist/tools/tool-definition-utils.js +35 -0
- package/dist/tools/ui.d.ts +64 -34
- package/dist/tools/ui.js +134 -214
- package/dist/types/automation-responses.d.ts +115 -0
- package/dist/types/automation-responses.js +2 -0
- package/dist/types/env.d.ts +0 -3
- package/dist/types/env.js +0 -7
- package/dist/types/responses.d.ts +249 -0
- package/dist/types/responses.js +2 -0
- package/dist/types/tool-interfaces.d.ts +898 -0
- package/dist/types/tool-interfaces.js +2 -0
- package/dist/types/tool-types.d.ts +183 -19
- package/dist/types/tool-types.js +0 -4
- package/dist/unreal-bridge.d.ts +24 -131
- package/dist/unreal-bridge.js +364 -1506
- package/dist/utils/command-validator.d.ts +9 -0
- package/dist/utils/command-validator.js +68 -0
- package/dist/utils/elicitation.d.ts +1 -1
- package/dist/utils/elicitation.js +12 -15
- package/dist/utils/error-handler.d.ts +2 -51
- package/dist/utils/error-handler.js +11 -87
- package/dist/utils/ini-reader.d.ts +3 -0
- package/dist/utils/ini-reader.js +69 -0
- package/dist/utils/logger.js +9 -6
- package/dist/utils/normalize.d.ts +3 -0
- package/dist/utils/normalize.js +56 -0
- package/dist/utils/path-security.d.ts +2 -0
- package/dist/utils/path-security.js +24 -0
- package/dist/utils/response-factory.d.ts +7 -0
- package/dist/utils/response-factory.js +27 -0
- package/dist/utils/response-validator.d.ts +3 -24
- package/dist/utils/response-validator.js +130 -81
- package/dist/utils/result-helpers.d.ts +4 -5
- package/dist/utils/result-helpers.js +15 -16
- package/dist/utils/safe-json.js +5 -11
- package/dist/utils/unreal-command-queue.d.ts +24 -0
- package/dist/utils/unreal-command-queue.js +120 -0
- package/dist/utils/validation.d.ts +0 -40
- package/dist/utils/validation.js +1 -78
- package/dist/wasm/index.d.ts +70 -0
- package/dist/wasm/index.js +535 -0
- package/docs/GraphQL-API.md +888 -0
- package/docs/Migration-Guide-v0.5.0.md +684 -0
- package/docs/Roadmap.md +53 -0
- package/docs/WebAssembly-Integration.md +628 -0
- package/docs/editor-plugin-extension.md +370 -0
- package/docs/handler-mapping.md +242 -0
- package/docs/native-automation-progress.md +128 -0
- package/docs/testing-guide.md +423 -0
- package/mcp-config-example.json +6 -6
- package/package.json +67 -28
- package/plugins/McpAutomationBridge/Config/FilterPlugin.ini +8 -0
- package/plugins/McpAutomationBridge/McpAutomationBridge.uplugin +64 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/McpAutomationBridge.Build.cs +189 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.cpp +22 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.h +30 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeHelpers.h +1983 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeModule.cpp +72 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSettings.cpp +46 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +581 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +2394 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetQueryHandlers.cpp +300 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetWorkflowHandlers.cpp +2807 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AudioHandlers.cpp +1087 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BehaviorTreeHandlers.cpp +488 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.cpp +643 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.h +31 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +1184 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +5652 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers_List.cpp +152 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ControlHandlers.cpp +2614 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_DebugHandlers.cpp +42 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EditorFunctionHandlers.cpp +1237 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +1701 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +2145 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_FoliageHandlers.cpp +954 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InputHandlers.cpp +209 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InsightsHandlers.cpp +41 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LandscapeHandlers.cpp +1164 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LevelHandlers.cpp +762 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +634 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LogHandlers.cpp +136 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_MaterialGraphHandlers.cpp +494 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraGraphHandlers.cpp +278 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraHandlers.cpp +625 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PerformanceHandlers.cpp +401 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PipelineHandlers.cpp +67 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +735 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PropertyHandlers.cpp +2634 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_RenderHandlers.cpp +189 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.cpp +917 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.h +39 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +2670 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequencerHandlers.cpp +519 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_TestHandlers.cpp +38 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_UiHandlers.cpp +668 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_WorldPartitionHandlers.cpp +346 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +1330 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.h +149 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +783 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSettings.h +115 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSubsystem.h +796 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpConnectionManager.h +117 -0
- package/scripts/check-unreal-connection.mjs +19 -0
- package/scripts/clean-tmp.js +23 -0
- package/scripts/patch-wasm.js +26 -0
- package/scripts/run-all-tests.mjs +136 -0
- package/scripts/smoke-test.ts +94 -0
- package/scripts/sync-mcp-plugin.js +143 -0
- package/scripts/test-no-plugin-alternates.mjs +113 -0
- package/scripts/validate-server.js +46 -0
- package/scripts/verify-automation-bridge.js +200 -0
- package/server.json +58 -21
- package/src/automation/bridge.ts +558 -0
- package/src/automation/connection-manager.ts +130 -0
- package/src/automation/handshake.ts +99 -0
- package/src/automation/index.ts +2 -0
- package/src/automation/message-handler.ts +167 -0
- package/src/automation/request-tracker.ts +123 -0
- package/src/automation/types.ts +107 -0
- package/src/cli.ts +33 -6
- package/src/config.ts +73 -0
- package/src/constants.ts +19 -0
- package/src/graphql/loaders.ts +244 -0
- package/src/graphql/resolvers.ts +1008 -0
- package/src/graphql/schema.ts +452 -0
- package/src/graphql/server.ts +156 -0
- package/src/graphql/types.ts +10 -0
- package/src/handlers/resource-handlers.ts +186 -0
- package/src/index.ts +166 -664
- package/src/resources/actors.ts +58 -76
- package/src/resources/assets.ts +148 -134
- package/src/resources/levels.ts +28 -33
- package/src/server/resource-registry.ts +47 -0
- package/src/server/tool-registry.ts +354 -0
- package/src/server-setup.ts +114 -0
- package/src/services/health-monitor.ts +132 -0
- package/src/services/metrics-server.ts +142 -0
- package/src/tools/actors.ts +426 -323
- package/src/tools/animation.ts +672 -461
- package/src/tools/assets.ts +364 -289
- package/src/tools/audio.ts +323 -766
- package/src/tools/base-tool.ts +52 -0
- package/src/tools/behavior-tree.ts +45 -0
- package/src/tools/blueprint.ts +792 -970
- package/src/tools/consolidated-tool-definitions.ts +993 -515
- package/src/tools/consolidated-tool-handlers.ts +258 -1146
- package/src/tools/debug.ts +292 -187
- package/src/tools/dynamic-handler-registry.ts +33 -0
- package/src/tools/editor.ts +329 -253
- package/src/tools/engine.ts +14 -3
- package/src/tools/environment.ts +281 -0
- package/src/tools/foliage.ts +330 -392
- package/src/tools/handlers/actor-handlers.ts +265 -0
- package/src/tools/handlers/animation-handlers.ts +237 -0
- package/src/tools/handlers/argument-helper.ts +142 -0
- package/src/tools/handlers/asset-handlers.ts +532 -0
- package/src/tools/handlers/audio-handlers.ts +194 -0
- package/src/tools/handlers/blueprint-handlers.ts +380 -0
- package/src/tools/handlers/common-handlers.ts +87 -0
- package/src/tools/handlers/editor-handlers.ts +123 -0
- package/src/tools/handlers/effect-handlers.ts +220 -0
- package/src/tools/handlers/environment-handlers.ts +183 -0
- package/src/tools/handlers/graph-handlers.ts +116 -0
- package/src/tools/handlers/input-handlers.ts +28 -0
- package/src/tools/handlers/inspect-handlers.ts +450 -0
- package/src/tools/handlers/level-handlers.ts +252 -0
- package/src/tools/handlers/lighting-handlers.ts +147 -0
- package/src/tools/handlers/performance-handlers.ts +132 -0
- package/src/tools/handlers/pipeline-handlers.ts +127 -0
- package/src/tools/handlers/sequence-handlers.ts +415 -0
- package/src/tools/handlers/system-handlers.ts +564 -0
- package/src/tools/input.ts +101 -0
- package/src/tools/introspection.ts +493 -584
- package/src/tools/landscape.ts +418 -507
- package/src/tools/level.ts +786 -708
- package/src/tools/lighting.ts +588 -984
- package/src/tools/logs.ts +9 -57
- package/src/tools/materials.ts +237 -121
- package/src/tools/niagara.ts +335 -168
- package/src/tools/performance.ts +320 -169
- package/src/tools/physics.ts +274 -613
- package/src/tools/property-dictionary.ts +98 -0
- package/src/tools/sequence.ts +276 -820
- package/src/tools/tool-definition-utils.ts +35 -0
- package/src/tools/ui.ts +205 -283
- package/src/types/automation-responses.ts +119 -0
- package/src/types/env.ts +0 -10
- package/src/types/responses.ts +355 -0
- package/src/types/tool-interfaces.ts +250 -0
- package/src/types/tool-types.ts +243 -21
- package/src/unreal-bridge.ts +460 -1550
- package/src/utils/command-validator.ts +76 -0
- package/src/utils/elicitation.ts +10 -7
- package/src/utils/error-handler.ts +14 -90
- package/src/utils/ini-reader.ts +86 -0
- package/src/utils/logger.ts +8 -3
- package/src/utils/normalize.test.ts +162 -0
- package/src/utils/normalize.ts +60 -0
- package/src/utils/path-security.ts +43 -0
- package/src/utils/response-factory.ts +44 -0
- package/src/utils/response-validator.ts +176 -56
- package/src/utils/result-helpers.ts +21 -19
- package/src/utils/safe-json.test.ts +90 -0
- package/src/utils/safe-json.ts +14 -11
- package/src/utils/unreal-command-queue.ts +152 -0
- package/src/utils/validation.test.ts +184 -0
- package/src/utils/validation.ts +4 -1
- package/src/wasm/index.ts +838 -0
- package/test-server.mjs +100 -0
- package/tests/run-unreal-tool-tests.mjs +242 -14
- package/tests/test-animation.mjs +369 -0
- package/tests/test-asset-advanced.mjs +82 -0
- package/tests/test-asset-errors.mjs +35 -0
- package/tests/test-asset-graph.mjs +311 -0
- package/tests/test-audio.mjs +417 -0
- package/tests/test-automation-timeouts.mjs +98 -0
- package/tests/test-behavior-tree.mjs +444 -0
- package/tests/test-blueprint-graph.mjs +410 -0
- package/tests/test-blueprint.mjs +577 -0
- package/tests/test-client-mode.mjs +86 -0
- package/tests/test-console-command.mjs +56 -0
- package/tests/test-control-actor.mjs +425 -0
- package/tests/test-control-editor.mjs +112 -0
- package/tests/test-graphql.mjs +372 -0
- package/tests/test-input.mjs +349 -0
- package/tests/test-inspect.mjs +302 -0
- package/tests/test-landscape.mjs +316 -0
- package/tests/test-lighting.mjs +428 -0
- package/tests/test-manage-asset.mjs +438 -0
- package/tests/test-manage-level.mjs +89 -0
- package/tests/test-materials.mjs +356 -0
- package/tests/test-niagara.mjs +185 -0
- package/tests/test-no-inline-python.mjs +122 -0
- package/tests/test-performance.mjs +539 -0
- package/tests/test-plugin-handshake.mjs +82 -0
- package/tests/test-runner.mjs +933 -0
- package/tests/test-sequence.mjs +104 -0
- package/tests/test-system.mjs +96 -0
- package/tests/test-wasm.mjs +283 -0
- package/tests/test-world-partition.mjs +215 -0
- package/tsconfig.json +3 -3
- package/vitest.config.ts +35 -0
- package/wasm/Cargo.lock +363 -0
- package/wasm/Cargo.toml +42 -0
- package/wasm/LICENSE +21 -0
- package/wasm/README.md +253 -0
- package/wasm/src/dependency_resolver.rs +377 -0
- package/wasm/src/lib.rs +153 -0
- package/wasm/src/property_parser.rs +271 -0
- package/wasm/src/transform_math.rs +396 -0
- package/wasm/tests/integration.rs +109 -0
- package/.github/workflows/smithery-build.yml +0 -29
- package/dist/prompts/index.d.ts +0 -21
- package/dist/prompts/index.js +0 -217
- package/dist/tools/build_environment_advanced.d.ts +0 -65
- package/dist/tools/build_environment_advanced.js +0 -633
- package/dist/tools/rc.d.ts +0 -110
- package/dist/tools/rc.js +0 -437
- package/dist/tools/visual.d.ts +0 -40
- package/dist/tools/visual.js +0 -282
- package/dist/utils/http.d.ts +0 -6
- package/dist/utils/http.js +0 -151
- package/dist/utils/python-output.d.ts +0 -18
- package/dist/utils/python-output.js +0 -290
- package/dist/utils/python.d.ts +0 -2
- package/dist/utils/python.js +0 -4
- package/dist/utils/stdio-redirect.d.ts +0 -2
- package/dist/utils/stdio-redirect.js +0 -20
- package/docs/unreal-tool-test-cases.md +0 -574
- package/smithery.yaml +0 -29
- package/src/prompts/index.ts +0 -249
- package/src/tools/build_environment_advanced.ts +0 -732
- package/src/tools/rc.ts +0 -515
- package/src/tools/visual.ts +0 -281
- package/src/utils/http.ts +0 -187
- package/src/utils/python-output.ts +0 -351
- package/src/utils/python.ts +0 -3
- package/src/utils/stdio-redirect.ts +0 -18
package/src/tools/physics.ts
CHANGED
|
@@ -1,90 +1,55 @@
|
|
|
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';
|
|
6
|
+
import { wasmIntegration } from '../wasm/index.js';
|
|
4
7
|
|
|
5
8
|
export class PhysicsTools {
|
|
6
|
-
constructor(private bridge: UnrealBridge) {}
|
|
7
|
-
|
|
9
|
+
constructor(private bridge: UnrealBridge, private automationBridge?: AutomationBridge) { }
|
|
10
|
+
|
|
11
|
+
setAutomationBridge(automationBridge?: AutomationBridge) { this.automationBridge = automationBridge; }
|
|
12
|
+
|
|
8
13
|
/**
|
|
9
14
|
* Helper to find a valid skeletal mesh in the project
|
|
10
15
|
*/
|
|
11
16
|
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
|
-
`;
|
|
17
|
+
if (!this.automationBridge) {
|
|
18
|
+
// Return common fallback paths without plugin
|
|
19
|
+
return '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple';
|
|
20
|
+
}
|
|
60
21
|
|
|
61
22
|
try {
|
|
62
|
-
const response = await this.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
23
|
+
const response = await this.automationBridge.sendAutomationRequest('find_skeletal_mesh', {
|
|
24
|
+
commonPaths: [
|
|
25
|
+
'/Game/Characters/Mannequins/Meshes/SKM_Manny',
|
|
26
|
+
'/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
|
|
27
|
+
'/Game/Characters/Mannequins/Meshes/SKM_Manny_Complex',
|
|
28
|
+
'/Game/Characters/Mannequins/Meshes/SKM_Quinn',
|
|
29
|
+
'/Game/Characters/Mannequins/Meshes/SKM_Quinn_Simple',
|
|
30
|
+
'/Game/Characters/Mannequins/Meshes/SKM_Quinn_Complex'
|
|
31
|
+
],
|
|
32
|
+
fallback: '/Engine/EngineMeshes/SkeletalCube'
|
|
33
|
+
}, {
|
|
34
|
+
timeoutMs: 30000
|
|
66
35
|
});
|
|
67
36
|
|
|
68
|
-
if (
|
|
69
|
-
const meshPath = coerceString(
|
|
37
|
+
if (response.success !== false && response.result) {
|
|
38
|
+
const meshPath = coerceString((response.result as any).meshPath);
|
|
70
39
|
if (meshPath) {
|
|
71
40
|
return meshPath;
|
|
72
41
|
}
|
|
73
42
|
}
|
|
74
43
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const detail = bestEffortInterpretedText(interpreted);
|
|
81
|
-
if (detail) {
|
|
82
|
-
console.error('Failed to parse skeletal mesh discovery:', detail);
|
|
44
|
+
// Fallback to alternate path
|
|
45
|
+
const alternate = coerceString((response.result as any)?.alternate);
|
|
46
|
+
if (alternate) {
|
|
47
|
+
return alternate;
|
|
83
48
|
}
|
|
84
49
|
} catch (error) {
|
|
85
50
|
console.error('Failed to find skeletal mesh:', error);
|
|
86
51
|
}
|
|
87
|
-
|
|
52
|
+
|
|
88
53
|
return '/Engine/EngineMeshes/SkeletalCube';
|
|
89
54
|
}
|
|
90
55
|
|
|
@@ -119,33 +84,33 @@ print('RESULT:' + json.dumps(result))
|
|
|
119
84
|
error: 'Name cannot be empty'
|
|
120
85
|
};
|
|
121
86
|
}
|
|
122
|
-
|
|
87
|
+
|
|
123
88
|
// Check for invalid characters in name
|
|
124
|
-
if (params.physicsAssetName.includes('@') || params.physicsAssetName.includes('#') ||
|
|
125
|
-
|
|
89
|
+
if (params.physicsAssetName.includes('@') || params.physicsAssetName.includes('#') ||
|
|
90
|
+
params.physicsAssetName.includes('$') || params.physicsAssetName.includes('%')) {
|
|
126
91
|
return {
|
|
127
92
|
success: false,
|
|
128
93
|
message: 'Failed to setup ragdoll: Name contains invalid characters',
|
|
129
94
|
error: 'Name contains invalid characters'
|
|
130
95
|
};
|
|
131
96
|
}
|
|
132
|
-
|
|
97
|
+
|
|
133
98
|
// Check if skeleton path is provided instead of skeletal mesh
|
|
134
|
-
if (params.skeletonPath && (params.skeletonPath.includes('_Skeleton') ||
|
|
135
|
-
|
|
99
|
+
if (params.skeletonPath && (params.skeletonPath.includes('_Skeleton') ||
|
|
100
|
+
params.skeletonPath.includes('SK_Mannequin') && !params.skeletonPath.includes('SKM_'))) {
|
|
136
101
|
return {
|
|
137
102
|
success: false,
|
|
138
103
|
message: 'Failed to setup ragdoll: Must specify a valid skeletal mesh',
|
|
139
104
|
error: 'Must specify a valid skeletal mesh, not a skeleton'
|
|
140
105
|
};
|
|
141
106
|
}
|
|
142
|
-
|
|
107
|
+
|
|
143
108
|
// Validate and sanitize parameters
|
|
144
109
|
const validation = validateAssetParams({
|
|
145
110
|
name: params.physicsAssetName,
|
|
146
111
|
savePath: params.savePath || '/Game/Physics'
|
|
147
112
|
});
|
|
148
|
-
|
|
113
|
+
|
|
149
114
|
if (!validation.valid) {
|
|
150
115
|
return {
|
|
151
116
|
success: false,
|
|
@@ -153,20 +118,20 @@ print('RESULT:' + json.dumps(result))
|
|
|
153
118
|
error: validation.error
|
|
154
119
|
};
|
|
155
120
|
}
|
|
156
|
-
|
|
121
|
+
|
|
157
122
|
const sanitizedParams = validation.sanitized;
|
|
158
123
|
const path = sanitizedParams.savePath || '/Game/Physics';
|
|
159
|
-
|
|
124
|
+
|
|
160
125
|
// Resolve skeletal mesh path
|
|
161
126
|
let meshPath = params.skeletonPath;
|
|
162
|
-
|
|
127
|
+
|
|
163
128
|
// Try to resolve skeleton to mesh mapping
|
|
164
129
|
const resolvedPath = resolveSkeletalMeshPath(meshPath);
|
|
165
130
|
if (resolvedPath && resolvedPath !== meshPath) {
|
|
166
131
|
console.error(`Auto-correcting path from ${meshPath} to ${resolvedPath}`);
|
|
167
132
|
meshPath = resolvedPath;
|
|
168
133
|
}
|
|
169
|
-
|
|
134
|
+
|
|
170
135
|
// Auto-resolve if it looks like a skeleton path or is empty
|
|
171
136
|
if (!meshPath || meshPath.includes('_Skeleton') || meshPath === 'None' || meshPath === '') {
|
|
172
137
|
console.error('Resolving skeletal mesh path...');
|
|
@@ -176,15 +141,15 @@ print('RESULT:' + json.dumps(result))
|
|
|
176
141
|
console.error(`Using resolved skeletal mesh: ${meshPath}`);
|
|
177
142
|
}
|
|
178
143
|
}
|
|
179
|
-
|
|
144
|
+
|
|
180
145
|
// Add concurrency delay to prevent race conditions
|
|
181
146
|
await concurrencyDelay();
|
|
182
|
-
|
|
147
|
+
|
|
183
148
|
// IMPORTANT: Physics assets require a SKELETAL MESH, not a skeleton
|
|
184
149
|
// UE5 uses: /Game/Characters/Mannequins/Meshes/SKM_Manny_Simple or SKM_Quinn_Simple
|
|
185
150
|
// UE4 used: /Game/Mannequin/Character/Mesh/SK_Mannequin (which no longer exists)
|
|
186
|
-
//
|
|
187
|
-
|
|
151
|
+
// Alternate path: /Engine/EngineMeshes/SkeletalCube
|
|
152
|
+
|
|
188
153
|
// Common skeleton paths that should be replaced with actual skeletal mesh paths
|
|
189
154
|
const skeletonToMeshMap: { [key: string]: string } = {
|
|
190
155
|
'/Game/Mannequin/Character/Mesh/UE4_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
|
|
@@ -193,356 +158,50 @@ print('RESULT:' + json.dumps(result))
|
|
|
193
158
|
'/Game/Characters/Mannequins/Skeletons/UE5_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
|
|
194
159
|
'/Game/Characters/Mannequins/Skeletons/UE5_Female_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Quinn_Simple'
|
|
195
160
|
};
|
|
196
|
-
|
|
161
|
+
|
|
197
162
|
// Auto-fix common incorrect paths
|
|
198
163
|
let actualSkeletonPath = params.skeletonPath;
|
|
199
164
|
if (actualSkeletonPath && skeletonToMeshMap[actualSkeletonPath]) {
|
|
200
165
|
console.error(`Auto-correcting path from ${actualSkeletonPath} to ${skeletonToMeshMap[actualSkeletonPath]}`);
|
|
201
166
|
actualSkeletonPath = skeletonToMeshMap[actualSkeletonPath];
|
|
202
167
|
}
|
|
203
|
-
|
|
168
|
+
|
|
204
169
|
if (actualSkeletonPath && (actualSkeletonPath.includes('_Skeleton') || actualSkeletonPath.includes('SK_Mannequin'))) {
|
|
205
170
|
// This is likely a skeleton path, not a skeletal mesh
|
|
206
171
|
console.error('Warning: Path appears to be a skeleton, not a skeletal mesh. Auto-correcting to SKM_Manny_Simple.');
|
|
207
172
|
}
|
|
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
173
|
|
|
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
|
|
174
|
+
// Use Automation Bridge for physics asset creation
|
|
175
|
+
if (!this.automationBridge) {
|
|
176
|
+
throw new Error('Automation Bridge not available. Physics asset creation requires plugin support.');
|
|
177
|
+
}
|
|
178
|
+
|
|
500
179
|
try {
|
|
501
|
-
const response = await this.
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
180
|
+
const response = await this.automationBridge.sendAutomationRequest('setup_ragdoll', {
|
|
181
|
+
meshPath,
|
|
182
|
+
physicsAssetName: sanitizedParams.name,
|
|
183
|
+
savePath: path,
|
|
184
|
+
blendWeight: params.blendWeight,
|
|
185
|
+
constraints: params.constraints
|
|
186
|
+
}, {
|
|
187
|
+
timeoutMs: 120000 // 2 minutes for complex physics asset creation
|
|
505
188
|
});
|
|
506
189
|
|
|
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}`
|
|
190
|
+
if (response.success === false) {
|
|
191
|
+
return {
|
|
192
|
+
success: false,
|
|
193
|
+
message: response.error || response.message || `Failed to setup ragdoll for ${sanitizedParams.name}`,
|
|
194
|
+
error: response.error || response.message || 'Failed to setup ragdoll'
|
|
522
195
|
};
|
|
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
196
|
}
|
|
537
197
|
|
|
538
|
-
const
|
|
539
|
-
|
|
198
|
+
const result = response.result as any;
|
|
540
199
|
return {
|
|
541
|
-
success:
|
|
542
|
-
message:
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
200
|
+
success: true,
|
|
201
|
+
message: response.message || `Ragdoll physics setup completed for ${sanitizedParams.name}`,
|
|
202
|
+
path: coerceString(result?.path) ?? coerceString(result?.physicsAssetPath) ?? `${path}/${sanitizedParams.name}`,
|
|
203
|
+
existingAsset: result?.existingAsset,
|
|
204
|
+
...(result || {})
|
|
546
205
|
};
|
|
547
206
|
} catch (error) {
|
|
548
207
|
return {
|
|
@@ -556,6 +215,7 @@ print('RESULT:' + json.dumps(result))
|
|
|
556
215
|
}
|
|
557
216
|
}
|
|
558
217
|
|
|
218
|
+
|
|
559
219
|
/**
|
|
560
220
|
* Create Physics Constraint
|
|
561
221
|
*/
|
|
@@ -577,17 +237,17 @@ print('RESULT:' + json.dumps(result))
|
|
|
577
237
|
// Spawn constraint actor
|
|
578
238
|
const spawnCmd = `spawnactor /Script/Engine.PhysicsConstraintActor ${params.location[0]} ${params.location[1]} ${params.location[2]}`;
|
|
579
239
|
await this.bridge.executeConsoleCommand(spawnCmd);
|
|
580
|
-
|
|
240
|
+
|
|
581
241
|
// Configure constraint
|
|
582
242
|
const commands = [
|
|
583
243
|
`SetConstraintActors ${params.name} ${params.actor1} ${params.actor2}`,
|
|
584
244
|
`SetConstraintType ${params.name} ${params.constraintType}`
|
|
585
245
|
];
|
|
586
|
-
|
|
246
|
+
|
|
587
247
|
if (params.breakThreshold) {
|
|
588
248
|
commands.push(`SetConstraintBreakThreshold ${params.name} ${params.breakThreshold}`);
|
|
589
249
|
}
|
|
590
|
-
|
|
250
|
+
|
|
591
251
|
if (params.limits) {
|
|
592
252
|
const limits = params.limits;
|
|
593
253
|
if (limits.swing1 !== undefined) {
|
|
@@ -603,12 +263,12 @@ print('RESULT:' + json.dumps(result))
|
|
|
603
263
|
commands.push(`SetConstraintLinear ${params.name} ${limits.linear}`);
|
|
604
264
|
}
|
|
605
265
|
}
|
|
606
|
-
|
|
266
|
+
|
|
607
267
|
await this.bridge.executeConsoleCommands(commands);
|
|
608
|
-
|
|
609
|
-
return {
|
|
610
|
-
success: true,
|
|
611
|
-
message: `Physics constraint ${params.name} created between ${params.actor1} and ${params.actor2}`
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
success: true,
|
|
271
|
+
message: `Physics constraint ${params.name} created between ${params.actor1} and ${params.actor2}`
|
|
612
272
|
};
|
|
613
273
|
} catch (err) {
|
|
614
274
|
return { success: false, error: `Failed to create constraint: ${err}` };
|
|
@@ -632,11 +292,11 @@ print('RESULT:' + json.dumps(result))
|
|
|
632
292
|
}) {
|
|
633
293
|
try {
|
|
634
294
|
const path = params.savePath || '/Game/Destruction';
|
|
635
|
-
|
|
295
|
+
|
|
636
296
|
const commands = [
|
|
637
297
|
`CreateGeometryCollection ${params.destructionName} ${params.meshPath} ${path}`
|
|
638
298
|
];
|
|
639
|
-
|
|
299
|
+
|
|
640
300
|
// Configure fracture
|
|
641
301
|
if (params.fractureSettings) {
|
|
642
302
|
const settings = params.fractureSettings;
|
|
@@ -644,21 +304,21 @@ print('RESULT:' + json.dumps(result))
|
|
|
644
304
|
`FractureGeometry ${params.destructionName} ${settings.cellCount} ${settings.minimumVolumeSize} ${settings.seed}`
|
|
645
305
|
);
|
|
646
306
|
}
|
|
647
|
-
|
|
307
|
+
|
|
648
308
|
// Set damage threshold
|
|
649
309
|
if (params.damageThreshold) {
|
|
650
310
|
commands.push(`SetDamageThreshold ${params.destructionName} ${params.damageThreshold}`);
|
|
651
311
|
}
|
|
652
|
-
|
|
312
|
+
|
|
653
313
|
// Set debris lifetime
|
|
654
314
|
if (params.debrisLifetime) {
|
|
655
315
|
commands.push(`SetDebrisLifetime ${params.destructionName} ${params.debrisLifetime}`);
|
|
656
316
|
}
|
|
657
|
-
|
|
317
|
+
|
|
658
318
|
await this.bridge.executeConsoleCommands(commands);
|
|
659
|
-
|
|
660
|
-
return {
|
|
661
|
-
success: true,
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
success: true,
|
|
662
322
|
message: `Chaos destruction ${params.destructionName} created`,
|
|
663
323
|
path: `${path}/${params.destructionName}`
|
|
664
324
|
};
|
|
@@ -689,58 +349,126 @@ print('RESULT:' + json.dumps(result))
|
|
|
689
349
|
gears: number[];
|
|
690
350
|
finalDriveRatio: number;
|
|
691
351
|
};
|
|
352
|
+
pluginDependencies?: string[];
|
|
692
353
|
}) {
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
354
|
+
// Plugin check removed as ensurePluginsEnabled is deprecated.
|
|
355
|
+
// Users should ensure required plugins are enabled in the editor.
|
|
356
|
+
|
|
357
|
+
const rawParams: any = params as any;
|
|
358
|
+
|
|
359
|
+
const pluginDeps: string[] | undefined = Array.isArray(params.pluginDependencies) && params.pluginDependencies.length > 0
|
|
360
|
+
? params.pluginDependencies
|
|
361
|
+
: (Array.isArray(rawParams.plugins) && rawParams.plugins.length > 0 ? rawParams.plugins : undefined);
|
|
362
|
+
|
|
363
|
+
if (pluginDeps && pluginDeps.length > 0) {
|
|
364
|
+
return {
|
|
365
|
+
success: false,
|
|
366
|
+
error: 'MISSING_ENGINE_PLUGINS',
|
|
367
|
+
missingPlugins: pluginDeps,
|
|
368
|
+
message: `Required engine plugins not enabled: ${pluginDeps.join(', ')}`
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const warnings: string[] = [];
|
|
373
|
+
|
|
374
|
+
const hasExplicitEmptyWheels = Array.isArray(params.wheels) && params.wheels.length === 0;
|
|
375
|
+
|
|
376
|
+
const effectiveVehicleType = typeof params.vehicleType === 'string' && params.vehicleType.trim().length > 0
|
|
377
|
+
? params.vehicleType
|
|
378
|
+
: 'Car';
|
|
379
|
+
|
|
380
|
+
const commands = [
|
|
381
|
+
`CreateVehicle ${params.vehicleName} ${effectiveVehicleType}`
|
|
382
|
+
];
|
|
383
|
+
|
|
384
|
+
// Configure wheels when provided
|
|
385
|
+
if (Array.isArray(params.wheels) && params.wheels.length > 0) {
|
|
386
|
+
for (const wheel of params.wheels) {
|
|
387
|
+
commands.push(
|
|
388
|
+
`AddVehicleWheel ${params.vehicleName} ${wheel.name} ${wheel.radius} ${wheel.width} ${wheel.mass}`
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
if (wheel.isSteering) {
|
|
392
|
+
commands.push(`SetWheelSteering ${params.vehicleName} ${wheel.name} true`);
|
|
711
393
|
}
|
|
394
|
+
if (wheel.isDriving) {
|
|
395
|
+
commands.push(`SetWheelDriving ${params.vehicleName} ${wheel.name} true`);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Configure engine (optional). Clamp negative RPMs and tolerate missing torqueCurve.
|
|
401
|
+
const effectiveEngine = params.engine ?? ((typeof rawParams.maxRPM === 'number' || Array.isArray(rawParams.torqueCurve))
|
|
402
|
+
? { maxRPM: rawParams.maxRPM, torqueCurve: rawParams.torqueCurve }
|
|
403
|
+
: undefined);
|
|
404
|
+
|
|
405
|
+
if (effectiveEngine) {
|
|
406
|
+
let maxRPM = typeof effectiveEngine.maxRPM === 'number' ? effectiveEngine.maxRPM : 0;
|
|
407
|
+
if (maxRPM < 0) {
|
|
408
|
+
maxRPM = 0;
|
|
409
|
+
warnings.push('Engine maxRPM was negative and has been clamped to 0.');
|
|
712
410
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
411
|
+
commands.push(`SetEngineMaxRPM ${params.vehicleName} ${maxRPM}`);
|
|
412
|
+
|
|
413
|
+
const rawCurve = Array.isArray(effectiveEngine.torqueCurve) ? effectiveEngine.torqueCurve : [];
|
|
414
|
+
for (const point of rawCurve) {
|
|
415
|
+
let rpm: number | undefined;
|
|
416
|
+
let torque: number | undefined;
|
|
417
|
+
|
|
418
|
+
if (Array.isArray(point) && point.length >= 2) {
|
|
419
|
+
rpm = Number(point[0]);
|
|
420
|
+
torque = Number(point[1]);
|
|
421
|
+
} else if (point && typeof point === 'object') {
|
|
422
|
+
const anyPoint: any = point;
|
|
423
|
+
rpm = typeof anyPoint.rpm === 'number' ? anyPoint.rpm : undefined;
|
|
424
|
+
torque = typeof anyPoint.torque === 'number' ? anyPoint.torque : undefined;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (typeof rpm === 'number' && typeof torque === 'number') {
|
|
719
428
|
commands.push(`AddTorqueCurvePoint ${params.vehicleName} ${rpm} ${torque}`);
|
|
720
429
|
}
|
|
721
430
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Configure transmission
|
|
434
|
+
if (params.transmission) {
|
|
435
|
+
if (Array.isArray(params.transmission.gears)) {
|
|
725
436
|
for (let i = 0; i < params.transmission.gears.length; i++) {
|
|
726
437
|
commands.push(
|
|
727
438
|
`SetGearRatio ${params.vehicleName} ${i} ${params.transmission.gears[i]}`
|
|
728
439
|
);
|
|
729
440
|
}
|
|
441
|
+
}
|
|
442
|
+
if (typeof params.transmission.finalDriveRatio === 'number') {
|
|
730
443
|
commands.push(
|
|
731
444
|
`SetFinalDriveRatio ${params.vehicleName} ${params.transmission.finalDriveRatio}`
|
|
732
445
|
);
|
|
733
446
|
}
|
|
734
|
-
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
try {
|
|
735
450
|
await this.bridge.executeConsoleCommands(commands);
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
451
|
+
} catch (_error) {
|
|
452
|
+
// If vehicle console commands fail (e.g., `Command not executed`), treat this as
|
|
453
|
+
// a best-effort configuration that falls back to engine defaults.
|
|
454
|
+
if (warnings.length === 0) {
|
|
455
|
+
warnings.push('Vehicle configuration commands could not be executed; using engine defaults.');
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (hasExplicitEmptyWheels) {
|
|
460
|
+
warnings.push('No wheels specified; using default wheels from vehicle preset.');
|
|
743
461
|
}
|
|
462
|
+
|
|
463
|
+
if (warnings.length === 0) {
|
|
464
|
+
warnings.push('Verify wheel class assignments and offsets in the vehicle movement component to ensure they match your project defaults.');
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return {
|
|
468
|
+
success: true,
|
|
469
|
+
message: `Vehicle ${params.vehicleName} configured`,
|
|
470
|
+
warnings
|
|
471
|
+
};
|
|
744
472
|
}
|
|
745
473
|
|
|
746
474
|
/**
|
|
@@ -753,148 +481,42 @@ print('RESULT:' + json.dumps(result))
|
|
|
753
481
|
boneName?: string;
|
|
754
482
|
isLocal?: boolean;
|
|
755
483
|
}) {
|
|
484
|
+
if (!this.automationBridge) {
|
|
485
|
+
throw new Error('Automation Bridge not available. Physics force application requires plugin support.');
|
|
486
|
+
}
|
|
487
|
+
|
|
756
488
|
try {
|
|
757
|
-
// Use
|
|
758
|
-
const
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
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'
|
|
489
|
+
// Use WASM for vector normalization/validation
|
|
490
|
+
const zeroVector: [number, number, number] = [0, 0, 0];
|
|
491
|
+
const normalizedVector = wasmIntegration.vectorAdd(zeroVector, params.vector);
|
|
492
|
+
console.error('[WASM] Using vectorAdd for physics force vector processing');
|
|
493
|
+
|
|
494
|
+
const response = await this.automationBridge.sendAutomationRequest('apply_force', {
|
|
495
|
+
actorName: params.actorName,
|
|
496
|
+
forceType: params.forceType,
|
|
497
|
+
vector: normalizedVector,
|
|
498
|
+
boneName: params.boneName,
|
|
499
|
+
isLocal: params.isLocal
|
|
500
|
+
}, {
|
|
501
|
+
timeoutMs: 30000
|
|
861
502
|
});
|
|
862
503
|
|
|
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)) {
|
|
504
|
+
if (response.success === false) {
|
|
505
|
+
const result = response.result as any;
|
|
885
506
|
return {
|
|
886
507
|
success: false,
|
|
887
|
-
error:
|
|
888
|
-
availableActors,
|
|
889
|
-
details:
|
|
508
|
+
error: response.error || response.message || 'Force application failed',
|
|
509
|
+
availableActors: result?.available_actors ? coerceStringArray(result.available_actors) : undefined,
|
|
510
|
+
details: result?.details
|
|
890
511
|
};
|
|
891
512
|
}
|
|
892
513
|
|
|
514
|
+
const result = response.result as any;
|
|
893
515
|
return {
|
|
894
|
-
success:
|
|
895
|
-
|
|
896
|
-
availableActors,
|
|
897
|
-
|
|
516
|
+
success: true,
|
|
517
|
+
message: response.message || `Applied ${params.forceType} to ${params.actorName}`,
|
|
518
|
+
availableActors: result?.available_actors ? coerceStringArray(result.available_actors) : undefined,
|
|
519
|
+
...(result || {})
|
|
898
520
|
};
|
|
899
521
|
} catch (err) {
|
|
900
522
|
return { success: false, error: `Failed to apply force: ${err}` };
|
|
@@ -921,10 +543,10 @@ print(f"RESULT:{json.dumps(result)}")
|
|
|
921
543
|
`EnableClothSimulation ${params.meshName}`,
|
|
922
544
|
`SetClothPreset ${params.meshName} ${params.clothPreset}`
|
|
923
545
|
];
|
|
924
|
-
|
|
546
|
+
|
|
925
547
|
if (params.clothPreset === 'Custom' && params.customSettings) {
|
|
926
548
|
const settings = params.customSettings;
|
|
927
|
-
|
|
549
|
+
|
|
928
550
|
if (settings.stiffness !== undefined) {
|
|
929
551
|
commands.push(`SetClothStiffness ${params.meshName} ${settings.stiffness}`);
|
|
930
552
|
}
|
|
@@ -945,12 +567,12 @@ print(f"RESULT:{json.dumps(result)}")
|
|
|
945
567
|
commands.push(`SetClothWind ${params.meshName} ${wind[0]} ${wind[1]} ${wind[2]}`);
|
|
946
568
|
}
|
|
947
569
|
}
|
|
948
|
-
|
|
570
|
+
|
|
949
571
|
await this.bridge.executeConsoleCommands(commands);
|
|
950
|
-
|
|
951
|
-
return {
|
|
952
|
-
success: true,
|
|
953
|
-
message: `Cloth simulation enabled for ${params.meshName}`
|
|
572
|
+
|
|
573
|
+
return {
|
|
574
|
+
success: true,
|
|
575
|
+
message: `Cloth simulation enabled for ${params.meshName}`
|
|
954
576
|
};
|
|
955
577
|
} catch (err) {
|
|
956
578
|
return { success: false, error: `Failed to setup cloth: ${err}` };
|
|
@@ -976,14 +598,14 @@ print(f"RESULT:{json.dumps(result)}")
|
|
|
976
598
|
try {
|
|
977
599
|
const locStr = `${params.location[0]} ${params.location[1]} ${params.location[2]}`;
|
|
978
600
|
const volStr = `${params.volume[0]} ${params.volume[1]} ${params.volume[2]}`;
|
|
979
|
-
|
|
601
|
+
|
|
980
602
|
const commands = [
|
|
981
603
|
`CreateFluidSimulation ${params.name} ${params.fluidType} ${locStr} ${volStr}`
|
|
982
604
|
];
|
|
983
|
-
|
|
605
|
+
|
|
984
606
|
if (params.customSettings) {
|
|
985
607
|
const settings = params.customSettings;
|
|
986
|
-
|
|
608
|
+
|
|
987
609
|
if (settings.viscosity !== undefined) {
|
|
988
610
|
commands.push(`SetFluidViscosity ${params.name} ${settings.viscosity}`);
|
|
989
611
|
}
|
|
@@ -1003,16 +625,55 @@ print(f"RESULT:{json.dumps(result)}")
|
|
|
1003
625
|
);
|
|
1004
626
|
}
|
|
1005
627
|
}
|
|
1006
|
-
|
|
628
|
+
|
|
1007
629
|
await this.bridge.executeConsoleCommands(commands);
|
|
1008
|
-
|
|
1009
|
-
return {
|
|
1010
|
-
success: true,
|
|
1011
|
-
message: `Fluid simulation ${params.name} created`
|
|
630
|
+
|
|
631
|
+
return {
|
|
632
|
+
success: true,
|
|
633
|
+
message: `Fluid simulation ${params.name} created`
|
|
1012
634
|
};
|
|
1013
635
|
} catch (err) {
|
|
1014
636
|
return { success: false, error: `Failed to create fluid simulation: ${err}` };
|
|
1015
637
|
}
|
|
1016
638
|
}
|
|
1017
639
|
|
|
640
|
+
/**
|
|
641
|
+
* Setup Physics Simulation (Create Physics Asset)
|
|
642
|
+
*/
|
|
643
|
+
async setupPhysicsSimulation(params: {
|
|
644
|
+
meshPath?: string;
|
|
645
|
+
skeletonPath?: string;
|
|
646
|
+
physicsAssetName?: string;
|
|
647
|
+
savePath?: string;
|
|
648
|
+
}) {
|
|
649
|
+
if (!this.automationBridge) {
|
|
650
|
+
throw new Error('Automation Bridge not available. Physics asset creation requires plugin support.');
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
try {
|
|
654
|
+
const response = await this.automationBridge.sendAutomationRequest('animation_physics', {
|
|
655
|
+
action: 'setup_physics_simulation',
|
|
656
|
+
...params
|
|
657
|
+
}, {
|
|
658
|
+
timeoutMs: 60000
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
if (response.success === false) {
|
|
662
|
+
return {
|
|
663
|
+
success: false,
|
|
664
|
+
message: response.error || response.message || 'Failed to setup physics simulation',
|
|
665
|
+
error: response.error || response.message
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
return {
|
|
670
|
+
success: true,
|
|
671
|
+
message: response.message || 'Physics simulation setup completed',
|
|
672
|
+
...(response.result || {})
|
|
673
|
+
};
|
|
674
|
+
} catch (err) {
|
|
675
|
+
return { success: false, error: `Failed to setup physics simulation: ${err}` };
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
1018
679
|
}
|