Working on editing materials

This commit is contained in:
2026-03-11 22:03:32 -04:00
parent 0e79b02307
commit 2e058035f3
102 changed files with 428 additions and 463 deletions

Binary file not shown.

View File

@@ -11,14 +11,46 @@
#include "K2Node_CallFunction.h"
#include "K2Node_FunctionEntry.h"
static FString WrapText(const FString& Text, int32 ColLimit, const FString& Prefix)
{
FString Clean = Text;
Clean.ReplaceInline(TEXT("\r\n"), TEXT("\n"));
TArray<FString> Words;
Clean.ParseIntoArrayWS(Words);
TStringBuilder<1024> Result;
int32 Col = 0;
for (const FString& Word : Words)
{
if (Col > 0 && Col + 1 + Word.Len() > ColLimit)
{
Result.Append(TEXT("\n"));
Col = 0;
}
if (Col == 0)
{
Result.Append(Prefix);
Col = Prefix.Len();
}
else
{
Result.Append(TEXT(" "));
Col += 1;
}
Result.Append(Word);
Col += Word.Len();
}
return Result.ToString();
}
FlxBlueprintExporter::FlxBlueprintExporter(UEdGraph* InGraph)
: Graph(InGraph)
{
SortNodes();
AssignNodeNames();
EmitLocalVariables();
EmitNodeList();
EmitDetails();
EmitGraph();
EmitComments();
}
////////////////////////////////////////////////////////
@@ -194,20 +226,11 @@ void FlxBlueprintExporter::SortNodes()
}
}
void FlxBlueprintExporter::AssignNodeNames()
{
// Node names are now computed on the fly by FormatNodeName.
}
void FlxBlueprintExporter::EmitNode(UEdGraphNode* Node)
{
if (Node->IsA<UEdGraphNode_Comment>())
{
Output.Appendf(TEXT("\n// %s\n"), *Node->NodeComment);
return;
}
if (Node->IsA<UEdGraphNode_Comment>()) return;
Output.Appendf(TEXT("\nnode %s\n"), *MCPUtils::FormatName(Node));
Output.Appendf(TEXT("\nnode %s: %s\n"), *MCPUtils::FormatName(Node), *MCPUtils::FormatNodeTitle(Node));
// Emit input data pins.
for (UEdGraphPin* Pin : FilterPins(Node, EGPD_Input))
@@ -280,14 +303,50 @@ void FlxBlueprintExporter::EmitGraph()
}
}
void FlxBlueprintExporter::EmitNodeList()
void FlxBlueprintExporter::EmitDetails()
{
for (UEdGraphNode* Node : SortedNodes)
{
if (Node->IsA<UK2Node_Knot>()) continue;
if (Node->IsA<UEdGraphNode_Comment>()) continue;
if (Node->IsA<UK2Node_VariableGet>()) continue;
Details.Appendf(TEXT("%s = %s\n"),
*MCPUtils::FormatName(Node), *Node->NodeGuid.ToString());
Details.Appendf(TEXT("details %s\n"), *MCPUtils::FormatName(Node));
Details.Appendf(TEXT(" pos %d, %d\n"), Node->NodePosX, Node->NodePosY);
}
}
void FlxBlueprintExporter::EmitComments()
{
for (UEdGraphNode* CommentNode : SortedNodes)
{
if (!CommentNode->IsA<UEdGraphNode_Comment>()) continue;
int32 CX = CommentNode->NodePosX;
int32 CY = CommentNode->NodePosY;
int32 CW = CommentNode->NodeWidth;
int32 CH = CommentNode->NodeHeight;
// Emit header.
Output.Append(TEXT("\ncomment:\n"));
// Emit wrapped, indented body.
Output.Append(WrapText(CommentNode->NodeComment, 70, TEXT(" - ")));
Output.Append(TEXT("\n"));
// Find contained nodes.
TArray<FString> ContainedNames;
for (UEdGraphNode* Node : SortedNodes)
{
if (Node->IsA<UEdGraphNode_Comment>()) continue;
int32 NX = Node->NodePosX;
int32 NY = Node->NodePosY;
if (NX >= CX && NY >= CY && NX <= CX + CW && NY <= CY + CH)
ContainedNames.Add(MCPUtils::FormatName(Node));
}
if (ContainedNames.Num() > 0)
{
Output.Appendf(TEXT(" applies to: %s\n"), *FString::Join(ContainedNames, TEXT(", ")));
}
}
}

View File

@@ -1,99 +1,45 @@
# Your Goals
We want to do several things. I'll give you
the concise list of goals, then I'll circle back
and give you the coding standard we're trying
to achieve.
- Convert these handlers to use plain-text output
wherever it is practical.
- MCPAssetFinder and MCPFetcher are powerful tools
to retrieve assets and objects. We want to migrate
the code to use these over hand-rolled solutions.
- Get rid of ad-hoc code to calculate object names,
which inevitably results in inconsistent naming.
- To make some of these handlers process batches,
rather than single objects, where it makes sense
to do so.
- To reduce the amount of code that's just passing
error messages around.
- To get rid of UE_LOG calls.
# Our Coding Standards
This is what we're trying to achieve with these goals.
* When searching for uassets, the tool MCPAssetFinder is
often the most precise, concise, and efficient tool.
Please study this API before starting. Using this
tool gives more reliable and more concise code than
hand-rolling code to obtain assets.
* Command-handlers are classes that implement the MCPHandler
interface. The command's json parameters automatically get
injected into the handler's properties using reflection
code. Check out a few handlers to see how this works.
* Class MCPFetcher can precisely and easily retrieve objects
of all different kinds using clear, specific identifiers.
It is the best tool when you need the caller to specify a
blueprint, or a graph, or a pin - you name it. It relieves
you of the burden of digging around in unreal's data
structures. It also enforces a naming consistency
across handlers. Please study this API.
of all different kinds using a 'path'. Study the API
of this class, we use it everywhere. It is the best tool
when you need the caller to specify a blueprint, or a
graph, or a pin - you name it.
* To get the name of an object, there are tons of methods
you could call: pin->GetName(), pin->NodeGuid.ToString(),
node->GetTitle()... there are *so many* ways to get a
readable label. All of those ways are *NOT ALLOWED*. The
only valid way to get a name for an object is using
MCPUtils::FormatName(obj). This is to enforce consistent
naming. FormatName has been designed to produce very
clear, very readable, and highly unique identifiers.
More importantly, if one tool outputs a name, a different
tool will accept that name, because both tools use the
same naming scheme.
* When you want to scan for a list of assets, MCPAssets
is the best tool. This wraps Unreal's asset database
in a convenient interface. Please study this API.
* The only valid way to get a name for an object is using
MCPUtils::FormatName(obj). We don't allow the use of
pin->GetName, or node->GetTitle, or any other name-fetching
routine. Using only MCPUtils::FormatName guarantees
consistency: that means, names output by one tool can
be parsed by a different tool.
* To check whether a string matches an object's name,
there's only one valid way to do that as well: using
bool MCPUtils::Identifies(Name, Obj). Don't compare
the name yourself: you might get the case-sensitivity
wrong, you might forget to ignore trailing whitespace,
there are too many things that could go wrong. We've
provided MCPUtils::Identifies, which does it right
every time, for every kind of object.
bool MCPUtils::Identifies(Name, Obj).
* Concise output is better. The output of these handlers
will primarily be consumed by LLMs with finite context
windows. Plain-text handlers beat json handlers for
minimizing tokens: read the API for MCPHandler.h to learn
how to create a plain text handler. Avoid sending
information the caller didn't ask for. Don't echo the
command parameters: the caller knows what parameters he
sent. Don't make things unnecessarily verbose:
"type=int varname=x" can be shortened to "int x."
windows. Avoid sending information the caller didn't ask
for. Don't echo the command parameters: the caller knows
what parameters he sent. Don't make things unnecessarily
verbose: "type=int varname=x" can be shortened to "int x."
Generate output in a form that *you* would want to
consume.
* It's really important to send good error messages back to
the caller. Every handler has a second parameter, the
"output device", which is used to send data back to the
caller. The "output device" can either be a stringbuilder,
or a json tree. Our two core tools, MCPFetcher and
MCPAssetFinder, have powerful error handling built in: if
you give them a reference to the output device, they
will send error messages directly back to the caller
without your intervention. This is bulletproof.
Study class MCPErrorCallback to see how this works: any
function that takes MCPErrorCallback can put errors
directly into an "output device".
* If the output device of your handler is a shared ptr
to a json tree, you might see code that passes &*Json to
an MCPErrorCallback. That's legacy: it used to be necessary
to convert the shared pointer into a regular pointer.
Now the MCPErrorCallback can accept the shared pointer
directly.
* You can pass the output StringBuilder directly into
MCPFetcher and MCPAssets. If you do, these will automatically
generate good error messages. If either of these classes
returns 'false', you don't need to generate an error
message: it's already been done. Any other routine that
accepts MCPErrorCallback can do the same.
* It's good for handlers to do operations in batches,
where possible. SpawnNodes is better than SpawnNode.
@@ -108,10 +54,9 @@ This is what we're trying to achieve with these goals.
Better to report problems via the response. Please remove
UE_LOG calls in handlers.
* In addition to refactoring some handlers, we'd like you
to make some notes in a markdown file. The markdown
file should have the name UMCPHandler_XXX.Notes.md.
Tell us what you think is good about the handler, what
needs more work, and areas where you weren't entirely
confident about what to do, so you acted conservatively.
If there's more to do, we'd like to know.
* When you're going to edit something, it's important to
mark things dirty. There's a very powerful tool for that:
MCPUtils::PreEdit and MCPUtils::PostEdit, which can also
be accessed through MCPFetcher::PreEdit and PostEdit.
These routines are very careful about marking everything
dirty, so you don't have to worry about it.

View File

@@ -17,7 +17,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_AddMaterialExpression : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -36,7 +36,7 @@ struct FConnectMaterialPinsEntry
};
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_ConnectMaterialExpressionPins : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -16,7 +16,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_DeleteMaterialExpression : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -30,7 +30,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_DescribeMaterialInEnglish : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -19,7 +19,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_DisconnectMaterialExpressionPin : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -21,7 +21,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_DumpMaterial : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -29,7 +29,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_DumpMaterialFunction : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -17,7 +17,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_SearchWithinMaterials : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -17,7 +17,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_SetMaterialExpressionPosition : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -26,7 +26,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_SetMaterialExpressionProperty : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -19,7 +19,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_AddAnimStateToMachine : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -16,7 +16,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_AddAnimStateTransition : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -16,7 +16,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_AddBlueprintComponent : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -14,7 +14,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_AddBlueprintInterface : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -13,7 +13,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_AddBlueprintVariable : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -29,7 +29,7 @@ struct FDispatcherParamEntry
FString Type;
};
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_AddEventDispatcher : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -18,7 +18,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_AddFunctionParameter : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -13,7 +13,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_AddStructField : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -13,7 +13,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_BackupAsset : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -17,7 +17,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_ChangeBlueprintVariableType : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -18,7 +18,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_ChangeFunctionParameterType : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -19,7 +19,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_ChangeStructNodeType : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -14,7 +14,7 @@
// Pre-flight check: can two pins be connected?
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_CheckPinConnectionCompatibility : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -13,7 +13,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_CompileBlueprint : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -13,7 +13,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_CompileMaterial : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -26,7 +26,7 @@ struct FConnectPinsEntry
};
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_ConnectPins : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -16,7 +16,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_CreateAnimBlueprintAsset : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -13,7 +13,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_CreateBlendSpaceAsset : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -13,7 +13,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_CreateBlueprintAsset : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -17,7 +17,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_CreateBlueprintGraph : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -16,7 +16,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_CreateEnumAsset : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -16,7 +16,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_CreateMaterialAsset : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -15,7 +15,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_CreateMaterialFunctionAsset : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -17,7 +17,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_CreateMaterialInstanceAsset : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -29,7 +29,7 @@ struct FStructPropertyEntry
FString Type;
};
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_CreateStructAsset : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -15,7 +15,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_DeleteAsset : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -14,7 +14,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_DeleteBlueprintGraph : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -16,7 +16,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_DeleteNodeFromGraph : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -15,7 +15,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_DiffTwoBlueprints : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -26,7 +26,7 @@ struct FDisconnectPinEntry
};
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_DisconnectPins : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -16,7 +16,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_DumpBlueprint : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -16,7 +16,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_DumpGraphs : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -19,7 +19,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_DumpMaterialInstanceParameters : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -11,7 +11,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_DumpProperties : public UObject, public IMCPHandler
{
GENERATED_BODY()
@@ -26,6 +26,9 @@ public:
UPROPERTY(meta=(Optional, Description="Truncate values to 80 characters (default true)"))
bool Truncate = true;
UPROPERTY(meta=(Optional, Description="Only show properties declared on the object's own class, not inherited ones"))
bool Local = false;
virtual FString GetDescription() const override
{
return TEXT("List all blueprint-visible properties on an object resolved via MCPFetcher path, "
@@ -34,16 +37,12 @@ public:
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
{
// Resolve the path to an object.
// Resolve the path to an object and get its editable template.
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);
UObject* Template = F.Walk(Path).Template().Cast<UObject>();
if (!Template) return;
TArray<FProperty*> Props = MCPUtils::BlueprintVisibleProperties(Template, Query);
TArray<FProperty*> Props = MCPUtils::SearchProperties(Template, Query, CPF_Edit, Local);
UStruct* CurrentOwner = nullptr;
for (FProperty* Prop : Props)
@@ -63,7 +62,7 @@ public:
if (Truncate && (ValueStr.Len() > 80))
ValueStr = ValueStr.Left(80) + TEXT("...");
bool bEditable = Prop->HasAnyPropertyFlags(CPF_Edit);
bool bEditable = !Prop->HasAnyPropertyFlags(CPF_EditConst);
Result.Appendf(TEXT(" %s %s %s = %s\n"),
bEditable ? TEXT("editable") : TEXT("readonly"),
*MCPUtils::FormatPropertyType(Prop),

View File

@@ -16,7 +16,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_DuplicateNodesInGraph : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -11,7 +11,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_FindAssetReferences : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -14,7 +14,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_FindMaterialReferences : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -12,7 +12,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_GetNodeComment : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -16,7 +16,7 @@
// HandleGetPinInfo — detailed information about a specific pin
// ============================================================
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_GetPinDetails : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -13,7 +13,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_ListAnimSlotNames : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -13,7 +13,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_ListAnimSyncGroups : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -13,7 +13,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_ListBlueprintAssets : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -14,7 +14,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_ListBlueprintComponents : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -12,7 +12,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_ListBlueprintInterfaces : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -10,7 +10,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_ListClassProperties : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -17,7 +17,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_ListEventDispatchers : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -10,7 +10,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_ListOpenAssetEditors : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -11,7 +11,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_OpenAssetEditor : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -16,7 +16,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_RefreshAllNodesInGraph : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -16,7 +16,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_RemoveAnimStateFromMachine : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -15,7 +15,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_RemoveBlueprintComponent : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -13,7 +13,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_RemoveBlueprintInterface : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -13,7 +13,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_RemoveBlueprintVariable : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -16,7 +16,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_RemoveFunctionParameter : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -13,7 +13,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_RemoveStructField : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -13,7 +13,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_RenameAsset : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -14,7 +14,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_RenameBlueprintGraph : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -15,7 +15,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_ReparentBlueprint : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -14,7 +14,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_ReparentMaterialInstance : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -18,7 +18,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_ReplaceFunctionCallsInBlueprint : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -13,7 +13,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_RestoreAsset : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -11,7 +11,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_SearchAssets : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -13,7 +13,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_SearchSpawnableNodeTypes : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -31,7 +31,7 @@
// HandleSearchByType — find all usages of a type across blueprints
// ============================================================
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_SearchTypeUsageInBlueprints : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -15,7 +15,7 @@
// HandleListClasses — discover available UClasses
// ============================================================
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_SearchUnrealClasses : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -21,7 +21,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_SearchWithinBlueprints : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -20,7 +20,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_SetAnimStateAnimation : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -23,7 +23,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_SetAnimStateBlendSpace : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -15,7 +15,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_SetAnimTransitionRule : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -29,7 +29,7 @@ struct FBlendSpaceSampleEntry
float Y = 0.0f;
};
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_SetBlendSpaceSamplePoints : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -13,7 +13,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_SetBlueprintVariableMetadata : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -15,7 +15,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_SetClassDefaultValue : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -19,7 +19,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_SetMaterialInstanceParameter : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -13,7 +13,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_SetMaterialProperty : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -13,7 +13,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_SetNodeComment : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -30,7 +30,7 @@ struct FMoveNodeEntry
};
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_SetNodePositions : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -26,7 +26,7 @@ struct FSetPinDefaultEntry
};
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_SetPinDefaultValues : public UObject, public IMCPHandler
{
GENERATED_BODY()
@@ -45,7 +45,6 @@ public:
int32 SuccessCount = 0;
int32 TotalCount = Pins.Array.Num();
TSet<UEdGraphNode*> ModifiedNodes;
TSet<UBlueprint*> ModifiedBlueprints;
for (const TSharedPtr<FJsonValue>& PinVal : Pins.Array)
{
@@ -67,19 +66,13 @@ public:
const UEdGraphSchema* Schema = Node->GetGraph()->GetSchema();
if (Schema)
{
FString ValidationError = Schema->IsPinDefaultValid(Pin, Entry.Value, nullptr, FText::GetEmpty());
if (!ValidationError.IsEmpty())
{
Result.Appendf(TEXT("error: Invalid value for %s: %s\n"), *MCPUtils::FormatName(Pin), *ValidationError);
continue;
}
}
Schema->TrySetDefaultValue(*Pin, Entry.Value);
else
Pin->DefaultValue = Entry.Value;
SuccessCount++;
ModifiedNodes.Add(Node);
ModifiedBlueprints.Add(FBlueprintEditorUtils::FindBlueprintForNodeChecked(Node));
F.PostEdit();
}
for (UEdGraphNode* Node : ModifiedNodes)
@@ -87,11 +80,6 @@ public:
Node->ReconstructNode();
}
for (UBlueprint* BP : ModifiedBlueprints)
{
FBlueprintEditorUtils::MarkBlueprintAsModified(BP);
}
Result.Appendf(TEXT("Set %d/%d pin defaults.\n"), SuccessCount, TotalCount);
}
};

View File

@@ -11,7 +11,7 @@
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_SetProperties : public UObject, public IMCPHandler
{
GENERATED_BODY()
@@ -31,13 +31,9 @@ public:
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
{
// Resolve the path to an object.
// Resolve the path to an object and get its editable template.
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);
UObject* Template = F.Walk(Path).Template().Cast<UObject>();
if (!Template) return;
if (!Properties.Json || Properties.Json->Values.Num() == 0)
@@ -67,7 +63,7 @@ public:
}
// Apply all changes in a single Pre/PostEditChange bracket.
Template->PreEditChange(nullptr);
F.PreEdit();
int32 SuccessCount = 0;
for (const auto& Pair : Properties.Json->Values)
@@ -86,11 +82,10 @@ public:
SuccessCount++;
}
Template->PostEditChange();
F.PostEdit();
// Save.
Template->MarkPackageDirty();
bool bSaved = MCPUtils::SaveGenericPackage(Obj);
bool bSaved = MCPUtils::SaveGenericPackage(Template);
Result.Appendf(TEXT("Set %d/%d properties.\n"), SuccessCount, Properties.Json->Values.Num());
if (!bSaved)

View File

@@ -2,10 +2,11 @@
#include "CoreMinimal.h"
#include "MCPHandler.h"
#include "MCPFetcher.h"
#include "MCPUtils.h"
#include "UMCPHandler_ShowCommands.generated.h"
UCLASS()
UCLASS(meta=(Group="Documentation"))
class UMCPHandler_ShowCommands : public UObject, public IMCPHandler
{
GENERATED_BODY()
@@ -22,22 +23,16 @@ public:
return TEXT("List all available commands with their descriptions.");
}
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
void EmitGroup(const FString& GroupName, const TArray<UClass*>& Classes, FStringBuilderBase& Result)
{
FString QueryLower = Query.ToLower();
for (UClass* Class : MCPUtils::CollectHandlerClasses())
Result.Appendf(TEXT("\n=== %s ===\n\n"), *GroupName);
for (UClass* Class : Classes)
{
FString ToolName = MCPUtils::GetToolName(Class);
if (!ToolName.ToLower().Contains(QueryLower))
continue;
if (Verbose)
{
MCPUtils::FormatCommandHelp(Class, Result);
continue;
}
else
{
Result.Append(MCPUtils::GetToolName(Class));
Result.Append(TEXT("("));
bool bFirst = true;
@@ -46,10 +41,53 @@ public:
if (!bFirst) Result.Append(TEXT(","));
bFirst = false;
if (PropIt->HasMetaData(TEXT("Optional"))) Result.Append(TEXT("?"));
Result.Append(MCPUtils::FormatPropertyType(*PropIt));
Result.Append(TEXT(" "));
Result.Append(MCPUtils::PropertyNameToJsonKey(PropIt->GetName()));
}
Result.Append(TEXT(")\n"));
}
}
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
{
FString QueryLower = Query.ToLower();
// Group handlers by their Group metadata.
TMap<FString, TArray<UClass*>> Groups;
for (UClass* Class : MCPUtils::CollectHandlerClasses())
{
FString ToolName = MCPUtils::GetToolName(Class);
if (!ToolName.ToLower().Contains(QueryLower))
continue;
FString Group = Class->GetMetaData(TEXT("Group"));
if (Group.IsEmpty()) Group = TEXT("Unclassified");
Groups.FindOrAdd(Group).Add(Class);
}
// Emit high-priority groups first, in order.
static const TCHAR* HighPriority[] = { TEXT("Documentation") };
for (const TCHAR* HP : HighPriority)
{
TArray<UClass*> Classes;
if (Groups.RemoveAndCopyValue(HP, Classes))
EmitGroup(HP, Classes, Result);
}
// Emit remaining groups.
for (const auto& Pair : Groups)
EmitGroup(Pair.Key, Pair.Value, Result);
// Append Path documentation.
Result.Append(TEXT("\n"));
Result.Append(TEXT("Some commands take a Path parameter. A Path starts with an asset\n"));
Result.Append(TEXT("package path (e.g. /Game/Widgets/WB_Hotkeys), followed by zero or\n"));
Result.Append(TEXT("more comma-separated steps that navigate into the asset:\n"));
Result.Append(TEXT("\n"));
for (const MCPFetcher::FWalker& W : MCPFetcher::GetWalkerTable())
{
Result.Appendf(TEXT(" %s — %s\n"), W.Key, W.Description);
}
Result.Append(TEXT("\nExample: /Game/Widgets/WB_Hotkeys,graph:EventGraph,node:Self_Reference_03,pin:Result\n"));
}
};

View File

@@ -32,7 +32,7 @@ struct FSpawnNodeEntry
};
UCLASS()
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_SpawnNodes : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -4,12 +4,12 @@
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphPin.h"
#include "UObject/PropertyAccessUtil.h"
#include "Engine/SimpleConstructionScript.h"
#include "Engine/SCS_Node.h"
#include "Engine/World.h"
#include "Materials/Material.h"
#include "MaterialGraph/MaterialGraph.h"
#include "MaterialGraph/MaterialGraphNode.h"
#include "Engine/LevelScriptBlueprint.h"
MCPFetcher& MCPFetcher::SetError(const FString& Msg)
@@ -31,6 +31,19 @@ MCPFetcher& MCPFetcher::TypeMismatch(const TCHAR* Walker, const TCHAR* Expected)
return *this;
}
const TArray<MCPFetcher::FWalker>& MCPFetcher::GetWalkerTable()
{
static const TArray<FWalker> Table = {
{ TEXT("graph"), TEXT("Find a named UEdGraph (blank name for material graphs)"), &MCPFetcher::Graph },
{ TEXT("node"), TEXT("Find a named UEdGraphNode within a graph or blueprint"), &MCPFetcher::Node },
{ TEXT("pin"), TEXT("Find a named UEdGraphPin on a node"), &MCPFetcher::Pin },
{ TEXT("component"), TEXT("Find a named component in a Blueprint's SCS"), &MCPFetcher::Component },
{ TEXT("levelblueprint"), TEXT("Get the level blueprint from a UWorld"), &MCPFetcher::LevelBlueprint },
// { TEXT("matexp"), TEXT("Get the UMaterialExpression from a UMaterialGraphNode"), &MCPFetcher::MatExp },
};
return Table;
}
MCPFetcher& MCPFetcher::Walk(const FString& Path)
{
if (bError) return *this;
@@ -53,6 +66,8 @@ MCPFetcher& MCPFetcher::Walk(const FString& Path)
Start = 1;
}
const TArray<FWalker>& Walkers = GetWalkerTable();
// Walk each subsequent segment
for (int32 i = Start; i < Segments.Num(); i++)
{
@@ -60,13 +75,16 @@ MCPFetcher& MCPFetcher::Walk(const FString& Path)
if (!Segments[i].Split(TEXT(":"), &Key, &Value))
Key = Segments[i];
if (StrEq(Key, TEXT("graph"))) Graph(Value);
else if (StrEq(Key, TEXT("node"))) Node(Value);
else if (StrEq(Key, TEXT("pin"))) Pin(Value);
else if (StrEq(Key, TEXT("component"))) Component(Value);
else if (StrEq(Key, TEXT("property"))) Property(Value);
else if (StrEq(Key, TEXT("levelblueprint"))) LevelBlueprint();
else
bool bFound = false;
for (const FWalker& W : Walkers)
{
if (!StrEq(Key, W.Key)) continue;
(this->*W.Func)(Value);
bFound = true;
break;
}
if (!bFound)
{
SetError(FString::Printf(TEXT("Unknown walker '%s'"), *Key));
return *this;
@@ -215,30 +233,7 @@ MCPFetcher& MCPFetcher::Component(const FString& Value)
return SetError(FString::Printf(TEXT("Component '%s' not found in %s"), *Value, *BP->GetName()));
}
MCPFetcher& MCPFetcher::Property(const FString& Value)
{
if (bError) return *this;
if (!Obj)
return TypeMismatch(TEXT("property"), TEXT("UObject"));
FProperty* Prop = Obj->GetClass()->FindPropertyByName(FName(*Value));
if (!Prop)
return SetError(FString::Printf(TEXT("Property '%s' not found on %s"), *Value, *Obj->GetClass()->GetName()));
FObjectProperty* ObjProp = CastField<FObjectProperty>(Prop);
if (!ObjProp)
return SetError(FString::Printf(TEXT("Property '%s' is not an object property (type: %s)"), *Value, *Prop->GetCPPType()));
UObject* PropValue = ObjProp->GetObjectPropertyValue(Prop->ContainerPtrToValuePtr<void>(Obj));
if (!PropValue)
return SetError(FString::Printf(TEXT("Property '%s' is null on %s"), *Value, *Obj->GetName()));
SetObj(PropValue);
return *this;
}
MCPFetcher& MCPFetcher::LevelBlueprint()
MCPFetcher& MCPFetcher::LevelBlueprint(const FString& Value)
{
if (bError) return *this;
@@ -256,3 +251,36 @@ MCPFetcher& MCPFetcher::LevelBlueprint()
SetObj(LevelBP);
return *this;
}
MCPFetcher& MCPFetcher::Template()
{
if (bError) return *this;
if (!Obj)
return SetError(TEXT("Template: object is null"));
if (UBlueprint* BP = ::Cast<UBlueprint>(Obj))
{
if (!BP->GeneratedClass)
return SetError(FString::Printf(TEXT("Blueprint '%s' has no GeneratedClass"), *Obj->GetName()));
SetObj(BP->GeneratedClass->GetDefaultObject());
return *this;
}
// Everything else is its own template — no navigation needed.
return *this;
}
MCPFetcher& MCPFetcher::MatExp(const FString& Value)
{
if (bError) return *this;
UMaterialGraphNode* MatNode = ::Cast<UMaterialGraphNode>(Obj);
if (!MatNode)
return TypeMismatch(TEXT("matexp"), TEXT("UMaterialGraphNode"));
if (!MatNode->MaterialExpression)
return SetError(FString::Printf(TEXT("Node '%s' has no MaterialExpression"), *MCPUtils::FormatName(MatNode)));
SetObj(MatNode->MaterialExpression);
return *this;
}

View File

@@ -5,7 +5,6 @@
#include "Handlers/UMCPHandler_AddBlueprintVariable.h"
#include "Handlers/UMCPHandler_AddEventDispatcher.h"
#include "Handlers/UMCPHandler_AddFunctionParameter.h"
#include "Handlers/UMCPHandler_AddMaterialExpression.h"
#include "Handlers/UMCPHandler_AddStructField.h"
#include "Handlers/UMCPHandler_BackupAsset.h"
#include "Handlers/UMCPHandler_ChangeBlueprintVariableType.h"
@@ -15,7 +14,6 @@
#include "Handlers/UMCPHandler_CompileBlueprint.h"
#include "Handlers/UMCPHandler_CompileMaterial.h"
#include "Handlers/UMCPHandler_ConnectPins.h"
#include "Handlers/UMCPHandler_ConnectMaterialExpressionPins.h"
#include "Handlers/UMCPHandler_CreateAnimBlueprintAsset.h"
#include "Handlers/UMCPHandler_CreateBlendSpaceAsset.h"
#include "Handlers/UMCPHandler_CreateBlueprintAsset.h"
@@ -27,17 +25,12 @@
#include "Handlers/UMCPHandler_CreateStructAsset.h"
#include "Handlers/UMCPHandler_DeleteAsset.h"
#include "Handlers/UMCPHandler_DeleteBlueprintGraph.h"
#include "Handlers/UMCPHandler_DeleteMaterialExpression.h"
#include "Handlers/UMCPHandler_DeleteNodeFromGraph.h"
#include "Handlers/UMCPHandler_DescribeMaterialInEnglish.h"
#include "Handlers/UMCPHandler_DiffTwoBlueprints.h"
#include "Handlers/UMCPHandler_DisconnectPins.h"
#include "Handlers/UMCPHandler_DisconnectMaterialExpressionPin.h"
#include "Handlers/UMCPHandler_DumpBlueprint.h"
#include "Handlers/UMCPHandler_DumpProperties.h"
#include "Handlers/UMCPHandler_DumpGraphs.h"
#include "Handlers/UMCPHandler_DumpMaterial.h"
#include "Handlers/UMCPHandler_DumpMaterialFunction.h"
#include "Handlers/UMCPHandler_DumpMaterialInstanceParameters.h"
#include "Handlers/UMCPHandler_DuplicateNodesInGraph.h"
#include "Handlers/UMCPHandler_FindAssetReferences.h"
@@ -71,15 +64,12 @@
#include "Handlers/UMCPHandler_SearchTypeUsageInBlueprints.h"
#include "Handlers/UMCPHandler_SearchUnrealClasses.h"
#include "Handlers/UMCPHandler_SearchWithinBlueprints.h"
#include "Handlers/UMCPHandler_SearchWithinMaterials.h"
#include "Handlers/UMCPHandler_SetAnimStateAnimation.h"
#include "Handlers/UMCPHandler_SetAnimStateBlendSpace.h"
#include "Handlers/UMCPHandler_SetAnimTransitionRule.h"
#include "Handlers/UMCPHandler_SetBlendSpaceSamplePoints.h"
#include "Handlers/UMCPHandler_SetBlueprintVariableMetadata.h"
#include "Handlers/UMCPHandler_SetClassDefaultValue.h"
#include "Handlers/UMCPHandler_SetMaterialExpressionPosition.h"
#include "Handlers/UMCPHandler_SetMaterialExpressionProperty.h"
#include "Handlers/UMCPHandler_SetMaterialInstanceParameter.h"
#include "Handlers/UMCPHandler_SetMaterialProperty.h"
#include "Handlers/UMCPHandler_SetNodeComment.h"

View File

@@ -125,11 +125,6 @@ void MCPUtils::SanitizeNameInPlace(FString &Name)
if (Name.IsEmpty()) Name = TEXT("_");
}
void MCPUtils::AppendNumericSuffix(FString &Name, int32 N)
{
if (N > 0)
Name += FString::Printf(TEXT("_%d"), N - 1);
}
FString MCPUtils::FormatName(const UWorld *World)
{
@@ -155,14 +150,7 @@ FString MCPUtils::FormatName(const UEdGraph *Graph)
FString MCPUtils::FormatName(const UEdGraphNode* Node)
{
// Sanitized first line of the node title.
FString Title = Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString();
int32 NewlineIdx;
if (Title.FindChar(TEXT('\n'), NewlineIdx))
Title.LeftInline(NewlineIdx);
SanitizeNameInPlace(Title);
AppendNumericSuffix(Title, Node->GetFName().GetNumber());
return Title;
return Node->GetName();
}
FString MCPUtils::FormatName(const UEdGraphPin *Pin)
@@ -259,99 +247,12 @@ FString MCPUtils::FormatName(const FProperty *Prop)
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)
{
return FormatName(Material).Equals(Name, ESearchCase::IgnoreCase);
}
bool MCPUtils::Identifies(const FString &Name, const UMaterialInstance *MaterialInstance)
{
return FormatName(MaterialInstance).Equals(Name, ESearchCase::IgnoreCase);
}
bool MCPUtils::Identifies(const FString &Name, const UMaterialFunction *MaterialFunction)
{
return FormatName(MaterialFunction).Equals(Name, ESearchCase::IgnoreCase);
}
bool MCPUtils::Identifies(const FString &Name, const UMaterialExpression *Expression)
{
return FormatName(Expression).Equals(Name, ESearchCase::IgnoreCase);
}
bool MCPUtils::Identifies(const FString &Name, const UStaticMesh *Mesh)
{
return FormatName(Mesh).Equals(Name, ESearchCase::IgnoreCase);
}
bool MCPUtils::Identifies(const FString &Name, const USkeletalMesh *Mesh)
{
return FormatName(Mesh).Equals(Name, ESearchCase::IgnoreCase);
}
bool MCPUtils::Identifies(const FString &Name, const UAnimSequence *Anim)
{
return FormatName(Anim).Equals(Name, ESearchCase::IgnoreCase);
}
bool MCPUtils::Identifies(const FString &Name, const UBlendSpace *BlendSpace)
{
return FormatName(BlendSpace).Equals(Name, ESearchCase::IgnoreCase);
}
bool MCPUtils::Identifies(const FString &Name, const UTexture *Texture)
{
return FormatName(Texture).Equals(Name, ESearchCase::IgnoreCase);
}
bool MCPUtils::Identifies(const FString &Name, const UScriptStruct *Struct)
{
return FormatName(Struct).Equals(Name, ESearchCase::IgnoreCase);
}
bool MCPUtils::Identifies(const FString &Name, const UEnum *Enum)
{
return FormatName(Enum).Equals(Name, ESearchCase::IgnoreCase);
}
bool MCPUtils::Identifies(const FString &Name, const FProperty *Prop)
{
return FormatName(Prop).Equals(Name, ESearchCase::IgnoreCase);
}
// ============================================================
// Identifies
// ============================================================
bool MCPUtils::Identifies(const FString &Name, const UWorld *World)
{
return FormatName(World).Equals(Name, ESearchCase::IgnoreCase);
}
bool MCPUtils::Identifies(const FString &Name, const UBlueprint *BP)
{
return FormatName(BP).Equals(Name, ESearchCase::IgnoreCase);
}
bool MCPUtils::Identifies(const FString &Name, const UActorComponent *C)
{
return FormatName(C).Equals(Name, ESearchCase::IgnoreCase);
}
bool MCPUtils::Identifies(const FString &Name, const UEdGraph *Graph)
{
return FormatName(Graph).Equals(Name, ESearchCase::IgnoreCase);
}
// Most types are handled by the template in MCPUtils.h.
// UEdGraphNode also matches by GUID:
bool MCPUtils::Identifies(const FString &Name, const UEdGraphNode* Node)
{
@@ -360,15 +261,9 @@ bool MCPUtils::Identifies(const FString &Name, const UEdGraphNode* Node)
return FormatName(Node).Equals(Name, ESearchCase::IgnoreCase);
}
bool MCPUtils::Identifies(const FString &Name, const UEdGraphPin *Pin)
{
return FormatName(Pin).Equals(Name, ESearchCase::IgnoreCase);
}
bool MCPUtils::Identifies(const FString &Name, const FMemberReference &Ref)
{
return FormatName(Ref).Equals(Name, ESearchCase::IgnoreCase);
}
// ============================================================
// Formatting other things
// ============================================================
FString MCPUtils::FormatPinType(const FEdGraphPinType& PinType)
{
@@ -386,6 +281,14 @@ FString MCPUtils::FormatPinType(UEdGraphPin* Pin)
return FormatPinType(Pin->PinType);
}
FString MCPUtils::FormatNodeTitle(const UEdGraphNode *Node)
{
FString Title = Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString();
int32 NewlineIdx;
if (Title.FindChar(TEXT('\n'), NewlineIdx))
Title.LeftInline(NewlineIdx);
return Title;
}
// ============================================================
// JSON helpers
@@ -1210,6 +1113,29 @@ UMaterial* MCPUtils::ReplaceMaterialWithTransientCopy(UMaterial* Material)
return Material;
}
// ============================================================
// PreEdit / PostEdit
// ============================================================
void MCPUtils::PreEdit(const TArray<UObject*>& Objects)
{
for (UObject* Obj : Objects)
Obj->PreEditChange(nullptr);
}
void MCPUtils::PostEdit(const TArray<UObject*>& Objects)
{
for (int32 i = Objects.Num() - 1; i >= 0; --i)
{
UObject* Obj = Objects[i];
Obj->PostEditChange();
Obj->MarkPackageDirty();
if (UBlueprint* BP = Cast<UBlueprint>(Obj))
FBlueprintEditorUtils::MarkBlueprintAsModified(BP);
}
}
bool MCPUtils::SaveMaterialPackage(UMaterial* Material)
{
if (!Material) return false;
@@ -1761,10 +1687,10 @@ FString MCPUtils::FormatPropertyType(FProperty* Prop)
}
// ============================================================
// GetEditableTemplate
// GetTemplate
// ============================================================
UObject* MCPUtils::GetEditableTemplate(UObject* Obj, MCPErrorCallback Error)
UObject* MCPUtils::GetTemplate(UObject* Obj, MCPErrorCallback Error)
{
if (!Obj)
{
@@ -1783,17 +1709,7 @@ UObject* MCPUtils::GetEditableTemplate(UObject* Obj, MCPErrorCallback Error)
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;
return Obj;
}
// ============================================================
@@ -1856,18 +1772,20 @@ bool MCPUtils::SetPropertyValueText(UObject* Container, FProperty* Prop, const F
}
// ============================================================
// BlueprintVisibleProperties
// SearchProperties
// ============================================================
TArray<FProperty*> MCPUtils::BlueprintVisibleProperties(UObject* Obj, const FString& Query)
TArray<FProperty*> MCPUtils::SearchProperties(UObject* Obj, const FString& Query, EPropertyFlags Flags, bool bLocal)
{
TArray<FProperty*> Result;
if (!Obj) return Result;
for (TFieldIterator<FProperty> PropIt(Obj->GetClass()); PropIt; ++PropIt)
UClass* ObjClass = Obj->GetClass();
for (TFieldIterator<FProperty> PropIt(ObjClass); PropIt; ++PropIt)
{
FProperty* Prop = *PropIt;
if (!Prop) continue;
if (!Prop->HasAnyPropertyFlags(CPF_BlueprintVisible)) continue;
if (Flags != 0 && !Prop->HasAnyPropertyFlags(Flags)) continue;
if (bLocal && Prop->GetOwnerStruct() != ObjClass) continue;
if (!Query.IsEmpty() && !FormatName(Prop).Contains(Query, ESearchCase::IgnoreCase))
continue;
Result.Add(Prop);

View File

@@ -59,11 +59,11 @@ private:
FString FormatPinSource(UEdGraphPin* Pin);
void Traverse(UEdGraphNode* Node);
void SortNodes();
void AssignNodeNames();
void EmitNode(UEdGraphNode* Node);
void EmitLocalVariables();
void EmitGraph();
void EmitNodeList();
void EmitDetails();
void EmitComments();
////////////////////////////////////////////////////////
//

View File

@@ -11,17 +11,11 @@ class UEdGraphPin;
// followed by zero or more comma-separated walker segments of the form
// "key:value". Each segment navigates from the current object to a child.
//
// Supported walkers:
// graph:Name - find a named UEdGraph within a UBlueprint
// node:Name - find a named UEdGraphNode within a UEdGraph
// pin:Name - find a named UEdGraphPin on a UEdGraphNode
// component:Name - find a named component in a Blueprint's SCS
// property:Name - follow an object-valued UProperty to its value
// levelblueprint - get the level blueprint from a UWorld
// The list of supported walkers is defined by GetWalkerTable().
//
// Example paths:
// /Game/Widgets/WB_Hotkeys,graph:ReadLuaConfiguration,node:Self_Reference_03,pin:Result
// /Game/Tangibles/TAN_Character,component:CharacterMesh0,property:SkeletalMeshAsset
// /Game/Tangibles/TAN_Character,component:CharacterMesh0
//
// Builder-style usage:
// MCPFetcher F(cb);
@@ -46,13 +40,31 @@ public:
MCPFetcher& Node(const FString& Value);
MCPFetcher& Pin(const FString& Value);
MCPFetcher& Component(const FString& Value);
MCPFetcher& Property(const FString& Value);
MCPFetcher& LevelBlueprint();
MCPFetcher& LevelBlueprint(const FString& Value);
MCPFetcher& MatExp(const FString& Value);
// C++-only walk step: resolve to the editable template
// (e.g. Blueprint → CDO, everything else → as-is).
MCPFetcher& Template();
// Parse string and walk multiple steps.
MCPFetcher& Walk(const FString& Path);
// Walker table entry.
struct FWalker
{
const TCHAR* Key; // e.g. "graph", "node", "matexp"
const TCHAR* Description; // brief help text
MCPFetcher& (MCPFetcher::*Func)(const FString& Value);
};
// Returns the static table of all supported walkers.
static const TArray<FWalker>& GetWalkerTable();
bool Ok() const { return !bError; }
const TArray<UObject*>& Visited() const { return Chain; }
void PreEdit() { MCPUtils::PreEdit(Chain); }
void PostEdit() { MCPUtils::PostEdit(Chain); }
template<class T> T *Cast()
{
if (bError) return nullptr;
@@ -63,8 +75,9 @@ public:
}
private:
TArray<UObject*> Chain;
static bool StrEq(const FString& A, const TCHAR* B) { return A.Equals(B, ESearchCase::IgnoreCase); }
void SetObj(UObject* InObj) { Obj = InObj; ResultPin = nullptr; }
void SetObj(UObject* InObj) { if (InObj) Chain.AddUnique(InObj); Obj = InObj; ResultPin = nullptr; }
void SetPin(UEdGraphPin* InPin) { ResultPin = InPin; Obj = nullptr; }
MCPFetcher& SetError(const FString& Msg);
MCPFetcher& TypeMismatch(const TCHAR* Walker, const TCHAR* Expected);

View File

@@ -148,32 +148,20 @@ public:
//
////////////////////////////////////////////////////////
static bool Identifies(const FString &Name, const UWorld *World);
static bool Identifies(const FString &Name, const UBlueprint *BP);
static bool Identifies(const FString &Name, const UActorComponent *C);
static bool Identifies(const FString &Name, const UEdGraph *Graph);
template<typename T>
static bool Identifies(const FString &Name, T&& Obj)
{
return FormatName(std::forward<T>(Obj)).Equals(Name, ESearchCase::IgnoreCase);
}
// UEdGraphNode also matches by GUID.
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 FMemberReference &Ref);
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 UMaterialInstance *MaterialInstance);
static bool Identifies(const FString &Name, const UMaterialFunction *MaterialFunction);
static bool Identifies(const FString &Name, const UMaterialExpression *Expression);
static bool Identifies(const FString &Name, const UStaticMesh *Mesh);
static bool Identifies(const FString &Name, const USkeletalMesh *Mesh);
static bool Identifies(const FString &Name, const UAnimSequence *Anim);
static bool Identifies(const FString &Name, const UBlendSpace *BlendSpace);
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 UEnum *Enum);
static bool Identifies(const FString &Name, const FProperty *Prop);
////////////////////////////////////////////////////////
static FString FormatPinType(const FEdGraphPinType& PinType);
static FString FormatPinType(UEdGraphPin* Pin);
static FString FormatNodeTitle(const UEdGraphNode *Node);
// ----- Asset path helpers -----
// Splits "/Game/Foo/Bar" into PackagePath="/Game/Foo" and AssetName="Bar".
@@ -282,12 +270,19 @@ public:
static FString ActionFullName(const TSharedPtr<FEdGraphSchemaAction>& Action);
static TArray<TSharedPtr<FEdGraphSchemaAction>> SearchGraphActions(UEdGraph* Graph, const FString& Query, int32 MaxResults = 0, bool ExactMatch = false);
// ----- Pre/Post edit -----
// Call before and after modifying objects. Walks the list looking for
// known parent types (UMaterial, UBlueprint, etc.) and issues the
// appropriate notifications.
static void PreEdit(const TArray<UObject*>& Objects);
static void PostEdit(const TArray<UObject*>& Objects);
// ----- 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 UObject* GetTemplate(UObject* Obj, MCPErrorCallback Error);
static TArray<FProperty*> SearchProperties(UObject* Obj, const FString& Query, EPropertyFlags Flags, bool bLocal);
static FProperty* FindPropertyByName(UObject* Obj, const FString& Name, MCPErrorCallback Error = nullptr);
static FString GetPropertyValueText(UObject* Container, FProperty* Prop);

View File

@@ -1720,7 +1720,6 @@ p_thread(Info *info) { /* ... thread */
/* Write general information. */
WRITE_VALUE(thread->status, uint8_t);
WRITE_VALUE(thread->nextid, uint64_t);
WRITE_VALUE(eris_savestackidx(thread,
eris_restorestack(thread, thread->errfunc)), size_t);
/* These are only used while a thread is being executed or can be deduced:
@@ -1887,7 +1886,6 @@ u_thread(Info *info) { /* ... */
/* Read general information. */
thread->status = READ_VALUE(uint8_t);
thread->nextid = READ_VALUE(uint64_t);
thread->errfunc = eris_savestack(thread,
eris_restorestackidx(thread, READ_VALUE(size_t)));
if (thread->errfunc) {

Some files were not shown because too many files have changed in this diff Show More