From 23c096b614e120b2917518835675a2cf4da7236b Mon Sep 17 00:00:00 2001 From: jyelon Date: Thu, 19 Mar 2026 10:56:20 -0400 Subject: [PATCH] Removed all code to save packages from the MCP --- Plugins/UEWingman/Deprecated/OldWingUtils.cpp | 867 ++++++++++++++++++ .../HalfBaked/AnimBlueprint_Create.h | 4 +- .../AnimBlueprint_SetBlendSpaceSamples.h | 7 - .../UEWingman/HalfBaked/BlendSpace_Create.h | 3 - .../HalfBaked/BlueprintGraph_Create.h | 1 - .../HalfBaked/BlueprintGraph_Delete.h | 3 - .../HalfBaked/BlueprintGraph_Rename.h | 1 - .../HalfBaked/Blueprint_AddComponent.h | 4 +- .../HalfBaked/Blueprint_AddEventDispatcher.h | 3 - .../HalfBaked/Blueprint_RemoveComponent.h | 6 +- .../UEWingman/HalfBaked/Blueprint_Reparent.h | 7 +- .../Source/UEWingman/HalfBaked/Enum_Create.h | 4 - .../HalfBaked/MaterialFunction_Create.h | 4 - .../HalfBaked/StateMachine_AddState.h | 3 +- .../HalfBaked/StateMachine_AddTransition.h | 3 +- .../HalfBaked/StateMachine_RemoveState.h | 3 +- .../HalfBaked/StateMachine_SetAnimation.h | 3 +- .../HalfBaked/StateMachine_SetBlendSpace.h | 5 +- .../StateMachine_SetTransitionRule.h | 3 +- .../UEWingman/HalfBaked/Struct_Create.h | 4 - .../UEWingman/Handlers/Blueprint_Create.h | 4 +- .../MaterialInstance_ClearParameter.h | 1 - .../Handlers/MaterialInstance_Create.h | 5 - .../Handlers/MaterialInstance_SetParameter.h | 2 - .../UEWingman/Handlers/Material_Create.h | 2 - .../Source/UEWingman/Handlers/Property_Set.h | 5 - .../Source/UEWingman/Private/WingUtils.cpp | 171 ---- .../Source/UEWingman/Public/WingUtils.h | 3 - 28 files changed, 880 insertions(+), 251 deletions(-) create mode 100644 Plugins/UEWingman/Deprecated/OldWingUtils.cpp diff --git a/Plugins/UEWingman/Deprecated/OldWingUtils.cpp b/Plugins/UEWingman/Deprecated/OldWingUtils.cpp new file mode 100644 index 00000000..586de583 --- /dev/null +++ b/Plugins/UEWingman/Deprecated/OldWingUtils.cpp @@ -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. +// 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(Struct) || Cast(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 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 WingUtils::AllGraphs(UBlueprint* BP) +{ + TArray Graphs; + BP->GetAllGraphs(Graphs); + return Graphs; +} + + +TArray WingUtils::AllNodes(UBlueprint* BP) +{ + TArray 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(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(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)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( + 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(); + 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(EditorInstance); + UMaterialInterface* Edited = MatEditor->GetMaterialInterface(); + if (UMaterial* EditedMat = Cast(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 AllGraphs; + BP->GetAllGraphs(AllGraphs); + for (UEdGraph* Graph : AllGraphs) + { + if (UAnimationStateMachineGraph* SMGraph = Cast(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(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(Node)) + { + UAnimStateNode* FromState = Cast(TransNode->GetPreviousState()); + UAnimStateNode* ToState = Cast(TransNode->GetNextState()); + if (FromState && ToState && + (FromState->GetStateName() == FromStateName) && + (ToState->GetStateName() == ToStateName)) + { + return TransNode; + } + } + } + return nullptr; +} + +// ============================================================ +// Graph actions (node spawning) +// ============================================================ + +FString WingUtils::ActionFullName(const TSharedPtr& Action) +{ + FString Category = Action->GetCategory().ToString(); + FString MenuName = Action->GetMenuDescription().ToString(); + if (Category.IsEmpty()) + return MenuName; + return Category + TEXT("|") + MenuName; +} + +TArray> WingUtils::SearchGraphActions(UEdGraph* Graph, const FString& Query, int32 MaxResults, bool ExactMatch) +{ + FString QueryLower = Query.ToLower(); + TArray> Result; + + FGraphContextMenuBuilder ContextMenuBuilder(Graph); + Graph->GetSchema()->GetGraphContextActions(ContextMenuBuilder); + + for (int32 i = 0; i < ContextMenuBuilder.GetNumActions(); i++) + { + TSharedPtr 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 WingUtils::CollectHandlerClasses() +{ + TArray Result; + for (TObjectIterator 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 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(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(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(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 WingUtils::SearchProperties(UObject* Obj, const FString& Query, EPropertyFlags Flags, bool bLocal) +{ + TArray Result; + if (!Obj) return Result; + UClass* ObjClass = Obj->GetClass(); + for (TFieldIterator 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(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 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 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")); + } +} diff --git a/Plugins/UEWingman/Source/UEWingman/HalfBaked/AnimBlueprint_Create.h b/Plugins/UEWingman/Source/UEWingman/HalfBaked/AnimBlueprint_Create.h index d79d2d6c..198102fc 100644 --- a/Plugins/UEWingman/Source/UEWingman/HalfBaked/AnimBlueprint_Create.h +++ b/Plugins/UEWingman/Source/UEWingman/HalfBaked/AnimBlueprint_Create.h @@ -91,13 +91,11 @@ public: // Set target skeleton NewAnimBP->TargetSkeleton = SkeletonObj; - // Compile and save + // Compile FKismetEditorUtilities::CompileBlueprint(NewAnimBP); - bool bSaved = WingUtils::SaveBlueprintPackage(NewAnimBP); UWingServer::Printf(TEXT("Created: %s\n"), *AssetPath); UWingServer::Printf(TEXT("ParentClass: %s\n"), *WingUtils::FormatName(ParentClassObj)); - UWingServer::Printf(TEXT("Saved: %s\n"), bSaved ? TEXT("true") : TEXT("false")); TArray Graphs = WingUtils::AllGraphs(NewAnimBP); for (UEdGraph* Graph : Graphs) diff --git a/Plugins/UEWingman/Source/UEWingman/HalfBaked/AnimBlueprint_SetBlendSpaceSamples.h b/Plugins/UEWingman/Source/UEWingman/HalfBaked/AnimBlueprint_SetBlendSpaceSamples.h index d6c683a9..d9958691 100644 --- a/Plugins/UEWingman/Source/UEWingman/HalfBaked/AnimBlueprint_SetBlendSpaceSamples.h +++ b/Plugins/UEWingman/Source/UEWingman/HalfBaked/AnimBlueprint_SetBlendSpaceSamples.h @@ -126,13 +126,6 @@ public: BS->ValidateSampleData(); - // Save - bool bSaved = WingUtils::SaveGenericPackage(BS); - UWingServer::Printf(TEXT("Set %d samples on %s\n"), SamplesSet, *WingUtils::FormatName(BS)); - if (!bSaved) - { - UWingServer::Print(TEXT("WARNING: package save failed\n")); - } } }; diff --git a/Plugins/UEWingman/Source/UEWingman/HalfBaked/BlendSpace_Create.h b/Plugins/UEWingman/Source/UEWingman/HalfBaked/BlendSpace_Create.h index 1905c680..c9599024 100644 --- a/Plugins/UEWingman/Source/UEWingman/HalfBaked/BlendSpace_Create.h +++ b/Plugins/UEWingman/Source/UEWingman/HalfBaked/BlendSpace_Create.h @@ -55,11 +55,8 @@ public: NewBS->SetSkeleton(SkeletonObj); NewBS->MarkPackageDirty(); - bool bSaved = WingUtils::SaveGenericPackage(NewBS); UWingServer::Printf(TEXT("Created %s\n"), *NewBS->GetPathName()); UWingServer::Printf(TEXT("Skeleton: %s\n"), *SkeletonObj->GetPathName()); - if (!bSaved) - UWingServer::Print(TEXT("WARNING: Package save failed\n")); } }; diff --git a/Plugins/UEWingman/Source/UEWingman/HalfBaked/BlueprintGraph_Create.h b/Plugins/UEWingman/Source/UEWingman/HalfBaked/BlueprintGraph_Create.h index f0bc5810..298c0db7 100644 --- a/Plugins/UEWingman/Source/UEWingman/HalfBaked/BlueprintGraph_Create.h +++ b/Plugins/UEWingman/Source/UEWingman/HalfBaked/BlueprintGraph_Create.h @@ -112,6 +112,5 @@ public: UWingServer::Printf(TEXT("Created custom event: %s\n"), *WingUtils::FormatName(NewEvent)); } - WingUtils::SaveBlueprintPackage(BP); } }; diff --git a/Plugins/UEWingman/Source/UEWingman/HalfBaked/BlueprintGraph_Delete.h b/Plugins/UEWingman/Source/UEWingman/HalfBaked/BlueprintGraph_Delete.h index a78ea022..2ffef780 100644 --- a/Plugins/UEWingman/Source/UEWingman/HalfBaked/BlueprintGraph_Delete.h +++ b/Plugins/UEWingman/Source/UEWingman/HalfBaked/BlueprintGraph_Delete.h @@ -87,10 +87,7 @@ public: // Remove the graph FString GraphName = WingUtils::FormatName(TargetGraph); FBlueprintEditorUtils::RemoveGraph(BP, TargetGraph, EGraphRemoveFlags::Default); - bool bSaved = WingUtils::SaveBlueprintPackage(BP); UWingServer::Printf(TEXT("Deleted %s graph %s\n"), *GraphType, *GraphName); - if (!bSaved) - UWingServer::Print(TEXT("WARNING: Package save failed.\n")); } }; diff --git a/Plugins/UEWingman/Source/UEWingman/HalfBaked/BlueprintGraph_Rename.h b/Plugins/UEWingman/Source/UEWingman/HalfBaked/BlueprintGraph_Rename.h index 706b6b30..447d5515 100644 --- a/Plugins/UEWingman/Source/UEWingman/HalfBaked/BlueprintGraph_Rename.h +++ b/Plugins/UEWingman/Source/UEWingman/HalfBaked/BlueprintGraph_Rename.h @@ -75,7 +75,6 @@ public: } FBlueprintEditorUtils::RenameGraph(TargetGraph, NewName); - WingUtils::SaveBlueprintPackage(BP); UWingServer::Printf(TEXT("Renamed to %s %s\n"), bIsFunction ? TEXT("function") : TEXT("macro"), diff --git a/Plugins/UEWingman/Source/UEWingman/HalfBaked/Blueprint_AddComponent.h b/Plugins/UEWingman/Source/UEWingman/HalfBaked/Blueprint_AddComponent.h index c3deaac3..f28e666e 100644 --- a/Plugins/UEWingman/Source/UEWingman/HalfBaked/Blueprint_AddComponent.h +++ b/Plugins/UEWingman/Source/UEWingman/HalfBaked/Blueprint_AddComponent.h @@ -110,8 +110,6 @@ public: SCS->AddNode(NewNode); } - bool bSaved = WingUtils::SaveBlueprintPackage(BP); - UWingServer::Printf(TEXT("Added component %s (%s)"), *WingUtils::FormatName(NewNode->ComponentTemplate), *WingUtils::FormatName(ComponentClassObj)); @@ -119,6 +117,6 @@ public: { UWingServer::Printf(TEXT(" under %s"), *WingUtils::FormatName(ParentSCSNode->ComponentTemplate)); } - UWingServer::Printf(TEXT("\nSaved: %s\n"), bSaved ? TEXT("true") : TEXT("false")); + UWingServer::Print(TEXT("\n")); } }; diff --git a/Plugins/UEWingman/Source/UEWingman/HalfBaked/Blueprint_AddEventDispatcher.h b/Plugins/UEWingman/Source/UEWingman/HalfBaked/Blueprint_AddEventDispatcher.h index 15ce4c1d..d9651903 100644 --- a/Plugins/UEWingman/Source/UEWingman/HalfBaked/Blueprint_AddEventDispatcher.h +++ b/Plugins/UEWingman/Source/UEWingman/HalfBaked/Blueprint_AddEventDispatcher.h @@ -115,7 +115,6 @@ public: if (!EntryNode) { - WingUtils::SaveBlueprintPackage(BP); UWingServer::Print(TEXT("Error: Event dispatcher created but entry node not found — parameters could not be added.\n")); return; } @@ -135,8 +134,6 @@ public: } } - WingUtils::SaveBlueprintPackage(BP); - UWingServer::Printf(TEXT("Created event dispatcher '%s'"), *DispatcherName); if (ParamCount > 0) UWingServer::Printf(TEXT(" with %d parameter(s)"), ParamCount); diff --git a/Plugins/UEWingman/Source/UEWingman/HalfBaked/Blueprint_RemoveComponent.h b/Plugins/UEWingman/Source/UEWingman/HalfBaked/Blueprint_RemoveComponent.h index 8f2b594c..c3466362 100644 --- a/Plugins/UEWingman/Source/UEWingman/HalfBaked/Blueprint_RemoveComponent.h +++ b/Plugins/UEWingman/Source/UEWingman/HalfBaked/Blueprint_RemoveComponent.h @@ -86,10 +86,6 @@ public: // Remove the node (promotes children to parent if it has any — but we've guarded root above) SCS->RemoveNodeAndPromoteChildren(NodeToRemove); - bool bSaved = WingUtils::SaveBlueprintPackage(BP); - - UWingServer::Printf(TEXT("Removed component %s.%s\n"), - *RemovedName, - bSaved ? TEXT("") : TEXT(" WARNING: save failed.")); + UWingServer::Printf(TEXT("Removed component %s.\n"), *RemovedName); } }; diff --git a/Plugins/UEWingman/Source/UEWingman/HalfBaked/Blueprint_Reparent.h b/Plugins/UEWingman/Source/UEWingman/HalfBaked/Blueprint_Reparent.h index 6d572755..5505f664 100644 --- a/Plugins/UEWingman/Source/UEWingman/HalfBaked/Blueprint_Reparent.h +++ b/Plugins/UEWingman/Source/UEWingman/HalfBaked/Blueprint_Reparent.h @@ -25,12 +25,12 @@ public: UPROPERTY(meta=(Description="Blueprint package path")) FString Blueprint; - UPROPERTY(meta=(Description="New parent class: C++ class name or Blueprint package path")) - FString NewParentClass; + UPROPERTY(meta=(Description="New parent class")) + FString Parent; 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 @@ -50,7 +50,6 @@ public: BP->ParentClass = NewParentClassObj; FBlueprintEditorUtils::RefreshAllNodes(BP); FKismetEditorUtilities::CompileBlueprint(BP); - bool bSaved = WingUtils::SaveBlueprintPackage(BP); UWingServer::Printf(TEXT("Reparented %s: %s -> %s\n"), *WingUtils::FormatName(BP), *OldParentName, *WingUtils::FormatName(NewParentClassObj)); diff --git a/Plugins/UEWingman/Source/UEWingman/HalfBaked/Enum_Create.h b/Plugins/UEWingman/Source/UEWingman/HalfBaked/Enum_Create.h index fc4dd66f..8dd64c16 100644 --- a/Plugins/UEWingman/Source/UEWingman/HalfBaked/Enum_Create.h +++ b/Plugins/UEWingman/Source/UEWingman/HalfBaked/Enum_Create.h @@ -62,10 +62,6 @@ public: 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()); - if (!bSaved) - UWingServer::Print(TEXT("WARNING: Package save failed\n")); } }; diff --git a/Plugins/UEWingman/Source/UEWingman/HalfBaked/MaterialFunction_Create.h b/Plugins/UEWingman/Source/UEWingman/HalfBaked/MaterialFunction_Create.h index 0a1d9c0a..42aae2e6 100644 --- a/Plugins/UEWingman/Source/UEWingman/HalfBaked/MaterialFunction_Create.h +++ b/Plugins/UEWingman/Source/UEWingman/HalfBaked/MaterialFunction_Create.h @@ -44,10 +44,6 @@ public: if (!Description.IsEmpty()) MF->Description = Description; - bool bSaved = WingUtils::SaveGenericPackage(MF); - UWingServer::Printf(TEXT("Created %s\n"), *MF->GetPathName()); - if (!bSaved) - UWingServer::Print(TEXT("WARNING: Package save failed\n")); } }; diff --git a/Plugins/UEWingman/Source/UEWingman/HalfBaked/StateMachine_AddState.h b/Plugins/UEWingman/Source/UEWingman/HalfBaked/StateMachine_AddState.h index 2c8ef12f..80f8276f 100644 --- a/Plugins/UEWingman/Source/UEWingman/HalfBaked/StateMachine_AddState.h +++ b/Plugins/UEWingman/Source/UEWingman/HalfBaked/StateMachine_AddState.h @@ -107,9 +107,8 @@ public: NewState->GetBoundGraph()->AddNode(SeqNode, false, false); } - // Compile and save + // Compile FKismetEditorUtilities::CompileBlueprint(AnimBP); - WingUtils::SaveBlueprintPackage(AnimBP); UWingServer::Printf(TEXT("Created state '%s' in %s\n"), *StateName, *WingUtils::FormatName(SMGraph)); UWingServer::Printf(TEXT(" node: %s\n"), *WingUtils::FormatName(NewState)); diff --git a/Plugins/UEWingman/Source/UEWingman/HalfBaked/StateMachine_AddTransition.h b/Plugins/UEWingman/Source/UEWingman/HalfBaked/StateMachine_AddTransition.h index 3d9628a3..6deae007 100644 --- a/Plugins/UEWingman/Source/UEWingman/HalfBaked/StateMachine_AddTransition.h +++ b/Plugins/UEWingman/Source/UEWingman/HalfBaked/StateMachine_AddTransition.h @@ -89,9 +89,8 @@ public: TransNode->PriorityOrder = Priority; TransNode->Bidirectional = BBidirectional; - // Compile and save + // Compile FKismetEditorUtilities::CompileBlueprint(AnimBP); - WingUtils::SaveBlueprintPackage(AnimBP); UWingServer::Printf(TEXT("Created transition %s -> %s: %s\n"), *FromState, *ToState, *WingUtils::FormatName(TransNode)); diff --git a/Plugins/UEWingman/Source/UEWingman/HalfBaked/StateMachine_RemoveState.h b/Plugins/UEWingman/Source/UEWingman/HalfBaked/StateMachine_RemoveState.h index b1dd4d2b..f5e4436e 100644 --- a/Plugins/UEWingman/Source/UEWingman/HalfBaked/StateMachine_RemoveState.h +++ b/Plugins/UEWingman/Source/UEWingman/HalfBaked/StateMachine_RemoveState.h @@ -72,9 +72,8 @@ public: StateNode->BreakAllNodeLinks(); SMGraph->RemoveNode(StateNode); - // Compile and save + // Compile FKismetEditorUtilities::CompileBlueprint(BP); - WingUtils::SaveBlueprintPackage(BP); UWingServer::Printf(TEXT("Removed state %s and %d transition(s).\n"), *WingUtils::FormatName(StateNode), RemovedTransitions); diff --git a/Plugins/UEWingman/Source/UEWingman/HalfBaked/StateMachine_SetAnimation.h b/Plugins/UEWingman/Source/UEWingman/HalfBaked/StateMachine_SetAnimation.h index 185e3089..c922ce8f 100644 --- a/Plugins/UEWingman/Source/UEWingman/HalfBaked/StateMachine_SetAnimation.h +++ b/Plugins/UEWingman/Source/UEWingman/HalfBaked/StateMachine_SetAnimation.h @@ -97,9 +97,8 @@ public: SeqNode->SetAnimationAsset(AnimSeq); - // Compile and save + // Compile FKismetEditorUtilities::CompileBlueprint(AnimBP); - WingUtils::SaveBlueprintPackage(AnimBP); if (bCreatedNew) UWingServer::Printf(TEXT("Created sequence player in state '%s', assigned %s\n"), *StateName, *WingUtils::FormatName(AnimSeq)); diff --git a/Plugins/UEWingman/Source/UEWingman/HalfBaked/StateMachine_SetBlendSpace.h b/Plugins/UEWingman/Source/UEWingman/HalfBaked/StateMachine_SetBlendSpace.h index 964ff02e..e1c72d06 100644 --- a/Plugins/UEWingman/Source/UEWingman/HalfBaked/StateMachine_SetBlendSpace.h +++ b/Plugins/UEWingman/Source/UEWingman/HalfBaked/StateMachine_SetBlendSpace.h @@ -103,14 +103,11 @@ public: WireVariable(AnimBP, InnerGraph, BSNode, XVariable, TEXT("X")); WireVariable(AnimBP, InnerGraph, BSNode, YVariable, TEXT("Y")); - // Compile and save + // Compile FKismetEditorUtilities::CompileBlueprint(AnimBP); - bool bSaved = WingUtils::SaveBlueprintPackage(AnimBP); UWingServer::Printf(TEXT("BlendSpacePlayer %s placed in state %s\n"), *WingUtils::FormatName(BSNode), *StateName); - if (!bSaved) - UWingServer::Print(TEXT("WARNING: Failed to save package\n")); } private: diff --git a/Plugins/UEWingman/Source/UEWingman/HalfBaked/StateMachine_SetTransitionRule.h b/Plugins/UEWingman/Source/UEWingman/HalfBaked/StateMachine_SetTransitionRule.h index f3425e37..40d5c419 100644 --- a/Plugins/UEWingman/Source/UEWingman/HalfBaked/StateMachine_SetTransitionRule.h +++ b/Plugins/UEWingman/Source/UEWingman/HalfBaked/StateMachine_SetTransitionRule.h @@ -82,9 +82,8 @@ public: TransNode->LogicType = (ETransitionLogicType::Type)LogicType; TransNode->Bidirectional = BBidirectional; - // Compile and save + // Compile FKismetEditorUtilities::CompileBlueprint(AnimBP); - WingUtils::SaveBlueprintPackage(AnimBP); UWingServer::Printf(TEXT("Updated transition %s -> %s: %s\n"), *FromState, *ToState, *WingUtils::FormatName(TransNode)); diff --git a/Plugins/UEWingman/Source/UEWingman/HalfBaked/Struct_Create.h b/Plugins/UEWingman/Source/UEWingman/HalfBaked/Struct_Create.h index faf35ad7..0ce794b0 100644 --- a/Plugins/UEWingman/Source/UEWingman/HalfBaked/Struct_Create.h +++ b/Plugins/UEWingman/Source/UEWingman/HalfBaked/Struct_Create.h @@ -88,12 +88,8 @@ public: PropsAdded++; } - bool bSaved = WingUtils::SaveGenericPackage(NewStruct); - UWingServer::Printf(TEXT("Created %s\n"), *NewStruct->GetPathName()); if (PropsAdded > 0) UWingServer::Printf(TEXT("Properties added: %d\n"), PropsAdded); - if (!bSaved) - UWingServer::Print(TEXT("WARNING: Package save failed\n")); } }; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Create.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Create.h index d548a100..e2cd257c 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Create.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Create.h @@ -83,12 +83,10 @@ public: return; } - // Compile and save + // Compile FKismetEditorUtilities::CompileBlueprint(NewBP); - bool bSaved = WingUtils::SaveBlueprintPackage(NewBP); // Report result UWingServer::Printf(TEXT("Created: %s\n"), *WingUtils::FormatName(NewBP)); - if (!bSaved) UWingServer::Print(TEXT("Warning: save failed\n")); } }; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/MaterialInstance_ClearParameter.h b/Plugins/UEWingman/Source/UEWingman/Handlers/MaterialInstance_ClearParameter.h index 124ece36..de140df5 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/MaterialInstance_ClearParameter.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/MaterialInstance_ClearParameter.h @@ -73,7 +73,6 @@ public: return; } - WingUtils::SaveGenericPackage(MI); UWingServer::Printf(TEXT("Cleared override for '%s' on %s\n"), *Parameter, *WingUtils::FormatName(MI)); } diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/MaterialInstance_Create.h b/Plugins/UEWingman/Source/UEWingman/Handlers/MaterialInstance_Create.h index 1aadfec2..da716410 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/MaterialInstance_Create.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/MaterialInstance_Create.h @@ -56,9 +56,6 @@ public: // Set parent. MI->Parent = ParentMaterialObj; - // Save. - bool bSaved = WingUtils::SaveGenericPackage(MI); - UWingServer::Printf(TEXT("Created %s\n"), *MI->GetPathName()); if (UMaterialInstance* ParentMI = Cast(ParentMaterialObj)) UWingServer::Printf(TEXT("Parent: %s\n"), *WingUtils::FormatName(ParentMI)); @@ -66,7 +63,5 @@ public: UWingServer::Printf(TEXT("Parent: %s\n"), *WingUtils::FormatName(ParentMat)); else UWingServer::Printf(TEXT("Parent: %s\n"), *ParentMaterialObj->GetPathName()); - if (!bSaved) - UWingServer::Print(TEXT("WARNING: Package save failed\n")); } }; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/MaterialInstance_SetParameter.h b/Plugins/UEWingman/Source/UEWingman/Handlers/MaterialInstance_SetParameter.h index 49c540e9..bad14395 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/MaterialInstance_SetParameter.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/MaterialInstance_SetParameter.h @@ -101,8 +101,6 @@ public: UWingServer::Printf(TEXT("Parameters of type %d (see EMaterialParameterType) are not implemented"), (int)Type); return; } - WingUtils::SaveGenericPackage(MI); - UWingServer::Printf(TEXT("Set '%s' = %s on %s\n"), *Parameter, *Value, *WingUtils::FormatName(MI)); } diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Material_Create.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Material_Create.h index e2df6cf7..e35cc16d 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Material_Create.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Material_Create.h @@ -38,8 +38,6 @@ public: UMaterial* MaterialObj = Maker.CreateAsset(); if (!MaterialObj) return; - bool bSaved = WingUtils::SaveGenericPackage(MaterialObj); UWingServer::Printf(TEXT("Created %s\n"), *MaterialObj->GetPathName()); - if (!bSaved) UWingServer::Print(TEXT("WARNING: Package save failed\n")); } }; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Set.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Set.h index 8719ff46..e0ba1b59 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Set.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Set.h @@ -69,11 +69,6 @@ public: SuccessCount++; } - // Save. - bool bSaved = WingUtils::SaveGenericPackage(Obj); - UWingServer::Printf(TEXT("Set %d/%d properties.\n"), SuccessCount, Properties.Json->Values.Num()); - if (!bSaved) - UWingServer::Print(TEXT("Warning: Save failed\n")); } }; diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp index 586de583..aa101047 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp @@ -15,7 +15,6 @@ #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" @@ -43,13 +42,6 @@ #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 @@ -354,132 +346,6 @@ TArray WingUtils::AllNodes(UBlueprint* BP) 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(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(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)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 // ============================================================ @@ -529,43 +395,6 @@ UMaterial* WingUtils::ReplaceMaterialWithTransientCopy(UMaterial* 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 diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h b/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h index e30dd117..081918ba 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h @@ -164,11 +164,8 @@ public: return Result; } - static bool SaveBlueprintPackage(UBlueprint* BP); - // ----- Material helpers ----- static void EnsureMaterialGraph(UMaterial* Material); - static bool SaveGenericPackage(UObject* Asset); // If the material editor has a transient preview copy of this material, // return that copy (which is what the editor is actually working on).