unreal-engine-mcp-server 0.4.6 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +26 -0
- package/.env.production +38 -7
- package/.eslintrc.json +0 -54
- package/.eslintrc.override.json +8 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +94 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +56 -0
- package/.github/copilot-instructions.md +478 -45
- package/.github/dependabot.yml +19 -0
- package/.github/labeler.yml +24 -0
- package/.github/labels.yml +70 -0
- package/.github/pull_request_template.md +42 -0
- package/.github/release-drafter.yml +148 -0
- package/.github/workflows/auto-merge.yml +38 -0
- package/.github/workflows/ci.yml +38 -0
- package/.github/workflows/dependency-review.yml +17 -0
- package/.github/workflows/gemini-issue-triage.yml +172 -0
- package/.github/workflows/greetings.yml +23 -0
- package/.github/workflows/labeler.yml +16 -0
- package/.github/workflows/links.yml +80 -0
- package/.github/workflows/pr-size-labeler.yml +137 -0
- package/.github/workflows/publish-mcp.yml +12 -7
- package/.github/workflows/release-drafter.yml +23 -0
- package/.github/workflows/release.yml +112 -0
- package/.github/workflows/semantic-pull-request.yml +35 -0
- package/.github/workflows/smoke-test.yml +36 -0
- package/.github/workflows/stale.yml +28 -0
- package/CHANGELOG.md +269 -22
- package/CONTRIBUTING.md +140 -0
- package/README.md +166 -72
- package/claude_desktop_config_example.json +7 -6
- package/dist/automation/bridge.d.ts +50 -0
- package/dist/automation/bridge.js +452 -0
- package/dist/automation/connection-manager.d.ts +23 -0
- package/dist/automation/connection-manager.js +107 -0
- package/dist/automation/handshake.d.ts +11 -0
- package/dist/automation/handshake.js +89 -0
- package/dist/automation/index.d.ts +3 -0
- package/dist/automation/index.js +3 -0
- package/dist/automation/message-handler.d.ts +12 -0
- package/dist/automation/message-handler.js +149 -0
- package/dist/automation/request-tracker.d.ts +25 -0
- package/dist/automation/request-tracker.js +98 -0
- package/dist/automation/types.d.ts +130 -0
- package/dist/automation/types.js +2 -0
- package/dist/cli.js +32 -5
- package/dist/config.d.ts +27 -0
- package/dist/config.js +60 -0
- package/dist/constants.d.ts +12 -0
- package/dist/constants.js +12 -0
- package/dist/graphql/resolvers.d.ts +268 -0
- package/dist/graphql/resolvers.js +743 -0
- package/dist/graphql/schema.d.ts +5 -0
- package/dist/graphql/schema.js +437 -0
- package/dist/graphql/server.d.ts +26 -0
- package/dist/graphql/server.js +115 -0
- package/dist/graphql/types.d.ts +7 -0
- package/dist/graphql/types.js +2 -0
- package/dist/handlers/resource-handlers.d.ts +20 -0
- package/dist/handlers/resource-handlers.js +180 -0
- package/dist/index.d.ts +31 -18
- package/dist/index.js +119 -604
- package/dist/prompts/index.js +4 -4
- package/dist/resources/actors.d.ts +17 -12
- package/dist/resources/actors.js +56 -76
- package/dist/resources/assets.d.ts +6 -14
- package/dist/resources/assets.js +115 -147
- package/dist/resources/levels.d.ts +13 -13
- package/dist/resources/levels.js +25 -34
- package/dist/server/resource-registry.d.ts +20 -0
- package/dist/server/resource-registry.js +37 -0
- package/dist/server/tool-registry.d.ts +23 -0
- package/dist/server/tool-registry.js +322 -0
- package/dist/server-setup.d.ts +21 -0
- package/dist/server-setup.js +111 -0
- package/dist/services/health-monitor.d.ts +34 -0
- package/dist/services/health-monitor.js +105 -0
- package/dist/services/metrics-server.d.ts +11 -0
- package/dist/services/metrics-server.js +105 -0
- package/dist/tools/actors.d.ts +147 -9
- package/dist/tools/actors.js +350 -311
- package/dist/tools/animation.d.ts +135 -4
- package/dist/tools/animation.js +510 -411
- package/dist/tools/assets.d.ts +117 -19
- package/dist/tools/assets.js +259 -284
- package/dist/tools/audio.d.ts +102 -42
- package/dist/tools/audio.js +272 -685
- package/dist/tools/base-tool.d.ts +17 -0
- package/dist/tools/base-tool.js +46 -0
- package/dist/tools/behavior-tree.d.ts +94 -0
- package/dist/tools/behavior-tree.js +39 -0
- package/dist/tools/blueprint/helpers.d.ts +29 -0
- package/dist/tools/blueprint/helpers.js +182 -0
- package/dist/tools/blueprint.d.ts +228 -118
- package/dist/tools/blueprint.js +685 -832
- package/dist/tools/consolidated-tool-definitions.d.ts +5475 -1627
- package/dist/tools/consolidated-tool-definitions.js +829 -482
- package/dist/tools/consolidated-tool-handlers.d.ts +2 -1
- package/dist/tools/consolidated-tool-handlers.js +211 -1009
- package/dist/tools/debug.d.ts +143 -85
- package/dist/tools/debug.js +234 -180
- package/dist/tools/dynamic-handler-registry.d.ts +11 -0
- package/dist/tools/dynamic-handler-registry.js +101 -0
- package/dist/tools/editor.d.ts +139 -18
- package/dist/tools/editor.js +239 -244
- package/dist/tools/engine.d.ts +10 -4
- package/dist/tools/engine.js +13 -5
- package/dist/tools/environment.d.ts +36 -0
- package/dist/tools/environment.js +267 -0
- package/dist/tools/foliage.d.ts +105 -14
- package/dist/tools/foliage.js +219 -331
- package/dist/tools/handlers/actor-handlers.d.ts +3 -0
- package/dist/tools/handlers/actor-handlers.js +232 -0
- package/dist/tools/handlers/animation-handlers.d.ts +3 -0
- package/dist/tools/handlers/animation-handlers.js +185 -0
- package/dist/tools/handlers/argument-helper.d.ts +16 -0
- package/dist/tools/handlers/argument-helper.js +80 -0
- package/dist/tools/handlers/asset-handlers.d.ts +3 -0
- package/dist/tools/handlers/asset-handlers.js +496 -0
- package/dist/tools/handlers/audio-handlers.d.ts +3 -0
- package/dist/tools/handlers/audio-handlers.js +166 -0
- package/dist/tools/handlers/blueprint-handlers.d.ts +4 -0
- package/dist/tools/handlers/blueprint-handlers.js +358 -0
- package/dist/tools/handlers/common-handlers.d.ts +14 -0
- package/dist/tools/handlers/common-handlers.js +56 -0
- package/dist/tools/handlers/editor-handlers.d.ts +3 -0
- package/dist/tools/handlers/editor-handlers.js +119 -0
- package/dist/tools/handlers/effect-handlers.d.ts +3 -0
- package/dist/tools/handlers/effect-handlers.js +171 -0
- package/dist/tools/handlers/environment-handlers.d.ts +3 -0
- package/dist/tools/handlers/environment-handlers.js +170 -0
- package/dist/tools/handlers/graph-handlers.d.ts +3 -0
- package/dist/tools/handlers/graph-handlers.js +90 -0
- package/dist/tools/handlers/input-handlers.d.ts +3 -0
- package/dist/tools/handlers/input-handlers.js +21 -0
- package/dist/tools/handlers/inspect-handlers.d.ts +3 -0
- package/dist/tools/handlers/inspect-handlers.js +383 -0
- package/dist/tools/handlers/level-handlers.d.ts +3 -0
- package/dist/tools/handlers/level-handlers.js +237 -0
- package/dist/tools/handlers/lighting-handlers.d.ts +3 -0
- package/dist/tools/handlers/lighting-handlers.js +144 -0
- package/dist/tools/handlers/performance-handlers.d.ts +3 -0
- package/dist/tools/handlers/performance-handlers.js +130 -0
- package/dist/tools/handlers/pipeline-handlers.d.ts +3 -0
- package/dist/tools/handlers/pipeline-handlers.js +110 -0
- package/dist/tools/handlers/sequence-handlers.d.ts +3 -0
- package/dist/tools/handlers/sequence-handlers.js +376 -0
- package/dist/tools/handlers/system-handlers.d.ts +4 -0
- package/dist/tools/handlers/system-handlers.js +506 -0
- package/dist/tools/input.d.ts +19 -0
- package/dist/tools/input.js +89 -0
- package/dist/tools/introspection.d.ts +103 -40
- package/dist/tools/introspection.js +425 -568
- package/dist/tools/landscape.d.ts +97 -36
- package/dist/tools/landscape.js +280 -409
- package/dist/tools/level.d.ts +130 -10
- package/dist/tools/level.js +639 -675
- package/dist/tools/lighting.d.ts +77 -38
- package/dist/tools/lighting.js +441 -943
- package/dist/tools/logs.d.ts +45 -0
- package/dist/tools/logs.js +210 -0
- package/dist/tools/materials.d.ts +91 -24
- package/dist/tools/materials.js +190 -118
- package/dist/tools/niagara.d.ts +149 -39
- package/dist/tools/niagara.js +232 -182
- package/dist/tools/performance.d.ts +27 -12
- package/dist/tools/performance.js +204 -122
- package/dist/tools/physics.d.ts +32 -77
- package/dist/tools/physics.js +171 -582
- package/dist/tools/property-dictionary.d.ts +13 -0
- package/dist/tools/property-dictionary.js +82 -0
- package/dist/tools/sequence.d.ts +73 -48
- package/dist/tools/sequence.js +196 -748
- package/dist/tools/tool-definition-utils.d.ts +59 -0
- package/dist/tools/tool-definition-utils.js +35 -0
- package/dist/tools/ui.d.ts +66 -34
- package/dist/tools/ui.js +134 -214
- package/dist/types/env.d.ts +0 -3
- package/dist/types/env.js +0 -7
- package/dist/types/tool-interfaces.d.ts +898 -0
- package/dist/types/tool-interfaces.js +2 -0
- package/dist/types/tool-types.d.ts +195 -11
- package/dist/types/tool-types.js +0 -4
- package/dist/unreal-bridge.d.ts +24 -131
- package/dist/unreal-bridge.js +364 -1506
- package/dist/utils/command-validator.d.ts +9 -0
- package/dist/utils/command-validator.js +67 -0
- package/dist/utils/elicitation.d.ts +1 -1
- package/dist/utils/elicitation.js +12 -15
- package/dist/utils/error-handler.d.ts +2 -51
- package/dist/utils/error-handler.js +11 -87
- package/dist/utils/ini-reader.d.ts +3 -0
- package/dist/utils/ini-reader.js +69 -0
- package/dist/utils/logger.js +9 -6
- package/dist/utils/normalize.d.ts +3 -0
- package/dist/utils/normalize.js +56 -0
- package/dist/utils/response-factory.d.ts +7 -0
- package/dist/utils/response-factory.js +33 -0
- package/dist/utils/response-validator.d.ts +3 -24
- package/dist/utils/response-validator.js +130 -81
- package/dist/utils/result-helpers.d.ts +4 -5
- package/dist/utils/result-helpers.js +15 -16
- package/dist/utils/safe-json.js +5 -11
- package/dist/utils/unreal-command-queue.d.ts +24 -0
- package/dist/utils/unreal-command-queue.js +120 -0
- package/dist/utils/validation.d.ts +0 -40
- package/dist/utils/validation.js +1 -78
- package/dist/wasm/index.d.ts +70 -0
- package/dist/wasm/index.js +535 -0
- package/docs/GraphQL-API.md +888 -0
- package/docs/Migration-Guide-v0.5.0.md +692 -0
- package/docs/Roadmap.md +53 -0
- package/docs/WebAssembly-Integration.md +628 -0
- package/docs/editor-plugin-extension.md +370 -0
- package/docs/handler-mapping.md +242 -0
- package/docs/native-automation-progress.md +128 -0
- package/docs/testing-guide.md +423 -0
- package/mcp-config-example.json +6 -6
- package/package.json +60 -27
- package/plugins/McpAutomationBridge/Config/FilterPlugin.ini +8 -0
- package/plugins/McpAutomationBridge/McpAutomationBridge.uplugin +64 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/McpAutomationBridge.Build.cs +189 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.cpp +22 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.h +30 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeHelpers.h +1983 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeModule.cpp +72 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSettings.cpp +46 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +581 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +2394 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetQueryHandlers.cpp +300 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetWorkflowHandlers.cpp +2807 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AudioHandlers.cpp +1087 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BehaviorTreeHandlers.cpp +488 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.cpp +643 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.h +31 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +1184 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +5652 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers_List.cpp +152 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ControlHandlers.cpp +2614 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_DebugHandlers.cpp +42 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EditorFunctionHandlers.cpp +1237 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +1701 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +2145 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_FoliageHandlers.cpp +954 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InputHandlers.cpp +209 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InsightsHandlers.cpp +41 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LandscapeHandlers.cpp +1164 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LevelHandlers.cpp +762 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +634 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LogHandlers.cpp +136 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_MaterialGraphHandlers.cpp +494 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraGraphHandlers.cpp +278 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraHandlers.cpp +625 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PerformanceHandlers.cpp +401 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PipelineHandlers.cpp +67 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +735 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PropertyHandlers.cpp +2634 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_RenderHandlers.cpp +189 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.cpp +917 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.h +39 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +2670 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequencerHandlers.cpp +519 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_TestHandlers.cpp +38 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_UiHandlers.cpp +668 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_WorldPartitionHandlers.cpp +346 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +1330 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.h +149 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +783 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSettings.h +115 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSubsystem.h +796 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpConnectionManager.h +117 -0
- package/scripts/check-unreal-connection.mjs +19 -0
- package/scripts/clean-tmp.js +23 -0
- package/scripts/patch-wasm.js +26 -0
- package/scripts/run-all-tests.mjs +131 -0
- package/scripts/smoke-test.ts +94 -0
- package/scripts/sync-mcp-plugin.js +143 -0
- package/scripts/test-no-plugin-alternates.mjs +113 -0
- package/scripts/validate-server.js +46 -0
- package/scripts/verify-automation-bridge.js +200 -0
- package/server.json +57 -21
- package/src/automation/bridge.ts +558 -0
- package/src/automation/connection-manager.ts +130 -0
- package/src/automation/handshake.ts +99 -0
- package/src/automation/index.ts +2 -0
- package/src/automation/message-handler.ts +167 -0
- package/src/automation/request-tracker.ts +123 -0
- package/src/automation/types.ts +107 -0
- package/src/cli.ts +33 -6
- package/src/config.ts +73 -0
- package/src/constants.ts +12 -0
- package/src/graphql/resolvers.ts +1010 -0
- package/src/graphql/schema.ts +452 -0
- package/src/graphql/server.ts +154 -0
- package/src/graphql/types.ts +7 -0
- package/src/handlers/resource-handlers.ts +186 -0
- package/src/index.ts +152 -649
- package/src/prompts/index.ts +4 -4
- package/src/resources/actors.ts +58 -76
- package/src/resources/assets.ts +147 -134
- package/src/resources/levels.ts +28 -33
- package/src/server/resource-registry.ts +47 -0
- package/src/server/tool-registry.ts +354 -0
- package/src/server-setup.ts +148 -0
- package/src/services/health-monitor.ts +132 -0
- package/src/services/metrics-server.ts +142 -0
- package/src/tools/actors.ts +417 -322
- package/src/tools/animation.ts +671 -461
- package/src/tools/assets.ts +353 -289
- package/src/tools/audio.ts +323 -766
- package/src/tools/base-tool.ts +52 -0
- package/src/tools/behavior-tree.ts +45 -0
- package/src/tools/blueprint/helpers.ts +189 -0
- package/src/tools/blueprint.ts +787 -965
- package/src/tools/consolidated-tool-definitions.ts +993 -500
- package/src/tools/consolidated-tool-handlers.ts +272 -1122
- package/src/tools/debug.ts +292 -187
- package/src/tools/dynamic-handler-registry.ts +151 -0
- package/src/tools/editor.ts +309 -246
- package/src/tools/engine.ts +14 -3
- package/src/tools/environment.ts +287 -0
- package/src/tools/foliage.ts +314 -379
- package/src/tools/handlers/actor-handlers.ts +271 -0
- package/src/tools/handlers/animation-handlers.ts +237 -0
- package/src/tools/handlers/argument-helper.ts +142 -0
- package/src/tools/handlers/asset-handlers.ts +532 -0
- package/src/tools/handlers/audio-handlers.ts +194 -0
- package/src/tools/handlers/blueprint-handlers.ts +380 -0
- package/src/tools/handlers/common-handlers.ts +87 -0
- package/src/tools/handlers/editor-handlers.ts +123 -0
- package/src/tools/handlers/effect-handlers.ts +220 -0
- package/src/tools/handlers/environment-handlers.ts +183 -0
- package/src/tools/handlers/graph-handlers.ts +116 -0
- package/src/tools/handlers/input-handlers.ts +28 -0
- package/src/tools/handlers/inspect-handlers.ts +450 -0
- package/src/tools/handlers/level-handlers.ts +252 -0
- package/src/tools/handlers/lighting-handlers.ts +147 -0
- package/src/tools/handlers/performance-handlers.ts +132 -0
- package/src/tools/handlers/pipeline-handlers.ts +127 -0
- package/src/tools/handlers/sequence-handlers.ts +415 -0
- package/src/tools/handlers/system-handlers.ts +564 -0
- package/src/tools/input.ts +101 -0
- package/src/tools/introspection.ts +493 -584
- package/src/tools/landscape.ts +394 -489
- package/src/tools/level.ts +752 -694
- package/src/tools/lighting.ts +583 -984
- package/src/tools/logs.ts +219 -0
- package/src/tools/materials.ts +231 -121
- package/src/tools/niagara.ts +293 -168
- package/src/tools/performance.ts +320 -168
- package/src/tools/physics.ts +268 -613
- package/src/tools/property-dictionary.ts +98 -0
- package/src/tools/sequence.ts +255 -815
- package/src/tools/tool-definition-utils.ts +35 -0
- package/src/tools/ui.ts +207 -283
- package/src/types/env.ts +0 -10
- package/src/types/tool-interfaces.ts +250 -0
- package/src/types/tool-types.ts +250 -13
- package/src/unreal-bridge.ts +460 -1550
- package/src/utils/command-validator.ts +75 -0
- package/src/utils/elicitation.ts +10 -7
- package/src/utils/error-handler.ts +14 -90
- package/src/utils/ini-reader.ts +86 -0
- package/src/utils/logger.ts +8 -3
- package/src/utils/normalize.ts +60 -0
- package/src/utils/response-factory.ts +39 -0
- package/src/utils/response-validator.ts +176 -56
- package/src/utils/result-helpers.ts +21 -19
- package/src/utils/safe-json.ts +14 -11
- package/src/utils/unreal-command-queue.ts +152 -0
- package/src/utils/validation.ts +4 -1
- package/src/wasm/index.ts +838 -0
- package/test-server.mjs +100 -0
- package/tests/run-unreal-tool-tests.mjs +242 -14
- package/tests/test-animation.mjs +44 -0
- package/tests/test-asset-advanced.mjs +82 -0
- package/tests/test-asset-errors.mjs +35 -0
- package/tests/test-audio.mjs +219 -0
- package/tests/test-automation-timeouts.mjs +98 -0
- package/tests/test-behavior-tree.mjs +261 -0
- package/tests/test-blueprint-events.mjs +35 -0
- package/tests/test-blueprint-graph.mjs +79 -0
- package/tests/test-blueprint.mjs +577 -0
- package/tests/test-client-mode.mjs +86 -0
- package/tests/test-console-command.mjs +56 -0
- package/tests/test-control-actor.mjs +425 -0
- package/tests/test-control-editor.mjs +80 -0
- package/tests/test-extra-tools.mjs +38 -0
- package/tests/test-graphql.mjs +322 -0
- package/tests/test-inspect.mjs +72 -0
- package/tests/test-landscape.mjs +60 -0
- package/tests/test-manage-asset.mjs +438 -0
- package/tests/test-manage-level.mjs +70 -0
- package/tests/test-materials.mjs +356 -0
- package/tests/test-niagara.mjs +185 -0
- package/tests/test-no-inline-python.mjs +122 -0
- package/tests/test-plugin-handshake.mjs +82 -0
- package/tests/test-render.mjs +33 -0
- package/tests/test-runner.mjs +933 -0
- package/tests/test-search-assets.mjs +66 -0
- package/tests/test-sequence.mjs +68 -0
- package/tests/test-system.mjs +57 -0
- package/tests/test-wasm.mjs +193 -0
- package/tests/test-world-partition.mjs +215 -0
- package/tsconfig.json +3 -3
- package/wasm/Cargo.lock +363 -0
- package/wasm/Cargo.toml +42 -0
- package/wasm/LICENSE +21 -0
- package/wasm/README.md +253 -0
- package/wasm/src/dependency_resolver.rs +377 -0
- package/wasm/src/lib.rs +153 -0
- package/wasm/src/property_parser.rs +271 -0
- package/wasm/src/transform_math.rs +396 -0
- package/wasm/tests/integration.rs +109 -0
- package/.github/workflows/smithery-build.yml +0 -29
- package/dist/tools/build_environment_advanced.d.ts +0 -65
- package/dist/tools/build_environment_advanced.js +0 -633
- package/dist/tools/rc.d.ts +0 -110
- package/dist/tools/rc.js +0 -437
- package/dist/tools/visual.d.ts +0 -40
- package/dist/tools/visual.js +0 -282
- package/dist/utils/http.d.ts +0 -6
- package/dist/utils/http.js +0 -151
- package/dist/utils/python-output.d.ts +0 -18
- package/dist/utils/python-output.js +0 -290
- package/dist/utils/python.d.ts +0 -2
- package/dist/utils/python.js +0 -4
- package/dist/utils/stdio-redirect.d.ts +0 -2
- package/dist/utils/stdio-redirect.js +0 -20
- package/docs/unreal-tool-test-cases.md +0 -572
- package/smithery.yaml +0 -29
- package/src/tools/build_environment_advanced.ts +0 -732
- package/src/tools/rc.ts +0 -515
- package/src/tools/visual.ts +0 -281
- package/src/utils/http.ts +0 -187
- package/src/utils/python-output.ts +0 -351
- package/src/utils/python.ts +0 -3
- package/src/utils/stdio-redirect.ts +0 -18
package/dist/tools/physics.js
CHANGED
|
@@ -1,81 +1,40 @@
|
|
|
1
1
|
import { validateAssetParams, resolveSkeletalMeshPath, concurrencyDelay } from '../utils/validation.js';
|
|
2
|
-
import {
|
|
2
|
+
import { coerceString, coerceStringArray } from '../utils/result-helpers.js';
|
|
3
3
|
export class PhysicsTools {
|
|
4
4
|
bridge;
|
|
5
|
-
|
|
5
|
+
automationBridge;
|
|
6
|
+
constructor(bridge, automationBridge) {
|
|
6
7
|
this.bridge = bridge;
|
|
8
|
+
this.automationBridge = automationBridge;
|
|
7
9
|
}
|
|
8
|
-
|
|
9
|
-
* Helper to find a valid skeletal mesh in the project
|
|
10
|
-
*/
|
|
10
|
+
setAutomationBridge(automationBridge) { this.automationBridge = automationBridge; }
|
|
11
11
|
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
|
-
`;
|
|
12
|
+
if (!this.automationBridge) {
|
|
13
|
+
return '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple';
|
|
14
|
+
}
|
|
60
15
|
try {
|
|
61
|
-
const response = await this.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
16
|
+
const response = await this.automationBridge.sendAutomationRequest('find_skeletal_mesh', {
|
|
17
|
+
commonPaths: [
|
|
18
|
+
'/Game/Characters/Mannequins/Meshes/SKM_Manny',
|
|
19
|
+
'/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
|
|
20
|
+
'/Game/Characters/Mannequins/Meshes/SKM_Manny_Complex',
|
|
21
|
+
'/Game/Characters/Mannequins/Meshes/SKM_Quinn',
|
|
22
|
+
'/Game/Characters/Mannequins/Meshes/SKM_Quinn_Simple',
|
|
23
|
+
'/Game/Characters/Mannequins/Meshes/SKM_Quinn_Complex'
|
|
24
|
+
],
|
|
25
|
+
fallback: '/Engine/EngineMeshes/SkeletalCube'
|
|
26
|
+
}, {
|
|
27
|
+
timeoutMs: 30000
|
|
65
28
|
});
|
|
66
|
-
if (
|
|
67
|
-
const meshPath = coerceString(
|
|
29
|
+
if (response.success !== false && response.result) {
|
|
30
|
+
const meshPath = coerceString(response.result.meshPath);
|
|
68
31
|
if (meshPath) {
|
|
69
32
|
return meshPath;
|
|
70
33
|
}
|
|
71
34
|
}
|
|
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);
|
|
35
|
+
const alternate = coerceString(response.result?.alternate);
|
|
36
|
+
if (alternate) {
|
|
37
|
+
return alternate;
|
|
79
38
|
}
|
|
80
39
|
}
|
|
81
40
|
catch (error) {
|
|
@@ -83,16 +42,8 @@ print('RESULT:' + json.dumps(result))
|
|
|
83
42
|
}
|
|
84
43
|
return '/Engine/EngineMeshes/SkeletalCube';
|
|
85
44
|
}
|
|
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
45
|
async setupRagdoll(params) {
|
|
94
46
|
try {
|
|
95
|
-
// Strong validation for physics asset name
|
|
96
47
|
if (!params.physicsAssetName || params.physicsAssetName.trim() === '') {
|
|
97
48
|
return {
|
|
98
49
|
success: false,
|
|
@@ -100,7 +51,6 @@ print('RESULT:' + json.dumps(result))
|
|
|
100
51
|
error: 'Name cannot be empty'
|
|
101
52
|
};
|
|
102
53
|
}
|
|
103
|
-
// Check for invalid characters in name
|
|
104
54
|
if (params.physicsAssetName.includes('@') || params.physicsAssetName.includes('#') ||
|
|
105
55
|
params.physicsAssetName.includes('$') || params.physicsAssetName.includes('%')) {
|
|
106
56
|
return {
|
|
@@ -109,7 +59,6 @@ print('RESULT:' + json.dumps(result))
|
|
|
109
59
|
error: 'Name contains invalid characters'
|
|
110
60
|
};
|
|
111
61
|
}
|
|
112
|
-
// Check if skeleton path is provided instead of skeletal mesh
|
|
113
62
|
if (params.skeletonPath && (params.skeletonPath.includes('_Skeleton') ||
|
|
114
63
|
params.skeletonPath.includes('SK_Mannequin') && !params.skeletonPath.includes('SKM_'))) {
|
|
115
64
|
return {
|
|
@@ -118,7 +67,6 @@ print('RESULT:' + json.dumps(result))
|
|
|
118
67
|
error: 'Must specify a valid skeletal mesh, not a skeleton'
|
|
119
68
|
};
|
|
120
69
|
}
|
|
121
|
-
// Validate and sanitize parameters
|
|
122
70
|
const validation = validateAssetParams({
|
|
123
71
|
name: params.physicsAssetName,
|
|
124
72
|
savePath: params.savePath || '/Game/Physics'
|
|
@@ -132,15 +80,12 @@ print('RESULT:' + json.dumps(result))
|
|
|
132
80
|
}
|
|
133
81
|
const sanitizedParams = validation.sanitized;
|
|
134
82
|
const path = sanitizedParams.savePath || '/Game/Physics';
|
|
135
|
-
// Resolve skeletal mesh path
|
|
136
83
|
let meshPath = params.skeletonPath;
|
|
137
|
-
// Try to resolve skeleton to mesh mapping
|
|
138
84
|
const resolvedPath = resolveSkeletalMeshPath(meshPath);
|
|
139
85
|
if (resolvedPath && resolvedPath !== meshPath) {
|
|
140
86
|
console.error(`Auto-correcting path from ${meshPath} to ${resolvedPath}`);
|
|
141
87
|
meshPath = resolvedPath;
|
|
142
88
|
}
|
|
143
|
-
// Auto-resolve if it looks like a skeleton path or is empty
|
|
144
89
|
if (!meshPath || meshPath.includes('_Skeleton') || meshPath === 'None' || meshPath === '') {
|
|
145
90
|
console.error('Resolving skeletal mesh path...');
|
|
146
91
|
const resolvedMesh = await this.findValidSkeletalMesh();
|
|
@@ -149,13 +94,7 @@ print('RESULT:' + json.dumps(result))
|
|
|
149
94
|
console.error(`Using resolved skeletal mesh: ${meshPath}`);
|
|
150
95
|
}
|
|
151
96
|
}
|
|
152
|
-
// Add concurrency delay to prevent race conditions
|
|
153
97
|
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
98
|
const skeletonToMeshMap = {
|
|
160
99
|
'/Game/Mannequin/Character/Mesh/UE4_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
|
|
161
100
|
'/Game/Characters/Mannequins/Meshes/SK_Mannequin': '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
|
|
@@ -163,337 +102,41 @@ print('RESULT:' + json.dumps(result))
|
|
|
163
102
|
'/Game/Characters/Mannequins/Skeletons/UE5_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Manny_Simple',
|
|
164
103
|
'/Game/Characters/Mannequins/Skeletons/UE5_Female_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Quinn_Simple'
|
|
165
104
|
};
|
|
166
|
-
// Auto-fix common incorrect paths
|
|
167
105
|
let actualSkeletonPath = params.skeletonPath;
|
|
168
106
|
if (actualSkeletonPath && skeletonToMeshMap[actualSkeletonPath]) {
|
|
169
107
|
console.error(`Auto-correcting path from ${actualSkeletonPath} to ${skeletonToMeshMap[actualSkeletonPath]}`);
|
|
170
108
|
actualSkeletonPath = skeletonToMeshMap[actualSkeletonPath];
|
|
171
109
|
}
|
|
172
110
|
if (actualSkeletonPath && (actualSkeletonPath.includes('_Skeleton') || actualSkeletonPath.includes('SK_Mannequin'))) {
|
|
173
|
-
// This is likely a skeleton path, not a skeletal mesh
|
|
174
111
|
console.error('Warning: Path appears to be a skeleton, not a skeletal mesh. Auto-correcting to SKM_Manny_Simple.');
|
|
175
112
|
}
|
|
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
|
|
113
|
+
if (!this.automationBridge) {
|
|
114
|
+
throw new Error('Automation Bridge not available. Physics asset creation requires plugin support.');
|
|
115
|
+
}
|
|
465
116
|
try {
|
|
466
|
-
const response = await this.
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
117
|
+
const response = await this.automationBridge.sendAutomationRequest('setup_ragdoll', {
|
|
118
|
+
meshPath,
|
|
119
|
+
physicsAssetName: sanitizedParams.name,
|
|
120
|
+
savePath: path,
|
|
121
|
+
blendWeight: params.blendWeight,
|
|
122
|
+
constraints: params.constraints
|
|
123
|
+
}, {
|
|
124
|
+
timeoutMs: 120000
|
|
470
125
|
});
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
message: interpreted.message,
|
|
477
|
-
path: coerceString(interpreted.payload.path) ?? `${path}/${sanitizedParams.name}`
|
|
126
|
+
if (response.success === false) {
|
|
127
|
+
return {
|
|
128
|
+
success: false,
|
|
129
|
+
message: response.error || response.message || `Failed to setup ragdoll for ${sanitizedParams.name}`,
|
|
130
|
+
error: response.error || response.message || 'Failed to setup ragdoll'
|
|
478
131
|
};
|
|
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
132
|
}
|
|
490
|
-
const
|
|
133
|
+
const result = response.result;
|
|
491
134
|
return {
|
|
492
|
-
success:
|
|
493
|
-
message:
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
135
|
+
success: true,
|
|
136
|
+
message: response.message || `Ragdoll physics setup completed for ${sanitizedParams.name}`,
|
|
137
|
+
path: coerceString(result?.path) ?? coerceString(result?.physicsAssetPath) ?? `${path}/${sanitizedParams.name}`,
|
|
138
|
+
existingAsset: result?.existingAsset,
|
|
139
|
+
...(result || {})
|
|
497
140
|
};
|
|
498
141
|
}
|
|
499
142
|
catch (error) {
|
|
@@ -508,15 +151,10 @@ print('RESULT:' + json.dumps(result))
|
|
|
508
151
|
return { success: false, error: `Failed to setup ragdoll: ${err}` };
|
|
509
152
|
}
|
|
510
153
|
}
|
|
511
|
-
/**
|
|
512
|
-
* Create Physics Constraint
|
|
513
|
-
*/
|
|
514
154
|
async createConstraint(params) {
|
|
515
155
|
try {
|
|
516
|
-
// Spawn constraint actor
|
|
517
156
|
const spawnCmd = `spawnactor /Script/Engine.PhysicsConstraintActor ${params.location[0]} ${params.location[1]} ${params.location[2]}`;
|
|
518
157
|
await this.bridge.executeConsoleCommand(spawnCmd);
|
|
519
|
-
// Configure constraint
|
|
520
158
|
const commands = [
|
|
521
159
|
`SetConstraintActors ${params.name} ${params.actor1} ${params.actor2}`,
|
|
522
160
|
`SetConstraintType ${params.name} ${params.constraintType}`
|
|
@@ -549,25 +187,19 @@ print('RESULT:' + json.dumps(result))
|
|
|
549
187
|
return { success: false, error: `Failed to create constraint: ${err}` };
|
|
550
188
|
}
|
|
551
189
|
}
|
|
552
|
-
/**
|
|
553
|
-
* Setup Chaos Destruction
|
|
554
|
-
*/
|
|
555
190
|
async setupDestruction(params) {
|
|
556
191
|
try {
|
|
557
192
|
const path = params.savePath || '/Game/Destruction';
|
|
558
193
|
const commands = [
|
|
559
194
|
`CreateGeometryCollection ${params.destructionName} ${params.meshPath} ${path}`
|
|
560
195
|
];
|
|
561
|
-
// Configure fracture
|
|
562
196
|
if (params.fractureSettings) {
|
|
563
197
|
const settings = params.fractureSettings;
|
|
564
198
|
commands.push(`FractureGeometry ${params.destructionName} ${settings.cellCount} ${settings.minimumVolumeSize} ${settings.seed}`);
|
|
565
199
|
}
|
|
566
|
-
// Set damage threshold
|
|
567
200
|
if (params.damageThreshold) {
|
|
568
201
|
commands.push(`SetDamageThreshold ${params.destructionName} ${params.damageThreshold}`);
|
|
569
202
|
}
|
|
570
|
-
// Set debris lifetime
|
|
571
203
|
if (params.debrisLifetime) {
|
|
572
204
|
commands.push(`SetDebrisLifetime ${params.destructionName} ${params.debrisLifetime}`);
|
|
573
205
|
}
|
|
@@ -582,199 +214,131 @@ print('RESULT:' + json.dumps(result))
|
|
|
582
214
|
return { success: false, error: `Failed to setup destruction: ${err}` };
|
|
583
215
|
}
|
|
584
216
|
}
|
|
585
|
-
/**
|
|
586
|
-
* Configure Vehicle Physics
|
|
587
|
-
*/
|
|
588
217
|
async configureVehicle(params) {
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
218
|
+
const rawParams = params;
|
|
219
|
+
const pluginDeps = Array.isArray(params.pluginDependencies) && params.pluginDependencies.length > 0
|
|
220
|
+
? params.pluginDependencies
|
|
221
|
+
: (Array.isArray(rawParams.plugins) && rawParams.plugins.length > 0 ? rawParams.plugins : undefined);
|
|
222
|
+
if (pluginDeps && pluginDeps.length > 0) {
|
|
223
|
+
return {
|
|
224
|
+
success: false,
|
|
225
|
+
error: 'MISSING_ENGINE_PLUGINS',
|
|
226
|
+
missingPlugins: pluginDeps,
|
|
227
|
+
message: `Required engine plugins not enabled: ${pluginDeps.join(', ')}`
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
const warnings = [];
|
|
231
|
+
const hasExplicitEmptyWheels = Array.isArray(params.wheels) && params.wheels.length === 0;
|
|
232
|
+
const effectiveVehicleType = typeof params.vehicleType === 'string' && params.vehicleType.trim().length > 0
|
|
233
|
+
? params.vehicleType
|
|
234
|
+
: 'Car';
|
|
235
|
+
const commands = [
|
|
236
|
+
`CreateVehicle ${params.vehicleName} ${effectiveVehicleType}`
|
|
237
|
+
];
|
|
238
|
+
if (Array.isArray(params.wheels) && params.wheels.length > 0) {
|
|
239
|
+
for (const wheel of params.wheels) {
|
|
240
|
+
commands.push(`AddVehicleWheel ${params.vehicleName} ${wheel.name} ${wheel.radius} ${wheel.width} ${wheel.mass}`);
|
|
241
|
+
if (wheel.isSteering) {
|
|
242
|
+
commands.push(`SetWheelSteering ${params.vehicleName} ${wheel.name} true`);
|
|
603
243
|
}
|
|
244
|
+
if (wheel.isDriving) {
|
|
245
|
+
commands.push(`SetWheelDriving ${params.vehicleName} ${wheel.name} true`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const effectiveEngine = params.engine ?? ((typeof rawParams.maxRPM === 'number' || Array.isArray(rawParams.torqueCurve))
|
|
250
|
+
? { maxRPM: rawParams.maxRPM, torqueCurve: rawParams.torqueCurve }
|
|
251
|
+
: undefined);
|
|
252
|
+
if (effectiveEngine) {
|
|
253
|
+
let maxRPM = typeof effectiveEngine.maxRPM === 'number' ? effectiveEngine.maxRPM : 0;
|
|
254
|
+
if (maxRPM < 0) {
|
|
255
|
+
maxRPM = 0;
|
|
256
|
+
warnings.push('Engine maxRPM was negative and has been clamped to 0.');
|
|
604
257
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
258
|
+
commands.push(`SetEngineMaxRPM ${params.vehicleName} ${maxRPM}`);
|
|
259
|
+
const rawCurve = Array.isArray(effectiveEngine.torqueCurve) ? effectiveEngine.torqueCurve : [];
|
|
260
|
+
for (const point of rawCurve) {
|
|
261
|
+
let rpm;
|
|
262
|
+
let torque;
|
|
263
|
+
if (Array.isArray(point) && point.length >= 2) {
|
|
264
|
+
rpm = Number(point[0]);
|
|
265
|
+
torque = Number(point[1]);
|
|
266
|
+
}
|
|
267
|
+
else if (point && typeof point === 'object') {
|
|
268
|
+
const anyPoint = point;
|
|
269
|
+
rpm = typeof anyPoint.rpm === 'number' ? anyPoint.rpm : undefined;
|
|
270
|
+
torque = typeof anyPoint.torque === 'number' ? anyPoint.torque : undefined;
|
|
271
|
+
}
|
|
272
|
+
if (typeof rpm === 'number' && typeof torque === 'number') {
|
|
609
273
|
commands.push(`AddTorqueCurvePoint ${params.vehicleName} ${rpm} ${torque}`);
|
|
610
274
|
}
|
|
611
275
|
}
|
|
612
|
-
|
|
613
|
-
|
|
276
|
+
}
|
|
277
|
+
if (params.transmission) {
|
|
278
|
+
if (Array.isArray(params.transmission.gears)) {
|
|
614
279
|
for (let i = 0; i < params.transmission.gears.length; i++) {
|
|
615
280
|
commands.push(`SetGearRatio ${params.vehicleName} ${i} ${params.transmission.gears[i]}`);
|
|
616
281
|
}
|
|
282
|
+
}
|
|
283
|
+
if (typeof params.transmission.finalDriveRatio === 'number') {
|
|
617
284
|
commands.push(`SetFinalDriveRatio ${params.vehicleName} ${params.transmission.finalDriveRatio}`);
|
|
618
285
|
}
|
|
286
|
+
}
|
|
287
|
+
try {
|
|
619
288
|
await this.bridge.executeConsoleCommands(commands);
|
|
620
|
-
return {
|
|
621
|
-
success: true,
|
|
622
|
-
message: `Vehicle ${params.vehicleName} configured`
|
|
623
|
-
};
|
|
624
289
|
}
|
|
625
|
-
catch (
|
|
626
|
-
|
|
290
|
+
catch (_error) {
|
|
291
|
+
if (warnings.length === 0) {
|
|
292
|
+
warnings.push('Vehicle configuration commands could not be executed; using engine defaults.');
|
|
293
|
+
}
|
|
627
294
|
}
|
|
295
|
+
if (hasExplicitEmptyWheels) {
|
|
296
|
+
warnings.push('No wheels specified; using default wheels from vehicle preset.');
|
|
297
|
+
}
|
|
298
|
+
if (warnings.length === 0) {
|
|
299
|
+
warnings.push('Verify wheel class assignments and offsets in the vehicle movement component to ensure they match your project defaults.');
|
|
300
|
+
}
|
|
301
|
+
return {
|
|
302
|
+
success: true,
|
|
303
|
+
message: `Vehicle ${params.vehicleName} configured`,
|
|
304
|
+
warnings
|
|
305
|
+
};
|
|
628
306
|
}
|
|
629
|
-
/**
|
|
630
|
-
* Apply Force or Impulse to Actor
|
|
631
|
-
*/
|
|
632
307
|
async applyForce(params) {
|
|
308
|
+
if (!this.automationBridge) {
|
|
309
|
+
throw new Error('Automation Bridge not available. Physics force application requires plugin support.');
|
|
310
|
+
}
|
|
633
311
|
try {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
try:
|
|
643
|
-
les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
|
|
644
|
-
if les and les.is_in_play_in_editor():
|
|
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'
|
|
312
|
+
const response = await this.automationBridge.sendAutomationRequest('apply_force', {
|
|
313
|
+
actorName: params.actorName,
|
|
314
|
+
forceType: params.forceType,
|
|
315
|
+
vector: params.vector,
|
|
316
|
+
boneName: params.boneName,
|
|
317
|
+
isLocal: params.isLocal
|
|
318
|
+
}, {
|
|
319
|
+
timeoutMs: 30000
|
|
737
320
|
});
|
|
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)) {
|
|
321
|
+
if (response.success === false) {
|
|
322
|
+
const result = response.result;
|
|
757
323
|
return {
|
|
758
324
|
success: false,
|
|
759
|
-
error:
|
|
760
|
-
availableActors,
|
|
761
|
-
details:
|
|
325
|
+
error: response.error || response.message || 'Force application failed',
|
|
326
|
+
availableActors: result?.available_actors ? coerceStringArray(result.available_actors) : undefined,
|
|
327
|
+
details: result?.details
|
|
762
328
|
};
|
|
763
329
|
}
|
|
330
|
+
const result = response.result;
|
|
764
331
|
return {
|
|
765
|
-
success:
|
|
766
|
-
|
|
767
|
-
availableActors,
|
|
768
|
-
|
|
332
|
+
success: true,
|
|
333
|
+
message: response.message || `Applied ${params.forceType} to ${params.actorName}`,
|
|
334
|
+
availableActors: result?.available_actors ? coerceStringArray(result.available_actors) : undefined,
|
|
335
|
+
...(result || {})
|
|
769
336
|
};
|
|
770
337
|
}
|
|
771
338
|
catch (err) {
|
|
772
339
|
return { success: false, error: `Failed to apply force: ${err}` };
|
|
773
340
|
}
|
|
774
341
|
}
|
|
775
|
-
/**
|
|
776
|
-
* Configure Cloth Simulation
|
|
777
|
-
*/
|
|
778
342
|
async setupCloth(params) {
|
|
779
343
|
try {
|
|
780
344
|
const commands = [
|
|
@@ -813,9 +377,6 @@ print(f"RESULT:{json.dumps(result)}")
|
|
|
813
377
|
return { success: false, error: `Failed to setup cloth: ${err}` };
|
|
814
378
|
}
|
|
815
379
|
}
|
|
816
|
-
/**
|
|
817
|
-
* Create Fluid Simulation (Niagara-based)
|
|
818
|
-
*/
|
|
819
380
|
async createFluidSimulation(params) {
|
|
820
381
|
try {
|
|
821
382
|
const locStr = `${params.location[0]} ${params.location[1]} ${params.location[2]}`;
|
|
@@ -852,5 +413,33 @@ print(f"RESULT:{json.dumps(result)}")
|
|
|
852
413
|
return { success: false, error: `Failed to create fluid simulation: ${err}` };
|
|
853
414
|
}
|
|
854
415
|
}
|
|
416
|
+
async setupPhysicsSimulation(params) {
|
|
417
|
+
if (!this.automationBridge) {
|
|
418
|
+
throw new Error('Automation Bridge not available. Physics asset creation requires plugin support.');
|
|
419
|
+
}
|
|
420
|
+
try {
|
|
421
|
+
const response = await this.automationBridge.sendAutomationRequest('animation_physics', {
|
|
422
|
+
action: 'setup_physics_simulation',
|
|
423
|
+
...params
|
|
424
|
+
}, {
|
|
425
|
+
timeoutMs: 60000
|
|
426
|
+
});
|
|
427
|
+
if (response.success === false) {
|
|
428
|
+
return {
|
|
429
|
+
success: false,
|
|
430
|
+
message: response.error || response.message || 'Failed to setup physics simulation',
|
|
431
|
+
error: response.error || response.message
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
return {
|
|
435
|
+
success: true,
|
|
436
|
+
message: response.message || 'Physics simulation setup completed',
|
|
437
|
+
...(response.result || {})
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
catch (err) {
|
|
441
|
+
return { success: false, error: `Failed to setup physics simulation: ${err}` };
|
|
442
|
+
}
|
|
443
|
+
}
|
|
855
444
|
}
|
|
856
445
|
//# sourceMappingURL=physics.js.map
|