Delete MCPAssets and the things that use it.
This commit is contained in:
@@ -1,79 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "MCPServer.h"
|
||||
#include "MCPHandler.h"
|
||||
#include "MCPAssets.h"
|
||||
#include "MCPUtils.h"
|
||||
#include "MCPTypes.h"
|
||||
#include "Engine/Blueprint.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Blueprint_Search.generated.h"
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
UCLASS()
|
||||
class UMCP_Blueprint_Search : public UObject, public IMCPHandler
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(meta=(Optional, Description="Substring filter for blueprint name or path"))
|
||||
FString Query;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="Filter by parent class name (exact match, case-insensitive)"))
|
||||
FString ParentClass;
|
||||
|
||||
virtual FString GetDescription() const override
|
||||
{
|
||||
return TEXT("List all Blueprint assets in the project, with optional filtering by name, parent class, or type.");
|
||||
}
|
||||
|
||||
virtual void Handle() override
|
||||
{
|
||||
MCPAssets<UObject> Assets;
|
||||
Assets.Scan<UBlueprint>().Substring(Query).Limit(500);
|
||||
if (!Assets.Info()) return;
|
||||
|
||||
UClass *Parent = nullptr;
|
||||
if (!ParentClass.IsEmpty())
|
||||
{
|
||||
Parent = UMCPTypes::TextToOneObjectType(ParentClass);
|
||||
if (!Parent) return;
|
||||
}
|
||||
|
||||
int32 Count = 0;
|
||||
for (const FAssetData& Asset : Assets.AllData())
|
||||
{
|
||||
// Extract parent class name from asset tags
|
||||
FString ParentClassName;
|
||||
Asset.GetTagValue(FName(TEXT("ParentClass")), ParentClassName);
|
||||
int32 DotIndex;
|
||||
if (ParentClassName.FindLastChar('.', DotIndex))
|
||||
{
|
||||
ParentClassName = ParentClassName.Mid(DotIndex + 1);
|
||||
}
|
||||
ParentClassName.RemoveFromEnd(TEXT("'"));
|
||||
|
||||
// Apply parent class filter
|
||||
if (!ParentClass.IsEmpty())
|
||||
{
|
||||
if (!ParentClassName.Equals(ParentClass, ESearchCase::IgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
UMCPServer::Printf(TEXT("%30s %s\n"), *ParentClassName, *Asset.PackageName.ToString());
|
||||
Count++;
|
||||
}
|
||||
|
||||
if (Count == 0)
|
||||
{
|
||||
UMCPServer::Print(TEXT("No blueprint assets found.\n"));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,127 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "MCPServer.h"
|
||||
#include "MCPHandler.h"
|
||||
#include "MCPAssets.h"
|
||||
#include "MCPUtils.h"
|
||||
#include "Engine/Blueprint.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Engine/Level.h"
|
||||
#include "Engine/LevelScriptBlueprint.h"
|
||||
#include "EdGraph/EdGraphNode.h"
|
||||
#include "K2Node_CallFunction.h"
|
||||
#include "K2Node_Event.h"
|
||||
#include "K2Node_CustomEvent.h"
|
||||
#include "K2Node_VariableGet.h"
|
||||
#include "K2Node_VariableSet.h"
|
||||
#include "Blueprint_SearchContents.generated.h"
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
UCLASS()
|
||||
class UMCP_Blueprint_SearchContents : public UObject, public IMCPHandler
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(meta=(Description="Search query string to match against node titles, function names, event names, and variable names"))
|
||||
FString Query;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="Filter results to blueprints whose path contains this substring"))
|
||||
FString Path;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="Maximum number of results to return (default 50, max 200)"))
|
||||
int32 MaxResults = 0;
|
||||
|
||||
virtual FString GetDescription() const override
|
||||
{
|
||||
return TEXT("Search across all Blueprint graphs for nodes matching a query string.");
|
||||
}
|
||||
|
||||
virtual void Handle() override
|
||||
{
|
||||
int32 Limit = (MaxResults > 0) ? FMath::Clamp(MaxResults, 1, 200) : 50;
|
||||
int32 Count = 0;
|
||||
|
||||
// Search one blueprint's nodes for the query string.
|
||||
auto SearchBlueprint = [&](UBlueprint* BP, bool bIsLevelBP)
|
||||
{
|
||||
for (UEdGraphNode* Node : MCPUtils::AllNodes(BP))
|
||||
{
|
||||
if (Count >= Limit) return;
|
||||
|
||||
FString Title = Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString();
|
||||
|
||||
FString FuncName, EventName, VarName;
|
||||
if (auto* CF = Cast<UK2Node_CallFunction>(Node))
|
||||
{
|
||||
FuncName = CF->FunctionReference.GetMemberName().ToString();
|
||||
}
|
||||
else if (auto* Ev = Cast<UK2Node_Event>(Node))
|
||||
{
|
||||
EventName = Ev->EventReference.GetMemberName().ToString();
|
||||
}
|
||||
else if (auto* CE = Cast<UK2Node_CustomEvent>(Node))
|
||||
{
|
||||
EventName = CE->CustomFunctionName.ToString();
|
||||
}
|
||||
else if (auto* VG = Cast<UK2Node_VariableGet>(Node))
|
||||
{
|
||||
VarName = VG->GetVarName().ToString();
|
||||
}
|
||||
else if (auto* VS = Cast<UK2Node_VariableSet>(Node))
|
||||
{
|
||||
VarName = VS->GetVarName().ToString();
|
||||
}
|
||||
|
||||
bool bMatch = Title.Contains(Query, ESearchCase::IgnoreCase) ||
|
||||
(!FuncName.IsEmpty() && FuncName.Contains(Query, ESearchCase::IgnoreCase)) ||
|
||||
(!EventName.IsEmpty() && EventName.Contains(Query, ESearchCase::IgnoreCase)) ||
|
||||
(!VarName.IsEmpty() && VarName.Contains(Query, ESearchCase::IgnoreCase));
|
||||
|
||||
if (!bMatch) continue;
|
||||
|
||||
Count++;
|
||||
UMCPServer::Printf(TEXT("blueprint: %s\n"), *MCPUtils::FormatName(BP));
|
||||
UMCPServer::Printf(TEXT(" graph: %s\n"), *MCPUtils::FormatName(Node->GetGraph()));
|
||||
UMCPServer::Printf(TEXT(" node: %s\n"), *MCPUtils::FormatName(Node));
|
||||
UMCPServer::Printf(TEXT(" class: %s\n"), *MCPUtils::FormatName(Node->GetClass()));
|
||||
if (!FuncName.IsEmpty()) UMCPServer::Printf(TEXT(" function: %s\n"), *FuncName);
|
||||
if (!EventName.IsEmpty()) UMCPServer::Printf(TEXT(" event: %s\n"), *EventName);
|
||||
if (!VarName.IsEmpty()) UMCPServer::Printf(TEXT(" variable: %s\n"), *VarName);
|
||||
if (bIsLevelBP) UMCPServer::Print(TEXT(" level-blueprint: true\n"));
|
||||
UMCPServer::Print(TEXT("\n"));
|
||||
}
|
||||
};
|
||||
|
||||
// Search regular blueprints
|
||||
MCPAssets<UBlueprint> AllBlueprints;
|
||||
if (!Path.IsEmpty()) AllBlueprints.Substring(Path);
|
||||
AllBlueprints.Load();
|
||||
for (UBlueprint* BP : AllBlueprints.Objects())
|
||||
{
|
||||
if (Count >= Limit) break;
|
||||
SearchBlueprint(BP, false);
|
||||
}
|
||||
|
||||
// Search level blueprints
|
||||
MCPAssets<UWorld> AllWorlds;
|
||||
if (!Path.IsEmpty()) AllWorlds.Substring(Path);
|
||||
AllWorlds.Load();
|
||||
for (UWorld* World : AllWorlds.Objects())
|
||||
{
|
||||
if (Count >= Limit) break;
|
||||
if (!World || !World->PersistentLevel) continue;
|
||||
ULevelScriptBlueprint* LevelBP = World->PersistentLevel->GetLevelScriptBlueprint(false);
|
||||
if (!LevelBP) continue;
|
||||
SearchBlueprint(LevelBP, true);
|
||||
}
|
||||
|
||||
UMCPServer::Printf(TEXT("Results: %d\n"), Count);
|
||||
if (Count >= Limit) UMCPServer::Printf(TEXT("(limit %d reached, use MaxResults to increase)\n"), Limit);
|
||||
}
|
||||
};
|
||||
@@ -1,169 +0,0 @@
|
||||
#include "MCPAssets.h"
|
||||
#include "MCPServer.h"
|
||||
#include "Engine/Blueprint.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Engine/Level.h"
|
||||
#include "Engine/LevelScriptBlueprint.h"
|
||||
#include "Materials/Material.h"
|
||||
#include "MCPUtils.h"
|
||||
#include "AssetRegistry/AssetRegistryModule.h"
|
||||
#include "AssetRegistry/IAssetRegistry.h"
|
||||
|
||||
// ============================================================
|
||||
// MCPAssetsBase
|
||||
// ============================================================
|
||||
|
||||
MCPAssetsBase::MCPAssetsBase(UClass* InTargetClass)
|
||||
: TargetClass(InTargetClass)
|
||||
{
|
||||
Scans.Add(InTargetClass);
|
||||
}
|
||||
|
||||
MCPAssetsBase& MCPAssetsBase::Exact(const FString& InName)
|
||||
{
|
||||
MatchName = InName;
|
||||
bExactMatch = true;
|
||||
bPatternHasSlash = MatchName.Contains(TEXT("/"));
|
||||
return *this;
|
||||
}
|
||||
|
||||
MCPAssetsBase& MCPAssetsBase::Substring(const FString& InFilter)
|
||||
{
|
||||
MatchName = InFilter;
|
||||
bExactMatch = false;
|
||||
bPatternHasSlash = MatchName.Contains(TEXT("/"));
|
||||
return *this;
|
||||
}
|
||||
|
||||
MCPAssetsBase& MCPAssetsBase::NoDerived()
|
||||
{
|
||||
bNoDerived = true;
|
||||
return *this;
|
||||
}
|
||||
|
||||
MCPAssetsBase& MCPAssetsBase::AllContent()
|
||||
{
|
||||
bAllContent = true;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
bool MCPAssetsBase::Info()
|
||||
{
|
||||
// In theory, there's no reason a person couldn't load/info
|
||||
// more than once, to obtain updates. Might as well allow it.
|
||||
AssetResults.Empty();
|
||||
UObjectResults.Empty();
|
||||
|
||||
// Query the asset registry
|
||||
IAssetRegistry& AR = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry").Get();
|
||||
|
||||
TArray<FAssetData> Candidates;
|
||||
AR.GetAssets(ConfigureFilter(), Candidates);
|
||||
for (const FAssetData &Data : Candidates)
|
||||
{
|
||||
if (AssetMatches(Data)) AssetResults.Add(Data);
|
||||
if (bErrorIfAny && (AssetResults.Num() > 0))
|
||||
{
|
||||
SetError(FString::Printf(TEXT("%s '%s' already exists."), *TargetClass->GetName(), *AssetResults[0].PackageName.ToString()));
|
||||
return false;
|
||||
}
|
||||
if (bErrorIfTwo && (AssetResults.Num() > 1))
|
||||
{
|
||||
SetError(FString::Printf(
|
||||
TEXT("Ambiguous %s name '%s' — matches '%s' and '%s'. Use the full package path to disambiguate."),
|
||||
*TargetClass->GetName(), *MatchName, *AssetResults[0].PackageName.ToString(), *AssetResults[1].PackageName.ToString()));
|
||||
return false;
|
||||
}
|
||||
if (AssetResults.Num() >= MaxResults) break;
|
||||
}
|
||||
|
||||
// Check error conditions on the result count
|
||||
if (bErrorIfNone && AssetResults.IsEmpty())
|
||||
{
|
||||
SetError(FString::Printf(TEXT("%s '%s' not found."), *TargetClass->GetName(), *MatchName));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MCPAssetsBase::Load()
|
||||
{
|
||||
if (!Info()) return false;
|
||||
|
||||
TArray<FAssetData> AssetsToLoad;
|
||||
Swap(AssetsToLoad, AssetResults);
|
||||
for (const FAssetData &Asset : AssetsToLoad)
|
||||
{
|
||||
UObject *Obj = TryLoadAsset(Asset);
|
||||
if (!Obj) continue;
|
||||
|
||||
// If this is a material open in the editor, use the editor's transient copy.
|
||||
if (UMaterial* Mat = Cast<UMaterial>(Obj))
|
||||
Obj = MCPUtils::ReplaceMaterialWithTransientCopy(Mat);
|
||||
|
||||
AssetResults.Add(Asset);
|
||||
UObjectResults.Add(Obj);
|
||||
}
|
||||
if (bErrorIfNone && AssetResults.IsEmpty())
|
||||
{
|
||||
SetError(FString::Printf(TEXT("%s '%s' exists but cannot be loaded."), *TargetClass->GetName(),
|
||||
*AssetsToLoad[0].PackageName.ToString()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
FARFilter MCPAssetsBase::ConfigureFilter()
|
||||
{
|
||||
FARFilter Filter;
|
||||
for (UClass* C : Scans) Filter.ClassPaths.Add(C->GetClassPathName());
|
||||
Filter.bRecursiveClasses = !bNoDerived;
|
||||
if (!bAllContent)
|
||||
{
|
||||
Filter.PackagePaths.Add(FName(TEXT("/Game")));
|
||||
Filter.bRecursivePaths = true;
|
||||
}
|
||||
return Filter;
|
||||
}
|
||||
|
||||
bool MCPAssetsBase::AssetMatches(const FAssetData &Asset)
|
||||
{
|
||||
if (bExactMatch)
|
||||
{
|
||||
FString Name = bPatternHasSlash ? Asset.PackageName.ToString() : Asset.AssetName.ToString();
|
||||
return Name.Equals(MatchName, ESearchCase::IgnoreCase);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Asset.AssetName.ToString().Contains(MatchName, ESearchCase::IgnoreCase) ||
|
||||
Asset.PackageName.ToString().Contains(MatchName, ESearchCase::IgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
UObject *MCPAssetsBase::TryLoadAsset(const FAssetData &Asset)
|
||||
{
|
||||
UObject* Obj = Asset.GetAsset();
|
||||
if (Obj == nullptr) return nullptr;
|
||||
if (Obj->IsA(TargetClass)) return Obj;
|
||||
|
||||
if (TargetClass->IsChildOf(UBlueprint::StaticClass()) &&
|
||||
ULevelScriptBlueprint::StaticClass()->IsChildOf(TargetClass))
|
||||
{
|
||||
UWorld* World = Cast<UWorld>(Obj);
|
||||
if (World && World->PersistentLevel)
|
||||
{
|
||||
ULevelScriptBlueprint* LevelBP = World->PersistentLevel->GetLevelScriptBlueprint(true);
|
||||
if (LevelBP) return LevelBP;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void MCPAssetsBase::SetError(const FString &Msg)
|
||||
{
|
||||
AssetResults.Empty();
|
||||
UObjectResults.Empty();
|
||||
UMCPServer::Printf(TEXT("ERROR: %s\n"), *Msg);
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "MCPJson.h"
|
||||
#include "LogCapture.h"
|
||||
#include "MCPUtils.h"
|
||||
#include "MCPAssets.h"
|
||||
#include "UObject/StrongObjectPtr.h"
|
||||
#include "Materials/MaterialExpression.h"
|
||||
#include "AssetRegistry/AssetRegistryModule.h"
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "AssetRegistry/AssetData.h"
|
||||
#include "MCPUtils.h"
|
||||
#include "Engine/Blueprint.h"
|
||||
#include "Engine/LevelScriptBlueprint.h"
|
||||
#include "Engine/World.h"
|
||||
|
||||
struct FARFilter;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//
|
||||
// MCPAssets - search for assets.
|
||||
//
|
||||
//
|
||||
// Construct an object of class MCPAssets like this:
|
||||
//
|
||||
// MCPAssets<UBlueprint> Assets;
|
||||
//
|
||||
// The UBlueprint template parameter means that this example
|
||||
// Assets loader is capable of storing pointers to
|
||||
// UBlueprint.
|
||||
//
|
||||
// It also means that by default, it will scan
|
||||
// all UBlueprint assets. You can narrow that:
|
||||
//
|
||||
// Assets.NoScans();
|
||||
// Assets.Scan<UAnimBlueprint>();
|
||||
// Assets.Scan<ULuprexBlueprint>();
|
||||
//
|
||||
// To get string matching, call either 'Exact' or
|
||||
// 'Substring'. If you don't call either of these, there's
|
||||
// no string filter. If the string you pass in contains a
|
||||
// slash, then the search is by asset-path, otherwise, by
|
||||
// asset-name:
|
||||
//
|
||||
// Assets.Substring(TEXT("MyAsset"));
|
||||
//
|
||||
// By default, the asset finder limits itself to assets in
|
||||
// the /Game folder. You can expand that:
|
||||
//
|
||||
// Assets.AllContent();
|
||||
//
|
||||
// You can specify that you don't want to see derived
|
||||
// classes:
|
||||
//
|
||||
// Assets.NoDerived()
|
||||
//
|
||||
// You can specify a limit on the number of results
|
||||
// returned:
|
||||
//
|
||||
// Assets.Limit(100)
|
||||
//
|
||||
// You can specify what constitutes an error condition. If
|
||||
// the asset finder detects an error, then it will report
|
||||
// it:
|
||||
//
|
||||
// Assets.ENone() - it's an error if nothing is found
|
||||
// Assets.EAny() - it's an error if anything is found
|
||||
// Assets.ETwo() - it's an error if two or more are found
|
||||
//
|
||||
// Errors are reported via UMCPServer::Printf.
|
||||
//
|
||||
// Once the Assets object is configured, it's time to scan
|
||||
// the assets. Use 'Info' if you just want to see
|
||||
// FAssetData. Use 'Load' if you want to load the assets
|
||||
// into memory. Both of these functions return true if
|
||||
// there were no errors, or false if there was one.
|
||||
//
|
||||
// bool ok = Assets.Load();
|
||||
//
|
||||
// Once you've scanned the assets, you can examine the
|
||||
// results using the following methods. The objects array
|
||||
// will be empty if you called 'Info' instead of 'Load':
|
||||
//
|
||||
// const TArray<FAssetData>& AllData();
|
||||
// const FAssetData& OneData();
|
||||
// const TArray<UBlueprint*> Objects();
|
||||
// UBlueprint* Object();
|
||||
//
|
||||
//
|
||||
// MCPAssets configuration methods can be chained:
|
||||
//
|
||||
// Assets.Limit(100).ENone().ETwo();
|
||||
//
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
class MCPAssetsBase
|
||||
{
|
||||
public:
|
||||
MCPAssetsBase& NoScans() { Scans.Empty(); return *this; }
|
||||
MCPAssetsBase& Scan(UClass* Class) { Scans.Add(Class); return *this; }
|
||||
template<class T> MCPAssetsBase& Scan() { return Scan(T::StaticClass()); }
|
||||
MCPAssetsBase& Exact(const FString& InName);
|
||||
MCPAssetsBase& Substring(const FString& InFilter);
|
||||
MCPAssetsBase& Limit(int32 Count) { MaxResults = Count; return *this; }
|
||||
MCPAssetsBase& NoDerived();
|
||||
MCPAssetsBase& AllContent();
|
||||
MCPAssetsBase& EAny() { bErrorIfAny = true; return *this; }
|
||||
MCPAssetsBase& ENone() { bErrorIfNone = true; return *this; }
|
||||
MCPAssetsBase& ETwo() { bErrorIfTwo = true; return *this; }
|
||||
|
||||
bool Load();
|
||||
bool Info();
|
||||
|
||||
const TArray<FAssetData>& AllData() const { return AssetResults; }
|
||||
const FAssetData& OneData() const { return AssetResults[0]; }
|
||||
|
||||
private:
|
||||
FARFilter ConfigureFilter();
|
||||
bool AssetMatches(const FAssetData &Data);
|
||||
UObject *TryLoadAsset(const FAssetData &Asset);
|
||||
void SetError(const FString &Msg);
|
||||
|
||||
protected:
|
||||
MCPAssetsBase(UClass* InTargetClass);
|
||||
|
||||
UClass* TargetClass;
|
||||
TSet<UClass*> Scans;
|
||||
TArray<FAssetData> AssetResults;
|
||||
TArray<UObject*> UObjectResults;
|
||||
FString MatchName;
|
||||
bool bExactMatch = false;
|
||||
bool bPatternHasSlash = false;
|
||||
bool bNoDerived = false;
|
||||
bool bAllContent = false;
|
||||
bool bErrorIfAny = false;
|
||||
bool bErrorIfNone = false;
|
||||
bool bErrorIfTwo = false;
|
||||
int32 MaxResults = 50;
|
||||
};
|
||||
|
||||
|
||||
template<class T>
|
||||
class MCPAssets : public MCPAssetsBase
|
||||
{
|
||||
public:
|
||||
MCPAssets() : MCPAssetsBase(T::StaticClass()) {}
|
||||
|
||||
TArrayView<T* const> Objects() const
|
||||
{
|
||||
return TArrayView<T* const>(reinterpret_cast<T* const*>(UObjectResults.GetData()), UObjectResults.Num());
|
||||
}
|
||||
T* Object() const { return UObjectResults.IsEmpty() ? nullptr : static_cast<T*>(UObjectResults[0]); }
|
||||
};
|
||||
Reference in New Issue
Block a user