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,900 @@
|
|
|
1
|
+
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
2
|
+
|
|
3
|
+
#include "AnimationHandlers.h"
|
|
4
|
+
#include "HandlerUtils.h"
|
|
5
|
+
|
|
6
|
+
#if UE_MCP_HAS_5_8_API
|
|
7
|
+
#include "Editor.h"
|
|
8
|
+
#include "EditorAssetLibrary.h"
|
|
9
|
+
#include "Engine/SkeletalMesh.h"
|
|
10
|
+
#include "Rig/IKRigDataTypes.h"
|
|
11
|
+
#include "Rig/IKRigDefinition.h"
|
|
12
|
+
#include "Rig/IKRigSkeleton.h"
|
|
13
|
+
#include "Rig/Solvers/IKRigFullBodyIK.h"
|
|
14
|
+
#include "RigEditor/IKRigAutoCharacterizer.h"
|
|
15
|
+
#include "RigEditor/IKRigController.h"
|
|
16
|
+
#include "ScopedTransaction.h"
|
|
17
|
+
#include "StructUtils/InstancedStruct.h"
|
|
18
|
+
#endif
|
|
19
|
+
|
|
20
|
+
namespace UE_MCP_IKRigAuthoring
|
|
21
|
+
{
|
|
22
|
+
static TSharedPtr<FJsonValue> Error(
|
|
23
|
+
const FString& Code,
|
|
24
|
+
const FString& Message,
|
|
25
|
+
const TOptional<bool>& RollbackSucceeded = TOptional<bool>())
|
|
26
|
+
{
|
|
27
|
+
TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
|
|
28
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
29
|
+
Result->SetStringField(TEXT("errorCode"), Code);
|
|
30
|
+
Result->SetStringField(TEXT("error"), Message);
|
|
31
|
+
Result->SetBoolField(TEXT("rollbackSafe"), !RollbackSucceeded.IsSet() || RollbackSucceeded.GetValue());
|
|
32
|
+
if (RollbackSucceeded.IsSet())
|
|
33
|
+
{
|
|
34
|
+
Result->SetBoolField(TEXT("rollbackAttempted"), true);
|
|
35
|
+
Result->SetBoolField(TEXT("rollbackSucceeded"), RollbackSucceeded.GetValue());
|
|
36
|
+
}
|
|
37
|
+
return MCPResult(Result);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
#if UE_MCP_HAS_5_8_API
|
|
41
|
+
|
|
42
|
+
constexpr int32 MaxChains = 256;
|
|
43
|
+
constexpr int32 MaxGoals = 256;
|
|
44
|
+
constexpr int32 MaxExclusions = 2048;
|
|
45
|
+
|
|
46
|
+
struct FChainRequest
|
|
47
|
+
{
|
|
48
|
+
FName Name;
|
|
49
|
+
FName StartBone;
|
|
50
|
+
FName EndBone;
|
|
51
|
+
FName Goal;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
struct FGoalRequest
|
|
55
|
+
{
|
|
56
|
+
FName Name;
|
|
57
|
+
FName Bone;
|
|
58
|
+
TOptional<float> PositionAlpha;
|
|
59
|
+
TOptional<float> RotationAlpha;
|
|
60
|
+
TOptional<int32> ChainDepth;
|
|
61
|
+
TOptional<float> StrengthAlpha;
|
|
62
|
+
TOptional<float> PullChainAlpha;
|
|
63
|
+
TOptional<float> PinRotation;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
struct FFullBodyRequest
|
|
67
|
+
{
|
|
68
|
+
bool bPresent = false;
|
|
69
|
+
TOptional<int32> SolverIndex;
|
|
70
|
+
FName RootBone;
|
|
71
|
+
TOptional<bool> bEnabled;
|
|
72
|
+
TArray<FGoalRequest> Goals;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
struct FExclusionRequest
|
|
76
|
+
{
|
|
77
|
+
FName Bone;
|
|
78
|
+
bool bExcluded = false;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
static bool ReadRequiredString(
|
|
82
|
+
const TSharedPtr<FJsonObject>& Object,
|
|
83
|
+
const TCHAR* Field,
|
|
84
|
+
FString& Out,
|
|
85
|
+
FString& OutError)
|
|
86
|
+
{
|
|
87
|
+
if (!Object.IsValid() || !Object->TryGetStringField(Field, Out))
|
|
88
|
+
{
|
|
89
|
+
OutError = FString::Printf(TEXT("'%s' must be a string"), Field);
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
Out.TrimStartAndEndInline();
|
|
93
|
+
if (Out.IsEmpty())
|
|
94
|
+
{
|
|
95
|
+
OutError = FString::Printf(TEXT("'%s' must not be empty"), Field);
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
static bool ReadOptionalUnitFloat(
|
|
102
|
+
const TSharedPtr<FJsonObject>& Object,
|
|
103
|
+
const TCHAR* Field,
|
|
104
|
+
TOptional<float>& Out,
|
|
105
|
+
FString& OutError)
|
|
106
|
+
{
|
|
107
|
+
if (!Object->HasField(Field))
|
|
108
|
+
{
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
double Value = 0.0;
|
|
112
|
+
if (!Object->TryGetNumberField(Field, Value) || !FMath::IsFinite(Value) || Value < 0.0 || Value > 1.0)
|
|
113
|
+
{
|
|
114
|
+
OutError = FString::Printf(TEXT("'%s' must be a finite number in [0, 1]"), Field);
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
Out = static_cast<float>(Value);
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
static bool ReadOptionalNonNegativeInteger(
|
|
122
|
+
const TSharedPtr<FJsonObject>& Object,
|
|
123
|
+
const TCHAR* Field,
|
|
124
|
+
TOptional<int32>& Out,
|
|
125
|
+
FString& OutError)
|
|
126
|
+
{
|
|
127
|
+
if (!Object->HasField(Field))
|
|
128
|
+
{
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
double Value = 0.0;
|
|
132
|
+
if (!Object->TryGetNumberField(Field, Value) || !FMath::IsFinite(Value) ||
|
|
133
|
+
Value < 0.0 || Value > static_cast<double>(MAX_int32) || Value != FMath::TruncToDouble(Value))
|
|
134
|
+
{
|
|
135
|
+
OutError = FString::Printf(TEXT("'%s' must be a non-negative integer"), Field);
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
Out = static_cast<int32>(Value);
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
static bool ReadIKRigOptionalBool(
|
|
143
|
+
const TSharedPtr<FJsonObject>& Object,
|
|
144
|
+
const TCHAR* Field,
|
|
145
|
+
TOptional<bool>& Out,
|
|
146
|
+
FString& OutError)
|
|
147
|
+
{
|
|
148
|
+
if (!Object->HasField(Field))
|
|
149
|
+
{
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
bool Value = false;
|
|
153
|
+
if (!Object->TryGetBoolField(Field, Value))
|
|
154
|
+
{
|
|
155
|
+
OutError = FString::Printf(TEXT("'%s' must be a boolean"), Field);
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
Out = Value;
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
static bool IsFullBodySolver(const UIKRigController* Controller, const int32 SolverIndex)
|
|
163
|
+
{
|
|
164
|
+
FInstancedStruct* SolverStruct = Controller->GetSolverStructAtIndex(SolverIndex);
|
|
165
|
+
return SolverStruct && SolverStruct->GetScriptStruct() == FIKRigFullBodyIKSolver::StaticStruct();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
static FIKRigFullBodyIKSolver* GetFullBodySolver(const UIKRigController* Controller, const int32 SolverIndex)
|
|
169
|
+
{
|
|
170
|
+
return IsFullBodySolver(Controller, SolverIndex)
|
|
171
|
+
? static_cast<FIKRigFullBodyIKSolver*>(Controller->GetSolverAtIndex(SolverIndex))
|
|
172
|
+
: nullptr;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
static bool GoalNameIsSafe(const FString& Name)
|
|
176
|
+
{
|
|
177
|
+
FString Sanitized = Name;
|
|
178
|
+
UIKRigController::SanitizeGoalName(Sanitized);
|
|
179
|
+
return Sanitized == Name && !FName(*Name).IsNone();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
#endif
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
TSharedPtr<FJsonValue> FAnimationHandlers::ConfigureIKRig(const TSharedPtr<FJsonObject>& Params)
|
|
186
|
+
{
|
|
187
|
+
#if !UE_MCP_HAS_5_8_API
|
|
188
|
+
return UE_MCP_IKRigAuthoring::Error(
|
|
189
|
+
TEXT("unsupported_engine_version"),
|
|
190
|
+
TEXT("IK Rig authoring requires Unreal Engine 5.8 or newer"));
|
|
191
|
+
#else
|
|
192
|
+
using namespace UE_MCP_IKRigAuthoring;
|
|
193
|
+
using UE_MCP_IKRigAuthoring::Error;
|
|
194
|
+
|
|
195
|
+
if (!Params.IsValid())
|
|
196
|
+
{
|
|
197
|
+
return Error(TEXT("invalid_params"), TEXT("Parameters are required"));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
FString ParseError;
|
|
201
|
+
FString RigPath;
|
|
202
|
+
if (!ReadRequiredString(Params, TEXT("rigPath"), RigPath, ParseError))
|
|
203
|
+
{
|
|
204
|
+
return Error(TEXT("invalid_params"), ParseError);
|
|
205
|
+
}
|
|
206
|
+
if (MCPIsProtectedAssetPath(RigPath))
|
|
207
|
+
{
|
|
208
|
+
return Error(TEXT("protected_asset"), FString::Printf(TEXT("Protected asset cannot be modified: %s"), *RigPath));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
FString AutoSetup;
|
|
212
|
+
if (Params->HasField(TEXT("autoSetup")))
|
|
213
|
+
{
|
|
214
|
+
if (!Params->TryGetStringField(TEXT("autoSetup"), AutoSetup) ||
|
|
215
|
+
(AutoSetup != TEXT("retarget") && AutoSetup != TEXT("full_body")))
|
|
216
|
+
{
|
|
217
|
+
return Error(TEXT("invalid_params"), TEXT("'autoSetup' must be 'retarget' or 'full_body'"));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const bool bAutoRetarget = AutoSetup == TEXT("retarget") || AutoSetup == TEXT("full_body");
|
|
221
|
+
const bool bAutoFullBody = AutoSetup == TEXT("full_body");
|
|
222
|
+
|
|
223
|
+
TOptional<FName> RetargetRoot;
|
|
224
|
+
if (Params->HasField(TEXT("retargetRoot")))
|
|
225
|
+
{
|
|
226
|
+
FString Value;
|
|
227
|
+
if (!ReadRequiredString(Params, TEXT("retargetRoot"), Value, ParseError))
|
|
228
|
+
{
|
|
229
|
+
return Error(TEXT("invalid_params"), ParseError);
|
|
230
|
+
}
|
|
231
|
+
RetargetRoot = FName(*Value);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
TOptional<FName> RootMotionBone;
|
|
235
|
+
if (Params->HasField(TEXT("rootMotionBone")))
|
|
236
|
+
{
|
|
237
|
+
FString Value;
|
|
238
|
+
if (!ReadRequiredString(Params, TEXT("rootMotionBone"), Value, ParseError))
|
|
239
|
+
{
|
|
240
|
+
return Error(TEXT("invalid_params"), ParseError);
|
|
241
|
+
}
|
|
242
|
+
RootMotionBone = FName(*Value);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
TArray<FChainRequest> Chains;
|
|
246
|
+
TSet<FName> RequestedChainNames;
|
|
247
|
+
if (Params->HasField(TEXT("chains")))
|
|
248
|
+
{
|
|
249
|
+
const TArray<TSharedPtr<FJsonValue>>* Values = nullptr;
|
|
250
|
+
if (!Params->TryGetArrayField(TEXT("chains"), Values) || !Values || Values->Num() > MaxChains)
|
|
251
|
+
{
|
|
252
|
+
return Error(TEXT("invalid_params"), FString::Printf(TEXT("'chains' must be an array with at most %d entries"), MaxChains));
|
|
253
|
+
}
|
|
254
|
+
for (int32 Index = 0; Index < Values->Num(); ++Index)
|
|
255
|
+
{
|
|
256
|
+
if (!(*Values)[Index].IsValid() || (*Values)[Index]->Type != EJson::Object)
|
|
257
|
+
{
|
|
258
|
+
return Error(TEXT("invalid_params"), FString::Printf(TEXT("chains[%d] must be an object"), Index));
|
|
259
|
+
}
|
|
260
|
+
const TSharedPtr<FJsonObject> Object = (*Values)[Index]->AsObject();
|
|
261
|
+
FString Name;
|
|
262
|
+
FString StartBone;
|
|
263
|
+
FString EndBone;
|
|
264
|
+
if (!ReadRequiredString(Object, TEXT("name"), Name, ParseError) ||
|
|
265
|
+
!ReadRequiredString(Object, TEXT("startBone"), StartBone, ParseError) ||
|
|
266
|
+
!ReadRequiredString(Object, TEXT("endBone"), EndBone, ParseError))
|
|
267
|
+
{
|
|
268
|
+
return Error(TEXT("invalid_params"), FString::Printf(TEXT("chains[%d]: %s"), Index, *ParseError));
|
|
269
|
+
}
|
|
270
|
+
FString Goal;
|
|
271
|
+
if (Object->HasField(TEXT("goal")) && !Object->TryGetStringField(TEXT("goal"), Goal))
|
|
272
|
+
{
|
|
273
|
+
return Error(TEXT("invalid_params"), FString::Printf(TEXT("chains[%d].goal must be a string"), Index));
|
|
274
|
+
}
|
|
275
|
+
Goal.TrimStartAndEndInline();
|
|
276
|
+
FChainRequest Request{FName(*Name), FName(*StartBone), FName(*EndBone), Goal.IsEmpty() ? NAME_None : FName(*Goal)};
|
|
277
|
+
if (Request.Name.IsNone() || RequestedChainNames.Contains(Request.Name))
|
|
278
|
+
{
|
|
279
|
+
return Error(TEXT("invalid_params"), FString::Printf(TEXT("chains[%d].name must be non-empty and unique"), Index));
|
|
280
|
+
}
|
|
281
|
+
RequestedChainNames.Add(Request.Name);
|
|
282
|
+
Chains.Add(Request);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
FFullBodyRequest FullBody;
|
|
287
|
+
if (Params->HasField(TEXT("fullBodyIK")))
|
|
288
|
+
{
|
|
289
|
+
const TSharedPtr<FJsonObject>* ObjectPtr = nullptr;
|
|
290
|
+
if (!Params->TryGetObjectField(TEXT("fullBodyIK"), ObjectPtr) || !ObjectPtr || !ObjectPtr->IsValid())
|
|
291
|
+
{
|
|
292
|
+
return Error(TEXT("invalid_params"), TEXT("'fullBodyIK' must be an object"));
|
|
293
|
+
}
|
|
294
|
+
const TSharedPtr<FJsonObject> Object = *ObjectPtr;
|
|
295
|
+
FullBody.bPresent = true;
|
|
296
|
+
FString RootBone;
|
|
297
|
+
if (!ReadRequiredString(Object, TEXT("rootBone"), RootBone, ParseError))
|
|
298
|
+
{
|
|
299
|
+
return Error(TEXT("invalid_params"), FString::Printf(TEXT("fullBodyIK: %s"), *ParseError));
|
|
300
|
+
}
|
|
301
|
+
FullBody.RootBone = FName(*RootBone);
|
|
302
|
+
if (!ReadOptionalNonNegativeInteger(Object, TEXT("solverIndex"), FullBody.SolverIndex, ParseError) ||
|
|
303
|
+
!ReadIKRigOptionalBool(Object, TEXT("enabled"), FullBody.bEnabled, ParseError))
|
|
304
|
+
{
|
|
305
|
+
return Error(TEXT("invalid_params"), FString::Printf(TEXT("fullBodyIK: %s"), *ParseError));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const TArray<TSharedPtr<FJsonValue>>* GoalValues = nullptr;
|
|
309
|
+
if (!Object->TryGetArrayField(TEXT("goals"), GoalValues) || !GoalValues || GoalValues->Num() > MaxGoals)
|
|
310
|
+
{
|
|
311
|
+
return Error(TEXT("invalid_params"), FString::Printf(TEXT("fullBodyIK.goals must be an array with at most %d entries"), MaxGoals));
|
|
312
|
+
}
|
|
313
|
+
TSet<FName> RequestedGoalNames;
|
|
314
|
+
for (int32 Index = 0; Index < GoalValues->Num(); ++Index)
|
|
315
|
+
{
|
|
316
|
+
if (!(*GoalValues)[Index].IsValid() || (*GoalValues)[Index]->Type != EJson::Object)
|
|
317
|
+
{
|
|
318
|
+
return Error(TEXT("invalid_params"), FString::Printf(TEXT("fullBodyIK.goals[%d] must be an object"), Index));
|
|
319
|
+
}
|
|
320
|
+
const TSharedPtr<FJsonObject> GoalObject = (*GoalValues)[Index]->AsObject();
|
|
321
|
+
FString Name;
|
|
322
|
+
FString Bone;
|
|
323
|
+
if (!ReadRequiredString(GoalObject, TEXT("name"), Name, ParseError) ||
|
|
324
|
+
!ReadRequiredString(GoalObject, TEXT("bone"), Bone, ParseError))
|
|
325
|
+
{
|
|
326
|
+
return Error(TEXT("invalid_params"), FString::Printf(TEXT("fullBodyIK.goals[%d]: %s"), Index, *ParseError));
|
|
327
|
+
}
|
|
328
|
+
if (!GoalNameIsSafe(Name))
|
|
329
|
+
{
|
|
330
|
+
return Error(TEXT("invalid_params"), FString::Printf(TEXT("fullBodyIK.goals[%d].name is not a native-safe IK goal name"), Index));
|
|
331
|
+
}
|
|
332
|
+
FGoalRequest Request;
|
|
333
|
+
Request.Name = FName(*Name);
|
|
334
|
+
Request.Bone = FName(*Bone);
|
|
335
|
+
if (RequestedGoalNames.Contains(Request.Name))
|
|
336
|
+
{
|
|
337
|
+
return Error(TEXT("invalid_params"), FString::Printf(TEXT("Duplicate fullBodyIK goal '%s'"), *Name));
|
|
338
|
+
}
|
|
339
|
+
RequestedGoalNames.Add(Request.Name);
|
|
340
|
+
if (!ReadOptionalUnitFloat(GoalObject, TEXT("positionAlpha"), Request.PositionAlpha, ParseError) ||
|
|
341
|
+
!ReadOptionalUnitFloat(GoalObject, TEXT("rotationAlpha"), Request.RotationAlpha, ParseError) ||
|
|
342
|
+
!ReadOptionalNonNegativeInteger(GoalObject, TEXT("chainDepth"), Request.ChainDepth, ParseError) ||
|
|
343
|
+
!ReadOptionalUnitFloat(GoalObject, TEXT("strengthAlpha"), Request.StrengthAlpha, ParseError) ||
|
|
344
|
+
!ReadOptionalUnitFloat(GoalObject, TEXT("pullChainAlpha"), Request.PullChainAlpha, ParseError) ||
|
|
345
|
+
!ReadOptionalUnitFloat(GoalObject, TEXT("pinRotation"), Request.PinRotation, ParseError))
|
|
346
|
+
{
|
|
347
|
+
return Error(TEXT("invalid_params"), FString::Printf(TEXT("fullBodyIK.goals[%d]: %s"), Index, *ParseError));
|
|
348
|
+
}
|
|
349
|
+
FullBody.Goals.Add(Request);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
TArray<FExclusionRequest> Exclusions;
|
|
354
|
+
TSet<FName> RequestedExclusionBones;
|
|
355
|
+
if (Params->HasField(TEXT("exclusions")))
|
|
356
|
+
{
|
|
357
|
+
const TArray<TSharedPtr<FJsonValue>>* Values = nullptr;
|
|
358
|
+
if (!Params->TryGetArrayField(TEXT("exclusions"), Values) || !Values || Values->Num() > MaxExclusions)
|
|
359
|
+
{
|
|
360
|
+
return Error(TEXT("invalid_params"), FString::Printf(TEXT("'exclusions' must be an array with at most %d entries"), MaxExclusions));
|
|
361
|
+
}
|
|
362
|
+
for (int32 Index = 0; Index < Values->Num(); ++Index)
|
|
363
|
+
{
|
|
364
|
+
if (!(*Values)[Index].IsValid() || (*Values)[Index]->Type != EJson::Object)
|
|
365
|
+
{
|
|
366
|
+
return Error(TEXT("invalid_params"), FString::Printf(TEXT("exclusions[%d] must be an object"), Index));
|
|
367
|
+
}
|
|
368
|
+
const TSharedPtr<FJsonObject> Object = (*Values)[Index]->AsObject();
|
|
369
|
+
FString Bone;
|
|
370
|
+
bool bExcluded = false;
|
|
371
|
+
if (!ReadRequiredString(Object, TEXT("bone"), Bone, ParseError) ||
|
|
372
|
+
!Object->TryGetBoolField(TEXT("excluded"), bExcluded))
|
|
373
|
+
{
|
|
374
|
+
return Error(TEXT("invalid_params"), FString::Printf(TEXT("exclusions[%d] requires string 'bone' and boolean 'excluded'"), Index));
|
|
375
|
+
}
|
|
376
|
+
const FName BoneName(*Bone);
|
|
377
|
+
if (RequestedExclusionBones.Contains(BoneName))
|
|
378
|
+
{
|
|
379
|
+
return Error(TEXT("invalid_params"), FString::Printf(TEXT("Duplicate exclusion bone '%s'"), *Bone));
|
|
380
|
+
}
|
|
381
|
+
RequestedExclusionBones.Add(BoneName);
|
|
382
|
+
Exclusions.Add({BoneName, bExcluded});
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (!bAutoRetarget && !RetargetRoot.IsSet() && !RootMotionBone.IsSet() &&
|
|
387
|
+
!Params->HasField(TEXT("chains")) && !FullBody.bPresent && !Params->HasField(TEXT("exclusions")))
|
|
388
|
+
{
|
|
389
|
+
return Error(TEXT("invalid_params"), TEXT("No IK Rig configuration was requested"));
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
UIKRigDefinition* Rig = LoadAssetByPath<UIKRigDefinition>(RigPath);
|
|
393
|
+
if (!Rig)
|
|
394
|
+
{
|
|
395
|
+
return Error(TEXT("asset_not_found"), FString::Printf(TEXT("IK Rig not found: %s"), *RigPath));
|
|
396
|
+
}
|
|
397
|
+
UIKRigController* Controller = UIKRigController::GetController(Rig);
|
|
398
|
+
if (!Controller)
|
|
399
|
+
{
|
|
400
|
+
return Error(TEXT("controller_unavailable"), FString::Printf(TEXT("Could not acquire the native IK Rig controller for %s"), *RigPath));
|
|
401
|
+
}
|
|
402
|
+
USkeletalMesh* Mesh = Controller->GetSkeletalMesh();
|
|
403
|
+
const FIKRigSkeleton& Skeleton = Controller->GetIKRigSkeleton();
|
|
404
|
+
if (!Mesh || Skeleton.BoneNames.IsEmpty() || !Controller->IsSkeletalMeshCompatible(Mesh))
|
|
405
|
+
{
|
|
406
|
+
return Error(TEXT("incompatible_rig"), TEXT("The IK Rig must have a non-empty compatible skeletal mesh before it can be configured"));
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
auto RequireBone = [&Skeleton](const FName Bone, const FString& Context, FString& OutError) -> bool
|
|
410
|
+
{
|
|
411
|
+
if (Bone.IsNone() || Skeleton.GetBoneIndexFromName(Bone) == INDEX_NONE)
|
|
412
|
+
{
|
|
413
|
+
OutError = FString::Printf(TEXT("%s references unknown bone '%s'"), *Context, *Bone.ToString());
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
return true;
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
if ((RetargetRoot.IsSet() && !RequireBone(RetargetRoot.GetValue(), TEXT("retargetRoot"), ParseError)) ||
|
|
420
|
+
(RootMotionBone.IsSet() && !RequireBone(RootMotionBone.GetValue(), TEXT("rootMotionBone"), ParseError)) ||
|
|
421
|
+
(FullBody.bPresent && !RequireBone(FullBody.RootBone, TEXT("fullBodyIK.rootBone"), ParseError)))
|
|
422
|
+
{
|
|
423
|
+
return Error(TEXT("invalid_bone"), ParseError);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
TMap<FName, FName> AvailableGoals;
|
|
427
|
+
for (const UIKRigEffectorGoal* Goal : Controller->GetAllGoals())
|
|
428
|
+
{
|
|
429
|
+
if (Goal)
|
|
430
|
+
{
|
|
431
|
+
AvailableGoals.Add(Goal->GoalName, Goal->BoneName);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
FAutoCharacterizeResults AutoResults;
|
|
436
|
+
if (bAutoRetarget)
|
|
437
|
+
{
|
|
438
|
+
Controller->AutoGenerateRetargetDefinition(AutoResults);
|
|
439
|
+
if (!AutoResults.bUsedTemplate)
|
|
440
|
+
{
|
|
441
|
+
return Error(TEXT("auto_setup_unavailable"), TEXT("Unreal could not match this skeleton to a native IK Rig template"));
|
|
442
|
+
}
|
|
443
|
+
const FRetargetDefinition& Definition = AutoResults.AutoRetargetDefinition.RetargetDefinition;
|
|
444
|
+
if (!RequireBone(Definition.PelvisBone, TEXT("autoSetup retarget root"), ParseError))
|
|
445
|
+
{
|
|
446
|
+
return Error(TEXT("invalid_bone"), ParseError);
|
|
447
|
+
}
|
|
448
|
+
for (const FBoneChain& Chain : Definition.BoneChains)
|
|
449
|
+
{
|
|
450
|
+
if (Chain.ChainName.IsNone() ||
|
|
451
|
+
!RequireBone(Chain.StartBone.BoneName, FString::Printf(TEXT("auto chain '%s' start"), *Chain.ChainName.ToString()), ParseError) ||
|
|
452
|
+
!RequireBone(Chain.EndBone.BoneName, FString::Printf(TEXT("auto chain '%s' end"), *Chain.ChainName.ToString()), ParseError) ||
|
|
453
|
+
!Skeleton.IsBoneInDirectLineage(Chain.EndBone.BoneName, Chain.StartBone.BoneName))
|
|
454
|
+
{
|
|
455
|
+
return Error(TEXT("invalid_chain"), ParseError.IsEmpty()
|
|
456
|
+
? FString::Printf(TEXT("Auto chain '%s' has invalid ancestry"), *Chain.ChainName.ToString())
|
|
457
|
+
: ParseError);
|
|
458
|
+
}
|
|
459
|
+
if (bAutoFullBody && !Chain.IKGoalName.IsNone())
|
|
460
|
+
{
|
|
461
|
+
FString GoalName = Chain.IKGoalName.ToString();
|
|
462
|
+
if (!GoalNameIsSafe(GoalName))
|
|
463
|
+
{
|
|
464
|
+
return Error(TEXT("invalid_goal"), FString::Printf(TEXT("Auto goal '%s' is not a native-safe goal name"), *GoalName));
|
|
465
|
+
}
|
|
466
|
+
AvailableGoals.Add(Chain.IKGoalName, Chain.EndBone.BoneName);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (bAutoFullBody &&
|
|
472
|
+
(Controller->GetNumSolvers() != 0 || !Controller->GetAllGoals().IsEmpty() ||
|
|
473
|
+
!Controller->GetRetargetChains().IsEmpty() || !Skeleton.ExcludedBones.IsEmpty() ||
|
|
474
|
+
!Controller->GetRetargetRoot().IsNone() || !Controller->GetRootMotionBone().IsNone()))
|
|
475
|
+
{
|
|
476
|
+
return Error(TEXT("non_empty_rig"), TEXT("autoSetup 'full_body' is only safe on an empty IK Rig definition"));
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
for (const FGoalRequest& Goal : FullBody.Goals)
|
|
480
|
+
{
|
|
481
|
+
if (!RequireBone(Goal.Bone, FString::Printf(TEXT("goal '%s'"), *Goal.Name.ToString()), ParseError))
|
|
482
|
+
{
|
|
483
|
+
return Error(TEXT("invalid_bone"), ParseError);
|
|
484
|
+
}
|
|
485
|
+
if (Goal.ChainDepth.IsSet() && Goal.ChainDepth.GetValue() > Skeleton.BoneNames.Num())
|
|
486
|
+
{
|
|
487
|
+
return Error(TEXT("invalid_range"), FString::Printf(TEXT("Goal '%s' chainDepth exceeds the skeleton bone count"), *Goal.Name.ToString()));
|
|
488
|
+
}
|
|
489
|
+
AvailableGoals.Add(Goal.Name, Goal.Bone);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
for (const FChainRequest& Chain : Chains)
|
|
493
|
+
{
|
|
494
|
+
if (!RequireBone(Chain.StartBone, FString::Printf(TEXT("chain '%s' start"), *Chain.Name.ToString()), ParseError) ||
|
|
495
|
+
!RequireBone(Chain.EndBone, FString::Printf(TEXT("chain '%s' end"), *Chain.Name.ToString()), ParseError))
|
|
496
|
+
{
|
|
497
|
+
return Error(TEXT("invalid_bone"), ParseError);
|
|
498
|
+
}
|
|
499
|
+
if (!Skeleton.IsBoneInDirectLineage(Chain.EndBone, Chain.StartBone))
|
|
500
|
+
{
|
|
501
|
+
return Error(TEXT("invalid_chain"), FString::Printf(TEXT("Chain '%s' end bone must descend from its start bone"), *Chain.Name.ToString()));
|
|
502
|
+
}
|
|
503
|
+
if (!Chain.Goal.IsNone() && !AvailableGoals.Contains(Chain.Goal))
|
|
504
|
+
{
|
|
505
|
+
return Error(TEXT("dangling_goal"), FString::Printf(TEXT("Chain '%s' references missing goal '%s'"), *Chain.Name.ToString(), *Chain.Goal.ToString()));
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
TSet<FName> RequiredFBIKBones;
|
|
510
|
+
for (int32 Index = 0; Index < Controller->GetNumSolvers(); ++Index)
|
|
511
|
+
{
|
|
512
|
+
FIKRigFullBodyIKSolver* ExistingSolver = GetFullBodySolver(Controller, Index);
|
|
513
|
+
if (!ExistingSolver) continue;
|
|
514
|
+
const FName ExistingRoot = Controller->GetStartBone(Index);
|
|
515
|
+
if (!ExistingRoot.IsNone()) RequiredFBIKBones.Add(ExistingRoot);
|
|
516
|
+
TSet<FName> ConnectedGoals;
|
|
517
|
+
ExistingSolver->GetRequiredGoals(ConnectedGoals);
|
|
518
|
+
for (const FName GoalName : ConnectedGoals)
|
|
519
|
+
{
|
|
520
|
+
if (const FName* GoalBone = AvailableGoals.Find(GoalName)) RequiredFBIKBones.Add(*GoalBone);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
if (FullBody.bPresent)
|
|
524
|
+
{
|
|
525
|
+
RequiredFBIKBones.Add(FullBody.RootBone);
|
|
526
|
+
for (const FGoalRequest& Goal : FullBody.Goals) RequiredFBIKBones.Add(Goal.Bone);
|
|
527
|
+
}
|
|
528
|
+
if (bAutoFullBody)
|
|
529
|
+
{
|
|
530
|
+
RequiredFBIKBones.Add(AutoResults.AutoRetargetDefinition.RetargetDefinition.PelvisBone);
|
|
531
|
+
for (const FBoneChain& Chain : AutoResults.AutoRetargetDefinition.RetargetDefinition.BoneChains)
|
|
532
|
+
{
|
|
533
|
+
if (const FName* GoalBone = AvailableGoals.Find(Chain.IKGoalName)) RequiredFBIKBones.Add(*GoalBone);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
for (const FExclusionRequest& Exclusion : Exclusions)
|
|
538
|
+
{
|
|
539
|
+
if (!RequireBone(Exclusion.Bone, TEXT("exclusions"), ParseError))
|
|
540
|
+
{
|
|
541
|
+
return Error(TEXT("invalid_bone"), ParseError);
|
|
542
|
+
}
|
|
543
|
+
if (Exclusion.bExcluded && RequiredFBIKBones.Contains(Exclusion.Bone))
|
|
544
|
+
{
|
|
545
|
+
return Error(TEXT("invalid_exclusion"), FString::Printf(TEXT("Bone '%s' cannot be excluded while an FBIK solver requires it as a root or goal bone"), *Exclusion.Bone.ToString()));
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
int32 SolverIndex = INDEX_NONE;
|
|
550
|
+
bool bCreateSolver = false;
|
|
551
|
+
if (FullBody.bPresent || bAutoFullBody)
|
|
552
|
+
{
|
|
553
|
+
if (bAutoFullBody)
|
|
554
|
+
{
|
|
555
|
+
SolverIndex = 0;
|
|
556
|
+
if (FullBody.SolverIndex.IsSet() && FullBody.SolverIndex.GetValue() != 0)
|
|
557
|
+
{
|
|
558
|
+
return Error(TEXT("invalid_solver"), TEXT("autoSetup 'full_body' creates its FBIK solver at index 0"));
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
else if (FullBody.SolverIndex.IsSet())
|
|
562
|
+
{
|
|
563
|
+
SolverIndex = FullBody.SolverIndex.GetValue();
|
|
564
|
+
if (SolverIndex >= Controller->GetNumSolvers() || !IsFullBodySolver(Controller, SolverIndex))
|
|
565
|
+
{
|
|
566
|
+
return Error(TEXT("invalid_solver"), FString::Printf(TEXT("Solver index %d is not an existing Full Body IK solver"), SolverIndex));
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
else
|
|
570
|
+
{
|
|
571
|
+
for (int32 Index = 0; Index < Controller->GetNumSolvers(); ++Index)
|
|
572
|
+
{
|
|
573
|
+
if (IsFullBodySolver(Controller, Index))
|
|
574
|
+
{
|
|
575
|
+
SolverIndex = Index;
|
|
576
|
+
break;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
if (SolverIndex == INDEX_NONE)
|
|
580
|
+
{
|
|
581
|
+
SolverIndex = Controller->GetNumSolvers();
|
|
582
|
+
bCreateSolver = true;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const FName SolverRoot = FullBody.bPresent
|
|
587
|
+
? FullBody.RootBone
|
|
588
|
+
: AutoResults.AutoRetargetDefinition.RetargetDefinition.PelvisBone;
|
|
589
|
+
TSet<FName> ConnectedGoals;
|
|
590
|
+
if (!bCreateSolver && !bAutoFullBody)
|
|
591
|
+
{
|
|
592
|
+
if (FIKRigFullBodyIKSolver* ExistingSolver = GetFullBodySolver(Controller, SolverIndex))
|
|
593
|
+
{
|
|
594
|
+
ExistingSolver->GetRequiredGoals(ConnectedGoals);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
if (bAutoFullBody)
|
|
598
|
+
{
|
|
599
|
+
for (const FBoneChain& Chain : AutoResults.AutoRetargetDefinition.RetargetDefinition.BoneChains)
|
|
600
|
+
{
|
|
601
|
+
if (!Chain.IKGoalName.IsNone()) ConnectedGoals.Add(Chain.IKGoalName);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
for (const FGoalRequest& Goal : FullBody.Goals) ConnectedGoals.Add(Goal.Name);
|
|
605
|
+
for (const FName GoalName : ConnectedGoals)
|
|
606
|
+
{
|
|
607
|
+
const FName* GoalBone = AvailableGoals.Find(GoalName);
|
|
608
|
+
if (!GoalBone)
|
|
609
|
+
{
|
|
610
|
+
return Error(TEXT("dangling_goal"), FString::Printf(TEXT("FBIK solver references missing goal '%s'"), *GoalName.ToString()));
|
|
611
|
+
}
|
|
612
|
+
if (!Skeleton.IsBoneInDirectLineage(*GoalBone, SolverRoot))
|
|
613
|
+
{
|
|
614
|
+
return Error(TEXT("invalid_ancestry"), FString::Printf(TEXT("Goal bone '%s' must descend from FBIK root '%s'"), *GoalBone->ToString(), *SolverRoot.ToString()));
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
const int32 BeforeChainCount = Controller->GetRetargetChains().Num();
|
|
620
|
+
const int32 BeforeGoalCount = Controller->GetAllGoals().Num();
|
|
621
|
+
const int32 BeforeSolverCount = Controller->GetNumSolvers();
|
|
622
|
+
const TSet<FName> BeforeExcluded(Skeleton.ExcludedBones);
|
|
623
|
+
UPackage* Package = Rig->GetOutermost();
|
|
624
|
+
const bool bWasDirty = Package && Package->IsDirty();
|
|
625
|
+
bool bFailed = false;
|
|
626
|
+
FString ApplyError;
|
|
627
|
+
TArray<FString> Warnings;
|
|
628
|
+
|
|
629
|
+
{
|
|
630
|
+
FScopedTransaction Transaction(NSLOCTEXT("UE_MCP", "ConfigureIKRig", "Configure IK Rig"));
|
|
631
|
+
FScopedReinitializeIKRig Reinitialize(Controller, true);
|
|
632
|
+
Rig->Modify();
|
|
633
|
+
|
|
634
|
+
if (bAutoRetarget && !Controller->ApplyAutoGeneratedRetargetDefinition())
|
|
635
|
+
{
|
|
636
|
+
bFailed = true;
|
|
637
|
+
ApplyError = TEXT("Native auto retarget setup failed");
|
|
638
|
+
}
|
|
639
|
+
if (!bFailed && bAutoFullBody && !Controller->ApplyAutoFBIK())
|
|
640
|
+
{
|
|
641
|
+
bFailed = true;
|
|
642
|
+
ApplyError = TEXT("Native auto Full Body IK setup failed");
|
|
643
|
+
}
|
|
644
|
+
if (!bFailed && RetargetRoot.IsSet() && Controller->GetRetargetRoot() != RetargetRoot.GetValue() &&
|
|
645
|
+
!Controller->SetRetargetRoot(RetargetRoot.GetValue()))
|
|
646
|
+
{
|
|
647
|
+
bFailed = true;
|
|
648
|
+
ApplyError = TEXT("Failed to set retargetRoot");
|
|
649
|
+
}
|
|
650
|
+
if (!bFailed && RootMotionBone.IsSet() && Controller->GetRootMotionBone() != RootMotionBone.GetValue() &&
|
|
651
|
+
!Controller->SetRootMotionBone(RootMotionBone.GetValue()))
|
|
652
|
+
{
|
|
653
|
+
bFailed = true;
|
|
654
|
+
ApplyError = TEXT("Failed to set rootMotionBone");
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
for (const FGoalRequest& Request : FullBody.Goals)
|
|
658
|
+
{
|
|
659
|
+
if (bFailed) break;
|
|
660
|
+
UIKRigEffectorGoal* Goal = Controller->GetGoal(Request.Name);
|
|
661
|
+
if (!Goal)
|
|
662
|
+
{
|
|
663
|
+
if (Controller->AddNewGoal(Request.Name, Request.Bone) != Request.Name)
|
|
664
|
+
{
|
|
665
|
+
bFailed = true;
|
|
666
|
+
ApplyError = FString::Printf(TEXT("Failed to create goal '%s'"), *Request.Name.ToString());
|
|
667
|
+
break;
|
|
668
|
+
}
|
|
669
|
+
Goal = Controller->GetGoal(Request.Name);
|
|
670
|
+
}
|
|
671
|
+
else if (Goal->BoneName != Request.Bone && !Controller->SetGoalBone(Request.Name, Request.Bone))
|
|
672
|
+
{
|
|
673
|
+
bFailed = true;
|
|
674
|
+
ApplyError = FString::Printf(TEXT("Failed to move goal '%s'"), *Request.Name.ToString());
|
|
675
|
+
break;
|
|
676
|
+
}
|
|
677
|
+
Goal = Controller->GetGoal(Request.Name);
|
|
678
|
+
const bool bCoreSettingsChanged = Goal &&
|
|
679
|
+
((Request.PositionAlpha.IsSet() && !FMath::IsNearlyEqual(Goal->PositionAlpha, Request.PositionAlpha.GetValue())) ||
|
|
680
|
+
(Request.RotationAlpha.IsSet() && !FMath::IsNearlyEqual(Goal->RotationAlpha, Request.RotationAlpha.GetValue())));
|
|
681
|
+
if (!Goal || (bCoreSettingsChanged && !Controller->ModifyGoal(Request.Name)))
|
|
682
|
+
{
|
|
683
|
+
bFailed = true;
|
|
684
|
+
ApplyError = FString::Printf(TEXT("Failed to modify goal '%s'"), *Request.Name.ToString());
|
|
685
|
+
break;
|
|
686
|
+
}
|
|
687
|
+
if (Request.PositionAlpha.IsSet()) Goal->PositionAlpha = Request.PositionAlpha.GetValue();
|
|
688
|
+
if (Request.RotationAlpha.IsSet()) Goal->RotationAlpha = Request.RotationAlpha.GetValue();
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
for (const FChainRequest& Request : Chains)
|
|
692
|
+
{
|
|
693
|
+
if (bFailed) break;
|
|
694
|
+
const FBoneChain* Existing = Controller->GetRetargetChainByName(Request.Name);
|
|
695
|
+
if (!Existing)
|
|
696
|
+
{
|
|
697
|
+
if (Controller->AddRetargetChain(Request.Name, Request.StartBone, Request.EndBone, Request.Goal) != Request.Name)
|
|
698
|
+
{
|
|
699
|
+
bFailed = true;
|
|
700
|
+
ApplyError = FString::Printf(TEXT("Failed to create chain '%s'"), *Request.Name.ToString());
|
|
701
|
+
}
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
if (Existing->StartBone.BoneName != Request.StartBone && !Controller->SetRetargetChainStartBone(Request.Name, Request.StartBone))
|
|
705
|
+
{
|
|
706
|
+
bFailed = true;
|
|
707
|
+
ApplyError = FString::Printf(TEXT("Failed to set chain '%s' start bone"), *Request.Name.ToString());
|
|
708
|
+
break;
|
|
709
|
+
}
|
|
710
|
+
Existing = Controller->GetRetargetChainByName(Request.Name);
|
|
711
|
+
if (Existing->EndBone.BoneName != Request.EndBone && !Controller->SetRetargetChainEndBone(Request.Name, Request.EndBone))
|
|
712
|
+
{
|
|
713
|
+
bFailed = true;
|
|
714
|
+
ApplyError = FString::Printf(TEXT("Failed to set chain '%s' end bone"), *Request.Name.ToString());
|
|
715
|
+
break;
|
|
716
|
+
}
|
|
717
|
+
Existing = Controller->GetRetargetChainByName(Request.Name);
|
|
718
|
+
if (Existing->IKGoalName != Request.Goal && !Controller->SetRetargetChainGoal(Request.Name, Request.Goal))
|
|
719
|
+
{
|
|
720
|
+
bFailed = true;
|
|
721
|
+
ApplyError = FString::Printf(TEXT("Failed to set chain '%s' goal"), *Request.Name.ToString());
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
if (!bFailed && (FullBody.bPresent || bAutoFullBody))
|
|
726
|
+
{
|
|
727
|
+
if (bCreateSolver)
|
|
728
|
+
{
|
|
729
|
+
const int32 AddedIndex = Controller->AddSolver(FIKRigFullBodyIKSolver::StaticStruct());
|
|
730
|
+
if (AddedIndex != SolverIndex)
|
|
731
|
+
{
|
|
732
|
+
bFailed = true;
|
|
733
|
+
ApplyError = TEXT("Failed to create the Full Body IK solver at the predicted index");
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
FIKRigFullBodyIKSolver* Solver = bFailed ? nullptr : GetFullBodySolver(Controller, SolverIndex);
|
|
737
|
+
if (!Solver)
|
|
738
|
+
{
|
|
739
|
+
bFailed = true;
|
|
740
|
+
ApplyError = TEXT("Full Body IK solver readback failed");
|
|
741
|
+
}
|
|
742
|
+
if (!bFailed && FullBody.bPresent)
|
|
743
|
+
{
|
|
744
|
+
if (Controller->GetStartBone(SolverIndex) != FullBody.RootBone && !Controller->SetStartBone(FullBody.RootBone, SolverIndex))
|
|
745
|
+
{
|
|
746
|
+
bFailed = true;
|
|
747
|
+
ApplyError = TEXT("Failed to set the Full Body IK root bone");
|
|
748
|
+
}
|
|
749
|
+
if (!bFailed && FullBody.bEnabled.IsSet() && Controller->GetSolverEnabled(SolverIndex) != FullBody.bEnabled.GetValue() &&
|
|
750
|
+
!Controller->SetSolverEnabled(SolverIndex, FullBody.bEnabled.GetValue()))
|
|
751
|
+
{
|
|
752
|
+
bFailed = true;
|
|
753
|
+
ApplyError = TEXT("Failed to set the Full Body IK enabled state");
|
|
754
|
+
}
|
|
755
|
+
for (const FGoalRequest& Request : FullBody.Goals)
|
|
756
|
+
{
|
|
757
|
+
if (bFailed) break;
|
|
758
|
+
if (!Controller->IsGoalConnectedToSolver(Request.Name, SolverIndex) &&
|
|
759
|
+
!Controller->ConnectGoalToSolver(Request.Name, SolverIndex))
|
|
760
|
+
{
|
|
761
|
+
bFailed = true;
|
|
762
|
+
ApplyError = FString::Printf(TEXT("Failed to connect goal '%s' to the Full Body IK solver"), *Request.Name.ToString());
|
|
763
|
+
break;
|
|
764
|
+
}
|
|
765
|
+
FIKRigFBIKGoalSettings* Settings = static_cast<FIKRigFBIKGoalSettings*>(Solver->GetGoalSettings(Request.Name));
|
|
766
|
+
if (!Settings)
|
|
767
|
+
{
|
|
768
|
+
bFailed = true;
|
|
769
|
+
ApplyError = FString::Printf(TEXT("Full Body IK settings are missing for goal '%s'"), *Request.Name.ToString());
|
|
770
|
+
break;
|
|
771
|
+
}
|
|
772
|
+
if (Request.ChainDepth.IsSet()) Settings->ChainDepth = Request.ChainDepth.GetValue();
|
|
773
|
+
if (Request.StrengthAlpha.IsSet()) Settings->StrengthAlpha = Request.StrengthAlpha.GetValue();
|
|
774
|
+
if (Request.PullChainAlpha.IsSet()) Settings->PullChainAlpha = Request.PullChainAlpha.GetValue();
|
|
775
|
+
if (Request.PinRotation.IsSet()) Settings->PinRotation = Request.PinRotation.GetValue();
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
for (const FExclusionRequest& Request : Exclusions)
|
|
781
|
+
{
|
|
782
|
+
if (bFailed) break;
|
|
783
|
+
if (Controller->GetBoneExcluded(Request.Bone) != Request.bExcluded &&
|
|
784
|
+
!Controller->SetBoneExcluded(Request.Bone, Request.bExcluded))
|
|
785
|
+
{
|
|
786
|
+
bFailed = true;
|
|
787
|
+
ApplyError = FString::Printf(TEXT("Failed to set exclusion for bone '%s'"), *Request.Bone.ToString());
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
if (!bFailed && RetargetRoot.IsSet() && Controller->GetRetargetRoot() != RetargetRoot.GetValue())
|
|
792
|
+
{
|
|
793
|
+
bFailed = true;
|
|
794
|
+
ApplyError = TEXT("retargetRoot readback did not match");
|
|
795
|
+
}
|
|
796
|
+
if (!bFailed && RootMotionBone.IsSet() && Controller->GetRootMotionBone() != RootMotionBone.GetValue())
|
|
797
|
+
{
|
|
798
|
+
bFailed = true;
|
|
799
|
+
ApplyError = TEXT("rootMotionBone readback did not match");
|
|
800
|
+
}
|
|
801
|
+
for (const FChainRequest& Request : Chains)
|
|
802
|
+
{
|
|
803
|
+
if (bFailed) break;
|
|
804
|
+
const FBoneChain* Chain = Controller->GetRetargetChainByName(Request.Name);
|
|
805
|
+
TSet<int32> ChainIndices;
|
|
806
|
+
if (!Chain || Chain->StartBone.BoneName != Request.StartBone || Chain->EndBone.BoneName != Request.EndBone ||
|
|
807
|
+
Chain->IKGoalName != Request.Goal || !Controller->ValidateChain(Request.Name, &Skeleton, ChainIndices))
|
|
808
|
+
{
|
|
809
|
+
bFailed = true;
|
|
810
|
+
ApplyError = FString::Printf(TEXT("Chain '%s' failed native readback validation"), *Request.Name.ToString());
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
for (const FGoalRequest& Request : FullBody.Goals)
|
|
814
|
+
{
|
|
815
|
+
if (bFailed) break;
|
|
816
|
+
const UIKRigEffectorGoal* Goal = Controller->GetGoal(Request.Name);
|
|
817
|
+
FIKRigFullBodyIKSolver* Solver = GetFullBodySolver(Controller, SolverIndex);
|
|
818
|
+
const FIKRigFBIKGoalSettings* Settings = Solver
|
|
819
|
+
? static_cast<const FIKRigFBIKGoalSettings*>(Solver->GetGoalSettings(Request.Name))
|
|
820
|
+
: nullptr;
|
|
821
|
+
if (!Goal || Goal->BoneName != Request.Bone || !Controller->IsGoalConnectedToSolver(Request.Name, SolverIndex) || !Settings ||
|
|
822
|
+
(Request.PositionAlpha.IsSet() && !FMath::IsNearlyEqual(Goal->PositionAlpha, Request.PositionAlpha.GetValue())) ||
|
|
823
|
+
(Request.RotationAlpha.IsSet() && !FMath::IsNearlyEqual(Goal->RotationAlpha, Request.RotationAlpha.GetValue())) ||
|
|
824
|
+
(Request.ChainDepth.IsSet() && Settings->ChainDepth != Request.ChainDepth.GetValue()) ||
|
|
825
|
+
(Request.StrengthAlpha.IsSet() && !FMath::IsNearlyEqual(Settings->StrengthAlpha, Request.StrengthAlpha.GetValue())) ||
|
|
826
|
+
(Request.PullChainAlpha.IsSet() && !FMath::IsNearlyEqual(Settings->PullChainAlpha, Request.PullChainAlpha.GetValue())) ||
|
|
827
|
+
(Request.PinRotation.IsSet() && !FMath::IsNearlyEqual(Settings->PinRotation, Request.PinRotation.GetValue())))
|
|
828
|
+
{
|
|
829
|
+
bFailed = true;
|
|
830
|
+
ApplyError = FString::Printf(TEXT("Goal '%s' failed native readback validation"), *Request.Name.ToString());
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
for (const FExclusionRequest& Request : Exclusions)
|
|
834
|
+
{
|
|
835
|
+
if (!bFailed && Controller->GetBoneExcluded(Request.Bone) != Request.bExcluded)
|
|
836
|
+
{
|
|
837
|
+
bFailed = true;
|
|
838
|
+
ApplyError = FString::Printf(TEXT("Exclusion for bone '%s' failed readback"), *Request.Bone.ToString());
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
auto Undo = [&]() -> bool
|
|
844
|
+
{
|
|
845
|
+
const bool bUndone = GEditor && GEditor->UndoTransaction();
|
|
846
|
+
if (bUndone && Package)
|
|
847
|
+
{
|
|
848
|
+
Package->SetDirtyFlag(bWasDirty);
|
|
849
|
+
FScopedReinitializeIKRig Refresh(Controller, true);
|
|
850
|
+
}
|
|
851
|
+
return bUndone;
|
|
852
|
+
};
|
|
853
|
+
|
|
854
|
+
if (bFailed)
|
|
855
|
+
{
|
|
856
|
+
const bool bUndone = Undo();
|
|
857
|
+
return Error(TEXT("mutation_failed"), ApplyError, bUndone);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
Rig->MarkPackageDirty();
|
|
861
|
+
if (!UEditorAssetLibrary::SaveLoadedAsset(Rig, false))
|
|
862
|
+
{
|
|
863
|
+
const bool bUndone = Undo();
|
|
864
|
+
return Error(TEXT("save_failed"), TEXT("IK Rig changes could not be saved"), bUndone);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
const FIKRigSkeleton& FinalSkeleton = Controller->GetIKRigSkeleton();
|
|
868
|
+
const TSet<FName> FinalExcluded(FinalSkeleton.ExcludedBones);
|
|
869
|
+
int32 ExclusionsChanged = 0;
|
|
870
|
+
for (const FName Bone : BeforeExcluded) if (!FinalExcluded.Contains(Bone)) ++ExclusionsChanged;
|
|
871
|
+
for (const FName Bone : FinalExcluded) if (!BeforeExcluded.Contains(Bone)) ++ExclusionsChanged;
|
|
872
|
+
|
|
873
|
+
TSharedPtr<FJsonObject> Result = MCPSuccess();
|
|
874
|
+
MCPSetUpdated(Result);
|
|
875
|
+
Result->SetStringField(TEXT("rigPath"), Rig->GetPathName());
|
|
876
|
+
Result->SetBoolField(TEXT("validated"), true);
|
|
877
|
+
Result->SetBoolField(TEXT("rollbackSafe"), true);
|
|
878
|
+
Result->SetBoolField(TEXT("autoSetupApplied"), bAutoRetarget);
|
|
879
|
+
if (!AutoSetup.IsEmpty()) Result->SetStringField(TEXT("autoSetup"), AutoSetup);
|
|
880
|
+
Result->SetStringField(TEXT("retargetRoot"), Controller->GetRetargetRoot().ToString());
|
|
881
|
+
Result->SetStringField(TEXT("rootMotionBone"), Controller->GetRootMotionBone().ToString());
|
|
882
|
+
Result->SetNumberField(TEXT("chainsCreated"), FMath::Max(0, Controller->GetRetargetChains().Num() - BeforeChainCount));
|
|
883
|
+
Result->SetNumberField(TEXT("chainsUpserted"), Chains.Num());
|
|
884
|
+
Result->SetNumberField(TEXT("goalsCreated"), FMath::Max(0, Controller->GetAllGoals().Num() - BeforeGoalCount));
|
|
885
|
+
Result->SetNumberField(TEXT("goalsUpserted"), FullBody.Goals.Num());
|
|
886
|
+
Result->SetNumberField(TEXT("exclusionsChanged"), ExclusionsChanged);
|
|
887
|
+
Result->SetNumberField(TEXT("chainCount"), Controller->GetRetargetChains().Num());
|
|
888
|
+
Result->SetNumberField(TEXT("goalCount"), Controller->GetAllGoals().Num());
|
|
889
|
+
Result->SetNumberField(TEXT("solverCount"), Controller->GetNumSolvers());
|
|
890
|
+
if (SolverIndex != INDEX_NONE)
|
|
891
|
+
{
|
|
892
|
+
Result->SetNumberField(TEXT("fullBodyIKSolverIndex"), SolverIndex);
|
|
893
|
+
Result->SetBoolField(TEXT("fullBodyIKSolverCreated"), Controller->GetNumSolvers() > BeforeSolverCount);
|
|
894
|
+
}
|
|
895
|
+
TArray<TSharedPtr<FJsonValue>> WarningValues;
|
|
896
|
+
for (const FString& Warning : Warnings) WarningValues.Add(MakeShared<FJsonValueString>(Warning));
|
|
897
|
+
Result->SetArrayField(TEXT("warnings"), WarningValues);
|
|
898
|
+
return MCPResult(Result);
|
|
899
|
+
#endif
|
|
900
|
+
}
|