Instantane de reference de la reprise de migration (2026-09-29). Contient GI.uasset tel que trouve (copie UE4 identique a 4.27, MD5 F4550DA5DC05FC005400CDCA2FC123C9). Ajouts de ce commit : .gitattributes (aucune conversion de fin de ligne), regles .gitignore CHG-001, Tools/ (Snapshot, Restore, AssetHeaders), empreintes L0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
223 lines
7 KiB
C++
223 lines
7 KiB
C++
// Portage UE 5.5 - voir LPFixZeroScaleActorsCommandlet.h
|
|
|
|
#include "LPFixZeroScaleActorsCommandlet.h"
|
|
|
|
#include "AssetRegistry/AssetRegistryModule.h"
|
|
#include "Components/SceneComponent.h"
|
|
#include "Components/PrimitiveComponent.h"
|
|
#include "Components/StaticMeshComponent.h"
|
|
#include "Engine/StaticMesh.h"
|
|
#include "Engine/World.h"
|
|
#include "GameFramework/Actor.h"
|
|
#include "Misc/PackageName.h"
|
|
#include "UObject/Package.h"
|
|
#include "UObject/SavePackage.h"
|
|
#include "UObject/UObjectGlobals.h"
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogLPFixZeroScale, Log, All);
|
|
|
|
namespace
|
|
{
|
|
/** Seuil du moteur : FMath::IsNearlyZero sur chaque composante avant creation du body. */
|
|
constexpr double ZeroScaleTolerance = UE_KINDA_SMALL_NUMBER;
|
|
|
|
/** Decrit le maillage porte par un composant, pour pouvoir juger de la valeur a remettre. */
|
|
FString DescribeMesh(const USceneComponent* Component)
|
|
{
|
|
const UStaticMeshComponent* MeshComponent = Cast<UStaticMeshComponent>(Component);
|
|
if (!MeshComponent || !MeshComponent->GetStaticMesh())
|
|
{
|
|
return TEXT("(pas de maillage statique)");
|
|
}
|
|
|
|
const UStaticMesh* Mesh = MeshComponent->GetStaticMesh();
|
|
const FBoxSphereBounds Bounds = Mesh->GetBounds();
|
|
return FString::Printf(TEXT("%s demi-dimensions locales (X=%.2f Y=%.2f Z=%.2f)"),
|
|
*Mesh->GetPathName(),
|
|
Bounds.BoxExtent.X, Bounds.BoxExtent.Y, Bounds.BoxExtent.Z);
|
|
}
|
|
|
|
/**
|
|
* Visibilite et collision : ce sont elles qui dictent la valeur de remplacement.
|
|
* Un bloqueur invisible tolere une epaisseur quelconque ; un mur visible non.
|
|
*/
|
|
FString DescribeRendering(const USceneComponent* Component)
|
|
{
|
|
const UPrimitiveComponent* Primitive = Cast<UPrimitiveComponent>(Component);
|
|
if (!Primitive)
|
|
{
|
|
return TEXT("(pas un composant primitif)");
|
|
}
|
|
|
|
const UEnum* CollisionEnum = StaticEnum<ECollisionEnabled::Type>();
|
|
const ECollisionEnabled::Type Collision = Primitive->GetCollisionEnabled();
|
|
|
|
return FString::Printf(TEXT("visible=%s cacheEnJeu=%s collision=%s profil=%s"),
|
|
Primitive->IsVisible() ? TEXT("oui") : TEXT("NON"),
|
|
Primitive->bHiddenInGame ? TEXT("oui") : TEXT("non"),
|
|
CollisionEnum ? *CollisionEnum->GetNameStringByValue(static_cast<int64>(Collision)) : TEXT("?"),
|
|
*Primitive->GetCollisionProfileName().ToString());
|
|
}
|
|
}
|
|
|
|
ULPFixZeroScaleActorsCommandlet::ULPFixZeroScaleActorsCommandlet()
|
|
{
|
|
IsClient = false;
|
|
IsServer = false;
|
|
IsEditor = true;
|
|
LogToConsole = true;
|
|
}
|
|
|
|
int32 ULPFixZeroScaleActorsCommandlet::Main(const FString& Params)
|
|
{
|
|
TArray<FString> Tokens;
|
|
TArray<FString> Switches;
|
|
TMap<FString, FString> ParamsMap;
|
|
ParseCommandLine(*Params, Tokens, Switches, ParamsMap);
|
|
|
|
const bool bFix = Switches.Contains(TEXT("Fix"));
|
|
double Replacement = 1.0;
|
|
if (const FString* ScaleText = ParamsMap.Find(TEXT("Scale")))
|
|
{
|
|
Replacement = FCString::Atod(**ScaleText);
|
|
}
|
|
// Garde-fou volontaire : PowerShell coupe « -Scale=0.1 » en deux arguments si on ne le
|
|
// guillemette pas, et la commandlet recevrait alors 0 sans rien dire. Voir l'en-tete.
|
|
if (bFix && FMath::IsNearlyZero(Replacement, ZeroScaleTolerance))
|
|
{
|
|
UE_LOG(LogLPFixZeroScale, Error,
|
|
TEXT("-Scale lu a %f : valeur nulle ou argument coupe par le shell. Guillemettez \"-Scale=0.1\"."),
|
|
Replacement);
|
|
return 1;
|
|
}
|
|
|
|
UE_LOG(LogLPFixZeroScale, Warning, TEXT("=== Composantes d'echelle nulles (R-06)%s ==="),
|
|
bFix ? *FString::Printf(TEXT(" [CORRECTION -> %.4f]"), Replacement) : TEXT(" [RAPPORT SEUL]"));
|
|
|
|
FAssetRegistryModule& AssetRegistryModule =
|
|
FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
|
|
AssetRegistryModule.Get().SearchAllAssets(true);
|
|
|
|
TArray<FAssetData> MapAssets;
|
|
AssetRegistryModule.Get().GetAssetsByClass(UWorld::StaticClass()->GetClassPathName(), MapAssets, true);
|
|
|
|
int32 ComponentsFound = 0;
|
|
int32 ComponentsFixed = 0;
|
|
int32 MapsModified = 0;
|
|
int32 SaveFailures = 0;
|
|
|
|
for (const FAssetData& AssetData : MapAssets)
|
|
{
|
|
const FString PackageName = AssetData.PackageName.ToString();
|
|
if (!PackageName.StartsWith(TEXT("/Game/")))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
UPackage* Package = LoadPackage(nullptr, *PackageName, LOAD_None);
|
|
UWorld* World = Package ? UWorld::FindWorldInPackage(Package) : nullptr;
|
|
if (!World || !World->PersistentLevel)
|
|
{
|
|
UE_LOG(LogLPFixZeroScale, Warning, TEXT(" %s : monde illisible, ignore."), *PackageName);
|
|
continue;
|
|
}
|
|
|
|
int32 FixedHere = 0;
|
|
|
|
for (AActor* Actor : World->PersistentLevel->Actors)
|
|
{
|
|
if (!Actor)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
TArray<USceneComponent*> Components;
|
|
Actor->GetComponents<USceneComponent>(Components);
|
|
|
|
for (USceneComponent* Component : Components)
|
|
{
|
|
if (!Component)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FVector Scale = Component->GetRelativeScale3D();
|
|
const bool bZeroX = FMath::IsNearlyZero(Scale.X, ZeroScaleTolerance);
|
|
const bool bZeroY = FMath::IsNearlyZero(Scale.Y, ZeroScaleTolerance);
|
|
const bool bZeroZ = FMath::IsNearlyZero(Scale.Z, ZeroScaleTolerance);
|
|
if (!bZeroX && !bZeroY && !bZeroZ)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
++ComponentsFound;
|
|
UE_LOG(LogLPFixZeroScale, Warning, TEXT(" %s : %s.%s"),
|
|
*PackageName, *Actor->GetName(), *Component->GetName());
|
|
UE_LOG(LogLPFixZeroScale, Warning, TEXT(" echelle : X=%.4f Y=%.4f Z=%.4f"),
|
|
Scale.X, Scale.Y, Scale.Z);
|
|
UE_LOG(LogLPFixZeroScale, Warning, TEXT(" maillage : %s"), *DescribeMesh(Component));
|
|
UE_LOG(LogLPFixZeroScale, Warning, TEXT(" rendu : %s"), *DescribeRendering(Component));
|
|
|
|
if (!bFix)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (bZeroX) { Scale.X = Replacement; }
|
|
if (bZeroY) { Scale.Y = Replacement; }
|
|
if (bZeroZ) { Scale.Z = Replacement; }
|
|
|
|
Actor->Modify();
|
|
Component->Modify();
|
|
Component->SetRelativeScale3D(Scale);
|
|
Component->MarkRenderStateDirty();
|
|
// Recalcule le corps physique avec la nouvelle echelle.
|
|
Component->RecreatePhysicsState();
|
|
|
|
UE_LOG(LogLPFixZeroScale, Warning, TEXT(" -> X=%.4f Y=%.4f Z=%.4f"),
|
|
Scale.X, Scale.Y, Scale.Z);
|
|
|
|
++ComponentsFixed;
|
|
++FixedHere;
|
|
}
|
|
}
|
|
|
|
if (FixedHere > 0)
|
|
{
|
|
++MapsModified;
|
|
|
|
FString Filename;
|
|
if (!FPackageName::TryConvertLongPackageNameToFilename(
|
|
PackageName, Filename, FPackageName::GetMapPackageExtension()))
|
|
{
|
|
++SaveFailures;
|
|
UE_LOG(LogLPFixZeroScale, Error, TEXT(" chemin introuvable pour %s"), *PackageName);
|
|
continue;
|
|
}
|
|
|
|
Package->MarkPackageDirty();
|
|
|
|
FSavePackageArgs SaveArgs;
|
|
SaveArgs.TopLevelFlags = RF_Public | RF_Standalone;
|
|
SaveArgs.SaveFlags = SAVE_None;
|
|
SaveArgs.bWarnOfLongFilename = true;
|
|
SaveArgs.Error = GWarn;
|
|
|
|
if (UPackage::SavePackage(Package, World, *Filename, SaveArgs))
|
|
{
|
|
UE_LOG(LogLPFixZeroScale, Warning, TEXT(" -> %s sauvegarde (%d composant(s))."), *PackageName, FixedHere);
|
|
}
|
|
else
|
|
{
|
|
++SaveFailures;
|
|
UE_LOG(LogLPFixZeroScale, Error, TEXT(" -> ECHEC de sauvegarde de %s"), *PackageName);
|
|
}
|
|
}
|
|
}
|
|
|
|
UE_LOG(LogLPFixZeroScale, Warning,
|
|
TEXT("=== Termine : %d composant(s) a echelle nulle, %d corrige(s) dans %d carte(s), %d echec(s) de sauvegarde. ==="),
|
|
ComponentsFound, ComponentsFixed, MapsModified, SaveFailures);
|
|
|
|
return SaveFailures > 0 ? 1 : 0;
|
|
}
|