// Copyright Epic Games, Inc. All Rights Reserved. #include "SequencerUtilities.h" #include "AnimatedRange.h" #include "CineCameraActor.h" #include "CameraRig_Rail.h" #include "CameraRig_Crane.h" #include "Components/SplineComponent.h" #include "Containers/ArrayBuilder.h" #include "Editor/EditorEngine.h" #include "Engine/Selection.h" #include "EngineUtils.h" #include "Exporters/Exporter.h" #include "Factories.h" #include "Misc/Attribute.h" #include "Misc/Paths.h" #include "Layout/Margin.h" #include "LevelEditorViewport.h" #include "Fonts/SlateFontInfo.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/SBoxPanel.h" #include "Widgets/Images/SImage.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/Input/SButton.h" #include "Styling/CoreStyle.h" #include "Styling/AppStyle.h" #include "MovieSceneCopyableBinding.h" #include "MovieSceneCopyableTrack.h" #include "EntitySystem/MovieSceneBlenderSystem.h" #include "EntitySystem/IMovieSceneBlenderSystemSupport.h" #include "MovieSceneFolder.h" #include "MovieSceneNameableTrack.h" #include "MovieSceneSection.h" #include "MovieSceneSpawnRegister.h" #include "MovieSceneTimeHelpers.h" #include "MovieSceneToolHelpers.h" #include "MovieSceneTrack.h" #include "Tracks/MovieScene3DAttachTrack.h" #include "Tracks/MovieSceneCameraCutTrack.h" #include "Tracks/MovieSceneSpawnTrack.h" #include "Compilation/MovieSceneCompiledDataManager.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Styling/SlateIconFinder.h" #include "ISequencerTrackEditor.h" #include "ISequencer.h" #include "Sequencer.h" #include "SequencerLog.h" #include "SequencerNodeTree.h" #include "MovieSceneBindingProxy.h" #include "ScopedTransaction.h" #include "UnrealEdGlobals.h" #include "UnrealExporter.h" #include "UObject/Package.h" #include "AssetRegistry/AssetRegistryModule.h" #include "ISequencerObjectSchema.h" #include "FileHelpers.h" #include "HAL/PlatformApplicationMisc.h" #include "LevelSequence.h" #include "MVVM/Extensions/IObjectBindingExtension.h" #include "MVVM/Extensions/IOutlinerExtension.h" #include "MVVM/ViewModels/TrackModel.h" #include "MVVM/ViewModels/SectionModel.h" #include "MVVM/Views/ViewUtilities.h" #include "Framework/Notifications/NotificationManager.h" #include "Widgets/Notifications/SNotificationList.h" #include "Bindings/MovieSceneCustomBinding.h" #include "Bindings/MovieSceneSpawnableBinding.h" #include "Bindings/MovieSceneReplaceableBinding.h" #include "Bindings/MovieSceneSpawnableActorBinding.h" #include "Bindings/MovieSceneReplaceableActorBinding.h" #include "ActorFactories/ActorFactory.h" #include "Tracks/MovieSceneBindingLifetimeTrack.h" #include "ClassViewerModule.h" #include "ClassViewerFilter.h" #include "ClassIconFinder.h" #include "Styling/SlateIconFinder.h" #include "Framework/Application/SlateApplication.h" #include "SequencerCommands.h" #include "MVVM/Selection/Selection.h" #include "UnrealEdGlobals.h" #include "Misc/FeedbackContext.h" #include "Variants/MovieSceneTimeWarpVariant.h" #include "Variants/MovieSceneTimeWarpGetter.h" #include "UObject/UObjectIterator.h" #include "ObjectTools.h" #define LOCTEXT_NAMESPACE "FSequencerUtilities" static void ResetCopiedTracksFlags(UMovieSceneTrack* Track) { Track->ClearFlags(RF_Transient); ForEachObjectWithOuter(Track, [](UObject* InObject) { InObject->ClearFlags(RF_Transient); }); for (UMovieSceneSection* Section : Track->GetAllSections()) { Section->PostPaste(); } } TSharedRef FSequencerUtilities::MakeAddButton(FText HoverText, FOnGetContent MenuContent, const TAttribute& HoverState, TWeakPtr InSequencer) { TAttribute IsEnabled = MakeAttributeLambda([InSequencer]() -> bool { return InSequencer.IsValid() ? !InSequencer.Pin()->IsReadOnly() : false; }); return UE::Sequencer::MakeAddButton(HoverText, MenuContent, HoverState, IsEnabled); } TSharedRef FSequencerUtilities::MakeAddButton(FText HoverText, FOnClicked OnClicked, const TAttribute& HoverState, TWeakPtr InSequencer) { TAttribute IsEnabled = MakeAttributeLambda([InSequencer]() -> bool { return InSequencer.IsValid() ? !InSequencer.Pin()->IsReadOnly() : false; }); return UE::Sequencer::MakeAddButton(HoverText, OnClicked, HoverState, IsEnabled); } void FSequencerUtilities::MakeTimeWarpMenuEntry(FMenuBuilder& MenuBuilder, UE::Sequencer::TWeakViewModelPtr WeakTrackModel) { using namespace UE::Sequencer; TViewModelPtr TrackModel = WeakTrackModel.Pin(); if (!TrackModel) { return; } TOptional CommonClass; for (const TViewModelPtr& SectionModel : TrackModel->GetSectionModels().IterateSubList()) { UMovieSceneSection* Section = SectionModel->GetSection(); if (!Section) { continue; } FMovieSceneTimeWarpVariant* Variant = Section->GetTimeWarp(); UMovieSceneTimeWarpGetter* Getter = Variant && Variant->GetType() == EMovieSceneTimeWarpType::Custom ? Variant->AsCustom() : nullptr; if (Getter) { if (!CommonClass) { CommonClass = Getter->GetClass(); } else if (CommonClass.GetValue() != Getter->GetClass()) { CommonClass = nullptr; } } } FText TimeWarpLabel = CommonClass.IsSet() ? LOCTEXT("ReplaceTimeWarp_Label", "Replace Time Warp") : LOCTEXT("AddTimeWarp_Label", "Add Time Warp"); FText TimeWarpToolTip = CommonClass.IsSet() ? LOCTEXT("ReplaceTimeWarp_ToolTip", "Replaces the Time Warp implementation with a different kind") : LOCTEXT("AddTimeWarp_ToolTip", "Add Time Warp"); MenuBuilder.AddSubMenu( TimeWarpLabel, TimeWarpToolTip, FNewMenuDelegate::CreateStatic(PopulateTimeWarpChannelSubMenu, WeakTrackModel) ); } void FSequencerUtilities::PopulateTimeWarpSubMenu(FMenuBuilder& MenuBuilder, TFunction)> OnTimeWarpPicked) { using namespace UE::Sequencer; FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); TSet AllTimeWarpClasses; { FTopLevelAssetPath TargetClassPath(UMovieSceneTimeWarpGetter::StaticClass()); AssetRegistryModule.Get().GetDerivedClassNames({ TargetClassPath }, TSet(), AllTimeWarpClasses); AllTimeWarpClasses.Remove(TargetClassPath); } if (AllTimeWarpClasses.Num() == 0) { MenuBuilder.AddWidget(SNew(STextBlock).Text(LOCTEXT("NoTimeWarpTypesError", "No Time Warp implementations found")), FText(), true); return; } auto HandleTimeWarpSelection = [OnTimeWarpPicked](FTopLevelAssetPath ClassPath) { UClass* Class = FSoftClassPath(ClassPath.ToString()).TryLoadClass(); if (Class) { OnTimeWarpPicked(Class); } }; MenuBuilder.BeginSection(NAME_None, LOCTEXT("TimeWarpCategoryLabel", "Time Warp Types:")); for (const FTopLevelAssetPath& ClassPath : AllTimeWarpClasses) { FAssetData AssetData = AssetRegistryModule.Get().GetAssetByObjectPath(FSoftObjectPath(ClassPath.ToString())); const UClass* IconClass = FClassIconFinder::GetIconClassForAssetData(AssetData); const UClass* Class = Cast(AssetData.FastGetAsset()); if (!Class->HasMetaData("Hidden")) { MenuBuilder.AddMenuEntry( Class ? Class->GetDisplayNameText() : FText::FromName(ClassPath.GetAssetName()), Class ? Class->GetToolTipText() : FText(), FSlateIconFinder::FindIconForClass(IconClass), FUIAction(FExecuteAction::CreateLambda(HandleTimeWarpSelection, ClassPath)) ); } } MenuBuilder.EndSection(); } void FSequencerUtilities::PopulateTimeWarpChannelSubMenu(FMenuBuilder& MenuBuilder, UE::Sequencer::TWeakViewModelPtr WeakTrackModel) { using namespace UE::Sequencer; auto HandleTimeWarpSelection = [WeakTrackModel](TSubclassOf Class) { TViewModelPtr TrackModel = WeakTrackModel.Pin(); if (!TrackModel) { return; } FScopedTransaction Transaction(LOCTEXT("ChangeTimeWarpType", "Changed Time Warp type")); for (const TViewModelPtr& SectionModel : TrackModel->GetSectionModels().IterateSubList()) { UMovieSceneSection* Section = SectionModel->GetSection(); FMovieSceneTimeWarpVariant* Variant = Section ? Section->GetTimeWarp() : nullptr; if (Variant) { Section->Modify(); UMovieSceneTimeWarpGetter* Getter = NewObject(Section, Class.Get(), NAME_None, RF_Transactional); Getter->InitializeDefaults(); Variant->Set(Getter); Section->InvalidateChannelProxy(); TViewModelPtr Outliner = TrackModel.ImplicitCast(); if (Outliner && !Outliner->IsExpanded()) { Outliner->SetExpansion(true); } } } }; PopulateTimeWarpSubMenu(MenuBuilder, HandleTimeWarpSelection); } void FSequencerUtilities::CreateNewSection(UMovieSceneTrack* InTrack, TWeakPtr InSequencer, int32 InRowIndex, EMovieSceneBlendType InBlendType) { TSharedPtr Sequencer = InSequencer.Pin(); if (!Sequencer.IsValid()) { return; } FScopedTransaction Transaction(LOCTEXT("AddSectionTransactionText", "Add Section")); if (UMovieSceneSection* NewSection = InTrack->CreateNewSection()) { int32 OverlapPriority = 0; for (UMovieSceneSection* Section : InTrack->GetAllSections()) { OverlapPriority = FMath::Max(Section->GetOverlapPriority() + 1, OverlapPriority); // Move existing sections on the same row or beyond so that they don't overlap with the new section if (Section != NewSection && Section->GetRowIndex() >= InRowIndex) { Section->SetRowIndex(Section->GetRowIndex() + 1); } } InTrack->Modify(); if (Sequencer->GetInfiniteKeyAreas()) { NewSection->SetRange(TRange::All()); } NewSection->SetOverlapPriority(OverlapPriority); NewSection->SetRowIndex(InRowIndex); NewSection->SetBlendType(InBlendType); InTrack->AddSection(*NewSection); InTrack->UpdateEasing(); Sequencer->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded); Sequencer->EmptySelection(); Sequencer->SelectSection(NewSection); Sequencer->ThrobSectionSelection(); } else { Transaction.Cancel(); } } void FSequencerUtilities::PopulateMenu_CreateNewSection(FMenuBuilder& MenuBuilder, int32 RowIndex, UMovieSceneTrack* Track, TWeakPtr InSequencer) { if (!Track) { return; } auto CreateNewSection = [Track, InSequencer, RowIndex](EMovieSceneBlendType BlendType) { TSharedPtr Sequencer = InSequencer.IsValid() ? InSequencer.Pin() : nullptr; if (!Sequencer) { return; } FQualifiedFrameTime CurrentTime = Sequencer->GetLocalTime(); FFrameNumber PlaybackEnd = UE::MovieScene::DiscreteExclusiveUpper(Sequencer->GetFocusedMovieSceneSequence()->GetMovieScene()->GetPlaybackRange()); int32 SpecifiedRowIndex = RowIndex; FScopedTransaction Transaction(LOCTEXT("AddSectionTransactionText", "Add Section")); if (UMovieSceneSection* NewSection = Track->CreateNewSection()) { int32 OverlapPriority = 0; TMap NewToOldRowIndices; //if creating with an override force the row index to be last if (Track->GetSupportedBlendTypes().Contains(EMovieSceneBlendType::Override)) { SpecifiedRowIndex = Track->GetMaxRowIndex() + 1; } for (UMovieSceneSection* Section : Track->GetAllSections()) { OverlapPriority = FMath::Max(Section->GetOverlapPriority() + 1, OverlapPriority); // Move existing sections on the same row or beyond so that they don't overlap with the new section if (Section != NewSection && Section->GetRowIndex() >= SpecifiedRowIndex) { int32 OldRowIndex = Section->GetRowIndex(); int32 NewRowIndex = Section->GetRowIndex() + 1; NewToOldRowIndices.FindOrAdd(NewRowIndex, OldRowIndex); Section->Modify(); Section->SetRowIndex(NewRowIndex); } } Track->Modify(); Track->OnRowIndicesChanged(NewToOldRowIndices); if (Sequencer->GetInfiniteKeyAreas() && NewSection->GetSupportsInfiniteRange()) { NewSection->SetRange(TRange::All()); } else { FFrameNumber NewSectionRangeEnd = PlaybackEnd; if (PlaybackEnd <= CurrentTime.Time.FrameNumber) { const FAnimatedRange ViewRange = Sequencer->GetViewRange(); const FFrameRate TickResolution = Sequencer->GetFocusedTickResolution(); NewSectionRangeEnd = (ViewRange.GetUpperBoundValue() * TickResolution).FloorToFrame(); } NewSection->SetRange(TRange(CurrentTime.Time.FrameNumber, NewSectionRangeEnd)); } NewSection->SetOverlapPriority(OverlapPriority); NewSection->SetRowIndex(SpecifiedRowIndex); NewSection->SetBlendType(BlendType); Track->AddSection(*NewSection); Track->UpdateEasing(); if (UMovieSceneNameableTrack* NameableTrack = Cast(Track)) { NameableTrack->SetTrackRowDisplayName(FText::GetEmpty(), SpecifiedRowIndex); } Sequencer->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded); } else { Transaction.Cancel(); } }; FText NameOverride = Track->GetSupportedBlendTypes().Num() == 1 ? LOCTEXT("AddSectionText", "Add New Section") : FText(); FText TooltipOverride = Track->GetSupportedBlendTypes().Num() == 1 ? LOCTEXT("AddSectionToolTip", "Adds a new section") : FText(); const UEnum* MovieSceneBlendType = FindObjectChecked(nullptr, TEXT("/Script/MovieScene.EMovieSceneBlendType")); for (EMovieSceneBlendType BlendType : Track->GetSupportedBlendTypes()) { FText DisplayName = MovieSceneBlendType->GetDisplayNameTextByValue((int64)BlendType); FName EnumValueName = MovieSceneBlendType->GetNameByValue((int64)BlendType); MenuBuilder.AddMenuEntry( NameOverride.IsEmpty() ? DisplayName : NameOverride, TooltipOverride.IsEmpty() ? FText::Format(LOCTEXT("AddSectionFormatToolTip", "Adds a new {0} section"), DisplayName) : TooltipOverride, FSlateIcon(FAppStyle::GetAppStyleSetName(), EnumValueName), FUIAction( FExecuteAction::CreateLambda(CreateNewSection, BlendType), FCanExecuteAction::CreateLambda([InSequencer] { return InSequencer.IsValid() && !InSequencer.Pin()->IsReadOnly(); }) ) ); } } void FSequencerUtilities::PopulateMenu_BlenderSubMenu(FMenuBuilder& MenuBuilder, UMovieSceneTrack* Track, TWeakPtr InSequencer) { IMovieSceneBlenderSystemSupport* BlenderSystemSupport = Cast(Track); // Shouldn't have been called with a track that does not implement this interface check(BlenderSystemSupport); TArray> BlenderTypes; BlenderSystemSupport->GetSupportedBlenderSystems(BlenderTypes); // Ensure no nulls BlenderTypes.Remove(TSubclassOf()); // Sort alphabetically Algo::Sort(BlenderTypes, [](TSubclassOf A, TSubclassOf B) { return A->GetDisplayNameText().CompareTo(B->GetDisplayNameText()) < 0; } ); MenuBuilder.BeginSection(TEXT("Blending"), LOCTEXT("BlendingMenuSection", "Blending")); for (TSubclassOf SystemClass : BlenderTypes) { MenuBuilder.AddMenuEntry( SystemClass->GetDisplayNameText(), SystemClass->GetToolTipText(), FSlateIconFinder::FindIconForClass(SystemClass.Get()), FUIAction( FExecuteAction::CreateLambda([Track, BlenderSystemSupport, SystemClass]{ FScopedTransaction Transaction(FText::Format(LOCTEXT("ChangeBlenderType", "Change blender to '{0}'"), SystemClass.Get()->GetDisplayNameText())); Track->Modify(); BlenderSystemSupport->SetBlenderSystem(SystemClass); }), FCanExecuteAction::CreateLambda([InSequencer] { return InSequencer.IsValid() && !InSequencer.Pin()->IsReadOnly(); }), FIsActionChecked::CreateLambda([BlenderSystemSupport, SystemClass] { return BlenderSystemSupport->GetBlenderSystem() == SystemClass; })), NAME_None, EUserInterfaceActionType::RadioButton ); } MenuBuilder.EndSection(); } void FSequencerUtilities::PopulateMenu_SetBlendType(FMenuBuilder& MenuBuilder, UMovieSceneSection* Section, TWeakPtr InSequencer) { PopulateMenu_SetBlendType(MenuBuilder, TArray>({ Section }), InSequencer); } void FSequencerUtilities::PopulateMenu_SetBlendType(FMenuBuilder& MenuBuilder, const TArray>& InSections, TWeakPtr InSequencer) { using namespace UE::MovieScene; using namespace UE::Sequencer; auto Execute = [InSections, InSequencer](EMovieSceneBlendType BlendType) { FScopedTransaction Transaction(LOCTEXT("SetBlendType", "Set Blend Type")); for (TWeakObjectPtr WeakSection : InSections) { if (UMovieSceneSection* Section = WeakSection.Get()) { Section->Modify(); Section->SetBlendType(BlendType); } } TSharedPtr Sequencer = StaticCastSharedPtr(InSequencer.Pin()); if (Sequencer.IsValid()) { // If the blend type is changed to additive or relative, restore the state of the objects boud to this section before evaluating again. // This allows the additive or relative to evaluate based on the initial values of the object, rather than the current animated values. if (BlendType == EMovieSceneBlendType::Additive || BlendType == EMovieSceneBlendType::Relative) { TSet ObjectsToRestore; TSharedRef SequencerNodeTree = Sequencer->GetNodeTree(); for (TWeakObjectPtr WeakSection : InSections) { if (UMovieSceneSection* Section = WeakSection.Get()) { TSharedPtr SectionHandle = SequencerNodeTree->GetSectionModel(Section); if (!SectionHandle) { continue; } TSharedPtr ParentObjectBindingNode = SectionHandle->FindAncestorOfType(); if (!ParentObjectBindingNode.IsValid()) { continue; } for (TWeakObjectPtr<> BoundObject : Sequencer->FindObjectsInCurrentSequence(ParentObjectBindingNode->GetObjectGuid())) { if (AActor* BoundActor = Cast(BoundObject)) { for (UActorComponent* Component : TInlineComponentArray(BoundActor)) { if (Component) { ObjectsToRestore.Add(Component); } } } ObjectsToRestore.Add(BoundObject.Get()); } } } for (UObject* ObjectToRestore : ObjectsToRestore) { Sequencer->PreAnimatedState.RestorePreAnimatedState(*ObjectToRestore); } } Sequencer->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::TrackValueChanged); } }; const UEnum* MovieSceneBlendType = FindObjectChecked(nullptr, TEXT("/Script/MovieScene.EMovieSceneBlendType")); for (int32 NameIndex = 0; NameIndex < MovieSceneBlendType->NumEnums() - 1; ++NameIndex) { EMovieSceneBlendType BlendType = (EMovieSceneBlendType)MovieSceneBlendType->GetValueByIndex(NameIndex); // Include this if any section supports it bool bAnySupported = false; for (TWeakObjectPtr WeakSection : InSections) { UMovieSceneSection* Section = WeakSection.Get(); if (Section && Section->GetSupportedBlendTypes().Contains(BlendType)) { bAnySupported = true; break; } } if (!bAnySupported) { continue; } FName EnumValueName = MovieSceneBlendType->GetNameByIndex(NameIndex); MenuBuilder.AddMenuEntry( MovieSceneBlendType->GetDisplayNameTextByIndex(NameIndex), MovieSceneBlendType->GetToolTipTextByIndex(NameIndex), FSlateIcon(FAppStyle::GetAppStyleSetName(), EnumValueName), FUIAction( FExecuteAction::CreateLambda(Execute, BlendType), FCanExecuteAction::CreateLambda([InSequencer] { return InSequencer.IsValid() && !InSequencer.Pin()->IsReadOnly(); }), FIsActionChecked::CreateLambda([InSections, BlendType] { int32 NumActiveBlendTypes = 0; for (TWeakObjectPtr WeakSection : InSections) { UMovieSceneSection* Section = WeakSection.Get(); if (Section && Section->GetBlendType() == BlendType) { ++NumActiveBlendTypes; } } return NumActiveBlendTypes == InSections.Num(); })), NAME_None, EUserInterfaceActionType::RadioButton ); } } FName FSequencerUtilities::GetUniqueName( FName CandidateName, const TArray& ExistingNames ) { if (!ExistingNames.Contains(CandidateName)) { return CandidateName; } FString CandidateNameString = CandidateName.ToString(); FString BaseNameString = CandidateNameString; if ( CandidateNameString.Len() >= 3 && CandidateNameString.Right(3).IsNumeric() ) { BaseNameString = CandidateNameString.Left( CandidateNameString.Len() - 3 ); } FName UniqueName = FName(*BaseNameString); int32 NameIndex = 1; while ( ExistingNames.Contains( UniqueName ) ) { UniqueName = FName( *FString::Printf(TEXT("%s%i"), *BaseNameString, NameIndex ) ); NameIndex++; } return UniqueName; } TArray FSequencerUtilities::GetAssociatedLevelSequenceMapPackages(const ULevelSequence* InSequence) { if (!InSequence) { return TArray(); } const FName LSMapPathName = *InSequence->GetOutermost()->GetPathName(); return GetAssociatedLevelSequenceMapPackages(LSMapPathName); } TArray FSequencerUtilities::GetAssociatedLevelSequenceMapPackages(FName LevelSequencePackageName) { FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); TArray AssociatedMaps; TArray AssociatedAssets; // This makes the assumption these functions will append the array, and not clear it. AssetRegistryModule.Get().GetReferencers(LevelSequencePackageName, AssociatedAssets); AssetRegistryModule.Get().GetDependencies(LevelSequencePackageName, AssociatedAssets); for (FAssetIdentifier& AssociatedMap : AssociatedAssets) { FString MapFilePath; FString LevelPath = AssociatedMap.PackageName.ToString(); if (FEditorFileUtils::IsMapPackageAsset(LevelPath, MapFilePath)) { AssociatedMaps.AddUnique(LevelPath); } } AssociatedMaps.Sort([](const FString& One, const FString& Two) { return FPaths::GetBaseFilename(One) < FPaths::GetBaseFilename(Two); }); return AssociatedMaps; } /** Recurses through a folder to replace converted GUID with new GUID */ bool UpdateFolderBindingID(UMovieSceneFolder* Folder, FGuid OldGuid, FGuid NewGuid) { for (FGuid ChildGuid : Folder->GetChildObjectBindings()) { if (ChildGuid == OldGuid) { Folder->AddChildObjectBinding(NewGuid); Folder->RemoveChildObjectBinding(OldGuid); return true; } } for (UMovieSceneFolder* ChildFolder : Folder->GetChildFolders()) { if (UpdateFolderBindingID(ChildFolder, OldGuid, NewGuid)) { return true; } } return false; } /** Expands Possessables with multiple bindings into individual Possessables for each binding */ TArray ExpandMultiplePossessableBindings(TSharedRef Sequencer, FGuid PossessableGuid) { TArray NewPossessableGuids; UMovieSceneSequence* Sequence = Sequencer->GetFocusedMovieSceneSequence(); if (!Sequence) { return NewPossessableGuids; } UMovieScene* MovieScene = Sequence->GetMovieScene(); if (!MovieScene) { return NewPossessableGuids; } // Create a copy of the TArrayView of bound objects, as the underlying array will get destroyed TArray> FoundObjects; for (TWeakObjectPtr<> BoundObject : Sequencer->FindBoundObjects(PossessableGuid, Sequencer->GetFocusedTemplateID())) { FoundObjects.Insert(BoundObject, 0); } if (FoundObjects.Num() < 2) { // If less than two objects, nothing to do, return the same Guid NewPossessableGuids.Add(PossessableGuid); return NewPossessableGuids; } Sequence->Modify(); MovieScene->Modify(); FMovieSceneBinding* PossessableBinding = MovieScene->FindBinding(PossessableGuid); // First gather the children TArray ChildPossessableGuids; for (int32 Index = 0; Index < MovieScene->GetPossessableCount(); ++Index) { FMovieScenePossessable& Possessable = MovieScene->GetPossessable(Index); if (Possessable.GetParent() == PossessableGuid) { ChildPossessableGuids.Add(Possessable.GetGuid()); } } TArray Tracks = PossessableBinding->StealTracks(MovieScene); // Remove binding to stop any children from claiming the old guid as their parent if (MovieScene->RemovePossessable(PossessableGuid)) { Sequence->UnbindPossessableObjects(PossessableGuid); } for (TWeakObjectPtr<> FoundObjectPtr : FoundObjects) { UObject* FoundObject = FoundObjectPtr.Get(); if (!FoundObject) { continue; } FoundObject->Modify(); UObject* BindingContext = Sequencer->GetPlaybackContext(); // Find this object's parent object, if it has one. UObject* ParentObject = Sequence->GetParentObject(FoundObject); if (ParentObject) { BindingContext = ParentObject; } // Create a new Possessable for this object AActor* PossessedActor = Cast(FoundObject); const FGuid NewPossessableGuid = MovieScene->AddPossessable(PossessedActor != nullptr ? PossessedActor->GetActorLabel() : FoundObject->GetName(), FoundObject->GetClass()); FMovieScenePossessable* NewPossessable = MovieScene->FindPossessable(NewPossessableGuid); if (NewPossessable) { FMovieSceneBinding* NewPossessableBinding = MovieScene->FindBinding(NewPossessableGuid); if (ParentObject) { FGuid ParentGuid = Sequencer->FindObjectId(*ParentObject, Sequencer->GetFocusedTemplateID()); NewPossessable->SetParent(ParentGuid, MovieScene); } if (!NewPossessable->BindSpawnableObject(Sequencer->GetFocusedTemplateID(), FoundObject, Sequencer->GetSharedPlaybackState())) { Sequence->BindPossessableObject(NewPossessableGuid, *FoundObject, BindingContext); NewPossessable->FixupPossessedObjectClass(Sequence, BindingContext); } NewPossessableGuids.Add(NewPossessableGuid); // Create copies of the tracks for (UMovieSceneTrack* Track : Tracks) { UMovieSceneTrack* DuplicatedTrack = Cast(StaticDuplicateObject(Track, MovieScene)); NewPossessableBinding->AddTrack(*DuplicatedTrack, MovieScene); } } } // Finally, recurse in to any children for (FGuid ChildPossessableGuid : ChildPossessableGuids) { ExpandMultiplePossessableBindings(Sequencer, ChildPossessableGuid); } Sequencer->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemsChanged); return NewPossessableGuids; } void NewCameraAdded(TSharedRef Sequencer, ACameraActor* NewCamera, FGuid CameraGuid) { if (Sequencer->OnCameraAddedToSequencer().IsBound() && !Sequencer->OnCameraAddedToSequencer().Execute(NewCamera, CameraGuid)) { return; } MovieSceneToolHelpers::LockCameraActorToViewport(Sequencer, NewCamera); UMovieSceneSequence* Sequence = Sequencer->GetFocusedMovieSceneSequence(); if (Sequence && Sequence->IsTrackSupported(UMovieSceneCameraCutTrack::StaticClass()) == ETrackSupport::Supported) { MovieSceneToolHelpers::CreateCameraCutSectionForCamera(Sequence->GetMovieScene(), CameraGuid, Sequencer->GetLocalTime().Time.FloorToFrame()); } } FGuid AddSpawnable(TSharedRef Sequencer, UObject& Object, UActorFactory* ActorFactory = nullptr, FName SpawnableName = NAME_None) { UMovieSceneSequence* Sequence = Sequencer->GetFocusedMovieSceneSequence(); if (!Sequence->AllowsSpawnableObjects()) { return FGuid(); } // Grab the MovieScene that is currently focused. We'll add our Blueprint as an inner of the // MovieScene asset. UMovieScene* OwnerMovieScene = Sequence->GetMovieScene(); TValueOrError Result = Sequencer->GetSpawnRegister().CreateNewSpawnableType(Object, *OwnerMovieScene, ActorFactory); if (!Result.IsValid()) { FNotificationInfo Info(Result.GetError()); Info.ExpireDuration = 3.0f; FSlateNotificationManager::Get().AddNotification(Info); return FGuid(); } FNewSpawnable& NewSpawnable = Result.GetValue(); if (SpawnableName == NAME_None) { NewSpawnable.Name = MovieSceneHelpers::MakeUniqueSpawnableName(OwnerMovieScene, NewSpawnable.Name); } else { NewSpawnable.Name = SpawnableName.ToString(); } FGuid NewGuid = OwnerMovieScene->AddSpawnable(NewSpawnable.Name, *NewSpawnable.ObjectTemplate); Sequencer->ForceEvaluate(); return NewGuid; } FGuid FSequencerUtilities::MakeNewSpawnable(TSharedRef Sequencer, UObject& Object, UActorFactory* ActorFactory, bool bSetupDefaults, FName SpawnableName) { UMovieSceneSequence* Sequence = Sequencer->GetFocusedMovieSceneSequence(); if (!Sequence) { return FGuid(); } UMovieScene* MovieScene = Sequence->GetMovieScene(); if (!MovieScene) { return FGuid(); } if (MovieScene->IsReadOnly()) { ShowReadOnlyError(); return FGuid(); } if (!Sequence->AllowsSpawnableObjects()) { ShowSpawnableNotAllowedError(); return FGuid(); } FGuid NewGuid = AddSpawnable(Sequencer, Object, ActorFactory, SpawnableName); if (!NewGuid.IsValid()) { return FGuid(); } // Spawn the object so we can position it correctly, it's going to get spawned anyway since things default to spawned. UObject* SpawnedObject = Sequencer->GetSpawnRegister().SpawnObject(NewGuid, *MovieScene, Sequencer->GetFocusedTemplateID(), Sequencer.Get()); if (bSetupDefaults) { FTransformData TransformData; Sequencer->GetSpawnRegister().SetupDefaultsForSpawnable(SpawnedObject, NewGuid, TransformData, Sequencer, Sequencer->GetSequencerSettings()); } if (ACameraActor* NewCamera = Cast(SpawnedObject)) { NewCameraAdded(Sequencer, NewCamera, NewGuid); } return NewGuid; } FGuid FSequencerUtilities::CreateCamera(TSharedRef Sequencer, const bool bSpawnable, ACineCameraActor*& OutActor) { FGuid CameraGuid; UMovieSceneSequence* Sequence = Sequencer->GetFocusedMovieSceneSequence(); if (!Sequence) { return CameraGuid; } UMovieScene* MovieScene = Sequence->GetMovieScene(); if (!MovieScene) { return CameraGuid; } if (MovieScene->IsReadOnly()) { ShowReadOnlyError(); return CameraGuid; } UWorld* World = GCurrentLevelEditingViewportClient ? GCurrentLevelEditingViewportClient->GetWorld() : nullptr; if (!World) { return CameraGuid; } const FScopedTransaction Transaction(LOCTEXT("CreateCamera", "Create Camera")); FActorSpawnParameters SpawnParams; if (bSpawnable) { // Don't bother transacting this object if we're creating a spawnable since it's temporary SpawnParams.ObjectFlags &= ~RF_Transactional; } // Set new camera to match viewport OutActor = World->SpawnActor(SpawnParams); if (!OutActor) { return CameraGuid; } OutActor->SetActorLocation(GCurrentLevelEditingViewportClient->GetViewLocation(), false); OutActor->SetActorRotation(GCurrentLevelEditingViewportClient->GetViewRotation()); //OutActor->CameraComponent->FieldOfView = ViewportClient->ViewFOV; //@todo set the focal length from this field of view FActorLabelUtilities::SetActorLabelUnique(OutActor, ACineCameraActor::StaticClass()->GetName()); CameraGuid = CreateBinding(Sequencer, *OutActor); TSubclassOf CustomBindingClass = bSpawnable ? UMovieSceneSpawnableActorBinding::StaticClass() : UMovieSceneReplaceableActorBinding::StaticClass(); const FMovieSceneBindingReferences* BindingReferences = Sequence->GetBindingReferences(); if (BindingReferences) { for (const FMovieSceneBindingReference& Reference : BindingReferences->GetReferences(CameraGuid)) { for (const TSubclassOf& SupportedCustomBindingType : Sequencer->GetSupportedCustomBindingTypes()) { if (SupportedCustomBindingType && SupportedCustomBindingType->IsChildOf(CustomBindingClass) && SupportedCustomBindingType->GetDefaultObject()->SupportsConversionFromBinding(Reference, OutActor)) { FMovieScenePossessable* NewPossessable = FSequencerUtilities::ConvertToCustomBinding(Sequencer->AsShared(), CameraGuid, CustomBindingClass); if (NewPossessable) { for (TWeakObjectPtr<> WeakObject : Sequencer->FindBoundObjects(NewPossessable->GetGuid(), Sequencer->GetFocusedTemplateID())) { ACineCameraActor* SpawnedActor = Cast(WeakObject.Get()); if (SpawnedActor) { OutActor = SpawnedActor; } } CameraGuid = NewPossessable->GetGuid(); } break; } } } } if (!CameraGuid.IsValid()) { return CameraGuid; } NewCameraAdded(Sequencer, OutActor, CameraGuid); return CameraGuid; } FGuid FSequencerUtilities::CreateCameraWithRig(TSharedRef Sequencer, AActor* Actor, const bool bSpawnable, ACineCameraActor*& OutActor) { FGuid CameraGuid; UMovieSceneSequence* Sequence = Sequencer->GetFocusedMovieSceneSequence(); if (!Sequence) { return CameraGuid; } UMovieScene* MovieScene = Sequence->GetMovieScene(); if (!MovieScene) { return CameraGuid; } if (MovieScene->IsReadOnly()) { ShowReadOnlyError(); return CameraGuid; } const FScopedTransaction Transaction(LOCTEXT("CreateCameraWithRig", "Create Camera with Rig")); ACameraRig_Rail* RailActor = nullptr; if (Actor->GetClass() == ACameraRig_Rail::StaticClass()) { RailActor = Cast(Actor); } // Create a cine camera actor UWorld* World = GCurrentLevelEditingViewportClient ? GCurrentLevelEditingViewportClient->GetWorld() : nullptr; OutActor = World->SpawnActor(); FString NewCameraName = MovieSceneHelpers::MakeUniqueSpawnableName(MovieScene, FName::NameToDisplayString(ACineCameraActor::StaticClass()->GetFName().ToString(), false)); UE::Sequencer::FCreateBindingParams CreateBindingParams; CreateBindingParams.BindingNameOverride = NewCameraName; CreateBindingParams.bSpawnable = bSpawnable; CameraGuid = CreateBinding(Sequencer, *OutActor, CreateBindingParams); if (RailActor) { OutActor->SetActorRotation(FRotator(0.f, -90.f, 0.f)); } TRange PlaybackRange = MovieScene->GetPlaybackRange(); if (bSpawnable) { for (TWeakObjectPtr<> WeakObject : Sequencer->FindBoundObjects(CameraGuid, Sequencer->GetFocusedTemplateID())) { OutActor = Cast(WeakObject.Get()); if (OutActor) { break; } } OutActor->SetActorLabel(NewCameraName, false); // Create an attach track UMovieScene3DAttachTrack* AttachTrack = Cast(MovieScene->AddTrack(UMovieScene3DAttachTrack::StaticClass(), CameraGuid)); FGuid NewGuid = Sequencer->FindObjectId(*Actor, Sequencer->GetFocusedTemplateID()); FMovieSceneObjectBindingID AttachBindingID = UE::MovieScene::FRelativeObjectBindingID(NewGuid); FFrameNumber StartTime = UE::MovieScene::DiscreteInclusiveLower(PlaybackRange); FFrameNumber Duration = UE::MovieScene::DiscreteSize(PlaybackRange); AttachTrack->AddConstraint(StartTime, Duration.Value, NAME_None, NAME_None, AttachBindingID); } else { FActorLabelUtilities::SetActorLabelUnique(OutActor, ACineCameraActor::StaticClass()->GetName()); // Parent it OutActor->AttachToActor(Actor, FAttachmentTransformRules::KeepRelativeTransform); } if (RailActor) { // Extend the rail a bit if (RailActor->GetRailSplineComponent()->GetNumberOfSplinePoints() == 2) { FVector SplinePoint1 = RailActor->GetRailSplineComponent()->GetLocationAtSplinePoint(0, ESplineCoordinateSpace::Local); FVector SplinePoint2 = RailActor->GetRailSplineComponent()->GetLocationAtSplinePoint(1, ESplineCoordinateSpace::Local); FVector SplineDirection = SplinePoint2 - SplinePoint1; SplineDirection.Normalize(); float DefaultRailDistance = 650.f; SplinePoint2 = SplinePoint1 + SplineDirection * DefaultRailDistance; RailActor->GetRailSplineComponent()->SetLocationAtSplinePoint(1, SplinePoint2, ESplineCoordinateSpace::Local); RailActor->GetRailSplineComponent()->bSplineHasBeenEdited = true; } // Create a track for the CurrentPositionOnRail FPropertyPath PropertyPath; PropertyPath.AddProperty(FPropertyInfo(RailActor->GetClass()->FindPropertyByName(TEXT("CurrentPositionOnRail")))); FKeyPropertyParams KeyPropertyParams(TArrayBuilder().Add(RailActor), PropertyPath, ESequencerKeyMode::ManualKeyForced); FFrameTime OriginalTime = Sequencer->GetLocalTime().Time; Sequencer->SetLocalTimeDirectly(UE::MovieScene::DiscreteInclusiveLower(PlaybackRange)); RailActor->CurrentPositionOnRail = 0.f; Sequencer->KeyProperty(KeyPropertyParams); Sequencer->SetLocalTimeDirectly(UE::MovieScene::DiscreteExclusiveUpper(PlaybackRange) - 1); RailActor->CurrentPositionOnRail = 1.f; Sequencer->KeyProperty(KeyPropertyParams); Sequencer->SetLocalTimeDirectly(OriginalTime); } NewCameraAdded(Sequencer, OutActor, CameraGuid); return CameraGuid; } TArray FSequencerUtilities::AddActors(TSharedRef Sequencer, const TArray >& InActors) { TArray PossessableGuids; UMovieSceneSequence* Sequence = Sequencer->GetFocusedMovieSceneSequence(); if (!Sequence) { return PossessableGuids; } UMovieScene* MovieScene = Sequence->GetMovieScene(); if (!MovieScene) { return PossessableGuids; } if (MovieScene->IsReadOnly()) { ShowReadOnlyError(); return PossessableGuids; } const FScopedTransaction Transaction(LOCTEXT("AddActors", "Add Actors")); Sequence->Modify(); for (TWeakObjectPtr WeakActor : InActors) { if (AActor* Actor = WeakActor.Get()) { FGuid ExistingGuid = Sequencer->FindObjectId(*Actor, Sequencer->GetFocusedTemplateID()); if (!ExistingGuid.IsValid()) { FGuid PossessableGuid = CreateBinding(Sequencer, *Actor); PossessableGuids.Add(PossessableGuid); if (ACameraActor* CameraActor = Cast(Actor)) { NewCameraAdded(Sequencer, CameraActor, PossessableGuid); } } } } return PossessableGuids; } TArray FSequencerUtilities::ConvertToSpawnable(TSharedRef Sequencer, FGuid PossessableGuid) { TArray CreatedSpawnables; UMovieSceneSequence* Sequence = Sequencer->GetFocusedMovieSceneSequence(); if (!Sequence) { return CreatedSpawnables; } UMovieScene* MovieScene = Sequence->GetMovieScene(); if (!MovieScene) { return CreatedSpawnables; } if (MovieScene->IsReadOnly() || !Sequence->AllowsSpawnableObjects()) { ShowReadOnlyError(); return CreatedSpawnables; } TArrayView> FoundObjects = Sequencer->FindBoundObjects(PossessableGuid, Sequencer->GetFocusedTemplateID()); if (FoundObjects.Num() == 0) { FMovieScenePossessable* Possessable = MovieScene->FindPossessable(PossessableGuid); UE_LOG(LogSequencer, Error, TEXT("Failed to convert %s to spawnable because there are no objects bound to it"), Possessable ? *Possessable->GetName() : TEXT("")); } else if (FoundObjects.Num() > 1) { // Expand to individual possessables for each bound object, then convert each one individually TArray ExpandedPossessableGuids = ExpandMultiplePossessableBindings(Sequencer, PossessableGuid); for (FGuid NewPossessableGuid : ExpandedPossessableGuids) { CreatedSpawnables.Append(ConvertToSpawnable(Sequencer, NewPossessableGuid)); } Sequencer->ForceEvaluate(); } else { UObject* FoundObject = FoundObjects[0].Get(); if (!FoundObject) { return CreatedSpawnables; } Sequence->Modify(); MovieScene->Modify(); // Locate the folder containing the original possessable UMovieSceneFolder* ParentFolder = nullptr; for (UMovieSceneFolder* Folder : MovieScene->GetRootFolders()) { ParentFolder = Folder->FindFolderContaining(PossessableGuid); if (ParentFolder != nullptr) { break; } } FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(AddSpawnable(Sequencer, *FoundObject)); if (Spawnable) { FGuid SpawnableGuid = Spawnable->GetGuid(); CreatedSpawnables.Add(Spawnable); // Remap all the spawnable's tracks and child bindings onto the new possessable MovieScene->MoveBindingContents(PossessableGuid, SpawnableGuid); FMovieSceneBinding* PossessableBinding = MovieScene->FindBinding(PossessableGuid); check(PossessableBinding); for (UMovieSceneFolder* Folder : MovieScene->GetRootFolders()) { if (UpdateFolderBindingID(Folder, PossessableGuid, SpawnableGuid)) { break; } } int32 SortingOrder = PossessableBinding->GetSortingOrder(); if (MovieScene->RemovePossessable(PossessableGuid)) { Sequence->UnbindPossessableObjects(PossessableGuid); FMovieSceneBinding* SpawnableBinding = MovieScene->FindBinding(SpawnableGuid); check(SpawnableBinding); SpawnableBinding->SetSortingOrder(SortingOrder); } TOptional TransformData; Sequencer->GetSpawnRegister().HandleConvertPossessableToSpawnable(FoundObject, *Sequencer, TransformData); Sequencer->GetSpawnRegister().SetupDefaultsForSpawnable(nullptr, Spawnable->GetGuid(), TransformData, Sequencer, Sequencer->GetSequencerSettings()); UpdateBindingIDs(Sequencer, PossessableGuid, Spawnable->GetGuid()); Sequencer->ForceEvaluate(); } } return CreatedSpawnables; } FMovieScenePossessable* FSequencerUtilities::ConvertToPossessable(TSharedRef Sequencer, FGuid BindingGuid, int32 BindingIndex/*=0*/) { FMovieScenePossessable* CreatedPossessable = nullptr; UMovieSceneSequence* Sequence = Sequencer->GetFocusedMovieSceneSequence(); if (!Sequence) { return CreatedPossessable; } UMovieScene* MovieScene = Sequence->GetMovieScene(); if (!MovieScene) { return CreatedPossessable; } if (MovieScene->IsReadOnly()) { ShowReadOnlyError(); return CreatedPossessable; } FMovieScenePossessable* ExistingPossessable = MovieScene->FindPossessable(BindingGuid); if (ExistingPossessable) { if (const FMovieSceneBindingReference* ExistingReference = Sequence->GetBindingReferences()->GetReference(BindingGuid, BindingIndex)) { if (!ExistingReference->CustomBinding) { // Already a possessable, just return return ExistingPossessable; } } } UObject* BoundObject = MovieSceneHelpers::GetSingleBoundObject(Sequence, BindingGuid, Sequencer->GetSharedPlaybackState(), BindingIndex); UObject* ObjectToConvert = BoundObject; // If we have an old-style spawnable, use the template as the object to convert instead. bool bConvertFromSpawnable = MovieSceneHelpers::IsBoundToSpawnable(Sequence, BindingGuid, Sequencer->GetSharedPlaybackState(), BindingIndex); if (bConvertFromSpawnable && MovieSceneHelpers::SupportsObjectTemplate(Sequence, BindingGuid, Sequencer->GetSharedPlaybackState(), BindingIndex)) { ObjectToConvert = MovieSceneHelpers::GetObjectTemplate(Sequence, BindingGuid, Sequencer->GetSharedPlaybackState(), BindingIndex); } AActor* SpawnableActorTemplate = Cast(ObjectToConvert); TMap, FTransform> AttachedChildTransforms; FTransform DefaultTransform = SpawnableActorTemplate ? SpawnableActorTemplate->GetActorTransform() : FTransform(); // Prefer the transform at the current time over the spawnable actor template's transform because that's most likely 0. // This makes it so that the object will return to the current position on restore state. AActor* Actor = Cast(BoundObject); if (Actor) { if (Actor->GetRootComponent()) { DefaultTransform = Actor->GetRootComponent()->GetRelativeTransform(); } // Removing a parent will compensate the children at their world transform. We don't want that since we'll be replacing that parent right away. // To negate that, we store the relative transform of these children and reset it after the parent is replaced with the new possessable. TArray AttachedActors; Actor->GetAttachedActors(AttachedActors); for (AActor* ChildActor : AttachedActors) { if (ChildActor && ChildActor->GetRootComponent()) { // Only do this for child actors that Sequencer is controlling FGuid ExistingID = Sequencer->FindObjectId(*ChildActor, Sequencer->GetFocusedTemplateID()); if (ExistingID.IsValid()) { AttachedChildTransforms.Add(ChildActor); AttachedChildTransforms[ChildActor] = ChildActor->GetRootComponent()->GetRelativeTransform(); } } } } FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(BindingGuid); // TODO: How to convert to possessable of non-actor type? Presumably we need to generalize the 'creation' step here for now. UObject* PossessedObject = nullptr; if (Actor) { FActorSpawnParameters SpawnInfo; SpawnInfo.bDeferConstruction = true; SpawnInfo.Template = SpawnableActorTemplate; UWorld* World = GCurrentLevelEditingViewportClient ? GCurrentLevelEditingViewportClient->GetWorld() : nullptr; AActor* PossessedActor = World->SpawnActor(ObjectToConvert->GetClass(), &DefaultTransform, SpawnInfo); if (!PossessedActor) { return nullptr; } FString ActorLabel = Actor->GetActorLabel(); if (Spawnable) { ActorLabel = Spawnable->GetName(); } else if (ExistingPossessable) { if (const FMovieSceneBindingReferences* BindingReferences = Sequence->GetBindingReferences()) { // If we don't have multiple bound objects, use the Possessable name instead of the template label if (BindingReferences->GetReferences(BindingGuid).Num() == 1) { ActorLabel = ExistingPossessable->GetName(); } } } PossessedActor->SetActorLabel(ActorLabel); const bool bIsDefaultTransform = true; PossessedActor->FinishSpawning(DefaultTransform, bIsDefaultTransform); // The transform needs to be set again for deferred construction and dynamic root components. Until the fix for: UE-67537 PossessedActor->SetActorTransform(DefaultTransform); PossessedObject = PossessedActor; } Sequence->Modify(); MovieScene->Modify(); UE::Sequencer::FCreateBindingParams CreateBindingParams; CreateBindingParams.ReplacementGuid = BindingGuid; CreateBindingParams.BindingIndex = BindingIndex; CreateBindingParams.bAllowCustomBinding = false; CreateBindingParams.bAllowEmptyBinding = PossessedObject == nullptr; // Create or replace the binding FGuid NewPossessableGuid = CreateOrReplaceBinding(Sequencer, Sequence, PossessedObject, CreateBindingParams); TArrayView BindingReferences = Sequence->GetBindingReferences()->GetReferences(BindingGuid); bool bAnySpawnablesLeft = Algo::AnyOf(BindingReferences, [](const FMovieSceneBindingReference& BindingReference) { return BindingReference.CustomBinding && BindingReference.CustomBinding->IsA(); }); // If we're converting from a spawnable and none of the other bindings on the guid are spawnable, we'll need to remove the spawn track if (bConvertFromSpawnable && !bAnySpawnablesLeft) { // Delete the spawn track UMovieSceneSpawnTrack* SpawnTrack = Cast(MovieScene->FindTrack(UMovieSceneSpawnTrack::StaticClass(), BindingGuid, NAME_None)); if (SpawnTrack) { MovieScene->RemoveTrack(*SpawnTrack); } } FMovieScenePossessable* Possessable = MovieScene->FindPossessable(NewPossessableGuid); if (Spawnable) { // Remap all the spawnable's tracks and child bindings onto the new possessable MovieScene->MoveBindingContents(BindingGuid, NewPossessableGuid); FMovieSceneBinding* SpawnableBinding = MovieScene->FindBinding(BindingGuid); check(SpawnableBinding); for (UMovieSceneFolder* Folder : MovieScene->GetRootFolders()) { if (UpdateFolderBindingID(Folder, Spawnable->GetGuid(), Possessable->GetGuid())) { break; } } int32 SortingOrder = SpawnableBinding->GetSortingOrder(); // Remove the spawnable and all it's sub tracks if (MovieScene->RemoveSpawnable(BindingGuid)) { UpdateBindingIDs(Sequencer, BindingGuid, NewPossessableGuid); FMovieSceneBinding* PossessableBinding = MovieScene->FindBinding(NewPossessableGuid); check(PossessableBinding); PossessableBinding->SetSortingOrder(SortingOrder); } } // If we previously had an old-style spawnable or a spawnable custom binding, destroy the old spawned object if (bConvertFromSpawnable) { Sequencer->GetSpawnRegister().DestroySpawnedObject(BindingGuid, Sequencer->GetFocusedTemplateID(), Sequencer->GetSharedPlaybackState(), BindingIndex); } if (AActor* PossessedActor = Cast(PossessedObject)) { static const FName SequencerActorTag(TEXT("SequencerActor")); static const FName SequencerPreviewActorTag(TEXT("SequencerPreviewActor")); PossessedActor->Tags.Remove(SequencerActorTag); PossessedActor->Tags.Remove(SequencerPreviewActorTag); GEditor->SelectActor(PossessedActor, false, true); for (TPair, FTransform> AttachedChildTransform : AttachedChildTransforms) { if (AActor* AttachedChild = AttachedChildTransform.Key.Get()) { if (AttachedChild->GetRootComponent()) { AttachedChild->GetRootComponent()->SetRelativeTransform(AttachedChildTransform.Value); } } } } Sequencer->ForceEvaluate(); return Possessable; } FMovieScenePossessable* FSequencerUtilities::ConvertToCustomBinding(TSharedRef Sequencer, FGuid BindingGuid, TSubclassOf CustomBindingType, int32 BindingIndex/*=0*/) { FMovieScenePossessable* CreatedPossessable = nullptr; UMovieSceneSequence* Sequence = Sequencer->GetFocusedMovieSceneSequence(); if (!Sequence) { return CreatedPossessable; } UMovieScene* MovieScene = Sequence->GetMovieScene(); if (!MovieScene) { return CreatedPossessable; } if (MovieScene->IsReadOnly()) { ShowReadOnlyError(); return CreatedPossessable; } const FMovieSceneBindingReferences* BindingReferences = Sequence->GetBindingReferences(); if (!BindingReferences) { // Not supported with this sequence type- show an error? return CreatedPossessable; } if (!CustomBindingType) { return CreatedPossessable; } UObject* ObjectToConvert = MovieSceneHelpers::GetSingleBoundObject(Sequence, BindingGuid, Sequencer->GetSharedPlaybackState(), BindingIndex); bool bConvertFromSpawnable = false; // If we have an old-style spawnable, use the template as the object to convert instead. FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(BindingGuid); const FMovieSceneBindingReference* PreviousBindingReference = BindingReferences->GetReference(BindingGuid, BindingIndex); const UMovieSceneCustomBinding* PreviousCustomBinding = nullptr; if (Spawnable) { ObjectToConvert = Spawnable->GetObjectTemplate(); bConvertFromSpawnable = true; } else if (PreviousBindingReference) { if (const UMovieSceneCustomBinding* CustomBinding = PreviousBindingReference->CustomBinding) { PreviousCustomBinding = CustomBinding; bConvertFromSpawnable = PreviousCustomBinding->WillSpawnObject(Sequencer->GetSharedPlaybackState()); } } bool bConvertFromPossessable = !bConvertFromSpawnable && !BindingReferences->GetCustomBinding(BindingGuid, BindingIndex); UMovieSceneCustomBinding* NewCustomBinding = nullptr; if (PreviousBindingReference) { NewCustomBinding = CustomBindingType->GetDefaultObject()->CreateCustomBindingFromBinding(*PreviousBindingReference, ObjectToConvert, *MovieScene); } else { NewCustomBinding = CustomBindingType->GetDefaultObject()->CreateNewCustomBinding(ObjectToConvert, *MovieScene); } if (!NewCustomBinding) { return CreatedPossessable; } Sequence->Modify(); MovieScene->Modify(); UE::Sequencer::FCreateBindingParams CreateBindingParams; CreateBindingParams.ReplacementGuid = BindingGuid; CreateBindingParams.BindingIndex = BindingIndex; CreateBindingParams.BindingNameOverride = NewCustomBinding->GetDesiredBindingName(); CreateBindingParams.CustomBinding = NewCustomBinding; CreateBindingParams.bSetupDefaults = false; CreateBindingParams.bAllowEmptyBinding = !ObjectToConvert; // Create or replace the binding FGuid NewPossessableGuid = CreateOrReplaceBinding(Sequencer, ObjectToConvert, CreateBindingParams); TArrayView BindingReferencesForGuid = BindingReferences->GetReferences(BindingGuid); bool bAnySpawnablesLeft = Algo::AnyOf(BindingReferencesForGuid, [](const FMovieSceneBindingReference& BindingReference) { return BindingReference.CustomBinding && BindingReference.CustomBinding->IsA(); }); bool bAnyReplaceablesLeft = Algo::AnyOf(BindingReferencesForGuid, [](const FMovieSceneBindingReference& BindingReference) { return BindingReference.CustomBinding && BindingReference.CustomBinding->IsA(); }); // If we're converting from a spawnable and the new custom binding isn't a spawnable, remove the spawn track if (PreviousCustomBinding && PreviousCustomBinding->IsA() && !bAnySpawnablesLeft) { // Delete the spawn track UMovieSceneSpawnTrack* SpawnTrack = Cast(MovieScene->FindTrack(UMovieSceneSpawnTrack::StaticClass(), BindingGuid, NAME_None)); if (SpawnTrack) { MovieScene->RemoveTrack(*SpawnTrack); } } else if (PreviousCustomBinding && PreviousCustomBinding->IsA() && !bAnyReplaceablesLeft) { // Delete the binding lifetime track UMovieSceneBindingLifetimeTrack* BindingLifetimeTrack = Cast(MovieScene->FindTrack(UMovieSceneBindingLifetimeTrack::StaticClass(), BindingGuid, NAME_None)); if (BindingLifetimeTrack) { MovieScene->RemoveTrack(*BindingLifetimeTrack); } } CreatedPossessable = MovieScene->FindPossessable(NewPossessableGuid); // If we previously had an old-style spawnable, we need to move over bindings if (Spawnable) { // Remap all the spawnable's tracks and child bindings onto the new possessable MovieScene->MoveBindingContents(BindingGuid, NewPossessableGuid); FMovieSceneBinding* SpawnableBinding = MovieScene->FindBinding(BindingGuid); check(SpawnableBinding); for (UMovieSceneFolder* Folder : MovieScene->GetRootFolders()) { if (UpdateFolderBindingID(Folder, Spawnable->GetGuid(), NewPossessableGuid)) { break; } } int32 SortingOrder = SpawnableBinding->GetSortingOrder(); // Remove the spawnable and all its' sub tracks if (MovieScene->RemoveSpawnable(BindingGuid)) { FMovieSceneBinding* PossessableBinding = MovieScene->FindBinding(NewPossessableGuid); check(PossessableBinding); PossessableBinding->SetSortingOrder(SortingOrder); } UpdateBindingIDs(Sequencer, BindingGuid, NewPossessableGuid); Sequencer->ForceEvaluate(); } TOptional TransformData; if (bConvertFromSpawnable) { Sequencer->GetSpawnRegister().DestroySpawnedObject(BindingGuid, Sequencer->GetFocusedTemplateID(), Sequencer->GetSharedPlaybackState(), BindingIndex); } else if (bConvertFromPossessable && NewCustomBinding->WillSpawnObject(Sequencer->GetSharedPlaybackState())) { // We have an old possessable to destroy Sequencer->GetSpawnRegister().HandleConvertPossessableToSpawnable(ObjectToConvert, *Sequencer, TransformData); } // If this is a new spawnable or replaceable binding, we need to set up some defaults if (NewCustomBinding->WillSpawnObject(Sequencer->GetSharedPlaybackState())) { // We purposefully pass in nullptr to SetupDefaultsForSpawnable below. // This will prevent a section of code in it from calling OnActorAddedToSequencer, which should not be called in the case of binding conversion, // as it may cause some default tracks to get added for a second time. // Allow the binding to set up any necessary defaults NewCustomBinding->SetupDefaults(nullptr, NewPossessableGuid, *MovieScene); Sequencer->GetSpawnRegister().SetupDefaultsForSpawnable(nullptr, NewPossessableGuid, TransformData, Sequencer, Sequencer->GetSequencerSettings()); } //Sequencer->State.Invalidate(NewPossessableGuid, Sequencer->GetFocusedTemplateID()); return CreatedPossessable; } void ExportObjectsToText(const TArray& ObjectsToExport, FString& ExportedText) { if (ObjectsToExport.Num() == 0) { return; } // Clear the mark state for saving. UnMarkAllObjects(EObjectMark(OBJECTMARK_TagExp | OBJECTMARK_TagImp)); FStringOutputDevice Archive; const FExportObjectInnerContext Context; // Export each of the selected nodes UObject* LastOuter = nullptr; for (UObject* ObjectToExport : ObjectsToExport) { // The nodes should all be from the same scope UObject* ThisOuter = ObjectToExport->GetOuter(); if (LastOuter != nullptr && ThisOuter != LastOuter) { UE_LOG(LogSequencer, Error, TEXT("Cannot copy objects from different outers. Only copying from %s"), *LastOuter->GetName()); continue; } LastOuter = ThisOuter; UExporter::ExportToOutputDevice(&Context, ObjectToExport, nullptr, Archive, TEXT("copy"), 0, PPF_ExportsNotFullyQualified | PPF_Copy | PPF_Delimited, false, ThisOuter); } ExportedText = Archive; } /** * * Copy/paste folders * */ void GatherChildFolders(UMovieSceneFolder* ParentFolder, TArray& Objects) { for (UMovieSceneFolder* ChildFolder : ParentFolder->GetChildFolders()) { if (ChildFolder) { Objects.AddUnique(ChildFolder); GatherChildFolders(ChildFolder, Objects); } } } void FSequencerUtilities::CopyFolders(const TArray& Folders, FString& ExportedText) { TArray Objects; for (UMovieSceneFolder* Folder : Folders) { Objects.AddUnique(Folder); GatherChildFolders(Folder, Objects); } ExportObjectsToText(Objects, /*out*/ ExportedText); } void GatherFolderContents(UMovieSceneFolder* Folder, TArray& Folders, TArray& Tracks, TArray& Bindings) { if (!Folder) { return; } Folders.AddUnique(Folder); UMovieScene* MovieScene = CastChecked(Folder->GetOuter()); UMovieSceneSequence* Sequence = CastChecked(MovieScene->GetOuter()); for (const FGuid& ObjectBinding : Folder->GetChildObjectBindings()) { Bindings.AddUnique(FMovieSceneBindingProxy(ObjectBinding, Sequence)); } for (UMovieSceneTrack* ChildTrack : Folder->GetChildTracks()) { Tracks.AddUnique(ChildTrack); } for (UMovieSceneFolder* ChildFolder : Folder->GetChildFolders()) { if (ChildFolder) { GatherFolderContents(ChildFolder, Folders, Tracks, Bindings); } } } void FSequencerUtilities::CopyFolders(TSharedRef Sequencer, const TArray& InFolders, FString& FoldersExportedText, FString& TracksExportedText, FString& ObjectsExportedText) { TArray Folders; TArray Tracks; TArray Bindings; for (UMovieSceneFolder* Folder : InFolders) { GatherFolderContents(Folder, Folders, Tracks, Bindings); } CopyTracks(Tracks, Folders, TracksExportedText); CopyBindings(Sequencer, Bindings, Folders, ObjectsExportedText); TArray Objects; for (UMovieSceneFolder* Folder : Folders) { Objects.Add(Folder); } ExportObjectsToText(Objects, /*out*/ FoldersExportedText); } class FFolderObjectTextFactory : public FCustomizableTextObjectFactory { public: FFolderObjectTextFactory() : FCustomizableTextObjectFactory(GWarn) { } // FCustomizableTextObjectFactory implementation virtual bool CanCreateClass(UClass* InObjectClass, bool& bOmitSubObjs) const override { if (InObjectClass->IsChildOf(UMovieSceneFolder::StaticClass())) { return true; } return false; } virtual void ProcessConstructedObject(UObject* NewObject) override { check(NewObject); NewFolders.Add(Cast(NewObject)); } public: TArray NewFolders; }; void ImportFoldersFromText(const FString& TextToImport, /*out*/ TArray& ImportedFolders) { UPackage* TempPackage = NewObject(nullptr, TEXT("/Engine/Sequencer/Editor/Transient"), RF_Transient); TempPackage->AddToRoot(); // Turn the text buffer into objects FFolderObjectTextFactory Factory; Factory.ProcessBuffer(TempPackage, RF_Transactional, TextToImport); ImportedFolders = Factory.NewFolders; // Remove the temp package from the root now that it has served its purpose TempPackage->RemoveFromRoot(); } bool FSequencerUtilities::PasteFolders(const FString& TextToImport, FMovieScenePasteFoldersParams PasteFoldersParams, TArray& OutFolders, TArray& OutErrors) { if (!PasteFoldersParams.Sequence) { return false; } UMovieScene* MovieScene = PasteFoldersParams.Sequence->GetMovieScene(); if (!MovieScene) { return false; } TArray ImportedFolders; ImportFoldersFromText(TextToImport, ImportedFolders); if (ImportedFolders.Num() == 0) { return false; } const FScopedTransaction Transaction(LOCTEXT("PasteFolders", "Paste Folders")); MovieScene->Modify(); for (UMovieSceneFolder* CopiedFolder : ImportedFolders) { CopiedFolder->Rename(nullptr, MovieScene); OutFolders.Add(CopiedFolder); // Clear the folder contents, those relationships will be made when the tracks are pasted CopiedFolder->ClearChildTracks(); CopiedFolder->ClearChildObjectBindings(); bool bHasParent = false; for (UMovieSceneFolder* ImportedParentFolder : ImportedFolders) { if (ImportedParentFolder != CopiedFolder) { if (ImportedParentFolder->GetChildFolders().Contains(CopiedFolder)) { bHasParent = true; break; } } } if (!bHasParent) { if (PasteFoldersParams.ParentFolder) { PasteFoldersParams.ParentFolder->AddChildFolder(CopiedFolder); } else { MovieScene->AddRootFolder(CopiedFolder); } } } return true; } bool FSequencerUtilities::CanPasteFolders(const FString& TextToImport) { FFolderObjectTextFactory FolderFactory; return FolderFactory.CanCreateObjectsFromText(TextToImport); } /** * * Copy/paste tracks * */ void FSequencerUtilities::CopyTracks(const TArray& Tracks, const TArray& Folders, FString& ExportedText) { TArray Objects; for (UMovieSceneTrack* Track : Tracks) { UMovieScene* MovieScene = Track->GetTypedOuter(); UMovieSceneCopyableTrack* CopyableTrack = NewObject(GetTransientPackage(), UMovieSceneCopyableTrack::StaticClass(), NAME_None, RF_Transient); Objects.Add(CopyableTrack); UMovieSceneTrack* DuplicatedTrack = Cast(StaticDuplicateObject(Track, CopyableTrack)); CopyableTrack->Track = DuplicatedTrack; CopyableTrack->bIsRootTrack = MovieScene->ContainsTrack(*Track); CopyableTrack->bIsCameraCutTrack = Track->IsA(); UMovieSceneFolder* Folder = nullptr; for (UMovieSceneFolder* RootFolder : MovieScene->GetRootFolders()) { Folder = RootFolder->FindFolderContaining(Track); if (Folder && Folders.Contains(Folder)) { UMovieSceneFolder::CalculateFolderPath(Folder, Folders, CopyableTrack->FolderPath); break; } } } ExportObjectsToText(Objects, /*out*/ ExportedText); } class FTrackObjectTextFactory : public FCustomizableTextObjectFactory { public: FTrackObjectTextFactory() : FCustomizableTextObjectFactory(GWarn) { } // FCustomizableTextObjectFactory implementation virtual bool CanCreateClass(UClass* InObjectClass, bool& bOmitSubObjs) const override { if (InObjectClass->IsChildOf(UMovieSceneCopyableTrack::StaticClass())) { return true; } return false; } virtual void ProcessConstructedObject(UObject* NewObject) override { check(NewObject); NewTracks.Add(Cast(NewObject)); } public: TArray NewTracks; }; void ImportTracksFromText(const FString& TextToImport, /*out*/ TArray& ImportedTracks) { UPackage* TempPackage = NewObject(nullptr, TEXT("/Engine/Sequencer/Editor/Transient"), RF_Transient); TempPackage->AddToRoot(); // Turn the text buffer into objects FTrackObjectTextFactory Factory; Factory.ProcessBuffer(TempPackage, RF_Transactional, TextToImport); ImportedTracks = Factory.NewTracks; // Remove the temp package from the root now that it has served its purpose TempPackage->RemoveFromRoot(); } bool FSequencerUtilities::PasteTracks(const FString& TextToImport, FMovieScenePasteTracksParams PasteTracksParams, TArray& OutTracks, TArray& OutErrors) { TArray ImportedTracks; ImportTracksFromText(TextToImport, ImportedTracks); if (ImportedTracks.Num() == 0) { return false; } const FScopedTransaction Transaction(LOCTEXT("PasteTracks", "Paste Tracks")); int32 NumRootOrCameraCutTracks = 0; int32 NumTracks = 0; for (UMovieSceneCopyableTrack* CopyableTrack : ImportedTracks) { if (CopyableTrack->bIsRootTrack || CopyableTrack->bIsCameraCutTrack) { ++NumRootOrCameraCutTracks; } else { ++NumTracks; } } int32 NumTracksPasted = 0; int32 NumRootOrCameraCutTracksPasted = 0; for (const FMovieSceneBindingProxy& ObjectBinding : PasteTracksParams.Bindings) { TArray NewTracks; ImportTracksFromText(TextToImport, NewTracks); UMovieScene* MovieScene = ObjectBinding.GetMovieScene(); if (!MovieScene) { continue; } for (UMovieSceneCopyableTrack* CopyableTrack : NewTracks) { if (!CopyableTrack->bIsRootTrack && !CopyableTrack->bIsCameraCutTrack) { UMovieSceneTrack* NewTrack = CopyableTrack->Track; ResetCopiedTracksFlags(NewTrack); // Remove tracks with the same name before adding if (const FMovieSceneBinding* Binding = MovieScene->FindBinding(ObjectBinding.BindingID)) { for (UMovieSceneTrack* Track : Binding->GetTracks()) { if (Track->GetClass() == NewTrack->GetClass() && Track->GetTrackName() == NewTrack->GetTrackName() && Track->GetDisplayName().IdenticalTo(NewTrack->GetDisplayName())) { // If a track of the same class and name exists, remove it so the new track replaces it MovieScene->RemoveTrack(*Track); break; } } } if (!MovieScene->AddGivenTrack(NewTrack, ObjectBinding.BindingID)) { continue; } else { OutTracks.Add(NewTrack); ++NumTracksPasted; } } } } UMovieScene* MovieScene = PasteTracksParams.Sequence ? PasteTracksParams.Sequence->GetMovieScene() : nullptr; if (MovieScene) { // Add as root track or set camera cut track for (UMovieSceneCopyableTrack* CopyableTrack : ImportedTracks) { if (CopyableTrack->bIsRootTrack || CopyableTrack->bIsCameraCutTrack) { UMovieSceneTrack* NewTrack = CopyableTrack->Track; ResetCopiedTracksFlags(NewTrack); UMovieSceneFolder* ParentFolder = PasteTracksParams.ParentFolder; if (CopyableTrack->FolderPath.Num() > 0) { ParentFolder = UMovieSceneFolder::GetFolderWithPath(CopyableTrack->FolderPath, PasteTracksParams.Folders, ParentFolder ? ParentFolder->GetChildFolders() : MovieScene->GetRootFolders()); } if (NewTrack->IsA(UMovieSceneCameraCutTrack::StaticClass())) { MovieScene->SetCameraCutTrack(NewTrack); if (ParentFolder != nullptr) { ParentFolder->AddChildTrack(NewTrack); } ++NumRootOrCameraCutTracksPasted; } else { if (MovieScene->AddGivenTrack(NewTrack)) { if (ParentFolder != nullptr) { ParentFolder->AddChildTrack(NewTrack); } } ++NumRootOrCameraCutTracksPasted; } OutTracks.Add(NewTrack); } } } if (NumRootOrCameraCutTracksPasted < NumRootOrCameraCutTracks) { FNotificationInfo Info(LOCTEXT("PasteTracks_NoTracks", "Can't paste track. Root track could not be pasted")); OutErrors.Add(Info); } if (NumTracksPasted < NumTracks) { FNotificationInfo Info(LOCTEXT("PasteTracks_NoSelectedObjects", "Can't paste track. No selected objects to paste tracks onto")); OutErrors.Add(Info); } return (NumRootOrCameraCutTracksPasted + NumTracksPasted) > 0; } bool FSequencerUtilities::CanPasteTracks(const FString& TextToImport) { FTrackObjectTextFactory TrackFactory; return TrackFactory.CanCreateObjectsFromText(TextToImport); } /** * * Copy/paste sections * */ void FSequencerUtilities::CopySections(const TArray& Sections, FString& ExportedText) { TArray Objects; for (UMovieSceneSection* Section : Sections) { Objects.Add(Section); } ExportObjectsToText(Objects, /*out*/ ExportedText); } class FSectionObjectTextFactory : public FCustomizableTextObjectFactory { public: FSectionObjectTextFactory() : FCustomizableTextObjectFactory(GWarn) { } // FCustomizableTextObjectFactory implementation virtual bool CanCreateClass(UClass* InObjectClass, bool& bOmitSubObjs) const override { if (InObjectClass->IsChildOf(UMovieSceneSection::StaticClass())) { return true; } return false; } virtual void ProcessConstructedObject(UObject* NewObject) override { check(NewObject); NewSections.Add(Cast(NewObject)); } public: TArray NewSections; }; void ImportSectionsFromText(const FString& TextToImport, /*out*/ TArray& ImportedSections) { UPackage* TempPackage = NewObject(nullptr, TEXT("/Engine/Sequencer/Editor/Transient"), RF_Transient); TempPackage->AddToRoot(); // Turn the text buffer into objects FSectionObjectTextFactory Factory; Factory.ProcessBuffer(TempPackage, RF_Transactional, TextToImport); ImportedSections = Factory.NewSections; // Remove the temp package from the root now that it has served its purpose TempPackage->RemoveFromRoot(); } bool FSequencerUtilities::PasteSections(const FString& TextToImport, FMovieScenePasteSectionsParams PasteSectionsParams, TArray& OutSections, TArray& OutErrors) { // First import as a track and extract sections to allow for copying track contents to another track TArray ImportedTracks; ImportTracksFromText(TextToImport, ImportedTracks); TArray ImportedSections; for (UMovieSceneCopyableTrack* CopyableTrack : ImportedTracks) { for (UMovieSceneSection* CopyableSection : CopyableTrack->Track->GetAllSections()) { ImportedSections.Add(CopyableSection); } } // Otherwise, import as sections if (ImportedSections.Num() == 0) { ImportSectionsFromText(TextToImport, ImportedSections); } if (ImportedSections.Num() == 0) { return false; } const FScopedTransaction Transaction(LOCTEXT("PasteSections", "Paste Sections")); TOptional FirstFrame; for (UMovieSceneSection* Section : ImportedSections) { if (Section->HasStartFrame()) { if (FirstFrame.IsSet()) { if (FirstFrame.GetValue() > Section->GetInclusiveStartFrame()) { FirstFrame = Section->GetInclusiveStartFrame(); } } else { FirstFrame = Section->GetInclusiveStartFrame(); } } } TArray SectionIndicesImported; for (int32 Index = 0; Index < PasteSectionsParams.Tracks.Num(); ++Index) { UMovieSceneTrack* Track = PasteSectionsParams.Tracks[Index]; if (!Track) { continue; } const bool bAllowOverlap = Track->SupportsMultipleRows() || Track->GetSupportedBlendTypes().Num() > 0; for (int32 SectionIndex = 0; SectionIndex < ImportedSections.Num(); ++SectionIndex) { UMovieSceneSection* Section = ImportedSections[SectionIndex]; if (!Track->SupportsType(Section->GetClass())) { continue; } int32 RowIndex = Section->GetRowIndex(); // If there is only 1 track to paste onto, paste the sections all onto the that track's row index if (PasteSectionsParams.TrackRowIndices.Num() == 1) { RowIndex = PasteSectionsParams.TrackRowIndices[0]; } // Otherwise if pasting onto multiple track rows, paste onto the same row index as the copied section else if (Index < PasteSectionsParams.TrackRowIndices.Num()) { if (PasteSectionsParams.TrackRowIndices[Index] != Section->GetRowIndex()) { continue; } } Track->Modify(); Section->ClearFlags(RF_Transient); Section->PostPaste(); Section->Rename(nullptr, Track); if (Track->SupportsMultipleRows()) { Section->SetRowIndex(RowIndex); } else if (!Section->HasStartFrame() && !Section->HasEndFrame()) { // If the track doesn't support multiple rows and the pasted section is infinite, it should win out over existing sections Track->RemoveAllAnimationData(); } Track->AddSection(*Section); if (Section->HasStartFrame()) { FFrameNumber NewStartFrame = PasteSectionsParams.Time.FrameNumber + (Section->GetInclusiveStartFrame() - FirstFrame.GetValue()); Section->MoveSection(NewStartFrame - Section->GetInclusiveStartFrame()); } if (!bAllowOverlap) { if (Section->OverlapsWithSections(Track->GetAllSections())) { Track->RemoveSection(*Section); UE_LOG(LogSequencer, Error, TEXT("Could not paste section because it overlaps with existing sections and this track type does not allow overlaps")); continue; } } SectionIndicesImported.AddUnique(SectionIndex); OutSections.Add(Section); } // Fix up rows after sections are in place if (Track->SupportsMultipleRows()) { // If any newly created section overlaps the previous sections, put all the sections on the max available row // Find the this section overlaps any previous sections, int32 MaxAvailableRowIndex = -1; for (UMovieSceneSection* Section : OutSections) { if (!Track->SupportsType(Section->GetClass())) { continue; } if (MovieSceneToolHelpers::OverlapsSection(Track, Section, OutSections)) { int32 AvailableRowIndex = MovieSceneToolHelpers::FindAvailableRowIndex(Track, Section, OutSections); MaxAvailableRowIndex = FMath::Max(AvailableRowIndex, MaxAvailableRowIndex); } } if (MaxAvailableRowIndex != -1) { for (UMovieSceneSection* Section : OutSections) { Section->SetRowIndex(MaxAvailableRowIndex); } } } // Remove sections that were pasted so that they aren't pasted again to another track for (UMovieSceneSection* OutSection : OutSections) { ImportedSections.Remove(OutSection); } } for (int32 SectionIndex = 0; SectionIndex < ImportedSections.Num(); ++SectionIndex) { if (!SectionIndicesImported.Contains(SectionIndex)) { UE_LOG(LogSequencer, Error, TEXT("Could not paste section of type %s"), *ImportedSections[SectionIndex]->GetClass()->GetName()); } } if (SectionIndicesImported.Num() == 0) { FNotificationInfo Info(LOCTEXT("PasteSections_NothingPasted", "Can't paste section. No matching section types found.")); OutErrors.Add(Info); return false; } return true; } bool FSequencerUtilities::CanPasteSections(const FString& TextToImport) { FSectionObjectTextFactory SectionFactory; return SectionFactory.CanCreateObjectsFromText(TextToImport); } /** * * Copy/paste object bindings * */ void ExportObjectBindingsToText(const TArray& ObjectsToExport, FOutputDevice& Archive, TSharedRef SharedPlaybackState) { // Clear the mark state for saving. UnMarkAllObjects(EObjectMark(OBJECTMARK_TagExp | OBJECTMARK_TagImp)); const FExportObjectInnerContext Context; // Export each of the selected nodes UObject* LastOuter = nullptr; for (UMovieSceneCopyableBinding* ObjectToExport : ObjectsToExport) { // The nodes should all be from the same scope UObject* ThisOuter = ObjectToExport->GetOuter(); check((LastOuter == ThisOuter) || (LastOuter == nullptr)); LastOuter = ThisOuter; // We can't use TextExportTransient on USTRUCTS (which our object contains) so we're going to manually null out some references before serializing them. These references are // serialized manually into the archive, as the auto-serialization will only store a reference (to a privately owned object) which creates issues on deserialization. Attempting // to deserialize these private objects throws a superflous error in the console that makes it look like things went wrong when they're actually OK and expected. TArray OldTracks = ObjectToExport->Binding.StealTracks(nullptr); TArray> OldObjectTemplates; TArray> OldCustomBindings; TMap OldPreviewSpawnables; if (ObjectToExport->Spawnable.GetGuid().IsValid()) { OldObjectTemplates.Add(ObjectToExport->Spawnable.GetObjectTemplate()); ObjectToExport->Spawnable.SetObjectTemplate(nullptr); } else { for (int32 CustomBindingIndex = 0; CustomBindingIndex < ObjectToExport->CustomBindings.Num(); ++CustomBindingIndex) { UMovieSceneCustomBinding* CustomBinding = ObjectToExport->CustomBindings[CustomBindingIndex]; if (UMovieSceneSpawnableBindingBase* SpawnableBinding = Cast(CustomBinding)) { if (SpawnableBinding->SupportsObjectTemplates()) { OldObjectTemplates.Add(SpawnableBinding->GetObjectTemplate()); SpawnableBinding->SetObjectTemplate(nullptr); } } else if (UMovieSceneReplaceableBindingBase * ReplaceableBinding = Cast(CustomBinding)) { // Prevent inner references here during export if (ReplaceableBinding->PreviewSpawnable) { OldPreviewSpawnables.Add(CustomBindingIndex, ReplaceableBinding->PreviewSpawnable); // The Preview Spawnable is next in the CustomBindings list ObjectToExport->PreviewSpawnableBindings.Add(CustomBindingIndex+1); ReplaceableBinding->PreviewSpawnable = nullptr; } } OldCustomBindings.Add(CustomBinding); } ObjectToExport->CustomBindings.Empty(); } ObjectToExport->NumCustomBindings = OldCustomBindings.Num(); ObjectToExport->NumSpawnableObjectTemplates = OldObjectTemplates.Num(); UExporter::ExportToOutputDevice(&Context, ObjectToExport, nullptr, Archive, TEXT("copy"), 0, PPF_ExportsNotFullyQualified | PPF_Copy | PPF_Delimited, false, ThisOuter); // Restore the references (as we don't want to modify the original in the event of a copy operation!) ObjectToExport->Binding.SetTracks(MoveTemp(OldTracks), nullptr); ObjectToExport->CustomBindings.Append(OldCustomBindings); for (UMovieSceneCustomBinding* CustomBinding : ObjectToExport->CustomBindings) { UExporter::ExportToOutputDevice(&Context, CustomBinding, nullptr, Archive, TEXT("copy"), 0, PPF_ExportsNotFullyQualified | PPF_Copy | PPF_Delimited); } // Restore replaceable references now that we've exported them for (const TPair& PreviewSpawnable : OldPreviewSpawnables) { if (UMovieSceneReplaceableBindingBase* ReplaceableBinding = Cast(ObjectToExport->CustomBindings[PreviewSpawnable.Key].Get())) { ReplaceableBinding->PreviewSpawnable = PreviewSpawnable.Value; } } int32 ObjectTemplateIndex = 0; if (ObjectToExport->Spawnable.GetGuid().IsValid()) { ObjectToExport->Spawnable.SetObjectTemplate(OldObjectTemplates[ObjectTemplateIndex++]); } else { for (UMovieSceneCustomBinding* CustomBinding : ObjectToExport->CustomBindings) { if (UMovieSceneSpawnableBindingBase* SpawnableBinding = CustomBinding->AsSpawnable(SharedPlaybackState)) { // Ignore bindings with their template already set, which is possible for Replaceables since they'll show up twice in the list. if (SpawnableBinding->SupportsObjectTemplates() && !SpawnableBinding->GetObjectTemplate()) { SpawnableBinding->SetObjectTemplate(OldObjectTemplates[ObjectTemplateIndex++]); } } } } // We manually export the object templates for the same private-ownership reason as above. Templates need to be re-created anyways as each Spawnable contains its own copy of the template. for (UObject* ObjectTemplate : ObjectToExport->SpawnableObjectTemplates) { UExporter::ExportToOutputDevice(&Context, ObjectTemplate, nullptr, Archive, TEXT("copy"), 0, PPF_ExportsNotFullyQualified | PPF_Copy | PPF_Delimited); } } } void FSequencerUtilities::CopyBindings(TSharedRef Sequencer, const TArray& Bindings, const TArray& InFolders, FString& ExportedText) { FStringOutputDevice Archive; CopyBindings(Sequencer, Bindings, InFolders, (FOutputDevice&)Archive); ExportedText = Archive; } void FSequencerUtilities::CopyBindings(TSharedRef Sequencer, const TArray& Bindings, const TArray& InFolders, FOutputDevice& Ar) { UWorld* World = GCurrentLevelEditingViewportClient ? GCurrentLevelEditingViewportClient->GetWorld() : nullptr; TArray Objects; for (const FMovieSceneBindingProxy& ObjectBinding : Bindings) { UMovieSceneCopyableBinding* CopyableBinding = NewObject(GetTransientPackage(), UMovieSceneCopyableBinding::StaticClass(), NAME_None, RF_Transient); Objects.Add(CopyableBinding); UMovieScene* MovieScene = ObjectBinding.GetMovieScene(); if (!MovieScene) { continue; } FMovieScenePossessable* Possessable = MovieScene->FindPossessable(ObjectBinding.BindingID); if (Possessable) { CopyableBinding->Possessable = *Possessable; // Store any custom bindings if (FMovieSceneBindingReferences* BindingReferences = ObjectBinding.Sequence->GetBindingReferences()) { int32 BindingIndex = 0; for (const FMovieSceneBindingReference& BindingReference : BindingReferences->GetReferences(ObjectBinding.BindingID)) { if (UMovieSceneCustomBinding* CustomBinding = BindingReference.CustomBinding) { CopyableBinding->CustomBindings.Add(CustomBinding); if (UMovieSceneSpawnableBindingBase* SpawnableBinding = CustomBinding->AsSpawnable(Sequencer->GetSharedPlaybackState())) { if (SpawnableBinding->SupportsObjectTemplates()) { // We manually serialize the spawnable object template so that it's not a reference to a privately owned object. Spawnables all have unique copies of their template objects anyways. // Object Templates are re-created on paste (based on these templates) with the correct ownership set up. CopyableBinding->SpawnableObjectTemplates.Add(SpawnableBinding->GetObjectTemplate()); } // This is the inner spawnable of a replaceable and is always placed after the replaceable in the list if (SpawnableBinding != CustomBinding) { CopyableBinding->CustomBindings.Add(SpawnableBinding); } } } else if (UObject* RuntimeObject = MovieSceneHelpers::GetSingleBoundObject(ObjectBinding.Sequence, ObjectBinding.BindingID, Sequencer->GetSharedPlaybackState(), BindingIndex)) { CopyableBinding->BoundObjectNames.Add(RuntimeObject->GetPathName(World)); } BindingIndex++; } } else { // Store the names of the bound objects so that they can be found on paste for (TWeakObjectPtr<> RuntimeObject : Sequencer->FindBoundObjects(CopyableBinding->Possessable.GetGuid(), Sequencer->GetFocusedTemplateID())) { CopyableBinding->BoundObjectNames.Add(RuntimeObject->GetPathName(World)); } } } else { FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(ObjectBinding.BindingID); if (Spawnable) { CopyableBinding->Spawnable = *Spawnable; // We manually serialize the spawnable object template so that it's not a reference to a privately owned object. Spawnables all have unique copies of their template objects anyways. // Object Templates are re-created on paste (based on these templates) with the correct ownership set up. CopyableBinding->SpawnableObjectTemplates.Add(Spawnable->GetObjectTemplate()); } } const FMovieSceneBinding* Binding = MovieScene->FindBinding(ObjectBinding.BindingID); if (Binding) { CopyableBinding->Binding = *Binding; for (UMovieSceneTrack* Track : Binding->GetTracks()) { // Tracks suffer from the same issues as Spawnable's Object Templates (reference to a privately owned object). We'll manually serialize the tracks to copy them, // and then restore them on paste. UMovieSceneTrack* DuplicatedTrack = Cast(StaticDuplicateObject(Track, CopyableBinding)); CopyableBinding->Tracks.Add(DuplicatedTrack); } } UMovieSceneFolder* Folder = nullptr; for (UMovieSceneFolder* RootFolder : MovieScene->GetRootFolders()) { Folder = RootFolder->FindFolderContaining(ObjectBinding.BindingID); if (Folder && InFolders.Contains(Folder)) { UMovieSceneFolder::CalculateFolderPath(Folder, InFolders, CopyableBinding->FolderPath); break; } } for (TPair TaggedBinding : Sequencer->GetRootMovieSceneSequence()->GetMovieScene()->AllTaggedBindings()) { if (TaggedBinding.Value.IDs.Contains(FMovieSceneObjectBindingID(UE::MovieScene::FFixedObjectBindingID(ObjectBinding.BindingID, Sequencer->GetFocusedTemplateID())))) { CopyableBinding->Tags.Add(TaggedBinding.Key); } } } ExportObjectBindingsToText(Objects, /*out*/ Ar, Sequencer->GetSharedPlaybackState()); } class FObjectBindingTextFactory : public FCustomizableTextObjectFactory { public: FObjectBindingTextFactory(ISequencer& InSequencer) : FCustomizableTextObjectFactory(GWarn) , Sequencer(&InSequencer) { } // FCustomizableTextObjectFactory implementation virtual bool CanCreateClass(UClass* InObjectClass, bool& bOmitSubObjs) const override { if (InObjectClass->IsChildOf()) { return true; } else if (InObjectClass->IsChildOf()) { return true; } else { return Sequencer->GetSpawnRegister().CanSpawnObject(InObjectClass); } } virtual void ProcessConstructedObject(UObject* NewObject) override { check(NewObject); if (NewObject->IsA()) { UMovieSceneCopyableBinding* CopyableBinding = Cast(NewObject); NewCopyableBindings.Add(CopyableBinding); } else if (NewObject->IsA()) { NewCustomBindings.Add(Cast(NewObject)); } else { NewSpawnableObjectTemplates.Add(NewObject); } } public: TArray NewCopyableBindings; TArray NewSpawnableObjectTemplates; TArray NewCustomBindings; private: ISequencer* Sequencer; }; void ImportObjectBindingsFromText(ISequencer& InSequencer, const FString& TextToImport, /*out*/ TArray& ImportedObjects) { UPackage* TempPackage = NewObject(nullptr, TEXT("/Engine/Sequencer/Editor/Transient"), RF_Transient); TempPackage->AddToRoot(); // Turn the text buffer into objects FObjectBindingTextFactory Factory(InSequencer); Factory.ProcessBuffer(TempPackage, RF_Transactional, TextToImport); ImportedObjects = Factory.NewCopyableBindings; // We had to explicitly serialize object templates due to them being a reference to a privately owned object. We now deserialize these object template copies // and match them up with their MovieSceneCopyableBinding again. int32 SpawnableObjectTemplateIndex = 0; int32 CustomBindingIndex = 0; for (auto ImportedObject : ImportedObjects) { if (ImportedObject->Spawnable.GetGuid().IsValid()) { // This Spawnable Object Template is owned by our transient package, so you'll need to change the owner if you want to keep it later. ImportedObject->SpawnableObjectTemplates.Add(Factory.NewSpawnableObjectTemplates[SpawnableObjectTemplateIndex++]); } else if (CustomBindingIndex < Factory.NewCustomBindings.Num()) { for(int32 Index = 0; Index < ImportedObject->NumCustomBindings; ++Index) { ImportedObject->CustomBindings.Add(Factory.NewCustomBindings[CustomBindingIndex++]); } if (ImportedObject->CustomBindings.Num() > 0 && SpawnableObjectTemplateIndex < Factory.NewSpawnableObjectTemplates.Num()) { for (UMovieSceneCustomBinding* CustomBinding : ImportedObject->CustomBindings) { if (UMovieSceneSpawnableBindingBase* SpawnableBinding = Cast(CustomBinding)) { if (SpawnableBinding->SupportsObjectTemplates()) { ImportedObject->SpawnableObjectTemplates.Add(Factory.NewSpawnableObjectTemplates[SpawnableObjectTemplateIndex++]); } } } } } } // Remove the temp package from the root now that it has served its purpose TempPackage->RemoveFromRoot(); } FGuid TryCreateCustomBinding(TSharedPtr Sequencer, UObject* CustomBindingObject, AActor* FactoryCreatedActor, FMovieSceneBindingReferences* BindingReferences, const UE::Sequencer::FCreateBindingParams& InParams, UMovieScene* OwnerMovieScene, bool bSpawnable, bool bReplaceable) { UMovieSceneCustomBinding * NewCustomBinding = nullptr; // If the passed in object is a UClass, and we have an actor factory created instance, prioritize that, otherwise let the binding choose if (CustomBindingObject && FactoryCreatedActor && CustomBindingObject->IsA()) { CustomBindingObject = FactoryCreatedActor; FactoryCreatedActor = nullptr; } if (InParams.CustomBinding) { const FMovieSceneBindingReference* PreviousBindingReference = BindingReferences->GetReference(InParams.ReplacementGuid, InParams.BindingIndex); // We've been provided a custom binding pre-created. Ensure it supports the object given if (CustomBindingObject == nullptr || InParams.CustomBinding->SupportsBindingCreationFromObject(CustomBindingObject) || (PreviousBindingReference && InParams.CustomBinding->SupportsConversionFromBinding(*PreviousBindingReference, CustomBindingObject))) { NewCustomBinding = InParams.CustomBinding; } } else { TArrayView> PrioritySortedCustomBindingTypes; if (Sequencer) { PrioritySortedCustomBindingTypes = Sequencer->GetSupportedCustomBindingTypes(); } else { static TArray> CachedCustomBindingTypes; static bool CustomBindingTypesCached = false; if (!CustomBindingTypesCached) { CustomBindingTypesCached = true; MovieSceneHelpers::GetPrioritySortedCustomBindingTypes(CachedCustomBindingTypes); } PrioritySortedCustomBindingTypes = CachedCustomBindingTypes; } for (const TSubclassOf& CustomBindingType : PrioritySortedCustomBindingTypes) { // If 'spawnable' has been passed in, we can use children of UMovieSceneSpawnableBindingBase // If 'replaceable' has been passed in, we can use children of UMovieSceneReplaceableBindingBase // Otherwise if neither has been passed in, we only want to use bindings that aren't children of either. const bool bIsCustomSpawnableBinding = CustomBindingType->IsChildOf(); const bool bIsCustomReplaceableBinding = CustomBindingType->IsChildOf(); if ((bSpawnable && bIsCustomSpawnableBinding) || (bReplaceable && bIsCustomReplaceableBinding) || (!bSpawnable && !bReplaceable && !bIsCustomSpawnableBinding && !bIsCustomReplaceableBinding)) { if (UMovieSceneCustomBinding* CustomBindingCDO = CustomBindingType ? CustomBindingType->GetDefaultObject() : nullptr) { if (CustomBindingObject && CustomBindingCDO->SupportsBindingCreationFromObject(CustomBindingObject)) { // Create a custom binding from this Object NewCustomBinding = CustomBindingCDO->CreateNewCustomBinding(CustomBindingObject, *OwnerMovieScene); if (NewCustomBinding) { break; } } if (!NewCustomBinding && FactoryCreatedActor && CustomBindingCDO->SupportsBindingCreationFromObject(FactoryCreatedActor)) { // Create a custom binding from the factory created actor NewCustomBinding = CustomBindingCDO->CreateNewCustomBinding(FactoryCreatedActor, *OwnerMovieScene); if (NewCustomBinding) { break; } } } } } } if (NewCustomBinding) { FString DesiredBindingName = NewCustomBinding->GetDesiredBindingName(); FString CurrentName = DesiredBindingName.IsEmpty() ? (InParams.BindingNameOverride.IsEmpty() && CustomBindingObject ? FName::NameToDisplayString(CustomBindingObject->GetName(), false) : InParams.BindingNameOverride) : DesiredBindingName; CurrentName = MovieSceneHelpers::MakeUniqueBindingName(OwnerMovieScene, CurrentName); FMovieScenePossessable* NewPossessable = nullptr; FGuid NewID; if (InParams.ReplacementGuid.IsValid()) { NewID = InParams.ReplacementGuid; NewPossessable = OwnerMovieScene->FindPossessable(InParams.ReplacementGuid); } if (!NewPossessable) { // Add a possessable binding track- we will use these even if the custom binding is a 'spawnable' one NewID = OwnerMovieScene->AddPossessable(CurrentName, NewCustomBinding->GetBoundObjectClass()); NewPossessable = OwnerMovieScene->FindPossessable(NewID); } // Add the custom binding BindingReferences->AddOrReplaceBinding(NewID, NewCustomBinding, InParams.BindingIndex); UObject* SpawnedObject = nullptr; // If this is a spawnable or replaceable binding, we need to set up some defaults if (Sequencer) { if (NewCustomBinding->WillSpawnObject(Sequencer->GetSharedPlaybackState())) { // Spawn the object so we can position it correctly, it's going to get spawned anyway since things default to spawned. SpawnedObject = Sequencer->GetSpawnRegister().SpawnObject(NewID, *OwnerMovieScene, Sequencer->GetFocusedTemplateID(), Sequencer->GetSharedPlaybackState(), 0); } } // Allow the binding to set up any necessary defaults NewCustomBinding->SetupDefaults(SpawnedObject, NewID, *OwnerMovieScene); if (Sequencer) { if (InParams.bSetupDefaults) { FTransformData TransformData; Sequencer->GetSpawnRegister().SetupDefaultsForSpawnable(SpawnedObject, NewID, TransformData, Sequencer.ToSharedRef(), Sequencer->GetSequencerSettings()); } Sequencer->GetEvaluationState()->Invalidate(NewID, Sequencer->GetFocusedTemplateID()); Sequencer->ForceEvaluate(); // We don't call these events in the case bSetupDefaults is false because they may add tracks. if (InParams.bSetupDefaults) { if (AActor* Actor = Cast(SpawnedObject)) { Sequencer->OnActorAddedToSequencer().Broadcast(Actor, NewID); } Sequencer->OnAddBinding(NewID, OwnerMovieScene); } } return NewID; } return FGuid(); } FGuid CreateGenericBinding(TSharedPtr Sequencer, UMovieSceneSequence* OwnerSequence, UObject* InObject, FMovieSceneBindingReferences* BindingReferences, const UE::Sequencer::FCreateBindingParams& InParams) { if (!OwnerSequence) { return FGuid(); } using namespace UE::Sequencer; UMovieScene* OwnerMovieScene = OwnerSequence->GetMovieScene(); ISequencerModule& Module = FModuleManager::Get().LoadModuleChecked("Sequencer"); bool bSpawnable = InParams.bSpawnable && OwnerSequence->AllowsSpawnableObjects(); bool bAllowCustom = InParams.bAllowCustomBinding && OwnerSequence->AllowsCustomBindings(); bool bReplaceable = InParams.bReplaceable && bAllowCustom; FGuid NewBindingID; // First see if any custom bindings support creation from this object type directly. if (bAllowCustom) { // In addition to the raw object, we also try spawning an actor from an actor factory if relevant, to give the custom binding an option to create from that as well AActor* FactoryCreatedActorInstance = nullptr; UWorld* World = GCurrentLevelEditingViewportClient ? GCurrentLevelEditingViewportClient->GetWorld() : nullptr; if (InObject && !InObject->IsA()) { // Workaround for a bug in UActorFactoryBlueprint- the actor factory will claim it can create an actor for a blueprint generated class, but then fail to do so // This pattern of redirecting to the UBlueprint asset is present also in FAssetData constructor. const UClass* InClass = Cast(InObject); if (InClass && InClass->ClassGeneratedBy) { InObject = InClass->ClassGeneratedBy; } // If the passed in object is not an actor, see if we can create an Actor from it, and if so, if that Actor type has a custom binding that supports it UActorFactory* FactoryToUse = InParams.ActorFactory ? InParams.ActorFactory.Get() : FActorFactoryAssetProxy::GetFactoryForAssetObject(InObject); if (FactoryToUse) { FText ErrorMessage; if (FactoryToUse->CanCreateActorFrom(FAssetData(InObject), ErrorMessage)) { if (World) { const FName ActorName = MakeUniqueObjectName(World->PersistentLevel, FactoryToUse->NewActorClass->StaticClass(), *InParams.BindingNameOverride); FActorSpawnParameters SpawnParams; SpawnParams.ObjectFlags = RF_Transient | RF_Transactional; SpawnParams.Name = ActorName; FactoryCreatedActorInstance = FactoryToUse->CreateActor(InObject, World->PersistentLevel, FTransform(), SpawnParams); if (FactoryCreatedActorInstance) { FactoryCreatedActorInstance->SetActorLabel(MovieSceneHelpers::MakeUniqueBindingName(OwnerMovieScene, FName::NameToDisplayString(InObject->GetName(), false))); FactoryCreatedActorInstance->bIsEditorPreviewActor = false; } } } } } NewBindingID = TryCreateCustomBinding(Sequencer, InObject, FactoryCreatedActorInstance, BindingReferences, InParams, OwnerMovieScene, bSpawnable, bReplaceable); if (FactoryCreatedActorInstance) { const bool bNetForce = false; const bool bShouldModifyLevel = false; World->DestroyActor(FactoryCreatedActorInstance, bNetForce, bShouldModifyLevel); FactoryCreatedActorInstance = nullptr; } if (NewBindingID.IsValid()) { return NewBindingID; } } if (!InObject && !InParams.bAllowEmptyBinding) { return FGuid(); } // If no custom bindings support this object type, but InParams.bSpawnable is true, attempt to make an old-style spawnable. if (!BindingReferences) { if (InObject && bSpawnable && Sequencer) { NewBindingID = FSequencerUtilities::MakeNewSpawnable(Sequencer.ToSharedRef(), *InObject, InParams.ActorFactory); if (NewBindingID.IsValid()) { return NewBindingID; } } } // Otherwise, create a possessable. TArray> ObjectsToPossess; // Build up the list of child->parent bindings required for this object if (!InObject) { ObjectsToPossess.Add(MakeTuple(InObject, InParams.BindingNameOverride.IsEmpty() ? TEXT("EmptyBinding") : InParams.BindingNameOverride)); } else { UObject* CurrentObject = InObject; while (CurrentObject) { TSharedPtr Schema = Module.FindObjectSchema(CurrentObject); if (Schema) { if (ObjectsToPossess.Num() == 0 && InParams.BindingNameOverride.Len() != 0) { ObjectsToPossess.Add(MakeTuple(CurrentObject, InParams.BindingNameOverride)); } else { ObjectsToPossess.Add(MakeTuple(CurrentObject, Schema->GetPrettyName(CurrentObject).ToString())); } CurrentObject = Schema->GetParentObject(CurrentObject); } else { break; } } } // Nothing to possess? if (ObjectsToPossess.Num() == 0) { // We've failed to find a custom binding type return FGuid(); } const bool bParentContextsAreSignificant = OwnerSequence->AreParentContextsSignificant(); UObject* Context = Sequencer ? Sequencer->GetPlaybackContext() : nullptr; FGuid ParentID; // Iterate in reverse (parent -> child) for (int32 Index = ObjectsToPossess.Num() - 1; Index >= 0; --Index) { UObject* CurrentObject = ObjectsToPossess[Index].Key; // If we're not purposefully replacing a binding, then check to see if we already have one, and use that if (Sequencer && !InParams.ReplacementGuid.IsValid()) { FGuid ObjectGuid = Sequencer->GetHandleToObject(CurrentObject, false); // If the object already has a binding, use that and move on if (ObjectGuid.IsValid()) { ParentID = ObjectGuid; if (bParentContextsAreSignificant) { Context = CurrentObject; } continue; } } // Create a new binding for this object FString CurrentName = MoveTemp(ObjectsToPossess[Index].Value); FMovieScenePossessable* NewPossessable = nullptr; FGuid NewID; if (InParams.ReplacementGuid.IsValid() && !ParentID.IsValid()) { NewID = InParams.ReplacementGuid; NewPossessable = OwnerMovieScene->FindPossessable(InParams.ReplacementGuid); } if (!NewPossessable) { NewID = OwnerMovieScene->AddPossessable(CurrentName, CurrentObject ? CurrentObject->GetClass() : UObject::StaticClass()); NewPossessable = OwnerMovieScene->FindPossessable(NewID); } // If we're not trying to replace a binding, and the object is a spawnable, try and bind to that first if (InParams.ReplacementGuid.IsValid() || (Sequencer && (!CurrentObject || !NewPossessable->BindSpawnableObject(Sequencer->GetFocusedTemplateID(), CurrentObject, Sequencer->GetSharedPlaybackState())))) { FUniversalObjectLocator Locator; if (CurrentObject && (!OwnerSequence->MakeLocatorForObject(CurrentObject, Context, Locator) || Locator.IsEmpty())) { // Unable to possess this object return FGuid(); } if (InParams.ReplacementGuid.IsValid() && !ParentID.IsValid()) { BindingReferences->AddOrReplaceBinding(NewID, MoveTemp(Locator), InParams.BindingIndex); if (Sequencer) { Sequencer->GetEvaluationState()->Invalidate(NewID, Sequencer->GetFocusedTemplateID()); } } else { BindingReferences->AddBinding(NewID, MoveTemp(Locator)); } } if (ParentID.IsValid()) { NewPossessable->SetParent(ParentID, OwnerMovieScene); FMovieSceneSpawnable* ParentSpawnable = OwnerMovieScene->FindSpawnable(ParentID); if (ParentSpawnable) { ParentSpawnable->AddChildPossessable(NewID); } } ParentID = NewID; if (Sequencer) { if (AActor* Actor = Cast(CurrentObject)) { Sequencer->OnActorAddedToSequencer().Broadcast(Actor, NewID); } } // If this is the last one if (Index == 0) { if (Sequencer) { Sequencer->OnAddBinding(NewID, OwnerMovieScene); } return NewID; } if (bParentContextsAreSignificant) { Context = CurrentObject; } } // Should never get here - we should always hit the Index == 0 condition inside the loop return FGuid(); } bool FSequencerUtilities::PasteBindings(const FString& TextToImport, TSharedRef Sequencer, FMovieScenePasteBindingsParams PasteBindingsParams, TArray& OutBindings, TArray& OutErrors) { UMovieSceneSequence* Sequence = Sequencer->GetFocusedMovieSceneSequence(); if (!Sequence) { return false; } FMovieSceneBindingReferences* BindingReferences = Sequence->GetBindingReferences(); UMovieScene* MovieScene = Sequence->GetMovieScene(); if (!MovieScene) { return false; } UMovieScene* RootMovieScene = Sequencer->GetRootMovieSceneSequence()->GetMovieScene(); UWorld* World = GCurrentLevelEditingViewportClient ? GCurrentLevelEditingViewportClient->GetWorld() : nullptr; const FScopedTransaction Transaction(LOCTEXT("PasteBindings", "Paste Bindings")); TMap OldToNewGuidMap; TArray PossessableGuids; TArray > PossessableObjectNames; TArray SpawnableGuids; TMap GuidToFolderMap; TArray BindingsPasted; const int NumTargets = FMath::Max(1, PasteBindingsParams.Bindings.Num()); for (int32 TargetIndex = 0; TargetIndex < NumTargets; ++TargetIndex) { TArray ImportedBindings; ImportObjectBindingsFromText(Sequencer.Get(), TextToImport, ImportedBindings); if (ImportedBindings.Num() == 0) { return false; } TArray SectionSubObjects; for (UMovieSceneCopyableBinding* CopyableBinding : ImportedBindings) { // Clear transient flags on the imported tracks for (UMovieSceneTrack* CopiedTrack : CopyableBinding->Tracks) { ResetCopiedTracksFlags(CopiedTrack); } UMovieSceneFolder* ParentFolder = PasteBindingsParams.ParentFolder; if (CopyableBinding->FolderPath.Num() > 0) { ParentFolder = UMovieSceneFolder::GetFolderWithPath(CopyableBinding->FolderPath, PasteBindingsParams.Folders, ParentFolder ? ParentFolder->GetChildFolders() : MovieScene->GetRootFolders()); } if (CopyableBinding->Possessable.GetGuid().IsValid()) { // TODO: We likely need additional work here for possessable bindings using locators other than actor locators. // For now, we'll at least handle the custom binding case // If we have a custom binding, we need to let the sequence create it, especially since it could have a spawnable template. // However, making a new custom spawnable also creates the binding for us - this is a problem // because we need to use our binding (which has tracks associated with it). To solve this, we let it create // an object template based off of our (transient package owned) template, then find the newly created binding // and update it. FGuid NewGuid; if (!CopyableBinding->CustomBindings.IsEmpty()) { if (BindingReferences) { int32 SpawnableBindingIndex = 0; UMovieSceneCustomBinding* PreviousBinding = nullptr; for(int32 BindingIndex = 0; BindingIndex < CopyableBinding->CustomBindings.Num(); ++BindingIndex) { UMovieSceneCustomBinding* CustomBinding = CopyableBinding->CustomBindings[BindingIndex]; if (CustomBinding) { UMovieSceneCustomBinding* NewCustomBinding = Cast(StaticDuplicateObject(CustomBinding, MovieScene)); // Need to re-copy the object template to avoid private object issues if (UMovieSceneSpawnableBindingBase* SpawnableBinding = Cast(NewCustomBinding)) { if (CopyableBinding->SpawnableObjectTemplates.IsValidIndex(SpawnableBindingIndex)) { UObject* SpawnableObjectTemplate = CopyableBinding->SpawnableObjectTemplates[SpawnableBindingIndex++]; if (SpawnableObjectTemplate) { UObject* NewObjectTemplate = StaticDuplicateObject(SpawnableObjectTemplate, MovieScene); SpawnableBinding->SetObjectTemplate(NewObjectTemplate); } } // If this is a preview spawnable, find the just added replaceable and link with that rather than creating a new binding if (CopyableBinding->PreviewSpawnableBindings.Contains(BindingIndex)) { if (UMovieSceneReplaceableBindingBase* PreviousReplaceableBinding = Cast(PreviousBinding)) { PreviousReplaceableBinding->PreviewSpawnable = SpawnableBinding; PreviousBinding = NewCustomBinding; continue; } } } // This will either add a brand new possessable and binding (if one doesn't exist for that guid), or just add a new binding to that same possessable UE::Sequencer::FCreateBindingParams CreateBindingParams; CreateBindingParams.ReplacementGuid = NewGuid; CreateBindingParams.BindingIndex = BindingIndex; CreateBindingParams.bAllowCustomBinding = true; CreateBindingParams.CustomBinding = NewCustomBinding; CreateBindingParams.bSetupDefaults = false; CreateBindingParams.BindingNameOverride = CopyableBinding->Possessable.GetName(); NewGuid = CreateGenericBinding(Sequencer, Sequence, nullptr, BindingReferences, CreateBindingParams); PreviousBinding = NewCustomBinding; } } } } else { FMovieScenePossessable NewPossessable = CopyableBinding->Possessable; NewPossessable.SetGuid(FGuid::NewGuid()); MovieScene->AddPossessable(NewPossessable, FMovieSceneBinding(NewPossessable.GetGuid(), NewPossessable.GetName())); NewGuid = NewPossessable.GetGuid(); } FMovieSceneBinding NewBinding(NewGuid, CopyableBinding->Binding.GetName(), CopyableBinding->Tracks); FMovieScenePossessable* Possessable = MovieScene->FindPossessable(NewGuid); // Clear the transient flags on the copyable binding before assigning to the new possessable for (UMovieSceneTrack* Track : NewBinding.GetTracks()) { ResetCopiedTracksFlags(Track); } // Replace the auto-generated binding with our deserialized bindings (which has our tracks) MovieScene->ReplaceBinding(NewGuid, NewBinding); OldToNewGuidMap.Add(CopyableBinding->Possessable.GetGuid(), NewGuid); BindingsPasted.Add(NewBinding); PossessableGuids.Add(NewGuid); if (ParentFolder) { GuidToFolderMap.Add(NewGuid, ParentFolder); } if (CopyableBinding->Tags.Num() > 0) { RootMovieScene->Modify(); for (const FName& Tag : CopyableBinding->Tags) { RootMovieScene->TagBinding(Tag, UE::MovieScene::FFixedObjectBindingID(NewGuid, Sequencer->GetFocusedTemplateID())); } } // Find the objects that this pasted binding should bind to TArray ObjectsToBind; UObject* ResolutionContext = FindResolutionContext(Sequencer , *MovieScene->GetTypedOuter() , *MovieScene , Possessable->GetParent() , Sequencer->GetPlaybackContext()); if (World) { for (TActorIterator ActorItr(World); ActorItr; ++ActorItr) { AActor* Actor = *ActorItr; if (Actor && CopyableBinding->BoundObjectNames.Contains(Actor->GetPathName(World))) { // If this actor is already bound and we're not duplicating actors, don't bind to anything if (!PasteBindingsParams.bDuplicateExistingActors && Sequencer->FindObjectId(*Actor, Sequencer->GetFocusedTemplateID()).IsValid()) { continue; } ObjectsToBind.Add(Actor); CopyableBinding->BoundObjectNames.Remove(Actor->GetPathName(World)); } } } bool bSetParent = false; if (const UClass* PossessedObjectClass = CopyableBinding->Possessable.GetPossessedObjectClass()) { if (!PossessedObjectClass->IsChildOf(AActor::StaticClass())) { // Attempt to set the parent to be the paste target only if the possessed object class is not an actor bSetParent = true; } } else if (ObjectsToBind.IsEmpty()) { // Attempt to set the parent to be the paste target only if the binding does not resolve to an actor in the world. bSetParent = true; } if (bSetParent) { if (TargetIndex < PasteBindingsParams.Bindings.Num()) { Possessable->SetParent(PasteBindingsParams.Bindings[TargetIndex].BindingID, MovieScene); } } if (ObjectsToBind.Num() != 0) { if (PasteBindingsParams.bDuplicateExistingActors) { GEditor->SelectNone(false, true); TArray SelectedObjects; for (UObject* ObjectToBind : ObjectsToBind) { if (AActor* Actor = Cast(ObjectToBind)) { GEditor->SelectActor(Actor, true, false, false); SelectedObjects.Add(Actor); } } // Duplicate the bound actors GEditor->edactDuplicateSelected(World->GetCurrentLevel(), false); // Duplicating the bound actor through GEditor, edits the copy/paste clipboard. This is not desired from the user's // point of view since the user didn't explicitly invoke the copy operation. Instead, restore the copied contents // of the clipboard after duplicating the actor FPlatformApplicationMisc::ClipboardCopy(*TextToImport); ObjectsToBind.RemoveAll([&SelectedObjects](UObject* Object) { return SelectedObjects.Contains(Object);}); USelection* ActorSelection = GEditor->GetSelectedActors(); for (FSelectionIterator Iter(*ActorSelection); Iter; ++Iter) { AActor* Actor = Cast(*Iter); if (Actor) { ObjectsToBind.Add(Actor); CopyableBinding->BoundObjectNames.Add(Actor->GetPathName(ResolutionContext)); } } } // Bind the actors if (ObjectsToBind.Num()) { AddObjectsToBinding(Sequencer, ObjectsToBind, FMovieSceneBindingProxy(NewGuid, Sequence), ResolutionContext); } } PossessableObjectNames.Add(CopyableBinding->BoundObjectNames); } else if (CopyableBinding->Spawnable.GetGuid().IsValid()) { // We need to let the sequence create the spawnable so that it has everything set up properly internally. // This is required to get spawnables with the correct references to object templates, object templates with // correct owners, etc. However, making a new spawnable also creates the binding for us - this is a problem // because we need to use our binding (which has tracks associated with it). To solve this, we let it create // an object template based off of our (transient package owned) template, then find the newly created binding // and update it. FGuid NewGuid; if (!CopyableBinding->SpawnableObjectTemplates.IsEmpty()) { NewGuid = MakeNewSpawnable(Sequencer, *CopyableBinding->SpawnableObjectTemplates[0], nullptr, false, FName(*CopyableBinding->Spawnable.GetName())); } else { FMovieSceneSpawnable NewSpawnable{}; NewSpawnable.SetGuid(FGuid::NewGuid()); NewSpawnable.SetName(CopyableBinding->Spawnable.GetName()); MovieScene->AddSpawnable(NewSpawnable, FMovieSceneBinding(NewSpawnable.GetGuid(), NewSpawnable.GetName())); NewGuid = NewSpawnable.GetGuid(); } FMovieSceneBinding NewBinding(NewGuid, CopyableBinding->Binding.GetName(), CopyableBinding->Tracks); FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(NewGuid); // Copy the name of the original spawnable too. Spawnable->SetName(CopyableBinding->Spawnable.GetName()); // Clear the transient flags on the copyable binding before assigning to the new spawnable for (auto Track : NewBinding.GetTracks()) { ResetCopiedTracksFlags(Track); } // Replace the auto-generated binding with our deserialized bindings (which has our tracks) MovieScene->ReplaceBinding(NewGuid, NewBinding); OldToNewGuidMap.Add(CopyableBinding->Spawnable.GetGuid(), NewGuid); BindingsPasted.Add(NewBinding); SpawnableGuids.Add(NewGuid); if (ParentFolder) { GuidToFolderMap.Add(NewGuid, ParentFolder); } if (CopyableBinding->Tags.Num() > 0) { RootMovieScene->Modify(); for (const FName& Tag : CopyableBinding->Tags) { RootMovieScene->TagBinding(Tag, UE::MovieScene::FFixedObjectBindingID(NewGuid, Sequencer->GetFocusedTemplateID())); } } } } } // Fix possessable actor bindings for (int32 PossessableGuidIndex = 0; PossessableGuidIndex < PossessableGuids.Num(); ++PossessableGuidIndex) { if (BindingReferences && Algo::AnyOf(BindingReferences->GetReferences(PossessableGuids[PossessableGuidIndex]), [](const FMovieSceneBindingReference& Reference) {return Reference.CustomBinding; })) { continue; } FMovieScenePossessable* Possessable = MovieScene->FindPossessable(PossessableGuids[PossessableGuidIndex]); if (Possessable && World) { auto AddActor = [&](AActor* Actor) { FGuid ExistingGuid = Sequencer->FindObjectId(*Actor, Sequencer->GetFocusedTemplateID()); if (!ExistingGuid.IsValid()) { FGuid NewGuid = AssignActor(Sequencer, Actor, Possessable->GetGuid()); // If assigning produces a new guid, update the possessable guids and the bindings pasted data if (NewGuid.IsValid()) { for (auto BindingPasted : BindingsPasted) { if (BindingPasted.GetObjectGuid() == PossessableGuids[PossessableGuidIndex]) { BindingPasted.SetObjectGuid(NewGuid); } } if (GuidToFolderMap.Contains(PossessableGuids[PossessableGuidIndex])) { GuidToFolderMap.Add(NewGuid, GuidToFolderMap[PossessableGuids[PossessableGuidIndex]]); GuidToFolderMap.Remove(PossessableGuids[PossessableGuidIndex]); } for (TPair& OldToNewGuid : OldToNewGuidMap) { if (OldToNewGuid.Value == PossessableGuids[PossessableGuidIndex]) { OldToNewGuid.Value = NewGuid; } } PossessableGuids[PossessableGuidIndex] = NewGuid; } } }; for (TActorIterator ActorItr(World); ActorItr; ++ActorItr) { AActor* Actor = *ActorItr; FString ActorName = Actor ? Actor->GetName() : TEXT(""); if (Actor && PossessableGuidIndex < PossessableObjectNames.Num() && PossessableObjectNames[PossessableGuidIndex].Contains(Actor->GetName())) { AddActor(Actor); } } // If pasted actors have been provided, go through those as well for (const TPair>& PastedActorPair : PasteBindingsParams.PastedActors) { if (PastedActorPair.Value && PossessableGuidIndex < PossessableObjectNames.Num()) { FString PastedActorPairName = ObjectTools::SanitizeObjectPath(PastedActorPair.Key.ToString()); if (Algo::AnyOf(PossessableObjectNames[PossessableGuidIndex], [&PastedActorPairName] (const FString& PathName) { FString SanitizedObjectPath = ObjectTools::SanitizeObjectPath(PathName); int32 LastPeriod = INDEX_NONE; if (SanitizedObjectPath.FindLastChar('.', LastPeriod)) { return SanitizedObjectPath.RightChop(LastPeriod+1) == PastedActorPairName; } return SanitizedObjectPath == PastedActorPairName; })) { AddActor(PastedActorPair.Value.Get()); } } } } } // Fix up parent guids for (auto PossessableGuid : PossessableGuids) { FMovieScenePossessable* Possessable = MovieScene->FindPossessable(PossessableGuid); if (Possessable && OldToNewGuidMap.Contains(Possessable->GetParent()) && PossessableGuid != OldToNewGuidMap[Possessable->GetParent()]) { Possessable->SetParent(OldToNewGuidMap[Possessable->GetParent()], MovieScene); } } // Set up folders for (auto PossessableGuid : PossessableGuids) { FMovieScenePossessable* Possessable = MovieScene->FindPossessable(PossessableGuid); if (Possessable && !Possessable->GetParent().IsValid()) { if (GuidToFolderMap.Contains(PossessableGuid)) { GuidToFolderMap[PossessableGuid]->AddChildObjectBinding(PossessableGuid); } } } for (auto SpawnableGuid : SpawnableGuids) { FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(SpawnableGuid); if (Spawnable) { if (GuidToFolderMap.Contains(SpawnableGuid)) { GuidToFolderMap[SpawnableGuid]->AddChildObjectBinding(SpawnableGuid); } } } Sequencer->OnMovieSceneBindingsPasted().Broadcast(BindingsPasted); // Refresh all immediately so that spawned actors will be generated immediately Sequencer->ForceEvaluate(); // Fix possessable subobject bindings for (int32 PossessableGuidIndex = 0; PossessableGuidIndex < PossessableGuids.Num(); ++PossessableGuidIndex) { FGuid PossessableGuid = PossessableGuids[PossessableGuidIndex]; // If a possessable guid does not have any bound objects, they might be // possessable components for spawnables, so they need to be remapped if (Sequencer->FindBoundObjects(PossessableGuid, Sequencer->GetFocusedTemplateID()).Num() == 0) { FMovieScenePossessable* Possessable = MovieScene->FindPossessable(PossessableGuid); if (Possessable) { FGuid ParentGuid = Possessable->GetParent(); bool bBound = false; for (TWeakObjectPtr<> WeakObject : Sequencer->FindBoundObjects(ParentGuid, Sequencer->GetFocusedTemplateID())) { if (AActor* SpawnedActor = Cast(WeakObject.Get())) { for (UActorComponent* Component : SpawnedActor->GetComponents()) { if (Component->GetName() == Possessable->GetName()) { Sequence->BindPossessableObject(PossessableGuid, *Component, SpawnedActor); bBound = true; break; } } } if (!bBound) { if (PossessableGuidIndex < PossessableObjectNames.Num()) { for (const FString& BoundObjectPath : PossessableObjectNames[PossessableGuidIndex]) { if (UObject* FoundObject = FindObject(WeakObject.Get(), *BoundObjectPath)) { Sequence->BindPossessableObject(PossessableGuid, *FoundObject, WeakObject.Get()); bBound = true; break; } } } } } // If the parent doesn't actually exist, clear it. FMovieScenePossessable* PossessableParent = MovieScene->FindPossessable(ParentGuid); FMovieSceneSpawnable* SpawnableParent = MovieScene->FindSpawnable(ParentGuid); if (!PossessableParent && !SpawnableParent) { Possessable->SetParent(FGuid(), MovieScene); } else if (SpawnableParent) { SpawnableParent->AddChildPossessable(PossessableGuid); } } } } // Find all the sections that have been added and only remap bindings in those sections TSet Sections; for (auto BindingPasted : BindingsPasted) { if (FMovieSceneBinding* Binding = MovieScene->FindBinding(BindingPasted.GetObjectGuid())) { for (UMovieSceneTrack* Track : Binding->GetTracks()) { for (UMovieSceneSection* Section : Track->GetAllSections()) { Sections.Add(Section); } } } } if (Sections.Num() != 0) { FMovieSceneSequenceIDRef FocusedGuid = Sequencer->GetFocusedTemplateID(); TMap OldFixedToNewFixedMap; TSharedRef SharedPlaybackState = Sequencer->GetSharedPlaybackState(); for (TPair GuidPair : OldToNewGuidMap) { OldFixedToNewFixedMap.Add(UE::MovieScene::FFixedObjectBindingID(GuidPair.Key, FocusedGuid), UE::MovieScene::FFixedObjectBindingID(GuidPair.Value, FocusedGuid)); } for (UMovieSceneSection* Section : Sections) { Section->OnBindingIDsUpdated(OldFixedToNewFixedMap, FocusedGuid, SharedPlaybackState); } } for (auto BindingPasted : BindingsPasted) { OutBindings.Add(FMovieSceneBindingProxy(BindingPasted.GetObjectGuid(), Sequence)); Sequencer->OnAddBinding(BindingPasted.GetObjectGuid(), MovieScene); } return true; } bool FSequencerUtilities::CanPasteBindings(TSharedRef Sequencer, const FString& TextToImport) { FObjectBindingTextFactory ObjectBindingFactory(Sequencer.Get()); return ObjectBindingFactory.CanCreateObjectsFromText(TextToImport); } TArray FSequencerUtilities::GetPasteBindingsObjectNames(TSharedRef Sequencer, const FString& TextToImport) { TArray ObjectNames; TArray ImportedBindings; ImportObjectBindingsFromText(Sequencer.Get(), TextToImport, ImportedBindings); for (UMovieSceneCopyableBinding* CopyableBinding : ImportedBindings) { if (CopyableBinding) { for (const FString& BoundObjectName : CopyableBinding->BoundObjectNames) { ObjectNames.Add(BoundObjectName); } } } return ObjectNames; } FGuid CreateImplementationDefinedBinding(TSharedRef Sequencer, UObject& InObject, const UE::Sequencer::FCreateBindingParams& InParams) { UMovieSceneSequence* OwnerSequence = Sequencer->GetFocusedMovieSceneSequence(); UMovieScene* OwnerMovieScene = OwnerSequence->GetMovieScene(); AActor* Actor = Cast(&InObject); FString Name = InParams.BindingNameOverride.Len() > 0 ? InParams.BindingNameOverride : (Actor != nullptr ? Actor->GetActorLabel() : InObject.GetName()); FGuid PossessableGuid = OwnerMovieScene->AddPossessable(Name, InObject.GetClass()); // Attempt to use the parent as a context if necessary UObject* ParentObject = OwnerSequence->GetParentObject(&InObject); UObject* BindingContext = Sequencer->GetPlaybackContext(); AActor* ParentActorAdded = nullptr; FGuid ParentGuid; if (ParentObject) { // Ensure we have possessed the outer object, if necessary ParentGuid = Sequencer->GetHandleToObject(ParentObject, false); if (!ParentGuid.IsValid()) { ParentGuid = Sequencer->GetHandleToObject(ParentObject); ParentActorAdded = Cast(ParentObject); } if (OwnerSequence->AreParentContextsSignificant()) { BindingContext = ParentObject; } // Set up parent/child guids for possessables within spawnables if (ParentGuid.IsValid()) { FMovieScenePossessable* ChildPossessable = OwnerMovieScene->FindPossessable(PossessableGuid); if (ensure(ChildPossessable)) { ChildPossessable->SetParent(ParentGuid, OwnerMovieScene); } FMovieSceneSpawnable* ParentSpawnable = OwnerMovieScene->FindSpawnable(ParentGuid); if (ParentSpawnable) { ParentSpawnable->AddChildPossessable(PossessableGuid); } } } FMovieScenePossessable* NewPossessable = OwnerMovieScene->FindPossessable(PossessableGuid); if (!NewPossessable->BindSpawnableObject(Sequencer->GetFocusedTemplateID(), &InObject, Sequencer->GetSharedPlaybackState())) { OwnerSequence->BindPossessableObject(PossessableGuid, InObject, BindingContext); } // Broadcast if a parent actor was added as a result of adding this object if (ParentActorAdded && ParentGuid.IsValid()) { Sequencer->OnActorAddedToSequencer().Broadcast(ParentActorAdded, ParentGuid); } Sequencer->OnAddBinding(PossessableGuid, OwnerMovieScene); if (Actor) { Sequencer->OnActorAddedToSequencer().Broadcast(Actor, PossessableGuid); } return PossessableGuid; } UObject* FSequencerUtilities::FindResolutionContext(TSharedRef InSequencer , UMovieSceneSequence& InSequence , UMovieScene& InMovieScene , const FGuid& InParentGuid , UObject* InPlaybackContext) { if (!InPlaybackContext || !InParentGuid.IsValid() || !InSequence.AreParentContextsSignificant()) { return InPlaybackContext; } UObject* ResolutionContext = nullptr; // Recursive call up the hierarchy if (FMovieScenePossessable* const ParentPossessable = InMovieScene.FindPossessable(InParentGuid)) { ResolutionContext = FSequencerUtilities::FindResolutionContext(InSequencer , InSequence , InMovieScene , ParentPossessable->GetParent() , InPlaybackContext); } if (!ResolutionContext) { ResolutionContext = InPlaybackContext; } TArray> FoundObjects; for (TWeakObjectPtr<> WeakObj : InSequencer->FindBoundObjects(InParentGuid, InSequencer->GetFocusedTemplateID())) { FoundObjects.Add(WeakObj.Get()); } if (FoundObjects.IsEmpty()) { return ResolutionContext; } return FoundObjects[0] ? FoundObjects[0] : ResolutionContext; } FGuid FSequencerUtilities::CreateBinding(TSharedRef Sequencer, UObject& InObject, const UE::Sequencer::FCreateBindingParams& InParams) { return CreateOrReplaceBinding(Sequencer.ToSharedPtr(), Sequencer->GetFocusedMovieSceneSequence(), &InObject, InParams); } FGuid FSequencerUtilities::CreateOrReplaceBinding(TSharedRef Sequencer, UObject* InObject, const UE::Sequencer::FCreateBindingParams& InParams) { return CreateOrReplaceBinding(Sequencer.ToSharedPtr(), Sequencer->GetFocusedMovieSceneSequence(), InObject, InParams); } FGuid FSequencerUtilities::CreateOrReplaceBinding(TSharedPtr Sequencer, UMovieSceneSequence* OwnerSequence, UObject* InObject, const UE::Sequencer::FCreateBindingParams& InParams) { if (!OwnerSequence) return FGuid(); const FScopedTransaction Transaction(LOCTEXT("CreateBinding", "Create New Binding")); UMovieScene* OwnerMovieScene = OwnerSequence->GetMovieScene(); OwnerSequence->Modify(); OwnerMovieScene->Modify(); FGuid BindingGuid; FMovieSceneBindingReferences* BindingReferences = OwnerSequence->GetBindingReferences(); if (BindingReferences) { BindingGuid = CreateGenericBinding(Sequencer, OwnerSequence, InObject, BindingReferences, InParams); } else if (Sequencer && InParams.bSpawnable && InObject) { // Create an old-style spawnable BindingGuid = MakeNewSpawnable(Sequencer.ToSharedRef(), *InObject, InParams.ActorFactory.Get()); } else if (Sequencer && InObject) { BindingGuid = CreateImplementationDefinedBinding(Sequencer.ToSharedRef(), *InObject, InParams); } if (!BindingGuid.IsValid()) { return FGuid(); } if (InParams.DesiredFolder != NAME_None) { // Find the outermost object and put it in a folder of the specified name. FGuid RootObjectGuid = BindingGuid; while (true) { // This only applies to possessables/custom bindings, as old-style spawnables will not have parents. FMovieScenePossessable* Possessable = OwnerMovieScene->FindPossessable(RootObjectGuid); if (!Possessable || !Possessable->GetParent().IsValid()) { break; } RootObjectGuid = Possessable->GetParent(); } UMovieSceneFolder* DestinationFolder = nullptr; for (UMovieSceneFolder* Folder : OwnerMovieScene->GetRootFolders()) { if (Folder->GetFolderName() == InParams.DesiredFolder) { DestinationFolder = Folder; break; } } // If we didn't find a folder with the desired name then we create a new folder as a sibling of the existing folders. if (DestinationFolder == nullptr) { DestinationFolder = NewObject(OwnerMovieScene, NAME_None, RF_Transactional); DestinationFolder->SetFolderName(InParams.DesiredFolder); OwnerMovieScene->AddRootFolder(DestinationFolder); DestinationFolder->AddChildObjectBinding(RootObjectGuid); } else { DestinationFolder->AddChildObjectBinding(RootObjectGuid); } } if (Sequencer) { if (ACameraActor* NewCamera = Cast(InObject)) { NewCameraAdded(Sequencer.ToSharedRef(), NewCamera, BindingGuid); } Sequencer->OnAddBinding(BindingGuid, OwnerMovieScene); } return BindingGuid; } void FSequencerUtilities::UpdateBindingIDs(TSharedRef Sequencer, FGuid OldGuid, FGuid NewGuid) { UMovieSceneCompiledDataManager* CompiledDataManager = FindObject(GetTransientPackage(), TEXT("SequencerCompiledDataManager")); if (!CompiledDataManager) { CompiledDataManager = NewObject(GetTransientPackage(), "SequencerCompiledDataManager"); } if (!CompiledDataManager) { return; } const FMovieSceneSequenceHierarchy* Hierarchy = CompiledDataManager->FindHierarchy(Sequencer->GetEvaluationTemplate().GetCompiledDataID()); FMovieSceneSequenceIDRef FocusedGuid = Sequencer->GetFocusedTemplateID(); TMap OldFixedToNewFixedMap; OldFixedToNewFixedMap.Add(UE::MovieScene::FFixedObjectBindingID(OldGuid, FocusedGuid), UE::MovieScene::FFixedObjectBindingID(NewGuid, FocusedGuid)); TSharedRef SharedPlaybackState = Sequencer->GetSharedPlaybackState(); if (UMovieScene* MovieScene = Sequencer->GetRootMovieSceneSequence()->GetMovieScene()) { for (UMovieSceneSection* Section : MovieScene->GetAllSections()) { if (Section) { Section->OnBindingIDsUpdated(OldFixedToNewFixedMap, Sequencer->GetRootTemplateID(), SharedPlaybackState); } } } if (Hierarchy) { for (const TTuple& Pair : Hierarchy->AllSubSequenceData()) { if (UMovieSceneSequence* Sequence = Pair.Value.GetSequence()) { if (UMovieScene* MovieScene = Sequence->GetMovieScene()) { for (UMovieSceneSection* Section : MovieScene->GetAllSections()) { if (Section) { Section->OnBindingIDsUpdated(OldFixedToNewFixedMap, Pair.Key, SharedPlaybackState); } } } } } } } FGuid FSequencerUtilities::AssignActor(TSharedRef Sequencer, AActor* Actor, FGuid InObjectBinding) { if (Actor == nullptr) { return FGuid(); } UMovieSceneSequence* OwnerSequence = Sequencer->GetFocusedMovieSceneSequence(); UMovieScene* OwnerMovieScene = OwnerSequence->GetMovieScene(); if (OwnerMovieScene->IsReadOnly()) { ShowReadOnlyError(); return FGuid(); } FScopedTransaction AssignActor(LOCTEXT("AssignActor", "Assign Actor")); Actor->Modify(); OwnerSequence->Modify(); OwnerMovieScene->Modify(); TArrayView> RuntimeObjects = Sequencer->FindObjectsInCurrentSequence(InObjectBinding); UObject* RuntimeObject = RuntimeObjects.Num() ? RuntimeObjects[0].Get() : nullptr; // Replace the object itself FMovieScenePossessable NewPossessableActor; FGuid NewGuid; { // Get the object guid to assign, remove the binding if it already exists FGuid ParentGuid = Sequencer->FindObjectId(*Actor, Sequencer->GetFocusedTemplateID()); FString NewActorLabel = Actor->GetActorLabel(); if (ParentGuid.IsValid()) { OwnerMovieScene->RemovePossessable(ParentGuid); OwnerSequence->UnbindPossessableObjects(ParentGuid); } // Add this object NewPossessableActor = FMovieScenePossessable(NewActorLabel, Actor->GetClass()); NewGuid = NewPossessableActor.GetGuid(); if (!NewPossessableActor.BindSpawnableObject(Sequencer->GetFocusedTemplateID(), Actor, Sequencer->GetSharedPlaybackState())) { OwnerSequence->BindPossessableObject(NewPossessableActor.GetGuid(), *Actor, Sequencer->GetPlaybackContext()); } // Defer replacing this object until the components have been updated } auto UpdateComponent = [&](FGuid OldComponentGuid, UActorComponent* NewComponent, TArray& NewComponentGuids) { FMovieSceneSequenceIDRef FocusedGuid = Sequencer->GetFocusedTemplateID(); // Get the object guid to assign, remove the binding if it already exists FGuid NewComponentGuid = Sequencer->FindObjectId(*NewComponent, FocusedGuid); if (NewComponentGuid.IsValid()) { OwnerMovieScene->RemovePossessable(NewComponentGuid); OwnerSequence->UnbindPossessableObjects(NewComponentGuid); } // Add this object FMovieScenePossessable NewPossessable(NewComponent->GetName(), NewComponent->GetClass()); OwnerSequence->BindPossessableObject(NewPossessable.GetGuid(), *NewComponent, Actor); // Replace OwnerMovieScene->ReplacePossessable(OldComponentGuid, NewPossessable); OwnerSequence->UnbindPossessableObjects(OldComponentGuid); FMovieSceneEvaluationState* State = Sequencer->GetEvaluationState(); State->Invalidate(OldComponentGuid, FocusedGuid); State->Invalidate(NewPossessable.GetGuid(), FocusedGuid); NewComponentGuids.Add(NewPossessable.GetGuid()); }; TArray NewComponentGuids; // Handle components AActor* ActorToReplace = Cast(RuntimeObject); if (ActorToReplace != nullptr && ActorToReplace->IsActorBeingDestroyed() == false) { for (UActorComponent* ComponentToReplace : ActorToReplace->GetComponents()) { if (ComponentToReplace != nullptr) { FGuid ComponentGuid = Sequencer->FindObjectId(*ComponentToReplace, Sequencer->GetFocusedTemplateID()); if (ComponentGuid.IsValid()) { bool bComponentWasUpdated = false; for (UActorComponent* NewComponent : Actor->GetComponents()) { if (NewComponent->GetFullName(Actor) == ComponentToReplace->GetFullName(ActorToReplace)) { UpdateComponent(ComponentGuid, NewComponent, NewComponentGuids); bComponentWasUpdated = true; } } // Clear the parent guid since this possessable component doesn't match to any component on the new actor if (!bComponentWasUpdated) { FMovieScenePossessable* ThisPossessable = OwnerMovieScene->FindPossessable(ComponentGuid); ThisPossessable->SetParent(FGuid(), OwnerMovieScene); } } } } } else // If the actor didn't exist, try to find components who's parent guids were the previous actors guid. { TMap ComponentNameToComponent; for (UActorComponent* Component : Actor->GetComponents()) { ComponentNameToComponent.Add(Component->GetName(), Component); } TMap ComponentsToUpdate; for (int32 i = 0; i < OwnerMovieScene->GetPossessableCount(); i++) { FMovieScenePossessable& OldPossessable = OwnerMovieScene->GetPossessable(i); if (OldPossessable.GetParent() == InObjectBinding) { UActorComponent** ComponentPtr = ComponentNameToComponent.Find(OldPossessable.GetName()); if (ComponentPtr != nullptr) { ComponentsToUpdate.Add(OldPossessable.GetGuid(), *ComponentPtr); } } } for (TPair ComponentToUpdate : ComponentsToUpdate) { UpdateComponent(ComponentToUpdate.Key, ComponentToUpdate.Value, NewComponentGuids); } } // Replace the actor itself after components have been updated OwnerMovieScene->ReplacePossessable(InObjectBinding, NewPossessableActor); OwnerSequence->UnbindPossessableObjects(InObjectBinding); FMovieSceneEvaluationState* State = Sequencer->GetEvaluationState(); State->Invalidate(InObjectBinding, Sequencer->GetFocusedTemplateID()); State->Invalidate(NewPossessableActor.GetGuid(), Sequencer->GetFocusedTemplateID()); for (const FGuid& NewComponentGuid : NewComponentGuids) { FMovieScenePossessable* ThisPossessable = OwnerMovieScene->FindPossessable(NewComponentGuid); if (ensure(ThisPossessable)) { ThisPossessable->SetParent(NewGuid, OwnerMovieScene); } } // Try to fix up folders TArray FoldersToCheck; for (UMovieSceneFolder* Folder : Sequencer->GetFocusedMovieSceneSequence()->GetMovieScene()->GetRootFolders()) { FoldersToCheck.Add(Folder); } bool bFolderFound = false; while (FoldersToCheck.Num() > 0 && bFolderFound == false) { UMovieSceneFolder* Folder = FoldersToCheck[0]; FoldersToCheck.RemoveAt(0); if (Folder->GetChildObjectBindings().Contains(InObjectBinding)) { Folder->RemoveChildObjectBinding(InObjectBinding); Folder->AddChildObjectBinding(NewGuid); bFolderFound = true; } for (UMovieSceneFolder* ChildFolder : Folder->GetChildFolders()) { FoldersToCheck.Add(ChildFolder); } } Sequencer->RestorePreAnimatedState(); Sequencer->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemsChanged); return NewGuid; } void FSequencerUtilities::AddActorsToBinding(TSharedRef Sequencer, const TArray& Actors, const FMovieSceneBindingProxy& ObjectBinding) { FScopedTransaction AddActorsToBinding(LOCTEXT("AddActorsToBinding", "Add Actors to Binding")); TArray ObjectsToAdd; Algo::Transform(Actors, ObjectsToAdd, [](AActor* Actor) { return Actor;}); AddObjectsToBinding(Sequencer, ObjectsToAdd, ObjectBinding, Sequencer->GetPlaybackContext()); } void FSequencerUtilities::AddObjectsToBinding(TSharedRef InSequencer, const TArray& InObjectsToAdd, const FMovieSceneBindingProxy& InObjectBinding, UObject* InResolutionContext) { UMovieSceneSequence* Sequence = InObjectBinding.Sequence; if (!Sequence) { return; } UMovieScene* const MovieScene = Sequence->GetMovieScene(); if (!MovieScene || InObjectsToAdd.IsEmpty()) { return; } UClass* ObjectClass = nullptr; int32 ValidObjectCount = 0; FGuid Guid = InObjectBinding.BindingID; TArrayView> ObjectsInCurrentSequence = InSequencer->FindObjectsInCurrentSequence(Guid); for (TWeakObjectPtr<> Ptr : ObjectsInCurrentSequence) { if (const UObject* Object = Cast(Ptr.Get())) { ObjectClass = Object->GetClass(); ++ValidObjectCount; } } Sequence->Modify(); MovieScene->Modify(); TArray AddedObjects; AddedObjects.Reserve(InObjectsToAdd.Num()); for (UObject* ObjectToAdd : InObjectsToAdd) { // Skip invalid objects or objects already in the sequence if (!ObjectToAdd || ObjectsInCurrentSequence.Contains(ObjectToAdd)) { continue; } // Skip if the object has no common class with the objects already in the binding if (ObjectClass && !UClass::FindCommonBase(ObjectToAdd->GetClass(), ObjectClass)) { continue; } // if no objects are in the binding, set the class to this object's if (!ObjectClass) { ObjectClass = ObjectToAdd->GetClass(); } FMovieScenePossessable* Possessable = MovieScene->FindPossessable(Guid); if (!ensureAlways(Possessable)) { continue; } ObjectToAdd->Modify(); if (!Possessable->BindSpawnableObject(InSequencer->GetFocusedTemplateID(), ObjectToAdd, InSequencer->GetSharedPlaybackState())) { Sequence->BindPossessableObject(Guid, *ObjectToAdd, InResolutionContext); } // If the object was added successfully, continue FGuid AddedGuid = InSequencer->GetHandleToObject(ObjectToAdd, false); if (AddedGuid.IsValid()) { AddedObjects.Add(ObjectToAdd); continue; } // Otherwise... if (ObjectClass == nullptr || UClass::FindCommonBase(ObjectToAdd->GetClass(), ObjectClass) != nullptr) { if (ObjectClass == nullptr) { ObjectClass = ObjectToAdd->GetClass(); } ObjectToAdd->Modify(); if (!MovieScene->FindPossessable(Guid)->BindSpawnableObject(InSequencer->GetFocusedTemplateID(), ObjectToAdd, InSequencer->GetSharedPlaybackState())) { Sequence->BindPossessableObject(Guid, *ObjectToAdd, InResolutionContext); } AddedObjects.Add(ObjectToAdd); } else { const FText NotificationText = FText::Format(LOCTEXT("UnableToAssignObject", "Cannot assign object {0}. Expected class {1}"), FText::FromString(ObjectToAdd->GetPathName()), FText::FromString(ObjectClass->GetName())); FNotificationInfo Info(NotificationText); Info.ExpireDuration = 3.f; Info.bUseLargeFont = false; FSlateNotificationManager::Get().AddNotification(Info); } } // Update Labels if (ValidObjectCount + AddedObjects.Num() > 0) { FMovieScenePossessable* const Possessable = MovieScene->FindPossessable(Guid); if (Possessable && ObjectClass) { // If there are multiple objects within the same possessable, name possessable as "ClassName (Count)" if (ValidObjectCount + AddedObjects.Num() > 1) { Possessable->SetName(FString::Printf(TEXT("%s (%d)") , *ObjectClass->GetName() , ValidObjectCount + AddedObjects.Num())); } else if (!AddedObjects.IsEmpty()) { FString PossessableName = AddedObjects[0]->GetName(); if (AActor* const Actor = Cast(AddedObjects[0])) { PossessableName = Actor->GetActorLabel(); } Possessable->SetName(PossessableName); } Possessable->SetPossessedObjectClass(ObjectClass); } } InSequencer->RestorePreAnimatedState(); InSequencer->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemsChanged); } void FSequencerUtilities::ReplaceBindingWithActors(TSharedRef Sequencer, const TArray& Actors, const FMovieSceneBindingProxy& ObjectBinding) { FScopedTransaction ReplaceBindingWithActors(LOCTEXT("ReplaceBindingWithActors", "Replace Binding with Actors")); FGuid Guid = ObjectBinding.BindingID; TArray ExistingActors; for (TWeakObjectPtr<> Ptr : Sequencer->FindObjectsInCurrentSequence(Guid)) { if (AActor* Actor = Cast(Ptr.Get())) { if (!Actors.Contains(Actor)) { ExistingActors.Add(Actor); } } } RemoveActorsFromBinding(Sequencer, ExistingActors, ObjectBinding); TArray NewActors; for (AActor* NewActor : Actors) { if (!ExistingActors.Contains(NewActor)) { NewActors.Add(NewActor); } } AddActorsToBinding(Sequencer, NewActors, ObjectBinding); } void FSequencerUtilities::RemoveActorsFromBinding(TSharedRef Sequencer, const TArray& Actors, const FMovieSceneBindingProxy& ObjectBinding) { if (!Actors.Num()) { return; } UMovieSceneSequence* Sequence = Sequencer->GetFocusedMovieSceneSequence(); if (!Sequence) { return; } UMovieScene* MovieScene = Sequence->GetMovieScene(); if (!MovieScene) { return; } UClass* ActorClass = nullptr; int32 NumRuntimeObjects = 0; FGuid Guid = ObjectBinding.BindingID; for (TWeakObjectPtr<> Ptr : Sequencer->FindObjectsInCurrentSequence(Guid)) { if (const AActor* Actor = Cast(Ptr.Get())) { ActorClass = Actor->GetClass(); ++NumRuntimeObjects; } } FScopedTransaction RemoveSelectedFromBinding(LOCTEXT("RemoveSelectedFromBinding", "Remove Selected from Binding")); TArray ObjectsToRemove; for (AActor* ActorToRemove : Actors) { // Restore state on any components for (UActorComponent* Component : TInlineComponentArray(ActorToRemove)) { if (Component) { Sequencer->PreAnimatedState.RestorePreAnimatedState(*Component); } } // Restore state on the object itself Sequencer->PreAnimatedState.RestorePreAnimatedState(*ActorToRemove); ActorToRemove->Modify(); ObjectsToRemove.Add(ActorToRemove); } Sequence->Modify(); MovieScene->Modify(); // Unbind objects Sequence->UnbindObjects(Guid, ObjectsToRemove, Sequencer->GetPlaybackContext()); // Update label if (NumRuntimeObjects - ObjectsToRemove.Num() > 0) { FMovieScenePossessable* Possessable = MovieScene->FindPossessable(Guid); if (Possessable && ActorClass != nullptr) { if (NumRuntimeObjects - ObjectsToRemove.Num() > 1) { FString NewLabel = ActorClass->GetName() + FString::Printf(TEXT(" (%d)"), NumRuntimeObjects - ObjectsToRemove.Num()); Possessable->SetName(NewLabel); } else if (ObjectsToRemove.Num() > 0 && Actors.Num() > 0) { Possessable->SetName(Actors[0]->GetActorLabel()); } } } Sequencer->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemsChanged); } void FSequencerUtilities::ShowReadOnlyError() { FNotificationInfo Info(LOCTEXT("SequenceReadOnly", "Sequence is read only.")); Info.ExpireDuration = 5.0f; FSlateNotificationManager::Get().AddNotification(Info)->SetCompletionState(SNotificationItem::CS_Fail); } void FSequencerUtilities::ShowSpawnableNotAllowedError() { FNotificationInfo Info(LOCTEXT("SequenceSpawnableNotAllowed", "Spawnable object is not allowed for Sequence.")); Info.ExpireDuration = 5.0f; FSlateNotificationManager::Get().AddNotification(Info)->SetCompletionState(SNotificationItem::CS_Fail); } void FSequencerUtilities::SaveCurrentMovieSceneAs(TSharedRef Sequencer) { StaticCastSharedPtr(Sequencer.ToSharedPtr())->SaveCurrentMovieSceneAs(); } void FSequencerUtilities::SynchronizeExternalSelectionWithSequencerSelection (TSharedRef Sequencer) { StaticCastSharedPtr(Sequencer.ToSharedPtr())->SynchronizeExternalSelectionWithSequencerSelection(); } TRange FSequencerUtilities::GetTimeBounds(TSharedRef Sequencer) { return StaticCastSharedPtr(Sequencer.ToSharedPtr())->GetTimeBounds(); } void FSequencerUtilities::AddChangeClassMenu(FMenuBuilder& MenuBuilder, TSharedRef Sequencer, const TArray& Bindings, TFunction OnBindingChanged) { UMovieSceneSequence* Sequence = Sequencer->GetFocusedMovieSceneSequence(); if (!Sequence) { return; } UMovieScene* MovieScene = Sequence->GetMovieScene(); FClassViewerInitializationOptions Options; Options.Mode = EClassViewerMode::ClassPicker; Options.bIsPlaceableOnly = true; for (const FSequencerChangeBindingInfo& Binding : Bindings) { FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(Binding.BindingID); if (Spawnable) { Options.bIsActorsOnly = true; } else if (const FMovieSceneBindingReferences* BindingReferences = Sequence->GetBindingReferences()) { TArrayView BindingReferencesList = BindingReferences->GetReferences(Binding.BindingID); if (BindingReferencesList.IsValidIndex(Binding.BindingIndex) && BindingReferencesList[Binding.BindingIndex].CustomBinding && BindingReferencesList[Binding.BindingIndex].CustomBinding->WillSpawnObject(Sequencer->GetSharedPlaybackState())) { // Class filter for the custom binding type class FCustomBindingClassFilter : public IClassViewerFilter { public: bool IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const UClass* InClass, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs) override { return CustomBinding && InClass && CustomBinding->SupportsBindingCreationFromObject(InClass->GetDefaultObject()); } virtual bool IsUnloadedClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const TSharedRef< const IUnloadedBlueprintData > InClass, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs) override { if (const UClass* ClassWithin = InClass->GetClassWithin()) { return IsClassAllowed(InInitOptions, ClassWithin, InFilterFuncs); } return false; } TObjectPtr CustomBinding; }; TSharedRef ClassFilter = MakeShared(); ClassFilter->CustomBinding = BindingReferencesList[0].CustomBinding; Options.ClassFilters.Add(ClassFilter); } else { return; } } else { return; } const UClass* ClassForObjectBinding = MovieSceneHelpers::GetBoundObjectClass(Sequence, Binding.BindingID, Binding.BindingIndex); if (ClassForObjectBinding) { Options.ViewerTitleString = FText::FromString(TEXT("Change from: ") + ClassForObjectBinding->GetFName().ToString()); } else { Options.ViewerTitleString = FText::FromString(TEXT("Change from: (empty)")); } } FClassViewerModule& ClassViewerModule = FModuleManager::LoadModuleChecked("ClassViewer"); MenuBuilder.AddWidget( SNew(SBox) .MinDesiredWidth(300.0f) .MaxDesiredHeight(400.0f) [ ClassViewerModule.CreateClassViewer(Options, FOnClassPicked::CreateLambda([Sequencer, Bindings, OnBindingChanged](UClass* Class) { FSequencerUtilities::HandleTemplateActorClassPicked(Class, Sequencer, Bindings, OnBindingChanged); })) ], FText(), true, false ); } void UpdatePossessedClasses(UMovieScene* MovieScene, FMovieSceneSequenceIDRef SequenceID, const FMovieSceneSequenceHierarchy* Hierarchy, FGuid ObjectBindingID, UClass* ChosenClass) { for (int32 Index = 0; Index < MovieScene->GetPossessableCount(); ++Index) { FMovieScenePossessable& Possessable = MovieScene->GetPossessable(Index); if (Possessable.GetSpawnableObjectBindingID().GetGuid() == ObjectBindingID && Possessable.GetPossessedObjectClass() != ChosenClass) { MovieScene->Modify(); Possessable.SetPossessedObjectClass(ChosenClass); } } if (const FMovieSceneSequenceHierarchyNode* Node = Hierarchy->FindNode(SequenceID)) { for (FMovieSceneSequenceIDRef ChildID : Node->Children) { const FMovieSceneSubSequenceData* SubData = Hierarchy->FindSubData(ChildID); if (SubData) { UMovieSceneSequence* SubSequence = SubData->GetSequence(); UMovieScene* SubMovieScene = SubSequence ? SubSequence->GetMovieScene() : nullptr; if (SubMovieScene) { UpdatePossessedClasses(SubMovieScene, ChildID, Hierarchy, ObjectBindingID, ChosenClass); } } } } } void FSequencerUtilities::HandleTemplateActorClassPicked(UClass* ChosenClass, TSharedRef Sequencer, const TArray& Bindings, TFunction OnBindingChanged) { UMovieScene* MovieScene = Sequencer->GetFocusedMovieSceneSequence()->GetMovieScene(); FScopedTransaction Transaction(LOCTEXT("ChangeClass", "Change Class")); MovieScene->Modify(); TValueOrError Result = Sequencer->GetSpawnRegister().CreateNewSpawnableType(*ChosenClass, *MovieScene, nullptr); if (Result.IsValid()) { FMovieSceneRootEvaluationTemplateInstance& RootInstance = Sequencer->GetEvaluationTemplate(); const FMovieSceneSequenceHierarchy* Hierarchy = RootInstance.GetCompiledDataManager()->FindHierarchy(RootInstance.GetCompiledDataID()); for (const FSequencerChangeBindingInfo& Binding : Bindings) { UpdatePossessedClasses(Sequencer->GetRootMovieSceneSequence()->GetMovieScene(), MovieSceneSequenceID::Root, Hierarchy, Binding.BindingID, ChosenClass); MovieSceneHelpers::SetObjectTemplate(Sequencer->GetFocusedMovieSceneSequence(), Binding.BindingID, Result.GetValue().ObjectTemplate, Sequencer->GetSharedPlaybackState(), Binding.BindingIndex); Sequencer->GetSpawnRegister().DestroySpawnedObject(Binding.BindingID, Sequencer->GetFocusedTemplateID(), Sequencer->GetSharedPlaybackState(), Binding.BindingIndex); } Sequencer->ForceEvaluate(); } if (OnBindingChanged) { OnBindingChanged(); } } bool FSequencerUtilities::CanConvertToPossessable(TSharedRef Sequencer, FGuid BindingGuid, int32 BindingIndex) { UMovieSceneSequence* Sequence = Sequencer->GetFocusedMovieSceneSequence(); UMovieScene* MovieScene = Sequence ? Sequence->GetMovieScene() : nullptr; if (!MovieScene) { return false; } if (FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(BindingGuid)) { return true; } else if (const FMovieSceneBindingReferences* BindingReferences = Sequence->GetBindingReferences()) { TArrayView BindingReferencesList = BindingReferences->GetReferences(BindingGuid); if (BindingReferencesList.IsValidIndex(BindingIndex) && BindingReferencesList[BindingIndex].CustomBinding != nullptr) { return true; } } return false; } bool FSequencerUtilities::CanConvertToCustomBinding(TSharedRef Sequencer, FGuid BindingGuid, TSubclassOf CustomBindingType, int32 BindingIndex) { UMovieSceneSequence* Sequence = Sequencer->GetFocusedMovieSceneSequence(); UMovieScene* MovieScene = Sequence ? Sequence->GetMovieScene() : nullptr; if (!MovieScene) { return false; } if (FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(BindingGuid)) { if (UObject* CurrentBoundObject = Spawnable->GetObjectTemplate()) { return CustomBindingType && CustomBindingType->GetDefaultObject()->SupportsBindingCreationFromObject(CurrentBoundObject); } } else if (FMovieScenePossessable* Possessable = MovieScene->FindPossessable(BindingGuid)) { if (const FMovieSceneBindingReferences* BindingReferences = Sequence->GetBindingReferences()) { UObject* ResolutionContext = MovieSceneHelpers::GetResolutionContext(Sequence, BindingGuid, Sequencer->GetFocusedTemplateID(), Sequencer->GetSharedPlaybackState()); TArrayView BindingReferencesList = Sequencer->GetFocusedMovieSceneSequence()->GetBindingReferences()->GetReferences(BindingGuid); if (const FMovieSceneBindingReference* CurrentBindingReference = BindingReferences->GetReference(BindingGuid, BindingIndex)) { UE::UniversalObjectLocator::FResolveParams LocatorResolveParams(ResolutionContext); FMovieSceneBindingResolveParams BindingResolveParams{ Sequence, BindingGuid, Sequencer->GetFocusedTemplateID(), ResolutionContext }; UObject* CurrentBoundObject = BindingReferences->ResolveSingleBinding(BindingResolveParams, BindingIndex, LocatorResolveParams, Sequencer->GetSharedPlaybackState()); if (CustomBindingType && (!CurrentBindingReference->CustomBinding || CurrentBindingReference->CustomBinding->GetClass() != CustomBindingType) && CustomBindingType->GetDefaultObject()->SupportsConversionFromBinding(*CurrentBindingReference, CurrentBoundObject)) { return true; } } } } return false; } UMovieSceneSequence* FSequencerUtilities::GetMovieSceneSequence(TSharedPtr& InSequencer, const FMovieSceneSequenceID& SequenceID) { if (MovieSceneSequenceID::Root != SequenceID) { UMovieSceneSubSection* SubSection = InSequencer->FindSubSection(SequenceID); return SubSection ? SubSection->GetSequence() : nullptr; } return InSequencer->GetRootMovieSceneSequence(); } void FOpenSequencerWatcher::DoStartup(TFunction StartupComplete) { auto RegisterWatcher = [this, StartupCompleteFunc = MoveTemp(StartupComplete)]() { ISequencerModule& SequencerModule = FModuleManager::Get().LoadModuleChecked("Sequencer"); SequencerModule.RegisterOnSequencerCreated( FOnSequencerCreated::FDelegate::CreateRaw(this, &FOpenSequencerWatcher::OnSequencerCreated)); StartupCompleteFunc(); }; if (GEngine) { RegisterWatcher(); } else { FCoreDelegates::OnFEngineLoopInitComplete.AddLambda(RegisterWatcher); } } void FOpenSequencerWatcher::OnSequencerCreated(TSharedRef InSequencer) { FOpenSequencerData OpenSequencer; OpenSequencer.WeakSequencer = TWeakPtr(InSequencer); OpenSequencer.OnCloseEventHandle = InSequencer->OnCloseEvent().AddRaw(this, &FOpenSequencerWatcher::OnSequencerClosed); OpenSequencers.Add(MoveTemp(OpenSequencer)); } void FOpenSequencerWatcher::OnSequencerClosed(TSharedRef InSequencer) { OpenSequencers.RemoveAll([SequencerObject=&InSequencer.Get()](const FOpenSequencerData& Data) { return Data.WeakSequencer.HasSameObject(SequencerObject); }); } #undef LOCTEXT_NAMESPACE