// 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(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(Component); if (!Primitive) { return TEXT("(pas un composant primitif)"); } const UEnum* CollisionEnum = StaticEnum(); 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(Collision)) : TEXT("?"), *Primitive->GetCollisionProfileName().ToString()); } } ULPFixZeroScaleActorsCommandlet::ULPFixZeroScaleActorsCommandlet() { IsClient = false; IsServer = false; IsEditor = true; LogToConsole = true; } int32 ULPFixZeroScaleActorsCommandlet::Main(const FString& Params) { TArray Tokens; TArray Switches; TMap 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(TEXT("AssetRegistry")); AssetRegistryModule.Get().SearchAllAssets(true); TArray 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 Components; Actor->GetComponents(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; }