// Copyright Epic Games, Inc. All Rights Reserved. #include "SequencerTools.h" #include "SequencerScriptingEditor.h" #include "MovieSceneCapture.h" #include "MovieSceneCaptureDialogModule.h" #include "AutomatedLevelSequenceCapture.h" #include "MovieSceneTimeHelpers.h" #include "UObject/Stack.h" #include "UObject/Package.h" #include "LevelSequenceActor.h" #include "LevelSequencePlayer.h" #include "FbxExporter.h" #include "FbxImporter.h" #include "MovieSceneToolsUserSettings.h" #include "MovieSceneToolHelpers.h" #include "MovieSceneEventUtils.h" #include "MovieSceneSequenceEditor.h" #include "Sections/MovieSceneEventSectionBase.h" #include "CineCameraActor.h" #include "CineCameraComponent.h" #include "MovieSceneCommonHelpers.h" #include "Components/SkeletalMeshComponent.h" #include "Animation/AnimSequenceBase.h" #include "ScopedTransaction.h" #include "Exporters/AnimSeqExportOption.h" #include "K2Node_CustomEvent.h" #include "BlueprintFunctionNodeSpawner.h" #include "BlueprintActionMenuItem.h" #include "EdGraphSchema_K2.h" #include "AssetRegistry/AssetData.h" #include "LevelSequenceAnimSequenceLink.h" #include "AnimSequenceLevelSequenceLink.h" #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; } if (IsRenderingMovie()) { FFrame::KismetExecutionMessage(TEXT("Capture already in progress."), ELogVerbosity::Error); return false; } // 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); Player->Initialize(InSequence, InWorld->PersistentLevel, Settings, CameraSettings); Player->State.AssignSequence(MovieSceneSequenceID::Root, *InSequence, *Player); // 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->State.FindSequenceId(InSequence); TArray BoundObjects; for (FSequencerBindingProxy 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); Player->Initialize(InSequence, InWorld->PersistentLevel, Settings, CameraSettings); Player->State.AssignSequence(MovieSceneSequenceID::Root, *InSequence, *Player); 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); FSequencerBindingProxy Binding(ObjectBinding.GetGuid(), Player->State.FindSequence(SequenceID)); BoundObjects.Add(FSequencerBoundObjects(Binding, TArray({ Object }))); } } Player->Stop(); InWorld->DestroyActor(OutActor); return BoundObjects; } void GatherDescendantBindings(const FSequencerBindingProxy& 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) { FSequencerBindingProxy ChildBinding(Possessable.GetGuid(), Sequence); AllBindings.Add(ChildBinding); GatherDescendantBindings(ChildBinding, Sequence, AllBindings); } } } bool ExportFBXInternal(UWorld* World, UMovieSceneSequence* Sequence, const TArray& InBindings, const TArray& MasterTracks, UFbxExportOption* OverrideOptions, const FString& InFBXFileName, UMovieSceneSequencePlayer* Player) { UnFbx::FFbxExporter* Exporter = UnFbx::FFbxExporter::GetInstance(); //Show the fbx export dialog options Exporter->SetExportOptionsOverride(OverrideOptions); UMovieScene* MovieScene = Sequence->GetMovieScene(); TArray AllBindings; for (const FSequencerBindingProxy& Binding : InBindings) { AllBindings.Add(Binding); GatherDescendantBindings(Binding, Sequence, AllBindings); } TArray Bindings; for (const FSequencerBindingProxy& Proxy : AllBindings) { if (Proxy.Sequence == Sequence) { Bindings.Add(Proxy.BindingID); } } INodeNameAdapter NodeNameAdapter; Player->State.AssignSequence(MovieSceneSequenceID::Root, *Sequence, *Player); FMovieSceneSequenceIDRef Template = MovieSceneSequenceID::Root; bool bDidExport = false; FMovieSceneSequenceTransform RootToLocalTransform; { FSpawnableRestoreState SpawnableRestoreState(MovieScene); if (SpawnableRestoreState.bWasChanged) { // Evaluate at the beginning of the subscene time to ensure that spawnables are created before export Player->SetPlaybackPosition(FMovieSceneSequencePlaybackParams(UE::MovieScene::DiscreteInclusiveLower(MovieScene->GetPlaybackRange()).Value, EUpdatePositionMethod::Play)); } bDidExport = MovieSceneToolHelpers::ExportFBX(World, MovieScene, Player, Bindings, MasterTracks, NodeNameAdapter, Template, InFBXFileName, RootToLocalTransform); } Player->Stop(); Exporter->SetExportOptionsOverride(nullptr); return bDidExport; } bool USequencerToolsFunctionLibrary::ExportLevelSequenceFBX(UWorld* World, ULevelSequence* Sequence, const TArray& InBindings, const TArray& InMasterTracks, UFbxExportOption* OverrideOptions, const FString& InFBXFileName) { ALevelSequenceActor* OutActor; FMovieSceneSequencePlaybackSettings Settings; FLevelSequenceCameraSettings CameraSettings; ULevelSequencePlayer* Player = ULevelSequencePlayer::CreateLevelSequencePlayer(World, Sequence, Settings, OutActor); Player->Initialize(Sequence, World->PersistentLevel, Settings, CameraSettings); bool bSuccess = ExportFBXInternal(World, Sequence, InBindings, InMasterTracks, OverrideOptions, InFBXFileName, Player); World->DestroyActor(OutActor); return bSuccess; } static USkeletalMeshComponent* GetSkelMeshComponent(IMovieScenePlayer* Player, const FSequencerBindingProxy& 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->SkeletalMesh) { return SkeletalMeshComponent; } } } return nullptr; } bool USequencerToolsFunctionLibrary::ExportAnimSequence(UWorld* World, ULevelSequence* Sequence, UAnimSequence* AnimSequence, UAnimSeqExportOption* ExportOptions,const FSequencerBindingProxy& Binding, bool bCreateLink) { UMovieScene* MovieScene = Sequence->GetMovieScene(); if (Binding.Sequence != Sequence || !AnimSequence) { return false; } INodeNameAdapter NodeNameAdapter; ALevelSequenceActor* OutActor; FMovieSceneSequencePlaybackSettings Settings; FLevelSequenceCameraSettings CameraSettings; FMovieSceneSequenceIDRef Template = MovieSceneSequenceID::Root; FMovieSceneSequenceTransform RootToLocalTransform; ULevelSequencePlayer* Player = ULevelSequencePlayer::CreateLevelSequencePlayer(World, Sequence, Settings, OutActor); Player->Initialize(Sequence, World->PersistentLevel, Settings, CameraSettings); Player->State.AssignSequence(MovieSceneSequenceID::Root, *Sequence, *Player); bool bResult = false; { FSpawnableRestoreState SpawnableRestoreState(MovieScene); if (SpawnableRestoreState.bWasChanged) { // Evaluate at the beginning of the subscene time to ensure that spawnables are created before export Player->SetPlaybackPosition(FMovieSceneSequencePlaybackParams(UE::MovieScene::DiscreteInclusiveLower(MovieScene->GetPlaybackRange()).Value, EUpdatePositionMethod::Play)); } USkeletalMeshComponent* SkeletalMeshComp = GetSkelMeshComponent(Player, Binding); if (SkeletalMeshComp && SkeletalMeshComp->SkeletalMesh && SkeletalMeshComp->SkeletalMesh->GetSkeleton()) { AnimSequence->SetSkeleton(SkeletalMeshComp->SkeletalMesh->GetSkeleton()); bResult = MovieSceneToolHelpers::ExportToAnimSequence(AnimSequence,ExportOptions, MovieScene, Player, SkeletalMeshComp, Template, RootToLocalTransform); } } Player->Stop(); World->DestroyActor(OutActor); //create the link to the anim sequence if (bResult && bCreateLink) { //create from anim sequence back to level sequence 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; } //create from level sequence to anim sequence, trickier since we can have multiples here. 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.SkelTrackGuid == Binding.BindingID) { bAddItem = false; UAnimSequence* OtherAnimSequence = LevelAnimLinkItem.ResolveAnimSequence(); if (OtherAnimSequence != AnimSequence) { if (IInterface_AssetUserData* OtherAnimAssetUserData = Cast< IInterface_AssetUserData >(OtherAnimSequence)) { UAnimSequenceLevelSequenceLink* OtherAnimLevelLink = OtherAnimAssetUserData->GetAssetUserData< UAnimSequenceLevelSequenceLink >(); if (OtherAnimLevelLink) { 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.bRecordInWorldSpace = ExportOptions->bRecordInWorldSpace; 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.bExportMaterialCurves = ExportOptions->bExportMaterialCurves; LevelAnimLinkItem.bExportTransforms = ExportOptions->bExportTransforms; LevelAnimLinkItem.bRecordInWorldSpace = ExportOptions->bRecordInWorldSpace; LevelAnimLink->AnimSequenceLinks.Add(LevelAnimLinkItem); AssetUserDataInterface->AddAssetUserData(LevelAnimLink); } } } return bResult; } 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); //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 FSequencerBindingProxy& Binding : InBindings) { AllBindings.Add(Binding); GatherDescendantBindings(Binding, Sequence, AllBindings); } TMap ObjectBindingMap; for (const FSequencerBindingProxy& 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; Player->State.AssignSequence(MovieSceneSequenceID::Root, *Sequence, *Player); UnFbx::FFbxImporter* FbxImporter = UnFbx::FFbxImporter::GetInstance(); bool bResult = false; FScopedTransaction ImportFBXTransaction(NSLOCTEXT("Sequencer", "ImportFBX", "Import FBX")); { FSpawnableRestoreState SpawnableRestoreState(MovieScene); if (SpawnableRestoreState.bWasChanged) { // Evaluate at the beginning of the subscene time to ensure that spawnables are created before export Player->SetPlaybackPosition(FMovieSceneSequencePlaybackParams(UE::MovieScene::DiscreteInclusiveLower(MovieScene->GetPlaybackRange()).Value, 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* OutActor; FMovieSceneSequencePlaybackSettings Settings; FLevelSequenceCameraSettings CameraSettings; ULevelSequencePlayer* Player = ULevelSequencePlayer::CreateLevelSequencePlayer(World, Sequence, Settings, OutActor); Player->Initialize(Sequence, World->GetLevel(0), Settings, CameraSettings); bool bSuccess = ImportFBXInternal(World, Sequence, InBindings, ImportFBXSettings, ImportFilename, Player); 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); Player->Initialize(Sequence, World->GetLevel(0), Settings, CameraSettings); Player->State.AssignSequence(MovieSceneSequenceID::Root, *Sequence, *Player); 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; } 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 = FMovieSceneEventUtils::FindBoundObjectPin(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(); FMovieSceneEventEndpointParameters Params; Params.SanitizedObjectName = InObject->GetName(); Params.SanitizedEventName = InFunctionName; Params.BoundObjectPinClass = 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 = FMovieSceneEventUtils::CreateUserFacingEvent(Blueprint, Params); NewEventEndpoint->bCallInEditor = bCallInEditor; Result.EventEndpoint = NewEventEndpoint; UEdGraphPin* ThenPin = NewEventEndpoint->FindPin(UEdGraphSchema_K2::PN_Then, EGPD_Output); UEdGraphPin* BoundObjectPin = FMovieSceneEventUtils::FindBoundObjectPin(NewEventEndpoint, Params.BoundObjectPinClass); FVector2D 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"