More work on graph editing
This commit is contained in:
BIN
Content/Testing/M_Test.uasset
LFS
BIN
Content/Testing/M_Test.uasset
LFS
Binary file not shown.
@@ -6,6 +6,9 @@
|
|||||||
#include "MCPUtils.h"
|
#include "MCPUtils.h"
|
||||||
#include "EdGraph/EdGraph.h"
|
#include "EdGraph/EdGraph.h"
|
||||||
#include "EdGraph/EdGraphNode.h"
|
#include "EdGraph/EdGraphNode.h"
|
||||||
|
#include "MaterialGraph/MaterialGraphNode.h"
|
||||||
|
#include "Materials/Material.h"
|
||||||
|
#include "IMaterialEditor.h"
|
||||||
#include "GraphNode_Delete.generated.h"
|
#include "GraphNode_Delete.generated.h"
|
||||||
|
|
||||||
|
|
||||||
@@ -47,8 +50,21 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
F.PreEdit();
|
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();
|
F.PostEdit();
|
||||||
|
|
||||||
Result.Appendf(TEXT("Deleted node '%s' from graph '%s'.\n"), *NodeTitle, *GraphName);
|
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);
|
bool bEditable = !Prop->HasAnyPropertyFlags(CPF_EditConst);
|
||||||
Out.Appendf(TEXT(" %s %s %s = %s\n"),
|
Out.Appendf(TEXT(" %s %s %s = %s\n"),
|
||||||
bEditable ? TEXT("editable") : TEXT("readonly"),
|
bEditable ? TEXT("mxeditable") : TEXT("mxreadonly"),
|
||||||
*MCPUtils::FormatPropertyType(Prop),
|
*MCPUtils::FormatPropertyType(Prop),
|
||||||
*MCPUtils::FormatName(Prop),
|
*MCPUtils::FormatName(Prop),
|
||||||
*ValueStr);
|
*ValueStr);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "MaterialGraph/MaterialGraph.h"
|
#include "MaterialGraph/MaterialGraph.h"
|
||||||
#include "MaterialGraph/MaterialGraphNode.h"
|
#include "MaterialGraph/MaterialGraphNode.h"
|
||||||
#include "Engine/LevelScriptBlueprint.h"
|
#include "Engine/LevelScriptBlueprint.h"
|
||||||
|
#include "Subsystems/AssetEditorSubsystem.h"
|
||||||
|
|
||||||
MCPFetcher& MCPFetcher::SetError(const FString& Msg)
|
MCPFetcher& MCPFetcher::SetError(const FString& Msg)
|
||||||
{
|
{
|
||||||
@@ -86,7 +87,18 @@ MCPFetcher& MCPFetcher::Asset(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));
|
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 this is a material open in the editor, use the editor's transient copy.
|
||||||
if (UMaterial* Mat = ::Cast<UMaterial>(Obj))
|
if (UMaterial* Mat = ::Cast<UMaterial>(Obj))
|
||||||
|
|||||||
@@ -1126,6 +1126,7 @@ void MCPUtils::PreEdit(const TArray<UObject*>& Objects)
|
|||||||
|
|
||||||
void MCPUtils::PostEdit(const TArray<UObject*>& Objects)
|
void MCPUtils::PostEdit(const TArray<UObject*>& Objects)
|
||||||
{
|
{
|
||||||
|
TSet<UEdGraph*> Graphs;
|
||||||
TSet<UMaterial*> Materials;
|
TSet<UMaterial*> Materials;
|
||||||
TSet<UBlueprint*> Blueprints;
|
TSet<UBlueprint*> Blueprints;
|
||||||
for (int32 i = Objects.Num() - 1; i >= 0; --i)
|
for (int32 i = Objects.Num() - 1; i >= 0; --i)
|
||||||
@@ -1134,6 +1135,9 @@ void MCPUtils::PostEdit(const TArray<UObject*>& Objects)
|
|||||||
Obj->PostEditChange();
|
Obj->PostEditChange();
|
||||||
Obj->MarkPackageDirty();
|
Obj->MarkPackageDirty();
|
||||||
|
|
||||||
|
if (UEdGraph* Graph = Cast<UEdGraph>(Obj))
|
||||||
|
Graphs.Add(Graph);
|
||||||
|
|
||||||
if (UBlueprint* BP = Cast<UBlueprint>(Obj))
|
if (UBlueprint* BP = Cast<UBlueprint>(Obj))
|
||||||
Blueprints.Add(BP);
|
Blueprints.Add(BP);
|
||||||
|
|
||||||
@@ -1141,6 +1145,8 @@ void MCPUtils::PostEdit(const TArray<UObject*>& Objects)
|
|||||||
if (UMaterial* BaseMat = MatIface->GetMaterial())
|
if (UMaterial* BaseMat = MatIface->GetMaterial())
|
||||||
Materials.Add(BaseMat);
|
Materials.Add(BaseMat);
|
||||||
}
|
}
|
||||||
|
for (UEdGraph* Graph : Graphs)
|
||||||
|
Graph->NotifyGraphChanged();
|
||||||
for (UMaterial *Material : Materials)
|
for (UMaterial *Material : Materials)
|
||||||
UMaterialEditingLibrary::RebuildMaterialInstanceEditors(Material);
|
UMaterialEditingLibrary::RebuildMaterialInstanceEditors(Material);
|
||||||
for (UBlueprint *Blueprint : Blueprints)
|
for (UBlueprint *Blueprint : Blueprints)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "MCPUtils.h"
|
#include "MCPUtils.h"
|
||||||
|
|
||||||
class UEdGraphPin;
|
class UEdGraphPin;
|
||||||
|
class IAssetEditorInstance;
|
||||||
|
|
||||||
// Resolves a path string into a UObject or UEdGraphPin.
|
// Resolves a path string into a UObject or UEdGraphPin.
|
||||||
//
|
//
|
||||||
@@ -29,6 +30,8 @@ class MCPFetcher
|
|||||||
public:
|
public:
|
||||||
bool bError = false;
|
bool bError = false;
|
||||||
UObject* Obj = nullptr;
|
UObject* Obj = nullptr;
|
||||||
|
UObject* OriginalAsset = nullptr;
|
||||||
|
IAssetEditorInstance* Editor = nullptr;
|
||||||
UEdGraphPin* ResultPin = nullptr;
|
UEdGraphPin* ResultPin = nullptr;
|
||||||
MCPErrorCallback ErrorCB = nullptr;
|
MCPErrorCallback ErrorCB = nullptr;
|
||||||
|
|
||||||
@@ -69,6 +72,30 @@ public:
|
|||||||
static const TArray<FWalker>& GetWalkerTable();
|
static const TArray<FWalker>& GetWalkerTable();
|
||||||
|
|
||||||
bool Ok() const { return !bError; }
|
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; }
|
const TArray<UObject*>& Visited() const { return Chain; }
|
||||||
void PreEdit() { MCPUtils::PreEdit(Chain); }
|
void PreEdit() { MCPUtils::PreEdit(Chain); }
|
||||||
void PostEdit() { MCPUtils::PostEdit(Chain); }
|
void PostEdit() { MCPUtils::PostEdit(Chain); }
|
||||||
|
|||||||
Reference in New Issue
Block a user