Removed all code to save packages from the MCP
This commit is contained in:
867
Plugins/UEWingman/Deprecated/OldWingUtils.cpp
Normal file
867
Plugins/UEWingman/Deprecated/OldWingUtils.cpp
Normal file
@@ -0,0 +1,867 @@
|
|||||||
|
#include "WingUtils.h"
|
||||||
|
#include "WingJson.h"
|
||||||
|
#include "WingTypes.h"
|
||||||
|
#include "WingServer.h"
|
||||||
|
#include "WingHandler.h"
|
||||||
|
#include "Engine/Blueprint.h"
|
||||||
|
#include "Engine/MemberReference.h"
|
||||||
|
#include "Engine/World.h"
|
||||||
|
#include "Components/ActorComponent.h"
|
||||||
|
#include "Engine/SCS_Node.h"
|
||||||
|
#include "EdGraph/EdGraph.h"
|
||||||
|
#include "EdGraph/EdGraphNode.h"
|
||||||
|
#include "EdGraph/EdGraphPin.h"
|
||||||
|
#include "K2Node_EditablePinBase.h"
|
||||||
|
#include "EdGraph/EdGraphSchema.h"
|
||||||
|
#include "Kismet2/BlueprintEditorUtils.h"
|
||||||
|
#include "Kismet2/KismetEditorUtilities.h"
|
||||||
|
#include "UObject/SavePackage.h"
|
||||||
|
#include "UObject/UObjectIterator.h"
|
||||||
|
#include "UObject/UnrealType.h"
|
||||||
|
#include "Misc/Paths.h"
|
||||||
|
#include "Misc/PackageName.h"
|
||||||
|
|
||||||
|
// Animation Blueprint support
|
||||||
|
#include "AnimStateNode.h"
|
||||||
|
#include "AnimStateTransitionNode.h"
|
||||||
|
#include "AnimationStateMachineGraph.h"
|
||||||
|
|
||||||
|
// Material support
|
||||||
|
#include "Materials/Material.h"
|
||||||
|
#include "Materials/MaterialExpression.h"
|
||||||
|
#include "Materials/MaterialFunction.h"
|
||||||
|
#include "Materials/MaterialInstanceConstant.h"
|
||||||
|
#include "MaterialGraph/MaterialGraph.h"
|
||||||
|
#include "MaterialGraph/MaterialGraphSchema.h"
|
||||||
|
#include "IMaterialEditor.h"
|
||||||
|
#include "Subsystems/AssetEditorSubsystem.h"
|
||||||
|
|
||||||
|
// Mesh, animation, texture support
|
||||||
|
#include "Engine/StaticMesh.h"
|
||||||
|
#include "Engine/SkeletalMesh.h"
|
||||||
|
#include "Animation/AnimSequence.h"
|
||||||
|
#include "Animation/BlendSpace.h"
|
||||||
|
#include "Engine/Texture.h"
|
||||||
|
|
||||||
|
// SEH support (Windows only) — defined in BlueprintWingServer.cpp
|
||||||
|
#if PLATFORM_WINDOWS
|
||||||
|
extern int32 TryCompileBlueprintSEH(UBlueprint* BP, EBlueprintCompileOptions Opts);
|
||||||
|
extern int32 TrySavePackageSEH(
|
||||||
|
UPackage* Package, UObject* Asset, const TCHAR* Filename,
|
||||||
|
FSavePackageArgs* SaveArgs, ESavePackageResult* OutResult);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Name sanitization
|
||||||
|
//
|
||||||
|
// Our parsers reserve certain punctuation marks for parsing
|
||||||
|
// types, paths, and the like. For example: Array<Int>.
|
||||||
|
// We therefore cannot allow those specific punctuation marks
|
||||||
|
// in names. We replace them with similar-looking unicode
|
||||||
|
// characters.
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void WingUtils::SanitizeNameInPlace(FString &Name)
|
||||||
|
{
|
||||||
|
int32 Dst = 0;
|
||||||
|
for (int32 Src = 0; Src < Name.Len(); Src++)
|
||||||
|
{
|
||||||
|
TCHAR c = Name[Src];
|
||||||
|
if (c < 0x20 || c == 0x7F) continue;
|
||||||
|
if (c == ' ') c=L'·';
|
||||||
|
if (c == '<') c=L'◁';
|
||||||
|
if (c == '>') c=L'▷';
|
||||||
|
if (c == ',') c=L'·';
|
||||||
|
Name[Dst++] = c;
|
||||||
|
}
|
||||||
|
if (Dst == 0) Name[Dst++] = L'·';
|
||||||
|
Name.LeftInline(Dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
FString WingUtils::SanitizeName(const FString &Name)
|
||||||
|
{
|
||||||
|
FString Result = Name;
|
||||||
|
SanitizeNameInPlace(Result);
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
FString WingUtils::SanitizeName(FName Name)
|
||||||
|
{
|
||||||
|
FString Result = Name.ToString();
|
||||||
|
SanitizeNameInPlace(Result);
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Name Lookup
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
FString WingUtils::FormatName(const UWorld *World)
|
||||||
|
{
|
||||||
|
return World->GetPathName();
|
||||||
|
}
|
||||||
|
|
||||||
|
FString WingUtils::FormatName(const UBlueprint *BP)
|
||||||
|
{
|
||||||
|
return UWingTypes::TypeToTextOrDie(BP);
|
||||||
|
}
|
||||||
|
|
||||||
|
FString WingUtils::FormatName(const UActorComponent *C)
|
||||||
|
{
|
||||||
|
return SanitizeName(C->GetName());
|
||||||
|
}
|
||||||
|
|
||||||
|
FString WingUtils::FormatName(const USCS_Node *Node)
|
||||||
|
{
|
||||||
|
return SanitizeName(Node->GetVariableName());
|
||||||
|
}
|
||||||
|
|
||||||
|
FString WingUtils::FormatName(const UEdGraph *Graph)
|
||||||
|
{
|
||||||
|
return SanitizeName(Graph->GetName());
|
||||||
|
}
|
||||||
|
|
||||||
|
FString WingUtils::FormatName(const UEdGraphNode* Node)
|
||||||
|
{
|
||||||
|
return SanitizeName(Node->GetName());
|
||||||
|
}
|
||||||
|
|
||||||
|
FString WingUtils::FormatName(const UEdGraphPin *Pin)
|
||||||
|
{
|
||||||
|
return SanitizeName(Pin->GetName());
|
||||||
|
}
|
||||||
|
|
||||||
|
FString WingUtils::FormatName(const FMemberReference &Ref)
|
||||||
|
{
|
||||||
|
return SanitizeName(Ref.GetMemberName());
|
||||||
|
}
|
||||||
|
|
||||||
|
FString WingUtils::FormatName(const FBPVariableDescription &Var)
|
||||||
|
{
|
||||||
|
return SanitizeName(Var.VarName);
|
||||||
|
}
|
||||||
|
|
||||||
|
FString WingUtils::FormatName(const UStruct *Struct)
|
||||||
|
{
|
||||||
|
if (Cast<UScriptStruct>(Struct) || Cast<UClass>(Struct))
|
||||||
|
return UWingTypes::TypeToTextOrDie(Struct);
|
||||||
|
return SanitizeName(Struct->GetName());
|
||||||
|
}
|
||||||
|
|
||||||
|
FString WingUtils::FormatName(const UClass *Class)
|
||||||
|
{
|
||||||
|
return UWingTypes::TypeToTextOrDie(Class);
|
||||||
|
}
|
||||||
|
|
||||||
|
FString WingUtils::FormatName(const UMaterial *Material)
|
||||||
|
{
|
||||||
|
return Material->GetPathName();
|
||||||
|
}
|
||||||
|
|
||||||
|
FString WingUtils::FormatName(const UMaterialInstance *MaterialInstance)
|
||||||
|
{
|
||||||
|
return MaterialInstance->GetPathName();
|
||||||
|
}
|
||||||
|
|
||||||
|
FString WingUtils::FormatName(const UMaterialFunction *MaterialFunction)
|
||||||
|
{
|
||||||
|
return MaterialFunction->GetPathName();
|
||||||
|
}
|
||||||
|
|
||||||
|
FString WingUtils::FormatName(const UMaterialExpression *Expression)
|
||||||
|
{
|
||||||
|
return SanitizeName(Expression->GetName());
|
||||||
|
}
|
||||||
|
|
||||||
|
FString WingUtils::FormatName(const UStaticMesh *Mesh)
|
||||||
|
{
|
||||||
|
return Mesh->GetPathName();
|
||||||
|
}
|
||||||
|
|
||||||
|
FString WingUtils::FormatName(const USkeletalMesh *Mesh)
|
||||||
|
{
|
||||||
|
return Mesh->GetPathName();
|
||||||
|
}
|
||||||
|
|
||||||
|
FString WingUtils::FormatName(const UAnimSequence *Anim)
|
||||||
|
{
|
||||||
|
return Anim->GetPathName();
|
||||||
|
}
|
||||||
|
|
||||||
|
FString WingUtils::FormatName(const UBlendSpace *BlendSpace)
|
||||||
|
{
|
||||||
|
return BlendSpace->GetPathName();
|
||||||
|
}
|
||||||
|
|
||||||
|
FString WingUtils::FormatName(const UTexture *Texture)
|
||||||
|
{
|
||||||
|
return Texture->GetPathName();
|
||||||
|
}
|
||||||
|
|
||||||
|
FString WingUtils::FormatName(const UScriptStruct *Struct)
|
||||||
|
{
|
||||||
|
return UWingTypes::TypeToTextOrDie(Struct);
|
||||||
|
}
|
||||||
|
|
||||||
|
FString WingUtils::FormatName(const UEnum *Enum)
|
||||||
|
{
|
||||||
|
return UWingTypes::TypeToTextOrDie(Enum);
|
||||||
|
}
|
||||||
|
|
||||||
|
FString WingUtils::FormatName(const FProperty *Prop)
|
||||||
|
{
|
||||||
|
return SanitizeName(Prop->GetName());
|
||||||
|
}
|
||||||
|
|
||||||
|
FString WingUtils::FormatName(const FUserPinInfo &Pin)
|
||||||
|
{
|
||||||
|
return SanitizeName(Pin.PinName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Formatting other things
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
FString WingUtils::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
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Text formatting
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
FString WingUtils::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();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Enum helpers
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
FString WingUtils::EnumToString(UEnum* Enum, int64 Value, const FString& Prefix)
|
||||||
|
{
|
||||||
|
FString Full = Enum->GetNameStringByValue(Value);
|
||||||
|
if (!Prefix.IsEmpty() && Full.StartsWith(Prefix))
|
||||||
|
return Full.Mid(Prefix.Len());
|
||||||
|
return Full;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WingUtils::StringToEnum(UEnum* Enum, const FString& Str, int64& OutValue, const FString& Prefix)
|
||||||
|
{
|
||||||
|
OutValue = Enum->GetValueByNameString(Prefix + Str);
|
||||||
|
if (OutValue == INDEX_NONE)
|
||||||
|
{
|
||||||
|
UWingServer::Printf(TEXT("ERROR: Invalid value '%s' for %s\n"), *Str, *Enum->GetName());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Common Error Reporting
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
bool WingUtils::CheckExactlyOneNamed(int Count, const FString &Kind, const FString &Name)
|
||||||
|
{
|
||||||
|
if (Count == 0)
|
||||||
|
{
|
||||||
|
UWingServer::Printf(TEXT("Could not find a %s named %s.\n"), *Kind, *Name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (Count > 1)
|
||||||
|
{
|
||||||
|
UWingServer::Printf(TEXT("More than one %s named %s\n"), *Kind, *Name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WingUtils::CheckExactlyOneNamed(int Count, UClass *Class, const FString &Name)
|
||||||
|
{
|
||||||
|
return CheckExactlyOneNamed(Count, Class->GetName(), Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WingUtils::CheckExactlyNoneNamed(int Count, const FString &Kind, const FString &Name)
|
||||||
|
{
|
||||||
|
if (Count > 0)
|
||||||
|
{
|
||||||
|
UWingServer::Printf(TEXT("A %s named %s already exists."), *Kind, *Name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WingUtils::CheckExactlyNoneNamed(int Count, UClass *Class, const FString &Name)
|
||||||
|
{
|
||||||
|
return CheckExactlyNoneNamed(Count, Class->GetName(), Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Blueprint helpers
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
TArray<UEdGraph*> WingUtils::AllGraphs(UBlueprint* BP)
|
||||||
|
{
|
||||||
|
TArray<UEdGraph*> Graphs;
|
||||||
|
BP->GetAllGraphs(Graphs);
|
||||||
|
return Graphs;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TArray<UEdGraphNode*> WingUtils::AllNodes(UBlueprint* BP)
|
||||||
|
{
|
||||||
|
TArray<UEdGraphNode*> Nodes;
|
||||||
|
for (UEdGraph* Graph : AllGraphs(BP))
|
||||||
|
Nodes.Append(Graph->Nodes);
|
||||||
|
return Nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WingUtils::SaveBlueprintPackage(UBlueprint* BP)
|
||||||
|
{
|
||||||
|
UPackage* Package = BP->GetPackage();
|
||||||
|
|
||||||
|
// 1. Build absolute package filename — use .umap for map packages, .uasset otherwise
|
||||||
|
FString PackageExtension = Package->ContainsMap()
|
||||||
|
? FPackageName::GetMapPackageExtension()
|
||||||
|
: FPackageName::GetAssetPackageExtension();
|
||||||
|
FString PackageFilename = FPackageName::LongPackageNameToFilename(
|
||||||
|
Package->GetName(), PackageExtension);
|
||||||
|
PackageFilename = FPaths::ConvertRelativePathToFull(PackageFilename);
|
||||||
|
UE_LOG(LogTemp, Display, TEXT("UEWingman: Save target: %s"), *PackageFilename);
|
||||||
|
|
||||||
|
// 2. Phase 1: Try explicit compilation (same flags as UCompileAllBlueprintsCommandlet)
|
||||||
|
bool bCompiled = false;
|
||||||
|
{
|
||||||
|
EBlueprintCompileOptions CompileOpts =
|
||||||
|
EBlueprintCompileOptions::SkipSave |
|
||||||
|
EBlueprintCompileOptions::BatchCompile |
|
||||||
|
EBlueprintCompileOptions::SkipGarbageCollection |
|
||||||
|
EBlueprintCompileOptions::SkipFiBSearchMetaUpdate;
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Display, TEXT("UEWingman: Phase 1: Attempting explicit compilation..."));
|
||||||
|
|
||||||
|
#if PLATFORM_WINDOWS
|
||||||
|
int32 CompileResult = TryCompileBlueprintSEH(BP, CompileOpts);
|
||||||
|
if (CompileResult == 0)
|
||||||
|
{
|
||||||
|
bCompiled = (BP->Status == BS_UpToDate);
|
||||||
|
UE_LOG(LogTemp, Display, TEXT("UEWingman: Compilation %s (status=%d)"),
|
||||||
|
bCompiled ? TEXT("succeeded") : TEXT("completed with warnings"), (int32)BP->Status);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("UEWingman: Compilation crashed (SEH), proceeding uncompiled"));
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
FKismetEditorUtilities::CompileBlueprint(BP, CompileOpts, nullptr);
|
||||||
|
bCompiled = (BP->Status == BS_UpToDate);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Phase 2: Set guards for save
|
||||||
|
uint8 OldRegen = BP->bIsRegeneratingOnLoad;
|
||||||
|
BP->bIsRegeneratingOnLoad = true;
|
||||||
|
|
||||||
|
EBlueprintStatus OldStatus = (EBlueprintStatus)(uint8)BP->Status;
|
||||||
|
if (!bCompiled)
|
||||||
|
{
|
||||||
|
// Tell PreSave the BP is up-to-date so it doesn't try to compile
|
||||||
|
BP->Status = BS_UpToDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Clear read-only attribute if present (source control or LFS may set this)
|
||||||
|
if (FPlatformFileManager::Get().GetPlatformFile().IsReadOnly(*PackageFilename))
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Display, TEXT("UEWingman: Clearing read-only attribute on %s"), *PackageFilename);
|
||||||
|
FPlatformFileManager::Get().GetPlatformFile().SetReadOnly(*PackageFilename, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Phase 3: Save with SAVE_NoError + SEH protection
|
||||||
|
FSavePackageArgs SaveArgs;
|
||||||
|
SaveArgs.TopLevelFlags = RF_Public | RF_Standalone;
|
||||||
|
SaveArgs.SaveFlags = SAVE_NoError;
|
||||||
|
|
||||||
|
// For level blueprints (map packages), the base object should be the UWorld, not the BP
|
||||||
|
bool bIsMapPackage = Package->ContainsMap();
|
||||||
|
UObject* BaseObject = BP;
|
||||||
|
if (bIsMapPackage)
|
||||||
|
{
|
||||||
|
// Find the UWorld in this package — it's the actual asset for .umap files
|
||||||
|
UWorld* World = FindObject<UWorld>(Package, *Package->GetName().Mid(Package->GetName().Find(TEXT("/"), ESearchCase::IgnoreCase, ESearchDir::FromEnd) + 1));
|
||||||
|
if (!World)
|
||||||
|
{
|
||||||
|
// Fallback: iterate the package to find any UWorld
|
||||||
|
ForEachObjectWithPackage(Package, [&World](UObject* Obj) {
|
||||||
|
if (UWorld* W = Cast<UWorld>(Obj))
|
||||||
|
{
|
||||||
|
World = W;
|
||||||
|
return false; // stop
|
||||||
|
}
|
||||||
|
return true; // continue
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (World)
|
||||||
|
{
|
||||||
|
BaseObject = World;
|
||||||
|
UE_LOG(LogTemp, Display, TEXT("UEWingman: Map package detected — saving UWorld '%s'"), *World->GetName());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("UEWingman: Map package detected but no UWorld found — saving with BP as base"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ESavePackageResult SaveResult = ESavePackageResult::Error;
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Display, TEXT("UEWingman: Phase 3: Calling UPackage::Save (compiled=%s, isMap=%s)..."),
|
||||||
|
bCompiled ? TEXT("yes") : TEXT("no"), bIsMapPackage ? TEXT("yes") : TEXT("no"));
|
||||||
|
|
||||||
|
#if PLATFORM_WINDOWS
|
||||||
|
int32 SEHCode = TrySavePackageSEH(Package, BaseObject, *PackageFilename, &SaveArgs, &SaveResult);
|
||||||
|
if (SEHCode != 0)
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Error, TEXT("UEWingman: UPackage::Save CRASHED (SEH exception caught)"));
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
FSavePackageResultStruct Result = UPackage::Save(Package, BaseObject, *PackageFilename, SaveArgs);
|
||||||
|
SaveResult = Result.Result;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// 6. Restore guards
|
||||||
|
BP->bIsRegeneratingOnLoad = OldRegen;
|
||||||
|
if (!bCompiled)
|
||||||
|
{
|
||||||
|
BP->Status = (TEnumAsByte<EBlueprintStatus>)OldStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bSuccess = (SaveResult == ESavePackageResult::Success);
|
||||||
|
UE_LOG(LogTemp, Display, TEXT("UEWingman: SaveBlueprintPackage — %s for '%s' (compiled=%s, result=%d)"),
|
||||||
|
bSuccess ? TEXT("SUCCEEDED") : TEXT("FAILED"),
|
||||||
|
*BP->GetName(), bCompiled ? TEXT("yes") : TEXT("no"), (int32)SaveResult);
|
||||||
|
|
||||||
|
return bSuccess;
|
||||||
|
|
||||||
|
}
|
||||||
|
// ============================================================
|
||||||
|
// Material helpers
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void WingUtils::EnsureMaterialGraph(UMaterial* Material)
|
||||||
|
{
|
||||||
|
if (!Material) return;
|
||||||
|
if (!Material->MaterialGraph)
|
||||||
|
{
|
||||||
|
// In commandlet/headless mode the MaterialGraph is not auto-created.
|
||||||
|
// Replicate what the Material Editor does on open (MaterialEditor.cpp:619).
|
||||||
|
Material->MaterialGraph = CastChecked<UMaterialGraph>(
|
||||||
|
FBlueprintEditorUtils::CreateNewGraph(
|
||||||
|
Material, NAME_None,
|
||||||
|
UMaterialGraph::StaticClass(),
|
||||||
|
UMaterialGraphSchema::StaticClass()));
|
||||||
|
Material->MaterialGraph->Material = Material;
|
||||||
|
Material->MaterialGraph->RebuildGraph();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UMaterial* WingUtils::ReplaceMaterialWithTransientCopy(UMaterial* Material)
|
||||||
|
{
|
||||||
|
if (!Material) return nullptr;
|
||||||
|
|
||||||
|
// Already a preview material — nothing to do.
|
||||||
|
if (Material->GetOutermost() == GetTransientPackage())
|
||||||
|
return Material;
|
||||||
|
|
||||||
|
// If the material editor has a transient preview copy open, get it
|
||||||
|
// via the editor API. This follows the same pattern as Epic's
|
||||||
|
// MaterialEditingLibrary (FindMaterialEditorForAsset).
|
||||||
|
UAssetEditorSubsystem* Sub = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
|
||||||
|
IAssetEditorInstance* EditorInstance = Sub ? Sub->FindEditorForAsset(Material, false) : nullptr;
|
||||||
|
if (EditorInstance)
|
||||||
|
{
|
||||||
|
// This is a weird hack. We know that the IAssetEditorInstance for a material
|
||||||
|
// is always going to be an FMaterialEditor, which conforms to IMaterialEditor.
|
||||||
|
// If that weren't the case, this unsafe code would crash hard. However,
|
||||||
|
// lots of places in unreal use this same unsafe pattern.
|
||||||
|
IMaterialEditor* MatEditor = static_cast<IMaterialEditor*>(EditorInstance);
|
||||||
|
UMaterialInterface* Edited = MatEditor->GetMaterialInterface();
|
||||||
|
if (UMaterial* EditedMat = Cast<UMaterial>(Edited))
|
||||||
|
return EditedMat;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Material;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WingUtils::SaveGenericPackage(UObject* Asset)
|
||||||
|
{
|
||||||
|
if (!Asset) return false;
|
||||||
|
UPackage* Package = Asset->GetPackage();
|
||||||
|
UE_LOG(LogTemp, Display, TEXT("UEWingman: SaveGenericPackage — begin for '%s'"), *Asset->GetName());
|
||||||
|
|
||||||
|
FString PackageFilename = FPackageName::LongPackageNameToFilename(
|
||||||
|
Package->GetName(), FPackageName::GetAssetPackageExtension());
|
||||||
|
PackageFilename = FPaths::ConvertRelativePathToFull(PackageFilename);
|
||||||
|
|
||||||
|
if (FPlatformFileManager::Get().GetPlatformFile().IsReadOnly(*PackageFilename))
|
||||||
|
{
|
||||||
|
FPlatformFileManager::Get().GetPlatformFile().SetReadOnly(*PackageFilename, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
FSavePackageArgs SaveArgs;
|
||||||
|
SaveArgs.TopLevelFlags = RF_Public | RF_Standalone;
|
||||||
|
SaveArgs.SaveFlags = SAVE_NoError;
|
||||||
|
|
||||||
|
ESavePackageResult SaveResult = ESavePackageResult::Error;
|
||||||
|
#if PLATFORM_WINDOWS
|
||||||
|
int32 SEHCode = TrySavePackageSEH(Package, Asset, *PackageFilename, &SaveArgs, &SaveResult);
|
||||||
|
if (SEHCode != 0)
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Error, TEXT("UEWingman: SaveGenericPackage CRASHED (SEH exception)"));
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
FSavePackageResultStruct Result = UPackage::Save(Package, Asset, *PackageFilename, SaveArgs);
|
||||||
|
SaveResult = Result.Result;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool bSuccess = (SaveResult == ESavePackageResult::Success);
|
||||||
|
UE_LOG(LogTemp, Display, TEXT("UEWingman: SaveGenericPackage — %s for '%s'"),
|
||||||
|
bSuccess ? TEXT("SUCCEEDED") : TEXT("FAILED"), *Asset->GetName());
|
||||||
|
return bSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Anim blueprint helpers
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
UAnimationStateMachineGraph* WingUtils::FindStateMachineGraph(UBlueprint* BP, const FString& GraphName)
|
||||||
|
{
|
||||||
|
TArray<UEdGraph*> AllGraphs;
|
||||||
|
BP->GetAllGraphs(AllGraphs);
|
||||||
|
for (UEdGraph* Graph : AllGraphs)
|
||||||
|
{
|
||||||
|
if (UAnimationStateMachineGraph* SMGraph = Cast<UAnimationStateMachineGraph>(Graph))
|
||||||
|
{
|
||||||
|
if (SMGraph->GetName() == GraphName)
|
||||||
|
{
|
||||||
|
return SMGraph;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
UAnimStateNode* WingUtils::FindStateByName(UAnimationStateMachineGraph* SMGraph, const FString& StateName)
|
||||||
|
{
|
||||||
|
for (UEdGraphNode* Node : SMGraph->Nodes)
|
||||||
|
{
|
||||||
|
if (UAnimStateNode* StateNode = Cast<UAnimStateNode>(Node))
|
||||||
|
{
|
||||||
|
if (StateNode->GetStateName() == StateName)
|
||||||
|
{
|
||||||
|
return StateNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UWingServer::Printf(TEXT("ERROR: State '%s' not found in graph '%s'\n"), *StateName, *SMGraph->GetName());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
UAnimStateTransitionNode* WingUtils::FindTransition(UAnimationStateMachineGraph* SMGraph,
|
||||||
|
const FString& FromStateName, const FString& ToStateName)
|
||||||
|
{
|
||||||
|
for (UEdGraphNode* Node : SMGraph->Nodes)
|
||||||
|
{
|
||||||
|
if (UAnimStateTransitionNode* TransNode = Cast<UAnimStateTransitionNode>(Node))
|
||||||
|
{
|
||||||
|
UAnimStateNode* FromState = Cast<UAnimStateNode>(TransNode->GetPreviousState());
|
||||||
|
UAnimStateNode* ToState = Cast<UAnimStateNode>(TransNode->GetNextState());
|
||||||
|
if (FromState && ToState &&
|
||||||
|
(FromState->GetStateName() == FromStateName) &&
|
||||||
|
(ToState->GetStateName() == ToStateName))
|
||||||
|
{
|
||||||
|
return TransNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Graph actions (node spawning)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
FString WingUtils::ActionFullName(const TSharedPtr<FEdGraphSchemaAction>& Action)
|
||||||
|
{
|
||||||
|
FString Category = Action->GetCategory().ToString();
|
||||||
|
FString MenuName = Action->GetMenuDescription().ToString();
|
||||||
|
if (Category.IsEmpty())
|
||||||
|
return MenuName;
|
||||||
|
return Category + TEXT("|") + MenuName;
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<TSharedPtr<FEdGraphSchemaAction>> WingUtils::SearchGraphActions(UEdGraph* Graph, const FString& Query, int32 MaxResults, bool ExactMatch)
|
||||||
|
{
|
||||||
|
FString QueryLower = Query.ToLower();
|
||||||
|
TArray<TSharedPtr<FEdGraphSchemaAction>> Result;
|
||||||
|
|
||||||
|
FGraphContextMenuBuilder ContextMenuBuilder(Graph);
|
||||||
|
Graph->GetSchema()->GetGraphContextActions(ContextMenuBuilder);
|
||||||
|
|
||||||
|
for (int32 i = 0; i < ContextMenuBuilder.GetNumActions(); i++)
|
||||||
|
{
|
||||||
|
TSharedPtr<FEdGraphSchemaAction> Action = ContextMenuBuilder.GetSchemaAction(i);
|
||||||
|
if (!Action.IsValid()) continue;
|
||||||
|
|
||||||
|
FString FullName = ActionFullName(Action);
|
||||||
|
if (FullName.IsEmpty()) continue;
|
||||||
|
|
||||||
|
if (ExactMatch)
|
||||||
|
{
|
||||||
|
if (FullName.ToLower() != QueryLower)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FString Keywords = Action->GetKeywords().ToString();
|
||||||
|
if (!FullName.ToLower().Contains(QueryLower) && !Keywords.ToLower().Contains(QueryLower))
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result.Add(Action);
|
||||||
|
if ((MaxResults > 0) && (Result.Num() >= MaxResults))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// PopulateFromJson — fill a USTRUCT from a JSON object
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// CollectHandlerClasses — find all concrete IWingHandler classes
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
TArray<UClass*> WingUtils::CollectHandlerClasses()
|
||||||
|
{
|
||||||
|
TArray<UClass*> Result;
|
||||||
|
for (TObjectIterator<UClass> It; It; ++It)
|
||||||
|
{
|
||||||
|
UClass* Class = *It;
|
||||||
|
if (Class->HasAnyClassFlags(CLASS_Abstract)) continue;
|
||||||
|
if (!Class->ImplementsInterface(UWingHandler::StaticClass())) continue;
|
||||||
|
Result.Add(Class);
|
||||||
|
}
|
||||||
|
Result.Sort([](UClass& A, UClass& B) { return GetHandlerName(&A) < GetHandlerName(&B); });
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// GetHandlerName — derive tool name from handler class name
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
FString WingUtils::GetHandlerName(UClass* HandlerClass)
|
||||||
|
{
|
||||||
|
FString Name = HandlerClass->GetName();
|
||||||
|
// Strip "Wing_" prefix
|
||||||
|
Name.RemoveFromStart(TEXT("Wing_"));
|
||||||
|
return Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// GetHandlerGroup — derive group name from handler class name
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
FString WingUtils::GetHandlerGroup(UClass* HandlerClass)
|
||||||
|
{
|
||||||
|
FString Name = HandlerClass->GetName();
|
||||||
|
// Strip "Wing_" prefix
|
||||||
|
Name.RemoveFromStart(TEXT("Wing_"));
|
||||||
|
// Everything before the underscore is the group
|
||||||
|
int32 UnderscoreIdx;
|
||||||
|
if (Name.FindChar(TEXT('_'), UnderscoreIdx))
|
||||||
|
return Name.Left(UnderscoreIdx);
|
||||||
|
return Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// GetTemplate
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// FindPropertyByName
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
FProperty* WingUtils::FindPropertyByName(UObject* Obj, const FString& Name)
|
||||||
|
{
|
||||||
|
if (!Obj)
|
||||||
|
{
|
||||||
|
UWingServer::Print(TEXT("ERROR: Object is null\n"));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
FProperty* Found = nullptr;
|
||||||
|
for (TFieldIterator<FProperty> PropIt(Obj->GetClass()); PropIt; ++PropIt)
|
||||||
|
{
|
||||||
|
if (!Identifies(Name, *PropIt)) continue;
|
||||||
|
if (Found)
|
||||||
|
{
|
||||||
|
UWingServer::Printf(TEXT("ERROR: Ambiguous property '%s' on %s\n"), *Name, *FormatName(Obj->GetClass()));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
Found = *PropIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Found)
|
||||||
|
UWingServer::Printf(TEXT("ERROR: Property '%s' not found on %s\n"), *Name, *FormatName(Obj->GetClass()));
|
||||||
|
|
||||||
|
return Found;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// GetPropertyValueText
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
FString WingUtils::GetPropertyValueText(UObject* Container, FProperty* Prop)
|
||||||
|
{
|
||||||
|
FString Result;
|
||||||
|
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container);
|
||||||
|
Prop->ExportTextItem_Direct(Result, ValuePtr, nullptr, Container, PPF_None);
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// SetPropertyValueText
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
bool WingUtils::SetPropertyValueText(UObject* Container, FProperty* Prop, const FString& Value)
|
||||||
|
{
|
||||||
|
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container);
|
||||||
|
const TCHAR* ImportResult = Prop->ImportText_Direct(*Value, ValuePtr, Container, PPF_None);
|
||||||
|
if (!ImportResult)
|
||||||
|
{
|
||||||
|
UWingServer::Printf(TEXT("ERROR: Failed to parse '%s' for property '%s' (type: %s)\n"),
|
||||||
|
*Value, *FormatName(Prop), *Prop->GetCPPType());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WingUtils::SetPropertyValueText(void* Container, FProperty* Prop, const FString& Value, UObject* Owner)
|
||||||
|
{
|
||||||
|
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container);
|
||||||
|
const TCHAR* ImportResult = Prop->ImportText_Direct(*Value, ValuePtr, Owner, PPF_None);
|
||||||
|
if (!ImportResult)
|
||||||
|
{
|
||||||
|
UWingServer::Printf(TEXT("ERROR: Failed to parse '%s' for property '%s' (type: %s)\n"),
|
||||||
|
*Value, *FormatName(Prop), *Prop->GetCPPType());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// SearchProperties
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
TArray<FProperty*> WingUtils::SearchProperties(UObject* Obj, const FString& Query, EPropertyFlags Flags, bool bLocal)
|
||||||
|
{
|
||||||
|
TArray<FProperty*> Result;
|
||||||
|
if (!Obj) return Result;
|
||||||
|
UClass* ObjClass = Obj->GetClass();
|
||||||
|
for (TFieldIterator<FProperty> PropIt(ObjClass); PropIt; ++PropIt)
|
||||||
|
{
|
||||||
|
FProperty* Prop = *PropIt;
|
||||||
|
if (!Prop) 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);
|
||||||
|
}
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// FormatCommandHelp — verbose description of one handler command
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void WingUtils::FormatCommandHelp(UClass* HandlerClass)
|
||||||
|
{
|
||||||
|
const IWingHandler* Handler = Cast<IWingHandler>(HandlerClass->GetDefaultObject());
|
||||||
|
if (!Handler) return;
|
||||||
|
|
||||||
|
FString ToolName = GetHandlerName(HandlerClass);
|
||||||
|
|
||||||
|
UWingServer::Print(TEXT("\n"));
|
||||||
|
UWingServer::Print(WrapText(Handler->GetDescription(), 80, TEXT("// ")));
|
||||||
|
UWingServer::Print(TEXT("\n"));
|
||||||
|
|
||||||
|
// Command signature line
|
||||||
|
UWingServer::Print(ToolName);
|
||||||
|
UWingServer::Print(TEXT("("));
|
||||||
|
bool bFirst = true;
|
||||||
|
for (TFieldIterator<FProperty> PropIt(HandlerClass, EFieldIterationFlags::None); PropIt; ++PropIt)
|
||||||
|
{
|
||||||
|
if (!bFirst) UWingServer::Print(TEXT(","));
|
||||||
|
bFirst = false;
|
||||||
|
if (PropIt->HasMetaData(TEXT("Optional"))) UWingServer::Print(TEXT("?"));
|
||||||
|
UWingServer::Print(PropIt->GetName());
|
||||||
|
}
|
||||||
|
UWingServer::Print(TEXT(")\n"));
|
||||||
|
|
||||||
|
// parameter details
|
||||||
|
for (TFieldIterator<FProperty> PropIt(HandlerClass, EFieldIterationFlags::None); PropIt; ++PropIt)
|
||||||
|
{
|
||||||
|
FProperty* Prop = *PropIt;
|
||||||
|
FString Name = Prop->GetName();
|
||||||
|
FString Type = UWingTypes::TypeToText(Prop);
|
||||||
|
bool bOptional = Prop->HasMetaData(TEXT("Optional"));
|
||||||
|
const FString& Desc = Prop->GetMetaData(TEXT("Description"));
|
||||||
|
|
||||||
|
UWingServer::Printf(TEXT(" %s %s%s"),
|
||||||
|
*Type, *Name, bOptional ? TEXT(" (optional)") : TEXT(""));
|
||||||
|
if (!Desc.IsEmpty())
|
||||||
|
UWingServer::Printf(TEXT(" — %s"), *Desc);
|
||||||
|
UWingServer::Print(TEXT("\n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -91,13 +91,11 @@ public:
|
|||||||
// Set target skeleton
|
// Set target skeleton
|
||||||
NewAnimBP->TargetSkeleton = SkeletonObj;
|
NewAnimBP->TargetSkeleton = SkeletonObj;
|
||||||
|
|
||||||
// Compile and save
|
// Compile
|
||||||
FKismetEditorUtilities::CompileBlueprint(NewAnimBP);
|
FKismetEditorUtilities::CompileBlueprint(NewAnimBP);
|
||||||
bool bSaved = WingUtils::SaveBlueprintPackage(NewAnimBP);
|
|
||||||
|
|
||||||
UWingServer::Printf(TEXT("Created: %s\n"), *AssetPath);
|
UWingServer::Printf(TEXT("Created: %s\n"), *AssetPath);
|
||||||
UWingServer::Printf(TEXT("ParentClass: %s\n"), *WingUtils::FormatName(ParentClassObj));
|
UWingServer::Printf(TEXT("ParentClass: %s\n"), *WingUtils::FormatName(ParentClassObj));
|
||||||
UWingServer::Printf(TEXT("Saved: %s\n"), bSaved ? TEXT("true") : TEXT("false"));
|
|
||||||
|
|
||||||
TArray<UEdGraph*> Graphs = WingUtils::AllGraphs(NewAnimBP);
|
TArray<UEdGraph*> Graphs = WingUtils::AllGraphs(NewAnimBP);
|
||||||
for (UEdGraph* Graph : Graphs)
|
for (UEdGraph* Graph : Graphs)
|
||||||
|
|||||||
@@ -126,13 +126,6 @@ public:
|
|||||||
|
|
||||||
BS->ValidateSampleData();
|
BS->ValidateSampleData();
|
||||||
|
|
||||||
// Save
|
|
||||||
bool bSaved = WingUtils::SaveGenericPackage(BS);
|
|
||||||
|
|
||||||
UWingServer::Printf(TEXT("Set %d samples on %s\n"), SamplesSet, *WingUtils::FormatName(BS));
|
UWingServer::Printf(TEXT("Set %d samples on %s\n"), SamplesSet, *WingUtils::FormatName(BS));
|
||||||
if (!bSaved)
|
|
||||||
{
|
|
||||||
UWingServer::Print(TEXT("WARNING: package save failed\n"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -55,11 +55,8 @@ public:
|
|||||||
NewBS->SetSkeleton(SkeletonObj);
|
NewBS->SetSkeleton(SkeletonObj);
|
||||||
|
|
||||||
NewBS->MarkPackageDirty();
|
NewBS->MarkPackageDirty();
|
||||||
bool bSaved = WingUtils::SaveGenericPackage(NewBS);
|
|
||||||
|
|
||||||
UWingServer::Printf(TEXT("Created %s\n"), *NewBS->GetPathName());
|
UWingServer::Printf(TEXT("Created %s\n"), *NewBS->GetPathName());
|
||||||
UWingServer::Printf(TEXT("Skeleton: %s\n"), *SkeletonObj->GetPathName());
|
UWingServer::Printf(TEXT("Skeleton: %s\n"), *SkeletonObj->GetPathName());
|
||||||
if (!bSaved)
|
|
||||||
UWingServer::Print(TEXT("WARNING: Package save failed\n"));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -112,6 +112,5 @@ public:
|
|||||||
UWingServer::Printf(TEXT("Created custom event: %s\n"), *WingUtils::FormatName(NewEvent));
|
UWingServer::Printf(TEXT("Created custom event: %s\n"), *WingUtils::FormatName(NewEvent));
|
||||||
}
|
}
|
||||||
|
|
||||||
WingUtils::SaveBlueprintPackage(BP);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -87,10 +87,7 @@ public:
|
|||||||
// Remove the graph
|
// Remove the graph
|
||||||
FString GraphName = WingUtils::FormatName(TargetGraph);
|
FString GraphName = WingUtils::FormatName(TargetGraph);
|
||||||
FBlueprintEditorUtils::RemoveGraph(BP, TargetGraph, EGraphRemoveFlags::Default);
|
FBlueprintEditorUtils::RemoveGraph(BP, TargetGraph, EGraphRemoveFlags::Default);
|
||||||
bool bSaved = WingUtils::SaveBlueprintPackage(BP);
|
|
||||||
|
|
||||||
UWingServer::Printf(TEXT("Deleted %s graph %s\n"), *GraphType, *GraphName);
|
UWingServer::Printf(TEXT("Deleted %s graph %s\n"), *GraphType, *GraphName);
|
||||||
if (!bSaved)
|
|
||||||
UWingServer::Print(TEXT("WARNING: Package save failed.\n"));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
FBlueprintEditorUtils::RenameGraph(TargetGraph, NewName);
|
FBlueprintEditorUtils::RenameGraph(TargetGraph, NewName);
|
||||||
WingUtils::SaveBlueprintPackage(BP);
|
|
||||||
|
|
||||||
UWingServer::Printf(TEXT("Renamed to %s %s\n"),
|
UWingServer::Printf(TEXT("Renamed to %s %s\n"),
|
||||||
bIsFunction ? TEXT("function") : TEXT("macro"),
|
bIsFunction ? TEXT("function") : TEXT("macro"),
|
||||||
|
|||||||
@@ -110,8 +110,6 @@ public:
|
|||||||
SCS->AddNode(NewNode);
|
SCS->AddNode(NewNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool bSaved = WingUtils::SaveBlueprintPackage(BP);
|
|
||||||
|
|
||||||
UWingServer::Printf(TEXT("Added component %s (%s)"),
|
UWingServer::Printf(TEXT("Added component %s (%s)"),
|
||||||
*WingUtils::FormatName(NewNode->ComponentTemplate),
|
*WingUtils::FormatName(NewNode->ComponentTemplate),
|
||||||
*WingUtils::FormatName(ComponentClassObj));
|
*WingUtils::FormatName(ComponentClassObj));
|
||||||
@@ -119,6 +117,6 @@ public:
|
|||||||
{
|
{
|
||||||
UWingServer::Printf(TEXT(" under %s"), *WingUtils::FormatName(ParentSCSNode->ComponentTemplate));
|
UWingServer::Printf(TEXT(" under %s"), *WingUtils::FormatName(ParentSCSNode->ComponentTemplate));
|
||||||
}
|
}
|
||||||
UWingServer::Printf(TEXT("\nSaved: %s\n"), bSaved ? TEXT("true") : TEXT("false"));
|
UWingServer::Print(TEXT("\n"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -115,7 +115,6 @@ public:
|
|||||||
|
|
||||||
if (!EntryNode)
|
if (!EntryNode)
|
||||||
{
|
{
|
||||||
WingUtils::SaveBlueprintPackage(BP);
|
|
||||||
UWingServer::Print(TEXT("Error: Event dispatcher created but entry node not found — parameters could not be added.\n"));
|
UWingServer::Print(TEXT("Error: Event dispatcher created but entry node not found — parameters could not be added.\n"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -135,8 +134,6 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WingUtils::SaveBlueprintPackage(BP);
|
|
||||||
|
|
||||||
UWingServer::Printf(TEXT("Created event dispatcher '%s'"), *DispatcherName);
|
UWingServer::Printf(TEXT("Created event dispatcher '%s'"), *DispatcherName);
|
||||||
if (ParamCount > 0)
|
if (ParamCount > 0)
|
||||||
UWingServer::Printf(TEXT(" with %d parameter(s)"), ParamCount);
|
UWingServer::Printf(TEXT(" with %d parameter(s)"), ParamCount);
|
||||||
|
|||||||
@@ -86,10 +86,6 @@ public:
|
|||||||
// Remove the node (promotes children to parent if it has any — but we've guarded root above)
|
// Remove the node (promotes children to parent if it has any — but we've guarded root above)
|
||||||
SCS->RemoveNodeAndPromoteChildren(NodeToRemove);
|
SCS->RemoveNodeAndPromoteChildren(NodeToRemove);
|
||||||
|
|
||||||
bool bSaved = WingUtils::SaveBlueprintPackage(BP);
|
UWingServer::Printf(TEXT("Removed component %s.\n"), *RemovedName);
|
||||||
|
|
||||||
UWingServer::Printf(TEXT("Removed component %s.%s\n"),
|
|
||||||
*RemovedName,
|
|
||||||
bSaved ? TEXT("") : TEXT(" WARNING: save failed."));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,12 +25,12 @@ public:
|
|||||||
UPROPERTY(meta=(Description="Blueprint package path"))
|
UPROPERTY(meta=(Description="Blueprint package path"))
|
||||||
FString Blueprint;
|
FString Blueprint;
|
||||||
|
|
||||||
UPROPERTY(meta=(Description="New parent class: C++ class name or Blueprint package path"))
|
UPROPERTY(meta=(Description="New parent class"))
|
||||||
FString NewParentClass;
|
FString Parent;
|
||||||
|
|
||||||
virtual FString GetDescription() const override
|
virtual FString GetDescription() const override
|
||||||
{
|
{
|
||||||
return TEXT("Change a Blueprint's parent class. Accepts C++ class names or Blueprint package paths.");
|
return TEXT("Change a Blueprint's parent class.");
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void Handle() override
|
virtual void Handle() override
|
||||||
@@ -50,7 +50,6 @@ public:
|
|||||||
BP->ParentClass = NewParentClassObj;
|
BP->ParentClass = NewParentClassObj;
|
||||||
FBlueprintEditorUtils::RefreshAllNodes(BP);
|
FBlueprintEditorUtils::RefreshAllNodes(BP);
|
||||||
FKismetEditorUtilities::CompileBlueprint(BP);
|
FKismetEditorUtilities::CompileBlueprint(BP);
|
||||||
bool bSaved = WingUtils::SaveBlueprintPackage(BP);
|
|
||||||
|
|
||||||
UWingServer::Printf(TEXT("Reparented %s: %s -> %s\n"),
|
UWingServer::Printf(TEXT("Reparented %s: %s -> %s\n"),
|
||||||
*WingUtils::FormatName(BP), *OldParentName, *WingUtils::FormatName(NewParentClassObj));
|
*WingUtils::FormatName(BP), *OldParentName, *WingUtils::FormatName(NewParentClassObj));
|
||||||
|
|||||||
@@ -62,10 +62,6 @@ public:
|
|||||||
FEnumEditorUtils::SetEnumeratorDisplayName(NewEnum, NewIndex, FText::FromString(EnumValues[i]));
|
FEnumEditorUtils::SetEnumeratorDisplayName(NewEnum, NewIndex, FText::FromString(EnumValues[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool bSaved = WingUtils::SaveGenericPackage(NewEnum);
|
|
||||||
|
|
||||||
UWingServer::Printf(TEXT("Created %s with %d values\n"), *NewEnum->GetPathName(), EnumValues.Num());
|
UWingServer::Printf(TEXT("Created %s with %d values\n"), *NewEnum->GetPathName(), EnumValues.Num());
|
||||||
if (!bSaved)
|
|
||||||
UWingServer::Print(TEXT("WARNING: Package save failed\n"));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -44,10 +44,6 @@ public:
|
|||||||
if (!Description.IsEmpty())
|
if (!Description.IsEmpty())
|
||||||
MF->Description = Description;
|
MF->Description = Description;
|
||||||
|
|
||||||
bool bSaved = WingUtils::SaveGenericPackage(MF);
|
|
||||||
|
|
||||||
UWingServer::Printf(TEXT("Created %s\n"), *MF->GetPathName());
|
UWingServer::Printf(TEXT("Created %s\n"), *MF->GetPathName());
|
||||||
if (!bSaved)
|
|
||||||
UWingServer::Print(TEXT("WARNING: Package save failed\n"));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -107,9 +107,8 @@ public:
|
|||||||
NewState->GetBoundGraph()->AddNode(SeqNode, false, false);
|
NewState->GetBoundGraph()->AddNode(SeqNode, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile and save
|
// Compile
|
||||||
FKismetEditorUtilities::CompileBlueprint(AnimBP);
|
FKismetEditorUtilities::CompileBlueprint(AnimBP);
|
||||||
WingUtils::SaveBlueprintPackage(AnimBP);
|
|
||||||
|
|
||||||
UWingServer::Printf(TEXT("Created state '%s' in %s\n"), *StateName, *WingUtils::FormatName(SMGraph));
|
UWingServer::Printf(TEXT("Created state '%s' in %s\n"), *StateName, *WingUtils::FormatName(SMGraph));
|
||||||
UWingServer::Printf(TEXT(" node: %s\n"), *WingUtils::FormatName(NewState));
|
UWingServer::Printf(TEXT(" node: %s\n"), *WingUtils::FormatName(NewState));
|
||||||
|
|||||||
@@ -89,9 +89,8 @@ public:
|
|||||||
TransNode->PriorityOrder = Priority;
|
TransNode->PriorityOrder = Priority;
|
||||||
TransNode->Bidirectional = BBidirectional;
|
TransNode->Bidirectional = BBidirectional;
|
||||||
|
|
||||||
// Compile and save
|
// Compile
|
||||||
FKismetEditorUtilities::CompileBlueprint(AnimBP);
|
FKismetEditorUtilities::CompileBlueprint(AnimBP);
|
||||||
WingUtils::SaveBlueprintPackage(AnimBP);
|
|
||||||
|
|
||||||
UWingServer::Printf(TEXT("Created transition %s -> %s: %s\n"),
|
UWingServer::Printf(TEXT("Created transition %s -> %s: %s\n"),
|
||||||
*FromState, *ToState, *WingUtils::FormatName(TransNode));
|
*FromState, *ToState, *WingUtils::FormatName(TransNode));
|
||||||
|
|||||||
@@ -72,9 +72,8 @@ public:
|
|||||||
StateNode->BreakAllNodeLinks();
|
StateNode->BreakAllNodeLinks();
|
||||||
SMGraph->RemoveNode(StateNode);
|
SMGraph->RemoveNode(StateNode);
|
||||||
|
|
||||||
// Compile and save
|
// Compile
|
||||||
FKismetEditorUtilities::CompileBlueprint(BP);
|
FKismetEditorUtilities::CompileBlueprint(BP);
|
||||||
WingUtils::SaveBlueprintPackage(BP);
|
|
||||||
|
|
||||||
UWingServer::Printf(TEXT("Removed state %s and %d transition(s).\n"),
|
UWingServer::Printf(TEXT("Removed state %s and %d transition(s).\n"),
|
||||||
*WingUtils::FormatName(StateNode), RemovedTransitions);
|
*WingUtils::FormatName(StateNode), RemovedTransitions);
|
||||||
|
|||||||
@@ -97,9 +97,8 @@ public:
|
|||||||
|
|
||||||
SeqNode->SetAnimationAsset(AnimSeq);
|
SeqNode->SetAnimationAsset(AnimSeq);
|
||||||
|
|
||||||
// Compile and save
|
// Compile
|
||||||
FKismetEditorUtilities::CompileBlueprint(AnimBP);
|
FKismetEditorUtilities::CompileBlueprint(AnimBP);
|
||||||
WingUtils::SaveBlueprintPackage(AnimBP);
|
|
||||||
|
|
||||||
if (bCreatedNew)
|
if (bCreatedNew)
|
||||||
UWingServer::Printf(TEXT("Created sequence player in state '%s', assigned %s\n"), *StateName, *WingUtils::FormatName(AnimSeq));
|
UWingServer::Printf(TEXT("Created sequence player in state '%s', assigned %s\n"), *StateName, *WingUtils::FormatName(AnimSeq));
|
||||||
|
|||||||
@@ -103,14 +103,11 @@ public:
|
|||||||
WireVariable(AnimBP, InnerGraph, BSNode, XVariable, TEXT("X"));
|
WireVariable(AnimBP, InnerGraph, BSNode, XVariable, TEXT("X"));
|
||||||
WireVariable(AnimBP, InnerGraph, BSNode, YVariable, TEXT("Y"));
|
WireVariable(AnimBP, InnerGraph, BSNode, YVariable, TEXT("Y"));
|
||||||
|
|
||||||
// Compile and save
|
// Compile
|
||||||
FKismetEditorUtilities::CompileBlueprint(AnimBP);
|
FKismetEditorUtilities::CompileBlueprint(AnimBP);
|
||||||
bool bSaved = WingUtils::SaveBlueprintPackage(AnimBP);
|
|
||||||
|
|
||||||
UWingServer::Printf(TEXT("BlendSpacePlayer %s placed in state %s\n"),
|
UWingServer::Printf(TEXT("BlendSpacePlayer %s placed in state %s\n"),
|
||||||
*WingUtils::FormatName(BSNode), *StateName);
|
*WingUtils::FormatName(BSNode), *StateName);
|
||||||
if (!bSaved)
|
|
||||||
UWingServer::Print(TEXT("WARNING: Failed to save package\n"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -82,9 +82,8 @@ public:
|
|||||||
TransNode->LogicType = (ETransitionLogicType::Type)LogicType;
|
TransNode->LogicType = (ETransitionLogicType::Type)LogicType;
|
||||||
TransNode->Bidirectional = BBidirectional;
|
TransNode->Bidirectional = BBidirectional;
|
||||||
|
|
||||||
// Compile and save
|
// Compile
|
||||||
FKismetEditorUtilities::CompileBlueprint(AnimBP);
|
FKismetEditorUtilities::CompileBlueprint(AnimBP);
|
||||||
WingUtils::SaveBlueprintPackage(AnimBP);
|
|
||||||
|
|
||||||
UWingServer::Printf(TEXT("Updated transition %s -> %s: %s\n"),
|
UWingServer::Printf(TEXT("Updated transition %s -> %s: %s\n"),
|
||||||
*FromState, *ToState, *WingUtils::FormatName(TransNode));
|
*FromState, *ToState, *WingUtils::FormatName(TransNode));
|
||||||
|
|||||||
@@ -88,12 +88,8 @@ public:
|
|||||||
PropsAdded++;
|
PropsAdded++;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool bSaved = WingUtils::SaveGenericPackage(NewStruct);
|
|
||||||
|
|
||||||
UWingServer::Printf(TEXT("Created %s\n"), *NewStruct->GetPathName());
|
UWingServer::Printf(TEXT("Created %s\n"), *NewStruct->GetPathName());
|
||||||
if (PropsAdded > 0)
|
if (PropsAdded > 0)
|
||||||
UWingServer::Printf(TEXT("Properties added: %d\n"), PropsAdded);
|
UWingServer::Printf(TEXT("Properties added: %d\n"), PropsAdded);
|
||||||
if (!bSaved)
|
|
||||||
UWingServer::Print(TEXT("WARNING: Package save failed\n"));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -83,12 +83,10 @@ public:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile and save
|
// Compile
|
||||||
FKismetEditorUtilities::CompileBlueprint(NewBP);
|
FKismetEditorUtilities::CompileBlueprint(NewBP);
|
||||||
bool bSaved = WingUtils::SaveBlueprintPackage(NewBP);
|
|
||||||
|
|
||||||
// Report result
|
// Report result
|
||||||
UWingServer::Printf(TEXT("Created: %s\n"), *WingUtils::FormatName(NewBP));
|
UWingServer::Printf(TEXT("Created: %s\n"), *WingUtils::FormatName(NewBP));
|
||||||
if (!bSaved) UWingServer::Print(TEXT("Warning: save failed\n"));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -73,7 +73,6 @@ public:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
WingUtils::SaveGenericPackage(MI);
|
|
||||||
UWingServer::Printf(TEXT("Cleared override for '%s' on %s\n"),
|
UWingServer::Printf(TEXT("Cleared override for '%s' on %s\n"),
|
||||||
*Parameter, *WingUtils::FormatName(MI));
|
*Parameter, *WingUtils::FormatName(MI));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,9 +56,6 @@ public:
|
|||||||
// Set parent.
|
// Set parent.
|
||||||
MI->Parent = ParentMaterialObj;
|
MI->Parent = ParentMaterialObj;
|
||||||
|
|
||||||
// Save.
|
|
||||||
bool bSaved = WingUtils::SaveGenericPackage(MI);
|
|
||||||
|
|
||||||
UWingServer::Printf(TEXT("Created %s\n"), *MI->GetPathName());
|
UWingServer::Printf(TEXT("Created %s\n"), *MI->GetPathName());
|
||||||
if (UMaterialInstance* ParentMI = Cast<UMaterialInstance>(ParentMaterialObj))
|
if (UMaterialInstance* ParentMI = Cast<UMaterialInstance>(ParentMaterialObj))
|
||||||
UWingServer::Printf(TEXT("Parent: %s\n"), *WingUtils::FormatName(ParentMI));
|
UWingServer::Printf(TEXT("Parent: %s\n"), *WingUtils::FormatName(ParentMI));
|
||||||
@@ -66,7 +63,5 @@ public:
|
|||||||
UWingServer::Printf(TEXT("Parent: %s\n"), *WingUtils::FormatName(ParentMat));
|
UWingServer::Printf(TEXT("Parent: %s\n"), *WingUtils::FormatName(ParentMat));
|
||||||
else
|
else
|
||||||
UWingServer::Printf(TEXT("Parent: %s\n"), *ParentMaterialObj->GetPathName());
|
UWingServer::Printf(TEXT("Parent: %s\n"), *ParentMaterialObj->GetPathName());
|
||||||
if (!bSaved)
|
|
||||||
UWingServer::Print(TEXT("WARNING: Package save failed\n"));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -101,8 +101,6 @@ public:
|
|||||||
UWingServer::Printf(TEXT("Parameters of type %d (see EMaterialParameterType) are not implemented"), (int)Type);
|
UWingServer::Printf(TEXT("Parameters of type %d (see EMaterialParameterType) are not implemented"), (int)Type);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
WingUtils::SaveGenericPackage(MI);
|
|
||||||
|
|
||||||
UWingServer::Printf(TEXT("Set '%s' = %s on %s\n"),
|
UWingServer::Printf(TEXT("Set '%s' = %s on %s\n"),
|
||||||
*Parameter, *Value, *WingUtils::FormatName(MI));
|
*Parameter, *Value, *WingUtils::FormatName(MI));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,8 +38,6 @@ public:
|
|||||||
UMaterial* MaterialObj = Maker.CreateAsset<UMaterial, UMaterialFactoryNew>();
|
UMaterial* MaterialObj = Maker.CreateAsset<UMaterial, UMaterialFactoryNew>();
|
||||||
if (!MaterialObj) return;
|
if (!MaterialObj) return;
|
||||||
|
|
||||||
bool bSaved = WingUtils::SaveGenericPackage(MaterialObj);
|
|
||||||
UWingServer::Printf(TEXT("Created %s\n"), *MaterialObj->GetPathName());
|
UWingServer::Printf(TEXT("Created %s\n"), *MaterialObj->GetPathName());
|
||||||
if (!bSaved) UWingServer::Print(TEXT("WARNING: Package save failed\n"));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -69,11 +69,6 @@ public:
|
|||||||
SuccessCount++;
|
SuccessCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save.
|
|
||||||
bool bSaved = WingUtils::SaveGenericPackage(Obj);
|
|
||||||
|
|
||||||
UWingServer::Printf(TEXT("Set %d/%d properties.\n"), SuccessCount, Properties.Json->Values.Num());
|
UWingServer::Printf(TEXT("Set %d/%d properties.\n"), SuccessCount, Properties.Json->Values.Num());
|
||||||
if (!bSaved)
|
|
||||||
UWingServer::Print(TEXT("Warning: Save failed\n"));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
#include "EdGraph/EdGraphSchema.h"
|
#include "EdGraph/EdGraphSchema.h"
|
||||||
#include "Kismet2/BlueprintEditorUtils.h"
|
#include "Kismet2/BlueprintEditorUtils.h"
|
||||||
#include "Kismet2/KismetEditorUtilities.h"
|
#include "Kismet2/KismetEditorUtilities.h"
|
||||||
#include "UObject/SavePackage.h"
|
|
||||||
#include "UObject/UObjectIterator.h"
|
#include "UObject/UObjectIterator.h"
|
||||||
#include "UObject/UnrealType.h"
|
#include "UObject/UnrealType.h"
|
||||||
#include "Misc/Paths.h"
|
#include "Misc/Paths.h"
|
||||||
@@ -43,13 +42,6 @@
|
|||||||
#include "Animation/BlendSpace.h"
|
#include "Animation/BlendSpace.h"
|
||||||
#include "Engine/Texture.h"
|
#include "Engine/Texture.h"
|
||||||
|
|
||||||
// SEH support (Windows only) — defined in BlueprintWingServer.cpp
|
|
||||||
#if PLATFORM_WINDOWS
|
|
||||||
extern int32 TryCompileBlueprintSEH(UBlueprint* BP, EBlueprintCompileOptions Opts);
|
|
||||||
extern int32 TrySavePackageSEH(
|
|
||||||
UPackage* Package, UObject* Asset, const TCHAR* Filename,
|
|
||||||
FSavePackageArgs* SaveArgs, ESavePackageResult* OutResult);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Name sanitization
|
// Name sanitization
|
||||||
@@ -354,132 +346,6 @@ TArray<UEdGraphNode*> WingUtils::AllNodes(UBlueprint* BP)
|
|||||||
return Nodes;
|
return Nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WingUtils::SaveBlueprintPackage(UBlueprint* BP)
|
|
||||||
{
|
|
||||||
UPackage* Package = BP->GetPackage();
|
|
||||||
|
|
||||||
// 1. Build absolute package filename — use .umap for map packages, .uasset otherwise
|
|
||||||
FString PackageExtension = Package->ContainsMap()
|
|
||||||
? FPackageName::GetMapPackageExtension()
|
|
||||||
: FPackageName::GetAssetPackageExtension();
|
|
||||||
FString PackageFilename = FPackageName::LongPackageNameToFilename(
|
|
||||||
Package->GetName(), PackageExtension);
|
|
||||||
PackageFilename = FPaths::ConvertRelativePathToFull(PackageFilename);
|
|
||||||
UE_LOG(LogTemp, Display, TEXT("UEWingman: Save target: %s"), *PackageFilename);
|
|
||||||
|
|
||||||
// 2. Phase 1: Try explicit compilation (same flags as UCompileAllBlueprintsCommandlet)
|
|
||||||
bool bCompiled = false;
|
|
||||||
{
|
|
||||||
EBlueprintCompileOptions CompileOpts =
|
|
||||||
EBlueprintCompileOptions::SkipSave |
|
|
||||||
EBlueprintCompileOptions::BatchCompile |
|
|
||||||
EBlueprintCompileOptions::SkipGarbageCollection |
|
|
||||||
EBlueprintCompileOptions::SkipFiBSearchMetaUpdate;
|
|
||||||
|
|
||||||
UE_LOG(LogTemp, Display, TEXT("UEWingman: Phase 1: Attempting explicit compilation..."));
|
|
||||||
|
|
||||||
#if PLATFORM_WINDOWS
|
|
||||||
int32 CompileResult = TryCompileBlueprintSEH(BP, CompileOpts);
|
|
||||||
if (CompileResult == 0)
|
|
||||||
{
|
|
||||||
bCompiled = (BP->Status == BS_UpToDate);
|
|
||||||
UE_LOG(LogTemp, Display, TEXT("UEWingman: Compilation %s (status=%d)"),
|
|
||||||
bCompiled ? TEXT("succeeded") : TEXT("completed with warnings"), (int32)BP->Status);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
UE_LOG(LogTemp, Warning, TEXT("UEWingman: Compilation crashed (SEH), proceeding uncompiled"));
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
FKismetEditorUtilities::CompileBlueprint(BP, CompileOpts, nullptr);
|
|
||||||
bCompiled = (BP->Status == BS_UpToDate);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Phase 2: Set guards for save
|
|
||||||
uint8 OldRegen = BP->bIsRegeneratingOnLoad;
|
|
||||||
BP->bIsRegeneratingOnLoad = true;
|
|
||||||
|
|
||||||
EBlueprintStatus OldStatus = (EBlueprintStatus)(uint8)BP->Status;
|
|
||||||
if (!bCompiled)
|
|
||||||
{
|
|
||||||
// Tell PreSave the BP is up-to-date so it doesn't try to compile
|
|
||||||
BP->Status = BS_UpToDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Clear read-only attribute if present (source control or LFS may set this)
|
|
||||||
if (FPlatformFileManager::Get().GetPlatformFile().IsReadOnly(*PackageFilename))
|
|
||||||
{
|
|
||||||
UE_LOG(LogTemp, Display, TEXT("UEWingman: Clearing read-only attribute on %s"), *PackageFilename);
|
|
||||||
FPlatformFileManager::Get().GetPlatformFile().SetReadOnly(*PackageFilename, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. Phase 3: Save with SAVE_NoError + SEH protection
|
|
||||||
FSavePackageArgs SaveArgs;
|
|
||||||
SaveArgs.TopLevelFlags = RF_Public | RF_Standalone;
|
|
||||||
SaveArgs.SaveFlags = SAVE_NoError;
|
|
||||||
|
|
||||||
// For level blueprints (map packages), the base object should be the UWorld, not the BP
|
|
||||||
bool bIsMapPackage = Package->ContainsMap();
|
|
||||||
UObject* BaseObject = BP;
|
|
||||||
if (bIsMapPackage)
|
|
||||||
{
|
|
||||||
// Find the UWorld in this package — it's the actual asset for .umap files
|
|
||||||
UWorld* World = FindObject<UWorld>(Package, *Package->GetName().Mid(Package->GetName().Find(TEXT("/"), ESearchCase::IgnoreCase, ESearchDir::FromEnd) + 1));
|
|
||||||
if (!World)
|
|
||||||
{
|
|
||||||
// Fallback: iterate the package to find any UWorld
|
|
||||||
ForEachObjectWithPackage(Package, [&World](UObject* Obj) {
|
|
||||||
if (UWorld* W = Cast<UWorld>(Obj))
|
|
||||||
{
|
|
||||||
World = W;
|
|
||||||
return false; // stop
|
|
||||||
}
|
|
||||||
return true; // continue
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (World)
|
|
||||||
{
|
|
||||||
BaseObject = World;
|
|
||||||
UE_LOG(LogTemp, Display, TEXT("UEWingman: Map package detected — saving UWorld '%s'"), *World->GetName());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
UE_LOG(LogTemp, Warning, TEXT("UEWingman: Map package detected but no UWorld found — saving with BP as base"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ESavePackageResult SaveResult = ESavePackageResult::Error;
|
|
||||||
|
|
||||||
UE_LOG(LogTemp, Display, TEXT("UEWingman: Phase 3: Calling UPackage::Save (compiled=%s, isMap=%s)..."),
|
|
||||||
bCompiled ? TEXT("yes") : TEXT("no"), bIsMapPackage ? TEXT("yes") : TEXT("no"));
|
|
||||||
|
|
||||||
#if PLATFORM_WINDOWS
|
|
||||||
int32 SEHCode = TrySavePackageSEH(Package, BaseObject, *PackageFilename, &SaveArgs, &SaveResult);
|
|
||||||
if (SEHCode != 0)
|
|
||||||
{
|
|
||||||
UE_LOG(LogTemp, Error, TEXT("UEWingman: UPackage::Save CRASHED (SEH exception caught)"));
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
FSavePackageResultStruct Result = UPackage::Save(Package, BaseObject, *PackageFilename, SaveArgs);
|
|
||||||
SaveResult = Result.Result;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// 6. Restore guards
|
|
||||||
BP->bIsRegeneratingOnLoad = OldRegen;
|
|
||||||
if (!bCompiled)
|
|
||||||
{
|
|
||||||
BP->Status = (TEnumAsByte<EBlueprintStatus>)OldStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool bSuccess = (SaveResult == ESavePackageResult::Success);
|
|
||||||
UE_LOG(LogTemp, Display, TEXT("UEWingman: SaveBlueprintPackage — %s for '%s' (compiled=%s, result=%d)"),
|
|
||||||
bSuccess ? TEXT("SUCCEEDED") : TEXT("FAILED"),
|
|
||||||
*BP->GetName(), bCompiled ? TEXT("yes") : TEXT("no"), (int32)SaveResult);
|
|
||||||
|
|
||||||
return bSuccess;
|
|
||||||
|
|
||||||
}
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Material helpers
|
// Material helpers
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -529,43 +395,6 @@ UMaterial* WingUtils::ReplaceMaterialWithTransientCopy(UMaterial* Material)
|
|||||||
return Material;
|
return Material;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WingUtils::SaveGenericPackage(UObject* Asset)
|
|
||||||
{
|
|
||||||
if (!Asset) return false;
|
|
||||||
UPackage* Package = Asset->GetPackage();
|
|
||||||
UE_LOG(LogTemp, Display, TEXT("UEWingman: SaveGenericPackage — begin for '%s'"), *Asset->GetName());
|
|
||||||
|
|
||||||
FString PackageFilename = FPackageName::LongPackageNameToFilename(
|
|
||||||
Package->GetName(), FPackageName::GetAssetPackageExtension());
|
|
||||||
PackageFilename = FPaths::ConvertRelativePathToFull(PackageFilename);
|
|
||||||
|
|
||||||
if (FPlatformFileManager::Get().GetPlatformFile().IsReadOnly(*PackageFilename))
|
|
||||||
{
|
|
||||||
FPlatformFileManager::Get().GetPlatformFile().SetReadOnly(*PackageFilename, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
FSavePackageArgs SaveArgs;
|
|
||||||
SaveArgs.TopLevelFlags = RF_Public | RF_Standalone;
|
|
||||||
SaveArgs.SaveFlags = SAVE_NoError;
|
|
||||||
|
|
||||||
ESavePackageResult SaveResult = ESavePackageResult::Error;
|
|
||||||
#if PLATFORM_WINDOWS
|
|
||||||
int32 SEHCode = TrySavePackageSEH(Package, Asset, *PackageFilename, &SaveArgs, &SaveResult);
|
|
||||||
if (SEHCode != 0)
|
|
||||||
{
|
|
||||||
UE_LOG(LogTemp, Error, TEXT("UEWingman: SaveGenericPackage CRASHED (SEH exception)"));
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
FSavePackageResultStruct Result = UPackage::Save(Package, Asset, *PackageFilename, SaveArgs);
|
|
||||||
SaveResult = Result.Result;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool bSuccess = (SaveResult == ESavePackageResult::Success);
|
|
||||||
UE_LOG(LogTemp, Display, TEXT("UEWingman: SaveGenericPackage — %s for '%s'"),
|
|
||||||
bSuccess ? TEXT("SUCCEEDED") : TEXT("FAILED"), *Asset->GetName());
|
|
||||||
return bSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Anim blueprint helpers
|
// Anim blueprint helpers
|
||||||
|
|||||||
@@ -164,11 +164,8 @@ public:
|
|||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool SaveBlueprintPackage(UBlueprint* BP);
|
|
||||||
|
|
||||||
// ----- Material helpers -----
|
// ----- Material helpers -----
|
||||||
static void EnsureMaterialGraph(UMaterial* Material);
|
static void EnsureMaterialGraph(UMaterial* Material);
|
||||||
static bool SaveGenericPackage(UObject* Asset);
|
|
||||||
|
|
||||||
// If the material editor has a transient preview copy of this material,
|
// If the material editor has a transient preview copy of this material,
|
||||||
// return that copy (which is what the editor is actually working on).
|
// return that copy (which is what the editor is actually working on).
|
||||||
|
|||||||
Reference in New Issue
Block a user