// Portage UE 5.5 - voir LPFixSkeletalMeshGetCommandlet.h #include "LPFixSkeletalMeshGetCommandlet.h" #include "AssetRegistry/AssetData.h" #include "AssetRegistry/AssetRegistryModule.h" #include "Components/SkinnedMeshComponent.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphPin.h" #include "EdGraphSchema_K2.h" #include "Engine/Blueprint.h" #include "K2Node_CallFunction.h" #include "K2Node_VariableGet.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/KismetEditorUtilities.h" #include "LPMeshCompat.h" #include "Misc/PackageName.h" #include "UObject/Package.h" #include "UObject/SavePackage.h" DEFINE_LOG_CATEGORY_STATIC(LogLPFixSkelMesh, Log, All); namespace { const FName SkeletalMeshPropertyName(TEXT("SkeletalMesh")); const FName NewFunctionName(TEXT("GetSkeletalMesh")); const FName ComponentPinName(TEXT("Component")); /** Extremites reliees a une broche, triees : "Noeud.Broche". */ TArray Links(const UEdGraphPin* Pin) { TArray Out; if (Pin) { for (const UEdGraphPin* L : Pin->LinkedTo) { Out.Add(FString::Printf(TEXT("%s.%s"), L && L->GetOwningNode() ? *L->GetOwningNode()->GetName() : TEXT("?"), L ? *L->PinName.ToString() : TEXT("?"))); } } Out.Sort(); return Out; } bool IsDeprecatedSkeletalMeshGet(const UK2Node_VariableGet* Node) { if (!Node || Node->VariableReference.GetMemberName() != SkeletalMeshPropertyName) { return false; } const UClass* Owner = Node->VariableReference.GetMemberParentClass(); return Owner && Owner->IsChildOf(USkinnedMeshComponent::StaticClass()); } bool HasExecPins(const UEdGraphNode* Node) { for (const UEdGraphPin* Pin : Node->Pins) { if (Pin && Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) { return true; } } return false; } } ULPFixSkeletalMeshGetCommandlet::ULPFixSkeletalMeshGetCommandlet() { IsClient = false; IsServer = false; IsEditor = true; LogToConsole = true; } int32 ULPFixSkeletalMeshGetCommandlet::Main(const FString& Params) { const bool bDryRun = Params.Contains(TEXT("DryRun"), ESearchCase::IgnoreCase); UFunction* NewFunction = ULPMeshCompat::StaticClass()->FindFunctionByName(NewFunctionName); if (!NewFunction) { UE_LOG(LogLPFixSkelMesh, Error, TEXT("ULPMeshCompat::GetSkeletalMesh introuvable.")); return 1; } UE_LOG(LogLPFixSkelMesh, Warning, TEXT("=== Get SkeletalMesh (deprecie) -> LPMeshCompat::GetSkeletalMesh%s ==="), bDryRun ? TEXT(" [DRY RUN]") : TEXT("")); FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); AssetRegistryModule.Get().SearchAllAssets(true); TArray BlueprintAssets; // Sous-classes incluses : couvre les UWidgetBlueprint (CharGen, Inventory, ...). AssetRegistryModule.Get().GetAssetsByClass(UBlueprint::StaticClass()->GetClassPathName(), BlueprintAssets, true); const UEdGraphSchema_K2* Schema = GetDefault(); int32 NodesFound = 0; int32 NodesFixed = 0; int32 Unhandled = 0; int32 BlueprintsModified = 0; int32 SaveFailures = 0; for (const FAssetData& AssetData : BlueprintAssets) { if (!AssetData.PackageName.ToString().StartsWith(TEXT("/Game/"))) { continue; } UBlueprint* Blueprint = Cast(AssetData.GetAsset()); if (!Blueprint) { continue; } TArray Graphs; Blueprint->GetAllGraphs(Graphs); int32 FixedHere = 0; int32 FailedHere = 0; for (UEdGraph* Graph : Graphs) { if (!Graph) { continue; } // Copie : on ajoute et on detruit des noeuds pendant le parcours. TArray Snapshot = Graph->Nodes; for (UEdGraphNode* GraphNode : Snapshot) { UK2Node_VariableGet* VarGet = Cast(GraphNode); if (!IsDeprecatedSkeletalMeshGet(VarGet)) { continue; } ++NodesFound; UEdGraphPin* OldSelf = VarGet->FindPin(UEdGraphSchema_K2::PN_Self, EGPD_Input); UEdGraphPin* OldValue = VarGet->GetValuePin(); const TArray SelfBefore = Links(OldSelf); const TArray ValueBefore = Links(OldValue); UE_LOG(LogLPFixSkelMesh, Warning, TEXT(" %s :: %s -> %s | cible [%s] | valeur [%s]"), *Blueprint->GetName(), *Graph->GetName(), *VarGet->GetName(), *FString::Join(SelfBefore, TEXT(", ")), *FString::Join(ValueBefore, TEXT(", "))); if (!OldValue || SelfBefore.Num() == 0 || HasExecPins(VarGet)) { ++Unhandled; ++FailedHere; UE_LOG(LogLPFixSkelMesh, Error, TEXT(" cas non pris en charge (%s) : noeud laisse en l'etat."), !OldValue ? TEXT("pas de broche de valeur") : SelfBefore.Num() == 0 ? TEXT("cible = self") : TEXT("noeud impur")); continue; } if (bDryRun) { continue; } UK2Node_CallFunction* NewNode = NewObject(Graph); NewNode->SetFromFunction(NewFunction); NewNode->NodePosX = VarGet->NodePosX; NewNode->NodePosY = VarGet->NodePosY; NewNode->CreateNewGuid(); NewNode->PostPlacedNewNode(); NewNode->AllocateDefaultPins(); Graph->Modify(); Graph->AddNode(NewNode, false, false); UEdGraphPin* NewComponent = NewNode->FindPin(ComponentPinName, EGPD_Input); UEdGraphPin* NewValue = NewNode->GetReturnValuePin(); bool bOk = NewComponent && NewValue && Schema->MovePinLinks(*OldSelf, *NewComponent).Response != CONNECT_RESPONSE_DISALLOW && Schema->MovePinLinks(*OldValue, *NewValue).Response != CONNECT_RESPONSE_DISALLOW; // Les noms des noeuds voisins ne changent pas : on doit retrouver exactement les memes extremites. if (bOk && (Links(NewComponent) != SelfBefore || Links(NewValue) != ValueBefore)) { bOk = false; UE_LOG(LogLPFixSkelMesh, Error, TEXT(" apres : cible [%s] | valeur [%s]"), *FString::Join(Links(NewComponent), TEXT(", ")), *FString::Join(Links(NewValue), TEXT(", "))); } if (!bOk) { // Le Blueprint ne sera pas sauvegarde : le paquet sur disque reste intact. ++Unhandled; ++FailedHere; UE_LOG(LogLPFixSkelMesh, Error, TEXT(" ecart de liaisons : Blueprint non sauvegarde.")); continue; } VarGet->DestroyNode(); ++NodesFixed; ++FixedHere; } } if (FailedHere > 0) { UE_LOG(LogLPFixSkelMesh, Error, TEXT(" -> %s : %d noeud(s) en ecart, Blueprint NON sauvegarde."), *Blueprint->GetName(), FailedHere); continue; } if (FixedHere == 0 || bDryRun) { continue; } ++BlueprintsModified; FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); FKismetEditorUtilities::CompileBlueprint(Blueprint); UPackage* Package = Blueprint->GetOutermost(); FString Filename; if (!FPackageName::TryConvertLongPackageNameToFilename( Package->GetName(), Filename, FPackageName::GetAssetPackageExtension())) { ++SaveFailures; UE_LOG(LogLPFixSkelMesh, 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(LogLPFixSkelMesh, Warning, TEXT(" -> %s sauvegarde (%d noeud(s))."), *Package->GetName(), FixedHere); } else { ++SaveFailures; UE_LOG(LogLPFixSkelMesh, Error, TEXT(" -> ECHEC de sauvegarde de %s"), *Package->GetName()); } } UE_LOG(LogLPFixSkelMesh, Warning, TEXT("=== Termine : %d noeud(s) trouve(s), %d remplace(s), %d Blueprint(s) modifie(s), %d non traite(s), %d echec(s) de sauvegarde. ==="), NodesFound, NodesFixed, BlueprintsModified, Unhandled, SaveFailures); return (SaveFailures > 0 || Unhandled > 0) ? 1 : 0; }