|
|
|
|
@@ -59,7 +59,7 @@ void FBlueprintMCPServer::HandleCreateAnimBlueprint(const FJsonObject* Json, FJs
|
|
|
|
|
|
|
|
|
|
// Check if asset already exists
|
|
|
|
|
FString FullAssetPath = PackagePath / Name;
|
|
|
|
|
if (UMCPAssetFinder::FindAsset(UBlueprint::StaticClass(), Name) || UMCPAssetFinder::FindAsset(UBlueprint::StaticClass(), FullAssetPath))
|
|
|
|
|
if (UMCPAssetFinder::FindAsset(UBlueprint::StaticClass(), Name))
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(
|
|
|
|
|
TEXT("Blueprint '%s' already exists. Use a different name or delete the existing asset first."),
|
|
|
|
|
@@ -67,59 +67,8 @@ void FBlueprintMCPServer::HandleCreateAnimBlueprint(const FJsonObject* Json, FJs
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Resolve skeleton
|
|
|
|
|
USkeleton* Skeleton = nullptr;
|
|
|
|
|
|
|
|
|
|
if (SkeletonName == TEXT("__create_test_skeleton__"))
|
|
|
|
|
{
|
|
|
|
|
// Create a minimal in-memory skeleton for tests
|
|
|
|
|
FString TestSkeletonPath = PackagePath / (Name + TEXT("_TestSkeleton"));
|
|
|
|
|
UPackage* SkelPackage = CreatePackage(*TestSkeletonPath);
|
|
|
|
|
Skeleton = NewObject<USkeleton>(SkelPackage, FName(*(Name + TEXT("_TestSkeleton"))), RF_Public | RF_Standalone);
|
|
|
|
|
if (Skeleton)
|
|
|
|
|
{
|
|
|
|
|
Skeleton->MarkPackageDirty();
|
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created test skeleton '%s'"), *Skeleton->GetName());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Search asset registry for the skeleton
|
|
|
|
|
FAssetRegistryModule& ARM = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
|
|
|
|
|
TArray<FAssetData> SkeletonAssets;
|
|
|
|
|
ARM.Get().GetAssetsByClass(USkeleton::StaticClass()->GetClassPathName(), SkeletonAssets, false);
|
|
|
|
|
|
|
|
|
|
for (const FAssetData& Asset : SkeletonAssets)
|
|
|
|
|
{
|
|
|
|
|
if (Asset.AssetName.ToString() == SkeletonName ||
|
|
|
|
|
Asset.PackageName.ToString() == SkeletonName ||
|
|
|
|
|
Asset.GetObjectPathString() == SkeletonName)
|
|
|
|
|
{
|
|
|
|
|
Skeleton = Cast<USkeleton>(Asset.GetAsset());
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Case-insensitive fallback
|
|
|
|
|
if (!Skeleton)
|
|
|
|
|
{
|
|
|
|
|
for (const FAssetData& Asset : SkeletonAssets)
|
|
|
|
|
{
|
|
|
|
|
if (Asset.AssetName.ToString().Equals(SkeletonName, ESearchCase::IgnoreCase) ||
|
|
|
|
|
Asset.PackageName.ToString().Equals(SkeletonName, ESearchCase::IgnoreCase))
|
|
|
|
|
{
|
|
|
|
|
Skeleton = Cast<USkeleton>(Asset.GetAsset());
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Skeleton)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(
|
|
|
|
|
TEXT("Skeleton '%s' not found. Provide the skeleton asset name or path. Use '__create_test_skeleton__' for testing."),
|
|
|
|
|
*SkeletonName));
|
|
|
|
|
}
|
|
|
|
|
USkeleton* Skeleton = UMCPAssetFinder::LoadAsset<USkeleton>(SkeletonName, Result);
|
|
|
|
|
if (!Skeleton) return;
|
|
|
|
|
|
|
|
|
|
// Resolve parent class (default: UAnimInstance)
|
|
|
|
|
UClass* ParentClass = UAnimInstance::StaticClass();
|
|
|
|
|
@@ -213,27 +162,12 @@ void FBlueprintMCPServer::HandleAddAnimState(const FJsonObject* Json, FJsonObjec
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graph, stateName"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FString LoadError;
|
|
|
|
|
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(BlueprintName, LoadError);
|
|
|
|
|
if (!BP)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, LoadError);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(BP);
|
|
|
|
|
if (!AnimBP)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UAnimationStateMachineGraph* SMGraph = MCPUtils::FindStateMachineGraph(BP, GraphName);
|
|
|
|
|
if (!SMGraph)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("State machine graph '%s' not found"), *GraphName));
|
|
|
|
|
}
|
|
|
|
|
UAnimationStateMachineGraph* SMGraph = UMCPAssetFinder::LoadAnimStateMachineGraph(BlueprintName, GraphName, Result);
|
|
|
|
|
if (!SMGraph) return;
|
|
|
|
|
UAnimBlueprint* AnimBP = SMGraph->GetTypedOuter<UAnimBlueprint>();
|
|
|
|
|
|
|
|
|
|
// Check for duplicate state name
|
|
|
|
|
if (MCPUtils::FindStateByName(SMGraph, StateName))
|
|
|
|
|
if (MCPUtils::FindStateByName(SMGraph, StateName, nullptr))
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("State '%s' already exists in graph '%s'"), *StateName, *GraphName));
|
|
|
|
|
}
|
|
|
|
|
@@ -266,19 +200,8 @@ void FBlueprintMCPServer::HandleAddAnimState(const FJsonObject* Json, FJsonObjec
|
|
|
|
|
if (!AnimAssetName.IsEmpty() && NewState->GetBoundGraph())
|
|
|
|
|
{
|
|
|
|
|
// Try to find the animation asset and create a sequence player in the state's inner graph
|
|
|
|
|
UAnimSequence* AnimSeq = nullptr;
|
|
|
|
|
FAssetRegistryModule& ARM2 = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
|
|
|
|
|
TArray<FAssetData> AnimAssets;
|
|
|
|
|
ARM2.Get().GetAssetsByClass(UAnimSequence::StaticClass()->GetClassPathName(), AnimAssets, false);
|
|
|
|
|
for (const FAssetData& Asset : AnimAssets)
|
|
|
|
|
{
|
|
|
|
|
if (Asset.AssetName.ToString() == AnimAssetName ||
|
|
|
|
|
Asset.PackageName.ToString() == AnimAssetName)
|
|
|
|
|
{
|
|
|
|
|
AnimSeq = Cast<UAnimSequence>(Asset.GetAsset());
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
FAssetData* FoundAnimAsset = UMCPAssetFinder::FindAsset(UAnimSequence::StaticClass(), AnimAssetName);
|
|
|
|
|
UAnimSequence* AnimSeq = FoundAnimAsset ? Cast<UAnimSequence>(FoundAnimAsset->GetAsset()) : nullptr;
|
|
|
|
|
|
|
|
|
|
if (AnimSeq)
|
|
|
|
|
{
|
|
|
|
|
@@ -315,30 +238,12 @@ void FBlueprintMCPServer::HandleRemoveAnimState(const FJsonObject* Json, FJsonOb
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graph, stateName"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FString LoadError;
|
|
|
|
|
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(BlueprintName, LoadError);
|
|
|
|
|
if (!BP)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, LoadError);
|
|
|
|
|
}
|
|
|
|
|
UAnimationStateMachineGraph* SMGraph = UMCPAssetFinder::LoadAnimStateMachineGraph(BlueprintName, GraphName, Result);
|
|
|
|
|
if (!SMGraph) return;
|
|
|
|
|
UAnimBlueprint* AnimBP = SMGraph->GetTypedOuter<UAnimBlueprint>();
|
|
|
|
|
|
|
|
|
|
UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(BP);
|
|
|
|
|
if (!AnimBP)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UAnimationStateMachineGraph* SMGraph = MCPUtils::FindStateMachineGraph(BP, GraphName);
|
|
|
|
|
if (!SMGraph)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("State machine graph '%s' not found"), *GraphName));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UAnimStateNode* StateNode = MCPUtils::FindStateByName(SMGraph, StateName);
|
|
|
|
|
if (!StateNode)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("State '%s' not found in graph '%s'"), *StateName, *GraphName));
|
|
|
|
|
}
|
|
|
|
|
UAnimStateNode* StateNode = MCPUtils::FindStateByName(SMGraph, StateName, Result);
|
|
|
|
|
if (!StateNode) return;
|
|
|
|
|
|
|
|
|
|
// Collect and remove transitions connected to this state
|
|
|
|
|
TArray<UAnimStateTransitionNode*> TransitionsToRemove;
|
|
|
|
|
@@ -386,36 +291,15 @@ void FBlueprintMCPServer::HandleAddAnimTransition(const FJsonObject* Json, FJson
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graph, fromState, toState"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FString LoadError;
|
|
|
|
|
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(BlueprintName, LoadError);
|
|
|
|
|
if (!BP)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, LoadError);
|
|
|
|
|
}
|
|
|
|
|
UAnimationStateMachineGraph* SMGraph = UMCPAssetFinder::LoadAnimStateMachineGraph(BlueprintName, GraphName, Result);
|
|
|
|
|
if (!SMGraph) return;
|
|
|
|
|
UAnimBlueprint* AnimBP = SMGraph->GetTypedOuter<UAnimBlueprint>();
|
|
|
|
|
|
|
|
|
|
UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(BP);
|
|
|
|
|
if (!AnimBP)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
|
|
|
|
|
}
|
|
|
|
|
UAnimStateNode* FromState = MCPUtils::FindStateByName(SMGraph, FromStateName, Result);
|
|
|
|
|
if (!FromState) return;
|
|
|
|
|
|
|
|
|
|
UAnimationStateMachineGraph* SMGraph = MCPUtils::FindStateMachineGraph(BP, GraphName);
|
|
|
|
|
if (!SMGraph)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("State machine graph '%s' not found"), *GraphName));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UAnimStateNode* FromState = MCPUtils::FindStateByName(SMGraph, FromStateName);
|
|
|
|
|
if (!FromState)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("From state '%s' not found"), *FromStateName));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UAnimStateNode* ToState = MCPUtils::FindStateByName(SMGraph, ToStateName);
|
|
|
|
|
if (!ToState)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("To state '%s' not found"), *ToStateName));
|
|
|
|
|
}
|
|
|
|
|
UAnimStateNode* ToState = MCPUtils::FindStateByName(SMGraph, ToStateName, Result);
|
|
|
|
|
if (!ToState) return;
|
|
|
|
|
|
|
|
|
|
// Create transition node
|
|
|
|
|
UAnimStateTransitionNode* TransNode = NewObject<UAnimStateTransitionNode>(SMGraph);
|
|
|
|
|
@@ -473,24 +357,9 @@ void FBlueprintMCPServer::HandleSetTransitionRule(const FJsonObject* Json, FJson
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graph, fromState, toState"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FString LoadError;
|
|
|
|
|
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(BlueprintName, LoadError);
|
|
|
|
|
if (!BP)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, LoadError);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(BP);
|
|
|
|
|
if (!AnimBP)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UAnimationStateMachineGraph* SMGraph = MCPUtils::FindStateMachineGraph(BP, GraphName);
|
|
|
|
|
if (!SMGraph)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("State machine graph '%s' not found"), *GraphName));
|
|
|
|
|
}
|
|
|
|
|
UAnimationStateMachineGraph* SMGraph = UMCPAssetFinder::LoadAnimStateMachineGraph(BlueprintName, GraphName, Result);
|
|
|
|
|
if (!SMGraph) return;
|
|
|
|
|
UAnimBlueprint* AnimBP = SMGraph->GetTypedOuter<UAnimBlueprint>();
|
|
|
|
|
|
|
|
|
|
UAnimStateTransitionNode* TransNode = MCPUtils::FindTransition(SMGraph, FromStateName, ToStateName);
|
|
|
|
|
if (!TransNode)
|
|
|
|
|
@@ -565,18 +434,8 @@ void FBlueprintMCPServer::HandleAddAnimNode(const FJsonObject* Json, FJsonObject
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required fields: blueprint, nodeType"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FString LoadError;
|
|
|
|
|
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(BlueprintName, LoadError);
|
|
|
|
|
if (!BP)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, LoadError);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(BP);
|
|
|
|
|
if (!AnimBP)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
|
|
|
|
|
}
|
|
|
|
|
UAnimBlueprint* AnimBP = UMCPAssetFinder::LoadAsset<UAnimBlueprint>(BlueprintName, Result);
|
|
|
|
|
if (!AnimBP) return;
|
|
|
|
|
|
|
|
|
|
// Find target graph (default to AnimGraph if not specified)
|
|
|
|
|
UEdGraph* TargetGraph = nullptr;
|
|
|
|
|
@@ -586,7 +445,7 @@ void FBlueprintMCPServer::HandleAddAnimNode(const FJsonObject* Json, FJsonObject
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TArray<UEdGraph*> AllGraphs;
|
|
|
|
|
BP->GetAllGraphs(AllGraphs);
|
|
|
|
|
AnimBP->GetAllGraphs(AllGraphs);
|
|
|
|
|
for (UEdGraph* Graph : AllGraphs)
|
|
|
|
|
{
|
|
|
|
|
if (Graph->GetName() == GraphName)
|
|
|
|
|
@@ -617,20 +476,13 @@ void FBlueprintMCPServer::HandleAddAnimNode(const FJsonObject* Json, FJsonObject
|
|
|
|
|
FString AnimAssetName = Json->GetStringField(TEXT("animationAsset"));
|
|
|
|
|
if (!AnimAssetName.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
FAssetRegistryModule& ARM = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
|
|
|
|
|
TArray<FAssetData> AnimAssets;
|
|
|
|
|
ARM.Get().GetAssetsByClass(UAnimSequence::StaticClass()->GetClassPathName(), AnimAssets, false);
|
|
|
|
|
for (const FAssetData& Asset : AnimAssets)
|
|
|
|
|
FAssetData* FoundAnimAsset = UMCPAssetFinder::FindAsset(UAnimSequence::StaticClass(), AnimAssetName);
|
|
|
|
|
if (FoundAnimAsset)
|
|
|
|
|
{
|
|
|
|
|
if (Asset.AssetName.ToString() == AnimAssetName ||
|
|
|
|
|
Asset.PackageName.ToString() == AnimAssetName)
|
|
|
|
|
UAnimSequence* AnimSeq = Cast<UAnimSequence>(FoundAnimAsset->GetAsset());
|
|
|
|
|
if (AnimSeq)
|
|
|
|
|
{
|
|
|
|
|
UAnimSequence* AnimSeq = Cast<UAnimSequence>(Asset.GetAsset());
|
|
|
|
|
if (AnimSeq)
|
|
|
|
|
{
|
|
|
|
|
SeqNode->SetAnimationAsset(AnimSeq);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
SeqNode->SetAnimationAsset(AnimSeq);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -648,20 +500,13 @@ void FBlueprintMCPServer::HandleAddAnimNode(const FJsonObject* Json, FJsonObject
|
|
|
|
|
FString BSAssetName = Json->GetStringField(TEXT("animationAsset"));
|
|
|
|
|
if (!BSAssetName.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
FAssetRegistryModule& ARM = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
|
|
|
|
|
TArray<FAssetData> BSAssets;
|
|
|
|
|
ARM.Get().GetAssetsByClass(UBlendSpace::StaticClass()->GetClassPathName(), BSAssets, true);
|
|
|
|
|
for (const FAssetData& Asset : BSAssets)
|
|
|
|
|
FAssetData* FoundBSAsset = UMCPAssetFinder::FindAsset(UBlendSpace::StaticClass(), BSAssetName);
|
|
|
|
|
if (FoundBSAsset)
|
|
|
|
|
{
|
|
|
|
|
if (Asset.AssetName.ToString() == BSAssetName ||
|
|
|
|
|
Asset.PackageName.ToString() == BSAssetName)
|
|
|
|
|
UBlendSpace* BS = Cast<UBlendSpace>(FoundBSAsset->GetAsset());
|
|
|
|
|
if (BS)
|
|
|
|
|
{
|
|
|
|
|
UBlendSpace* BS = Cast<UBlendSpace>(Asset.GetAsset());
|
|
|
|
|
if (BS)
|
|
|
|
|
{
|
|
|
|
|
BSNode->SetAnimationAsset(BS);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
BSNode->SetAnimationAsset(BS);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -761,30 +606,12 @@ void FBlueprintMCPServer::HandleSetStateAnimation(const FJsonObject* Json, FJson
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graph, stateName, animationAsset"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FString LoadError;
|
|
|
|
|
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(BlueprintName, LoadError);
|
|
|
|
|
if (!BP)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, LoadError);
|
|
|
|
|
}
|
|
|
|
|
UAnimationStateMachineGraph* SMGraph = UMCPAssetFinder::LoadAnimStateMachineGraph(BlueprintName, GraphName, Result);
|
|
|
|
|
if (!SMGraph) return;
|
|
|
|
|
UAnimBlueprint* AnimBP = SMGraph->GetTypedOuter<UAnimBlueprint>();
|
|
|
|
|
|
|
|
|
|
UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(BP);
|
|
|
|
|
if (!AnimBP)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UAnimationStateMachineGraph* SMGraph = MCPUtils::FindStateMachineGraph(BP, GraphName);
|
|
|
|
|
if (!SMGraph)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("State machine graph '%s' not found"), *GraphName));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UAnimStateNode* StateNode = MCPUtils::FindStateByName(SMGraph, StateName);
|
|
|
|
|
if (!StateNode)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("State '%s' not found in graph '%s'"), *StateName, *GraphName));
|
|
|
|
|
}
|
|
|
|
|
UAnimStateNode* StateNode = MCPUtils::FindStateByName(SMGraph, StateName, Result);
|
|
|
|
|
if (!StateNode) return;
|
|
|
|
|
|
|
|
|
|
UEdGraph* InnerGraph = StateNode->GetBoundGraph();
|
|
|
|
|
if (!InnerGraph)
|
|
|
|
|
@@ -793,24 +620,8 @@ void FBlueprintMCPServer::HandleSetStateAnimation(const FJsonObject* Json, FJson
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find the animation asset
|
|
|
|
|
UAnimSequence* AnimSeq = nullptr;
|
|
|
|
|
FAssetRegistryModule& ARM = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
|
|
|
|
|
TArray<FAssetData> AnimAssets;
|
|
|
|
|
ARM.Get().GetAssetsByClass(UAnimSequence::StaticClass()->GetClassPathName(), AnimAssets, false);
|
|
|
|
|
for (const FAssetData& Asset : AnimAssets)
|
|
|
|
|
{
|
|
|
|
|
if (Asset.AssetName.ToString() == AnimAssetName ||
|
|
|
|
|
Asset.PackageName.ToString() == AnimAssetName)
|
|
|
|
|
{
|
|
|
|
|
AnimSeq = Cast<UAnimSequence>(Asset.GetAsset());
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!AnimSeq)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Animation asset '%s' not found"), *AnimAssetName));
|
|
|
|
|
}
|
|
|
|
|
UAnimSequence* AnimSeq = UMCPAssetFinder::LoadAsset<UAnimSequence>(AnimAssetName, Result);
|
|
|
|
|
if (!AnimSeq) return;
|
|
|
|
|
|
|
|
|
|
// Find existing SequencePlayer or create one
|
|
|
|
|
UAnimGraphNode_SequencePlayer* SeqNode = nullptr;
|
|
|
|
|
@@ -854,23 +665,13 @@ void FBlueprintMCPServer::HandleListAnimSlots(const FJsonObject* Json, FJsonObje
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required field: blueprint"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FString LoadError;
|
|
|
|
|
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(BlueprintName, LoadError);
|
|
|
|
|
if (!BP)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, LoadError);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(BP);
|
|
|
|
|
if (!AnimBP)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
|
|
|
|
|
}
|
|
|
|
|
UAnimBlueprint* AnimBP = UMCPAssetFinder::LoadAsset<UAnimBlueprint>(BlueprintName, Result);
|
|
|
|
|
if (!AnimBP) return;
|
|
|
|
|
|
|
|
|
|
// Walk all anim nodes to collect slot names
|
|
|
|
|
TSet<FString> SlotNames;
|
|
|
|
|
TArray<UEdGraph*> AllGraphs;
|
|
|
|
|
BP->GetAllGraphs(AllGraphs);
|
|
|
|
|
AnimBP->GetAllGraphs(AllGraphs);
|
|
|
|
|
for (UEdGraph* Graph : AllGraphs)
|
|
|
|
|
{
|
|
|
|
|
for (UEdGraphNode* Node : Graph->Nodes)
|
|
|
|
|
@@ -913,23 +714,13 @@ void FBlueprintMCPServer::HandleListSyncGroups(const FJsonObject* Json, FJsonObj
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required field: blueprint"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FString LoadError;
|
|
|
|
|
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(BlueprintName, LoadError);
|
|
|
|
|
if (!BP)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, LoadError);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(BP);
|
|
|
|
|
if (!AnimBP)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
|
|
|
|
|
}
|
|
|
|
|
UAnimBlueprint* AnimBP = UMCPAssetFinder::LoadAsset<UAnimBlueprint>(BlueprintName, Result);
|
|
|
|
|
if (!AnimBP) return;
|
|
|
|
|
|
|
|
|
|
// Walk all anim nodes to collect sync group names
|
|
|
|
|
TSet<FString> SyncGroupNames;
|
|
|
|
|
TArray<UEdGraph*> AllGraphs;
|
|
|
|
|
BP->GetAllGraphs(AllGraphs);
|
|
|
|
|
AnimBP->GetAllGraphs(AllGraphs);
|
|
|
|
|
for (UEdGraph* Graph : AllGraphs)
|
|
|
|
|
{
|
|
|
|
|
for (UEdGraphNode* Node : Graph->Nodes)
|
|
|
|
|
@@ -986,73 +777,16 @@ void FBlueprintMCPServer::HandleCreateBlendSpace(const FJsonObject* Json, FJsonO
|
|
|
|
|
|
|
|
|
|
// Check if asset already exists
|
|
|
|
|
FString FullAssetPath = PackagePath / Name;
|
|
|
|
|
if (UMCPAssetFinder::FindAsset(UBlendSpace::StaticClass(), Name))
|
|
|
|
|
{
|
|
|
|
|
FAssetRegistryModule& ARM = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
|
|
|
|
|
TArray<FAssetData> ExistingAssets;
|
|
|
|
|
ARM.Get().GetAssetsByClass(UBlendSpace::StaticClass()->GetClassPathName(), ExistingAssets, true);
|
|
|
|
|
for (const FAssetData& Asset : ExistingAssets)
|
|
|
|
|
{
|
|
|
|
|
if (Asset.AssetName.ToString() == Name || Asset.GetObjectPathString() == FullAssetPath)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(
|
|
|
|
|
TEXT("Blend Space '%s' already exists. Use a different name or delete the existing asset first."),
|
|
|
|
|
*Name));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(
|
|
|
|
|
TEXT("Blend Space '%s' already exists. Use a different name or delete the existing asset first."),
|
|
|
|
|
*Name));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Resolve skeleton
|
|
|
|
|
USkeleton* Skeleton = nullptr;
|
|
|
|
|
|
|
|
|
|
if (SkeletonName == TEXT("__create_test_skeleton__"))
|
|
|
|
|
{
|
|
|
|
|
FString TestSkeletonPath = PackagePath / (Name + TEXT("_TestSkeleton"));
|
|
|
|
|
UPackage* SkelPackage = CreatePackage(*TestSkeletonPath);
|
|
|
|
|
Skeleton = NewObject<USkeleton>(SkelPackage, FName(*(Name + TEXT("_TestSkeleton"))), RF_Public | RF_Standalone);
|
|
|
|
|
if (Skeleton)
|
|
|
|
|
{
|
|
|
|
|
Skeleton->MarkPackageDirty();
|
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created test skeleton '%s'"), *Skeleton->GetName());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
FAssetRegistryModule& ARM = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
|
|
|
|
|
TArray<FAssetData> SkeletonAssets;
|
|
|
|
|
ARM.Get().GetAssetsByClass(USkeleton::StaticClass()->GetClassPathName(), SkeletonAssets, false);
|
|
|
|
|
|
|
|
|
|
for (const FAssetData& Asset : SkeletonAssets)
|
|
|
|
|
{
|
|
|
|
|
if (Asset.AssetName.ToString() == SkeletonName ||
|
|
|
|
|
Asset.PackageName.ToString() == SkeletonName ||
|
|
|
|
|
Asset.GetObjectPathString() == SkeletonName)
|
|
|
|
|
{
|
|
|
|
|
Skeleton = Cast<USkeleton>(Asset.GetAsset());
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Case-insensitive fallback
|
|
|
|
|
if (!Skeleton)
|
|
|
|
|
{
|
|
|
|
|
for (const FAssetData& Asset : SkeletonAssets)
|
|
|
|
|
{
|
|
|
|
|
if (Asset.AssetName.ToString().Equals(SkeletonName, ESearchCase::IgnoreCase) ||
|
|
|
|
|
Asset.PackageName.ToString().Equals(SkeletonName, ESearchCase::IgnoreCase))
|
|
|
|
|
{
|
|
|
|
|
Skeleton = Cast<USkeleton>(Asset.GetAsset());
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Skeleton)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(
|
|
|
|
|
TEXT("Skeleton '%s' not found. Provide the skeleton asset name or path. Use '__create_test_skeleton__' for testing."),
|
|
|
|
|
*SkeletonName));
|
|
|
|
|
}
|
|
|
|
|
USkeleton* Skeleton = UMCPAssetFinder::LoadAsset<USkeleton>(SkeletonName, Result);
|
|
|
|
|
if (!Skeleton) return;
|
|
|
|
|
|
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Creating Blend Space '%s' in '%s' with skeleton '%s'"),
|
|
|
|
|
*Name, *PackagePath, *Skeleton->GetName());
|
|
|
|
|
@@ -1101,40 +835,8 @@ void FBlueprintMCPServer::HandleSetBlendSpaceSamples(const FJsonObject* Json, FJ
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Load the blend space
|
|
|
|
|
UBlendSpace* BS = nullptr;
|
|
|
|
|
{
|
|
|
|
|
FAssetRegistryModule& ARM = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
|
|
|
|
|
TArray<FAssetData> BSAssets;
|
|
|
|
|
ARM.Get().GetAssetsByClass(UBlendSpace::StaticClass()->GetClassPathName(), BSAssets, true);
|
|
|
|
|
for (const FAssetData& Asset : BSAssets)
|
|
|
|
|
{
|
|
|
|
|
if (Asset.AssetName.ToString() == BlendSpaceName ||
|
|
|
|
|
Asset.PackageName.ToString() == BlendSpaceName ||
|
|
|
|
|
Asset.GetObjectPathString() == BlendSpaceName)
|
|
|
|
|
{
|
|
|
|
|
BS = Cast<UBlendSpace>(Asset.GetAsset());
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Try FName-based path match (e.g. /Game/Folder/BS_Name)
|
|
|
|
|
if (!BS)
|
|
|
|
|
{
|
|
|
|
|
FString LeafName = FPaths::GetCleanFilename(BlendSpaceName);
|
|
|
|
|
for (const FAssetData& Asset : BSAssets)
|
|
|
|
|
{
|
|
|
|
|
if (Asset.AssetName.ToString() == LeafName)
|
|
|
|
|
{
|
|
|
|
|
BS = Cast<UBlendSpace>(Asset.GetAsset());
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!BS)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Blend Space '%s' not found"), *BlendSpaceName));
|
|
|
|
|
}
|
|
|
|
|
UBlendSpace* BS = UMCPAssetFinder::LoadAsset<UBlendSpace>(BlendSpaceName, Result);
|
|
|
|
|
if (!BS) return;
|
|
|
|
|
|
|
|
|
|
// Set axis parameters
|
|
|
|
|
BS->PreEditChange(nullptr);
|
|
|
|
|
@@ -1170,11 +872,6 @@ void FBlueprintMCPServer::HandleSetBlendSpaceSamples(const FJsonObject* Json, FJ
|
|
|
|
|
|
|
|
|
|
if (Json->TryGetArrayField(TEXT("samples"), SamplesArray) && SamplesArray)
|
|
|
|
|
{
|
|
|
|
|
// Pre-load all animation sequences for lookup
|
|
|
|
|
FAssetRegistryModule& ARM = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
|
|
|
|
|
TArray<FAssetData> AnimAssets;
|
|
|
|
|
ARM.Get().GetAssetsByClass(UAnimSequence::StaticClass()->GetClassPathName(), AnimAssets, false);
|
|
|
|
|
|
|
|
|
|
for (const TSharedPtr<FJsonValue>& SampleVal : *SamplesArray)
|
|
|
|
|
{
|
|
|
|
|
const TSharedPtr<FJsonObject>& SampleObj = SampleVal->AsObject();
|
|
|
|
|
@@ -1187,29 +884,10 @@ void FBlueprintMCPServer::HandleSetBlendSpaceSamples(const FJsonObject* Json, FJ
|
|
|
|
|
UAnimSequence* AnimSeq = nullptr;
|
|
|
|
|
if (!AnimAssetName.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
for (const FAssetData& Asset : AnimAssets)
|
|
|
|
|
FAssetData* FoundAnimAsset = UMCPAssetFinder::FindAsset(UAnimSequence::StaticClass(), AnimAssetName);
|
|
|
|
|
if (FoundAnimAsset)
|
|
|
|
|
{
|
|
|
|
|
if (Asset.AssetName.ToString() == AnimAssetName ||
|
|
|
|
|
Asset.PackageName.ToString() == AnimAssetName ||
|
|
|
|
|
Asset.GetObjectPathString() == AnimAssetName)
|
|
|
|
|
{
|
|
|
|
|
AnimSeq = Cast<UAnimSequence>(Asset.GetAsset());
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Also try extracting leaf name from path
|
|
|
|
|
if (!AnimSeq)
|
|
|
|
|
{
|
|
|
|
|
FString LeafName = FPaths::GetCleanFilename(AnimAssetName);
|
|
|
|
|
for (const FAssetData& Asset : AnimAssets)
|
|
|
|
|
{
|
|
|
|
|
if (Asset.AssetName.ToString() == LeafName)
|
|
|
|
|
{
|
|
|
|
|
AnimSeq = Cast<UAnimSequence>(Asset.GetAsset());
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
AnimSeq = Cast<UAnimSequence>(FoundAnimAsset->GetAsset());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -1260,30 +938,12 @@ void FBlueprintMCPServer::HandleSetStateBlendSpace(const FJsonObject* Json, FJso
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graph, stateName, blendSpace"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FString LoadError;
|
|
|
|
|
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(BlueprintName, LoadError);
|
|
|
|
|
if (!BP)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, LoadError);
|
|
|
|
|
}
|
|
|
|
|
UAnimationStateMachineGraph* SMGraph = UMCPAssetFinder::LoadAnimStateMachineGraph(BlueprintName, GraphName, Result);
|
|
|
|
|
if (!SMGraph) return;
|
|
|
|
|
UAnimBlueprint* AnimBP = SMGraph->GetTypedOuter<UAnimBlueprint>();
|
|
|
|
|
|
|
|
|
|
UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(BP);
|
|
|
|
|
if (!AnimBP)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UAnimationStateMachineGraph* SMGraph = MCPUtils::FindStateMachineGraph(BP, GraphName);
|
|
|
|
|
if (!SMGraph)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("State machine graph '%s' not found"), *GraphName));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UAnimStateNode* StateNode = MCPUtils::FindStateByName(SMGraph, StateName);
|
|
|
|
|
if (!StateNode)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("State '%s' not found in graph '%s'"), *StateName, *GraphName));
|
|
|
|
|
}
|
|
|
|
|
UAnimStateNode* StateNode = MCPUtils::FindStateByName(SMGraph, StateName, Result);
|
|
|
|
|
if (!StateNode) return;
|
|
|
|
|
|
|
|
|
|
UEdGraph* InnerGraph = StateNode->GetBoundGraph();
|
|
|
|
|
if (!InnerGraph)
|
|
|
|
|
@@ -1292,40 +952,8 @@ void FBlueprintMCPServer::HandleSetStateBlendSpace(const FJsonObject* Json, FJso
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find the blend space asset
|
|
|
|
|
UBlendSpace* BlendSpaceAsset = nullptr;
|
|
|
|
|
{
|
|
|
|
|
FAssetRegistryModule& ARM = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
|
|
|
|
|
TArray<FAssetData> BSAssets;
|
|
|
|
|
ARM.Get().GetAssetsByClass(UBlendSpace::StaticClass()->GetClassPathName(), BSAssets, true);
|
|
|
|
|
for (const FAssetData& Asset : BSAssets)
|
|
|
|
|
{
|
|
|
|
|
if (Asset.AssetName.ToString() == BlendSpaceName ||
|
|
|
|
|
Asset.PackageName.ToString() == BlendSpaceName ||
|
|
|
|
|
Asset.GetObjectPathString() == BlendSpaceName)
|
|
|
|
|
{
|
|
|
|
|
BlendSpaceAsset = Cast<UBlendSpace>(Asset.GetAsset());
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Leaf name fallback
|
|
|
|
|
if (!BlendSpaceAsset)
|
|
|
|
|
{
|
|
|
|
|
FString LeafName = FPaths::GetCleanFilename(BlendSpaceName);
|
|
|
|
|
for (const FAssetData& Asset : BSAssets)
|
|
|
|
|
{
|
|
|
|
|
if (Asset.AssetName.ToString() == LeafName)
|
|
|
|
|
{
|
|
|
|
|
BlendSpaceAsset = Cast<UBlendSpace>(Asset.GetAsset());
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!BlendSpaceAsset)
|
|
|
|
|
{
|
|
|
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Blend Space '%s' not found"), *BlendSpaceName));
|
|
|
|
|
}
|
|
|
|
|
UBlendSpace* BlendSpaceAsset = UMCPAssetFinder::LoadAsset<UBlendSpace>(BlendSpaceName, Result);
|
|
|
|
|
if (!BlendSpaceAsset) return;
|
|
|
|
|
|
|
|
|
|
// Find existing BlendSpacePlayer or create one
|
|
|
|
|
UAnimGraphNode_BlendSpacePlayer* BSNode = nullptr;
|
|
|
|
|
@@ -1406,7 +1034,7 @@ void FBlueprintMCPServer::HandleSetStateBlendSpace(const FJsonObject* Json, FJso
|
|
|
|
|
// Verify the variable exists in the blueprint
|
|
|
|
|
FName VarFName(*VarName);
|
|
|
|
|
bool bVarFound = false;
|
|
|
|
|
for (FBPVariableDescription& Var : BP->NewVariables)
|
|
|
|
|
for (FBPVariableDescription& Var : AnimBP->NewVariables)
|
|
|
|
|
{
|
|
|
|
|
if (Var.VarName == VarFName)
|
|
|
|
|
{
|
|
|
|
|
@@ -1417,7 +1045,7 @@ void FBlueprintMCPServer::HandleSetStateBlendSpace(const FJsonObject* Json, FJso
|
|
|
|
|
if (!bVarFound)
|
|
|
|
|
{
|
|
|
|
|
// Also check parent class properties
|
|
|
|
|
if (UClass* GenClass = BP->SkeletonGeneratedClass)
|
|
|
|
|
if (UClass* GenClass = AnimBP->SkeletonGeneratedClass)
|
|
|
|
|
{
|
|
|
|
|
if (FProperty* Prop = GenClass->FindPropertyByName(VarFName))
|
|
|
|
|
{
|
|
|
|
|
|