Files
UnrealEngine/Engine/Plugins/Animation/ControlRig/Source/ControlRigEditor/Private/ControlRigBlueprintActions.cpp
2025-05-18 13:04:45 +08:00

513 lines
18 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ControlRigBlueprintActions.h"
#include "ControlRigBlueprintFactory.h"
#include "ControlRigBlueprint.h"
#include "ControlRig.h"
#include "Editor/RigVMEditorStyle.h"
#include "IControlRigEditorModule.h"
#include "Styling/SlateIconFinder.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Images/SImage.h"
#include "Styling/AppStyle.h"
#include "Subsystems/AssetEditorSubsystem.h"
#include "AssetRegistry/AssetData.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "ToolMenus.h"
#include "ContentBrowserMenuContexts.h"
#include "Engine/SkeletalMesh.h"
#include "Animation/SkeletalMeshActor.h"
#include "Components/SkeletalMeshComponent.h"
#include "LevelSequenceActor.h"
#include "LevelSequencePlayer.h"
#include "EditorDirectories.h"
#include "ILevelSequenceEditorToolkit.h"
#include "ControlRigObjectBinding.h"
#include "EditMode/ControlRigEditMode.h"
#include "Editor.h"
#include "EditorModeManager.h"
#include "MovieSceneToolsProjectSettings.h"
#include "SBlueprintDiff.h"
#include "Misc/MessageDialog.h"
#include "Misc/PackageName.h"
#include "LevelSequenceEditorBlueprintLibrary.h"
#include "Sequencer/ControlRigParameterTrackEditor.h"
#include "Rigs/RigHierarchyController.h"
#include "ModularRig.h"
#define LOCTEXT_NAMESPACE "ControlRigBlueprintActions"
FDelegateHandle FControlRigBlueprintActions::OnSpawnedSkeletalMeshActorChangedHandle;
UFactory* FControlRigBlueprintActions::GetFactoryForBlueprintType(UBlueprint* InBlueprint) const
{
UControlRigBlueprintFactory* ControlRigBlueprintFactory = NewObject<UControlRigBlueprintFactory>();
UControlRigBlueprint* ControlRigBlueprint = CastChecked<UControlRigBlueprint>(InBlueprint);
ControlRigBlueprintFactory->ParentClass = TSubclassOf<UControlRig>(*InBlueprint->GeneratedClass);
return ControlRigBlueprintFactory;
}
void FControlRigBlueprintActions::OpenAssetEditor( const TArray<UObject*>& InObjects, TSharedPtr<IToolkitHost> EditWithinLevelEditor )
{
EToolkitMode::Type Mode = EditWithinLevelEditor.IsValid() ? EToolkitMode::WorldCentric : EToolkitMode::Standalone;
for (auto ObjIt = InObjects.CreateConstIterator(); ObjIt; ++ObjIt)
{
if (UControlRigBlueprint* ControlRigBlueprint = Cast<UControlRigBlueprint>(*ObjIt))
{
const bool bBringToFrontIfOpen = true;
UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
if (IAssetEditorInstance* EditorInstance = AssetEditorSubsystem->FindEditorForAsset(ControlRigBlueprint, bBringToFrontIfOpen))
{
EditorInstance->FocusWindow(ControlRigBlueprint);
}
else
{
// If any other editors are opened (for example, a BlueprintDiff window), close them
AssetEditorSubsystem->CloseAllEditorsForAsset(ControlRigBlueprint);
IControlRigEditorModule& ControlRigEditorModule = FModuleManager::LoadModuleChecked<IControlRigEditorModule>("ControlRigEditor");
ControlRigEditorModule.CreateControlRigEditor(Mode, EditWithinLevelEditor, ControlRigBlueprint);
}
}
}
}
TSharedPtr<SWidget> FControlRigBlueprintActions::GetThumbnailOverlay(const FAssetData& AssetData) const
{
const FSlateBrush* Icon = FSlateIconFinder::FindIconBrushForClass(UControlRigBlueprint::StaticClass());
return SNew(SBorder)
.BorderImage(FAppStyle::GetNoBrush())
.Visibility(EVisibility::HitTestInvisible)
.Padding(FMargin(0.0f, 0.0f, 0.0f, 3.0f))
.HAlign(HAlign_Right)
.VAlign(VAlign_Bottom)
[
SNew(SImage)
.Image(Icon)
];
}
void FControlRigBlueprintActions::PerformAssetDiff(UObject* OldAsset, UObject* NewAsset, const FRevisionInfo& OldRevision, const FRevisionInfo& NewRevision) const
{
UBlueprint* OldBlueprint = Cast<UBlueprint>(OldAsset);
UBlueprint* NewBlueprint = Cast<UBlueprint>(NewAsset);
static const FText DiffWindowMessage = LOCTEXT("ControlRigDiffWindow", "Opening a diff window will close the control rig editor. {0}.\nAre you sure?");
UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
if (OldAsset)
{
for (IAssetEditorInstance* Editor : AssetEditorSubsystem->FindEditorsForAsset(OldAsset))
{
const EAppReturnType::Type Answer = FMessageDialog::Open( EAppMsgType::YesNo,
FText::Format(DiffWindowMessage, FText::FromString(OldBlueprint->GetName())));
if(Answer == EAppReturnType::No)
{
return;
}
}
}
if (NewAsset)
{
for (IAssetEditorInstance* Editor : AssetEditorSubsystem->FindEditorsForAsset(NewAsset))
{
const EAppReturnType::Type Answer = FMessageDialog::Open( EAppMsgType::YesNo,
FText::Format(DiffWindowMessage, FText::FromString(NewBlueprint->GetName())));
if(Answer == EAppReturnType::No)
{
return;
}
}
}
if (OldAsset)
{
AssetEditorSubsystem->CloseAllEditorsForAsset(OldAsset);
}
if (NewAsset)
{
AssetEditorSubsystem->CloseAllEditorsForAsset(NewAsset);
}
#if WITH_RIGVMLEGACYEDITOR
SBlueprintDiff::CreateDiffWindow(OldBlueprint, NewBlueprint, OldRevision, NewRevision, GetSupportedClass());
#endif
}
void FControlRigBlueprintActions::ExtendSketalMeshToolMenu()
{
TArray<UToolMenu*> MenusToExtend;
MenusToExtend.Add(UToolMenus::Get()->ExtendMenu("ContentBrowser.AssetContextMenu.SkeletalMesh.CreateSkeletalMeshSubmenu"));
MenusToExtend.Add(UToolMenus::Get()->ExtendMenu("ContentBrowser.AssetContextMenu.Skeleton.CreateSkeletalMeshSubmenu"));
for(UToolMenu* Menu : MenusToExtend)
{
if (Menu == nullptr)
{
continue;
}
FToolMenuSection& Section = Menu->AddSection("ControlRig", LOCTEXT("ControlRigSectionName", "Control Rig"));
Section.AddDynamicEntry("CreateControlRig", FNewToolMenuSectionDelegate::CreateLambda([](FToolMenuSection& InSection)
{
UContentBrowserAssetContextMenuContext* Context = InSection.FindContext<UContentBrowserAssetContextMenuContext>();
if (Context)
{
if (Context->SelectedAssets.Num() > 0)
{
static constexpr bool bModularRig = true;
InSection.AddMenuEntry(
"CreateControlRig",
LOCTEXT("CreateControlRig", "Control Rig"),
LOCTEXT("CreateControlRig_ToolTip", "Creates a control rig and preconfigures it for this asset"),
FSlateIcon(FRigVMEditorStyle::Get().GetStyleSetName(), "RigVM", "RigVM.Unit"),
FExecuteAction::CreateLambda([InSelectedAssets = Context->SelectedAssets]()
{
TArray<UObject*> SelectedObjects;
SelectedObjects.Reserve(InSelectedAssets.Num());
for (const FAssetData& Asset : InSelectedAssets)
{
if (UObject* LoadedAsset = Asset.GetAsset())
{
SelectedObjects.Add(LoadedAsset);
}
}
for (UObject* SelectedObject : SelectedObjects)
{
CreateControlRigFromSkeletalMeshOrSkeleton(SelectedObject, !bModularRig);
}
})
);
InSection.AddMenuEntry(
"CreateModularRig",
LOCTEXT("CreateModularRig", "Modular Rig"),
LOCTEXT("CreateModularRig_ToolTip", "Creates a modular rig and preconfigures it for this asset"),
FSlateIcon(FRigVMEditorStyle::Get().GetStyleSetName(), "RigVM", "RigVM.Unit"),
FExecuteAction::CreateLambda([InSelectedAssets = Context->SelectedAssets]()
{
TArray<UObject*> SelectedObjects;
SelectedObjects.Reserve(InSelectedAssets.Num());
for (const FAssetData& Asset : InSelectedAssets)
{
if (UObject* LoadedAsset = Asset.GetAsset())
{
SelectedObjects.Add(LoadedAsset);
}
}
for (UObject* SelectedObject : SelectedObjects)
{
CreateControlRigFromSkeletalMeshOrSkeleton(SelectedObject, bModularRig);
}
})
);
}
}
}));
}
}
UControlRigBlueprint* FControlRigBlueprintActions::CreateNewControlRigAsset(const FString& InDesiredPackagePath, const bool bModularRig)
{
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
UControlRigBlueprintFactory* Factory = NewObject<UControlRigBlueprintFactory>();
Factory->ParentClass = bModularRig ? UModularRig::StaticClass() : UControlRig::StaticClass();
FString UniquePackageName;
FString UniqueAssetName;
AssetToolsModule.Get().CreateUniqueAssetName(InDesiredPackagePath, TEXT(""), UniquePackageName, UniqueAssetName);
if (UniquePackageName.EndsWith(UniqueAssetName))
{
UniquePackageName = UniquePackageName.LeftChop(UniqueAssetName.Len() + 1);
}
UObject* NewAsset = AssetToolsModule.Get().CreateAsset(*UniqueAssetName, *UniquePackageName, nullptr, Factory);
return Cast<UControlRigBlueprint>(NewAsset);
}
UControlRigBlueprint* FControlRigBlueprintActions::CreateControlRigFromSkeletalMeshOrSkeleton(UObject* InSelectedObject, const bool bModularRig)
{
USkeletalMesh* SkeletalMesh = Cast<USkeletalMesh>(InSelectedObject);
USkeleton* Skeleton = Cast<USkeleton>(InSelectedObject);
const FReferenceSkeleton* RefSkeleton = nullptr;
if(SkeletalMesh)
{
Skeleton = SkeletalMesh->GetSkeleton();
RefSkeleton = &SkeletalMesh->GetRefSkeleton();
}
else if (Skeleton)
{
RefSkeleton = &Skeleton->GetReferenceSkeleton();
}
else
{
UE_LOG(LogControlRigEditor, Error, TEXT("CreateControlRigFromSkeletalMeshOrSkeleton: Provided object has to be a SkeletalMesh or Skeleton."));
return nullptr;
}
check(RefSkeleton);
FString PackagePath = InSelectedObject->GetPathName();
FString ControlRigName = FString::Printf(TEXT("%s_CtrlRig"), *InSelectedObject->GetName());
int32 LastSlashPos = INDEX_NONE;
if (PackagePath.FindLastChar('/', LastSlashPos))
{
PackagePath = PackagePath.Left(LastSlashPos);
}
UControlRigBlueprint* NewControlRigBlueprint = CreateNewControlRigAsset(PackagePath / ControlRigName, bModularRig);
if (NewControlRigBlueprint == nullptr)
{
return nullptr;
}
if(URigHierarchyController* Controller = NewControlRigBlueprint->GetHierarchyController())
{
Controller->ImportBones(*RefSkeleton, NAME_None, false, false, false, false);
if(SkeletalMesh)
{
Controller->ImportCurvesFromSkeletalMesh(SkeletalMesh, NAME_None, false, false);
}
else
{
Controller->ImportCurves(Skeleton, NAME_None, false, false);
}
}
NewControlRigBlueprint->SourceHierarchyImport = Skeleton;
NewControlRigBlueprint->SourceCurveImport = Skeleton;
NewControlRigBlueprint->PropagateHierarchyFromBPToInstances();
if(SkeletalMesh)
{
NewControlRigBlueprint->SetPreviewMesh(SkeletalMesh);
}
if(!bModularRig)
{
NewControlRigBlueprint->RecompileVM();
}
return NewControlRigBlueprint;
}
USkeletalMesh* FControlRigBlueprintActions::GetSkeletalMeshFromControlRigBlueprint(const FAssetData& InAsset)
{
if (InAsset.GetClass() != UControlRigBlueprint::StaticClass())
{
return nullptr;
}
if (const UControlRigBlueprint* Blueprint = Cast<UControlRigBlueprint>(InAsset.GetAsset()))
{
return Blueprint->GetPreviewMesh();
}
return nullptr;
}
void FControlRigBlueprintActions::PostSpawningSkeletalMeshActor(AActor* InSpawnedActor, UObject* InAsset)
{
if (InSpawnedActor->HasAnyFlags(RF_Transient) || InSpawnedActor->bIsEditorPreviewActor)
{
return;
}
OnSpawnedSkeletalMeshActorChangedHandle = FCoreUObjectDelegates::OnObjectPropertyChanged.AddStatic(&FControlRigBlueprintActions::OnSpawnedSkeletalMeshActorChanged, InAsset);
}
void FControlRigBlueprintActions::OnSpawnedSkeletalMeshActorChanged(UObject* InObject, FPropertyChangedEvent& InEvent, UObject* InAsset)
{
if (!OnSpawnedSkeletalMeshActorChangedHandle.IsValid())
{
return;
}
// we are waiting for the top level property change event
// after the spawn.
if (InEvent.Property != nullptr)
{
return;
}
FCoreUObjectDelegates::OnObjectPropertyChanged.Remove(OnSpawnedSkeletalMeshActorChangedHandle);
OnSpawnedSkeletalMeshActorChangedHandle.Reset();
// Create a level sequence but delay until next tick so that the creation of the asset is not in the existing transaction
GEditor->GetTimerManager()->SetTimerForNextTick([InObject, InAsset]()
{
ASkeletalMeshActor* MeshActor = Cast<ASkeletalMeshActor>(InObject);
check(MeshActor);
UControlRigBlueprint* RigBlueprint = Cast<UControlRigBlueprint>(InAsset);
if (RigBlueprint == nullptr)
{
return;
}
UClass* ControlRigClass = RigBlueprint->GeneratedClass;
TGuardValue<bool> DisableTrackCreation(FControlRigParameterTrackEditor::bAutoGenerateControlRigTrack, false);
// find a level sequence in the world, if can't find that, create one
ULevelSequence* Sequence = ULevelSequenceEditorBlueprintLibrary::GetFocusedLevelSequence();
if (Sequence == nullptr)
{
FString SequenceName = FString::Printf(TEXT("%s_Take1"), *InAsset->GetName());
FString PackagePath;
const FString DefaultDirectory = FEditorDirectories::Get().GetLastDirectory(ELastDirectory::NEW_ASSET);
FPackageName::TryConvertFilenameToLongPackageName(DefaultDirectory, PackagePath);
if (PackagePath.IsEmpty())
{
PackagePath = TEXT("/Game");
}
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
FString UniquePackageName;
FString UniqueAssetName;
AssetToolsModule.Get().CreateUniqueAssetName(PackagePath / SequenceName, TEXT(""), UniquePackageName, UniqueAssetName);
UPackage* Package = CreatePackage(*UniquePackageName);
Sequence = NewObject<ULevelSequence>(Package, *UniqueAssetName, RF_Public | RF_Standalone);
Sequence->Initialize(); //creates movie scene
Sequence->MarkPackageDirty();
// Notify the asset registry
FAssetRegistryModule::AssetCreated(Sequence);
// Set up some sensible defaults
const UMovieSceneToolsProjectSettings* ProjectSettings = GetDefault<UMovieSceneToolsProjectSettings>();
FFrameRate TickResolution = Sequence->GetMovieScene()->GetTickResolution();
Sequence->GetMovieScene()->SetPlaybackRange((ProjectSettings->DefaultStartTime * TickResolution).FloorToFrame(), (ProjectSettings->DefaultDuration * TickResolution).FloorToFrame().Value);
if (UActorFactory* ActorFactory = GEditor->FindActorFactoryForActorClass(ALevelSequenceActor::StaticClass()))
{
if (ALevelSequenceActor* LevelSequenceActor = Cast<ALevelSequenceActor>(GEditor->UseActorFactory(ActorFactory, FAssetData(Sequence), &FTransform::Identity)))
{
LevelSequenceActor->SetSequence(Sequence);
}
}
}
if (Sequence == nullptr)
{
return;
}
UMovieScene* MovieScene = Sequence->GetMovieScene();
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(Sequence);
IAssetEditorInstance* AssetEditor = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->FindEditorForAsset(Sequence, false);
ILevelSequenceEditorToolkit* LevelSequenceEditor = static_cast<ILevelSequenceEditorToolkit*>(AssetEditor);
TWeakPtr<ISequencer> WeakSequencer = LevelSequenceEditor ? LevelSequenceEditor->GetSequencer() : nullptr;
if (WeakSequencer.IsValid())
{
TArray<TWeakObjectPtr<AActor> > ActorsToAdd;
ActorsToAdd.Add(MeshActor);
TArray<FGuid> ActorTracks = WeakSequencer.Pin()->AddActors(ActorsToAdd, false);
FControlRigEditMode* ControlRigEditMode = static_cast<FControlRigEditMode*>(GLevelEditorModeTools().GetActiveMode(FControlRigEditMode::ModeName));
for (FGuid ActorTrackGuid : ActorTracks)
{
//Delete binding from default animating rig
FGuid CompGuid = WeakSequencer.Pin()->FindObjectId(*(MeshActor->GetSkeletalMeshComponent()), WeakSequencer.Pin()->GetFocusedTemplateID());
if (CompGuid.IsValid())
{
if (ControlRigEditMode)
{
UMovieSceneControlRigParameterTrack* Track = Cast<UMovieSceneControlRigParameterTrack>(MovieScene->FindTrack(UMovieSceneControlRigParameterTrack::StaticClass(), CompGuid, NAME_None));
if (Track && Track->GetControlRig())
{
ControlRigEditMode->RemoveControlRig(Track->GetControlRig());
}
}
if (!MovieScene->RemovePossessable(CompGuid))
{
MovieScene->RemoveSpawnable(CompGuid);
}
}
UMovieSceneControlRigParameterTrack* Track = Cast<UMovieSceneControlRigParameterTrack>(MovieScene->FindTrack(UMovieSceneControlRigParameterTrack::StaticClass(), ActorTrackGuid));
if (!Track)
{
Track = MovieScene->AddTrack<UMovieSceneControlRigParameterTrack>(ActorTrackGuid);
}
UControlRig* ControlRig = Track->GetControlRig();
FString ObjectName = (ControlRigClass->GetName());
if (!ControlRig || ControlRig->GetClass() != ControlRigClass)
{
USkeletalMesh* SkeletalMesh = MeshActor->GetSkeletalMeshComponent()->GetSkeletalMeshAsset();
USkeleton* Skeleton = SkeletalMesh->GetSkeleton();
ObjectName.RemoveFromEnd(TEXT("_C"));
// This is either a UControlRig or a UModularRig
ControlRig = NewObject<UControlRig>(Track, ControlRigClass, FName(*ObjectName), RF_Transactional);
ControlRig->SetObjectBinding(MakeShared<FControlRigObjectBinding>());
ControlRig->GetObjectBinding()->BindToObject(MeshActor->GetSkeletalMeshComponent());
ControlRig->GetDataSourceRegistry()->RegisterDataSource(UControlRig::OwnerComponent, ControlRig->GetObjectBinding()->GetBoundObject());
ControlRig->Initialize();
ControlRig->Evaluate_AnyThread();
ControlRig->CreateRigControlsForCurveContainer();
WeakSequencer.Pin()->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemsChanged);
}
UMovieSceneSection* Section = Track->GetAllSections().Num() ? Track->GetAllSections()[0] : nullptr;
if (!Section)
{
Track->Modify();
Section = Track->CreateControlRigSection(0, ControlRig, true);
//mz todo need to have multiple rigs with same class
Track->SetTrackName(FName(*ObjectName));
Track->SetDisplayName(FText::FromString(ObjectName));
WeakSequencer.Pin()->EmptySelection();
WeakSequencer.Pin()->SelectSection(Section);
WeakSequencer.Pin()->ThrobSectionSelection();
WeakSequencer.Pin()->ObjectImplicitlyAdded(ControlRig);
}
WeakSequencer.Pin()->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded);
if (!ControlRigEditMode)
{
GLevelEditorModeTools().ActivateMode(FControlRigEditMode::ModeName);
ControlRigEditMode = static_cast<FControlRigEditMode*>(GLevelEditorModeTools().GetActiveMode(FControlRigEditMode::ModeName));
}
if (ControlRigEditMode)
{
ControlRigEditMode->AddControlRigObject(ControlRig, WeakSequencer.Pin());
}
}
}
});
}
const TArray<FText>& FControlRigBlueprintActions::GetSubMenus() const
{
static const TArray<FText> SubMenus
{
LOCTEXT("AnimControlRigSubMenu", "Control Rig")
};
return SubMenus;
}
#undef LOCTEXT_NAMESPACE