More work on graph editing
This commit is contained in:
@@ -6,6 +6,9 @@
|
||||
#include "MCPUtils.h"
|
||||
#include "EdGraph/EdGraph.h"
|
||||
#include "EdGraph/EdGraphNode.h"
|
||||
#include "MaterialGraph/MaterialGraphNode.h"
|
||||
#include "Materials/Material.h"
|
||||
#include "IMaterialEditor.h"
|
||||
#include "GraphNode_Delete.generated.h"
|
||||
|
||||
|
||||
@@ -47,8 +50,21 @@ public:
|
||||
}
|
||||
|
||||
F.PreEdit();
|
||||
FoundNode->BreakAllNodeLinks();
|
||||
Graph->RemoveNode(FoundNode);
|
||||
|
||||
if (Cast<UMaterialGraphNode>(FoundNode))
|
||||
{
|
||||
// Use the material editor's DeleteNodes to properly remove
|
||||
// both the graph node and the underlying material expression.
|
||||
IMaterialEditor* MatEditor = F.CastEditor<UMaterial, IMaterialEditor>();
|
||||
if (!MatEditor) return;
|
||||
MatEditor->DeleteNodes({FoundNode});
|
||||
}
|
||||
else
|
||||
{
|
||||
FoundNode->BreakAllNodeLinks();
|
||||
Graph->RemoveNode(FoundNode);
|
||||
}
|
||||
|
||||
F.PostEdit();
|
||||
|
||||
Result.Appendf(TEXT("Deleted node '%s' from graph '%s'.\n"), *NodeTitle, *GraphName);
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "MCPHandler.h"
|
||||
#include "MCPFetcher.h"
|
||||
#include "MCPUtils.h"
|
||||
#include "EdGraph/EdGraphPin.h"
|
||||
#include "EdGraphSchema_K2.h"
|
||||
#include "MaterialGraph/MaterialGraphSchema.h"
|
||||
#include "MaterialGraph/MaterialGraphNode.h"
|
||||
#include "GraphNode_SetDefaults.generated.h"
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
USTRUCT()
|
||||
struct FSetNodeDefaultEntry
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY()
|
||||
FString Node;
|
||||
|
||||
UPROPERTY()
|
||||
FString Name;
|
||||
|
||||
UPROPERTY()
|
||||
FString Value;
|
||||
};
|
||||
|
||||
|
||||
UCLASS()
|
||||
class UMCP_GraphNode_SetDefaults : public UObject, public IMCPHandler
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(meta=(Description="Graph path, e.g. /Game/Foo,graph:EventGraph"))
|
||||
FString Graph;
|
||||
|
||||
UPROPERTY(meta=(Description="Array of {node, name, value} objects"))
|
||||
FMCPJsonArray Pins;
|
||||
|
||||
virtual FString GetDescription() const override
|
||||
{
|
||||
return TEXT("Set the default value of input pins or material expression properties on nodes.");
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// K2 graphs: set pin default values.
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
void HandleK2Entry(const FSetNodeDefaultEntry& Entry, UEdGraph* GraphObj, const UEdGraphSchema_K2* K2Schema,
|
||||
TSet<UEdGraphNode*>& ModifiedNodes, FStringBuilderBase& Result)
|
||||
{
|
||||
MCPFetcher F(Result, GraphObj);
|
||||
UEdGraphPin* Pin = F.Node(Entry.Node).Pin(Entry.Name).Cast<UEdGraphPin>();
|
||||
if (!Pin) return;
|
||||
|
||||
UEdGraphNode* Node = Pin->GetOwningNode();
|
||||
|
||||
if (Pin->Direction != EGPD_Input)
|
||||
{
|
||||
Result.Appendf(TEXT("error: %s is an output pin\n"), *MCPUtils::FormatName(Pin));
|
||||
return;
|
||||
}
|
||||
|
||||
Pin->Modify();
|
||||
|
||||
FString UseDefaultValue;
|
||||
TObjectPtr<UObject> UseDefaultObject = nullptr;
|
||||
FText UseDefaultText;
|
||||
K2Schema->GetPinDefaultValuesFromString(Pin->PinType, Node, Entry.Value, UseDefaultValue, UseDefaultObject, UseDefaultText, false);
|
||||
FString Error = K2Schema->IsPinDefaultValid(Pin, UseDefaultValue, UseDefaultObject, UseDefaultText);
|
||||
if (!Error.IsEmpty())
|
||||
{
|
||||
Result.Appendf(TEXT("error: %s: %s\n"), *MCPUtils::FormatName(Pin), *Error);
|
||||
return;
|
||||
}
|
||||
K2Schema->TrySetDefaultValue(*Pin, Entry.Value);
|
||||
|
||||
ModifiedNodes.Add(Node);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Material graphs: set material expression properties.
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
void HandleMaterialEntry(const FSetNodeDefaultEntry& Entry, UEdGraph* GraphObj,
|
||||
TSet<UEdGraphNode*>& ModifiedNodes, FStringBuilderBase& Result)
|
||||
{
|
||||
MCPFetcher F(Result, GraphObj);
|
||||
UMaterialGraphNode* MatNode = F.Node(Entry.Node).Cast<UMaterialGraphNode>();
|
||||
if (!MatNode) return;
|
||||
if (!MatNode->MaterialExpression)
|
||||
{
|
||||
Result.Appendf(TEXT("error: %s has no material expression\n"), *MCPUtils::FormatName(MatNode));
|
||||
return;
|
||||
}
|
||||
|
||||
UMaterialExpression* Expression = MatNode->MaterialExpression;
|
||||
FProperty* Prop = MCPUtils::FindPropertyByName(Expression, Entry.Name, Result);
|
||||
if (!Prop) return;
|
||||
|
||||
if (!MCPUtils::SetPropertyValueText(Expression, Prop, Entry.Value, Result))
|
||||
return;
|
||||
|
||||
Expression->ForcePropertyValueChanged(Prop);
|
||||
ModifiedNodes.Add(MatNode);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
virtual void Handle(FStringBuilderBase& Result) override
|
||||
{
|
||||
// Fetch the graph once.
|
||||
MCPFetcher GraphFetcher(Result);
|
||||
UEdGraph* GraphObj = GraphFetcher.Walk(Graph).Cast<UEdGraph>();
|
||||
if (!GraphObj) return;
|
||||
|
||||
const UEdGraphSchema* Schema = GraphObj->GetSchema();
|
||||
const UEdGraphSchema_K2* K2Schema = Cast<UEdGraphSchema_K2>(Schema);
|
||||
const UMaterialGraphSchema* MGSchema = Cast<UMaterialGraphSchema>(Schema);
|
||||
|
||||
if (!K2Schema && !MGSchema)
|
||||
{
|
||||
Result.Appendf(TEXT("error: unsupported graph schema %s\n"), *Schema->GetClass()->GetName());
|
||||
return;
|
||||
}
|
||||
|
||||
TSet<UEdGraphNode*> ModifiedNodes;
|
||||
|
||||
GraphFetcher.PreEdit();
|
||||
for (const TSharedPtr<FJsonValue>& PinVal : Pins.Array)
|
||||
{
|
||||
FSetNodeDefaultEntry Entry;
|
||||
if (!MCPUtils::PopulateFromJson(FSetNodeDefaultEntry::StaticStruct(), &Entry, PinVal, Result))
|
||||
continue;
|
||||
|
||||
if (K2Schema)
|
||||
HandleK2Entry(Entry, GraphObj, K2Schema, ModifiedNodes, Result);
|
||||
else if (MGSchema)
|
||||
HandleMaterialEntry(Entry, GraphObj, ModifiedNodes, Result);
|
||||
}
|
||||
|
||||
for (UEdGraphNode* Node : ModifiedNodes)
|
||||
{
|
||||
Node->ReconstructNode();
|
||||
}
|
||||
GraphFetcher.PostEdit();
|
||||
|
||||
Result.Appendf(TEXT("Done.\n"));
|
||||
}
|
||||
};
|
||||
@@ -1,114 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "MCPHandler.h"
|
||||
#include "MCPFetcher.h"
|
||||
#include "MCPUtils.h"
|
||||
#include "EdGraph/EdGraphPin.h"
|
||||
#include "EdGraphSchema_K2.h"
|
||||
#include "GraphPin_SetDefault.generated.h"
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
USTRUCT()
|
||||
struct FSetPinDefaultEntry
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY()
|
||||
FString Node;
|
||||
|
||||
UPROPERTY()
|
||||
FString Pin;
|
||||
|
||||
UPROPERTY()
|
||||
FString Value;
|
||||
};
|
||||
|
||||
|
||||
UCLASS()
|
||||
class UMCP_GraphPin_SetDefault : public UObject, public IMCPHandler
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(meta=(Description="Graph path, e.g. /Game/Foo,graph:EventGraph"))
|
||||
FString Graph;
|
||||
|
||||
UPROPERTY(meta=(Description="Array of {node, pin, value} objects"))
|
||||
FMCPJsonArray Pins;
|
||||
|
||||
virtual FString GetDescription() const override
|
||||
{
|
||||
return TEXT("Set the default value of input pins on nodes.");
|
||||
}
|
||||
|
||||
virtual void Handle(FStringBuilderBase& Result) override
|
||||
{
|
||||
// Fetch the graph once.
|
||||
MCPFetcher GraphFetcher(Result);
|
||||
UEdGraph* GraphObj = GraphFetcher.Walk(Graph).Cast<UEdGraph>();
|
||||
if (!GraphObj) return;
|
||||
|
||||
int32 SuccessCount = 0;
|
||||
int32 TotalCount = Pins.Array.Num();
|
||||
TSet<UEdGraphNode*> ModifiedNodes;
|
||||
|
||||
GraphFetcher.PreEdit();
|
||||
for (const TSharedPtr<FJsonValue>& PinVal : Pins.Array)
|
||||
{
|
||||
FSetPinDefaultEntry Entry;
|
||||
if (!MCPUtils::PopulateFromJson(FSetPinDefaultEntry::StaticStruct(), &Entry, PinVal, Result))
|
||||
continue;
|
||||
|
||||
MCPFetcher F(Result, GraphObj);
|
||||
UEdGraphPin* Pin = F.Node(Entry.Node).Pin(Entry.Pin).Cast<UEdGraphPin>();
|
||||
if (!Pin) continue;
|
||||
|
||||
UEdGraphNode* Node = Pin->GetOwningNode();
|
||||
|
||||
if (Pin->Direction != EGPD_Input)
|
||||
{
|
||||
Result.Appendf(TEXT("error: %s is an output pin\n"), *MCPUtils::FormatName(Pin));
|
||||
continue;
|
||||
}
|
||||
|
||||
Pin->Modify();
|
||||
|
||||
// K2 schemas support validation before setting; other schemas (e.g. material) don't.
|
||||
const UEdGraphSchema_K2* K2Schema = Cast<UEdGraphSchema_K2>(Node->GetGraph()->GetSchema());
|
||||
if (K2Schema)
|
||||
{
|
||||
FString UseDefaultValue;
|
||||
TObjectPtr<UObject> UseDefaultObject = nullptr;
|
||||
FText UseDefaultText;
|
||||
K2Schema->GetPinDefaultValuesFromString(Pin->PinType, Node, Entry.Value, UseDefaultValue, UseDefaultObject, UseDefaultText, false);
|
||||
FString Error = K2Schema->IsPinDefaultValid(Pin, UseDefaultValue, UseDefaultObject, UseDefaultText);
|
||||
if (!Error.IsEmpty())
|
||||
{
|
||||
Result.Appendf(TEXT("error: %s: %s\n"), *MCPUtils::FormatName(Pin), *Error);
|
||||
continue;
|
||||
}
|
||||
K2Schema->TrySetDefaultValue(*Pin, Entry.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
Node->GetGraph()->GetSchema()->TrySetDefaultValue(*Pin, Entry.Value);
|
||||
}
|
||||
|
||||
SuccessCount++;
|
||||
ModifiedNodes.Add(Node);
|
||||
}
|
||||
|
||||
for (UEdGraphNode* Node : ModifiedNodes)
|
||||
{
|
||||
Node->ReconstructNode();
|
||||
}
|
||||
GraphFetcher.PostEdit();
|
||||
|
||||
Result.Appendf(TEXT("Set %d/%d pin defaults.\n"), SuccessCount, TotalCount);
|
||||
}
|
||||
};
|
||||
@@ -288,7 +288,7 @@ void FlxBlueprintExporter::EmitMaterialProperty(UMaterialExpression* Expression,
|
||||
|
||||
bool bEditable = !Prop->HasAnyPropertyFlags(CPF_EditConst);
|
||||
Out.Appendf(TEXT(" %s %s %s = %s\n"),
|
||||
bEditable ? TEXT("editable") : TEXT("readonly"),
|
||||
bEditable ? TEXT("mxeditable") : TEXT("mxreadonly"),
|
||||
*MCPUtils::FormatPropertyType(Prop),
|
||||
*MCPUtils::FormatName(Prop),
|
||||
*ValueStr);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "MaterialGraph/MaterialGraph.h"
|
||||
#include "MaterialGraph/MaterialGraphNode.h"
|
||||
#include "Engine/LevelScriptBlueprint.h"
|
||||
#include "Subsystems/AssetEditorSubsystem.h"
|
||||
|
||||
MCPFetcher& MCPFetcher::SetError(const FString& Msg)
|
||||
{
|
||||
@@ -86,7 +87,18 @@ MCPFetcher& MCPFetcher::Asset(const FString& PackagePath)
|
||||
{
|
||||
SetObj(LoadObject<UObject>(nullptr, *PackagePath));
|
||||
if (!Obj)
|
||||
SetError(FString::Printf(TEXT("Could not load asset '%s'"), *PackagePath));
|
||||
return SetError(FString::Printf(TEXT("Could not load asset '%s'"), *PackagePath));
|
||||
|
||||
OriginalAsset = Obj;
|
||||
|
||||
// Open the editor for this asset (or bring it to front if already open).
|
||||
UAssetEditorSubsystem* Sub = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
|
||||
if (!Sub || !Sub->OpenEditorForAsset(Obj))
|
||||
return SetError(FString::Printf(TEXT("Could not open editor for '%s'"), *PackagePath));
|
||||
|
||||
Editor = Sub->FindEditorForAsset(OriginalAsset, false);
|
||||
if (!Editor)
|
||||
return SetError(FString::Printf(TEXT("Could not find editor instance for '%s'"), *PackagePath));
|
||||
|
||||
// If this is a material open in the editor, use the editor's transient copy.
|
||||
if (UMaterial* Mat = ::Cast<UMaterial>(Obj))
|
||||
|
||||
@@ -1126,6 +1126,7 @@ void MCPUtils::PreEdit(const TArray<UObject*>& Objects)
|
||||
|
||||
void MCPUtils::PostEdit(const TArray<UObject*>& Objects)
|
||||
{
|
||||
TSet<UEdGraph*> Graphs;
|
||||
TSet<UMaterial*> Materials;
|
||||
TSet<UBlueprint*> Blueprints;
|
||||
for (int32 i = Objects.Num() - 1; i >= 0; --i)
|
||||
@@ -1134,6 +1135,9 @@ void MCPUtils::PostEdit(const TArray<UObject*>& Objects)
|
||||
Obj->PostEditChange();
|
||||
Obj->MarkPackageDirty();
|
||||
|
||||
if (UEdGraph* Graph = Cast<UEdGraph>(Obj))
|
||||
Graphs.Add(Graph);
|
||||
|
||||
if (UBlueprint* BP = Cast<UBlueprint>(Obj))
|
||||
Blueprints.Add(BP);
|
||||
|
||||
@@ -1141,6 +1145,8 @@ void MCPUtils::PostEdit(const TArray<UObject*>& Objects)
|
||||
if (UMaterial* BaseMat = MatIface->GetMaterial())
|
||||
Materials.Add(BaseMat);
|
||||
}
|
||||
for (UEdGraph* Graph : Graphs)
|
||||
Graph->NotifyGraphChanged();
|
||||
for (UMaterial *Material : Materials)
|
||||
UMaterialEditingLibrary::RebuildMaterialInstanceEditors(Material);
|
||||
for (UBlueprint *Blueprint : Blueprints)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "MCPUtils.h"
|
||||
|
||||
class UEdGraphPin;
|
||||
class IAssetEditorInstance;
|
||||
|
||||
// Resolves a path string into a UObject or UEdGraphPin.
|
||||
//
|
||||
@@ -29,6 +30,8 @@ class MCPFetcher
|
||||
public:
|
||||
bool bError = false;
|
||||
UObject* Obj = nullptr;
|
||||
UObject* OriginalAsset = nullptr;
|
||||
IAssetEditorInstance* Editor = nullptr;
|
||||
UEdGraphPin* ResultPin = nullptr;
|
||||
MCPErrorCallback ErrorCB = nullptr;
|
||||
|
||||
@@ -69,6 +72,30 @@ public:
|
||||
static const TArray<FWalker>& GetWalkerTable();
|
||||
|
||||
bool Ok() const { return !bError; }
|
||||
UObject* GetAsset() const { return OriginalAsset; }
|
||||
template<class T> T* CastAsset()
|
||||
{
|
||||
if (bError) return nullptr;
|
||||
T* Result = ::Cast<T>(OriginalAsset);
|
||||
if (!Result)
|
||||
SetError(FString::Printf(TEXT("Asset is %s, expected %s"),
|
||||
OriginalAsset ? *OriginalAsset->GetClass()->GetName() : TEXT("null"),
|
||||
*T::StaticClass()->GetName()));
|
||||
return Result;
|
||||
}
|
||||
template<class AssetType, class EditorType>
|
||||
EditorType* CastEditor()
|
||||
{
|
||||
if (bError) return nullptr;
|
||||
if (!OriginalAsset || !OriginalAsset->IsA<AssetType>())
|
||||
{
|
||||
SetError(FString::Printf(TEXT("Asset is %s, expected %s"),
|
||||
OriginalAsset ? *OriginalAsset->GetClass()->GetName() : TEXT("null"),
|
||||
*AssetType::StaticClass()->GetName()));
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<EditorType*>(Editor);
|
||||
}
|
||||
const TArray<UObject*>& Visited() const { return Chain; }
|
||||
void PreEdit() { MCPUtils::PreEdit(Chain); }
|
||||
void PostEdit() { MCPUtils::PostEdit(Chain); }
|
||||
|
||||
Reference in New Issue
Block a user