Files
UnrealEngine/Engine/Source/Editor/Persona/Private/PersonaAssetFamily.cpp
2025-05-18 13:04:45 +08:00

649 lines
20 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "PersonaAssetFamily.h"
#include "Modules/ModuleManager.h"
#include "AssetRegistry/ARFilter.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "Animation/AnimBlueprint.h"
#include "PhysicsEngine/PhysicsAsset.h"
#include "Styling/AppStyle.h"
#include "AssetToolsModule.h"
#include "Preferences/PersonaOptions.h"
#include "UObject/UObjectIterator.h"
#define LOCTEXT_NAMESPACE "PersonaAssetFamily"
FPersonaAssetFamily::FPersonaAssetFamily(const UObject* InFromObject)
{
for (TObjectIterator<UAnimationEditorsAssetFamilyExtension> ExtensionIterator(RF_NoFlags); ExtensionIterator; ++ExtensionIterator)
{
if (ExtensionIterator->GetClass() == UAnimationEditorsAssetFamilyExtension::StaticClass())
{
continue;
}
FExtenderObjects& Extender = Extenders.AddDefaulted_GetRef();
Extender.Extension = *ExtensionIterator;
Extender.Asset = nullptr;
}
// Construct a directed graph to sort extenders according to position rules
{
TMap<FName, TArray<FName>> Graph;
TMap<FName, int32> InDegree;
TMap<FName, FExtenderObjects*> ExtenderMap;
for (FExtenderObjects& Extender : Extenders)
{
const FName AssetClassName = Extender.Extension->GetAssetClass()->GetFName();
ExtenderMap.Add(AssetClassName, &Extender);
InDegree.Add(AssetClassName, 0);
Graph.Add(AssetClassName, TArray<FName>());
}
for (FExtenderObjects& Extender : Extenders)
{
const FName AssetClassName = Extender.Extension->GetAssetClass()->GetFName();
FName BeforeName = NAME_None;
FName AfterName = NAME_None;
Extender.Extension->GetPosition(BeforeName, AfterName);
if (BeforeName != NAME_None && Graph.Contains(AssetClassName) && InDegree.Contains(BeforeName))
{
Graph[AssetClassName].Add(BeforeName);
++InDegree[BeforeName];
}
if (AfterName != NAME_None && Graph.Contains(AfterName) && InDegree.Contains(AssetClassName))
{
Graph[AfterName].Add(AssetClassName);
++InDegree[AssetClassName];
}
}
TQueue<FName> Queue;
for (const auto& [ClassName, Degree] : InDegree)
{
if (Degree == 0)
{
Queue.Enqueue(ClassName);
}
}
TArray<FExtenderObjects> SortedExtenders;
while (!Queue.IsEmpty())
{
FName Current = NAME_None;
Queue.Dequeue(Current);
SortedExtenders.Add(*ExtenderMap[Current]);
for (const auto& Neighbour : Graph[Current])
{
--InDegree[Neighbour];
if (InDegree[Neighbour] == 0)
{
Queue.Enqueue(Neighbour);
}
}
}
if (SortedExtenders.Num() != Extenders.Num())
{
UE_LOG(LogAnimation, Warning, TEXT("Unable to sort animation editor extenders"));
}
else
{
Extenders = SortedExtenders;
}
}
if (InFromObject)
{
for (FExtenderObjects& Extender : Extenders)
{
if (InFromObject->IsA(Extender.Extension->GetAssetClass()))
{
Extender.Asset = InFromObject;
Extender.Extension->FindCounterpartAssets(InFromObject, *this);
}
}
}
}
FPersonaAssetFamily::FPersonaAssetFamily(const UObject* InFromObject, const TSharedRef<FPersonaAssetFamily> InFromFamily)
: Extenders(InFromFamily->Extenders)
{
if (InFromObject)
{
for (FExtenderObjects& Extender : Extenders)
{
if (InFromObject->IsA(Extender.Extension->GetAssetClass()))
{
Extender.Asset = InFromObject;
Extender.Extension->FindCounterpartAssets(InFromObject, *this);
}
}
}
}
void FPersonaAssetFamily::Initialize()
{
GetMutableDefault<UPersonaOptions>()->RegisterOnUpdateSettings(UPersonaOptions::FOnUpdateSettingsMulticaster::FDelegate::CreateSP(this, &FPersonaAssetFamily::OnSettingsChange));
}
void FPersonaAssetFamily::GetAssetTypes(TArray<UClass*>& OutAssetTypes) const
{
OutAssetTypes.Reset();
for (const FExtenderObjects& Extender : Extenders)
{
OutAssetTypes.Add(Extender.Extension->GetAssetClass());
}
}
template<typename AssetType>
static void FindAssets(const USkeleton* InSkeleton, TArray<FAssetData>& OutAssetData, FName SkeletonTag)
{
if (!InSkeleton)
{
return;
}
InSkeleton->GetCompatibleAssets(AssetType::StaticClass(), *SkeletonTag.ToString(), OutAssetData);
}
FAssetData FPersonaAssetFamily::FindAssetOfType(UClass* InAssetClass) const
{
if (const FExtenderObjects* const Extender = GetExtensionForClass(InAssetClass))
{
if (Extender->Asset.IsValid())
{
return FAssetData(Extender->Asset.Get());
}
else
{
TArray<FAssetData> Assets;
Extender->Extension->FindAssetsOfType(Assets, *this);
if (Assets.Num() > 0)
{
return Assets[0];
}
}
}
return FAssetData();
}
void FPersonaAssetFamily::FindAssetsOfType(UClass* InAssetClass, TArray<FAssetData>& OutAssets) const
{
if (const FExtenderObjects* const Extender = GetExtensionForClass(InAssetClass))
{
Extender->Extension->FindAssetsOfType(OutAssets, *this);
}
}
FText FPersonaAssetFamily::GetAssetTypeDisplayName(UClass* InAssetClass) const
{
if (const FExtenderObjects* const Extender = GetExtensionForClass(InAssetClass))
{
return Extender->Extension->GetAssetTypeDisplayName();
}
return FText();
}
const FSlateBrush* FPersonaAssetFamily::GetAssetTypeDisplayIcon(UClass* InAssetClass) const
{
if (const FExtenderObjects* const Extender = GetExtensionForClass(InAssetClass))
{
return Extender->Extension->GetAssetTypeDisplayIcon();
}
return nullptr;
}
FSlateColor FPersonaAssetFamily::GetAssetTypeDisplayTint(UClass* InAssetClass) const
{
static const FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
if (InAssetClass)
{
if (const FExtenderObjects* const Extender = GetExtensionForClass(InAssetClass))
{
TWeakPtr<IAssetTypeActions> AssetTypeActions = AssetToolsModule.Get().GetAssetTypeActionsForClass(Extender->Extension->GetAssetClass());
if (AssetTypeActions.IsValid())
{
return AssetTypeActions.Pin()->GetTypeColor();
}
}
}
return FSlateColor::UseForeground();
}
bool FPersonaAssetFamily::IsAssetCompatible(const FAssetData& InAssetData) const
{
UClass* Class = InAssetData.GetClass();
if (const FExtenderObjects* const Extender = GetExtensionForClass(Class))
{
return Extender->Extension->IsAssetCompatible(InAssetData, *this);
}
return false;
}
UClass* FPersonaAssetFamily::GetAssetFamilyClass(UClass* InClass) const
{
if (const FExtenderObjects* const Extender = GetExtensionForClass(InClass))
{
return Extender->Extension->GetAssetClass();
}
return nullptr;
}
void FPersonaAssetFamily::RecordAssetOpened(const FAssetData& InAssetData)
{
if (IsAssetCompatible(InAssetData))
{
UClass* Class = InAssetData.GetClass();
if (Class)
{
if (FExtenderObjects* Extender = GetExtensionForClass(Class))
{
Extender->Asset = InAssetData.GetAsset();
}
}
OnAssetOpened.Broadcast(InAssetData.GetAsset());
}
}
bool FPersonaAssetFamily::IsAssetTypeInFamily(const TObjectPtr<UClass> InClass) const
{
return GetExtensionForClass(InClass) != nullptr;
}
TWeakObjectPtr<const UObject> FPersonaAssetFamily::GetAssetOfType(const TObjectPtr<UClass> InClass) const
{
if (const FExtenderObjects* const Extender = GetExtensionForClass(InClass))
{
return Extender->Asset;
}
return nullptr;
}
bool FPersonaAssetFamily::SetAssetOfType(const TObjectPtr<UClass> InClass, TWeakObjectPtr<const UObject> InObject)
{
if (FExtenderObjects* Extender = GetExtensionForClass(InClass))
{
Extender->Asset = InObject;
return true;
}
return false;
}
void FPersonaAssetFamily::AddReferencedObjects(FReferenceCollector& Collector)
{
for (FExtenderObjects& Extender : Extenders)
{
Collector.AddReferencedObject(Extender.Extension);
}
}
FString FPersonaAssetFamily::GetReferencerName() const
{
return TEXT("FPersonaAssetFamily");
}
void FPersonaAssetFamily::OnSettingsChange(const UPersonaOptions* InOptions, EPropertyChangeType::Type InChangeType)
{
OnAssetFamilyChanged.Broadcast();
}
FPersonaAssetFamily::FExtenderObjects* FPersonaAssetFamily::GetExtensionForClass(const TObjectPtr<const UClass> InClass)
{
if (InClass)
{
for (FExtenderObjects& Extender : Extenders)
{
if (InClass->IsChildOf(Extender.Extension->GetAssetClass()))
{
return &Extender;
}
}
}
return nullptr;
}
const FPersonaAssetFamily::FExtenderObjects* const FPersonaAssetFamily::GetExtensionForClass(const TObjectPtr<const UClass> InClass) const
{
if (InClass)
{
for (const FExtenderObjects& Extender : Extenders)
{
if (InClass->IsChildOf(Extender.Extension->GetAssetClass()))
{
return &Extender;
}
}
}
return nullptr;
}
// UAnimationEditorsAssetFamilyExtension_SkeletonAsset
TObjectPtr<UClass> UAnimationEditorsAssetFamilyExtension_SkeletonAsset::GetAssetClass() const
{
return USkeleton::StaticClass();
}
FText UAnimationEditorsAssetFamilyExtension_SkeletonAsset::GetAssetTypeDisplayName() const
{
return LOCTEXT("SkeletonAssetDisplayName", "Skeleton");
}
const FSlateBrush* UAnimationEditorsAssetFamilyExtension_SkeletonAsset::GetAssetTypeDisplayIcon() const
{
return FAppStyle::Get().GetBrush("Persona.AssetClass.Skeleton");
}
void UAnimationEditorsAssetFamilyExtension_SkeletonAsset::FindAssetsOfType(TArray<FAssetData>& OutAssets, const IAnimationEditorsAssetFamilyInterface& AssetFamilyInterface) const
{
if (TObjectPtr<const USkeleton> SkeletonAsset = AssetFamilyInterface.GetAssetOfType<USkeleton>())
{
SkeletonAsset->GetCompatibleSkeletonAssets(OutAssets);
}
}
bool UAnimationEditorsAssetFamilyExtension_SkeletonAsset::IsAssetCompatible(const FAssetData& InAssetData, const IAnimationEditorsAssetFamilyInterface& AssetFamilyInterface) const
{
if (TObjectPtr<const USkeleton> SkeletonAsset = AssetFamilyInterface.GetAssetOfType<USkeleton>())
{
return SkeletonAsset.Get()->IsCompatibleForEditor(InAssetData);
}
return false;
}
void UAnimationEditorsAssetFamilyExtension_SkeletonAsset::FindCounterpartAssets(const UObject* InAsset, IAnimationEditorsAssetFamilyInterface& AssetFamilyInterface)
{
const USkeleton* SkeletonAsset = CastChecked<const USkeleton>(InAsset);
AssetFamilyInterface.SetAssetOfType<USkeletalMesh>(SkeletonAsset->GetPreviewMesh());
};
void UAnimationEditorsAssetFamilyExtension_SkeletonAsset::GetPosition(FName& OutBeforeClass, FName& OutAfterClass) const
{
OutBeforeClass = NAME_None;
OutAfterClass = NAME_None;
}
// UAnimationEditorsAssetFamilyExtension_SkeletalMeshAsset
TObjectPtr<UClass> UAnimationEditorsAssetFamilyExtension_SkeletalMeshAsset::GetAssetClass() const
{
return USkeletalMesh::StaticClass();
}
FText UAnimationEditorsAssetFamilyExtension_SkeletalMeshAsset::GetAssetTypeDisplayName() const
{
return LOCTEXT("SkeletalMeshAssetDisplayName", "Skeletal Mesh");
}
const FSlateBrush* UAnimationEditorsAssetFamilyExtension_SkeletalMeshAsset::GetAssetTypeDisplayIcon() const
{
return FAppStyle::Get().GetBrush("Persona.AssetClass.SkeletalMesh");
}
void UAnimationEditorsAssetFamilyExtension_SkeletalMeshAsset::FindAssetsOfType(TArray<FAssetData>& OutAssets, const IAnimationEditorsAssetFamilyInterface& AssetFamilyInterface) const
{
if (TObjectPtr<const USkeleton> SkeletonAsset = AssetFamilyInterface.GetAssetOfType<USkeleton>())
{
FindAssets<USkeletalMesh>(SkeletonAsset.Get(), OutAssets, "Skeleton");
}
}
bool UAnimationEditorsAssetFamilyExtension_SkeletalMeshAsset::IsAssetCompatible(const FAssetData& InAssetData, const IAnimationEditorsAssetFamilyInterface& AssetFamilyInterface) const
{
FAssetDataTagMapSharedView::FFindTagResult Result = InAssetData.TagsAndValues.FindTag("Skeleton");
if (Result.IsSet())
{
if (TObjectPtr<const USkeleton> SkeletonAsset = AssetFamilyInterface.GetAssetOfType<USkeleton>())
{
return SkeletonAsset->IsCompatibleForEditor(Result.GetValue());
}
}
return false;
}
void UAnimationEditorsAssetFamilyExtension_SkeletalMeshAsset::FindCounterpartAssets(const UObject* InAsset, IAnimationEditorsAssetFamilyInterface& AssetFamilyInterface)
{
const USkeletalMesh* SkeletalMesh = CastChecked<const USkeletalMesh>(InAsset);
AssetFamilyInterface.SetAssetOfType<USkeleton>(SkeletalMesh->GetSkeleton());
};
void UAnimationEditorsAssetFamilyExtension_SkeletalMeshAsset::GetPosition(FName& OutBeforeClass, FName& OutAfterClass) const
{
OutBeforeClass = NAME_None;
OutAfterClass = USkeleton::StaticClass()->GetFName();
}
// UAnimationEditorsAssetFamilyExtension_AnimationAsset
TObjectPtr<UClass> UAnimationEditorsAssetFamilyExtension_AnimationAsset::GetAssetClass() const
{
return UAnimationAsset::StaticClass();
}
FText UAnimationEditorsAssetFamilyExtension_AnimationAsset::GetAssetTypeDisplayName() const
{
return LOCTEXT("AnimationAssetDisplayName", "Animation");
}
const FSlateBrush* UAnimationEditorsAssetFamilyExtension_AnimationAsset::GetAssetTypeDisplayIcon() const
{
return FAppStyle::Get().GetBrush("Persona.AssetClass.Animation");
}
void UAnimationEditorsAssetFamilyExtension_AnimationAsset::FindAssetsOfType(TArray<FAssetData>& OutAssets, const IAnimationEditorsAssetFamilyInterface& AssetFamilyInterface) const
{
if (TObjectPtr<const USkeleton> SkeletonAsset = AssetFamilyInterface.GetAssetOfType<USkeleton>())
{
FindAssets<UAnimationAsset>(SkeletonAsset, OutAssets, "Skeleton");
}
}
bool UAnimationEditorsAssetFamilyExtension_AnimationAsset::IsAssetCompatible(const FAssetData& InAssetData, const IAnimationEditorsAssetFamilyInterface& AssetFamilyInterface) const
{
FAssetDataTagMapSharedView::FFindTagResult Result = InAssetData.TagsAndValues.FindTag("Skeleton");
if (Result.IsSet())
{
if (TObjectPtr<const USkeleton> SkeletonAsset = AssetFamilyInterface.GetAssetOfType<USkeleton>())
{
return SkeletonAsset->IsCompatibleForEditor(Result.GetValue());
}
}
return false;
}
void UAnimationEditorsAssetFamilyExtension_AnimationAsset::FindCounterpartAssets(const UObject* InAsset, IAnimationEditorsAssetFamilyInterface& AssetFamilyInterface)
{
const UAnimationAsset* AnimationAsset = CastChecked<const UAnimationAsset>(InAsset);
AssetFamilyInterface.SetAssetOfType<USkeleton>(AnimationAsset->GetSkeleton());
AssetFamilyInterface.SetAssetOfType<USkeletalMesh>(AnimationAsset->GetPreviewMesh());
if (AssetFamilyInterface.IsAssetTypeInFamilyAndUnassigned<USkeletalMesh>())
{
AssetFamilyInterface.SetAssetOfType<USkeletalMesh>(AnimationAsset->GetSkeleton()->GetPreviewMesh());
}
if (AssetFamilyInterface.IsAssetTypeInFamilyAndUnassigned<USkeletalMesh>())
{
AssetFamilyInterface.SetAssetOfType<USkeletalMesh>(AnimationAsset->GetSkeleton()->FindCompatibleMesh());
}
};
void UAnimationEditorsAssetFamilyExtension_AnimationAsset::GetPosition(FName& OutBeforeClass, FName& OutAfterClass) const
{
OutBeforeClass = NAME_None;
OutAfterClass = USkeletalMesh::StaticClass()->GetFName();;
}
// UAnimationEditorsAssetFamilyExtension_AnimBlueprintAsset
TObjectPtr<UClass> UAnimationEditorsAssetFamilyExtension_AnimBlueprintAsset::GetAssetClass() const
{
return UAnimBlueprint::StaticClass();
}
FText UAnimationEditorsAssetFamilyExtension_AnimBlueprintAsset::GetAssetTypeDisplayName() const
{
return LOCTEXT("AnimBlueprintAssetDisplayName", "Animation Blueprint");
}
const FSlateBrush* UAnimationEditorsAssetFamilyExtension_AnimBlueprintAsset::GetAssetTypeDisplayIcon() const
{
return FAppStyle::Get().GetBrush("Persona.AssetClass.Blueprint");
}
void UAnimationEditorsAssetFamilyExtension_AnimBlueprintAsset::FindAssetsOfType(TArray<FAssetData>& OutAssets, const IAnimationEditorsAssetFamilyInterface& AssetFamilyInterface) const
{
if (TObjectPtr<const USkeleton> SkeletonAsset = AssetFamilyInterface.GetAssetOfType<USkeleton>())
{
FindAssets<UAnimBlueprint>(SkeletonAsset, OutAssets, "TargetSkeleton");
}
}
bool UAnimationEditorsAssetFamilyExtension_AnimBlueprintAsset::IsAssetCompatible(const FAssetData& InAssetData, const IAnimationEditorsAssetFamilyInterface& AssetFamilyInterface) const
{
FAssetDataTagMapSharedView::FFindTagResult Result = InAssetData.TagsAndValues.FindTag("TargetSkeleton");
if (Result.IsSet())
{
if (TObjectPtr<const USkeleton> SkeletonAsset = AssetFamilyInterface.GetAssetOfType<USkeleton>())
{
return SkeletonAsset->IsCompatibleForEditor(Result.GetValue());
}
}
return false;
}
void UAnimationEditorsAssetFamilyExtension_AnimBlueprintAsset::FindCounterpartAssets(const UObject* InAsset, IAnimationEditorsAssetFamilyInterface& AssetFamilyInterface)
{
const UAnimBlueprint* AnimBlueprint = CastChecked<const UAnimBlueprint>(InAsset);
AssetFamilyInterface.SetAssetOfType<USkeleton>(AnimBlueprint->TargetSkeleton);
AssetFamilyInterface.SetAssetOfType<USkeletalMesh>(AnimBlueprint->GetPreviewMesh());
check(AnimBlueprint->BlueprintType == BPTYPE_Interface || AnimBlueprint->bIsTemplate || AnimBlueprint->TargetSkeleton != nullptr);
if (AssetFamilyInterface.IsAssetTypeInFamilyAndUnassigned<USkeletalMesh>() && AnimBlueprint->TargetSkeleton)
{
AssetFamilyInterface.SetAssetOfType<USkeletalMesh>(AnimBlueprint->TargetSkeleton->GetPreviewMesh());
}
if (AssetFamilyInterface.IsAssetTypeInFamilyAndUnassigned<USkeletalMesh>() && AnimBlueprint->TargetSkeleton)
{
AssetFamilyInterface.SetAssetOfType<USkeletalMesh>(AnimBlueprint->TargetSkeleton->FindCompatibleMesh());
}
};
void UAnimationEditorsAssetFamilyExtension_AnimBlueprintAsset::GetPosition(FName& OutBeforeClass, FName& OutAfterClass) const
{
OutBeforeClass = NAME_None;
OutAfterClass = UAnimationAsset::StaticClass()->GetFName();;
}
// UAnimationEditorsAssetFamilyExtension_PhysicsAsset
TObjectPtr<UClass> UAnimationEditorsAssetFamilyExtension_PhysicsAsset::GetAssetClass() const
{
return UPhysicsAsset::StaticClass();
}
FText UAnimationEditorsAssetFamilyExtension_PhysicsAsset::GetAssetTypeDisplayName() const
{
return LOCTEXT("PhysicsAssetDisplayName", "Physics");
}
const FSlateBrush* UAnimationEditorsAssetFamilyExtension_PhysicsAsset::GetAssetTypeDisplayIcon() const
{
return FAppStyle::Get().GetBrush("Persona.AssetClass.Physics");
}
void UAnimationEditorsAssetFamilyExtension_PhysicsAsset::FindAssetsOfType(TArray<FAssetData>& OutAssets, const IAnimationEditorsAssetFamilyInterface& AssetFamilyInterface) const
{
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
FARFilter Filter;
Filter.bRecursiveClasses = true;
Filter.ClassPaths.Add(UPhysicsAsset::StaticClass()->GetClassPathName());
// If we have a mesh, look for a physics asset that has that mesh as its preview mesh
if (TObjectPtr<const USkeletalMesh> SkeletalMeshAsset = AssetFamilyInterface.GetAssetOfType<USkeletalMesh>())
{
Filter.TagsAndValues.Add(GET_MEMBER_NAME_CHECKED(UPhysicsAsset, PreviewSkeletalMesh), FSoftObjectPath(SkeletalMeshAsset).ToString());
}
AssetRegistryModule.Get().GetAssets(Filter, OutAssets);
// If we have a mesh and it has a physics asset, use it but only if its different from the one on the preview mesh
if (TObjectPtr<const USkeletalMesh> SkeletalMeshAsset = AssetFamilyInterface.GetAssetOfType<USkeletalMesh>())
{
if (UPhysicsAsset* MeshPhysicsAsset = SkeletalMeshAsset->GetPhysicsAsset())
{
OutAssets.AddUnique(FAssetData(MeshPhysicsAsset));
}
}
}
bool UAnimationEditorsAssetFamilyExtension_PhysicsAsset::IsAssetCompatible(const FAssetData& InAssetData, const IAnimationEditorsAssetFamilyInterface& AssetFamilyInterface) const
{
// If our mesh is valid and this is the physics asset used on it, we are compatible
TObjectPtr<const USkeletalMesh> SkeletalMeshAsset = AssetFamilyInterface.GetAssetOfType<USkeletalMesh>();
if (SkeletalMeshAsset && InAssetData.GetSoftObjectPath() == FSoftObjectPath(SkeletalMeshAsset->GetPhysicsAsset()))
{
return true;
}
// Otherwise check if our mesh is the preview mesh of the physics asset
FAssetDataTagMapSharedView::FFindTagResult Result = InAssetData.TagsAndValues.FindTag(GET_MEMBER_NAME_CHECKED(UPhysicsAsset, PreviewSkeletalMesh));
if (Result.IsSet() && SkeletalMeshAsset)
{
return Result.GetValue() == FSoftObjectPath(SkeletalMeshAsset).ToString();
}
return false;
}
void UAnimationEditorsAssetFamilyExtension_PhysicsAsset::FindCounterpartAssets(const UObject* InAsset, IAnimationEditorsAssetFamilyInterface& AssetFamilyInterface)
{
const UPhysicsAsset* PhysicsAsset = CastChecked<const UPhysicsAsset>(InAsset);
TObjectPtr<USkeletalMesh> SkeletalMesh = PhysicsAsset->PreviewSkeletalMesh.LoadSynchronous();
if (SkeletalMesh)
{
AssetFamilyInterface.SetAssetOfType<USkeletalMesh>(SkeletalMesh);
AssetFamilyInterface.SetAssetOfType<USkeleton>(SkeletalMesh->GetSkeleton());
}
};
void UAnimationEditorsAssetFamilyExtension_PhysicsAsset::GetPosition(FName& OutBeforeClass, FName& OutAfterClass) const
{
OutBeforeClass = NAME_None;
OutAfterClass = UAnimBlueprint::StaticClass()->GetFName();;
}
#undef LOCTEXT_NAMESPACE