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/dist/tools/physics.js
CHANGED
|
@@ -1,81 +1,41 @@
|
|
|
1
1
|
import { validateAssetParams, resolveSkeletalMeshPath, concurrencyDelay } from '../utils/validation.js';
|
|
2
|
-
import {
|
|
2
|
+
import { coerceString, coerceStringArray } from '../utils/result-helpers.js';
|
|
3
|
+
import { wasmIntegration } from '../wasm/index.js';
|
|
3
4
|
export class PhysicsTools {
|
|
4
5
|
bridge;
|
|
5
|
-
|
|
6
|
+
automationBridge;
|
|
7
|
+
constructor(bridge, automationBridge) {
|
|
6
8
|
this.bridge = bridge;
|
|
9
|
+
this.automationBridge = automationBridge;
|
|
7
10
|
}
|
|
8
|
-
|
|
9
|
-
* Helper to find a valid skeletal mesh in the project
|
|
10
|
-
*/
|
|
11
|
+
setAutomationBridge(automationBridge) { this.automationBridge = automationBridge; }
|
|
11
12
|
async findValidSkeletalMesh() {
|
|
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
|
-
`;
|
|
13
|
+
if (!this.automationBridge) {
|
|
14
|
+
return '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple';
|
|
15
|
+
}
|
|
60
16
|
try {
|
|
61
|
-
const response = await this.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
17
|
+
const response = await this.automationBridge.sendAutomationRequest('find_skeletal_mesh', {
|
|
18
|
+
commonPaths: [
|
|
19
|
+
'/Game/Characters/Mannequins/Meshes/SKM_Manny',
|
|
20
|
+
'/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
|
|
21
|
+
'/Game/Characters/Mannequins/Meshes/SKM_Manny_Complex',
|
|
22
|
+
'/Game/Characters/Mannequins/Meshes/SKM_Quinn',
|
|
23
|
+
'/Game/Characters/Mannequins/Meshes/SKM_Quinn_Simple',
|
|
24
|
+
'/Game/Characters/Mannequins/Meshes/SKM_Quinn_Complex'
|
|
25
|
+
],
|
|
26
|
+
fallback: '/Engine/EngineMeshes/SkeletalCube'
|
|
27
|
+
}, {
|
|
28
|
+
timeoutMs: 30000
|
|
65
29
|
});
|
|
66
|
-
if (
|
|
67
|
-
const meshPath = coerceString(
|
|
30
|
+
if (response.success !== false && response.result) {
|
|
31
|
+
const meshPath = coerceString(response.result.meshPath);
|
|
68
32
|
if (meshPath) {
|
|
69
33
|
return meshPath;
|
|
70
34
|
}
|
|
71
35
|
}
|
|
72
|
-
const
|
|
73
|
-
if (
|
|
74
|
-
return
|
|
75
|
-
}
|
|
76
|
-
const detail = bestEffortInterpretedText(interpreted);
|
|
77
|
-
if (detail) {
|
|
78
|
-
console.error('Failed to parse skeletal mesh discovery:', detail);
|
|
36
|
+
const alternate = coerceString(response.result?.alternate);
|
|
37
|
+
if (alternate) {
|
|
38
|
+
return alternate;
|
|
79
39
|
}
|
|
80
40
|
}
|
|
81
41
|
catch (error) {
|
|
@@ -83,16 +43,8 @@ print('RESULT:' + json.dumps(result))
|
|
|
83
43
|
}
|
|
84
44
|
return '/Engine/EngineMeshes/SkeletalCube';
|
|
85
45
|
}
|
|
86
|
-
/**
|
|
87
|
-
* Setup Ragdoll Physics
|
|
88
|
-
* NOTE: Requires a valid skeletal mesh to create physics asset
|
|
89
|
-
* @param skeletonPath - Path to an existing skeletal mesh asset (required)
|
|
90
|
-
* @param physicsAssetName - Name for the new physics asset
|
|
91
|
-
* @param savePath - Directory to save the asset (default: /Game/Physics)
|
|
92
|
-
*/
|
|
93
46
|
async setupRagdoll(params) {
|
|
94
47
|
try {
|
|
95
|
-
// Strong validation for physics asset name
|
|
96
48
|
if (!params.physicsAssetName || params.physicsAssetName.trim() === '') {
|
|
97
49
|
return {
|
|
98
50
|
success: false,
|
|
@@ -100,7 +52,6 @@ print('RESULT:' + json.dumps(result))
|
|
|
100
52
|
error: 'Name cannot be empty'
|
|
101
53
|
};
|
|
102
54
|
}
|
|
103
|
-
// Check for invalid characters in name
|
|
104
55
|
if (params.physicsAssetName.includes('@') || params.physicsAssetName.includes('#') ||
|
|
105
56
|
params.physicsAssetName.includes('$') || params.physicsAssetName.includes('%')) {
|
|
106
57
|
return {
|
|
@@ -109,7 +60,6 @@ print('RESULT:' + json.dumps(result))
|
|
|
109
60
|
error: 'Name contains invalid characters'
|
|
110
61
|
};
|
|
111
62
|
}
|
|
112
|
-
// Check if skeleton path is provided instead of skeletal mesh
|
|
113
63
|
if (params.skeletonPath && (params.skeletonPath.includes('_Skeleton') ||
|
|
114
64
|
params.skeletonPath.includes('SK_Mannequin') && !params.skeletonPath.includes('SKM_'))) {
|
|
115
65
|
return {
|
|
@@ -118,7 +68,6 @@ print('RESULT:' + json.dumps(result))
|
|
|
118
68
|
error: 'Must specify a valid skeletal mesh, not a skeleton'
|
|
119
69
|
};
|
|
120
70
|
}
|
|
121
|
-
// Validate and sanitize parameters
|
|
122
71
|
const validation = validateAssetParams({
|
|
123
72
|
name: params.physicsAssetName,
|
|
124
73
|
savePath: params.savePath || '/Game/Physics'
|
|
@@ -132,15 +81,12 @@ print('RESULT:' + json.dumps(result))
|
|
|
132
81
|
}
|
|
133
82
|
const sanitizedParams = validation.sanitized;
|
|
134
83
|
const path = sanitizedParams.savePath || '/Game/Physics';
|
|
135
|
-
// Resolve skeletal mesh path
|
|
136
84
|
let meshPath = params.skeletonPath;
|
|
137
|
-
// Try to resolve skeleton to mesh mapping
|
|
138
85
|
const resolvedPath = resolveSkeletalMeshPath(meshPath);
|
|
139
86
|
if (resolvedPath && resolvedPath !== meshPath) {
|
|
140
87
|
console.error(`Auto-correcting path from ${meshPath} to ${resolvedPath}`);
|
|
141
88
|
meshPath = resolvedPath;
|
|
142
89
|
}
|
|
143
|
-
// Auto-resolve if it looks like a skeleton path or is empty
|
|
144
90
|
if (!meshPath || meshPath.includes('_Skeleton') || meshPath === 'None' || meshPath === '') {
|
|
145
91
|
console.error('Resolving skeletal mesh path...');
|
|
146
92
|
const resolvedMesh = await this.findValidSkeletalMesh();
|
|
@@ -149,13 +95,7 @@ print('RESULT:' + json.dumps(result))
|
|
|
149
95
|
console.error(`Using resolved skeletal mesh: ${meshPath}`);
|
|
150
96
|
}
|
|
151
97
|
}
|
|
152
|
-
// Add concurrency delay to prevent race conditions
|
|
153
98
|
await concurrencyDelay();
|
|
154
|
-
// IMPORTANT: Physics assets require a SKELETAL MESH, not a skeleton
|
|
155
|
-
// UE5 uses: /Game/Characters/Mannequins/Meshes/SKM_Manny_Simple or SKM_Quinn_Simple
|
|
156
|
-
// UE4 used: /Game/Mannequin/Character/Mesh/SK_Mannequin (which no longer exists)
|
|
157
|
-
// Fallback: /Engine/EngineMeshes/SkeletalCube
|
|
158
|
-
// Common skeleton paths that should be replaced with actual skeletal mesh paths
|
|
159
99
|
const skeletonToMeshMap = {
|
|
160
100
|
'/Game/Mannequin/Character/Mesh/UE4_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
|
|
161
101
|
'/Game/Characters/Mannequins/Meshes/SK_Mannequin': '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
|
|
@@ -163,337 +103,41 @@ print('RESULT:' + json.dumps(result))
|
|
|
163
103
|
'/Game/Characters/Mannequins/Skeletons/UE5_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
|
|
164
104
|
'/Game/Characters/Mannequins/Skeletons/UE5_Female_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Quinn_Simple'
|
|
165
105
|
};
|
|
166
|
-
// Auto-fix common incorrect paths
|
|
167
106
|
let actualSkeletonPath = params.skeletonPath;
|
|
168
107
|
if (actualSkeletonPath && skeletonToMeshMap[actualSkeletonPath]) {
|
|
169
108
|
console.error(`Auto-correcting path from ${actualSkeletonPath} to ${skeletonToMeshMap[actualSkeletonPath]}`);
|
|
170
109
|
actualSkeletonPath = skeletonToMeshMap[actualSkeletonPath];
|
|
171
110
|
}
|
|
172
111
|
if (actualSkeletonPath && (actualSkeletonPath.includes('_Skeleton') || actualSkeletonPath.includes('SK_Mannequin'))) {
|
|
173
|
-
// This is likely a skeleton path, not a skeletal mesh
|
|
174
112
|
console.error('Warning: Path appears to be a skeleton, not a skeletal mesh. Auto-correcting to SKM_Manny_Simple.');
|
|
175
113
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
import time
|
|
180
|
-
import json
|
|
181
|
-
|
|
182
|
-
result = {
|
|
183
|
-
"success": False,
|
|
184
|
-
"path": None,
|
|
185
|
-
"message": "",
|
|
186
|
-
"error": None,
|
|
187
|
-
"warnings": [],
|
|
188
|
-
"details": [],
|
|
189
|
-
"existingAsset": False,
|
|
190
|
-
"meshPath": "${meshPath}"
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
def record_detail(message):
|
|
194
|
-
result["details"].append(message)
|
|
195
|
-
|
|
196
|
-
def record_warning(message):
|
|
197
|
-
result["warnings"].append(message)
|
|
198
|
-
|
|
199
|
-
def record_error(message):
|
|
200
|
-
result["error"] = message
|
|
201
|
-
|
|
202
|
-
# Helper function to ensure asset persistence
|
|
203
|
-
def ensure_asset_persistence(asset_path):
|
|
204
|
-
try:
|
|
205
|
-
asset = unreal.EditorAssetLibrary.load_asset(asset_path)
|
|
206
|
-
if not asset:
|
|
207
|
-
record_warning(f"Asset persistence check failed: {asset_path} not loaded")
|
|
208
|
-
return False
|
|
209
|
-
|
|
210
|
-
# Save the asset
|
|
211
|
-
saved = unreal.EditorAssetLibrary.save_asset(asset_path, only_if_is_dirty=False)
|
|
212
|
-
if saved:
|
|
213
|
-
print(f"Asset saved: {asset_path}")
|
|
214
|
-
record_detail(f"Asset saved: {asset_path}")
|
|
215
|
-
|
|
216
|
-
# Refresh the asset registry minimally for the asset's directory
|
|
217
|
-
try:
|
|
218
|
-
asset_dir = asset_path.rsplit('/', 1)[0]
|
|
219
|
-
unreal.AssetRegistryHelpers.get_asset_registry().scan_paths_synchronous([asset_dir], True)
|
|
220
|
-
except Exception as _reg_e:
|
|
221
|
-
record_warning(f"Asset registry refresh warning: {_reg_e}")
|
|
222
|
-
|
|
223
|
-
# Small delay to ensure filesystem sync
|
|
224
|
-
time.sleep(0.1)
|
|
225
|
-
|
|
226
|
-
return saved
|
|
227
|
-
except Exception as e:
|
|
228
|
-
print(f"Error ensuring persistence: {e}")
|
|
229
|
-
record_error(f"Error ensuring persistence: {e}")
|
|
230
|
-
return False
|
|
231
|
-
|
|
232
|
-
# Stop PIE if running using modern subsystems
|
|
233
|
-
try:
|
|
234
|
-
level_subsystem = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
|
|
235
|
-
play_subsystem = None
|
|
236
|
-
try:
|
|
237
|
-
play_subsystem = unreal.get_editor_subsystem(unreal.EditorPlayWorldSubsystem)
|
|
238
|
-
except Exception:
|
|
239
|
-
play_subsystem = None
|
|
240
|
-
|
|
241
|
-
is_playing = False
|
|
242
|
-
if level_subsystem and hasattr(level_subsystem, 'is_in_play_in_editor'):
|
|
243
|
-
is_playing = level_subsystem.is_in_play_in_editor()
|
|
244
|
-
elif play_subsystem and hasattr(play_subsystem, 'is_playing_in_editor'): # type: ignore[attr-defined]
|
|
245
|
-
is_playing = play_subsystem.is_playing_in_editor() # type: ignore[attr-defined]
|
|
246
|
-
|
|
247
|
-
if is_playing:
|
|
248
|
-
print("Stopping Play In Editor mode...")
|
|
249
|
-
record_detail("Stopping Play In Editor mode")
|
|
250
|
-
if level_subsystem and hasattr(level_subsystem, 'editor_request_end_play'):
|
|
251
|
-
level_subsystem.editor_request_end_play()
|
|
252
|
-
elif play_subsystem and hasattr(play_subsystem, 'stop_playing_session'): # type: ignore[attr-defined]
|
|
253
|
-
play_subsystem.stop_playing_session() # type: ignore[attr-defined]
|
|
254
|
-
elif play_subsystem and hasattr(play_subsystem, 'end_play'): # type: ignore[attr-defined]
|
|
255
|
-
play_subsystem.end_play() # type: ignore[attr-defined]
|
|
256
|
-
else:
|
|
257
|
-
record_warning('Unable to stop Play In Editor via modern subsystems; please stop PIE manually.')
|
|
258
|
-
time.sleep(0.5)
|
|
259
|
-
except Exception as pie_error:
|
|
260
|
-
record_warning(f"PIE stop check failed: {pie_error}")
|
|
261
|
-
|
|
262
|
-
# Main execution
|
|
263
|
-
success = False
|
|
264
|
-
error_msg = ""
|
|
265
|
-
new_asset = None
|
|
266
|
-
|
|
267
|
-
# Log the attempt
|
|
268
|
-
print("Setting up ragdoll for ${meshPath}")
|
|
269
|
-
record_detail("Setting up ragdoll for ${meshPath}")
|
|
270
|
-
|
|
271
|
-
asset_path = "${path}"
|
|
272
|
-
asset_name = "${sanitizedParams.name}"
|
|
273
|
-
full_path = f"{asset_path}/{asset_name}"
|
|
274
|
-
|
|
275
|
-
try:
|
|
276
|
-
# Check if already exists
|
|
277
|
-
if unreal.EditorAssetLibrary.does_asset_exist(full_path):
|
|
278
|
-
print(f"Physics asset already exists at {full_path}")
|
|
279
|
-
record_detail(f"Physics asset already exists at {full_path}")
|
|
280
|
-
existing = unreal.EditorAssetLibrary.load_asset(full_path)
|
|
281
|
-
if existing:
|
|
282
|
-
print(f"Loaded existing PhysicsAsset: {full_path}")
|
|
283
|
-
record_detail(f"Loaded existing PhysicsAsset: {full_path}")
|
|
284
|
-
success = True
|
|
285
|
-
result["existingAsset"] = True
|
|
286
|
-
result["message"] = f"Physics asset already exists at {full_path}"
|
|
287
|
-
else:
|
|
288
|
-
# Try to load skeletal mesh first - it's required
|
|
289
|
-
skeletal_mesh_path = "${meshPath}"
|
|
290
|
-
skeletal_mesh = None
|
|
291
|
-
|
|
292
|
-
if skeletal_mesh_path and skeletal_mesh_path != "None":
|
|
293
|
-
if unreal.EditorAssetLibrary.does_asset_exist(skeletal_mesh_path):
|
|
294
|
-
asset = unreal.EditorAssetLibrary.load_asset(skeletal_mesh_path)
|
|
295
|
-
if asset:
|
|
296
|
-
if isinstance(asset, unreal.SkeletalMesh):
|
|
297
|
-
skeletal_mesh = asset
|
|
298
|
-
print(f"Loaded skeletal mesh: {skeletal_mesh_path}")
|
|
299
|
-
record_detail(f"Loaded skeletal mesh: {skeletal_mesh_path}")
|
|
300
|
-
elif isinstance(asset, unreal.Skeleton):
|
|
301
|
-
error_msg = f"Provided path is a skeleton, not a skeletal mesh: {skeletal_mesh_path}"
|
|
302
|
-
print(f"Error: {error_msg}")
|
|
303
|
-
record_error(error_msg)
|
|
304
|
-
result["message"] = error_msg
|
|
305
|
-
print("Error: Physics assets require a skeletal mesh, not just a skeleton")
|
|
306
|
-
record_warning("Physics assets require a skeletal mesh, not just a skeleton")
|
|
307
|
-
else:
|
|
308
|
-
error_msg = f"Asset is not a skeletal mesh: {skeletal_mesh_path}"
|
|
309
|
-
print(f"Warning: {error_msg}")
|
|
310
|
-
record_warning(error_msg)
|
|
311
|
-
else:
|
|
312
|
-
error_msg = f"Skeletal mesh not found at {skeletal_mesh_path}"
|
|
313
|
-
print(f"Error: {error_msg}")
|
|
314
|
-
record_error(error_msg)
|
|
315
|
-
result["message"] = error_msg
|
|
316
|
-
|
|
317
|
-
if not skeletal_mesh:
|
|
318
|
-
if not error_msg:
|
|
319
|
-
error_msg = "Cannot create physics asset without a valid skeletal mesh"
|
|
320
|
-
print(f"Error: {error_msg}")
|
|
321
|
-
record_error(error_msg)
|
|
322
|
-
if not result["message"]:
|
|
323
|
-
result["message"] = error_msg
|
|
324
|
-
else:
|
|
325
|
-
# Create physics asset using a different approach
|
|
326
|
-
# Method 1: Direct creation with initialized factory
|
|
327
|
-
try:
|
|
328
|
-
factory = unreal.PhysicsAssetFactory()
|
|
329
|
-
|
|
330
|
-
# Ensure the directory exists
|
|
331
|
-
if not unreal.EditorAssetLibrary.does_directory_exist(asset_path):
|
|
332
|
-
unreal.EditorAssetLibrary.make_directory(asset_path)
|
|
333
|
-
|
|
334
|
-
# Alternative approach: Create physics asset from skeletal mesh
|
|
335
|
-
# This is the proper way in UE5
|
|
336
|
-
try:
|
|
337
|
-
# Try modern physics asset creation methods first
|
|
338
|
-
try:
|
|
339
|
-
# Method 1: Try using SkeletalMesh editor utilities if available
|
|
340
|
-
if hasattr(unreal, 'SkeletalMeshEditorSubsystem'):
|
|
341
|
-
skel_subsystem = unreal.get_editor_subsystem(unreal.SkeletalMeshEditorSubsystem)
|
|
342
|
-
if hasattr(skel_subsystem, 'create_physics_asset'):
|
|
343
|
-
physics_asset = skel_subsystem.create_physics_asset(skeletal_mesh)
|
|
344
|
-
else:
|
|
345
|
-
# Fallback to deprecated EditorSkeletalMeshLibrary
|
|
346
|
-
physics_asset = unreal.EditorSkeletalMeshLibrary.create_physics_asset(skeletal_mesh)
|
|
347
|
-
else:
|
|
348
|
-
physics_asset = unreal.EditorSkeletalMeshLibrary.create_physics_asset(skeletal_mesh)
|
|
349
|
-
except Exception as method1_modern_error:
|
|
350
|
-
record_warning(f"Modern creation path fallback: {method1_modern_error}")
|
|
351
|
-
# Final fallback to deprecated API
|
|
352
|
-
physics_asset = unreal.EditorSkeletalMeshLibrary.create_physics_asset(skeletal_mesh)
|
|
353
|
-
except Exception as e:
|
|
354
|
-
print(f"Physics asset creation failed: {str(e)}")
|
|
355
|
-
record_error(f"Physics asset creation failed: {str(e)}")
|
|
356
|
-
physics_asset = None
|
|
357
|
-
|
|
358
|
-
if physics_asset:
|
|
359
|
-
# Move/rename the physics asset to desired location
|
|
360
|
-
source_path = physics_asset.get_path_name()
|
|
361
|
-
if unreal.EditorAssetLibrary.rename_asset(source_path, full_path):
|
|
362
|
-
print(f"Successfully created and moved PhysicsAsset to {full_path}")
|
|
363
|
-
record_detail(f"Successfully created and moved PhysicsAsset to {full_path}")
|
|
364
|
-
new_asset = physics_asset
|
|
365
|
-
|
|
366
|
-
# Ensure persistence
|
|
367
|
-
if ensure_asset_persistence(full_path):
|
|
368
|
-
# Verify it was saved
|
|
369
|
-
if unreal.EditorAssetLibrary.does_asset_exist(full_path):
|
|
370
|
-
print(f"Verified PhysicsAsset exists after save: {full_path}")
|
|
371
|
-
record_detail(f"Verified PhysicsAsset exists after save: {full_path}")
|
|
372
|
-
success = True
|
|
373
|
-
result["message"] = f"Ragdoll physics setup completed for {asset_name}"
|
|
374
|
-
else:
|
|
375
|
-
error_msg = f"PhysicsAsset not found after save: {full_path}"
|
|
376
|
-
print(f"Warning: {error_msg}")
|
|
377
|
-
record_warning(error_msg)
|
|
378
|
-
else:
|
|
379
|
-
error_msg = "Failed to persist physics asset"
|
|
380
|
-
print(f"Warning: {error_msg}")
|
|
381
|
-
record_warning(error_msg)
|
|
382
|
-
else:
|
|
383
|
-
print(f"Created PhysicsAsset but couldn't move to {full_path}")
|
|
384
|
-
record_warning(f"Created PhysicsAsset but couldn't move to {full_path}")
|
|
385
|
-
# Still consider it a success if we created it
|
|
386
|
-
new_asset = physics_asset
|
|
387
|
-
success = True
|
|
388
|
-
result["message"] = f"Physics asset created but not moved to {full_path}"
|
|
389
|
-
else:
|
|
390
|
-
error_msg = "Failed to create PhysicsAsset from skeletal mesh"
|
|
391
|
-
print(error_msg)
|
|
392
|
-
record_error(error_msg)
|
|
393
|
-
new_asset = None
|
|
394
|
-
|
|
395
|
-
successMessage: \`Skeletal mesh discovery complete\`,
|
|
396
|
-
failureMessage: \`Failed to discover skeletal mesh\`
|
|
397
|
-
record_warning(f"Method 1 failed: {str(e)}")
|
|
398
|
-
|
|
399
|
-
# Method 2: Try older approach
|
|
400
|
-
try:
|
|
401
|
-
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
|
|
402
|
-
factory = unreal.PhysicsAssetFactory()
|
|
403
|
-
|
|
404
|
-
# Try to initialize factory with the skeletal mesh
|
|
405
|
-
factory.create_physics_asset_from_skeletal_mesh = skeletal_mesh
|
|
406
|
-
|
|
407
|
-
new_asset = asset_tools.create_asset(
|
|
408
|
-
asset_name=asset_name,
|
|
409
|
-
package_path=asset_path,
|
|
410
|
-
asset_class=unreal.PhysicsAsset,
|
|
411
|
-
factory=factory
|
|
412
|
-
)
|
|
413
|
-
|
|
414
|
-
if new_asset:
|
|
415
|
-
print(f"Successfully created PhysicsAsset at {full_path} (Method 2)")
|
|
416
|
-
record_detail(f"Successfully created PhysicsAsset at {full_path} (Method 2)")
|
|
417
|
-
# Ensure persistence
|
|
418
|
-
if ensure_asset_persistence(full_path):
|
|
419
|
-
success = True
|
|
420
|
-
result["message"] = f"Ragdoll physics setup completed for {asset_name}"
|
|
421
|
-
else:
|
|
422
|
-
record_warning("Persistence check failed after Method 2 creation")
|
|
423
|
-
except Exception as e2:
|
|
424
|
-
error_msg = f"Method 2 also failed: {str(e2)}"
|
|
425
|
-
print(error_msg)
|
|
426
|
-
record_error(error_msg)
|
|
427
|
-
new_asset = None
|
|
428
|
-
|
|
429
|
-
# Final check
|
|
430
|
-
if new_asset and not success:
|
|
431
|
-
# Try one more save
|
|
432
|
-
if ensure_asset_persistence(full_path):
|
|
433
|
-
if unreal.EditorAssetLibrary.does_asset_exist(full_path):
|
|
434
|
-
success = True
|
|
435
|
-
result["message"] = f"Ragdoll physics setup completed for {asset_name}"
|
|
436
|
-
else:
|
|
437
|
-
record_warning(f"Final existence check failed for {full_path}")
|
|
438
|
-
|
|
439
|
-
except Exception as e:
|
|
440
|
-
error_msg = str(e)
|
|
441
|
-
print(f"Error: {error_msg}")
|
|
442
|
-
record_error(error_msg)
|
|
443
|
-
import traceback
|
|
444
|
-
traceback.print_exc()
|
|
445
|
-
|
|
446
|
-
# Finalize result
|
|
447
|
-
result["success"] = bool(success)
|
|
448
|
-
result["path"] = full_path if success else None
|
|
449
|
-
|
|
450
|
-
if not result["message"]:
|
|
451
|
-
if success:
|
|
452
|
-
result["message"] = f"Ragdoll physics setup completed for {asset_name}"
|
|
453
|
-
elif error_msg:
|
|
454
|
-
result["message"] = error_msg
|
|
455
|
-
else:
|
|
456
|
-
result["message"] = "Failed to setup ragdoll"
|
|
457
|
-
|
|
458
|
-
if not success:
|
|
459
|
-
if not result["error"]:
|
|
460
|
-
result["error"] = error_msg or "Unknown error"
|
|
461
|
-
|
|
462
|
-
print('RESULT:' + json.dumps(result))
|
|
463
|
-
`;
|
|
464
|
-
// Execute Python and interpret response
|
|
114
|
+
if (!this.automationBridge) {
|
|
115
|
+
throw new Error('Automation Bridge not available. Physics asset creation requires plugin support.');
|
|
116
|
+
}
|
|
465
117
|
try {
|
|
466
|
-
const response = await this.
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
118
|
+
const response = await this.automationBridge.sendAutomationRequest('setup_ragdoll', {
|
|
119
|
+
meshPath,
|
|
120
|
+
physicsAssetName: sanitizedParams.name,
|
|
121
|
+
savePath: path,
|
|
122
|
+
blendWeight: params.blendWeight,
|
|
123
|
+
constraints: params.constraints
|
|
124
|
+
}, {
|
|
125
|
+
timeoutMs: 120000
|
|
470
126
|
});
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
message: interpreted.message,
|
|
477
|
-
path: coerceString(interpreted.payload.path) ?? `${path}/${sanitizedParams.name}`
|
|
127
|
+
if (response.success === false) {
|
|
128
|
+
return {
|
|
129
|
+
success: false,
|
|
130
|
+
message: response.error || response.message || `Failed to setup ragdoll for ${sanitizedParams.name}`,
|
|
131
|
+
error: response.error || response.message || 'Failed to setup ragdoll'
|
|
478
132
|
};
|
|
479
|
-
if (interpreted.payload.existingAsset === true) {
|
|
480
|
-
successPayload.existingAsset = true;
|
|
481
|
-
}
|
|
482
|
-
if (warnings.length > 0) {
|
|
483
|
-
successPayload.warnings = warnings;
|
|
484
|
-
}
|
|
485
|
-
if (details.length > 0) {
|
|
486
|
-
successPayload.details = details;
|
|
487
|
-
}
|
|
488
|
-
return successPayload;
|
|
489
133
|
}
|
|
490
|
-
const
|
|
134
|
+
const result = response.result;
|
|
491
135
|
return {
|
|
492
|
-
success:
|
|
493
|
-
message:
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
136
|
+
success: true,
|
|
137
|
+
message: response.message || `Ragdoll physics setup completed for ${sanitizedParams.name}`,
|
|
138
|
+
path: coerceString(result?.path) ?? coerceString(result?.physicsAssetPath) ?? `${path}/${sanitizedParams.name}`,
|
|
139
|
+
existingAsset: result?.existingAsset,
|
|
140
|
+
...(result || {})
|
|
497
141
|
};
|
|
498
142
|
}
|
|
499
143
|
catch (error) {
|
|
@@ -508,15 +152,10 @@ print('RESULT:' + json.dumps(result))
|
|
|
508
152
|
return { success: false, error: `Failed to setup ragdoll: ${err}` };
|
|
509
153
|
}
|
|
510
154
|
}
|
|
511
|
-
/**
|
|
512
|
-
* Create Physics Constraint
|
|
513
|
-
*/
|
|
514
155
|
async createConstraint(params) {
|
|
515
156
|
try {
|
|
516
|
-
// Spawn constraint actor
|
|
517
157
|
const spawnCmd = `spawnactor /Script/Engine.PhysicsConstraintActor ${params.location[0]} ${params.location[1]} ${params.location[2]}`;
|
|
518
158
|
await this.bridge.executeConsoleCommand(spawnCmd);
|
|
519
|
-
// Configure constraint
|
|
520
159
|
const commands = [
|
|
521
160
|
`SetConstraintActors ${params.name} ${params.actor1} ${params.actor2}`,
|
|
522
161
|
`SetConstraintType ${params.name} ${params.constraintType}`
|
|
@@ -549,25 +188,19 @@ print('RESULT:' + json.dumps(result))
|
|
|
549
188
|
return { success: false, error: `Failed to create constraint: ${err}` };
|
|
550
189
|
}
|
|
551
190
|
}
|
|
552
|
-
/**
|
|
553
|
-
* Setup Chaos Destruction
|
|
554
|
-
*/
|
|
555
191
|
async setupDestruction(params) {
|
|
556
192
|
try {
|
|
557
193
|
const path = params.savePath || '/Game/Destruction';
|
|
558
194
|
const commands = [
|
|
559
195
|
`CreateGeometryCollection ${params.destructionName} ${params.meshPath} ${path}`
|
|
560
196
|
];
|
|
561
|
-
// Configure fracture
|
|
562
197
|
if (params.fractureSettings) {
|
|
563
198
|
const settings = params.fractureSettings;
|
|
564
199
|
commands.push(`FractureGeometry ${params.destructionName} ${settings.cellCount} ${settings.minimumVolumeSize} ${settings.seed}`);
|
|
565
200
|
}
|
|
566
|
-
// Set damage threshold
|
|
567
201
|
if (params.damageThreshold) {
|
|
568
202
|
commands.push(`SetDamageThreshold ${params.destructionName} ${params.damageThreshold}`);
|
|
569
203
|
}
|
|
570
|
-
// Set debris lifetime
|
|
571
204
|
if (params.debrisLifetime) {
|
|
572
205
|
commands.push(`SetDebrisLifetime ${params.destructionName} ${params.debrisLifetime}`);
|
|
573
206
|
}
|
|
@@ -582,199 +215,134 @@ print('RESULT:' + json.dumps(result))
|
|
|
582
215
|
return { success: false, error: `Failed to setup destruction: ${err}` };
|
|
583
216
|
}
|
|
584
217
|
}
|
|
585
|
-
/**
|
|
586
|
-
* Configure Vehicle Physics
|
|
587
|
-
*/
|
|
588
218
|
async configureVehicle(params) {
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
219
|
+
const rawParams = params;
|
|
220
|
+
const pluginDeps = Array.isArray(params.pluginDependencies) && params.pluginDependencies.length > 0
|
|
221
|
+
? params.pluginDependencies
|
|
222
|
+
: (Array.isArray(rawParams.plugins) && rawParams.plugins.length > 0 ? rawParams.plugins : undefined);
|
|
223
|
+
if (pluginDeps && pluginDeps.length > 0) {
|
|
224
|
+
return {
|
|
225
|
+
success: false,
|
|
226
|
+
error: 'MISSING_ENGINE_PLUGINS',
|
|
227
|
+
missingPlugins: pluginDeps,
|
|
228
|
+
message: `Required engine plugins not enabled: ${pluginDeps.join(', ')}`
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
const warnings = [];
|
|
232
|
+
const hasExplicitEmptyWheels = Array.isArray(params.wheels) && params.wheels.length === 0;
|
|
233
|
+
const effectiveVehicleType = typeof params.vehicleType === 'string' && params.vehicleType.trim().length > 0
|
|
234
|
+
? params.vehicleType
|
|
235
|
+
: 'Car';
|
|
236
|
+
const commands = [
|
|
237
|
+
`CreateVehicle ${params.vehicleName} ${effectiveVehicleType}`
|
|
238
|
+
];
|
|
239
|
+
if (Array.isArray(params.wheels) && params.wheels.length > 0) {
|
|
240
|
+
for (const wheel of params.wheels) {
|
|
241
|
+
commands.push(`AddVehicleWheel ${params.vehicleName} ${wheel.name} ${wheel.radius} ${wheel.width} ${wheel.mass}`);
|
|
242
|
+
if (wheel.isSteering) {
|
|
243
|
+
commands.push(`SetWheelSteering ${params.vehicleName} ${wheel.name} true`);
|
|
603
244
|
}
|
|
245
|
+
if (wheel.isDriving) {
|
|
246
|
+
commands.push(`SetWheelDriving ${params.vehicleName} ${wheel.name} true`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const effectiveEngine = params.engine ?? ((typeof rawParams.maxRPM === 'number' || Array.isArray(rawParams.torqueCurve))
|
|
251
|
+
? { maxRPM: rawParams.maxRPM, torqueCurve: rawParams.torqueCurve }
|
|
252
|
+
: undefined);
|
|
253
|
+
if (effectiveEngine) {
|
|
254
|
+
let maxRPM = typeof effectiveEngine.maxRPM === 'number' ? effectiveEngine.maxRPM : 0;
|
|
255
|
+
if (maxRPM < 0) {
|
|
256
|
+
maxRPM = 0;
|
|
257
|
+
warnings.push('Engine maxRPM was negative and has been clamped to 0.');
|
|
604
258
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
259
|
+
commands.push(`SetEngineMaxRPM ${params.vehicleName} ${maxRPM}`);
|
|
260
|
+
const rawCurve = Array.isArray(effectiveEngine.torqueCurve) ? effectiveEngine.torqueCurve : [];
|
|
261
|
+
for (const point of rawCurve) {
|
|
262
|
+
let rpm;
|
|
263
|
+
let torque;
|
|
264
|
+
if (Array.isArray(point) && point.length >= 2) {
|
|
265
|
+
rpm = Number(point[0]);
|
|
266
|
+
torque = Number(point[1]);
|
|
267
|
+
}
|
|
268
|
+
else if (point && typeof point === 'object') {
|
|
269
|
+
const anyPoint = point;
|
|
270
|
+
rpm = typeof anyPoint.rpm === 'number' ? anyPoint.rpm : undefined;
|
|
271
|
+
torque = typeof anyPoint.torque === 'number' ? anyPoint.torque : undefined;
|
|
272
|
+
}
|
|
273
|
+
if (typeof rpm === 'number' && typeof torque === 'number') {
|
|
609
274
|
commands.push(`AddTorqueCurvePoint ${params.vehicleName} ${rpm} ${torque}`);
|
|
610
275
|
}
|
|
611
276
|
}
|
|
612
|
-
|
|
613
|
-
|
|
277
|
+
}
|
|
278
|
+
if (params.transmission) {
|
|
279
|
+
if (Array.isArray(params.transmission.gears)) {
|
|
614
280
|
for (let i = 0; i < params.transmission.gears.length; i++) {
|
|
615
281
|
commands.push(`SetGearRatio ${params.vehicleName} ${i} ${params.transmission.gears[i]}`);
|
|
616
282
|
}
|
|
283
|
+
}
|
|
284
|
+
if (typeof params.transmission.finalDriveRatio === 'number') {
|
|
617
285
|
commands.push(`SetFinalDriveRatio ${params.vehicleName} ${params.transmission.finalDriveRatio}`);
|
|
618
286
|
}
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
619
289
|
await this.bridge.executeConsoleCommands(commands);
|
|
620
|
-
return {
|
|
621
|
-
success: true,
|
|
622
|
-
message: `Vehicle ${params.vehicleName} configured`
|
|
623
|
-
};
|
|
624
290
|
}
|
|
625
|
-
catch (
|
|
626
|
-
|
|
291
|
+
catch (_error) {
|
|
292
|
+
if (warnings.length === 0) {
|
|
293
|
+
warnings.push('Vehicle configuration commands could not be executed; using engine defaults.');
|
|
294
|
+
}
|
|
627
295
|
}
|
|
296
|
+
if (hasExplicitEmptyWheels) {
|
|
297
|
+
warnings.push('No wheels specified; using default wheels from vehicle preset.');
|
|
298
|
+
}
|
|
299
|
+
if (warnings.length === 0) {
|
|
300
|
+
warnings.push('Verify wheel class assignments and offsets in the vehicle movement component to ensure they match your project defaults.');
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
success: true,
|
|
304
|
+
message: `Vehicle ${params.vehicleName} configured`,
|
|
305
|
+
warnings
|
|
306
|
+
};
|
|
628
307
|
}
|
|
629
|
-
/**
|
|
630
|
-
* Apply Force or Impulse to Actor
|
|
631
|
-
*/
|
|
632
308
|
async applyForce(params) {
|
|
309
|
+
if (!this.automationBridge) {
|
|
310
|
+
throw new Error('Automation Bridge not available. Physics force application requires plugin support.');
|
|
311
|
+
}
|
|
633
312
|
try {
|
|
634
|
-
|
|
635
|
-
const
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
result["message"] = "Cannot apply physics while in Play In Editor mode. Please stop PIE first."
|
|
646
|
-
print(f"RESULT:{json.dumps(result)}")
|
|
647
|
-
# Exit early from this script
|
|
648
|
-
raise SystemExit(0)
|
|
649
|
-
except SystemExit:
|
|
650
|
-
# Re-raise the SystemExit to exit properly
|
|
651
|
-
raise
|
|
652
|
-
except:
|
|
653
|
-
pass # Continue if we can't check PIE state
|
|
654
|
-
|
|
655
|
-
try:
|
|
656
|
-
actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
|
|
657
|
-
actors = actor_subsystem.get_all_level_actors()
|
|
658
|
-
search_name = "${params.actorName}"
|
|
659
|
-
|
|
660
|
-
for actor in actors:
|
|
661
|
-
if actor:
|
|
662
|
-
# Check both actor name and label with case-insensitive partial matching
|
|
663
|
-
actor_name = actor.get_name()
|
|
664
|
-
actor_label = actor.get_actor_label()
|
|
665
|
-
|
|
666
|
-
if (search_name.lower() in actor_label.lower() or
|
|
667
|
-
actor_label.lower().startswith(search_name.lower() + "_") or
|
|
668
|
-
actor_label.lower() == search_name.lower() or
|
|
669
|
-
actor_name.lower() == search_name.lower()):
|
|
670
|
-
|
|
671
|
-
result["actor_found"] = True
|
|
672
|
-
# Get the primitive component if it exists
|
|
673
|
-
root = actor.get_editor_property('root_component')
|
|
674
|
-
|
|
675
|
-
if root and isinstance(root, unreal.PrimitiveComponent):
|
|
676
|
-
# Check if the component is static or movable
|
|
677
|
-
mobility = root.get_editor_property('mobility')
|
|
678
|
-
if mobility == unreal.ComponentMobility.STATIC:
|
|
679
|
-
# Try to set to movable first
|
|
680
|
-
try:
|
|
681
|
-
root.set_editor_property('mobility', unreal.ComponentMobility.MOVABLE)
|
|
682
|
-
except:
|
|
683
|
-
result["message"] = f"Actor {actor_label} has static mobility and cannot simulate physics"
|
|
684
|
-
break
|
|
685
|
-
|
|
686
|
-
# Ensure physics is enabled
|
|
687
|
-
try:
|
|
688
|
-
root.set_simulate_physics(True)
|
|
689
|
-
result["physics_enabled"] = True
|
|
690
|
-
except Exception as physics_err:
|
|
691
|
-
# If we can't enable physics, try applying force anyway (some actors respond without physics sim)
|
|
692
|
-
result["physics_enabled"] = False
|
|
693
|
-
|
|
694
|
-
force = unreal.Vector(${params.vector[0]}, ${params.vector[1]}, ${params.vector[2]})
|
|
695
|
-
|
|
696
|
-
if "${params.forceType}" == "Force":
|
|
697
|
-
root.add_force(force, 'None', False)
|
|
698
|
-
result["success"] = True
|
|
699
|
-
result["message"] = f"Applied Force to {actor_label}: {force}"
|
|
700
|
-
elif "${params.forceType}" == "Impulse":
|
|
701
|
-
root.add_impulse(force, 'None', False)
|
|
702
|
-
result["success"] = True
|
|
703
|
-
result["message"] = f"Applied Impulse to {actor_label}: {force}"
|
|
704
|
-
elif "${params.forceType}" == "Velocity":
|
|
705
|
-
root.set_physics_linear_velocity(force)
|
|
706
|
-
result["success"] = True
|
|
707
|
-
result["message"] = f"Set Velocity on {actor_label}: {force}"
|
|
708
|
-
elif "${params.forceType}" == "Torque":
|
|
709
|
-
root.add_torque_in_radians(force, 'None', False)
|
|
710
|
-
result["success"] = True
|
|
711
|
-
result["message"] = f"Applied Torque to {actor_label}: {force}"
|
|
712
|
-
else:
|
|
713
|
-
result["message"] = f"Actor {actor_label} doesn't have a physics-enabled component"
|
|
714
|
-
break
|
|
715
|
-
|
|
716
|
-
if not result["actor_found"]:
|
|
717
|
-
result["message"] = f"Actor not found: {search_name}"
|
|
718
|
-
# List actors with physics enabled for debugging
|
|
719
|
-
physics_actors = []
|
|
720
|
-
for actor in actors[:20]:
|
|
721
|
-
if actor:
|
|
722
|
-
label = actor.get_actor_label()
|
|
723
|
-
if "mesh" in label.lower() or "cube" in label.lower() or "static" in label.lower():
|
|
724
|
-
physics_actors.append(label)
|
|
725
|
-
if physics_actors:
|
|
726
|
-
result["available_actors"] = physics_actors
|
|
727
|
-
|
|
728
|
-
except Exception as e:
|
|
729
|
-
result["message"] = f"Error applying force: {e}"
|
|
730
|
-
|
|
731
|
-
print(f"RESULT:{json.dumps(result)}")
|
|
732
|
-
`.trim();
|
|
733
|
-
const response = await this.bridge.executePython(pythonCode);
|
|
734
|
-
const interpreted = interpretStandardResult(response, {
|
|
735
|
-
successMessage: `Applied ${params.forceType} to ${params.actorName}`,
|
|
736
|
-
failureMessage: 'Force application failed'
|
|
313
|
+
const zeroVector = [0, 0, 0];
|
|
314
|
+
const normalizedVector = wasmIntegration.vectorAdd(zeroVector, params.vector);
|
|
315
|
+
console.error('[WASM] Using vectorAdd for physics force vector processing');
|
|
316
|
+
const response = await this.automationBridge.sendAutomationRequest('apply_force', {
|
|
317
|
+
actorName: params.actorName,
|
|
318
|
+
forceType: params.forceType,
|
|
319
|
+
vector: normalizedVector,
|
|
320
|
+
boneName: params.boneName,
|
|
321
|
+
isLocal: params.isLocal
|
|
322
|
+
}, {
|
|
323
|
+
timeoutMs: 30000
|
|
737
324
|
});
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
return {
|
|
741
|
-
success: true,
|
|
742
|
-
message: interpreted.message,
|
|
743
|
-
availableActors,
|
|
744
|
-
details: interpreted.details
|
|
745
|
-
};
|
|
746
|
-
}
|
|
747
|
-
const fallbackText = bestEffortInterpretedText(interpreted) ?? '';
|
|
748
|
-
if (/Applied/i.test(fallbackText)) {
|
|
749
|
-
return {
|
|
750
|
-
success: true,
|
|
751
|
-
message: fallbackText || interpreted.message,
|
|
752
|
-
availableActors,
|
|
753
|
-
details: interpreted.details
|
|
754
|
-
};
|
|
755
|
-
}
|
|
756
|
-
if (/not found/i.test(fallbackText) || /error/i.test(fallbackText)) {
|
|
325
|
+
if (response.success === false) {
|
|
326
|
+
const result = response.result;
|
|
757
327
|
return {
|
|
758
328
|
success: false,
|
|
759
|
-
error:
|
|
760
|
-
availableActors,
|
|
761
|
-
details:
|
|
329
|
+
error: response.error || response.message || 'Force application failed',
|
|
330
|
+
availableActors: result?.available_actors ? coerceStringArray(result.available_actors) : undefined,
|
|
331
|
+
details: result?.details
|
|
762
332
|
};
|
|
763
333
|
}
|
|
334
|
+
const result = response.result;
|
|
764
335
|
return {
|
|
765
|
-
success:
|
|
766
|
-
|
|
767
|
-
availableActors,
|
|
768
|
-
|
|
336
|
+
success: true,
|
|
337
|
+
message: response.message || `Applied ${params.forceType} to ${params.actorName}`,
|
|
338
|
+
availableActors: result?.available_actors ? coerceStringArray(result.available_actors) : undefined,
|
|
339
|
+
...(result || {})
|
|
769
340
|
};
|
|
770
341
|
}
|
|
771
342
|
catch (err) {
|
|
772
343
|
return { success: false, error: `Failed to apply force: ${err}` };
|
|
773
344
|
}
|
|
774
345
|
}
|
|
775
|
-
/**
|
|
776
|
-
* Configure Cloth Simulation
|
|
777
|
-
*/
|
|
778
346
|
async setupCloth(params) {
|
|
779
347
|
try {
|
|
780
348
|
const commands = [
|
|
@@ -813,9 +381,6 @@ print(f"RESULT:{json.dumps(result)}")
|
|
|
813
381
|
return { success: false, error: `Failed to setup cloth: ${err}` };
|
|
814
382
|
}
|
|
815
383
|
}
|
|
816
|
-
/**
|
|
817
|
-
* Create Fluid Simulation (Niagara-based)
|
|
818
|
-
*/
|
|
819
384
|
async createFluidSimulation(params) {
|
|
820
385
|
try {
|
|
821
386
|
const locStr = `${params.location[0]} ${params.location[1]} ${params.location[2]}`;
|
|
@@ -852,5 +417,33 @@ print(f"RESULT:{json.dumps(result)}")
|
|
|
852
417
|
return { success: false, error: `Failed to create fluid simulation: ${err}` };
|
|
853
418
|
}
|
|
854
419
|
}
|
|
420
|
+
async setupPhysicsSimulation(params) {
|
|
421
|
+
if (!this.automationBridge) {
|
|
422
|
+
throw new Error('Automation Bridge not available. Physics asset creation requires plugin support.');
|
|
423
|
+
}
|
|
424
|
+
try {
|
|
425
|
+
const response = await this.automationBridge.sendAutomationRequest('animation_physics', {
|
|
426
|
+
action: 'setup_physics_simulation',
|
|
427
|
+
...params
|
|
428
|
+
}, {
|
|
429
|
+
timeoutMs: 60000
|
|
430
|
+
});
|
|
431
|
+
if (response.success === false) {
|
|
432
|
+
return {
|
|
433
|
+
success: false,
|
|
434
|
+
message: response.error || response.message || 'Failed to setup physics simulation',
|
|
435
|
+
error: response.error || response.message
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
return {
|
|
439
|
+
success: true,
|
|
440
|
+
message: response.message || 'Physics simulation setup completed',
|
|
441
|
+
...(response.result || {})
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
catch (err) {
|
|
445
|
+
return { success: false, error: `Failed to setup physics simulation: ${err}` };
|
|
446
|
+
}
|
|
447
|
+
}
|
|
855
448
|
}
|
|
856
449
|
//# sourceMappingURL=physics.js.map
|