Files
UnrealEngine/Engine/Plugins/MovieScene/SequencerScripting/Source/SequencerScriptingEditor/Private/SequencerTools.cpp
2025-05-18 13:04:45 +08:00

993 lines
38 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SequencerTools.h"
#include "Animation/AnimSequence.h"
#include "Engine/SkeletalMesh.h"
#include "AutomatedLevelSequenceCapture.h"
#include "Channels/MovieSceneBoolChannel.h"
#include "LevelSequenceActor.h"
#include "Channels/MovieSceneEvent.h"
#include "FbxExporter.h"
#include "Channels/MovieSceneFloatChannel.h"
#include "MovieSceneToolsUserSettings.h"
#include "Channels/MovieSceneIntegerChannel.h"
#include "MovieSceneToolHelpers.h"
#include "Engine/World.h"
#include "MovieSceneEventUtils.h"
#include "INodeAndChannelMappings.h"
#include "MovieSceneSequenceEditor.h"
#include "LevelSequence.h"
#include "Sections/MovieSceneEventSectionBase.h"
#include "CineCameraActor.h"
#include "Components/SkeletalMeshComponent.h"
#include "MovieScenePossessable.h"
#include "ScopedTransaction.h"
#include "Exporters/AnimSeqExportOption.h"
#include "K2Node_CustomEvent.h"
#include "BlueprintFunctionNodeSpawner.h"
#include "BlueprintActionMenuItem.h"
#include "EdGraphSchema_K2.h"
#include "LevelSequenceAnimSequenceLink.h"
#include "AnimSequenceLevelSequenceLink.h"
#include "Compilation/MovieSceneCompiledDataManager.h"
#include "MovieSceneSpawnable.h"
#include "SequencerScriptingRange.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(SequencerTools)
#define LOCTEXT_NAMESPACE "SequencerTools"
bool USequencerToolsFunctionLibrary::RenderMovie(UMovieSceneCapture* InCaptureSettings, FOnRenderMovieStopped OnFinishedCallback)
{
IMovieSceneCaptureDialogModule& MovieSceneCaptureModule = FModuleManager::Get().LoadModuleChecked<IMovieSceneCaptureDialogModule>("MovieSceneCaptureDialog");
// Because this comes from the Python/BP layer we need to soft-validate the state before we pass it onto functions that do a assert-based validation.
if (!InCaptureSettings)
{
FFrame::KismetExecutionMessage(TEXT("Cannot start Render Sequence to Movie with null capture settings."), ELogVerbosity::Error);
return false;
}
PRAGMA_DISABLE_DEPRECATION_WARNINGS
if (IsRenderingMovie())
{
FFrame::KismetExecutionMessage(TEXT("Capture already in progress."), ELogVerbosity::Error);
return false;
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
// If they're capturing a level sequence we'll do some additional checking as there are more parameters on the Automated Level Sequence capture.
UAutomatedLevelSequenceCapture* LevelSequenceCapture = Cast<UAutomatedLevelSequenceCapture>(InCaptureSettings);
if (LevelSequenceCapture)
{
if (!LevelSequenceCapture->LevelSequenceAsset.IsValid())
{
// UE_LOG(LogTemp, Warning, TEXT("No Level Sequence Asset specified in UAutomatedLevelSequenceCapture."));
FFrame::KismetExecutionMessage(TEXT("No Level Sequence Asset specified in UAutomatedLevelSequenceCapture."), ELogVerbosity::Error);
return false;
}
if (!LevelSequenceCapture->bUseCustomStartFrame && !LevelSequenceCapture->bUseCustomEndFrame)
{
// If they don't want to use a custom start/end frame we override the default values to be the length of the sequence, as the default is [0,1)
ULevelSequence* LevelSequence = Cast<ULevelSequence>(LevelSequenceCapture->LevelSequenceAsset.TryLoad());
if (!LevelSequence)
{
const FString ErrorMessage = FString::Printf(TEXT("Specified Level Sequence Asset failed to load. Specified Asset Path: %s"), *LevelSequenceCapture->LevelSequenceAsset.GetAssetPathString());
FFrame::KismetExecutionMessage(*ErrorMessage, ELogVerbosity::Error);
return false;
}
FFrameRate DisplayRate = LevelSequence->GetMovieScene()->GetDisplayRate();
FFrameRate TickResolution = LevelSequence->GetMovieScene()->GetTickResolution();
LevelSequenceCapture->Settings.CustomFrameRate = DisplayRate;
LevelSequenceCapture->Settings.bUseCustomFrameRate = true;
LevelSequenceCapture->Settings.bUseRelativeFrameNumbers = false;
TRange<FFrameNumber> Range = LevelSequence->GetMovieScene()->GetPlaybackRange();
FFrameNumber StartFrame = UE::MovieScene::DiscreteInclusiveLower(Range);
FFrameNumber EndFrame = UE::MovieScene::DiscreteExclusiveUpper(Range);
FFrameNumber RoundedStartFrame = FFrameRate::TransformTime(StartFrame, TickResolution, DisplayRate).CeilToFrame();
FFrameNumber RoundedEndFrame = FFrameRate::TransformTime(EndFrame, TickResolution, DisplayRate).CeilToFrame();
LevelSequenceCapture->CustomStartFrame = RoundedStartFrame;
LevelSequenceCapture->CustomEndFrame = RoundedEndFrame;
}
}
auto LocalCaptureStoppedCallback = [OnFinishedCallback](bool bSuccess)
{
OnFinishedCallback.ExecuteIfBound(bSuccess);
};
MovieSceneCaptureModule.StartCapture(InCaptureSettings);
MovieSceneCaptureModule.GetCurrentCapture()->CaptureStoppedDelegate.AddLambda(LocalCaptureStoppedCallback);
return true;
}
void USequencerToolsFunctionLibrary::CancelMovieRender()
{
IMovieSceneCaptureDialogModule& MovieSceneCaptureModule = FModuleManager::Get().LoadModuleChecked<IMovieSceneCaptureDialogModule>("MovieSceneCaptureDialog");
TSharedPtr<FMovieSceneCaptureBase> CurrentCapture = MovieSceneCaptureModule.GetCurrentCapture();
if (CurrentCapture.IsValid())
{
// We just invoke the capture's Cancel function. This will cause a shut-down of the capture (the same as the UI)
// which will invoke all of the necessary callbacks as well. We don't null out CurrentCapture because that is done
// as the result of its shutdown callbacks.
CurrentCapture->Cancel();
}
}
TArray<FSequencerBoundObjects> USequencerToolsFunctionLibrary::GetBoundObjects(UWorld* InWorld, ULevelSequence* InSequence, const TArray<FMovieSceneBindingProxy>& InBindings, const FSequencerScriptingRange& InRange)
{
ALevelSequenceActor* OutActor;
FMovieSceneSequencePlaybackSettings Settings;
FLevelSequenceCameraSettings CameraSettings;
ULevelSequencePlayer* Player = ULevelSequencePlayer::CreateLevelSequencePlayer(InWorld, InSequence, Settings, OutActor);
// Evaluation needs to occur in order to obtain spawnables
FFrameRate Resolution = InSequence->GetMovieScene()->GetTickResolution();
TRange<FFrameNumber> SpecifiedRange = InRange.ToNative(Resolution);
Player->SetPlaybackPosition(FMovieSceneSequencePlaybackParams(SpecifiedRange.GetLowerBoundValue().Value, EUpdatePositionMethod::Play));
FMovieSceneSequenceID SequenceId = Player->GetEvaluationState()->FindSequenceId(InSequence);
TArray<FSequencerBoundObjects> BoundObjects;
for (FMovieSceneBindingProxy Binding : InBindings)
{
FMovieSceneObjectBindingID ObjectBinding = UE::MovieScene::FFixedObjectBindingID(Binding.BindingID, SequenceId);
BoundObjects.Add(FSequencerBoundObjects(Binding, Player->GetBoundObjects(ObjectBinding)));
}
Player->Stop();
InWorld->DestroyActor(OutActor);
return BoundObjects;
}
TArray<FSequencerBoundObjects> USequencerToolsFunctionLibrary::GetObjectBindings(UWorld* InWorld, ULevelSequence* InSequence, const TArray<UObject*>& InObjects, const FSequencerScriptingRange& InRange)
{
ALevelSequenceActor* OutActor;
FMovieSceneSequencePlaybackSettings Settings;
FLevelSequenceCameraSettings CameraSettings;
ULevelSequencePlayer* Player = ULevelSequencePlayer::CreateLevelSequencePlayer(InWorld, InSequence, Settings, OutActor);
FFrameRate Resolution = InSequence->GetMovieScene()->GetTickResolution();
TRange<FFrameNumber> SpecifiedRange = InRange.ToNative(Resolution);
Player->SetPlaybackPosition(FMovieSceneSequencePlaybackParams(SpecifiedRange.GetLowerBoundValue().Value, EUpdatePositionMethod::Play));
TArray<FSequencerBoundObjects> BoundObjects;
for (UObject* Object : InObjects)
{
TArray<FMovieSceneObjectBindingID> ObjectBindings = Player->GetObjectBindings(Object);
for (FMovieSceneObjectBindingID ObjectBinding : ObjectBindings)
{
FMovieSceneSequenceID SequenceID = ObjectBinding.ResolveSequenceID(MovieSceneSequenceID::Root, *Player);
FMovieSceneBindingProxy Binding(ObjectBinding.GetGuid(), Player->GetEvaluationState()->FindSequence(SequenceID));
BoundObjects.Add(FSequencerBoundObjects(Binding, TArray<UObject*>({ Object })));
}
}
Player->Stop();
InWorld->DestroyActor(OutActor);
return BoundObjects;
}
void GatherDescendantBindings(const FMovieSceneBindingProxy& Binding, UMovieSceneSequence* Sequence, TArray<FMovieSceneBindingProxy>& AllBindings)
{
UMovieScene* MovieScene = Sequence->GetMovieScene();
for (int32 Index = 0; Index < MovieScene->GetPossessableCount(); ++Index)
{
FMovieScenePossessable& Possessable = MovieScene->GetPossessable(Index);
if (Possessable.GetParent() == Binding.BindingID)
{
FMovieSceneBindingProxy ChildBinding(Possessable.GetGuid(), Sequence);
AllBindings.Add(ChildBinding);
GatherDescendantBindings(ChildBinding, Sequence, AllBindings);
}
}
}
bool ExportFBXInternal(const FSequencerExportFBXParams& InParams, UMovieSceneSequencePlayer* Player)
{
UWorld* World = InParams.World;
UMovieSceneSequence* Sequence = InParams.Sequence;
UMovieSceneSequence* RootSequence = InParams.RootSequence;
TArray<FMovieSceneBindingProxy> BindingProxies = InParams.Bindings;
TArray<UMovieSceneTrack*> Tracks = InParams.Tracks;
UFbxExportOption* OverrideOptions = InParams.OverrideOptions;
FString FBXFileName = InParams.FBXFileName;
if (!RootSequence)
{
RootSequence = Sequence;
}
if (!RootSequence)
{
return false;
}
UnFbx::FFbxExporter* Exporter = UnFbx::FFbxExporter::GetInstance();
//Show the fbx export dialog options
Exporter->SetExportOptionsOverride(OverrideOptions);
UMovieScene* MovieScene = Sequence->GetMovieScene();
TArray<FMovieSceneBindingProxy> AllBindings;
for (const FMovieSceneBindingProxy& Binding : BindingProxies)
{
AllBindings.Add(Binding);
GatherDescendantBindings(Binding, Sequence, AllBindings);
}
TArray<FGuid> Bindings;
for (const FMovieSceneBindingProxy& Proxy : AllBindings)
{
if (Proxy.Sequence == Sequence)
{
Bindings.Add(Proxy.BindingID);
}
}
FMovieSceneEvaluationState* State = Player->GetEvaluationState();
State->AssignSequence(MovieSceneSequenceID::Root, *RootSequence, *Player);
FMovieSceneSequenceIDRef Template = State->FindSequenceId(Sequence);
UnFbx::FFbxExporter::FLevelSequenceNodeNameAdapter NodeNameAdapter(MovieScene, Player, Template);
FMovieSceneSequenceHierarchy Hierarchy = FMovieSceneSequenceHierarchy();
UMovieSceneCompiledDataManager::CompileHierarchy(RootSequence, &Hierarchy, EMovieSceneServerClientMask::All);
const FMovieSceneSubSequenceData* SubSequenceData = Hierarchy.FindSubData(Template);
FMovieSceneSequenceTransform RootToLocalTransform;
FFrameTime StartTime = FFrameRate::TransformTime(UE::MovieScene::DiscreteInclusiveLower(MovieScene->GetPlaybackRange()).Value, MovieScene->GetTickResolution(), MovieScene->GetDisplayRate());
if (SubSequenceData)
{
RootToLocalTransform = SubSequenceData->RootToSequenceTransform;
StartTime = RootToLocalTransform.Inverse().TryTransformTime(UE::MovieScene::DiscreteInclusiveLower(SubSequenceData->PlayRange.Value)).Get(StartTime);
}
bool bDidExport = false;
{
FSpawnableRestoreState SpawnableRestoreState(MovieScene, Player->GetSharedPlaybackState().ToSharedPtr());
if (SpawnableRestoreState.bWasChanged)
{
// Evaluate at the beginning of the subscene time to ensure that spawnables are created before export
Player->SetPlaybackPosition(FMovieSceneSequencePlaybackParams(StartTime, EUpdatePositionMethod::Play));
}
FAnimExportSequenceParameters AESP;
AESP.Player = Player;
AESP.RootToLocalTransform = RootToLocalTransform;
AESP.MovieSceneSequence = Sequence;
AESP.RootMovieSceneSequence = RootSequence;
bDidExport = MovieSceneToolHelpers::ExportFBX(World, AESP, Bindings, Tracks, NodeNameAdapter, Template, FBXFileName);
}
Player->Stop();
Exporter->SetExportOptionsOverride(nullptr);
return bDidExport;
}
bool USequencerToolsFunctionLibrary::ExportLevelSequenceFBX(const FSequencerExportFBXParams& InParams)
{
ALevelSequenceActor* OutActor;
FMovieSceneSequencePlaybackSettings Settings;
FLevelSequenceCameraSettings CameraSettings;
ULevelSequence* RootSequence = InParams.RootSequence;
if (!RootSequence)
{
RootSequence = InParams.Sequence;
}
if (!RootSequence)
{
FFrame::KismetExecutionMessage(TEXT("Cannot export level sequence. Sequence is invalid."), ELogVerbosity::Error);
return false;
}
ULevelSequencePlayer* Player = ULevelSequencePlayer::CreateLevelSequencePlayer(InParams.World, RootSequence, Settings, OutActor);
bool bSuccess = ExportFBXInternal(InParams, Player);
InParams.World->DestroyActor(OutActor);
return bSuccess;
}
static USkeletalMeshComponent* GetSkelMeshComponent(IMovieScenePlayer* Player, const FMovieSceneBindingProxy& Binding)
{
FMovieSceneSequenceIDRef Template = MovieSceneSequenceID::Root;
for (TWeakObjectPtr<UObject> RuntimeObject : Player->FindBoundObjects(Binding.BindingID, Template))
{
if (AActor* Actor = Cast<AActor>(RuntimeObject.Get()))
{
for (UActorComponent* Component : Actor->GetComponents())
{
if (USkeletalMeshComponent* SkeletalMeshComp = Cast<USkeletalMeshComponent>(Component))
{
return SkeletalMeshComp;
}
}
}
else if (USkeletalMeshComponent* SkeletalMeshComponent = Cast<USkeletalMeshComponent>(RuntimeObject.Get()))
{
if (SkeletalMeshComponent->GetSkeletalMeshAsset())
{
return SkeletalMeshComponent;
}
}
}
return nullptr;
}
bool USequencerToolsFunctionLibrary::ExportAnimSequence(UWorld* World, ULevelSequence* Sequence, UAnimSequence* AnimSequence, UAnimSeqExportOption* ExportOptions,const FMovieSceneBindingProxy& Binding, bool bCreateLink)
{
UMovieScene* MovieScene = Sequence->GetMovieScene();
if (Binding.Sequence != Sequence || !AnimSequence)
{
return false;
}
ALevelSequenceActor* OutActor;
FMovieSceneSequencePlaybackSettings Settings;
FLevelSequenceCameraSettings CameraSettings;
FMovieSceneSequenceIDRef Template = MovieSceneSequenceID::Root;
FMovieSceneSequenceTransform RootToLocalTransform;
ULevelSequencePlayer* Player = ULevelSequencePlayer::CreateLevelSequencePlayer(World, Sequence, Settings, OutActor);
bool bResult = false;
{
FSpawnableRestoreState SpawnableRestoreState(MovieScene, Player->GetSharedPlaybackState().ToSharedPtr());
if (SpawnableRestoreState.bWasChanged)
{
// Evaluate at the beginning of the subscene time to ensure that spawnables are created before export
FFrameTime StartTime = FFrameRate::TransformTime(UE::MovieScene::DiscreteInclusiveLower(MovieScene->GetPlaybackRange()).Value, MovieScene->GetTickResolution(), MovieScene->GetDisplayRate());
Player->SetPlaybackPosition(FMovieSceneSequencePlaybackParams(StartTime, EUpdatePositionMethod::Play));
// Cannot transact changes due to in-flight spawnable transaction
ExportOptions->bTransactRecording = false;
}
USkeletalMeshComponent* SkeletalMeshComp = GetSkelMeshComponent(Player, Binding);
if (SkeletalMeshComp && SkeletalMeshComp->GetSkeletalMeshAsset() && SkeletalMeshComp->GetSkeletalMeshAsset()->GetSkeleton())
{
AnimSequence->SetSkeleton(SkeletalMeshComp->GetSkeletalMeshAsset()->GetSkeleton());
FAnimExportSequenceParameters AESQ;
AESQ.Player = Player;
AESQ.RootToLocalTransform = RootToLocalTransform;
AESQ.MovieSceneSequence = Sequence;
AESQ.RootMovieSceneSequence = Sequence;
bResult = MovieSceneToolHelpers::ExportToAnimSequence(AnimSequence, ExportOptions, AESQ, SkeletalMeshComp);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("USequencerToolsFunctionLibrary::ExportAnimSequence: No skel mesh found in Sequencer"));
}
}
Player->Stop();
World->DestroyActor(OutActor);
//create the link to the anim sequence
if (bResult && bCreateLink)
{
return LinkAnimSequence(Sequence, AnimSequence, ExportOptions, Binding);
}
return bResult;
}
void USequencerToolsFunctionLibrary::ClearLinkedAnimSequences(ULevelSequence* LevelSequence)
{
if (LevelSequence)
{
if (IInterface_AssetUserData* LevelSequenceUserDataInterface = Cast< IInterface_AssetUserData >(LevelSequence))
{
ULevelSequenceAnimSequenceLink* LevelAnimLink = LevelSequenceUserDataInterface->GetAssetUserData< ULevelSequenceAnimSequenceLink >();
if (LevelAnimLink)
{
for (int32 Index = 0; Index < LevelAnimLink->AnimSequenceLinks.Num(); ++Index)
{
FLevelSequenceAnimSequenceLinkItem& LevelAnimLinkItem = LevelAnimLink->AnimSequenceLinks[Index];
if (UAnimSequence* AnimSequence = LevelAnimLinkItem.ResolveAnimSequence())
{
if (IInterface_AssetUserData* AnimAssetUserData = Cast< IInterface_AssetUserData >(AnimSequence))
{
UAnimSequenceLevelSequenceLink* AnimLevelLink = AnimAssetUserData->GetAssetUserData< UAnimSequenceLevelSequenceLink >();
if (AnimLevelLink)
{
ULevelSequence* AnimLevelSequence = AnimLevelLink->ResolveLevelSequence();
if (AnimLevelSequence && AnimLevelSequence == LevelSequence)
{
AnimAssetUserData->RemoveUserDataOfClass(UAnimSequenceLevelSequenceLink::StaticClass());
}
}
}
}
}
LevelSequenceUserDataInterface->RemoveUserDataOfClass(ULevelSequenceAnimSequenceLink::StaticClass());
}
}
}
}
bool USequencerToolsFunctionLibrary::LinkAnimSequence(ULevelSequence* Sequence, UAnimSequence* AnimSequence, const UAnimSeqExportOption* ExportOptions,const FMovieSceneBindingProxy& Binding)
{
if (!Sequence || !AnimSequence || !ExportOptions || Binding.Sequence != Sequence)
{
return false;
}
if (Sequence && Sequence->GetClass()->ImplementsInterface(UInterface_AssetUserData::StaticClass())
&& AnimSequence->GetClass()->ImplementsInterface(UInterface_AssetUserData::StaticClass()))
{
Sequence->Modify();
AnimSequence->Modify();
if (IInterface_AssetUserData* AnimAssetUserData = Cast< IInterface_AssetUserData >(AnimSequence))
{
UAnimSequenceLevelSequenceLink* AnimLevelLink = AnimAssetUserData->GetAssetUserData< UAnimSequenceLevelSequenceLink >();
if (!AnimLevelLink)
{
AnimLevelLink = NewObject<UAnimSequenceLevelSequenceLink>(AnimSequence, NAME_None, RF_Public | RF_Transactional);
AnimAssetUserData->AddAssetUserData(AnimLevelLink);
}
AnimLevelLink->SetLevelSequence(Sequence);
AnimLevelLink->SkelTrackGuid = Binding.BindingID;
}
if (IInterface_AssetUserData* AssetUserDataInterface = Cast< IInterface_AssetUserData >(Sequence))
{
bool bAddItem = true;
ULevelSequenceAnimSequenceLink* LevelAnimLink = AssetUserDataInterface->GetAssetUserData< ULevelSequenceAnimSequenceLink >();
if (LevelAnimLink)
{
for (FLevelSequenceAnimSequenceLinkItem& LevelAnimLinkItem : LevelAnimLink->AnimSequenceLinks)
{
if (LevelAnimLinkItem.IsEqual(Binding.BindingID, ExportOptions->bUseCustomTimeRange,
ExportOptions->CustomStartFrame, ExportOptions->CustomEndFrame, ExportOptions->CustomDisplayRate,
ExportOptions->bUseCustomFrameRate, ExportOptions->CustomFrameRate))
{
bAddItem = false;
UAnimSequence* OtherAnimSequence = LevelAnimLinkItem.ResolveAnimSequence();
if (OtherAnimSequence != AnimSequence)
{
if (IInterface_AssetUserData* OtherAnimAssetUserData = Cast< IInterface_AssetUserData >(OtherAnimSequence))
{
UAnimSequenceLevelSequenceLink* OtherAnimLevelLink = OtherAnimAssetUserData->GetAssetUserData< UAnimSequenceLevelSequenceLink >();
if (OtherAnimLevelLink)
{
OtherAnimSequence->Modify();
OtherAnimAssetUserData->RemoveUserDataOfClass(UAnimSequenceLevelSequenceLink::StaticClass());
}
}
}
LevelAnimLinkItem.PathToAnimSequence = FSoftObjectPath(AnimSequence);
LevelAnimLinkItem.bExportMorphTargets = ExportOptions->bExportMorphTargets;
LevelAnimLinkItem.bExportAttributeCurves = ExportOptions->bExportAttributeCurves;
LevelAnimLinkItem.bExportMaterialCurves = ExportOptions->bExportMaterialCurves;
LevelAnimLinkItem.bExportTransforms = ExportOptions->bExportTransforms;
LevelAnimLinkItem.Interpolation = ExportOptions->Interpolation;
LevelAnimLinkItem.CurveInterpolation = ExportOptions->CurveInterpolation;
LevelAnimLinkItem.bRecordInWorldSpace = ExportOptions->bRecordInWorldSpace;
LevelAnimLinkItem.bEvaluateAllSkeletalMeshComponents = ExportOptions->bEvaluateAllSkeletalMeshComponents;
LevelAnimLinkItem.IncludeAnimationNames = ExportOptions->IncludeAnimationNames;
LevelAnimLinkItem.ExcludeAnimationNames = ExportOptions->ExcludeAnimationNames;
LevelAnimLinkItem.WarmUpFrames = ExportOptions->WarmUpFrames;
LevelAnimLinkItem.DelayBeforeStart = ExportOptions->DelayBeforeStart;
LevelAnimLinkItem.bUseCustomTimeRange = ExportOptions->bUseCustomTimeRange;
LevelAnimLinkItem.CustomStartFrame = ExportOptions->CustomStartFrame;
LevelAnimLinkItem.CustomEndFrame = ExportOptions->CustomEndFrame;
LevelAnimLinkItem.CustomDisplayRate = ExportOptions->CustomDisplayRate;
LevelAnimLinkItem.bUseCustomFrameRate = ExportOptions->bUseCustomFrameRate;
LevelAnimLinkItem.CustomFrameRate = ExportOptions->CustomFrameRate;
break;
}
}
}
else
{
LevelAnimLink = NewObject<ULevelSequenceAnimSequenceLink>(Sequence, NAME_None, RF_Public | RF_Transactional);
}
if (bAddItem == true)
{
FLevelSequenceAnimSequenceLinkItem LevelAnimLinkItem;
LevelAnimLinkItem.SkelTrackGuid = Binding.BindingID;
LevelAnimLinkItem.PathToAnimSequence = FSoftObjectPath(AnimSequence);
LevelAnimLinkItem.bExportMorphTargets = ExportOptions->bExportMorphTargets;
LevelAnimLinkItem.bExportAttributeCurves = ExportOptions->bExportAttributeCurves;
LevelAnimLinkItem.Interpolation = ExportOptions->Interpolation;
LevelAnimLinkItem.CurveInterpolation = ExportOptions->CurveInterpolation;
LevelAnimLinkItem.bExportMaterialCurves = ExportOptions->bExportMaterialCurves;
LevelAnimLinkItem.bExportTransforms = ExportOptions->bExportTransforms;
LevelAnimLinkItem.bRecordInWorldSpace = ExportOptions->bRecordInWorldSpace;
LevelAnimLinkItem.bEvaluateAllSkeletalMeshComponents = ExportOptions->bEvaluateAllSkeletalMeshComponents;
LevelAnimLinkItem.IncludeAnimationNames = ExportOptions->IncludeAnimationNames;
LevelAnimLinkItem.ExcludeAnimationNames = ExportOptions->ExcludeAnimationNames;
LevelAnimLinkItem.WarmUpFrames = ExportOptions->WarmUpFrames;
LevelAnimLinkItem.DelayBeforeStart = ExportOptions->DelayBeforeStart;
LevelAnimLinkItem.bUseCustomTimeRange = ExportOptions->bUseCustomTimeRange;
LevelAnimLinkItem.CustomStartFrame = ExportOptions->CustomStartFrame;
LevelAnimLinkItem.CustomEndFrame = ExportOptions->CustomEndFrame;
LevelAnimLinkItem.CustomDisplayRate = ExportOptions->CustomDisplayRate;
LevelAnimLinkItem.bUseCustomFrameRate = ExportOptions->bUseCustomFrameRate;
LevelAnimLinkItem.CustomFrameRate = ExportOptions->CustomFrameRate;
LevelAnimLink->AnimSequenceLinks.Add(LevelAnimLinkItem);
AssetUserDataInterface->AddAssetUserData(LevelAnimLink);
}
}
}
return true;
}
UAnimSequenceLevelSequenceLink* USequencerToolsFunctionLibrary::GetLevelSequenceLinkFromAnimSequence(UAnimSequence* InAnimSequence)
{
if (IInterface_AssetUserData* AnimAssetUserData = Cast< IInterface_AssetUserData >(InAnimSequence))
{
UAnimSequenceLevelSequenceLink* AnimLevelLink = AnimAssetUserData->GetAssetUserData< UAnimSequenceLevelSequenceLink >();
return AnimLevelLink;
}
return nullptr;
}
ULevelSequenceAnimSequenceLink* USequencerToolsFunctionLibrary::GetAnimSequenceLinkFromLevelSequence(ULevelSequence* InLevelSequence)
{
if (IInterface_AssetUserData* AssetUserDataInterface = Cast< IInterface_AssetUserData >(InLevelSequence))
{
ULevelSequenceAnimSequenceLink* LevelAnimLink = AssetUserDataInterface->GetAssetUserData< ULevelSequenceAnimSequenceLink >();
return LevelAnimLink;
}
return nullptr;
}
TArray<FGuid> AddActors(UWorld* World, UMovieSceneSequence* InSequence, UMovieScene* InMovieScene, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID,const TArray<TWeakObjectPtr<AActor> >& InActors)
{
TArray<FGuid> PossessableGuids;
if (InMovieScene->IsReadOnly())
{
return PossessableGuids;
}
for (TWeakObjectPtr<AActor> WeakActor : InActors)
{
if (AActor* Actor = WeakActor.Get())
{
FGuid ExistingGuid = Player->FindObjectId(*Actor, TemplateID);
if (!ExistingGuid.IsValid())
{
InMovieScene->Modify();
const FGuid PossessableGuid = InMovieScene->AddPossessable(Actor->GetActorLabel(), Actor->GetClass());
PossessableGuids.Add(PossessableGuid);
InSequence->BindPossessableObject(PossessableGuid, *Actor, World);
InMovieScene->FindPossessable(PossessableGuid)->FixupPossessedObjectClass(InSequence, World);
//TODO New to figure way to call void FLevelSequenceEditorToolkit::AddDefaultTracksForActor(AActor& Actor, const FGuid Binding)
if (Actor->IsA<ACameraActor>())
{
MovieSceneToolHelpers::CreateCameraCutSectionForCamera(InMovieScene, PossessableGuid, 0);
}
}
}
}
return PossessableGuids;
}
void ImportFBXCamera(UnFbx::FFbxImporter* FbxImporter, UWorld* World, UMovieSceneSequence* Sequence, UMovieScene* InMovieScene, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID, TMap<FGuid, FString>& InObjectBindingMap, bool bMatchByNameOnly, bool bCreateCameras)
{
if (bCreateCameras)
{
TArray<FbxCamera*> AllCameras;
MovieSceneToolHelpers::GetCameras(FbxImporter->Scene->GetRootNode(), AllCameras);
// Find unmatched cameras
TArray<FbxCamera*> UnmatchedCameras;
for (auto Camera : AllCameras)
{
FString NodeName = MovieSceneToolHelpers::GetCameraName(Camera);
bool bMatched = false;
for (auto InObjectBinding : InObjectBindingMap)
{
FString ObjectName = InObjectBinding.Value;
if (ObjectName == NodeName)
{
// Look for a valid bound object, otherwise need to create a new camera and assign this binding to it
bool bFoundBoundObject = false;
TArrayView<TWeakObjectPtr<>> BoundObjects = Player->FindBoundObjects(InObjectBinding.Key, TemplateID);
for (auto BoundObject : BoundObjects)
{
if (BoundObject.IsValid())
{
bFoundBoundObject = true;
break;
}
}
}
}
if (!bMatched)
{
UnmatchedCameras.Add(Camera);
}
}
// If there are new cameras, clear the object binding map so that we're only assigning values to the newly created cameras
if (UnmatchedCameras.Num() != 0)
{
InObjectBindingMap.Reset();
bMatchByNameOnly = true;
}
// Add any unmatched cameras
for (auto UnmatchedCamera : UnmatchedCameras)
{
FString CameraName = MovieSceneToolHelpers::GetCameraName(UnmatchedCamera);
AActor* NewCamera = nullptr;
if (UnmatchedCamera->GetApertureMode() == FbxCamera::eFocalLength)
{
FActorSpawnParameters SpawnParams;
NewCamera = World->SpawnActor<ACineCameraActor>(SpawnParams);
NewCamera->SetActorLabel(*CameraName);
}
else
{
FActorSpawnParameters SpawnParams;
NewCamera = World->SpawnActor<ACameraActor>(SpawnParams);
NewCamera->SetActorLabel(*CameraName);
}
// Copy camera properties before adding default tracks so that initial camera properties match and can be restored after sequencer finishes
MovieSceneToolHelpers::CopyCameraProperties(UnmatchedCamera, NewCamera);
TArray<TWeakObjectPtr<AActor> > NewCameras;
NewCameras.Add(NewCamera);
TArray<FGuid> NewCameraGuids = AddActors(World, Sequence,InMovieScene, Player, TemplateID,NewCameras);
if (NewCameraGuids.Num())
{
InObjectBindingMap.Add(NewCameraGuids[0]);
InObjectBindingMap[NewCameraGuids[0]] = CameraName;
}
}
}
//everything created now import it in.
MovieSceneToolHelpers::ImportFBXCameraToExisting(FbxImporter, Sequence, Player, TemplateID, InObjectBindingMap, bMatchByNameOnly, true);
}
bool ImportFBXInternal(UWorld* World, UMovieSceneSequence* Sequence, const TArray<FMovieSceneBindingProxy>& InBindings, UMovieSceneUserImportFBXSettings* ImportFBXSettings, const FString& ImportFilename, UMovieSceneSequencePlayer* Player)
{
UMovieScene* MovieScene = Sequence->GetMovieScene();
if (!MovieScene || MovieScene->IsReadOnly())
{
return false;
}
TArray<FMovieSceneBindingProxy> AllBindings;
for (const FMovieSceneBindingProxy& Binding : InBindings)
{
AllBindings.Add(Binding);
GatherDescendantBindings(Binding, Sequence, AllBindings);
}
TMap<FGuid, FString> ObjectBindingMap;
for (const FMovieSceneBindingProxy& Binding : AllBindings)
{
FString Name = MovieScene->GetObjectDisplayName(Binding.BindingID).ToString();
ObjectBindingMap.Add(Binding.BindingID, Name);
}
FFBXInOutParameters InOutParams;
if (!MovieSceneToolHelpers::ReadyFBXForImport(ImportFilename, ImportFBXSettings, InOutParams))
{
return false;
}
const bool bMatchByNameOnly = ImportFBXSettings->bMatchByNameOnly;
UnFbx::FFbxImporter* FbxImporter = UnFbx::FFbxImporter::GetInstance();
bool bResult = false;
FScopedTransaction ImportFBXTransaction(NSLOCTEXT("Sequencer", "ImportFBX", "Import FBX"));
{
FSpawnableRestoreState SpawnableRestoreState(MovieScene, Player->GetSharedPlaybackState().ToSharedPtr());
if (SpawnableRestoreState.bWasChanged)
{
// Evaluate at the beginning of the subscene time to ensure that spawnables are created before export
FFrameTime StartTime = FFrameRate::TransformTime(UE::MovieScene::DiscreteInclusiveLower(MovieScene->GetPlaybackRange()).Value, MovieScene->GetTickResolution(), MovieScene->GetDisplayRate());
Player->SetPlaybackPosition(FMovieSceneSequencePlaybackParams(StartTime, EUpdatePositionMethod::Play));
}
ImportFBXCamera(FbxImporter, World, Sequence, MovieScene, Player, MovieSceneSequenceID::Root, ObjectBindingMap, bMatchByNameOnly, ImportFBXSettings->bCreateCameras);
bResult = MovieSceneToolHelpers::ImportFBXIfReady(World, Sequence, Player, MovieSceneSequenceID::Root, ObjectBindingMap, ImportFBXSettings, InOutParams);
}
Player->Stop();
return bResult;
}
bool USequencerToolsFunctionLibrary::ImportLevelSequenceFBX(UWorld* World, ULevelSequence* Sequence, const TArray<FMovieSceneBindingProxy>& InBindings, UMovieSceneUserImportFBXSettings* ImportFBXSettings, const FString& ImportFilename, ALevelSequenceActor* ActorContext)
{
ALevelSequenceActor* OutActor = nullptr;
FMovieSceneSequencePlaybackSettings Settings;
FLevelSequenceCameraSettings CameraSettings;
ULevelSequencePlayer* Player = ActorContext ? ActorContext->GetSequencePlayer() : ULevelSequencePlayer::CreateLevelSequencePlayer(World, Sequence, Settings, OutActor);
bool bSuccess = ImportFBXInternal(World, Sequence, InBindings, ImportFBXSettings, ImportFilename, Player);
if(OutActor)
{
World->DestroyActor(OutActor);
}
return bSuccess;
}
bool USequencerToolsFunctionLibrary::ImportFBXToControlRig(UWorld* World, ULevelSequence* Sequence, const FString& ControlRigTrackName, const TArray<FString>& ControlRigNames,
UMovieSceneUserImportFBXControlRigSettings* ImportFBXControlRigSettings,
const FString& ImportFilename)
{
UMovieScene* MovieScene = Sequence->GetMovieScene();
if (!MovieScene || MovieScene->IsReadOnly())
{
return false;
}
bool bValid = false;
const TArray<FMovieSceneBinding>& Bindings = MovieScene->GetBindings();
for (const FMovieSceneBinding& Binding : Bindings)
{
if (Binding.GetName() == ControlRigTrackName)
{
ALevelSequenceActor* OutActor;
FMovieSceneSequencePlaybackSettings Settings;
FLevelSequenceCameraSettings CameraSettings;
ULevelSequencePlayer* Player = ULevelSequencePlayer::CreateLevelSequencePlayer(World, Sequence, Settings, OutActor);
const TArray<UMovieSceneTrack*>& Tracks = Binding.GetTracks();
TArray<FName> SelectedControls;
for (UMovieSceneTrack* Track : Tracks)
{
INodeAndChannelMappings* ChannelMapping = Cast<INodeAndChannelMappings>(Track);
if (ChannelMapping)
{
TArray<FRigControlFBXNodeAndChannels>* NodeAndChannels = ChannelMapping->GetNodeAndChannelMappings(nullptr);
//use passed in controls for selected, actually selected controls should almost be empty anyway since we just loaded/set everything up.
for (const FString& StringName : ControlRigNames)
{
FName Name(*StringName);
SelectedControls.Add(Name);
}
bValid = MovieSceneToolHelpers::ImportFBXIntoControlRigChannels(MovieScene,ImportFilename, ImportFBXControlRigSettings,
NodeAndChannels, SelectedControls, MovieScene->GetTickResolution());
if (NodeAndChannels)
{
delete NodeAndChannels;
}
}
}
return bValid;
}
}
return false;
}
bool USequencerToolsFunctionLibrary::ExportFBXFromControlRig(ULevelSequence* Sequence,
const FString& ActorWithControlRigTrack,
const UMovieSceneUserExportFBXControlRigSettings* ExportFBXControlRigSettings)
{
bool bValid = false;
if (!Sequence || !ExportFBXControlRigSettings)
{
return false;
}
UMovieScene* MovieScene = Sequence->GetMovieScene();
if (!MovieScene || MovieScene->IsReadOnly())
{
return false;
}
const TArray<FMovieSceneBinding>& Bindings = MovieScene->GetBindings();
for (const FMovieSceneBinding& Binding : Bindings)
{
if (Binding.GetName() != ActorWithControlRigTrack)
{
continue;
}
for (UMovieSceneTrack* Track : Binding.GetTracks())
{
INodeAndChannelMappings* ChannelMapping = Cast<INodeAndChannelMappings>(Track);
const UMovieSceneSection* Section = Track->GetSectionToKey();
if (!ChannelMapping || !Section)
{
continue;
}
TArray<FName> SelectedControls;
ChannelMapping->GetSelectedNodes(SelectedControls);
const FMovieSceneSequenceTransform RootToLocalTransform;
return MovieSceneToolHelpers::ExportFBXFromControlRigChannels(Section, ExportFBXControlRigSettings, SelectedControls, RootToLocalTransform);
}
}
return false;
}
FMovieSceneEvent USequencerToolsFunctionLibrary::CreateEvent(UMovieSceneSequence* InSequence, UMovieSceneEventSectionBase* InSection, const FSequencerQuickBindingResult& InEndpoint, const TArray<FString>& InPayload)
{
FMovieSceneEvent Event;
if (InEndpoint.EventEndpoint == nullptr)
{
FFrame::KismetExecutionMessage(TEXT("Invalid endpoint, event will not be initialized"), ELogVerbosity::Warning);
return Event;
}
UMovieScene* MovieScene = InSequence->GetMovieScene();
FGuid ObjectBindingID;
MovieScene->FindTrackBinding(*InSection->GetTypedOuter<UMovieSceneTrack>(), ObjectBindingID);
UClass* BoundObjectPinClass = nullptr;
if (FMovieScenePossessable* Possessable = MovieScene->FindPossessable(ObjectBindingID))
{
BoundObjectPinClass = const_cast<UClass*>(Possessable->GetPossessedObjectClass());
}
else if (FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(ObjectBindingID))
{
BoundObjectPinClass = Spawnable->GetObjectTemplate()->GetClass();
}
InSection->Modify();
FMovieSceneEventUtils::BindEventSectionToBlueprint(InSection, InEndpoint.EventEndpoint->GetBlueprint());
UEdGraphPin* BoundObjectPin = FMovieSceneDirectorBlueprintUtils::FindCallTargetPin(InEndpoint.EventEndpoint, BoundObjectPinClass);
FMovieSceneEventUtils::SetEndpoint(&Event, InSection, InEndpoint.EventEndpoint, BoundObjectPin);
if (InEndpoint.PayloadNames.Num() != InPayload.Num())
{
const FString Message = FString::Printf(TEXT("Wrong number of payload values, expecting %i got %i"), InEndpoint.PayloadNames.Num(), InPayload.Num());
FFrame::KismetExecutionMessage(*Message, ELogVerbosity::Warning);
return Event;
}
for (int32 Index = 0; Index < InEndpoint.PayloadNames.Num(); Index++)
{
const FName PayloadName = FName(InEndpoint.PayloadNames[Index]);
if (!Event.PayloadVariables.Contains(PayloadName))
{
Event.PayloadVariables.Add(PayloadName);
Event.PayloadVariables[PayloadName].Value = InPayload[Index];
}
}
return Event;
}
bool USequencerToolsFunctionLibrary::IsEventEndpointValid(const FSequencerQuickBindingResult& InEndpoint)
{
return InEndpoint.EventEndpoint != nullptr;
}
FSequencerQuickBindingResult USequencerToolsFunctionLibrary::CreateQuickBinding(UMovieSceneSequence* InSequence, UObject* InObject, const FString& InFunctionName, bool bCallInEditor)
{
FSequencerQuickBindingResult Result;
FMovieSceneSequenceEditor* SequenceEditor = FMovieSceneSequenceEditor::Find(InSequence);
if (!SequenceEditor)
{
return Result;
}
UBlueprint* Blueprint = SequenceEditor->GetOrCreateDirectorBlueprint(InSequence);
if (!Blueprint)
{
return Result;
}
UMovieScene* MovieScene = InSequence->GetMovieScene();
FMovieSceneDirectorBlueprintEndpointDefinition EndpointDefinition;
EndpointDefinition.EndpointType = EMovieSceneDirectorBlueprintEndpointType::Event;
EndpointDefinition.EndpointName = InFunctionName;
EndpointDefinition.PossibleCallTargetClass = InObject->GetClass();
EndpointDefinition.AddExtraOutputPin(InObject->GetName(), UEdGraphSchema_K2::PC_Object, InObject->GetClass());
UFunction* Function = InObject->GetClass()->FindFunctionByName(FName(InFunctionName));
if (Function == nullptr)
{
const FString Message = FString::Printf(TEXT("Cannot find function %s in class %s"), *(InFunctionName), *(InObject->GetClass()->GetName()));
FFrame::KismetExecutionMessage(*Message, ELogVerbosity::Warning);
return Result;
}
UBlueprintFunctionNodeSpawner* BlueprintFunctionNodeSpawner = UBlueprintFunctionNodeSpawner::Create(Function);
FBlueprintActionMenuItem Action(BlueprintFunctionNodeSpawner);
UK2Node_CustomEvent* NewEventEndpoint = FMovieSceneDirectorBlueprintUtils::CreateEventEndpoint(Blueprint, EndpointDefinition);
NewEventEndpoint->bCallInEditor = bCallInEditor;
Result.EventEndpoint = NewEventEndpoint;
UEdGraphPin* ThenPin = NewEventEndpoint->FindPin(UEdGraphSchema_K2::PN_Then, EGPD_Output);
UEdGraphPin* BoundObjectPin = FMovieSceneDirectorBlueprintUtils::FindCallTargetPin(NewEventEndpoint, EndpointDefinition.PossibleCallTargetClass);
FVector2f NodePosition(NewEventEndpoint->NodePosX + 400.f, NewEventEndpoint->NodePosY);
UEdGraphNode* NewNode = Action.PerformAction(NewEventEndpoint->GetGraph(), BoundObjectPin ? BoundObjectPin : ThenPin, NodePosition);
if (NewNode == nullptr)
{
const FString Message = FString::Printf(TEXT("Failed creating blueprint event node for function %s"), *InFunctionName);
FFrame::KismetExecutionMessage(*Message, ELogVerbosity::Warning);
return Result;
}
// Link execution pins
UEdGraphPin* ExecPin = NewNode->FindPin(UEdGraphSchema_K2::PN_Execute, EGPD_Input);
if (ensure(ThenPin && ExecPin))
{
ThenPin->MakeLinkTo(ExecPin);
}
// Link payload parameters' pins
UK2Node_EditablePinBase* EditableNode = Cast<UK2Node_EditablePinBase>(NewEventEndpoint);
if (EditableNode)
{
for (UEdGraphPin* PayloadPin : NewNode->Pins)
{
if (PayloadPin != BoundObjectPin && PayloadPin->Direction == EGPD_Input && PayloadPin->PinType.PinCategory != UEdGraphSchema_K2::PC_Exec && PayloadPin->LinkedTo.Num() == 0)
{
Result.PayloadNames.Add(PayloadPin->PinName.ToString());
UEdGraphPin* NewPin = EditableNode->CreateUserDefinedPin(PayloadPin->PinName, PayloadPin->PinType, EGPD_Output);
if (NewNode != NewEventEndpoint && NewPin)
{
NewPin->MakeLinkTo(PayloadPin);
}
}
}
}
return Result;
}
#undef LOCTEXT_NAMESPACE // "SequencerTools"