L7a : LPMeshCompat::GetSkeletalMesh (semantique exacte du getter moteur) et commandlet LPFixSkeletalMeshGet ; simulation 56 noeuds
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
111915420f
commit
665e1633e9
4 changed files with 333 additions and 0 deletions
19
Source/LifePlay/LPMeshCompat.cpp
Normal file
19
Source/LifePlay/LPMeshCompat.cpp
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// Portage UE 5.5 - voir LPMeshCompat.h
|
||||
|
||||
#include "LPMeshCompat.h"
|
||||
|
||||
#include "Components/SkinnedMeshComponent.h"
|
||||
#include "Engine/SkeletalMesh.h"
|
||||
#include "LifePlay.h"
|
||||
|
||||
USkeletalMesh* ULPMeshCompat::GetSkeletalMesh(const USkinnedMeshComponent* Component)
|
||||
{
|
||||
if (!Component)
|
||||
{
|
||||
// Le noeud d'origine emettait "Accessed None trying to read property SkeletalMesh" ;
|
||||
// la trace reste disponible en Verbose pour les mesures A/B.
|
||||
UE_LOG(LogLifePlay, Verbose, TEXT("GetSkeletalMesh : composant None"));
|
||||
return nullptr;
|
||||
}
|
||||
return Cast<USkeletalMesh>(Component->GetSkinnedAsset());
|
||||
}
|
||||
32
Source/LifePlay/LPMeshCompat.h
Normal file
32
Source/LifePlay/LPMeshCompat.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// Portage UE 5.5 - lot L7a (R-03, ERR-012) : remplacement des noeuds "Get SkeletalMesh" deprecies.
|
||||
//
|
||||
// En UE 5.1+, la propriete USkinnedMeshComponent::SkeletalMesh est depreciee (remplacee par
|
||||
// SkinnedAsset). Les Blueprints 4.27 la lisent encore : chaque lecture passe par son getter
|
||||
// GetSkeletalMesh_DEPRECATED(), qui renvoie Cast<USkeletalMesh>(GetSkinnedAsset()).
|
||||
//
|
||||
// Le premier essai (CX-B05) rebranchait ces lectures sur USkeletalMeshComponent::GetSkeletalMeshAsset().
|
||||
// Cette fonction n'existe que sur USkeletalMeshComponent : sur les composants proxy de LifePlay,
|
||||
// qui sont des USkinnedMeshComponent, le cast echouait et la lecture renvoyait None (ERR-002 aggrave).
|
||||
//
|
||||
// ULPMeshCompat::GetSkeletalMesh reproduit exactement le getter du moteur, pour tout
|
||||
// USkinnedMeshComponent, sans passer par une API depreciee.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "LPMeshCompat.generated.h"
|
||||
|
||||
class USkeletalMesh;
|
||||
class USkinnedMeshComponent;
|
||||
|
||||
UCLASS()
|
||||
class LIFEPLAY_API ULPMeshCompat : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Maillage squelettique du composant : Cast<USkeletalMesh>(GetSkinnedAsset()), None si Component est None. */
|
||||
UFUNCTION(BlueprintPure, Category = "Components|SkinnedMesh", meta = (DisplayName = "Get Skeletal Mesh (UE4)"))
|
||||
static USkeletalMesh* GetSkeletalMesh(const USkinnedMeshComponent* Component);
|
||||
};
|
||||
253
Source/LifePlayEditorTools/LPFixSkeletalMeshGetCommandlet.cpp
Normal file
253
Source/LifePlayEditorTools/LPFixSkeletalMeshGetCommandlet.cpp
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
// 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<FString> Links(const UEdGraphPin* Pin)
|
||||
{
|
||||
TArray<FString> 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<FAssetRegistryModule>(TEXT("AssetRegistry"));
|
||||
AssetRegistryModule.Get().SearchAllAssets(true);
|
||||
|
||||
TArray<FAssetData> BlueprintAssets;
|
||||
// Sous-classes incluses : couvre les UWidgetBlueprint (CharGen, Inventory, ...).
|
||||
AssetRegistryModule.Get().GetAssetsByClass(UBlueprint::StaticClass()->GetClassPathName(), BlueprintAssets, true);
|
||||
|
||||
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
|
||||
|
||||
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<UBlueprint>(AssetData.GetAsset());
|
||||
if (!Blueprint)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
TArray<UEdGraph*> 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<UEdGraphNode*> Snapshot = Graph->Nodes;
|
||||
for (UEdGraphNode* GraphNode : Snapshot)
|
||||
{
|
||||
UK2Node_VariableGet* VarGet = Cast<UK2Node_VariableGet>(GraphNode);
|
||||
if (!IsDeprecatedSkeletalMeshGet(VarGet))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
++NodesFound;
|
||||
|
||||
UEdGraphPin* OldSelf = VarGet->FindPin(UEdGraphSchema_K2::PN_Self, EGPD_Input);
|
||||
UEdGraphPin* OldValue = VarGet->GetValuePin();
|
||||
const TArray<FString> SelfBefore = Links(OldSelf);
|
||||
const TArray<FString> 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<UK2Node_CallFunction>(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;
|
||||
}
|
||||
29
Source/LifePlayEditorTools/LPFixSkeletalMeshGetCommandlet.h
Normal file
29
Source/LifePlayEditorTools/LPFixSkeletalMeshGetCommandlet.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// Portage UE 5.5 - lot L7a (R-03, ERR-012). Voir Source/LifePlay/LPMeshCompat.h.
|
||||
//
|
||||
// Remplace chaque noeud K2Node_VariableGet de la propriete depreciee
|
||||
// USkinnedMeshComponent::SkeletalMesh par un appel a ULPMeshCompat::GetSkeletalMesh(Component),
|
||||
// au meme emplacement : les liaisons de la cible (self) passent sur Component, celles de la
|
||||
// valeur sur ReturnValue.
|
||||
//
|
||||
// Controle integre : les liaisons relevees avant sont comparees a celles du nouveau noeud.
|
||||
// Tout ecart, cible non reliee (lecture sur self) ou noeud impur (broches d'execution) : le
|
||||
// noeud est laisse en l'etat, compte comme non traite, et son Blueprint n'est pas sauvegarde.
|
||||
//
|
||||
// Usage :
|
||||
// UnrealEditor-Cmd.exe <projet> -run=LPFixSkeletalMeshGet [-DryRun]
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Commandlets/Commandlet.h"
|
||||
#include "LPFixSkeletalMeshGetCommandlet.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class ULPFixSkeletalMeshGetCommandlet : public UCommandlet
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
ULPFixSkeletalMeshGetCommandlet();
|
||||
virtual int32 Main(const FString& Params) override;
|
||||
};
|
||||
Loading…
Reference in a new issue