From 935d11f55457d2db7136cb2a7fd12983199cb842 Mon Sep 17 00:00:00 2001 From: edujiro5972 <336072401+edujiro5972@users.noreply.github.com> Date: Wed, 30 Sep 2026 08:35:11 +0200 Subject: [PATCH] L6e : outils LPDumpWidgetTree et LPFixButtonPadding (CX-B09, ERR-034) ; photographie avant et simulation Co-Authored-By: Claude Opus 5.5 --- .../LPDumpWidgetTreeCommandlet.cpp | 124 ++++++++++++ .../LPDumpWidgetTreeCommandlet.h | 25 +++ .../LPFixButtonPaddingCommandlet.cpp | 177 ++++++++++++++++++ .../LPFixButtonPaddingCommandlet.h | 30 +++ 4 files changed, 356 insertions(+) create mode 100644 Source/LifePlayEditorTools/LPDumpWidgetTreeCommandlet.cpp create mode 100644 Source/LifePlayEditorTools/LPDumpWidgetTreeCommandlet.h create mode 100644 Source/LifePlayEditorTools/LPFixButtonPaddingCommandlet.cpp create mode 100644 Source/LifePlayEditorTools/LPFixButtonPaddingCommandlet.h diff --git a/Source/LifePlayEditorTools/LPDumpWidgetTreeCommandlet.cpp b/Source/LifePlayEditorTools/LPDumpWidgetTreeCommandlet.cpp new file mode 100644 index 0000000..e93f468 --- /dev/null +++ b/Source/LifePlayEditorTools/LPDumpWidgetTreeCommandlet.cpp @@ -0,0 +1,124 @@ +// Voir LPDumpWidgetTreeCommandlet.h + +#include "LPDumpWidgetTreeCommandlet.h" + +#include "AssetRegistry/AssetRegistryModule.h" +#include "Blueprint/UserWidget.h" +#include "Blueprint/WidgetTree.h" +#include "Components/PanelSlot.h" +#include "Components/PanelWidget.h" +#include "Components/Widget.h" +#include "WidgetBlueprint.h" + +DEFINE_LOG_CATEGORY_STATIC(LogLPWidgetDump, Log, All); + +namespace +{ + /** Proprietes de Obj qui different du CDO de sa classe, une par ligne. */ + void DumpNonDefault(const UObject* Obj, const FString& Indent, const TCHAR* Label) + { + if (!Obj) + { + return; + } + const UObject* Defaults = Obj->GetClass()->GetDefaultObject(); + for (TFieldIterator It(Obj->GetClass()); It; ++It) + { + const FProperty* Prop = *It; + if (Prop->HasAnyPropertyFlags(CPF_Transient | CPF_DuplicateTransient) || Prop->IsA() + || Prop->IsA()) + { + continue; + } + if (Prop->Identical_InContainer(Obj, Defaults, 0, PPF_None)) + { + continue; + } + FString Value; + Prop->ExportText_InContainer(0, Value, Obj, Defaults, const_cast(Obj), PPF_None); + if (Value.Len() > 4000) + { + Value = Value.Left(4000) + TEXT("..."); + } + UE_LOG(LogLPWidgetDump, Warning, TEXT("%s %s.%s = %s"), *Indent, Label, *Prop->GetName(), *Value); + } + } + + void DumpWidget(const UWidget* Widget, int32 Depth) + { + if (!Widget) + { + return; + } + const FString Indent = FString::ChrN(Depth * 2, TEXT(' ')); + UE_LOG(LogLPWidgetDump, Warning, TEXT("%s[%s] %s"), *Indent, *Widget->GetClass()->GetName(), *Widget->GetName()); + DumpNonDefault(Widget->Slot, Indent, TEXT("slot")); + DumpNonDefault(Widget, Indent, TEXT("w")); + + if (const UPanelWidget* Panel = Cast(Widget)) + { + for (int32 i = 0; i < Panel->GetChildrenCount(); ++i) + { + DumpWidget(Panel->GetChildAt(i), Depth + 1); + } + } + } +} + +ULPDumpWidgetTreeCommandlet::ULPDumpWidgetTreeCommandlet() +{ + IsClient = false; + IsServer = false; + IsEditor = true; + LogToConsole = true; +} + +int32 ULPDumpWidgetTreeCommandlet::Main(const FString& Params) +{ + TArray Tokens, Switches; + TMap ParamsMap; + ParseCommandLine(*Params, Tokens, Switches, ParamsMap); + + const FString WantedBP = ParamsMap.FindRef(TEXT("BP")); + if (WantedBP.IsEmpty()) + { + UE_LOG(LogLPWidgetDump, Error, TEXT("Usage : -BP=")); + return 1; + } + + FAssetRegistryModule& AssetRegistryModule = + FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + AssetRegistryModule.Get().SearchAllAssets(true); + + TArray Assets; + AssetRegistryModule.Get().GetAssetsByClass(UWidgetBlueprint::StaticClass()->GetClassPathName(), Assets, true); + + // -BP=* : tous les Widget Blueprints de /Game, tries par chemin (diff avant/apres d'un correctif). + const bool bAll = (WantedBP == TEXT("*")); + Assets.Sort([](const FAssetData& A, const FAssetData& B) { return A.PackageName.LexicalLess(B.PackageName); }); + + int32 Dumped = 0; + for (const FAssetData& AssetData : Assets) + { + if (!AssetData.PackageName.ToString().StartsWith(TEXT("/Game/")) + || (!bAll && AssetData.AssetName.ToString() != WantedBP)) + { + continue; + } + const UWidgetBlueprint* Target = Cast(AssetData.GetAsset()); + if (!Target || !Target->WidgetTree) + { + continue; + } + ++Dumped; + UE_LOG(LogLPWidgetDump, Warning, TEXT("=== %s : arbre de widgets ==="), *Target->GetPathName()); + DumpWidget(Target->WidgetTree->RootWidget, 0); + } + + if (Dumped == 0) + { + UE_LOG(LogLPWidgetDump, Error, TEXT("Widget Blueprint '%s' introuvable sous /Game."), *WantedBP); + return 1; + } + return 0; +} diff --git a/Source/LifePlayEditorTools/LPDumpWidgetTreeCommandlet.h b/Source/LifePlayEditorTools/LPDumpWidgetTreeCommandlet.h new file mode 100644 index 0000000..93816b2 --- /dev/null +++ b/Source/LifePlayEditorTools/LPDumpWidgetTreeCommandlet.h @@ -0,0 +1,25 @@ +// Portage UE 5.5 - diagnostic de mise en page UMG (ERR-034, ERR-031). +// +// Liste l'arbre d'un Widget Blueprint : pour chaque widget, sa classe, son nom, puis les +// proprietes du widget et de son slot qui different des valeurs par defaut de leur classe. +// Lecture seule : rien n'est modifie ni sauvegarde. +// +// Usage : +// UnrealEditor-Cmd.exe -run=LPDumpWidgetTree -BP= +// UnrealEditor-Cmd.exe -run=LPDumpWidgetTree -BP=* (tous, tries par chemin) + +#pragma once + +#include "CoreMinimal.h" +#include "Commandlets/Commandlet.h" +#include "LPDumpWidgetTreeCommandlet.generated.h" + +UCLASS() +class ULPDumpWidgetTreeCommandlet : public UCommandlet +{ + GENERATED_BODY() + +public: + ULPDumpWidgetTreeCommandlet(); + virtual int32 Main(const FString& Params) override; +}; diff --git a/Source/LifePlayEditorTools/LPFixButtonPaddingCommandlet.cpp b/Source/LifePlayEditorTools/LPFixButtonPaddingCommandlet.cpp new file mode 100644 index 0000000..1c5605d --- /dev/null +++ b/Source/LifePlayEditorTools/LPFixButtonPaddingCommandlet.cpp @@ -0,0 +1,177 @@ +// Portage UE 5.5 - voir LPFixButtonPaddingCommandlet.h + +#include "LPFixButtonPaddingCommandlet.h" + +#include "AssetRegistry/AssetData.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "Blueprint/WidgetTree.h" +#include "Components/Button.h" +#include "Kismet2/BlueprintEditorUtils.h" +#include "Kismet2/KismetEditorUtilities.h" +#include "Misc/PackageName.h" +#include "UObject/Package.h" +#include "UObject/SavePackage.h" +#include "WidgetBlueprint.h" + +DEFINE_LOG_CATEGORY_STATIC(LogLPFixButton, Log, All); + +namespace +{ + // Valeurs de UE 4.27 : Engine/Source/Runtime/SlateCore/Private/Styling/CoreStyle.cpp + const FMargin UE4NormalPadding(2.f, 2.f, 2.f, 2.f); + const FMargin UE4PressedPadding(2.f, 3.f, 2.f, 1.f); + + FString Str(const FMargin& M) + { + return FString::Printf(TEXT("(%g,%g,%g,%g)"), M.Left, M.Top, M.Right, M.Bottom); + } +} + +ULPFixButtonPaddingCommandlet::ULPFixButtonPaddingCommandlet() +{ + IsClient = false; + IsServer = false; + IsEditor = true; + LogToConsole = true; +} + +int32 ULPFixButtonPaddingCommandlet::Main(const FString& Params) +{ + const bool bDryRun = Params.Contains(TEXT("DryRun"), ESearchCase::IgnoreCase); + + // Defaut UE5 lu sur le CDO plutot que recopie : c'est lui que recoivent les boutons 4.27. + PRAGMA_DISABLE_DEPRECATION_WARNINGS + const FButtonStyle& UE5Style = GetDefault()->WidgetStyle; + PRAGMA_ENABLE_DEPRECATION_WARNINGS + const FMargin UE5Normal = UE5Style.NormalPadding; + const FMargin UE5Pressed = UE5Style.PressedPadding; + + UE_LOG(LogLPFixButton, Warning, TEXT("=== Marges de UButton : defaut UE5 Normal=%s Pressed=%s -> UE4 Normal=%s Pressed=%s%s ==="), + *Str(UE5Normal), *Str(UE5Pressed), *Str(UE4NormalPadding), *Str(UE4PressedPadding), + bDryRun ? TEXT(" [DRY RUN]") : TEXT("")); + + FAssetRegistryModule& AssetRegistryModule = + FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + AssetRegistryModule.Get().SearchAllAssets(true); + + TArray Assets; + AssetRegistryModule.Get().GetAssetsByClass(UWidgetBlueprint::StaticClass()->GetClassPathName(), Assets, true); + + int32 ButtonsSeen = 0; + int32 ButtonsFixed = 0; + int32 ButtonsKept = 0; + int32 BlueprintsModified = 0; + int32 SaveFailures = 0; + + for (const FAssetData& AssetData : Assets) + { + if (!AssetData.PackageName.ToString().StartsWith(TEXT("/Game/"))) + { + continue; + } + UWidgetBlueprint* Blueprint = Cast(AssetData.GetAsset()); + if (!Blueprint || !Blueprint->WidgetTree) + { + continue; + } + + TArray Widgets; + Blueprint->WidgetTree->GetAllWidgets(Widgets); + + int32 FixedHere = 0; + for (UWidget* Widget : Widgets) + { + UButton* Button = Cast(Widget); + if (!Button) + { + continue; + } + ++ButtonsSeen; + + PRAGMA_DISABLE_DEPRECATION_WARNINGS + FButtonStyle Style = Button->WidgetStyle; + PRAGMA_ENABLE_DEPRECATION_WARNINGS + const bool bNormal = Style.NormalPadding == UE5Normal; + const bool bPressed = Style.PressedPadding == UE5Pressed; + if (!bNormal && !bPressed) + { + ++ButtonsKept; + UE_LOG(LogLPFixButton, Display, TEXT(" garde %s :: %s Normal=%s Pressed=%s"), + *Blueprint->GetName(), *Button->GetName(), *Str(Style.NormalPadding), *Str(Style.PressedPadding)); + continue; + } + + UE_LOG(LogLPFixButton, Warning, TEXT(" %s :: %s Normal %s%s | Pressed %s%s"), + *Blueprint->GetName(), *Button->GetName(), + *Str(Style.NormalPadding), bNormal ? TEXT(" -> UE4") : TEXT(" (garde)"), + *Str(Style.PressedPadding), bPressed ? TEXT(" -> UE4") : TEXT(" (garde)")); + ++ButtonsFixed; + ++FixedHere; + + if (bDryRun) + { + continue; + } + if (bNormal) + { + Style.NormalPadding = UE4NormalPadding; + } + if (bPressed) + { + Style.PressedPadding = UE4PressedPadding; + } + Button->Modify(); + Button->SetStyle(Style); + } + + if (FixedHere == 0) + { + continue; + } + if (bDryRun) + { + // Liste des paquets a sauvegarder (Snapshot) avant l'application reelle. + UE_LOG(LogLPFixButton, Warning, TEXT(" paquet %s (%d bouton(s))"), *Blueprint->GetOutermost()->GetName(), FixedHere); + continue; + } + + ++BlueprintsModified; + FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); + FKismetEditorUtilities::CompileBlueprint(Blueprint); + + UPackage* Package = Blueprint->GetOutermost(); + FString Filename; + if (!FPackageName::TryConvertLongPackageNameToFilename( + Package->GetName(), Filename, FPackageName::GetAssetPackageExtension())) + { + ++SaveFailures; + UE_LOG(LogLPFixButton, Error, TEXT(" chemin introuvable pour %s"), *Package->GetName()); + continue; + } + + ResetLoaders(Package); + + FSavePackageArgs SaveArgs; + SaveArgs.TopLevelFlags = RF_Public | RF_Standalone; + SaveArgs.SaveFlags = SAVE_None; + SaveArgs.bWarnOfLongFilename = true; + // GWarn et non GError : en commandlet, GError rend toute erreur de save fatale. + SaveArgs.Error = GWarn; + + if (UPackage::SavePackage(Package, nullptr, *Filename, SaveArgs)) + { + UE_LOG(LogLPFixButton, Warning, TEXT(" -> %s sauvegarde (%d bouton(s))."), *Package->GetName(), FixedHere); + } + else + { + ++SaveFailures; + UE_LOG(LogLPFixButton, Error, TEXT(" -> ECHEC de sauvegarde de %s"), *Package->GetName()); + } + } + + UE_LOG(LogLPFixButton, Warning, + TEXT("=== Termine : %d bouton(s) vu(s), %d a la marge UE5 %s, %d garde(s), %d Widget Blueprint(s) modifie(s), %d echec(s) de sauvegarde. ==="), + ButtonsSeen, ButtonsFixed, bDryRun ? TEXT("(a corriger)") : TEXT("corrige(s)"), ButtonsKept, BlueprintsModified, SaveFailures); + + return SaveFailures > 0 ? 1 : 0; +} diff --git a/Source/LifePlayEditorTools/LPFixButtonPaddingCommandlet.h b/Source/LifePlayEditorTools/LPFixButtonPaddingCommandlet.h new file mode 100644 index 0000000..30661a2 --- /dev/null +++ b/Source/LifePlayEditorTools/LPFixButtonPaddingCommandlet.h @@ -0,0 +1,30 @@ +// Portage UE 5.5 - correctif CX-B09 (ERR-034). +// +// Le style par defaut d'un UButton a change entre les moteurs : +// UE 4.27 : FCoreStyle "Button" -> NormalPadding (2,2,2,2), PressedPadding (2,3,2,1) +// UE 5.5 : FUMGCoreStyle "Button" -> NormalPadding (12,1.5,12,1.5), PressedPadding (12,1.5,12,1.5) +// Les assets 4.27 n'enregistrent la marge que si elle differe du defaut UE4 : charges en 5.5, +// leurs boutons prennent donc 20 px de large en plus (barre de temps de UI/TopRight decalee). +// +// La commandlet parcourt les Widget Blueprints de /Game et, pour chaque UButton dont une marge +// vaut exactement le defaut UE5, remet le defaut UE4. Une marge differente (choisie par +// l'auteur) n'est pas touchee. +// +// Usage : +// UnrealEditor-Cmd.exe -run=LPFixButtonPadding [-DryRun] + +#pragma once + +#include "CoreMinimal.h" +#include "Commandlets/Commandlet.h" +#include "LPFixButtonPaddingCommandlet.generated.h" + +UCLASS() +class ULPFixButtonPaddingCommandlet : public UCommandlet +{ + GENERATED_BODY() + +public: + ULPFixButtonPaddingCommandlet(); + virtual int32 Main(const FString& Params) override; +};