// 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("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(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(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 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("MovieSceneCaptureDialog"); TSharedPtr 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 USequencerToolsFunctionLibrary::GetBoundObjects(UWorld* InWorld, ULevelSequence* InSequence, const TArray& 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 SpecifiedRange = InRange.ToNative(Resolution); Player->SetPlaybackPosition(FMovieSceneSequencePlaybackParams(SpecifiedRange.GetLowerBoundValue().Value, EUpdatePositionMethod::Play)); FMovieSceneSequenceID SequenceId = Player->GetEvaluationState()->FindSequenceId(InSequence); TArray 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 USequencerToolsFunctionLibrary::GetObjectBindings(UWorld* InWorld, ULevelSequence* InSequence, const TArray& InObjects, const FSequencerScriptingRange& InRange) { ALevelSequenceActor* OutActor; FMovieSceneSequencePlaybackSettings Settings; FLevelSequenceCameraSettings CameraSettings; ULevelSequencePlayer* Player = ULevelSequencePlayer::CreateLevelSequencePlayer(InWorld, InSequence, Settings, OutActor); FFrameRate Resolution = InSequence->GetMovieScene()->GetTickResolution(); TRange SpecifiedRange = InRange.ToNative(Resolution); Player->SetPlaybackPosition(FMovieSceneSequencePlaybackParams(SpecifiedRange.GetLowerBoundValue().Value, EUpdatePositionMethod::Play)); TArray BoundObjects; for (UObject* Object : InObjects) { TArray 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({ Object }))); } } Player->Stop(); InWorld->DestroyActor(OutActor); return BoundObjects; } void GatherDescendantBindings(const FMovieSceneBindingProxy& Binding, UMovieSceneSequence* Sequence, TArray& 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 BindingProxies = InParams.Bindings; TArray 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 AllBindings; for (const FMovieSceneBindingProxy& Binding : BindingProxies) { AllBindings.Add(Binding); GatherDescendantBindings(Binding, Sequence, AllBindings); } TArray 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 RuntimeObject : Player->FindBoundObjects(Binding.BindingID, Template)) { if (AActor* Actor = Cast(RuntimeObject.Get())) { for (UActorComponent* Component : Actor->GetComponents()) { if (USkeletalMeshComponent* SkeletalMeshComp = Cast(Component)) { return SkeletalMeshComp; } } } else if (USkeletalMeshComponent* SkeletalMeshComponent = Cast(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(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(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 AddActors(UWorld* World, UMovieSceneSequence* InSequence, UMovieScene* InMovieScene, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID,const TArray >& InActors) { TArray PossessableGuids; if (InMovieScene->IsReadOnly()) { return PossessableGuids; } for (TWeakObjectPtr 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()) { MovieSceneToolHelpers::CreateCameraCutSectionForCamera(InMovieScene, PossessableGuid, 0); } } } } return PossessableGuids; } void ImportFBXCamera(UnFbx::FFbxImporter* FbxImporter, UWorld* World, UMovieSceneSequence* Sequence, UMovieScene* InMovieScene, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID, TMap& InObjectBindingMap, bool bMatchByNameOnly, bool bCreateCameras) { if (bCreateCameras) { TArray AllCameras; MovieSceneToolHelpers::GetCameras(FbxImporter->Scene->GetRootNode(), AllCameras); // Find unmatched cameras TArray 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> 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(SpawnParams); NewCamera->SetActorLabel(*CameraName); } else { FActorSpawnParameters SpawnParams; NewCamera = World->SpawnActor(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 > NewCameras; NewCameras.Add(NewCamera); TArray 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& InBindings, UMovieSceneUserImportFBXSettings* ImportFBXSettings, const FString& ImportFilename, UMovieSceneSequencePlayer* Player) { UMovieScene* MovieScene = Sequence->GetMovieScene(); if (!MovieScene || MovieScene->IsReadOnly()) { return false; } TArray AllBindings; for (const FMovieSceneBindingProxy& Binding : InBindings) { AllBindings.Add(Binding); GatherDescendantBindings(Binding, Sequence, AllBindings); } TMap 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& 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& ControlRigNames, UMovieSceneUserImportFBXControlRigSettings* ImportFBXControlRigSettings, const FString& ImportFilename) { UMovieScene* MovieScene = Sequence->GetMovieScene(); if (!MovieScene || MovieScene->IsReadOnly()) { return false; } bool bValid = false; const TArray& 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& Tracks = Binding.GetTracks(); TArray SelectedControls; for (UMovieSceneTrack* Track : Tracks) { INodeAndChannelMappings* ChannelMapping = Cast(Track); if (ChannelMapping) { TArray* 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& Bindings = MovieScene->GetBindings(); for (const FMovieSceneBinding& Binding : Bindings) { if (Binding.GetName() != ActorWithControlRigTrack) { continue; } for (UMovieSceneTrack* Track : Binding.GetTracks()) { INodeAndChannelMappings* ChannelMapping = Cast(Track); const UMovieSceneSection* Section = Track->GetSectionToKey(); if (!ChannelMapping || !Section) { continue; } TArray 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& 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(), ObjectBindingID); UClass* BoundObjectPinClass = nullptr; if (FMovieScenePossessable* Possessable = MovieScene->FindPossessable(ObjectBindingID)) { BoundObjectPinClass = const_cast(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(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"