More work on MCP
This commit is contained in:
1
.clangd-query.pid
Normal file
1
.clangd-query.pid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
540424
|
||||||
BIN
Content/Testing/M_Test.uasset
LFS
Normal file
BIN
Content/Testing/M_Test.uasset
LFS
Normal file
Binary file not shown.
@@ -54,3 +54,11 @@ is actually fine — but it's a one-way door.
|
|||||||
recreated during the replacement
|
recreated during the replacement
|
||||||
- **DumpMaterialInstanceParameters** — parent chain lost class
|
- **DumpMaterialInstanceParameters** — parent chain lost class
|
||||||
type info (Material vs MaterialInstance)
|
type info (Material vs MaterialInstance)
|
||||||
|
|
||||||
|
## Design changes
|
||||||
|
|
||||||
|
- Saving assets is being done at somewhat unpredictable
|
||||||
|
points. It's not entirely clear that we *should* be
|
||||||
|
saving things every time an edit is made. It might be
|
||||||
|
better to have an explicit "Save" MCP command.
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
#include "BlueprintExporter.h"
|
#include "BlueprintExporter.h"
|
||||||
#include "Engine/Blueprint.h"
|
#include "Engine/Blueprint.h"
|
||||||
#include "EdGraph/EdGraph.h"
|
#include "EdGraph/EdGraph.h"
|
||||||
|
#include "Materials/Material.h"
|
||||||
|
#include "MaterialGraph/MaterialGraph.h"
|
||||||
#include "UMCPHandler_DumpGraphs.generated.h"
|
#include "UMCPHandler_DumpGraphs.generated.h"
|
||||||
|
|
||||||
|
|
||||||
@@ -20,13 +22,13 @@ class UMCPHandler_DumpGraphs : public UObject, public IMCPHandler
|
|||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY(meta=(Description="Path to a blueprint or graph, e.g. /Game/Foo or /Game/Foo,graph:EventGraph"))
|
UPROPERTY(meta=(Description="Path to a blueprint, material, or graph, e.g. /Game/Foo or /Game/Foo,graph:EventGraph"))
|
||||||
FString Path;
|
FString Path;
|
||||||
|
|
||||||
virtual FString GetDescription() const override
|
virtual FString GetDescription() const override
|
||||||
{
|
{
|
||||||
return TEXT("Dump blueprint graphs as readable text. "
|
return TEXT("Dump blueprint or material graphs as readable text. "
|
||||||
"If given a blueprint, dumps all graphs. If given a specific graph, dumps only that one.");
|
"If given a blueprint or material, dumps all graphs. If given a specific graph, dumps only that one.");
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
|
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
|
||||||
@@ -52,7 +54,19 @@ public:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result.Appendf(TEXT("ERROR: Expected a blueprint or graph, got %s\n"),
|
if (UMaterial* Mat = Cast<UMaterial>(F.Obj))
|
||||||
|
{
|
||||||
|
MCPUtils::EnsureMaterialGraph(Mat);
|
||||||
|
if (!Mat->MaterialGraph)
|
||||||
|
{
|
||||||
|
Result.Append(TEXT("ERROR: Could not build MaterialGraph for this material\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
EmitGraph(Mat->MaterialGraph, Result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result.Appendf(TEXT("ERROR: Expected a blueprint, material, or graph, got %s\n"),
|
||||||
*MCPUtils::FormatName(F.Obj->GetClass()));
|
*MCPUtils::FormatName(F.Obj->GetClass()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "CoreMinimal.h"
|
|
||||||
#include "MCPHandler.h"
|
|
||||||
#include "MCPAssetFinder.h"
|
|
||||||
#include "MCPUtils.h"
|
|
||||||
#include "Materials/Material.h"
|
|
||||||
#include "Materials/MaterialExpression.h"
|
|
||||||
#include "MaterialGraph/MaterialGraph.h"
|
|
||||||
#include "MaterialGraph/MaterialGraphNode.h"
|
|
||||||
#include "MaterialGraph/MaterialGraphNode_Root.h"
|
|
||||||
#include "MaterialGraph/MaterialGraphSchema.h"
|
|
||||||
#include "Kismet2/BlueprintEditorUtils.h"
|
|
||||||
#include "EdGraph/EdGraph.h"
|
|
||||||
#include "EdGraph/EdGraphNode.h"
|
|
||||||
#include "UMCPHandler_DumpMaterialExpressionGraph.generated.h"
|
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
UCLASS()
|
|
||||||
class UMCPHandler_DumpMaterialExpressionGraph : public UObject, public IMCPHandler
|
|
||||||
{
|
|
||||||
GENERATED_BODY()
|
|
||||||
|
|
||||||
public:
|
|
||||||
UPROPERTY(meta=(Description="Material name or package path"))
|
|
||||||
FString Material;
|
|
||||||
|
|
||||||
virtual FString GetDescription() const override
|
|
||||||
{
|
|
||||||
return TEXT("Dump the expression graph for a material, showing all nodes and connections as readable text.");
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
|
|
||||||
{
|
|
||||||
MCPAssets<UMaterial> Assets;
|
|
||||||
if (!Assets.Exact(Material).Errors(Result).ENone().ETwo().Load()) return;
|
|
||||||
UMaterial* Mat = Assets.Object();
|
|
||||||
|
|
||||||
// Ensure the material graph is built (it's created lazily by the material editor)
|
|
||||||
MCPUtils::EnsureMaterialGraph(Mat);
|
|
||||||
if (!Mat->MaterialGraph)
|
|
||||||
{
|
|
||||||
Result.Append(TEXT("ERROR: Could not build MaterialGraph for this material\n"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result.Appendf(TEXT("Material: %s\n"), *MCPUtils::FormatName(Mat));
|
|
||||||
Result.Appendf(TEXT("Expressions: %d\n\n"), Mat->GetExpressions().Num());
|
|
||||||
|
|
||||||
UMaterialGraph* Graph = Mat->MaterialGraph;
|
|
||||||
for (UEdGraphNode* Node : Graph->Nodes)
|
|
||||||
{
|
|
||||||
Result.Appendf(TEXT("--- %s ---\n"), *MCPUtils::FormatName(Node));
|
|
||||||
|
|
||||||
// Show the material expression type for material graph nodes
|
|
||||||
if (UMaterialGraphNode* MatNode = Cast<UMaterialGraphNode>(Node))
|
|
||||||
{
|
|
||||||
if (MatNode->MaterialExpression)
|
|
||||||
Result.Appendf(TEXT(" Expression: %s\n"), *MCPUtils::FormatName(MatNode->MaterialExpression));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output pins and their connections
|
|
||||||
for (UEdGraphPin* Pin : Node->Pins)
|
|
||||||
{
|
|
||||||
if (Pin->Direction == EGPD_Output && Pin->LinkedTo.Num() > 0)
|
|
||||||
{
|
|
||||||
for (UEdGraphPin* LinkedPin : Pin->LinkedTo)
|
|
||||||
{
|
|
||||||
Result.Appendf(TEXT(" %s -> %s.%s\n"),
|
|
||||||
*MCPUtils::FormatName(Pin),
|
|
||||||
*MCPUtils::FormatName(LinkedPin->GetOwningNode()),
|
|
||||||
*MCPUtils::FormatName(LinkedPin));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "MCPHandler.h"
|
||||||
|
#include "MCPFetcher.h"
|
||||||
|
#include "MCPUtils.h"
|
||||||
|
#include "UMCPHandler_DumpProperties.generated.h"
|
||||||
|
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
UCLASS()
|
||||||
|
class UMCPHandler_DumpProperties : public UObject, public IMCPHandler
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY(meta=(Description="MCPFetcher path to the object (e.g. /Game/Materials/M_Gold or /Game/Tangibles/TAN_Char,component:Mesh0)"))
|
||||||
|
FString Path;
|
||||||
|
|
||||||
|
UPROPERTY(meta=(Optional, Description="Substring filter for property names"))
|
||||||
|
FString Query;
|
||||||
|
|
||||||
|
UPROPERTY(meta=(Optional, Description="Truncate values to 80 characters (default true)"))
|
||||||
|
bool Truncate = true;
|
||||||
|
|
||||||
|
virtual FString GetDescription() const override
|
||||||
|
{
|
||||||
|
return TEXT("List all blueprint-visible properties on an object resolved via MCPFetcher path, "
|
||||||
|
"showing current values and which properties are editable.");
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
|
||||||
|
{
|
||||||
|
// Resolve the path to an object.
|
||||||
|
MCPFetcher F(Result);
|
||||||
|
UObject* Obj = F.Walk(Path).Cast<UObject>();
|
||||||
|
if (!Obj) return;
|
||||||
|
|
||||||
|
// Get the editable template (e.g. Blueprint → CDO).
|
||||||
|
UObject* Template = MCPUtils::GetEditableTemplate(Obj, Result);
|
||||||
|
if (!Template) return;
|
||||||
|
|
||||||
|
TArray<FProperty*> Props = MCPUtils::BlueprintVisibleProperties(Template, Query);
|
||||||
|
|
||||||
|
UStruct* CurrentOwner = nullptr;
|
||||||
|
for (FProperty* Prop : Props)
|
||||||
|
{
|
||||||
|
FString PropName = MCPUtils::FormatName(Prop);
|
||||||
|
|
||||||
|
// Print section heading when the owning class changes.
|
||||||
|
UStruct* Owner = Prop->GetOwnerStruct();
|
||||||
|
if (Owner != CurrentOwner)
|
||||||
|
{
|
||||||
|
CurrentOwner = Owner;
|
||||||
|
Result.Appendf(TEXT("\nFrom %s:\n\n"), *MCPUtils::FormatName(Owner));
|
||||||
|
}
|
||||||
|
|
||||||
|
FString ValueStr = MCPUtils::GetPropertyValueText(Template, Prop);
|
||||||
|
|
||||||
|
if (Truncate && (ValueStr.Len() > 80))
|
||||||
|
ValueStr = ValueStr.Left(80) + TEXT("...");
|
||||||
|
|
||||||
|
bool bEditable = Prop->HasAnyPropertyFlags(CPF_Edit);
|
||||||
|
Result.Appendf(TEXT(" %s %s %s = %s\n"),
|
||||||
|
bEditable ? TEXT("editable") : TEXT("readonly"),
|
||||||
|
*MCPUtils::FormatPropertyType(Prop),
|
||||||
|
*PropName,
|
||||||
|
*ValueStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Props.IsEmpty())
|
||||||
|
Result.Append(TEXT(" (no blueprint-visible properties found)\n"));
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -20,7 +20,7 @@ class UMCPHandler_ListBlueprintAssets : public UObject, public IMCPHandler
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY(meta=(Optional, Description="Substring filter for blueprint name or path"))
|
UPROPERTY(meta=(Optional, Description="Substring filter for blueprint name or path"))
|
||||||
FString Filter;
|
FString Query;
|
||||||
|
|
||||||
UPROPERTY(meta=(Optional, Description="Filter by parent class name (exact match, case-insensitive)"))
|
UPROPERTY(meta=(Optional, Description="Filter by parent class name (exact match, case-insensitive)"))
|
||||||
FString ParentClass;
|
FString ParentClass;
|
||||||
@@ -39,7 +39,7 @@ public:
|
|||||||
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
|
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
|
||||||
{
|
{
|
||||||
MCPAssets<UObject> Assets;
|
MCPAssets<UObject> Assets;
|
||||||
Assets.NoScans().Substring(Filter).Limit(500).Errors(Result);
|
Assets.NoScans().Substring(Query).Limit(500).Errors(Result);
|
||||||
if (IncludeRegular) Assets.Scan<UBlueprint>();
|
if (IncludeRegular) Assets.Scan<UBlueprint>();
|
||||||
if (IncludeLevel) Assets.Scan<UWorld>();
|
if (IncludeLevel) Assets.Scan<UWorld>();
|
||||||
Assets.Info();
|
Assets.Info();
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public:
|
|||||||
FString ClassName;
|
FString ClassName;
|
||||||
|
|
||||||
UPROPERTY(meta=(Optional, Description="Substring filter for property names"))
|
UPROPERTY(meta=(Optional, Description="Substring filter for property names"))
|
||||||
FString Filter;
|
FString Query;
|
||||||
|
|
||||||
virtual FString GetDescription() const override
|
virtual FString GetDescription() const override
|
||||||
{
|
{
|
||||||
@@ -46,7 +46,7 @@ public:
|
|||||||
|
|
||||||
FString PropName = Prop->GetName();
|
FString PropName = Prop->GetName();
|
||||||
|
|
||||||
if (!Filter.IsEmpty() && !PropName.Contains(Filter, ESearchCase::IgnoreCase))
|
if (!Query.IsEmpty() && !PropName.Contains(Query, ESearchCase::IgnoreCase))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Build compact flags string
|
// Build compact flags string
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "MCPHandler.h"
|
||||||
|
#include "Subsystems/AssetEditorSubsystem.h"
|
||||||
|
#include "UMCPHandler_ListOpenAssetEditors.generated.h"
|
||||||
|
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
UCLASS()
|
||||||
|
class UMCPHandler_ListOpenAssetEditors : public UObject, public IMCPHandler
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual FString GetDescription() const override
|
||||||
|
{
|
||||||
|
return TEXT("List all currently open asset editors, showing which has focus and whether they have unsaved changes.");
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
|
||||||
|
{
|
||||||
|
UAssetEditorSubsystem* Sub = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
|
||||||
|
if (!Sub)
|
||||||
|
{
|
||||||
|
Result.Append(TEXT("Error: AssetEditorSubsystem not available\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<UObject*> EditedAssets = Sub->GetAllEditedAssets();
|
||||||
|
if (EditedAssets.IsEmpty())
|
||||||
|
{
|
||||||
|
Result.Append(TEXT("No asset editors are open.\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (UObject* Asset : EditedAssets)
|
||||||
|
{
|
||||||
|
bool bDirty = Asset->GetOutermost()->IsDirty();
|
||||||
|
|
||||||
|
Result.Appendf(TEXT(" %s%s\n"),
|
||||||
|
bDirty ? TEXT("[unsaved] ") : TEXT(""),
|
||||||
|
*Asset->GetPathName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "MCPHandler.h"
|
||||||
|
#include "MCPFetcher.h"
|
||||||
|
#include "Subsystems/AssetEditorSubsystem.h"
|
||||||
|
#include "UMCPHandler_OpenAssetEditor.generated.h"
|
||||||
|
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
UCLASS()
|
||||||
|
class UMCPHandler_OpenAssetEditor : public UObject, public IMCPHandler
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY(meta=(Description="MCPFetcher path to the asset to open (e.g. /Game/Materials/M_Gold)"))
|
||||||
|
FString Path;
|
||||||
|
|
||||||
|
virtual FString GetDescription() const override
|
||||||
|
{
|
||||||
|
return TEXT("Open an asset in its editor and bring it to focus.");
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
|
||||||
|
{
|
||||||
|
MCPFetcher F(Result);
|
||||||
|
UObject* Obj = F.Walk(Path).Cast<UObject>();
|
||||||
|
if (!Obj) return;
|
||||||
|
|
||||||
|
UAssetEditorSubsystem* Sub = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
|
||||||
|
if (!Sub)
|
||||||
|
{
|
||||||
|
Result.Append(TEXT("Error: AssetEditorSubsystem not available\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Sub->OpenEditorForAsset(Obj))
|
||||||
|
Result.Appendf(TEXT("Opened editor for %s\n"), *Obj->GetPathName());
|
||||||
|
else
|
||||||
|
Result.Appendf(TEXT("Error: Could not open editor for %s\n"), *Obj->GetPathName());
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -18,7 +18,7 @@ class UMCPHandler_SearchAssets : public UObject, public IMCPHandler
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY(meta=(Optional, Description="Substring to match against asset package paths"))
|
UPROPERTY(meta=(Optional, Description="Substring to match against asset package paths"))
|
||||||
FString Search;
|
FString Query;
|
||||||
|
|
||||||
UPROPERTY(meta=(Optional, Description="Asset class name to filter by, e.g. Blueprint, Material, StaticMesh"))
|
UPROPERTY(meta=(Optional, Description="Asset class name to filter by, e.g. Blueprint, Material, StaticMesh"))
|
||||||
FString Type;
|
FString Type;
|
||||||
@@ -28,14 +28,14 @@ public:
|
|||||||
|
|
||||||
virtual FString GetDescription() const override
|
virtual FString GetDescription() const override
|
||||||
{
|
{
|
||||||
return TEXT("Search for assets by name and/or type. At least one of Search or Type must be specified.");
|
return TEXT("Search for assets by name and/or type. At least one of Query or Type must be specified.");
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
|
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
|
||||||
{
|
{
|
||||||
if (Search.IsEmpty() && Type.IsEmpty())
|
if (Query.IsEmpty() && Type.IsEmpty())
|
||||||
{
|
{
|
||||||
Result.Append(TEXT("ERROR: At least one of Search or Type must be specified\n"));
|
Result.Append(TEXT("ERROR: At least one of Query or Type must be specified\n"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,9 +53,9 @@ public:
|
|||||||
Assets.NoScans().Scan(TypeClass);
|
Assets.NoScans().Scan(TypeClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Search.IsEmpty())
|
if (!Query.IsEmpty())
|
||||||
{
|
{
|
||||||
Assets.Substring(Search);
|
Assets.Substring(Query);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assets.AllContent().Limit(Limit).Errors(Result).Info();
|
Assets.AllContent().Limit(Limit).Errors(Result).Info();
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "MCPHandler.h"
|
#include "MCPHandler.h"
|
||||||
#include "MCPAssetFinder.h"
|
|
||||||
#include "MCPFetcher.h"
|
#include "MCPFetcher.h"
|
||||||
#include "MCPUtils.h"
|
#include "MCPUtils.h"
|
||||||
#include "EdGraph/EdGraph.h"
|
#include "EdGraph/EdGraph.h"
|
||||||
|
#include "EdGraph/EdGraphSchema.h"
|
||||||
#include "UMCPHandler_SearchSpawnableNodeTypes.generated.h"
|
#include "UMCPHandler_SearchSpawnableNodeTypes.generated.h"
|
||||||
|
|
||||||
|
|
||||||
@@ -25,50 +25,36 @@ public:
|
|||||||
UPROPERTY(meta=(Optional, Description="Maximum number of results (default 50, max 500)"))
|
UPROPERTY(meta=(Optional, Description="Maximum number of results (default 50, max 500)"))
|
||||||
int32 MaxResults = 50;
|
int32 MaxResults = 50;
|
||||||
|
|
||||||
UPROPERTY(meta=(Optional, Description="Blueprint path. If specified with Graph, only returns nodes compatible with that graph."))
|
UPROPERTY(meta=(Description="MCPFetcher path to a graph, e.g. /Game/Foo,graph:EventGraph or /Game/Materials/M_Gold,graph:"))
|
||||||
FString Blueprint;
|
|
||||||
|
|
||||||
UPROPERTY(meta=(Optional, Description="Graph name to filter by compatibility. Requires Blueprint."))
|
|
||||||
FString Graph;
|
FString Graph;
|
||||||
|
|
||||||
virtual FString GetDescription() const override
|
virtual FString GetDescription() const override
|
||||||
{
|
{
|
||||||
return TEXT("Search the Blueprint action database for node spawners matching a query. "
|
return TEXT("Search the action database for node types that can be spawned in a graph. "
|
||||||
"Returns full action names for use with spawn_node. "
|
"Works with any graph type (Blueprint, Material, etc.). "
|
||||||
"Optionally filter by blueprint+graph to only show compatible node types.");
|
"Returns full action names for use with SpawnNodesInGraph.");
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
|
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
|
||||||
{
|
{
|
||||||
int32 ClampedMax = FMath::Clamp(MaxResults, 1, 500);
|
int32 ClampedMax = FMath::Clamp(MaxResults, 1, 500);
|
||||||
|
|
||||||
// Optionally resolve a graph to filter by compatibility
|
MCPFetcher F(Result);
|
||||||
UEdGraph* GraphFilter = nullptr;
|
UEdGraph* TargetGraph = F.Walk(Graph).Cast<UEdGraph>();
|
||||||
if (!Blueprint.IsEmpty() && !Graph.IsEmpty())
|
if (!TargetGraph) return;
|
||||||
|
|
||||||
|
TArray<TSharedPtr<FEdGraphSchemaAction>> Actions = MCPUtils::SearchGraphActions(TargetGraph, Query, ClampedMax, /*ExactMatch=*/false);
|
||||||
|
|
||||||
|
for (const TSharedPtr<FEdGraphSchemaAction>& Action : Actions)
|
||||||
{
|
{
|
||||||
MCPFetcher F(Result);
|
Result.Appendf(TEXT("%s\n"), *MCPUtils::ActionFullName(Action));
|
||||||
F.Walk(Blueprint).Graph(Graph);
|
|
||||||
if (!F.Ok()) return;
|
|
||||||
GraphFilter = Cast<UEdGraph>(F.Obj);
|
|
||||||
if (!GraphFilter)
|
|
||||||
{
|
|
||||||
Result.Appendf(TEXT("ERROR: '%s' is not a graph\n"), *Graph);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TArray<UBlueprintNodeSpawner*> Spawners = MCPUtils::SearchNodeSpawners(Query, ClampedMax, /*ExactMatch=*/false, GraphFilter);
|
if (Actions.Num() == 0)
|
||||||
|
|
||||||
for (UBlueprintNodeSpawner* Spawner : Spawners)
|
|
||||||
{
|
|
||||||
Result.Appendf(TEXT("%s\n"), *MCPUtils::NodeSpawnerFullName(Spawner));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Spawners.Num() == 0)
|
|
||||||
{
|
{
|
||||||
Result.Append(TEXT("No matching node types found.\n"));
|
Result.Append(TEXT("No matching node types found.\n"));
|
||||||
}
|
}
|
||||||
else if (Spawners.Num() >= ClampedMax)
|
else if (Actions.Num() >= ClampedMax)
|
||||||
{
|
{
|
||||||
Result.Appendf(TEXT("WARNING: Reached limit of %d results. Refine your query or increase MaxResults.\n"), ClampedMax);
|
Result.Appendf(TEXT("WARNING: Reached limit of %d results. Refine your query or increase MaxResults.\n"), ClampedMax);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ public:
|
|||||||
FString TypeName;
|
FString TypeName;
|
||||||
|
|
||||||
UPROPERTY(meta=(Optional, Description="Filter to blueprints whose name or path contains this substring"))
|
UPROPERTY(meta=(Optional, Description="Filter to blueprints whose name or path contains this substring"))
|
||||||
FString Filter;
|
FString Query;
|
||||||
|
|
||||||
UPROPERTY(meta=(Optional, Description="Maximum number of results to return (default 200, max 500)"))
|
UPROPERTY(meta=(Optional, Description="Maximum number of results to return (default 200, max 500)"))
|
||||||
int32 MaxResults = 0;
|
int32 MaxResults = 0;
|
||||||
@@ -54,7 +54,7 @@ public:
|
|||||||
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
|
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
|
||||||
{
|
{
|
||||||
FString DecodedTypeName = MCPUtils::UrlDecode(TypeName);
|
FString DecodedTypeName = MCPUtils::UrlDecode(TypeName);
|
||||||
FString FilterStr = Filter.IsEmpty() ? FString() : MCPUtils::UrlDecode(Filter);
|
FString FilterStr = Query.IsEmpty() ? FString() : MCPUtils::UrlDecode(Query);
|
||||||
|
|
||||||
int32 EffectiveMaxResults = (MaxResults > 0) ? FMath::Clamp(MaxResults, 1, 500) : 200;
|
int32 EffectiveMaxResults = (MaxResults > 0) ? FMath::Clamp(MaxResults, 1, 500) : 200;
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class UMCPHandler_SearchUnrealClasses : public UObject, public IMCPHandler
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY(meta=(Optional, Description="Substring filter for class names"))
|
UPROPERTY(meta=(Optional, Description="Substring filter for class names"))
|
||||||
FString Filter;
|
FString Query;
|
||||||
|
|
||||||
UPROPERTY(meta=(Optional, Description="Parent class name to restrict results to subclasses"))
|
UPROPERTY(meta=(Optional, Description="Parent class name to restrict results to subclasses"))
|
||||||
FString ParentClass;
|
FString ParentClass;
|
||||||
@@ -69,7 +69,7 @@ public:
|
|||||||
if (ParentClassObj && !Class->IsChildOf(ParentClassObj)) continue;
|
if (ParentClassObj && !Class->IsChildOf(ParentClassObj)) continue;
|
||||||
|
|
||||||
FString ClassName = MCPUtils::FormatName(Class);
|
FString ClassName = MCPUtils::FormatName(Class);
|
||||||
if (!Filter.IsEmpty() && !ClassName.Contains(Filter, ESearchCase::IgnoreCase)) continue;
|
if (!Query.IsEmpty() && !ClassName.Contains(Query, ESearchCase::IgnoreCase)) continue;
|
||||||
|
|
||||||
TotalMatched++;
|
TotalMatched++;
|
||||||
if (Matches.Num() < Limit)
|
if (Matches.Num() < Limit)
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "MCPHandler.h"
|
||||||
|
#include "MCPFetcher.h"
|
||||||
|
#include "MCPUtils.h"
|
||||||
|
#include "UMCPHandler_SetProperties.generated.h"
|
||||||
|
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
UCLASS()
|
||||||
|
class UMCPHandler_SetProperties : public UObject, public IMCPHandler
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY(meta=(Description="MCPFetcher path to the object (e.g. /Game/Materials/M_Gold or /Game/Tangibles/TAN_Char,component:Mesh0)"))
|
||||||
|
FString Path;
|
||||||
|
|
||||||
|
UPROPERTY(meta=(Description="Object mapping property names to new values in Unreal text format"))
|
||||||
|
FMCPJsonObject Properties;
|
||||||
|
|
||||||
|
virtual FString GetDescription() const override
|
||||||
|
{
|
||||||
|
return TEXT("Set one or more editable properties on an object resolved via MCPFetcher path. "
|
||||||
|
"Properties is a JSON object like {\"TwoSided\": \"true\", \"BlendMode\": \"BLEND_Translucent\"}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
|
||||||
|
{
|
||||||
|
// Resolve the path to an object.
|
||||||
|
MCPFetcher F(Result);
|
||||||
|
UObject* Obj = F.Walk(Path).Cast<UObject>();
|
||||||
|
if (!Obj) return;
|
||||||
|
|
||||||
|
// Get the editable template (e.g. Blueprint → CDO).
|
||||||
|
UObject* Template = MCPUtils::GetEditableTemplate(Obj, Result);
|
||||||
|
if (!Template) return;
|
||||||
|
|
||||||
|
if (!Properties.Json || Properties.Json->Values.Num() == 0)
|
||||||
|
{
|
||||||
|
Result.Append(TEXT("Error: No properties specified\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validation pass — check all properties before modifying anything.
|
||||||
|
for (const auto& Pair : Properties.Json->Values)
|
||||||
|
{
|
||||||
|
FProperty* Prop = MCPUtils::FindPropertyByName(Template, Pair.Key, Result);
|
||||||
|
if (!Prop) return;
|
||||||
|
|
||||||
|
if (!Prop->HasAnyPropertyFlags(CPF_Edit))
|
||||||
|
{
|
||||||
|
Result.Appendf(TEXT("Error: Property '%s' is not editable (no Edit flag)\n"), *Pair.Key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FString ValueStr;
|
||||||
|
if (!Pair.Value->TryGetString(ValueStr))
|
||||||
|
{
|
||||||
|
Result.Appendf(TEXT("Error: Value for '%s' must be a string\n"), *Pair.Key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply all changes in a single Pre/PostEditChange bracket.
|
||||||
|
Template->PreEditChange(nullptr);
|
||||||
|
|
||||||
|
int32 SuccessCount = 0;
|
||||||
|
for (const auto& Pair : Properties.Json->Values)
|
||||||
|
{
|
||||||
|
FProperty* Prop = MCPUtils::FindPropertyByName(Template, Pair.Key);
|
||||||
|
FString ValueStr;
|
||||||
|
Pair.Value->TryGetString(ValueStr);
|
||||||
|
|
||||||
|
FString OldValue = MCPUtils::GetPropertyValueText(Template, Prop);
|
||||||
|
|
||||||
|
if (!MCPUtils::SetPropertyValueText(Template, Prop, ValueStr, Result))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
FString NewValue = MCPUtils::GetPropertyValueText(Template, Prop);
|
||||||
|
Result.Appendf(TEXT("%s: %s -> %s\n"), *MCPUtils::FormatName(Prop), *OldValue, *NewValue);
|
||||||
|
SuccessCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Template->PostEditChange();
|
||||||
|
|
||||||
|
// Save.
|
||||||
|
Template->MarkPackageDirty();
|
||||||
|
bool bSaved = MCPUtils::SaveGenericPackage(Obj);
|
||||||
|
|
||||||
|
Result.Appendf(TEXT("Set %d/%d properties.\n"), SuccessCount, Properties.Json->Values.Num());
|
||||||
|
if (!bSaved)
|
||||||
|
Result.Append(TEXT("Warning: Save failed\n"));
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -11,6 +11,9 @@ class UMCPHandler_ShowCommands : public UObject, public IMCPHandler
|
|||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
UPROPERTY(meta=(Optional, Description="Substring filter for command names"))
|
||||||
|
FString Query;
|
||||||
|
|
||||||
UPROPERTY(meta=(Optional, Description="If true, return full details including parameter types and descriptions"))
|
UPROPERTY(meta=(Optional, Description="If true, return full details including parameter types and descriptions"))
|
||||||
bool Verbose = false;
|
bool Verbose = false;
|
||||||
|
|
||||||
@@ -21,8 +24,14 @@ public:
|
|||||||
|
|
||||||
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
|
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
|
||||||
{
|
{
|
||||||
|
FString QueryLower = Query.ToLower();
|
||||||
|
|
||||||
for (UClass* Class : MCPUtils::CollectHandlerClasses())
|
for (UClass* Class : MCPUtils::CollectHandlerClasses())
|
||||||
{
|
{
|
||||||
|
FString ToolName = MCPUtils::GetToolName(Class);
|
||||||
|
if (!ToolName.ToLower().Contains(QueryLower))
|
||||||
|
continue;
|
||||||
|
|
||||||
if (Verbose)
|
if (Verbose)
|
||||||
{
|
{
|
||||||
MCPUtils::FormatCommandHelp(Class, Result);
|
MCPUtils::FormatCommandHelp(Class, Result);
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
#include "Engine/Blueprint.h"
|
#include "Engine/Blueprint.h"
|
||||||
#include "EdGraph/EdGraph.h"
|
#include "EdGraph/EdGraph.h"
|
||||||
#include "EdGraph/EdGraphNode.h"
|
#include "EdGraph/EdGraphNode.h"
|
||||||
#include "BlueprintNodeSpawner.h"
|
#include "EdGraph/EdGraphSchema.h"
|
||||||
#include "Kismet2/BlueprintEditorUtils.h"
|
#include "Kismet2/BlueprintEditorUtils.h"
|
||||||
#include "UMCPHandler_SpawnNodesInGraph.generated.h"
|
#include "UMCPHandler_SpawnNodes.generated.h"
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -33,7 +33,7 @@ struct FSpawnNodeEntry
|
|||||||
|
|
||||||
|
|
||||||
UCLASS()
|
UCLASS()
|
||||||
class UMCPHandler_SpawnNodesInGraph : public UObject, public IMCPHandler
|
class UMCPHandler_SpawnNodes : public UObject, public IMCPHandler
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
@@ -41,14 +41,14 @@ public:
|
|||||||
UPROPERTY(meta=(Description="Path to a graph, e.g. /Game/Foo,graph:EventGraph"))
|
UPROPERTY(meta=(Description="Path to a graph, e.g. /Game/Foo,graph:EventGraph"))
|
||||||
FString Graph;
|
FString Graph;
|
||||||
|
|
||||||
UPROPERTY(meta=(Description="Array of {actionName, posX, posY} objects. Use search_spawnable_node_types to find action names."))
|
UPROPERTY(meta=(Description="Array of {actionName, posX, posY} objects. Use SearchSpawnableNodeTypes to find action names."))
|
||||||
FMCPJsonArray Nodes;
|
FMCPJsonArray Nodes;
|
||||||
|
|
||||||
virtual FString GetDescription() const override
|
virtual FString GetDescription() const override
|
||||||
{
|
{
|
||||||
return TEXT("Create nodes in a Blueprint graph using the editor's action database. "
|
return TEXT("Create nodes in any graph (Blueprint, Material, etc.) using the editor's action database. "
|
||||||
"Can create ANY node type that appears in the editor's right-click menu, including custom K2 nodes. "
|
"Can create ANY node type that appears in the editor's right-click menu. "
|
||||||
"Use search_spawnable_node_types first to find the exact action name.");
|
"Use SearchSpawnableNodeTypes first to find the exact action name.");
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
|
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
|
||||||
@@ -66,28 +66,27 @@ public:
|
|||||||
if (!MCPUtils::PopulateFromJson(FSpawnNodeEntry::StaticStruct(), &Entry, NodeVal, Result))
|
if (!MCPUtils::PopulateFromJson(FSpawnNodeEntry::StaticStruct(), &Entry, NodeVal, Result))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Find the spawner by exact full name
|
// Find the action by exact full name
|
||||||
TArray<UBlueprintNodeSpawner*> Matches = MCPUtils::SearchNodeSpawners(Entry.ActionName, 0, /*ExactMatch=*/true, TargetGraph);
|
TArray<TSharedPtr<FEdGraphSchemaAction>> Matches = MCPUtils::SearchGraphActions(TargetGraph, Entry.ActionName, 0, /*ExactMatch=*/true);
|
||||||
if (Matches.Num() == 0)
|
if (Matches.Num() == 0)
|
||||||
{
|
{
|
||||||
Result.Appendf(TEXT("ERROR: No action found matching '%s'. Use search_spawnable_node_types to find available actions.\n"),
|
Result.Appendf(TEXT("ERROR: No action found matching '%s'. Use SearchSpawnableNodeTypes to find available actions.\n"),
|
||||||
*Entry.ActionName);
|
*Entry.ActionName);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (Matches.Num() > 1)
|
if (Matches.Num() > 1)
|
||||||
{
|
{
|
||||||
Result.Appendf(TEXT("ERROR: Ambiguous: %d spawners match '%s'.\n"),
|
Result.Appendf(TEXT("ERROR: Ambiguous: %d actions match '%s'.\n"),
|
||||||
Matches.Num(), *Entry.ActionName);
|
Matches.Num(), *Entry.ActionName);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invoke the spawner
|
// Perform the action
|
||||||
FVector2D Location(Entry.PosX, Entry.PosY);
|
FVector2D Location(Entry.PosX, Entry.PosY);
|
||||||
IBlueprintNodeBinder::FBindingSet Bindings;
|
UEdGraphNode* NewNode = Matches[0]->PerformAction(TargetGraph, nullptr, Location, /*bSelectNewNode=*/false);
|
||||||
UEdGraphNode* NewNode = Matches[0]->Invoke(TargetGraph, Bindings, Location);
|
|
||||||
if (!NewNode)
|
if (!NewNode)
|
||||||
{
|
{
|
||||||
Result.Appendf(TEXT("ERROR: Spawner Invoke() returned null for '%s'.\n"), *Entry.ActionName);
|
Result.Appendf(TEXT("ERROR: PerformAction returned null for '%s'.\n"), *Entry.ActionName);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,10 +98,17 @@ public:
|
|||||||
SuccessCount++;
|
SuccessCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mark the owning asset as modified
|
||||||
UBlueprint* BP = Cast<UBlueprint>(TargetGraph->GetOuter());
|
UBlueprint* BP = Cast<UBlueprint>(TargetGraph->GetOuter());
|
||||||
if (BP)
|
if (BP)
|
||||||
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
|
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
|
||||||
|
|
||||||
|
TargetGraph->NotifyGraphChanged();
|
||||||
|
|
||||||
|
UObject* Outer = TargetGraph->GetOuter();
|
||||||
|
if (Outer)
|
||||||
|
Outer->MarkPackageDirty();
|
||||||
|
|
||||||
Result.Appendf(TEXT("Spawned %d/%d nodes.\n"), SuccessCount, TotalCount);
|
Result.Appendf(TEXT("Spawned %d/%d nodes.\n"), SuccessCount, TotalCount);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "Engine/World.h"
|
#include "Engine/World.h"
|
||||||
#include "Engine/Level.h"
|
#include "Engine/Level.h"
|
||||||
#include "Engine/LevelScriptBlueprint.h"
|
#include "Engine/LevelScriptBlueprint.h"
|
||||||
|
#include "Materials/Material.h"
|
||||||
#include "MCPUtils.h"
|
#include "MCPUtils.h"
|
||||||
#include "AssetRegistry/AssetRegistryModule.h"
|
#include "AssetRegistry/AssetRegistryModule.h"
|
||||||
#include "AssetRegistry/IAssetRegistry.h"
|
#include "AssetRegistry/IAssetRegistry.h"
|
||||||
@@ -101,11 +102,14 @@ bool MCPAssetsBase::Load()
|
|||||||
for (const FAssetData &Asset : AssetsToLoad)
|
for (const FAssetData &Asset : AssetsToLoad)
|
||||||
{
|
{
|
||||||
UObject *Obj = TryLoadAsset(Asset);
|
UObject *Obj = TryLoadAsset(Asset);
|
||||||
if (Obj != nullptr)
|
if (!Obj) continue;
|
||||||
{
|
|
||||||
AssetResults.Add(Asset);
|
// If this is a material open in the editor, use the editor's transient copy.
|
||||||
UObjectResults.Add(Obj);
|
if (UMaterial* Mat = Cast<UMaterial>(Obj))
|
||||||
}
|
Obj = MCPUtils::ReplaceMaterialWithTransientCopy(Mat);
|
||||||
|
|
||||||
|
AssetResults.Add(Asset);
|
||||||
|
UObjectResults.Add(Obj);
|
||||||
}
|
}
|
||||||
if (bErrorIfNone && AssetResults.IsEmpty())
|
if (bErrorIfNone && AssetResults.IsEmpty())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
#include "Engine/SimpleConstructionScript.h"
|
#include "Engine/SimpleConstructionScript.h"
|
||||||
#include "Engine/SCS_Node.h"
|
#include "Engine/SCS_Node.h"
|
||||||
#include "Engine/World.h"
|
#include "Engine/World.h"
|
||||||
|
#include "Materials/Material.h"
|
||||||
|
#include "MaterialGraph/MaterialGraph.h"
|
||||||
#include "Engine/LevelScriptBlueprint.h"
|
#include "Engine/LevelScriptBlueprint.h"
|
||||||
|
|
||||||
MCPFetcher& MCPFetcher::SetError(const FString& Msg)
|
MCPFetcher& MCPFetcher::SetError(const FString& Msg)
|
||||||
@@ -55,7 +57,8 @@ MCPFetcher& MCPFetcher::Walk(const FString& Path)
|
|||||||
for (int32 i = Start; i < Segments.Num(); i++)
|
for (int32 i = Start; i < Segments.Num(); i++)
|
||||||
{
|
{
|
||||||
FString Key, Value;
|
FString Key, Value;
|
||||||
Segments[i].Split(TEXT(":"), &Key, &Value);
|
if (!Segments[i].Split(TEXT(":"), &Key, &Value))
|
||||||
|
Key = Segments[i];
|
||||||
|
|
||||||
if (StrEq(Key, TEXT("graph"))) Graph(Value);
|
if (StrEq(Key, TEXT("graph"))) Graph(Value);
|
||||||
else if (StrEq(Key, TEXT("node"))) Node(Value);
|
else if (StrEq(Key, TEXT("node"))) Node(Value);
|
||||||
@@ -80,15 +83,31 @@ void MCPFetcher::LoadUAsset(const FString& PackagePath)
|
|||||||
SetObj(LoadObject<UObject>(nullptr, *PackagePath));
|
SetObj(LoadObject<UObject>(nullptr, *PackagePath));
|
||||||
if (!Obj)
|
if (!Obj)
|
||||||
SetError(FString::Printf(TEXT("Could not load asset '%s'"), *PackagePath));
|
SetError(FString::Printf(TEXT("Could not load asset '%s'"), *PackagePath));
|
||||||
|
|
||||||
|
// If this is a material open in the editor, use the editor's transient copy.
|
||||||
|
if (UMaterial* Mat = ::Cast<UMaterial>(Obj))
|
||||||
|
SetObj(MCPUtils::ReplaceMaterialWithTransientCopy(Mat));
|
||||||
}
|
}
|
||||||
|
|
||||||
MCPFetcher& MCPFetcher::Graph(const FString& Value)
|
MCPFetcher& MCPFetcher::Graph(const FString& Value)
|
||||||
{
|
{
|
||||||
if (bError) return *this;
|
if (bError) return *this;
|
||||||
|
|
||||||
|
// Material with blank graph name → navigate to the material graph.
|
||||||
|
if (UMaterial* Mat = ::Cast<UMaterial>(Obj))
|
||||||
|
{
|
||||||
|
if (!Value.IsEmpty())
|
||||||
|
return SetError(FString::Printf(TEXT("Materials do not have named graphs (got '%s')"), *Value));
|
||||||
|
MCPUtils::EnsureMaterialGraph(Mat);
|
||||||
|
if (!Mat->MaterialGraph)
|
||||||
|
return SetError(FString::Printf(TEXT("Material '%s' has no material graph"), *Mat->GetName()));
|
||||||
|
SetObj(Mat->MaterialGraph);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
UBlueprint* BP = ::Cast<UBlueprint>(Obj);
|
UBlueprint* BP = ::Cast<UBlueprint>(Obj);
|
||||||
if (!BP)
|
if (!BP)
|
||||||
return TypeMismatch(TEXT("graph"), TEXT("Blueprint"));
|
return TypeMismatch(TEXT("graph"), TEXT("Blueprint or Material"));
|
||||||
|
|
||||||
TArray<UEdGraph*> Matches = MCPUtils::AllGraphsNamed(BP, Value);
|
TArray<UEdGraph*> Matches = MCPUtils::AllGraphsNamed(BP, Value);
|
||||||
if (Matches.Num() == 0)
|
if (Matches.Num() == 0)
|
||||||
|
|||||||
@@ -34,9 +34,9 @@
|
|||||||
#include "Handlers/UMCPHandler_DisconnectPins.h"
|
#include "Handlers/UMCPHandler_DisconnectPins.h"
|
||||||
#include "Handlers/UMCPHandler_DisconnectMaterialExpressionPin.h"
|
#include "Handlers/UMCPHandler_DisconnectMaterialExpressionPin.h"
|
||||||
#include "Handlers/UMCPHandler_DumpBlueprint.h"
|
#include "Handlers/UMCPHandler_DumpBlueprint.h"
|
||||||
|
#include "Handlers/UMCPHandler_DumpProperties.h"
|
||||||
#include "Handlers/UMCPHandler_DumpGraphs.h"
|
#include "Handlers/UMCPHandler_DumpGraphs.h"
|
||||||
#include "Handlers/UMCPHandler_DumpMaterial.h"
|
#include "Handlers/UMCPHandler_DumpMaterial.h"
|
||||||
#include "Handlers/UMCPHandler_DumpMaterialExpressionGraph.h"
|
|
||||||
#include "Handlers/UMCPHandler_DumpMaterialFunction.h"
|
#include "Handlers/UMCPHandler_DumpMaterialFunction.h"
|
||||||
#include "Handlers/UMCPHandler_DumpMaterialInstanceParameters.h"
|
#include "Handlers/UMCPHandler_DumpMaterialInstanceParameters.h"
|
||||||
#include "Handlers/UMCPHandler_DuplicateNodesInGraph.h"
|
#include "Handlers/UMCPHandler_DuplicateNodesInGraph.h"
|
||||||
@@ -47,10 +47,12 @@
|
|||||||
#include "Handlers/UMCPHandler_ListAnimSlotNames.h"
|
#include "Handlers/UMCPHandler_ListAnimSlotNames.h"
|
||||||
#include "Handlers/UMCPHandler_ListAnimSyncGroups.h"
|
#include "Handlers/UMCPHandler_ListAnimSyncGroups.h"
|
||||||
#include "Handlers/UMCPHandler_ListBlueprintAssets.h"
|
#include "Handlers/UMCPHandler_ListBlueprintAssets.h"
|
||||||
|
#include "Handlers/UMCPHandler_ListOpenAssetEditors.h"
|
||||||
#include "Handlers/UMCPHandler_ListBlueprintComponents.h"
|
#include "Handlers/UMCPHandler_ListBlueprintComponents.h"
|
||||||
#include "Handlers/UMCPHandler_ListBlueprintInterfaces.h"
|
#include "Handlers/UMCPHandler_ListBlueprintInterfaces.h"
|
||||||
#include "Handlers/UMCPHandler_ListClassProperties.h"
|
#include "Handlers/UMCPHandler_ListClassProperties.h"
|
||||||
#include "Handlers/UMCPHandler_ListEventDispatchers.h"
|
#include "Handlers/UMCPHandler_ListEventDispatchers.h"
|
||||||
|
#include "Handlers/UMCPHandler_OpenAssetEditor.h"
|
||||||
#include "Handlers/UMCPHandler_RefreshAllNodesInGraph.h"
|
#include "Handlers/UMCPHandler_RefreshAllNodesInGraph.h"
|
||||||
#include "Handlers/UMCPHandler_RemoveAnimStateFromMachine.h"
|
#include "Handlers/UMCPHandler_RemoveAnimStateFromMachine.h"
|
||||||
#include "Handlers/UMCPHandler_RemoveBlueprintComponent.h"
|
#include "Handlers/UMCPHandler_RemoveBlueprintComponent.h"
|
||||||
@@ -81,7 +83,8 @@
|
|||||||
#include "Handlers/UMCPHandler_SetMaterialInstanceParameter.h"
|
#include "Handlers/UMCPHandler_SetMaterialInstanceParameter.h"
|
||||||
#include "Handlers/UMCPHandler_SetMaterialProperty.h"
|
#include "Handlers/UMCPHandler_SetMaterialProperty.h"
|
||||||
#include "Handlers/UMCPHandler_SetNodeComment.h"
|
#include "Handlers/UMCPHandler_SetNodeComment.h"
|
||||||
|
#include "Handlers/UMCPHandler_SetProperties.h"
|
||||||
#include "Handlers/UMCPHandler_SetNodePositions.h"
|
#include "Handlers/UMCPHandler_SetNodePositions.h"
|
||||||
#include "Handlers/UMCPHandler_SetPinDefaultValues.h"
|
#include "Handlers/UMCPHandler_SetPinDefaultValues.h"
|
||||||
#include "Handlers/UMCPHandler_ShowCommands.h"
|
#include "Handlers/UMCPHandler_ShowCommands.h"
|
||||||
#include "Handlers/UMCPHandler_SpawnNodesInGraph.h"
|
#include "Handlers/UMCPHandler_SpawnNodes.h"
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
#include "MCPUtils.h"
|
#include "MCPUtils.h"
|
||||||
#include "MCPHandler.h"
|
#include "MCPHandler.h"
|
||||||
#include "BlueprintActionDatabase.h"
|
|
||||||
#include "BlueprintNodeSpawner.h"
|
|
||||||
#include "Dom/JsonValue.h"
|
#include "Dom/JsonValue.h"
|
||||||
#include "Serialization/JsonReader.h"
|
#include "Serialization/JsonReader.h"
|
||||||
#include "Serialization/JsonWriter.h"
|
#include "Serialization/JsonWriter.h"
|
||||||
@@ -73,6 +71,8 @@
|
|||||||
#include "MaterialGraph/MaterialGraph.h"
|
#include "MaterialGraph/MaterialGraph.h"
|
||||||
#include "MaterialGraph/MaterialGraphNode.h"
|
#include "MaterialGraph/MaterialGraphNode.h"
|
||||||
#include "MaterialGraph/MaterialGraphSchema.h"
|
#include "MaterialGraph/MaterialGraphSchema.h"
|
||||||
|
#include "IMaterialEditor.h"
|
||||||
|
#include "Subsystems/AssetEditorSubsystem.h"
|
||||||
|
|
||||||
// Mesh, animation, texture support
|
// Mesh, animation, texture support
|
||||||
#include "Engine/StaticMesh.h"
|
#include "Engine/StaticMesh.h"
|
||||||
@@ -102,7 +102,7 @@ MCPErrorCallback::MCPErrorCallback(FString& OutError)
|
|||||||
{}
|
{}
|
||||||
|
|
||||||
MCPErrorCallback::MCPErrorCallback(FStringBuilderBase& OutResult)
|
MCPErrorCallback::MCPErrorCallback(FStringBuilderBase& OutResult)
|
||||||
: Func([&OutResult](const FString& Msg) { OutResult.Reset(); OutResult.Appendf(TEXT("ERROR: %s\n"), *Msg); })
|
: Func([&OutResult](const FString& Msg) { OutResult.Appendf(TEXT("ERROR: %s\n"), *Msg); })
|
||||||
{}
|
{}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -186,9 +186,9 @@ FString MCPUtils::FormatName(const FBPVariableDescription &Var)
|
|||||||
return Name;
|
return Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
FString MCPUtils::FormatName(const UClass *Class)
|
FString MCPUtils::FormatName(const UStruct *Struct)
|
||||||
{
|
{
|
||||||
FString Name = Class->GetName();
|
FString Name = Struct->GetName();
|
||||||
SanitizeNameInPlace(Name);
|
SanitizeNameInPlace(Name);
|
||||||
return Name;
|
return Name;
|
||||||
}
|
}
|
||||||
@@ -254,9 +254,19 @@ FString MCPUtils::FormatName(const UEnum *Enum)
|
|||||||
return Name;
|
return Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MCPUtils::Identifies(const FString &Name, const UClass *Class)
|
FString MCPUtils::FormatName(const FProperty *Prop)
|
||||||
{
|
{
|
||||||
return FormatName(Class).Equals(Name, ESearchCase::IgnoreCase);
|
return Prop->GetName();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MCPUtils::Identifies(const FString &Name, const FBPVariableDescription &Var)
|
||||||
|
{
|
||||||
|
return FormatName(Var).Equals(Name, ESearchCase::IgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MCPUtils::Identifies(const FString &Name, const UStruct *Struct)
|
||||||
|
{
|
||||||
|
return FormatName(Struct).Equals(Name, ESearchCase::IgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MCPUtils::Identifies(const FString &Name, const UMaterial *Material)
|
bool MCPUtils::Identifies(const FString &Name, const UMaterial *Material)
|
||||||
@@ -314,6 +324,11 @@ bool MCPUtils::Identifies(const FString &Name, const UEnum *Enum)
|
|||||||
return FormatName(Enum).Equals(Name, ESearchCase::IgnoreCase);
|
return FormatName(Enum).Equals(Name, ESearchCase::IgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MCPUtils::Identifies(const FString &Name, const FProperty *Prop)
|
||||||
|
{
|
||||||
|
return FormatName(Prop).Equals(Name, ESearchCase::IgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Identifies
|
// Identifies
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -1167,6 +1182,34 @@ void MCPUtils::EnsureMaterialGraph(UMaterial* Material)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UMaterial* MCPUtils::ReplaceMaterialWithTransientCopy(UMaterial* Material)
|
||||||
|
{
|
||||||
|
if (!Material) return nullptr;
|
||||||
|
|
||||||
|
// Already a preview material — nothing to do.
|
||||||
|
if (Material->GetOutermost() == GetTransientPackage())
|
||||||
|
return Material;
|
||||||
|
|
||||||
|
// If the material editor has a transient preview copy open, get it
|
||||||
|
// via the editor API. This follows the same pattern as Epic's
|
||||||
|
// MaterialEditingLibrary (FindMaterialEditorForAsset).
|
||||||
|
UAssetEditorSubsystem* Sub = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
|
||||||
|
IAssetEditorInstance* EditorInstance = Sub ? Sub->FindEditorForAsset(Material, false) : nullptr;
|
||||||
|
if (EditorInstance)
|
||||||
|
{
|
||||||
|
// This is a weird hack. We know that the IAssetEditorInstance for a material
|
||||||
|
// is always going to be an FMaterialEditor, which conforms to IMaterialEditor.
|
||||||
|
// If that weren't the case, this unsafe code would crash hard. However,
|
||||||
|
// lots of places in unreal use this same unsafe pattern.
|
||||||
|
IMaterialEditor* MatEditor = static_cast<IMaterialEditor*>(EditorInstance);
|
||||||
|
UMaterialInterface* Edited = MatEditor->GetMaterialInterface();
|
||||||
|
if (UMaterial* EditedMat = Cast<UMaterial>(Edited))
|
||||||
|
return EditedMat;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Material;
|
||||||
|
}
|
||||||
|
|
||||||
bool MCPUtils::SaveMaterialPackage(UMaterial* Material)
|
bool MCPUtils::SaveMaterialPackage(UMaterial* Material)
|
||||||
{
|
{
|
||||||
if (!Material) return false;
|
if (!Material) return false;
|
||||||
@@ -1392,70 +1435,53 @@ UAnimStateTransitionNode* MCPUtils::FindTransition(UAnimationStateMachineGraph*
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Node spawners
|
// Graph actions (node spawning)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
FString MCPUtils::NodeSpawnerFullName(UBlueprintNodeSpawner* Spawner)
|
#include "EdGraph/EdGraphSchema.h"
|
||||||
|
|
||||||
|
FString MCPUtils::ActionFullName(const TSharedPtr<FEdGraphSchemaAction>& Action)
|
||||||
{
|
{
|
||||||
const FBlueprintActionUiSpec& UiSpec = Spawner->PrimeDefaultUiSpec();
|
FString Category = Action->GetCategory().ToString();
|
||||||
FString Category = UiSpec.Category.ToString();
|
FString MenuName = Action->GetMenuDescription().ToString();
|
||||||
FString MenuName = UiSpec.MenuName.ToString();
|
|
||||||
if (Category.IsEmpty())
|
if (Category.IsEmpty())
|
||||||
{
|
|
||||||
return MenuName;
|
return MenuName;
|
||||||
}
|
|
||||||
return Category + TEXT("|") + MenuName;
|
return Category + TEXT("|") + MenuName;
|
||||||
}
|
}
|
||||||
|
|
||||||
TArray<UBlueprintNodeSpawner*> MCPUtils::SearchNodeSpawners(const FString& Query, int32 MaxResults, bool ExactMatch, UEdGraph* GraphFilter)
|
TArray<TSharedPtr<FEdGraphSchemaAction>> MCPUtils::SearchGraphActions(UEdGraph* Graph, const FString& Query, int32 MaxResults, bool ExactMatch)
|
||||||
{
|
{
|
||||||
FString QueryLower = Query.ToLower();
|
FString QueryLower = Query.ToLower();
|
||||||
TArray<UBlueprintNodeSpawner*> Result;
|
TArray<TSharedPtr<FEdGraphSchemaAction>> Result;
|
||||||
|
|
||||||
for (const auto& Pair : FBlueprintActionDatabase::Get().GetAllActions())
|
FGraphContextMenuBuilder ContextMenuBuilder(Graph);
|
||||||
|
Graph->GetSchema()->GetGraphContextActions(ContextMenuBuilder);
|
||||||
|
|
||||||
|
for (int32 i = 0; i < ContextMenuBuilder.GetNumActions(); i++)
|
||||||
{
|
{
|
||||||
for (UBlueprintNodeSpawner* Spawner : Pair.Value)
|
TSharedPtr<FEdGraphSchemaAction> Action = ContextMenuBuilder.GetSchemaAction(i);
|
||||||
|
if (!Action.IsValid()) continue;
|
||||||
|
|
||||||
|
FString FullName = ActionFullName(Action);
|
||||||
|
if (FullName.IsEmpty()) continue;
|
||||||
|
|
||||||
|
if (ExactMatch)
|
||||||
{
|
{
|
||||||
if (!Spawner) continue;
|
if (FullName.ToLower() != QueryLower)
|
||||||
if (Spawner->PrimeDefaultUiSpec().MenuName.IsEmpty()) continue;
|
continue;
|
||||||
|
|
||||||
// Filter by graph compatibility if a graph was provided
|
|
||||||
if (GraphFilter && Spawner->NodeClass)
|
|
||||||
{
|
|
||||||
UEdGraphNode* NodeCDO = CastChecked<UEdGraphNode>(Spawner->NodeClass->ClassDefaultObject);
|
|
||||||
if (!NodeCDO->IsCompatibleWithGraph(GraphFilter))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FString FullName = NodeSpawnerFullName(Spawner);
|
|
||||||
|
|
||||||
if (ExactMatch)
|
|
||||||
{
|
|
||||||
if (FullName.ToLower() != QueryLower)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
FString Keywords = Spawner->PrimeDefaultUiSpec().Keywords.ToString();
|
|
||||||
if (!FullName.ToLower().Contains(QueryLower)
|
|
||||||
&& !Keywords.ToLower().Contains(QueryLower))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Result.Add(Spawner);
|
|
||||||
|
|
||||||
if ((MaxResults > 0) && (Result.Num() >= MaxResults))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FString Keywords = Action->GetKeywords().ToString();
|
||||||
|
if (!FullName.ToLower().Contains(QueryLower) && !Keywords.ToLower().Contains(QueryLower))
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result.Add(Action);
|
||||||
|
if ((MaxResults > 0) && (Result.Num() >= MaxResults))
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1734,6 +1760,121 @@ FString MCPUtils::FormatPropertyType(FProperty* Prop)
|
|||||||
return TEXT("string");
|
return TEXT("string");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// GetEditableTemplate
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
UObject* MCPUtils::GetEditableTemplate(UObject* Obj, MCPErrorCallback Error)
|
||||||
|
{
|
||||||
|
if (!Obj)
|
||||||
|
{
|
||||||
|
Error.SetError(TEXT("Object is null"));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blueprint → operate on the Class Default Object.
|
||||||
|
if (UBlueprint* BP = Cast<UBlueprint>(Obj))
|
||||||
|
{
|
||||||
|
if (!BP->GeneratedClass)
|
||||||
|
{
|
||||||
|
Error.SetError(FString::Printf(TEXT("Blueprint '%s' has no GeneratedClass"), *Obj->GetName()));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return BP->GeneratedClass->GetDefaultObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
// These asset types are safe to edit directly.
|
||||||
|
if (Cast<UMaterial>(Obj)) return Obj;
|
||||||
|
if (Cast<UMaterialInstance>(Obj)) return Obj;
|
||||||
|
if (Cast<UStaticMesh>(Obj)) return Obj;
|
||||||
|
if (Cast<USkeletalMesh>(Obj)) return Obj;
|
||||||
|
if (Cast<UTexture>(Obj)) return Obj;
|
||||||
|
if (Cast<UActorComponent>(Obj)) return Obj;
|
||||||
|
|
||||||
|
// Unknown type — refuse for safety.
|
||||||
|
Error.SetError(FString::Printf(TEXT("Object type '%s' is not supported for generic property editing"), *Obj->GetClass()->GetName()));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// FindPropertyByName
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
FProperty* MCPUtils::FindPropertyByName(UObject* Obj, const FString& Name, MCPErrorCallback Error)
|
||||||
|
{
|
||||||
|
if (!Obj)
|
||||||
|
{
|
||||||
|
Error.SetError(TEXT("Object is null"));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
FProperty* Found = nullptr;
|
||||||
|
for (TFieldIterator<FProperty> PropIt(Obj->GetClass()); PropIt; ++PropIt)
|
||||||
|
{
|
||||||
|
if (!Identifies(Name, *PropIt)) continue;
|
||||||
|
if (Found)
|
||||||
|
{
|
||||||
|
Error.SetError(FString::Printf(TEXT("Ambiguous property '%s' on %s"), *Name, *FormatName(Obj->GetClass())));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
Found = *PropIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Found)
|
||||||
|
Error.SetError(FString::Printf(TEXT("Property '%s' not found on %s"), *Name, *FormatName(Obj->GetClass())));
|
||||||
|
|
||||||
|
return Found;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// GetPropertyValueText
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
FString MCPUtils::GetPropertyValueText(UObject* Container, FProperty* Prop)
|
||||||
|
{
|
||||||
|
FString Result;
|
||||||
|
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container);
|
||||||
|
Prop->ExportTextItem_Direct(Result, ValuePtr, nullptr, Container, PPF_None);
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// SetPropertyValueText
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
bool MCPUtils::SetPropertyValueText(UObject* Container, FProperty* Prop, const FString& Value, MCPErrorCallback Error)
|
||||||
|
{
|
||||||
|
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container);
|
||||||
|
const TCHAR* ImportResult = Prop->ImportText_Direct(*Value, ValuePtr, Container, PPF_None);
|
||||||
|
if (!ImportResult)
|
||||||
|
{
|
||||||
|
Error.SetError(FString::Printf(TEXT("Failed to parse '%s' for property '%s' (type: %s)"),
|
||||||
|
*Value, *FormatName(Prop), *Prop->GetCPPType()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// BlueprintVisibleProperties
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
TArray<FProperty*> MCPUtils::BlueprintVisibleProperties(UObject* Obj, const FString& Query)
|
||||||
|
{
|
||||||
|
TArray<FProperty*> Result;
|
||||||
|
if (!Obj) return Result;
|
||||||
|
for (TFieldIterator<FProperty> PropIt(Obj->GetClass()); PropIt; ++PropIt)
|
||||||
|
{
|
||||||
|
FProperty* Prop = *PropIt;
|
||||||
|
if (!Prop) continue;
|
||||||
|
if (!Prop->HasAnyPropertyFlags(CPF_BlueprintVisible)) continue;
|
||||||
|
if (!Query.IsEmpty() && !FormatName(Prop).Contains(Query, ESearchCase::IgnoreCase))
|
||||||
|
continue;
|
||||||
|
Result.Add(Prop);
|
||||||
|
}
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// FormatCommandHelp — verbose description of one handler command
|
// FormatCommandHelp — verbose description of one handler command
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class UMaterial;
|
|||||||
class UMaterialInstance;
|
class UMaterialInstance;
|
||||||
class UMaterialFunction;
|
class UMaterialFunction;
|
||||||
class UMaterialExpression;
|
class UMaterialExpression;
|
||||||
class UBlueprintNodeSpawner;
|
struct FEdGraphSchemaAction;
|
||||||
class UAnimationStateMachineGraph;
|
class UAnimationStateMachineGraph;
|
||||||
class UAnimStateNode;
|
class UAnimStateNode;
|
||||||
class UAnimStateTransitionNode;
|
class UAnimStateTransitionNode;
|
||||||
@@ -122,7 +122,7 @@ public:
|
|||||||
static FString FormatName(const UEdGraphPin *Pin);
|
static FString FormatName(const UEdGraphPin *Pin);
|
||||||
static FString FormatName(const FMemberReference &Ref);
|
static FString FormatName(const FMemberReference &Ref);
|
||||||
static FString FormatName(const FBPVariableDescription &Var);
|
static FString FormatName(const FBPVariableDescription &Var);
|
||||||
static FString FormatName(const UClass *Class);
|
static FString FormatName(const UStruct *Struct);
|
||||||
static FString FormatName(const UMaterial *Material);
|
static FString FormatName(const UMaterial *Material);
|
||||||
static FString FormatName(const UMaterialInstance *MaterialInstance);
|
static FString FormatName(const UMaterialInstance *MaterialInstance);
|
||||||
static FString FormatName(const UMaterialFunction *MaterialFunction);
|
static FString FormatName(const UMaterialFunction *MaterialFunction);
|
||||||
@@ -134,6 +134,7 @@ public:
|
|||||||
static FString FormatName(const UTexture *Texture);
|
static FString FormatName(const UTexture *Texture);
|
||||||
static FString FormatName(const UScriptStruct *Struct);
|
static FString FormatName(const UScriptStruct *Struct);
|
||||||
static FString FormatName(const UEnum *Enum);
|
static FString FormatName(const UEnum *Enum);
|
||||||
|
static FString FormatName(const FProperty *Prop);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
@@ -154,7 +155,8 @@ public:
|
|||||||
static bool Identifies(const FString &Name, const UEdGraphNode* Node);
|
static bool Identifies(const FString &Name, const UEdGraphNode* Node);
|
||||||
static bool Identifies(const FString &Name, const UEdGraphPin *Pin);
|
static bool Identifies(const FString &Name, const UEdGraphPin *Pin);
|
||||||
static bool Identifies(const FString &Name, const FMemberReference &Ref);
|
static bool Identifies(const FString &Name, const FMemberReference &Ref);
|
||||||
static bool Identifies(const FString &Name, const UClass *Class);
|
static bool Identifies(const FString &Name, const FBPVariableDescription &Var);
|
||||||
|
static bool Identifies(const FString &Name, const UStruct *Struct);
|
||||||
static bool Identifies(const FString &Name, const UMaterial *Material);
|
static bool Identifies(const FString &Name, const UMaterial *Material);
|
||||||
static bool Identifies(const FString &Name, const UMaterialInstance *MaterialInstance);
|
static bool Identifies(const FString &Name, const UMaterialInstance *MaterialInstance);
|
||||||
static bool Identifies(const FString &Name, const UMaterialFunction *MaterialFunction);
|
static bool Identifies(const FString &Name, const UMaterialFunction *MaterialFunction);
|
||||||
@@ -166,6 +168,7 @@ public:
|
|||||||
static bool Identifies(const FString &Name, const UTexture *Texture);
|
static bool Identifies(const FString &Name, const UTexture *Texture);
|
||||||
static bool Identifies(const FString &Name, const UScriptStruct *Struct);
|
static bool Identifies(const FString &Name, const UScriptStruct *Struct);
|
||||||
static bool Identifies(const FString &Name, const UEnum *Enum);
|
static bool Identifies(const FString &Name, const UEnum *Enum);
|
||||||
|
static bool Identifies(const FString &Name, const FProperty *Prop);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@@ -265,14 +268,30 @@ public:
|
|||||||
static bool SaveMaterialPackage(UMaterial* Material);
|
static bool SaveMaterialPackage(UMaterial* Material);
|
||||||
static bool SaveGenericPackage(UObject* Asset);
|
static bool SaveGenericPackage(UObject* Asset);
|
||||||
|
|
||||||
|
// If the material editor has a transient preview copy of this material,
|
||||||
|
// return that copy (which is what the editor is actually working on).
|
||||||
|
// Otherwise return the original.
|
||||||
|
static UMaterial* ReplaceMaterialWithTransientCopy(UMaterial* Material);
|
||||||
|
|
||||||
// ----- Anim blueprint helpers -----
|
// ----- Anim blueprint helpers -----
|
||||||
static UAnimationStateMachineGraph* FindStateMachineGraph(UBlueprint* BP, const FString& GraphName);
|
static UAnimationStateMachineGraph* FindStateMachineGraph(UBlueprint* BP, const FString& GraphName);
|
||||||
static UAnimStateNode* FindStateByName(UAnimationStateMachineGraph* SMGraph, const FString& StateName, MCPErrorCallback Error);
|
static UAnimStateNode* FindStateByName(UAnimationStateMachineGraph* SMGraph, const FString& StateName, MCPErrorCallback Error);
|
||||||
static UAnimStateTransitionNode* FindTransition(UAnimationStateMachineGraph* SMGraph, const FString& FromStateName, const FString& ToStateName);
|
static UAnimStateTransitionNode* FindTransition(UAnimationStateMachineGraph* SMGraph, const FString& FromStateName, const FString& ToStateName);
|
||||||
|
|
||||||
// ----- Node spawners -----
|
// ----- Graph actions (node spawning) -----
|
||||||
static FString NodeSpawnerFullName(UBlueprintNodeSpawner* Spawner);
|
static FString ActionFullName(const TSharedPtr<FEdGraphSchemaAction>& Action);
|
||||||
static TArray<UBlueprintNodeSpawner*> SearchNodeSpawners(const FString& Query, int32 MaxResults = 0, bool ExactMatch = false, UEdGraph* GraphFilter = nullptr);
|
static TArray<TSharedPtr<FEdGraphSchemaAction>> SearchGraphActions(UEdGraph* Graph, const FString& Query, int32 MaxResults = 0, bool ExactMatch = false);
|
||||||
|
|
||||||
|
// ----- Editable template -----
|
||||||
|
// Given an object, returns the appropriate template object for generic
|
||||||
|
// property editing, or nullptr if the type isn't whitelisted.
|
||||||
|
// UBlueprint → CDO; UMaterial, UActorComponent, etc. → as-is.
|
||||||
|
static UObject* GetEditableTemplate(UObject* Obj, MCPErrorCallback Error);
|
||||||
|
static TArray<FProperty*> BlueprintVisibleProperties(UObject* Obj, const FString& Query = FString());
|
||||||
|
|
||||||
|
static FProperty* FindPropertyByName(UObject* Obj, const FString& Name, MCPErrorCallback Error = nullptr);
|
||||||
|
static FString GetPropertyValueText(UObject* Container, FProperty* Prop);
|
||||||
|
static bool SetPropertyValueText(UObject* Container, FProperty* Prop, const FString& Value, MCPErrorCallback Error = nullptr);
|
||||||
|
|
||||||
// ----- Property population -----
|
// ----- Property population -----
|
||||||
static FString PropertyNameToJsonKey(const FString& PropName);
|
static FString PropertyNameToJsonKey(const FString& PropName);
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Split a handler source file into one file per UMCPHandler class.
|
|
||||||
|
|
||||||
Usage: python3 tools/split-handlers.py <source_file>
|
|
||||||
|
|
||||||
Reads the #include block from the top of the file, then splits at the
|
|
||||||
closing }; (column 0) of each UMCPHandler class. Each output file is
|
|
||||||
named after the handler class and placed in the same directory. The
|
|
||||||
shared #include block is prepended to every output file.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import re
|
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if len(sys.argv) != 2:
|
|
||||||
print(f"Usage: {sys.argv[0]} <source_file>")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
filepath = sys.argv[1]
|
|
||||||
with open(filepath) as f:
|
|
||||||
lines = f.readlines()
|
|
||||||
|
|
||||||
# Extract the #include block from the top of the file.
|
|
||||||
# Collect #pragma, #include, blank lines, and // comment lines
|
|
||||||
# until we hit something else.
|
|
||||||
include_block = []
|
|
||||||
body_start = 0
|
|
||||||
for i, line in enumerate(lines):
|
|
||||||
stripped = line.strip()
|
|
||||||
if (stripped == '' or stripped.startswith('#pragma') or
|
|
||||||
stripped.startswith('#include') or stripped.startswith('//')):
|
|
||||||
include_block.append(line)
|
|
||||||
else:
|
|
||||||
body_start = i
|
|
||||||
break
|
|
||||||
|
|
||||||
# Strip trailing blank/comment lines from include block
|
|
||||||
while include_block and include_block[-1].strip() in ('', ) or (
|
|
||||||
include_block and include_block[-1].strip().startswith('//')):
|
|
||||||
body_start -= 1
|
|
||||||
include_block.pop()
|
|
||||||
if not include_block:
|
|
||||||
break
|
|
||||||
|
|
||||||
# Remove the .generated.h include from the shared block (each output
|
|
||||||
# file gets its own).
|
|
||||||
include_block = [l for l in include_block if '.generated.h' not in l]
|
|
||||||
|
|
||||||
body_lines = lines[body_start:]
|
|
||||||
|
|
||||||
# Find split points in the body: after each }; that closes a UMCPHandler.
|
|
||||||
handler_name = None
|
|
||||||
splits = [] # list of (end_index_exclusive_in_body, class_name)
|
|
||||||
|
|
||||||
for i, line in enumerate(body_lines):
|
|
||||||
m = re.match(r'class (UMCPHandler_\w+)', line)
|
|
||||||
if m:
|
|
||||||
handler_name = m.group(1)
|
|
||||||
if line.startswith('};') and handler_name:
|
|
||||||
splits.append((i + 1, handler_name))
|
|
||||||
handler_name = None
|
|
||||||
|
|
||||||
if not splits:
|
|
||||||
print("No UMCPHandler classes found.")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
dirname = os.path.dirname(filepath)
|
|
||||||
start = 0
|
|
||||||
for end, name in splits:
|
|
||||||
outpath = os.path.join(dirname, name + '.h')
|
|
||||||
with open(outpath, 'w') as f:
|
|
||||||
f.writelines(include_block)
|
|
||||||
f.write(f'#include "{name}.generated.h"\n')
|
|
||||||
f.write('\n')
|
|
||||||
f.writelines(body_lines[start:end])
|
|
||||||
print(f" {name}.h ({end - start} lines)")
|
|
||||||
start = end
|
|
||||||
|
|
||||||
# Warn about trailing content
|
|
||||||
if start < len(body_lines):
|
|
||||||
remaining = ''.join(body_lines[start:]).strip()
|
|
||||||
if remaining:
|
|
||||||
print(f"Warning: {len(body_lines) - start} trailing lines not included in any output file:")
|
|
||||||
for line in body_lines[start:]:
|
|
||||||
print(f" | {line}", end='')
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
Reference in New Issue
Block a user