ue-mcp 1.2.4 → 1.3.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/README.md +4 -3
- package/dist/action-class.js +56 -3
- package/dist/action-class.js.map +1 -1
- package/dist/bridge-timeouts.d.ts +64 -0
- package/dist/bridge-timeouts.js +118 -0
- package/dist/bridge-timeouts.js.map +1 -0
- package/dist/bridge.js +10 -2
- package/dist/bridge.js.map +1 -1
- package/dist/editor-control.d.ts +72 -5
- package/dist/editor-control.js +164 -140
- package/dist/editor-control.js.map +1 -1
- package/dist/editor-target.d.ts +55 -1
- package/dist/editor-target.js +107 -10
- package/dist/editor-target.js.map +1 -1
- package/dist/engine-observer.d.ts +59 -5
- package/dist/engine-observer.js +249 -29
- package/dist/engine-observer.js.map +1 -1
- package/dist/engine-root.d.ts +91 -0
- package/dist/engine-root.js +448 -0
- package/dist/engine-root.js.map +1 -0
- package/dist/epic-enrich.js +16 -1
- package/dist/epic-enrich.js.map +1 -1
- package/dist/feedback-cli.js +4 -0
- package/dist/feedback-cli.js.map +1 -1
- package/dist/feedback-deferred.d.ts +9 -0
- package/dist/feedback-deferred.js +2 -0
- package/dist/feedback-deferred.js.map +1 -1
- package/dist/feedback-fallback.d.ts +99 -0
- package/dist/feedback-fallback.js +173 -0
- package/dist/feedback-fallback.js.map +1 -0
- package/dist/flow/write-methods.js +20 -0
- package/dist/flow/write-methods.js.map +1 -1
- package/dist/lean-context.js +8 -3
- package/dist/lean-context.js.map +1 -1
- package/dist/locking.js +11 -2
- package/dist/locking.js.map +1 -1
- package/dist/project.d.ts +11 -0
- package/dist/project.js +23 -2
- package/dist/project.js.map +1 -1
- package/dist/python-gate.d.ts +95 -0
- package/dist/python-gate.js +175 -0
- package/dist/python-gate.js.map +1 -0
- package/dist/tool-counts.json +16 -16
- package/dist/tools/animation.js +288 -24
- package/dist/tools/animation.js.map +1 -1
- package/dist/tools/asset.js +79 -14
- package/dist/tools/asset.js.map +1 -1
- package/dist/tools/audio.js +59 -5
- package/dist/tools/audio.js.map +1 -1
- package/dist/tools/blueprint.js +29 -12
- package/dist/tools/blueprint.js.map +1 -1
- package/dist/tools/chooser.js +4 -1
- package/dist/tools/chooser.js.map +1 -1
- package/dist/tools/editor.js +81 -43
- package/dist/tools/editor.js.map +1 -1
- package/dist/tools/feedback.js +214 -48
- package/dist/tools/feedback.js.map +1 -1
- package/dist/tools/foliage.js +27 -1
- package/dist/tools/foliage.js.map +1 -1
- package/dist/tools/gameplay.js +38 -11
- package/dist/tools/gameplay.js.map +1 -1
- package/dist/tools/gas.js +17 -5
- package/dist/tools/gas.js.map +1 -1
- package/dist/tools/landscape.js +22 -7
- package/dist/tools/landscape.js.map +1 -1
- package/dist/tools/level.js +322 -51
- package/dist/tools/level.js.map +1 -1
- package/dist/tools/material.js +10 -4
- package/dist/tools/material.js.map +1 -1
- package/dist/tools/niagara.js +3 -2
- package/dist/tools/niagara.js.map +1 -1
- package/dist/tools/pcg.js +6 -5
- package/dist/tools/pcg.js.map +1 -1
- package/dist/tools/project.js +47 -20
- package/dist/tools/project.js.map +1 -1
- package/dist/types.d.ts +21 -0
- package/dist/types.js +46 -3
- package/dist/types.js.map +1 -1
- package/dist/ue-mcp.default.yml +493 -125
- package/package.json +2 -2
- package/plugin/ue_mcp_bridge/NATIVE_RUNTIME_VISIBILITY.md +53 -0
- package/plugin/ue_mcp_bridge/NATIVE_STATIC_MESH_USAGE_PROGRESS.md +115 -0
- package/plugin/ue_mcp_bridge/README_MassHandlers.md +97 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.cpp +14 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.cpp +21 -4
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.h +9 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerJsonProperty.h +395 -69
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.cpp +79 -3
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.h +25 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRig.cpp +3 -3
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRigSequencer.cpp +3061 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_IKRetargeterAuthoring.cpp +961 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_IKRigAuthoring.cpp +900 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Pose.cpp +769 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Sequence.cpp +44 -3
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_SkeletalLive.cpp +217 -11
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_StateMachine.cpp +551 -24
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Validation.cpp +735 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.cpp +352 -73
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.h +8 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkProperties.cpp +1 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkRead.cpp +503 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkRead.h +21 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkUpsert.cpp +2 -2
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Enum.cpp +2 -2
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Geometry.cpp +902 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Geometry.h +33 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Import.cpp +478 -134
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Mesh.cpp +5 -5
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_MeshBoolean.cpp +933 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_MeshBoolean.h +35 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Redirectors.cpp +478 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_RenderTarget.cpp +1 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Sockets.cpp +4 -4
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Struct.cpp +2 -2
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Subobject.cpp +319 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers_SoundCue.cpp +3 -3
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.cpp +482 -35
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.h +10 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Collision.cpp +502 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Collision.h +43 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Functions.cpp +89 -7
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Graph.cpp +17 -51
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Internal.h +107 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Properties.cpp +10 -10
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Search.cpp +648 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.cpp +5 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DiffHandlers.cpp +252 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DiffHandlers.h +10 -2
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.cpp +252 -41
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.h +4 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_Build.cpp +250 -27
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_PIE.cpp +303 -125
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_PIERuntime.cpp +73 -50
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_RuntimeVisibility.cpp +757 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.cpp +11 -2
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.h +3 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers_Batch.cpp +419 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.cpp +39 -161
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.h +66 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_BTAuthoring.cpp +1323 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_BehaviorTree.cpp +1211 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +2 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +60 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers_Runtime.cpp +363 -12
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/HandlerEditorState.h +84 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.cpp +1288 -36
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.h +3 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers_Sculpt.cpp +41 -16
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.cpp +959 -213
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.h +66 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_BatchWrite.cpp +1571 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Convert.cpp +316 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Inspection.cpp +280 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_InstanceProjection.cpp +666 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_InstanceProjection_Internal.h +63 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Lights.cpp +43 -12
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_MultiLevel.cpp +13 -47
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_PostProcess.cpp +527 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Query.cpp +1169 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Refresh.cpp +723 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Save.cpp +325 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Spatial.cpp +360 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Trace.cpp +178 -58
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Transient.cpp +337 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Volumes.cpp +25 -12
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_WorldPartition.cpp +10 -9
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_WorldPartitionSettings.cpp +686 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MassHandlers.cpp +467 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MassHandlers.h +22 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.cpp +452 -175
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.h +5 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Build.cpp +597 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.cpp +17 -15
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.cpp +38 -168
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.cpp +39 -30
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.cpp +392 -78
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.h +2 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers_Internal.h +83 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SkeletalMeshHandlers.cpp +200 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SkeletalMeshHandlers.h +15 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.cpp +19 -12
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/StateTreeHandlers.cpp +31 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.cpp +307 -69
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.h +84 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers_Extraction.cpp +8 -41
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers_Properties.cpp +33 -52
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/ActorSelectorTests.cpp +344 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AnimationControlRigTimelineTests.cpp +60 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AnimationPoseTests.cpp +115 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetBulkReadTests.cpp +177 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetDeleteGuardTests.cpp +276 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetPathResolutionTests.cpp +243 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetPropertyPersistTests.cpp +213 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetSubobjectTests.cpp +235 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AutomationRunnerTests.cpp +72 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BehaviorTreeAuthoringTests.cpp +526 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BehaviorTreeNodeTests.cpp +334 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BlueprintHandlerSurfaceTests.cpp +362 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BridgeProtocolTests.cpp +14 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/CollisionQueryTests.cpp +288 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/DataTableRowWriteTests.cpp +453 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/EditorFunctionCallTests.cpp +223 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/FoliageBatchTests.cpp +147 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/GameThreadGateTests.cpp +82 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/GasLiveAttributeTests.cpp +183 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LandscapeSampleTests.cpp +119 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelBatchWriteTests.cpp +176 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelComponentTreeInstanceCountTests.cpp +189 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelConvertBrushesTests.cpp +100 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelInstanceProjectionTests.cpp +265 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelPostProcessOverrideTests.cpp +258 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelQueryComponentsTests.cpp +195 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelRefreshTests.cpp +142 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelSaveReportTests.cpp +125 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelScriptHandlersTests.cpp +42 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelStaticMeshUsageTests.cpp +412 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelTraceWorldScopeTests.cpp +133 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelTransientActorTests.cpp +205 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelWorldPartitionSettingsTests.cpp +168 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MCPInstancedStructPathTestTypes.h +51 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MCPInstancedStructPathTests.cpp +379 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MaterialBuildTests.cpp +238 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MeshBooleanTests.cpp +170 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MeshGeometryTests.cpp +116 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/PackageSaveExtensionTests.cpp +161 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/PackageWriteGuardTests.cpp +253 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/RuntimeVisibilityTests.cpp +143 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SequencerHandlerTests.cpp +660 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SequencerScrubTests.cpp +50 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SkeletonDiffTests.cpp +685 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/WidgetBlueprintResolveTests.cpp +145 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/UE_MCP_Bridge.cpp +80 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerFunctionCall.h +509 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerQuery.h +407 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerUtils.h +1008 -77
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/UE_MCP_Bridge.Build.cs +87 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/MCPEngineStatus.cpp +152 -14
- package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/Tests/MCPEngineStatusTests.cpp +65 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Public/MCPEngineStatus.h +32 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/UE_MCP_BridgeStatus.Build.cs +2 -0
- package/skills/ue-mcp-animation/SKILL.md +154 -0
- package/skills/ue-mcp-blueprint/SKILL.md +71 -0
|
@@ -0,0 +1,961 @@
|
|
|
1
|
+
#include "AnimationHandlers.h"
|
|
2
|
+
#include "HandlerUtils.h"
|
|
3
|
+
|
|
4
|
+
#if UE_MCP_HAS_5_8_API
|
|
5
|
+
#include "Editor.h"
|
|
6
|
+
#include "ScopedTransaction.h"
|
|
7
|
+
#include "Engine/SkeletalMesh.h"
|
|
8
|
+
#include "Rig/IKRigDefinition.h"
|
|
9
|
+
#include "Rig/IKRigSkeleton.h"
|
|
10
|
+
#include "RetargetEditor/IKRetargeterController.h"
|
|
11
|
+
#include "RetargetEditor/IKRetargeterPoseGenerator.h"
|
|
12
|
+
#include "Retargeter/IKRetargetChainMapping.h"
|
|
13
|
+
#include "Retargeter/IKRetargetOps.h"
|
|
14
|
+
#include "Retargeter/IKRetargetProcessor.h"
|
|
15
|
+
#include "Retargeter/IKRetargeter.h"
|
|
16
|
+
#include "Retargeter/RetargetOps/CurveRemapOp.h"
|
|
17
|
+
#include "Retargeter/RetargetOps/FKChainsOp.h"
|
|
18
|
+
#include "Retargeter/RetargetOps/PelvisMotionOp.h"
|
|
19
|
+
#include "Retargeter/RetargetOps/RootMotionGeneratorOp.h"
|
|
20
|
+
#include "Retargeter/RetargetOps/RunIKRigOp.h"
|
|
21
|
+
|
|
22
|
+
namespace
|
|
23
|
+
{
|
|
24
|
+
constexpr int32 MaxRetargeterItems = 10000;
|
|
25
|
+
|
|
26
|
+
struct FPreparedChainMapping
|
|
27
|
+
{
|
|
28
|
+
FName TargetChain;
|
|
29
|
+
FName SourceChain;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
struct FPreparedPoseRotation
|
|
33
|
+
{
|
|
34
|
+
FName Bone;
|
|
35
|
+
FQuat Rotation;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
struct FPreparedRetargetPose
|
|
39
|
+
{
|
|
40
|
+
ERetargetSourceOrTarget Side = ERetargetSourceOrTarget::Target;
|
|
41
|
+
FName Name;
|
|
42
|
+
bool bCreate = false;
|
|
43
|
+
bool bReset = false;
|
|
44
|
+
bool bHasAutoAlign = false;
|
|
45
|
+
bool bAutoAlignAll = false;
|
|
46
|
+
ERetargetAutoAlignMethod AutoAlignMethod = ERetargetAutoAlignMethod::ChainToChain;
|
|
47
|
+
TArray<FName> AutoAlignBones;
|
|
48
|
+
TArray<FPreparedPoseRotation> Rotations;
|
|
49
|
+
TOptional<double> RootOffsetZ;
|
|
50
|
+
TOptional<FName> SnapBone;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Processor initialization temporarily points serialized ops/settings at its
|
|
54
|
+
// live editor copies. Preserve any viewport-owned pointers so a validation-only
|
|
55
|
+
// processor cannot leave the open retargeter editor with dangling references.
|
|
56
|
+
class FScopedRetargetEditorInstanceRestore
|
|
57
|
+
{
|
|
58
|
+
struct FState
|
|
59
|
+
{
|
|
60
|
+
FIKRetargetOpBase* Op = nullptr;
|
|
61
|
+
FIKRetargetOpBase* OpEditorInstance = nullptr;
|
|
62
|
+
FIKRetargetOpSettingsBase* Settings = nullptr;
|
|
63
|
+
FIKRetargetOpSettingsBase* SettingsEditorInstance = nullptr;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
public:
|
|
67
|
+
explicit FScopedRetargetEditorInstanceRestore(UIKRetargeter* Retargeter)
|
|
68
|
+
{
|
|
69
|
+
if (!Retargeter) return;
|
|
70
|
+
States.Reserve(Retargeter->GetRetargetOps().Num());
|
|
71
|
+
for (const FInstancedStruct& OpStruct : Retargeter->GetRetargetOps())
|
|
72
|
+
{
|
|
73
|
+
FIKRetargetOpBase* Op = const_cast<FIKRetargetOpBase*>(OpStruct.GetPtr<FIKRetargetOpBase>());
|
|
74
|
+
if (!Op) continue;
|
|
75
|
+
FIKRetargetOpSettingsBase* Settings = Op->GetSettings();
|
|
76
|
+
States.Add({
|
|
77
|
+
Op,
|
|
78
|
+
Op->EditorInstance,
|
|
79
|
+
Settings,
|
|
80
|
+
Settings ? Settings->EditorInstance : nullptr});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
~FScopedRetargetEditorInstanceRestore()
|
|
85
|
+
{
|
|
86
|
+
for (const FState& State : States)
|
|
87
|
+
{
|
|
88
|
+
State.Op->EditorInstance = State.OpEditorInstance;
|
|
89
|
+
if (State.Settings) State.Settings->EditorInstance = State.SettingsEditorInstance;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private:
|
|
94
|
+
TArray<FState> States;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
bool ReadOptionalString(
|
|
98
|
+
const TSharedPtr<FJsonObject>& Object,
|
|
99
|
+
const TCHAR* Field,
|
|
100
|
+
bool& bOutPresent,
|
|
101
|
+
FString& OutValue,
|
|
102
|
+
FString& OutError)
|
|
103
|
+
{
|
|
104
|
+
bOutPresent = Object->HasField(Field);
|
|
105
|
+
if (!bOutPresent)
|
|
106
|
+
{
|
|
107
|
+
OutValue.Reset();
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
if (!Object->TryGetStringField(Field, OutValue) || OutValue.IsEmpty())
|
|
111
|
+
{
|
|
112
|
+
OutError = FString::Printf(TEXT("'%s' must be a non-empty string"), Field);
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
bool ReadRetargeterOptionalBool(
|
|
119
|
+
const TSharedPtr<FJsonObject>& Object,
|
|
120
|
+
const TCHAR* Field,
|
|
121
|
+
const bool DefaultValue,
|
|
122
|
+
bool& OutValue,
|
|
123
|
+
FString& OutError)
|
|
124
|
+
{
|
|
125
|
+
OutValue = DefaultValue;
|
|
126
|
+
if (!Object->HasField(Field)) return true;
|
|
127
|
+
if (!Object->TryGetBoolField(Field, OutValue))
|
|
128
|
+
{
|
|
129
|
+
OutError = FString::Printf(TEXT("'%s' must be a boolean"), Field);
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
bool ReadNormalizedQuaternion(
|
|
136
|
+
const TSharedPtr<FJsonObject>& Object,
|
|
137
|
+
FQuat& OutRotation,
|
|
138
|
+
FString& OutError)
|
|
139
|
+
{
|
|
140
|
+
const TSharedPtr<FJsonObject>* Quaternion = nullptr;
|
|
141
|
+
if (!Object->TryGetObjectField(TEXT("rotationQuaternion"), Quaternion)
|
|
142
|
+
|| !Quaternion || !Quaternion->IsValid())
|
|
143
|
+
{
|
|
144
|
+
OutError = TEXT("'rotationQuaternion' must be an object with finite x, y, z and w numbers");
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
double X = 0.0;
|
|
149
|
+
double Y = 0.0;
|
|
150
|
+
double Z = 0.0;
|
|
151
|
+
double W = 0.0;
|
|
152
|
+
if (!(*Quaternion)->TryGetNumberField(TEXT("x"), X)
|
|
153
|
+
|| !(*Quaternion)->TryGetNumberField(TEXT("y"), Y)
|
|
154
|
+
|| !(*Quaternion)->TryGetNumberField(TEXT("z"), Z)
|
|
155
|
+
|| !(*Quaternion)->TryGetNumberField(TEXT("w"), W)
|
|
156
|
+
|| !FMath::IsFinite(X) || !FMath::IsFinite(Y)
|
|
157
|
+
|| !FMath::IsFinite(Z) || !FMath::IsFinite(W))
|
|
158
|
+
{
|
|
159
|
+
OutError = TEXT("'rotationQuaternion' must contain finite x, y, z and w numbers");
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
OutRotation = FQuat(X, Y, Z, W);
|
|
164
|
+
const double Length = OutRotation.Size();
|
|
165
|
+
constexpr double NormalizedTolerance = 1e-3;
|
|
166
|
+
if (Length <= UE_SMALL_NUMBER)
|
|
167
|
+
{
|
|
168
|
+
OutError = TEXT("'rotationQuaternion' must have non-zero length");
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
if (FMath::Abs(Length - 1.0) > NormalizedTolerance)
|
|
172
|
+
{
|
|
173
|
+
OutError = FString::Printf(
|
|
174
|
+
TEXT("'rotationQuaternion' must be normalized within %.4f (length was %.8f)"),
|
|
175
|
+
NormalizedTolerance, Length);
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
OutRotation.Normalize();
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
bool ParseSide(const FString& Value, ERetargetSourceOrTarget& OutSide)
|
|
183
|
+
{
|
|
184
|
+
if (Value.Equals(TEXT("source"), ESearchCase::IgnoreCase))
|
|
185
|
+
{
|
|
186
|
+
OutSide = ERetargetSourceOrTarget::Source;
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
if (Value.Equals(TEXT("target"), ESearchCase::IgnoreCase))
|
|
190
|
+
{
|
|
191
|
+
OutSide = ERetargetSourceOrTarget::Target;
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
bool ParseAutoMapMode(const FString& Value, EAutoMapChainType& OutMode)
|
|
198
|
+
{
|
|
199
|
+
if (Value.Equals(TEXT("exact"), ESearchCase::IgnoreCase))
|
|
200
|
+
{
|
|
201
|
+
OutMode = EAutoMapChainType::Exact;
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
if (Value.Equals(TEXT("fuzzy"), ESearchCase::IgnoreCase))
|
|
205
|
+
{
|
|
206
|
+
OutMode = EAutoMapChainType::Fuzzy;
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
if (Value.Equals(TEXT("clear"), ESearchCase::IgnoreCase))
|
|
210
|
+
{
|
|
211
|
+
OutMode = EAutoMapChainType::Clear;
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
bool ParseAutoAlignMethod(const FString& Value, ERetargetAutoAlignMethod& OutMethod)
|
|
218
|
+
{
|
|
219
|
+
if (Value.Equals(TEXT("chain_to_chain"), ESearchCase::IgnoreCase))
|
|
220
|
+
{
|
|
221
|
+
OutMethod = ERetargetAutoAlignMethod::ChainToChain;
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
if (Value.Equals(TEXT("mesh_to_mesh"), ESearchCase::IgnoreCase))
|
|
225
|
+
{
|
|
226
|
+
OutMethod = ERetargetAutoAlignMethod::MeshToMesh;
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
if (Value.Equals(TEXT("local_axes"), ESearchCase::IgnoreCase))
|
|
230
|
+
{
|
|
231
|
+
OutMethod = ERetargetAutoAlignMethod::LocalRotationAxes;
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
if (Value.Equals(TEXT("global_axes"), ESearchCase::IgnoreCase))
|
|
235
|
+
{
|
|
236
|
+
OutMethod = ERetargetAutoAlignMethod::GlobalRotationAxes;
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
bool HasRetargetOpType(const UIKRetargeter* Retargeter, const UScriptStruct* Type)
|
|
243
|
+
{
|
|
244
|
+
if (!Retargeter || !Type) return false;
|
|
245
|
+
for (const FInstancedStruct& OpStruct : Retargeter->GetRetargetOps())
|
|
246
|
+
{
|
|
247
|
+
const UScriptStruct* ActualType = OpStruct.GetScriptStruct();
|
|
248
|
+
if (ActualType && ActualType->IsChildOf(Type)) return true;
|
|
249
|
+
}
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
bool HasAllDefaultOps(const UIKRetargeter* Retargeter)
|
|
254
|
+
{
|
|
255
|
+
return HasRetargetOpType(Retargeter, FIKRetargetPelvisMotionOp::StaticStruct())
|
|
256
|
+
&& HasRetargetOpType(Retargeter, FIKRetargetFKChainsOp::StaticStruct())
|
|
257
|
+
&& HasRetargetOpType(Retargeter, FIKRetargetRunIKRigOp::StaticStruct())
|
|
258
|
+
&& HasRetargetOpType(Retargeter, FIKRetargetRootMotionOp::StaticStruct())
|
|
259
|
+
&& HasRetargetOpType(Retargeter, FIKRetargetCurveRemapOp::StaticStruct());
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
TSet<FName> GetChainNames(const UIKRigDefinition* Rig)
|
|
263
|
+
{
|
|
264
|
+
TSet<FName> Names;
|
|
265
|
+
if (!Rig) return Names;
|
|
266
|
+
for (const FBoneChain& Chain : Rig->GetRetargetChains()) Names.Add(Chain.ChainName);
|
|
267
|
+
return Names;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
bool HasBone(const UIKRigDefinition* Rig, const USkeletalMesh* Mesh, const FName Bone)
|
|
271
|
+
{
|
|
272
|
+
if (Mesh && Mesh->GetRefSkeleton().FindBoneIndex(Bone) != INDEX_NONE) return true;
|
|
273
|
+
return Rig && Rig->GetSkeleton().GetBoneIndexFromName(Bone) != INDEX_NONE;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
int32 CountMappedChains(const UIKRetargeter* Retargeter)
|
|
277
|
+
{
|
|
278
|
+
int32 Count = 0;
|
|
279
|
+
if (!Retargeter) return Count;
|
|
280
|
+
for (const FInstancedStruct& OpStruct : Retargeter->GetRetargetOps())
|
|
281
|
+
{
|
|
282
|
+
const FIKRetargetOpBase* Op = OpStruct.GetPtr<FIKRetargetOpBase>();
|
|
283
|
+
const FRetargetChainMapping* Mapping = Op ? Op->GetChainMapping() : nullptr;
|
|
284
|
+
if (!Mapping) continue;
|
|
285
|
+
for (const FRetargetChainPair& Pair : Mapping->GetChainPairs())
|
|
286
|
+
{
|
|
287
|
+
if (!Pair.SourceChainName.IsNone()) ++Count;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return Count;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
TSharedPtr<FJsonObject> QuaternionJson(const FQuat& Rotation)
|
|
294
|
+
{
|
|
295
|
+
auto Result = MakeShared<FJsonObject>();
|
|
296
|
+
Result->SetNumberField(TEXT("x"), Rotation.X);
|
|
297
|
+
Result->SetNumberField(TEXT("y"), Rotation.Y);
|
|
298
|
+
Result->SetNumberField(TEXT("z"), Rotation.Z);
|
|
299
|
+
Result->SetNumberField(TEXT("w"), Rotation.W);
|
|
300
|
+
return Result;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
TArray<TSharedPtr<FJsonValue>> TextArrayJson(const TArray<FText>& Values)
|
|
304
|
+
{
|
|
305
|
+
TArray<TSharedPtr<FJsonValue>> Result;
|
|
306
|
+
Result.Reserve(Values.Num());
|
|
307
|
+
for (const FText& Value : Values)
|
|
308
|
+
{
|
|
309
|
+
Result.Add(MakeShared<FJsonValueString>(Value.ToString()));
|
|
310
|
+
}
|
|
311
|
+
return Result;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
TArray<TSharedPtr<FJsonValue>> BuildOpsJson(const UIKRetargeter* Retargeter)
|
|
315
|
+
{
|
|
316
|
+
TArray<TSharedPtr<FJsonValue>> Result;
|
|
317
|
+
if (!Retargeter) return Result;
|
|
318
|
+
const TArray<FInstancedStruct>& Ops = Retargeter->GetRetargetOps();
|
|
319
|
+
Result.Reserve(Ops.Num());
|
|
320
|
+
for (int32 Index = 0; Index < Ops.Num(); ++Index)
|
|
321
|
+
{
|
|
322
|
+
const FIKRetargetOpBase* Op = Ops[Index].GetPtr<FIKRetargetOpBase>();
|
|
323
|
+
if (!Op) continue;
|
|
324
|
+
auto Entry = MakeShared<FJsonObject>();
|
|
325
|
+
Entry->SetNumberField(TEXT("index"), Index);
|
|
326
|
+
Entry->SetStringField(TEXT("name"), Op->GetName().ToString());
|
|
327
|
+
Entry->SetStringField(TEXT("parent"), Op->GetParentOpName().ToString());
|
|
328
|
+
Entry->SetBoolField(TEXT("enabled"), Op->IsEnabled());
|
|
329
|
+
Entry->SetStringField(TEXT("type"), Op->GetType() ? Op->GetType()->GetPathName() : TEXT(""));
|
|
330
|
+
Entry->SetBoolField(TEXT("hasChainMapping"), Op->GetChainMapping() != nullptr);
|
|
331
|
+
if (const UIKRigDefinition* TargetRig = Op->GetCustomTargetIKRig())
|
|
332
|
+
{
|
|
333
|
+
Entry->SetStringField(TEXT("targetRig"), TargetRig->GetPathName());
|
|
334
|
+
}
|
|
335
|
+
Result.Add(MakeShared<FJsonValueObject>(Entry));
|
|
336
|
+
}
|
|
337
|
+
return Result;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
TArray<TSharedPtr<FJsonValue>> BuildMappingsJson(const UIKRetargeter* Retargeter)
|
|
341
|
+
{
|
|
342
|
+
TArray<TSharedPtr<FJsonValue>> Result;
|
|
343
|
+
if (!Retargeter) return Result;
|
|
344
|
+
const TArray<FInstancedStruct>& Ops = Retargeter->GetRetargetOps();
|
|
345
|
+
for (int32 Index = 0; Index < Ops.Num(); ++Index)
|
|
346
|
+
{
|
|
347
|
+
const FIKRetargetOpBase* Op = Ops[Index].GetPtr<FIKRetargetOpBase>();
|
|
348
|
+
const FRetargetChainMapping* Mapping = Op ? Op->GetChainMapping() : nullptr;
|
|
349
|
+
if (!Mapping) continue;
|
|
350
|
+
auto MappingObject = MakeShared<FJsonObject>();
|
|
351
|
+
MappingObject->SetNumberField(TEXT("opIndex"), Index);
|
|
352
|
+
MappingObject->SetStringField(TEXT("opName"), Op->GetName().ToString());
|
|
353
|
+
TArray<TSharedPtr<FJsonValue>> Chains;
|
|
354
|
+
for (const FRetargetChainPair& Pair : Mapping->GetChainPairs())
|
|
355
|
+
{
|
|
356
|
+
auto Chain = MakeShared<FJsonObject>();
|
|
357
|
+
Chain->SetStringField(TEXT("targetChain"), Pair.TargetChainName.ToString());
|
|
358
|
+
Chain->SetStringField(TEXT("sourceChain"), Pair.SourceChainName.ToString());
|
|
359
|
+
Chains.Add(MakeShared<FJsonValueObject>(Chain));
|
|
360
|
+
}
|
|
361
|
+
MappingObject->SetArrayField(TEXT("chains"), Chains);
|
|
362
|
+
Result.Add(MakeShared<FJsonValueObject>(MappingObject));
|
|
363
|
+
}
|
|
364
|
+
return Result;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
TSharedPtr<FJsonObject> BuildPoseJson(
|
|
368
|
+
UIKRetargeterController* Controller,
|
|
369
|
+
const ERetargetSourceOrTarget Side,
|
|
370
|
+
const FName PoseName,
|
|
371
|
+
const bool bAutoAlignResetPose)
|
|
372
|
+
{
|
|
373
|
+
auto Result = MakeShared<FJsonObject>();
|
|
374
|
+
Result->SetStringField(TEXT("side"), Side == ERetargetSourceOrTarget::Source ? TEXT("source") : TEXT("target"));
|
|
375
|
+
Result->SetStringField(TEXT("name"), PoseName.ToString());
|
|
376
|
+
Result->SetBoolField(TEXT("current"), Controller->GetCurrentRetargetPoseName(Side) == PoseName);
|
|
377
|
+
Result->SetBoolField(TEXT("autoAlignResetPose"), bAutoAlignResetPose);
|
|
378
|
+
FIKRetargetPose& Pose = Controller->GetRetargetPoses(Side).FindChecked(PoseName);
|
|
379
|
+
Result->SetNumberField(TEXT("rootOffsetZ"), Pose.GetRootTranslationDelta().Z);
|
|
380
|
+
|
|
381
|
+
TArray<FName> BoneNames;
|
|
382
|
+
Pose.GetAllDeltaRotations().GetKeys(BoneNames);
|
|
383
|
+
BoneNames.Sort([](const FName A, const FName B)
|
|
384
|
+
{
|
|
385
|
+
return A.ToString() < B.ToString();
|
|
386
|
+
});
|
|
387
|
+
TArray<TSharedPtr<FJsonValue>> RotationOffsets;
|
|
388
|
+
RotationOffsets.Reserve(BoneNames.Num());
|
|
389
|
+
for (const FName Bone : BoneNames)
|
|
390
|
+
{
|
|
391
|
+
auto Offset = MakeShared<FJsonObject>();
|
|
392
|
+
Offset->SetStringField(TEXT("bone"), Bone.ToString());
|
|
393
|
+
Offset->SetObjectField(TEXT("rotationQuaternion"), QuaternionJson(Pose.GetDeltaRotationForBone(Bone)));
|
|
394
|
+
RotationOffsets.Add(MakeShared<FJsonValueObject>(Offset));
|
|
395
|
+
}
|
|
396
|
+
Result->SetArrayField(TEXT("rotationOffsets"), RotationOffsets);
|
|
397
|
+
return Result;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
#endif
|
|
401
|
+
|
|
402
|
+
TSharedPtr<FJsonValue> FAnimationHandlers::ConfigureIKRetargeter(const TSharedPtr<FJsonObject>& Params)
|
|
403
|
+
{
|
|
404
|
+
#if !UE_MCP_HAS_5_8_API
|
|
405
|
+
auto Result = MakeShared<FJsonObject>();
|
|
406
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
407
|
+
Result->SetStringField(TEXT("errorCode"), TEXT("unsupported_engine_version"));
|
|
408
|
+
Result->SetStringField(TEXT("error"), TEXT("configure_ik_retargeter requires Unreal Engine 5.8 or newer"));
|
|
409
|
+
return MCPResult(Result);
|
|
410
|
+
#else
|
|
411
|
+
FString RetargeterPath;
|
|
412
|
+
if (auto Error = RequireString(Params, TEXT("retargeterPath"), RetargeterPath)) return Error;
|
|
413
|
+
if (MCPIsProtectedAssetPath(RetargeterPath))
|
|
414
|
+
{
|
|
415
|
+
return MCPError(FString::Printf(TEXT("Protected asset cannot be modified: %s"), *RetargeterPath));
|
|
416
|
+
}
|
|
417
|
+
UIKRetargeter* Retargeter = LoadAssetByPath<UIKRetargeter>(RetargeterPath);
|
|
418
|
+
if (!Retargeter) return MCPError(FString::Printf(TEXT("IKRetargeter not found: %s"), *RetargeterPath));
|
|
419
|
+
UIKRetargeterController* Controller = UIKRetargeterController::GetController(Retargeter);
|
|
420
|
+
if (!Controller) return MCPError(TEXT("IKRetargeterController unavailable"));
|
|
421
|
+
|
|
422
|
+
FString Error;
|
|
423
|
+
bool bHasSourceRig = false;
|
|
424
|
+
bool bHasTargetRig = false;
|
|
425
|
+
bool bHasSourcePreview = false;
|
|
426
|
+
bool bHasTargetPreview = false;
|
|
427
|
+
FString SourceRigPath;
|
|
428
|
+
FString TargetRigPath;
|
|
429
|
+
FString SourcePreviewPath;
|
|
430
|
+
FString TargetPreviewPath;
|
|
431
|
+
if (!ReadOptionalString(Params, TEXT("sourceRig"), bHasSourceRig, SourceRigPath, Error)
|
|
432
|
+
|| !ReadOptionalString(Params, TEXT("targetRig"), bHasTargetRig, TargetRigPath, Error)
|
|
433
|
+
|| !ReadOptionalString(Params, TEXT("sourcePreviewMesh"), bHasSourcePreview, SourcePreviewPath, Error)
|
|
434
|
+
|| !ReadOptionalString(Params, TEXT("targetPreviewMesh"), bHasTargetPreview, TargetPreviewPath, Error))
|
|
435
|
+
{
|
|
436
|
+
return MCPError(Error);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
bool bEnsureDefaultOps = true;
|
|
440
|
+
bool bForceRemap = false;
|
|
441
|
+
if (!ReadRetargeterOptionalBool(Params, TEXT("ensureDefaultOps"), true, bEnsureDefaultOps, Error)
|
|
442
|
+
|| !ReadRetargeterOptionalBool(Params, TEXT("forceRemap"), false, bForceRemap, Error))
|
|
443
|
+
{
|
|
444
|
+
return MCPError(Error);
|
|
445
|
+
}
|
|
446
|
+
if (bEnsureDefaultOps && Controller->GetNumRetargetOps() > 0 && !HasAllDefaultOps(Retargeter))
|
|
447
|
+
{
|
|
448
|
+
return MCPError(TEXT("ensureDefaultOps cannot safely augment a partial retarget op stack; complete the stack in the editor or pass ensureDefaultOps=false"));
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
UIKRigDefinition* SourceRig = bHasSourceRig
|
|
452
|
+
? LoadAssetByPath<UIKRigDefinition>(SourceRigPath)
|
|
453
|
+
: Retargeter->GetIKRigWriteable(ERetargetSourceOrTarget::Source);
|
|
454
|
+
UIKRigDefinition* TargetRig = bHasTargetRig
|
|
455
|
+
? LoadAssetByPath<UIKRigDefinition>(TargetRigPath)
|
|
456
|
+
: Retargeter->GetIKRigWriteable(ERetargetSourceOrTarget::Target);
|
|
457
|
+
if (bHasSourceRig && !SourceRig) return MCPError(FString::Printf(TEXT("Source IKRig not found: %s"), *SourceRigPath));
|
|
458
|
+
if (bHasTargetRig && !TargetRig) return MCPError(FString::Printf(TEXT("Target IKRig not found: %s"), *TargetRigPath));
|
|
459
|
+
|
|
460
|
+
USkeletalMesh* SourcePreview = bHasSourcePreview
|
|
461
|
+
? LoadAssetByPath<USkeletalMesh>(SourcePreviewPath)
|
|
462
|
+
: (bHasSourceRig ? SourceRig->GetPreviewMesh() : Controller->GetPreviewMesh(ERetargetSourceOrTarget::Source));
|
|
463
|
+
USkeletalMesh* TargetPreview = bHasTargetPreview
|
|
464
|
+
? LoadAssetByPath<USkeletalMesh>(TargetPreviewPath)
|
|
465
|
+
: (bHasTargetRig ? TargetRig->GetPreviewMesh() : Controller->GetPreviewMesh(ERetargetSourceOrTarget::Target));
|
|
466
|
+
if (bHasSourcePreview && !SourcePreview) return MCPError(FString::Printf(TEXT("Source SkeletalMesh not found: %s"), *SourcePreviewPath));
|
|
467
|
+
if (bHasTargetPreview && !TargetPreview) return MCPError(FString::Printf(TEXT("Target SkeletalMesh not found: %s"), *TargetPreviewPath));
|
|
468
|
+
|
|
469
|
+
bool bHasAutoMap = false;
|
|
470
|
+
FString AutoMapModeString;
|
|
471
|
+
EAutoMapChainType AutoMapMode = EAutoMapChainType::Exact;
|
|
472
|
+
if (!ReadOptionalString(Params, TEXT("autoMapMode"), bHasAutoMap, AutoMapModeString, Error)) return MCPError(Error);
|
|
473
|
+
if (bHasAutoMap && !ParseAutoMapMode(AutoMapModeString, AutoMapMode))
|
|
474
|
+
{
|
|
475
|
+
return MCPError(TEXT("'autoMapMode' must be 'exact', 'fuzzy' or 'clear'"));
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
TArray<FPreparedChainMapping> PreparedMappings;
|
|
479
|
+
TSet<FName> RequestedTargetChains;
|
|
480
|
+
const TArray<TSharedPtr<FJsonValue>>* MappingValues = nullptr;
|
|
481
|
+
if (Params->HasField(TEXT("chainMappings")))
|
|
482
|
+
{
|
|
483
|
+
if (!Params->TryGetArrayField(TEXT("chainMappings"), MappingValues) || !MappingValues)
|
|
484
|
+
return MCPError(TEXT("'chainMappings' must be an array"));
|
|
485
|
+
if (MappingValues->Num() > MaxRetargeterItems)
|
|
486
|
+
return MCPError(FString::Printf(TEXT("'chainMappings' exceeds the %d item limit"), MaxRetargeterItems));
|
|
487
|
+
if (!SourceRig || !TargetRig)
|
|
488
|
+
return MCPError(TEXT("'chainMappings' requires both source and target IK Rigs"));
|
|
489
|
+
const TSet<FName> SourceChains = GetChainNames(SourceRig);
|
|
490
|
+
const TSet<FName> TargetChains = GetChainNames(TargetRig);
|
|
491
|
+
for (int32 Index = 0; Index < MappingValues->Num(); ++Index)
|
|
492
|
+
{
|
|
493
|
+
const TSharedPtr<FJsonValue>& MappingValue = (*MappingValues)[Index];
|
|
494
|
+
const TSharedPtr<FJsonObject> Mapping = MappingValue.IsValid()
|
|
495
|
+
&& MappingValue->Type == EJson::Object
|
|
496
|
+
? (*MappingValues)[Index]->AsObject() : nullptr;
|
|
497
|
+
FString TargetName;
|
|
498
|
+
if (!Mapping || !Mapping->TryGetStringField(TEXT("targetChain"), TargetName) || TargetName.IsEmpty())
|
|
499
|
+
return MCPError(FString::Printf(TEXT("chainMappings[%d].targetChain must be a non-empty string"), Index));
|
|
500
|
+
const FName TargetChain(*TargetName);
|
|
501
|
+
if (TargetChain.IsNone())
|
|
502
|
+
return MCPError(FString::Printf(TEXT("chainMappings[%d].targetChain cannot be 'None'"), Index));
|
|
503
|
+
if (!TargetChains.Contains(TargetChain))
|
|
504
|
+
return MCPError(FString::Printf(TEXT("Target chain not found: %s"), *TargetName));
|
|
505
|
+
if (RequestedTargetChains.Contains(TargetChain))
|
|
506
|
+
return MCPError(FString::Printf(TEXT("Duplicate target chain mapping: %s"), *TargetName));
|
|
507
|
+
RequestedTargetChains.Add(TargetChain);
|
|
508
|
+
|
|
509
|
+
FName SourceChain = NAME_None;
|
|
510
|
+
const TSharedPtr<FJsonValue> SourceValue = Mapping->TryGetField(TEXT("sourceChain"));
|
|
511
|
+
if (SourceValue.IsValid() && SourceValue->Type != EJson::Null)
|
|
512
|
+
{
|
|
513
|
+
FString SourceName;
|
|
514
|
+
if (!SourceValue->TryGetString(SourceName) || SourceName.IsEmpty())
|
|
515
|
+
return MCPError(FString::Printf(TEXT("chainMappings[%d].sourceChain must be a non-empty string or null"), Index));
|
|
516
|
+
SourceChain = FName(*SourceName);
|
|
517
|
+
if (SourceChain.IsNone())
|
|
518
|
+
return MCPError(FString::Printf(TEXT("chainMappings[%d].sourceChain cannot be 'None'; omit it or use null to clear the mapping"), Index));
|
|
519
|
+
if (!SourceChains.Contains(SourceChain))
|
|
520
|
+
return MCPError(FString::Printf(TEXT("Source chain not found: %s"), *SourceName));
|
|
521
|
+
}
|
|
522
|
+
PreparedMappings.Add({TargetChain, SourceChain});
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
if ((bHasAutoMap || !PreparedMappings.IsEmpty()) && (!SourceRig || !TargetRig))
|
|
526
|
+
return MCPError(TEXT("Chain mapping requires both source and target IK Rigs"));
|
|
527
|
+
|
|
528
|
+
TOptional<FPreparedRetargetPose> PreparedPose;
|
|
529
|
+
const TSharedPtr<FJsonObject>* PoseObjectPointer = nullptr;
|
|
530
|
+
if (Params->HasField(TEXT("pose")))
|
|
531
|
+
{
|
|
532
|
+
if (!Params->TryGetObjectField(TEXT("pose"), PoseObjectPointer)
|
|
533
|
+
|| !PoseObjectPointer || !PoseObjectPointer->IsValid())
|
|
534
|
+
{
|
|
535
|
+
return MCPError(TEXT("'pose' must be an object"));
|
|
536
|
+
}
|
|
537
|
+
const TSharedPtr<FJsonObject>& PoseObject = *PoseObjectPointer;
|
|
538
|
+
FPreparedRetargetPose Pose;
|
|
539
|
+
FString SideString;
|
|
540
|
+
FString PoseName;
|
|
541
|
+
if (!PoseObject->TryGetStringField(TEXT("side"), SideString) || !ParseSide(SideString, Pose.Side))
|
|
542
|
+
return MCPError(TEXT("pose.side must be 'source' or 'target'"));
|
|
543
|
+
if (!PoseObject->TryGetStringField(TEXT("name"), PoseName) || PoseName.IsEmpty())
|
|
544
|
+
return MCPError(TEXT("pose.name must be a non-empty string"));
|
|
545
|
+
Pose.Name = FName(*PoseName);
|
|
546
|
+
if (Pose.Name.IsNone()) return MCPError(TEXT("pose.name cannot be 'None'"));
|
|
547
|
+
if (!ReadRetargeterOptionalBool(PoseObject, TEXT("create"), false, Pose.bCreate, Error)
|
|
548
|
+
|| !ReadRetargeterOptionalBool(PoseObject, TEXT("reset"), false, Pose.bReset, Error))
|
|
549
|
+
{
|
|
550
|
+
return MCPError(Error);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
UIKRigDefinition* PoseRig = Pose.Side == ERetargetSourceOrTarget::Source ? SourceRig : TargetRig;
|
|
554
|
+
USkeletalMesh* PoseMesh = Pose.Side == ERetargetSourceOrTarget::Source ? SourcePreview : TargetPreview;
|
|
555
|
+
if (!PoseRig) return MCPError(TEXT("Pose authoring requires an IK Rig on the selected side"));
|
|
556
|
+
const bool bPoseExists = Controller->GetRetargetPoses(Pose.Side).Contains(Pose.Name);
|
|
557
|
+
if (Pose.bCreate && bPoseExists)
|
|
558
|
+
return MCPError(FString::Printf(TEXT("Retarget pose already exists: %s"), *PoseName));
|
|
559
|
+
if (!Pose.bCreate && !bPoseExists)
|
|
560
|
+
return MCPError(FString::Printf(TEXT("Retarget pose not found: %s"), *PoseName));
|
|
561
|
+
|
|
562
|
+
FString AutoAlignString;
|
|
563
|
+
if (PoseObject->HasField(TEXT("autoAlign")))
|
|
564
|
+
{
|
|
565
|
+
if (!PoseObject->TryGetStringField(TEXT("autoAlign"), AutoAlignString)
|
|
566
|
+
|| !ParseAutoAlignMethod(AutoAlignString, Pose.AutoAlignMethod))
|
|
567
|
+
{
|
|
568
|
+
return MCPError(TEXT("pose.autoAlign must be 'chain_to_chain', 'mesh_to_mesh', 'local_axes' or 'global_axes'"));
|
|
569
|
+
}
|
|
570
|
+
Pose.bHasAutoAlign = true;
|
|
571
|
+
if (!SourcePreview || !TargetPreview)
|
|
572
|
+
return MCPError(TEXT("pose.autoAlign requires both source and target preview meshes"));
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const TArray<TSharedPtr<FJsonValue>>* BoneValues = nullptr;
|
|
576
|
+
if (PoseObject->HasField(TEXT("bones")))
|
|
577
|
+
{
|
|
578
|
+
if (!Pose.bHasAutoAlign) return MCPError(TEXT("pose.bones requires pose.autoAlign"));
|
|
579
|
+
if (!PoseObject->TryGetArrayField(TEXT("bones"), BoneValues) || !BoneValues)
|
|
580
|
+
return MCPError(TEXT("pose.bones must be an array of bone names"));
|
|
581
|
+
if (BoneValues->Num() > MaxRetargeterItems)
|
|
582
|
+
return MCPError(FString::Printf(TEXT("pose.bones exceeds the %d item limit"), MaxRetargeterItems));
|
|
583
|
+
TSet<FName> SeenBones;
|
|
584
|
+
for (int32 Index = 0; Index < BoneValues->Num(); ++Index)
|
|
585
|
+
{
|
|
586
|
+
FString BoneString;
|
|
587
|
+
if (!(*BoneValues)[Index].IsValid()
|
|
588
|
+
|| !(*BoneValues)[Index]->TryGetString(BoneString) || BoneString.IsEmpty())
|
|
589
|
+
return MCPError(FString::Printf(TEXT("pose.bones[%d] must be a non-empty string"), Index));
|
|
590
|
+
const FName Bone(*BoneString);
|
|
591
|
+
if (Bone.IsNone()) return MCPError(FString::Printf(TEXT("pose.bones[%d] cannot be 'None'"), Index));
|
|
592
|
+
if (SeenBones.Contains(Bone)) return MCPError(FString::Printf(TEXT("Duplicate pose auto-align bone: %s"), *BoneString));
|
|
593
|
+
if (!HasBone(PoseRig, PoseMesh, Bone)) return MCPError(FString::Printf(TEXT("Pose bone not found: %s"), *BoneString));
|
|
594
|
+
SeenBones.Add(Bone);
|
|
595
|
+
Pose.AutoAlignBones.Add(Bone);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
Pose.bAutoAlignAll = Pose.bHasAutoAlign && Pose.AutoAlignBones.IsEmpty();
|
|
599
|
+
if (Pose.bAutoAlignAll && !Pose.bCreate && !Pose.bReset)
|
|
600
|
+
{
|
|
601
|
+
return MCPError(TEXT("Auto-aligning all bones resets the entire existing pose; pass pose.reset=true to acknowledge that replacement"));
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const TArray<TSharedPtr<FJsonValue>>* RotationValues = nullptr;
|
|
605
|
+
if (PoseObject->HasField(TEXT("rotationOffsets")))
|
|
606
|
+
{
|
|
607
|
+
if (!PoseObject->TryGetArrayField(TEXT("rotationOffsets"), RotationValues) || !RotationValues)
|
|
608
|
+
return MCPError(TEXT("pose.rotationOffsets must be an array"));
|
|
609
|
+
if (RotationValues->Num() > MaxRetargeterItems)
|
|
610
|
+
return MCPError(FString::Printf(TEXT("pose.rotationOffsets exceeds the %d item limit"), MaxRetargeterItems));
|
|
611
|
+
TSet<FName> SeenBones;
|
|
612
|
+
for (int32 Index = 0; Index < RotationValues->Num(); ++Index)
|
|
613
|
+
{
|
|
614
|
+
const TSharedPtr<FJsonValue>& RotationValue = (*RotationValues)[Index];
|
|
615
|
+
const TSharedPtr<FJsonObject> Offset = RotationValue.IsValid()
|
|
616
|
+
&& RotationValue->Type == EJson::Object
|
|
617
|
+
? (*RotationValues)[Index]->AsObject() : nullptr;
|
|
618
|
+
FString BoneString;
|
|
619
|
+
if (!Offset || !Offset->TryGetStringField(TEXT("bone"), BoneString) || BoneString.IsEmpty())
|
|
620
|
+
return MCPError(FString::Printf(TEXT("pose.rotationOffsets[%d].bone must be a non-empty string"), Index));
|
|
621
|
+
const FName Bone(*BoneString);
|
|
622
|
+
if (Bone.IsNone()) return MCPError(FString::Printf(TEXT("pose.rotationOffsets[%d].bone cannot be 'None'"), Index));
|
|
623
|
+
if (SeenBones.Contains(Bone)) return MCPError(FString::Printf(TEXT("Duplicate pose rotation bone: %s"), *BoneString));
|
|
624
|
+
if (!HasBone(PoseRig, PoseMesh, Bone)) return MCPError(FString::Printf(TEXT("Pose bone not found: %s"), *BoneString));
|
|
625
|
+
FQuat Rotation;
|
|
626
|
+
if (!ReadNormalizedQuaternion(Offset, Rotation, Error))
|
|
627
|
+
return MCPError(FString::Printf(TEXT("pose.rotationOffsets[%d]: %s"), Index, *Error));
|
|
628
|
+
SeenBones.Add(Bone);
|
|
629
|
+
Pose.Rotations.Add({Bone, Rotation});
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
if (PoseObject->HasField(TEXT("rootOffsetZ")))
|
|
634
|
+
{
|
|
635
|
+
double RootOffsetZ = 0.0;
|
|
636
|
+
if (!PoseObject->TryGetNumberField(TEXT("rootOffsetZ"), RootOffsetZ) || !FMath::IsFinite(RootOffsetZ))
|
|
637
|
+
return MCPError(TEXT("pose.rootOffsetZ must be a finite number"));
|
|
638
|
+
Pose.RootOffsetZ = RootOffsetZ;
|
|
639
|
+
}
|
|
640
|
+
if (PoseObject->HasField(TEXT("snapBoneToGround")))
|
|
641
|
+
{
|
|
642
|
+
FString SnapBoneString;
|
|
643
|
+
if (!PoseObject->TryGetStringField(TEXT("snapBoneToGround"), SnapBoneString) || SnapBoneString.IsEmpty())
|
|
644
|
+
return MCPError(TEXT("pose.snapBoneToGround must be a non-empty bone name"));
|
|
645
|
+
const FName SnapBone(*SnapBoneString);
|
|
646
|
+
if (SnapBone.IsNone()) return MCPError(TEXT("pose.snapBoneToGround cannot be 'None'"));
|
|
647
|
+
if (!SourcePreview || !TargetPreview)
|
|
648
|
+
return MCPError(TEXT("pose.snapBoneToGround requires both source and target preview meshes"));
|
|
649
|
+
if (!PoseMesh || PoseMesh->GetRefSkeleton().FindBoneIndex(SnapBone) == INDEX_NONE)
|
|
650
|
+
return MCPError(FString::Printf(TEXT("Snap bone is not present in the selected preview mesh: %s"), *SnapBoneString));
|
|
651
|
+
Pose.SnapBone = SnapBone;
|
|
652
|
+
}
|
|
653
|
+
if (Pose.RootOffsetZ.IsSet() && Pose.SnapBone.IsSet())
|
|
654
|
+
return MCPError(TEXT("pose.rootOffsetZ and pose.snapBoneToGround are mutually exclusive because snapping changes the root offset"));
|
|
655
|
+
PreparedPose = MoveTemp(Pose);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
bool bMutationFailed = false;
|
|
659
|
+
FString MutationError;
|
|
660
|
+
bool bAddedDefaultOps = false;
|
|
661
|
+
bool bValidationRan = false;
|
|
662
|
+
bool bValidationInitialized = false;
|
|
663
|
+
TArray<FText> ValidationErrors;
|
|
664
|
+
TArray<FText> ValidationWarnings;
|
|
665
|
+
{
|
|
666
|
+
const FScopedTransaction Transaction(NSLOCTEXT("UE_MCP", "ConfigureIKRetargeter", "Configure IK Retargeter"));
|
|
667
|
+
Retargeter->Modify();
|
|
668
|
+
|
|
669
|
+
if (bEnsureDefaultOps && !HasAllDefaultOps(Retargeter))
|
|
670
|
+
{
|
|
671
|
+
Controller->AddDefaultOps();
|
|
672
|
+
bAddedDefaultOps = true;
|
|
673
|
+
if (!HasAllDefaultOps(Retargeter))
|
|
674
|
+
{
|
|
675
|
+
bMutationFailed = true;
|
|
676
|
+
MutationError = TEXT("Unreal failed to install the complete default retarget op stack");
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
if (!bMutationFailed && bHasSourceRig) Controller->SetIKRig(ERetargetSourceOrTarget::Source, SourceRig);
|
|
681
|
+
if (!bMutationFailed && bHasTargetRig) Controller->SetIKRig(ERetargetSourceOrTarget::Target, TargetRig);
|
|
682
|
+
if (!bMutationFailed && (bHasSourceRig || bAddedDefaultOps) && SourceRig)
|
|
683
|
+
Controller->AssignIKRigToAllOps(ERetargetSourceOrTarget::Source, SourceRig);
|
|
684
|
+
if (!bMutationFailed && (bHasTargetRig || bAddedDefaultOps) && TargetRig)
|
|
685
|
+
Controller->AssignIKRigToAllOps(ERetargetSourceOrTarget::Target, TargetRig);
|
|
686
|
+
if (!bMutationFailed && bHasSourcePreview) Controller->SetPreviewMesh(ERetargetSourceOrTarget::Source, SourcePreview);
|
|
687
|
+
if (!bMutationFailed && bHasTargetPreview) Controller->SetPreviewMesh(ERetargetSourceOrTarget::Target, TargetPreview);
|
|
688
|
+
|
|
689
|
+
if (!bMutationFailed && bHasAutoMap) Controller->AutoMapChains(AutoMapMode, bForceRemap);
|
|
690
|
+
if (!bMutationFailed)
|
|
691
|
+
{
|
|
692
|
+
for (const FPreparedChainMapping& Mapping : PreparedMappings)
|
|
693
|
+
{
|
|
694
|
+
if (!Controller->SetSourceChain(Mapping.SourceChain, Mapping.TargetChain))
|
|
695
|
+
{
|
|
696
|
+
bMutationFailed = true;
|
|
697
|
+
MutationError = FString::Printf(TEXT("No retarget op accepted target chain '%s'"), *Mapping.TargetChain.ToString());
|
|
698
|
+
break;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
if (!bMutationFailed && PreparedPose.IsSet()
|
|
703
|
+
&& PreparedPose.GetValue().bHasAutoAlign && !PreparedPose.GetValue().bAutoAlignAll)
|
|
704
|
+
{
|
|
705
|
+
const FPreparedRetargetPose& Pose = PreparedPose.GetValue();
|
|
706
|
+
FIKRetargetProcessor MappingProcessor;
|
|
707
|
+
FScopedRetargetEditorInstanceRestore RestoreEditorInstances(Retargeter);
|
|
708
|
+
FRetargetInitParameters InitParameters;
|
|
709
|
+
InitParameters.SourceSkeletalMesh = SourcePreview;
|
|
710
|
+
InitParameters.TargetSkeletalMesh = TargetPreview;
|
|
711
|
+
InitParameters.RetargeterAsset = Retargeter;
|
|
712
|
+
InitParameters.bSuppressWarnings = true;
|
|
713
|
+
MappingProcessor.Initialize(InitParameters);
|
|
714
|
+
const TArray<FText>& MappingErrors = MappingProcessor.Log.GetErrors();
|
|
715
|
+
if (!MappingProcessor.IsInitialized() || !MappingErrors.IsEmpty())
|
|
716
|
+
{
|
|
717
|
+
bMutationFailed = true;
|
|
718
|
+
MutationError = !MappingErrors.IsEmpty()
|
|
719
|
+
? MappingErrors[0].ToString()
|
|
720
|
+
: TEXT("Retarget pose auto-alignment could not initialize the retarget processor");
|
|
721
|
+
}
|
|
722
|
+
else
|
|
723
|
+
{
|
|
724
|
+
for (const FName Bone : Pose.AutoAlignBones)
|
|
725
|
+
{
|
|
726
|
+
if (!MappingProcessor.IsBoneMapped(Bone, Pose.Side))
|
|
727
|
+
{
|
|
728
|
+
bMutationFailed = true;
|
|
729
|
+
MutationError = FString::Printf(
|
|
730
|
+
TEXT("Retarget pose auto-align bone is not mapped: %s"), *Bone.ToString());
|
|
731
|
+
break;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
if (!bMutationFailed && PreparedPose.IsSet())
|
|
738
|
+
{
|
|
739
|
+
const FPreparedRetargetPose& Pose = PreparedPose.GetValue();
|
|
740
|
+
if (Pose.bCreate)
|
|
741
|
+
{
|
|
742
|
+
const FName CreatedPose = Controller->CreateRetargetPose(Pose.Name, Pose.Side);
|
|
743
|
+
if (CreatedPose != Pose.Name)
|
|
744
|
+
{
|
|
745
|
+
bMutationFailed = true;
|
|
746
|
+
MutationError = FString::Printf(TEXT("Unreal created retarget pose '%s' instead of '%s'"), *CreatedPose.ToString(), *Pose.Name.ToString());
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
else if (!Controller->SetCurrentRetargetPose(Pose.Name, Pose.Side))
|
|
750
|
+
{
|
|
751
|
+
bMutationFailed = true;
|
|
752
|
+
MutationError = FString::Printf(TEXT("Unreal failed to select retarget pose '%s'"), *Pose.Name.ToString());
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
if (!bMutationFailed && Pose.bReset)
|
|
756
|
+
Controller->ResetRetargetPose(Pose.Name, TArray<FName>(), Pose.Side);
|
|
757
|
+
if (!bMutationFailed && Pose.bHasAutoAlign)
|
|
758
|
+
{
|
|
759
|
+
if (CountMappedChains(Retargeter) == 0)
|
|
760
|
+
{
|
|
761
|
+
bMutationFailed = true;
|
|
762
|
+
MutationError = TEXT("Retarget pose auto-alignment requires at least one mapped chain");
|
|
763
|
+
}
|
|
764
|
+
else if (Pose.bAutoAlignAll)
|
|
765
|
+
{
|
|
766
|
+
Controller->AutoAlignAllBones(Pose.Side, Pose.AutoAlignMethod);
|
|
767
|
+
}
|
|
768
|
+
else
|
|
769
|
+
{
|
|
770
|
+
Controller->AutoAlignBones(Pose.AutoAlignBones, Pose.AutoAlignMethod, Pose.Side);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
if (!bMutationFailed)
|
|
774
|
+
{
|
|
775
|
+
for (const FPreparedPoseRotation& Rotation : Pose.Rotations)
|
|
776
|
+
Controller->SetRotationOffsetForRetargetPoseBone(Rotation.Bone, Rotation.Rotation, Pose.Side);
|
|
777
|
+
if (Pose.RootOffsetZ.IsSet())
|
|
778
|
+
{
|
|
779
|
+
const double CurrentZ = Controller->GetRootOffsetInRetargetPose(Pose.Side).Z;
|
|
780
|
+
Controller->SetRootOffsetInRetargetPose(FVector(0.0, 0.0, Pose.RootOffsetZ.GetValue() - CurrentZ), Pose.Side);
|
|
781
|
+
}
|
|
782
|
+
if (Pose.SnapBone.IsSet()) Controller->SnapBoneToGround(Pose.SnapBone.GetValue(), Pose.Side);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
if (!bMutationFailed) Controller->CleanAsset();
|
|
787
|
+
|
|
788
|
+
if (!bMutationFailed && bHasSourceRig
|
|
789
|
+
&& Controller->GetIKRig(ERetargetSourceOrTarget::Source) != SourceRig)
|
|
790
|
+
{
|
|
791
|
+
bMutationFailed = true;
|
|
792
|
+
MutationError = TEXT("Source IK Rig assignment did not survive native readback");
|
|
793
|
+
}
|
|
794
|
+
if (!bMutationFailed && bHasTargetRig
|
|
795
|
+
&& Controller->GetIKRig(ERetargetSourceOrTarget::Target) != TargetRig)
|
|
796
|
+
{
|
|
797
|
+
bMutationFailed = true;
|
|
798
|
+
MutationError = TEXT("Target IK Rig assignment did not survive native readback");
|
|
799
|
+
}
|
|
800
|
+
if (!bMutationFailed && bHasSourcePreview
|
|
801
|
+
&& Controller->GetPreviewMesh(ERetargetSourceOrTarget::Source) != SourcePreview)
|
|
802
|
+
{
|
|
803
|
+
bMutationFailed = true;
|
|
804
|
+
MutationError = TEXT("Source preview mesh assignment did not survive native readback");
|
|
805
|
+
}
|
|
806
|
+
if (!bMutationFailed && bHasTargetPreview
|
|
807
|
+
&& Controller->GetPreviewMesh(ERetargetSourceOrTarget::Target) != TargetPreview)
|
|
808
|
+
{
|
|
809
|
+
bMutationFailed = true;
|
|
810
|
+
MutationError = TEXT("Target preview mesh assignment did not survive native readback");
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
if (!bMutationFailed)
|
|
814
|
+
{
|
|
815
|
+
for (const FPreparedChainMapping& Expected : PreparedMappings)
|
|
816
|
+
{
|
|
817
|
+
bool bFoundTarget = false;
|
|
818
|
+
for (const FInstancedStruct& OpStruct : Retargeter->GetRetargetOps())
|
|
819
|
+
{
|
|
820
|
+
const FIKRetargetOpBase* Op = OpStruct.GetPtr<FIKRetargetOpBase>();
|
|
821
|
+
const FRetargetChainMapping* Mapping = Op ? Op->GetChainMapping() : nullptr;
|
|
822
|
+
if (!Mapping || !Mapping->HasChain(Expected.TargetChain, ERetargetSourceOrTarget::Target)) continue;
|
|
823
|
+
bFoundTarget = true;
|
|
824
|
+
if (Mapping->GetChainMappedTo(Expected.TargetChain, ERetargetSourceOrTarget::Target) != Expected.SourceChain)
|
|
825
|
+
{
|
|
826
|
+
bMutationFailed = true;
|
|
827
|
+
MutationError = FString::Printf(TEXT("Chain mapping readback failed for target '%s' on op '%s'"),
|
|
828
|
+
*Expected.TargetChain.ToString(), *Op->GetName().ToString());
|
|
829
|
+
break;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
if (bMutationFailed) break;
|
|
833
|
+
if (!bFoundTarget)
|
|
834
|
+
{
|
|
835
|
+
bMutationFailed = true;
|
|
836
|
+
MutationError = FString::Printf(TEXT("No chain mapping contained target '%s' during readback"), *Expected.TargetChain.ToString());
|
|
837
|
+
break;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
if (!bMutationFailed && bHasAutoMap && AutoMapMode == EAutoMapChainType::Clear)
|
|
843
|
+
{
|
|
844
|
+
for (const FInstancedStruct& OpStruct : Retargeter->GetRetargetOps())
|
|
845
|
+
{
|
|
846
|
+
const FIKRetargetOpBase* Op = OpStruct.GetPtr<FIKRetargetOpBase>();
|
|
847
|
+
const FRetargetChainMapping* Mapping = Op ? Op->GetChainMapping() : nullptr;
|
|
848
|
+
if (!Mapping) continue;
|
|
849
|
+
for (const FRetargetChainPair& Pair : Mapping->GetChainPairs())
|
|
850
|
+
{
|
|
851
|
+
if (!RequestedTargetChains.Contains(Pair.TargetChainName) && !Pair.SourceChainName.IsNone())
|
|
852
|
+
{
|
|
853
|
+
bMutationFailed = true;
|
|
854
|
+
MutationError = FString::Printf(TEXT("Clear auto-map readback left target chain '%s' mapped"), *Pair.TargetChainName.ToString());
|
|
855
|
+
break;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
if (bMutationFailed) break;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
if (!bMutationFailed && PreparedPose.IsSet())
|
|
863
|
+
{
|
|
864
|
+
const FPreparedRetargetPose& Pose = PreparedPose.GetValue();
|
|
865
|
+
if (Controller->GetCurrentRetargetPoseName(Pose.Side) != Pose.Name)
|
|
866
|
+
{
|
|
867
|
+
bMutationFailed = true;
|
|
868
|
+
MutationError = TEXT("Retarget pose selection did not survive native readback");
|
|
869
|
+
}
|
|
870
|
+
for (const FPreparedPoseRotation& Expected : Pose.Rotations)
|
|
871
|
+
{
|
|
872
|
+
const FQuat Actual = Controller->GetRotationOffsetForRetargetPoseBone(Expected.Bone, Pose.Side).GetNormalized();
|
|
873
|
+
if (FMath::Abs(Actual | Expected.Rotation) < 1.0 - 1e-6)
|
|
874
|
+
{
|
|
875
|
+
bMutationFailed = true;
|
|
876
|
+
MutationError = FString::Printf(TEXT("Retarget pose rotation readback failed for bone '%s'"), *Expected.Bone.ToString());
|
|
877
|
+
break;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
if (!bMutationFailed && Pose.RootOffsetZ.IsSet()
|
|
881
|
+
&& !FMath::IsNearlyEqual(Controller->GetRootOffsetInRetargetPose(Pose.Side).Z, Pose.RootOffsetZ.GetValue(), 1e-4))
|
|
882
|
+
{
|
|
883
|
+
bMutationFailed = true;
|
|
884
|
+
MutationError = TEXT("Retarget pose root offset did not survive native readback");
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
USkeletalMesh* EffectiveSourceMesh = Controller->GetPreviewMesh(ERetargetSourceOrTarget::Source);
|
|
889
|
+
USkeletalMesh* EffectiveTargetMesh = Controller->GetPreviewMesh(ERetargetSourceOrTarget::Target);
|
|
890
|
+
if (!bMutationFailed && EffectiveSourceMesh && EffectiveTargetMesh)
|
|
891
|
+
{
|
|
892
|
+
bValidationRan = true;
|
|
893
|
+
FIKRetargetProcessor Processor;
|
|
894
|
+
FScopedRetargetEditorInstanceRestore RestoreEditorInstances(Retargeter);
|
|
895
|
+
FRetargetInitParameters InitParameters;
|
|
896
|
+
InitParameters.SourceSkeletalMesh = EffectiveSourceMesh;
|
|
897
|
+
InitParameters.TargetSkeletalMesh = EffectiveTargetMesh;
|
|
898
|
+
InitParameters.RetargeterAsset = Retargeter;
|
|
899
|
+
InitParameters.bSuppressWarnings = false;
|
|
900
|
+
Processor.Initialize(InitParameters);
|
|
901
|
+
bValidationInitialized = Processor.IsInitialized();
|
|
902
|
+
ValidationErrors = Processor.Log.GetErrors();
|
|
903
|
+
ValidationWarnings = Processor.Log.GetWarnings();
|
|
904
|
+
if (!bValidationInitialized || !ValidationErrors.IsEmpty())
|
|
905
|
+
{
|
|
906
|
+
bMutationFailed = true;
|
|
907
|
+
MutationError = !ValidationErrors.IsEmpty()
|
|
908
|
+
? ValidationErrors[0].ToString()
|
|
909
|
+
: TEXT("IK Retargeter processor validation failed to initialize");
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
if (bMutationFailed)
|
|
915
|
+
{
|
|
916
|
+
const bool bRolledBack = GEditor && GEditor->UndoTransaction();
|
|
917
|
+
return MCPError(bRolledBack
|
|
918
|
+
? MutationError
|
|
919
|
+
: MutationError + TEXT("; the editor transaction could not be rolled back"));
|
|
920
|
+
}
|
|
921
|
+
if (!SaveAssetPackage(Retargeter))
|
|
922
|
+
{
|
|
923
|
+
const bool bRolledBack = GEditor && GEditor->UndoTransaction();
|
|
924
|
+
return MCPError(bRolledBack
|
|
925
|
+
? TEXT("IK Retargeter configuration could not be saved and was rolled back")
|
|
926
|
+
: TEXT("IK Retargeter configuration could not be saved and the editor transaction could not be rolled back"));
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
auto Result = MCPSuccess();
|
|
930
|
+
MCPSetUpdated(Result);
|
|
931
|
+
Result->SetStringField(TEXT("retargeterPath"), Retargeter->GetPathName());
|
|
932
|
+
if (const UIKRigDefinition* Rig = Controller->GetIKRig(ERetargetSourceOrTarget::Source))
|
|
933
|
+
Result->SetStringField(TEXT("sourceRig"), Rig->GetPathName());
|
|
934
|
+
if (const UIKRigDefinition* Rig = Controller->GetIKRig(ERetargetSourceOrTarget::Target))
|
|
935
|
+
Result->SetStringField(TEXT("targetRig"), Rig->GetPathName());
|
|
936
|
+
if (USkeletalMesh* Mesh = Controller->GetPreviewMesh(ERetargetSourceOrTarget::Source))
|
|
937
|
+
Result->SetStringField(TEXT("sourcePreviewMesh"), Mesh->GetPathName());
|
|
938
|
+
if (USkeletalMesh* Mesh = Controller->GetPreviewMesh(ERetargetSourceOrTarget::Target))
|
|
939
|
+
Result->SetStringField(TEXT("targetPreviewMesh"), Mesh->GetPathName());
|
|
940
|
+
Result->SetBoolField(TEXT("defaultOpsComplete"), HasAllDefaultOps(Retargeter));
|
|
941
|
+
Result->SetBoolField(TEXT("defaultOpsAdded"), bAddedDefaultOps);
|
|
942
|
+
Result->SetArrayField(TEXT("ops"), BuildOpsJson(Retargeter));
|
|
943
|
+
Result->SetArrayField(TEXT("mappings"), BuildMappingsJson(Retargeter));
|
|
944
|
+
Result->SetNumberField(TEXT("mappedChainCountAcrossOps"), CountMappedChains(Retargeter));
|
|
945
|
+
if (PreparedPose.IsSet())
|
|
946
|
+
{
|
|
947
|
+
const FPreparedRetargetPose& Pose = PreparedPose.GetValue();
|
|
948
|
+
Result->SetObjectField(TEXT("pose"), BuildPoseJson(Controller, Pose.Side, Pose.Name, Pose.bAutoAlignAll));
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
auto Validation = MakeShared<FJsonObject>();
|
|
952
|
+
Validation->SetBoolField(TEXT("ran"), bValidationRan);
|
|
953
|
+
Validation->SetBoolField(TEXT("initialized"), bValidationInitialized);
|
|
954
|
+
Validation->SetArrayField(TEXT("errors"), TextArrayJson(ValidationErrors));
|
|
955
|
+
Validation->SetArrayField(TEXT("warnings"), TextArrayJson(ValidationWarnings));
|
|
956
|
+
if (!bValidationRan)
|
|
957
|
+
Validation->SetStringField(TEXT("reason"), TEXT("Both source and target preview meshes are required for processor validation"));
|
|
958
|
+
Result->SetObjectField(TEXT("validation"), Validation);
|
|
959
|
+
return MCPResult(Result);
|
|
960
|
+
#endif
|
|
961
|
+
}
|