Files
UnrealEngine/Engine/Source/Editor/Sequencer/Private/SequencerUtilities.cpp
2025-05-18 13:04:45 +08:00

4654 lines
154 KiB
C++

// 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<SWidget> FSequencerUtilities::MakeAddButton(FText HoverText, FOnGetContent MenuContent, const TAttribute<bool>& HoverState, TWeakPtr<ISequencer> InSequencer)
{
TAttribute<bool> IsEnabled = MakeAttributeLambda([InSequencer]() -> bool { return InSequencer.IsValid() ? !InSequencer.Pin()->IsReadOnly() : false; });
return UE::Sequencer::MakeAddButton(HoverText, MenuContent, HoverState, IsEnabled);
}
TSharedRef<SWidget> FSequencerUtilities::MakeAddButton(FText HoverText, FOnClicked OnClicked, const TAttribute<bool>& HoverState, TWeakPtr<ISequencer> InSequencer)
{
TAttribute<bool> 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<UE::Sequencer::ITrackExtension> WeakTrackModel)
{
using namespace UE::Sequencer;
TViewModelPtr<ITrackExtension> TrackModel = WeakTrackModel.Pin();
if (!TrackModel)
{
return;
}
TOptional<UClass*> CommonClass;
for (const TViewModelPtr<FSectionModel>& SectionModel : TrackModel->GetSectionModels().IterateSubList<FSectionModel>())
{
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<void(TSubclassOf<UMovieSceneTimeWarpGetter>)> OnTimeWarpPicked)
{
using namespace UE::Sequencer;
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
TSet<FTopLevelAssetPath> AllTimeWarpClasses;
{
FTopLevelAssetPath TargetClassPath(UMovieSceneTimeWarpGetter::StaticClass());
AssetRegistryModule.Get().GetDerivedClassNames({ TargetClassPath }, TSet<FTopLevelAssetPath>(), 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<UMovieSceneTimeWarpGetter>();
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<const UClass>(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<UE::Sequencer::ITrackExtension> WeakTrackModel)
{
using namespace UE::Sequencer;
auto HandleTimeWarpSelection = [WeakTrackModel](TSubclassOf<UMovieSceneTimeWarpGetter> Class)
{
TViewModelPtr<ITrackExtension> TrackModel = WeakTrackModel.Pin();
if (!TrackModel)
{
return;
}
FScopedTransaction Transaction(LOCTEXT("ChangeTimeWarpType", "Changed Time Warp type"));
for (const TViewModelPtr<FSectionModel>& SectionModel : TrackModel->GetSectionModels().IterateSubList<FSectionModel>())
{
UMovieSceneSection* Section = SectionModel->GetSection();
FMovieSceneTimeWarpVariant* Variant = Section ? Section->GetTimeWarp() : nullptr;
if (Variant)
{
Section->Modify();
UMovieSceneTimeWarpGetter* Getter = NewObject<UMovieSceneTimeWarpGetter>(Section, Class.Get(), NAME_None, RF_Transactional);
Getter->InitializeDefaults();
Variant->Set(Getter);
Section->InvalidateChannelProxy();
TViewModelPtr<IOutlinerExtension> Outliner = TrackModel.ImplicitCast();
if (Outliner && !Outliner->IsExpanded())
{
Outliner->SetExpansion(true);
}
}
}
};
PopulateTimeWarpSubMenu(MenuBuilder, HandleTimeWarpSelection);
}
void FSequencerUtilities::CreateNewSection(UMovieSceneTrack* InTrack, TWeakPtr<ISequencer> InSequencer, int32 InRowIndex, EMovieSceneBlendType InBlendType)
{
TSharedPtr<ISequencer> 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<FFrameNumber>::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<ISequencer> InSequencer)
{
if (!Track)
{
return;
}
auto CreateNewSection = [Track, InSequencer, RowIndex](EMovieSceneBlendType BlendType)
{
TSharedPtr<ISequencer> 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<int32, int32> 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<FFrameNumber>::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<FFrameNumber>(CurrentTime.Time.FrameNumber, NewSectionRangeEnd));
}
NewSection->SetOverlapPriority(OverlapPriority);
NewSection->SetRowIndex(SpecifiedRowIndex);
NewSection->SetBlendType(BlendType);
Track->AddSection(*NewSection);
Track->UpdateEasing();
if (UMovieSceneNameableTrack* NameableTrack = Cast<UMovieSceneNameableTrack>(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<UEnum>(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<ISequencer> InSequencer)
{
IMovieSceneBlenderSystemSupport* BlenderSystemSupport = Cast<IMovieSceneBlenderSystemSupport>(Track);
// Shouldn't have been called with a track that does not implement this interface
check(BlenderSystemSupport);
TArray<TSubclassOf<UMovieSceneBlenderSystem>> BlenderTypes;
BlenderSystemSupport->GetSupportedBlenderSystems(BlenderTypes);
// Ensure no nulls
BlenderTypes.Remove(TSubclassOf<UMovieSceneBlenderSystem>());
// Sort alphabetically
Algo::Sort(BlenderTypes,
[](TSubclassOf<UMovieSceneBlenderSystem> A, TSubclassOf<UMovieSceneBlenderSystem> B)
{
return A->GetDisplayNameText().CompareTo(B->GetDisplayNameText()) < 0;
}
);
MenuBuilder.BeginSection(TEXT("Blending"), LOCTEXT("BlendingMenuSection", "Blending"));
for (TSubclassOf<UMovieSceneBlenderSystem> 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<ISequencer> InSequencer)
{
PopulateMenu_SetBlendType(MenuBuilder, TArray<TWeakObjectPtr<UMovieSceneSection>>({ Section }), InSequencer);
}
void FSequencerUtilities::PopulateMenu_SetBlendType(FMenuBuilder& MenuBuilder, const TArray<TWeakObjectPtr<UMovieSceneSection>>& InSections, TWeakPtr<ISequencer> InSequencer)
{
using namespace UE::MovieScene;
using namespace UE::Sequencer;
auto Execute = [InSections, InSequencer](EMovieSceneBlendType BlendType)
{
FScopedTransaction Transaction(LOCTEXT("SetBlendType", "Set Blend Type"));
for (TWeakObjectPtr<UMovieSceneSection> WeakSection : InSections)
{
if (UMovieSceneSection* Section = WeakSection.Get())
{
Section->Modify();
Section->SetBlendType(BlendType);
}
}
TSharedPtr<FSequencer> Sequencer = StaticCastSharedPtr<FSequencer>(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<UObject*> ObjectsToRestore;
TSharedRef<FSequencerNodeTree> SequencerNodeTree = Sequencer->GetNodeTree();
for (TWeakObjectPtr<UMovieSceneSection> WeakSection : InSections)
{
if (UMovieSceneSection* Section = WeakSection.Get())
{
TSharedPtr<FSectionModel> SectionHandle = SequencerNodeTree->GetSectionModel(Section);
if (!SectionHandle)
{
continue;
}
TSharedPtr<IObjectBindingExtension> ParentObjectBindingNode = SectionHandle->FindAncestorOfType<IObjectBindingExtension>();
if (!ParentObjectBindingNode.IsValid())
{
continue;
}
for (TWeakObjectPtr<> BoundObject : Sequencer->FindObjectsInCurrentSequence(ParentObjectBindingNode->GetObjectGuid()))
{
if (AActor* BoundActor = Cast<AActor>(BoundObject))
{
for (UActorComponent* Component : TInlineComponentArray<UActorComponent*>(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<UEnum>(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<UMovieSceneSection> 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<UMovieSceneSection> 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<FName>& 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<FString> FSequencerUtilities::GetAssociatedLevelSequenceMapPackages(const ULevelSequence* InSequence)
{
if (!InSequence)
{
return TArray<FString>();
}
const FName LSMapPathName = *InSequence->GetOutermost()->GetPathName();
return GetAssociatedLevelSequenceMapPackages(LSMapPathName);
}
TArray<FString> FSequencerUtilities::GetAssociatedLevelSequenceMapPackages(FName LevelSequencePackageName)
{
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
TArray<FString> AssociatedMaps;
TArray<FAssetIdentifier> 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<FGuid> ExpandMultiplePossessableBindings(TSharedRef<ISequencer> Sequencer, FGuid PossessableGuid)
{
TArray<FGuid> 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<TWeakObjectPtr<>> 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<FGuid> ChildPossessableGuids;
for (int32 Index = 0; Index < MovieScene->GetPossessableCount(); ++Index)
{
FMovieScenePossessable& Possessable = MovieScene->GetPossessable(Index);
if (Possessable.GetParent() == PossessableGuid)
{
ChildPossessableGuids.Add(Possessable.GetGuid());
}
}
TArray<UMovieSceneTrack* > 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<AActor>(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<UMovieSceneTrack>(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<ISequencer> 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<ISequencer> 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<FNewSpawnable, FText> 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<ISequencer> 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<ACameraActor>(SpawnedObject))
{
NewCameraAdded(Sequencer, NewCamera, NewGuid);
}
return NewGuid;
}
FGuid FSequencerUtilities::CreateCamera(TSharedRef<ISequencer> 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<ACineCameraActor>(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<UMovieSceneCustomBinding> CustomBindingClass = bSpawnable ? UMovieSceneSpawnableActorBinding::StaticClass() : UMovieSceneReplaceableActorBinding::StaticClass();
const FMovieSceneBindingReferences* BindingReferences = Sequence->GetBindingReferences();
if (BindingReferences)
{
for (const FMovieSceneBindingReference& Reference : BindingReferences->GetReferences(CameraGuid))
{
for (const TSubclassOf<UMovieSceneCustomBinding>& SupportedCustomBindingType : Sequencer->GetSupportedCustomBindingTypes())
{
if (SupportedCustomBindingType && SupportedCustomBindingType->IsChildOf(CustomBindingClass) &&
SupportedCustomBindingType->GetDefaultObject<UMovieSceneCustomBinding>()->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<ACineCameraActor>(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<ISequencer> 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<ACameraRig_Rail>(Actor);
}
// Create a cine camera actor
UWorld* World = GCurrentLevelEditingViewportClient ? GCurrentLevelEditingViewportClient->GetWorld() : nullptr;
OutActor = World->SpawnActor<ACineCameraActor>();
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<FFrameNumber> PlaybackRange = MovieScene->GetPlaybackRange();
if (bSpawnable)
{
for (TWeakObjectPtr<> WeakObject : Sequencer->FindBoundObjects(CameraGuid, Sequencer->GetFocusedTemplateID()))
{
OutActor = Cast<ACineCameraActor>(WeakObject.Get());
if (OutActor)
{
break;
}
}
OutActor->SetActorLabel(NewCameraName, false);
// Create an attach track
UMovieScene3DAttachTrack* AttachTrack = Cast<UMovieScene3DAttachTrack>(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<UObject*>().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<FGuid> FSequencerUtilities::AddActors(TSharedRef<ISequencer> Sequencer, const TArray<TWeakObjectPtr<AActor> >& InActors)
{
TArray<FGuid> 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<AActor> 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<ACameraActor>(Actor))
{
NewCameraAdded(Sequencer, CameraActor, PossessableGuid);
}
}
}
}
return PossessableGuids;
}
TArray<FMovieSceneSpawnable*> FSequencerUtilities::ConvertToSpawnable(TSharedRef<ISequencer> Sequencer, FGuid PossessableGuid)
{
TArray<FMovieSceneSpawnable*> 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<TWeakObjectPtr<>> 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<FGuid> 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<FTransformData> 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<ISequencer> 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<AActor>(ObjectToConvert);
TMap<TWeakObjectPtr<AActor>, 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<AActor>(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<AActor*> 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<const FMovieSceneBindingReference> BindingReferences = Sequence->GetBindingReferences()->GetReferences(BindingGuid);
bool bAnySpawnablesLeft = Algo::AnyOf(BindingReferences, [](const FMovieSceneBindingReference& BindingReference) { return BindingReference.CustomBinding && BindingReference.CustomBinding->IsA<UMovieSceneSpawnableBindingBase>(); });
// 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<UMovieSceneSpawnTrack>(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<AActor>(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<TWeakObjectPtr<AActor>, 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<ISequencer> Sequencer, FGuid BindingGuid, TSubclassOf<UMovieSceneCustomBinding> 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<UMovieSceneCustomBinding>()->CreateCustomBindingFromBinding(*PreviousBindingReference, ObjectToConvert, *MovieScene);
}
else
{
NewCustomBinding = CustomBindingType->GetDefaultObject<UMovieSceneCustomBinding>()->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<const FMovieSceneBindingReference> BindingReferencesForGuid = BindingReferences->GetReferences(BindingGuid);
bool bAnySpawnablesLeft = Algo::AnyOf(BindingReferencesForGuid, [](const FMovieSceneBindingReference& BindingReference) { return BindingReference.CustomBinding && BindingReference.CustomBinding->IsA<UMovieSceneSpawnableBindingBase>(); });
bool bAnyReplaceablesLeft = Algo::AnyOf(BindingReferencesForGuid, [](const FMovieSceneBindingReference& BindingReference) { return BindingReference.CustomBinding && BindingReference.CustomBinding->IsA<UMovieSceneReplaceableBindingBase>(); });
// If we're converting from a spawnable and the new custom binding isn't a spawnable, remove the spawn track
if (PreviousCustomBinding && PreviousCustomBinding->IsA<UMovieSceneSpawnableBindingBase>() && !bAnySpawnablesLeft)
{
// Delete the spawn track
UMovieSceneSpawnTrack* SpawnTrack = Cast<UMovieSceneSpawnTrack>(MovieScene->FindTrack(UMovieSceneSpawnTrack::StaticClass(), BindingGuid, NAME_None));
if (SpawnTrack)
{
MovieScene->RemoveTrack(*SpawnTrack);
}
}
else if (PreviousCustomBinding && PreviousCustomBinding->IsA<UMovieSceneReplaceableBindingBase>() && !bAnyReplaceablesLeft)
{
// Delete the binding lifetime track
UMovieSceneBindingLifetimeTrack* BindingLifetimeTrack = Cast<UMovieSceneBindingLifetimeTrack>(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<FTransformData> 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<UObject*>& 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<UObject*>& Objects)
{
for (UMovieSceneFolder* ChildFolder : ParentFolder->GetChildFolders())
{
if (ChildFolder)
{
Objects.AddUnique(ChildFolder);
GatherChildFolders(ChildFolder, Objects);
}
}
}
void FSequencerUtilities::CopyFolders(const TArray<UMovieSceneFolder*>& Folders, FString& ExportedText)
{
TArray<UObject*> Objects;
for (UMovieSceneFolder* Folder : Folders)
{
Objects.AddUnique(Folder);
GatherChildFolders(Folder, Objects);
}
ExportObjectsToText(Objects, /*out*/ ExportedText);
}
void GatherFolderContents(UMovieSceneFolder* Folder, TArray<UMovieSceneFolder*>& Folders, TArray<UMovieSceneTrack*>& Tracks, TArray<FMovieSceneBindingProxy>& Bindings)
{
if (!Folder)
{
return;
}
Folders.AddUnique(Folder);
UMovieScene* MovieScene = CastChecked<UMovieScene>(Folder->GetOuter());
UMovieSceneSequence* Sequence = CastChecked<UMovieSceneSequence>(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<ISequencer> Sequencer, const TArray<UMovieSceneFolder*>& InFolders, FString& FoldersExportedText, FString& TracksExportedText, FString& ObjectsExportedText)
{
TArray<UMovieSceneFolder*> Folders;
TArray<UMovieSceneTrack*> Tracks;
TArray<FMovieSceneBindingProxy> Bindings;
for (UMovieSceneFolder* Folder : InFolders)
{
GatherFolderContents(Folder, Folders, Tracks, Bindings);
}
CopyTracks(Tracks, Folders, TracksExportedText);
CopyBindings(Sequencer, Bindings, Folders, ObjectsExportedText);
TArray<UObject*> 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<UMovieSceneFolder>(NewObject));
}
public:
TArray<UMovieSceneFolder*> NewFolders;
};
void ImportFoldersFromText(const FString& TextToImport, /*out*/ TArray<UMovieSceneFolder*>& ImportedFolders)
{
UPackage* TempPackage = NewObject<UPackage>(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<UMovieSceneFolder*>& OutFolders, TArray<FNotificationInfo>& OutErrors)
{
if (!PasteFoldersParams.Sequence)
{
return false;
}
UMovieScene* MovieScene = PasteFoldersParams.Sequence->GetMovieScene();
if (!MovieScene)
{
return false;
}
TArray<UMovieSceneFolder*> 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<UMovieSceneTrack*>& Tracks, const TArray<UMovieSceneFolder*>& Folders, FString& ExportedText)
{
TArray<UObject*> Objects;
for (UMovieSceneTrack* Track : Tracks)
{
UMovieScene* MovieScene = Track->GetTypedOuter<UMovieScene>();
UMovieSceneCopyableTrack* CopyableTrack = NewObject<UMovieSceneCopyableTrack>(GetTransientPackage(), UMovieSceneCopyableTrack::StaticClass(), NAME_None, RF_Transient);
Objects.Add(CopyableTrack);
UMovieSceneTrack* DuplicatedTrack = Cast<UMovieSceneTrack>(StaticDuplicateObject(Track, CopyableTrack));
CopyableTrack->Track = DuplicatedTrack;
CopyableTrack->bIsRootTrack = MovieScene->ContainsTrack(*Track);
CopyableTrack->bIsCameraCutTrack = Track->IsA<UMovieSceneCameraCutTrack>();
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<UMovieSceneCopyableTrack>(NewObject));
}
public:
TArray<UMovieSceneCopyableTrack*> NewTracks;
};
void ImportTracksFromText(const FString& TextToImport, /*out*/ TArray<UMovieSceneCopyableTrack*>& ImportedTracks)
{
UPackage* TempPackage = NewObject<UPackage>(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<UMovieSceneTrack*>& OutTracks, TArray<FNotificationInfo>& OutErrors)
{
TArray<UMovieSceneCopyableTrack*> 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<UMovieSceneCopyableTrack*> 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<UMovieSceneSection*>& Sections, FString& ExportedText)
{
TArray<UObject*> 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<UMovieSceneSection>(NewObject));
}
public:
TArray<UMovieSceneSection*> NewSections;
};
void ImportSectionsFromText(const FString& TextToImport, /*out*/ TArray<UMovieSceneSection*>& ImportedSections)
{
UPackage* TempPackage = NewObject<UPackage>(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<UMovieSceneSection*>& OutSections, TArray<FNotificationInfo>& OutErrors)
{
// First import as a track and extract sections to allow for copying track contents to another track
TArray<UMovieSceneCopyableTrack*> ImportedTracks;
ImportTracksFromText(TextToImport, ImportedTracks);
TArray<UMovieSceneSection*> 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<FFrameNumber> FirstFrame;
for (UMovieSceneSection* Section : ImportedSections)
{
if (Section->HasStartFrame())
{
if (FirstFrame.IsSet())
{
if (FirstFrame.GetValue() > Section->GetInclusiveStartFrame())
{
FirstFrame = Section->GetInclusiveStartFrame();
}
}
else
{
FirstFrame = Section->GetInclusiveStartFrame();
}
}
}
TArray<int32> 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<UMovieSceneCopyableBinding*>& ObjectsToExport, FOutputDevice& Archive, TSharedRef<UE::MovieScene::FSharedPlaybackState> 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<UMovieSceneTrack*> OldTracks = ObjectToExport->Binding.StealTracks(nullptr);
TArray<UObject*, TInlineAllocator<1>> OldObjectTemplates;
TArray<UMovieSceneCustomBinding*, TInlineAllocator<1>> OldCustomBindings;
TMap<int32, UMovieSceneSpawnableBindingBase*> 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<UMovieSceneSpawnableBindingBase>(CustomBinding))
{
if (SpawnableBinding->SupportsObjectTemplates())
{
OldObjectTemplates.Add(SpawnableBinding->GetObjectTemplate());
SpawnableBinding->SetObjectTemplate(nullptr);
}
}
else if (UMovieSceneReplaceableBindingBase * ReplaceableBinding = Cast<UMovieSceneReplaceableBindingBase>(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<int32, UMovieSceneSpawnableBindingBase*>& PreviewSpawnable : OldPreviewSpawnables)
{
if (UMovieSceneReplaceableBindingBase* ReplaceableBinding = Cast<UMovieSceneReplaceableBindingBase>(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<ISequencer> Sequencer, const TArray<FMovieSceneBindingProxy>& Bindings, const TArray<UMovieSceneFolder*>& InFolders, FString& ExportedText)
{
FStringOutputDevice Archive;
CopyBindings(Sequencer, Bindings, InFolders, (FOutputDevice&)Archive);
ExportedText = Archive;
}
void FSequencerUtilities::CopyBindings(TSharedRef<ISequencer> Sequencer, const TArray<FMovieSceneBindingProxy>& Bindings, const TArray<UMovieSceneFolder*>& InFolders, FOutputDevice& Ar)
{
UWorld* World = GCurrentLevelEditingViewportClient ? GCurrentLevelEditingViewportClient->GetWorld() : nullptr;
TArray<UMovieSceneCopyableBinding*> Objects;
for (const FMovieSceneBindingProxy& ObjectBinding : Bindings)
{
UMovieSceneCopyableBinding* CopyableBinding = NewObject<UMovieSceneCopyableBinding>(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<UMovieSceneTrack>(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<FName, FMovieSceneObjectBindingIDs> 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<UMovieSceneCopyableBinding>())
{
return true;
}
else if (InObjectClass->IsChildOf<UMovieSceneCustomBinding>())
{
return true;
}
else
{
return Sequencer->GetSpawnRegister().CanSpawnObject(InObjectClass);
}
}
virtual void ProcessConstructedObject(UObject* NewObject) override
{
check(NewObject);
if (NewObject->IsA<UMovieSceneCopyableBinding>())
{
UMovieSceneCopyableBinding* CopyableBinding = Cast<UMovieSceneCopyableBinding>(NewObject);
NewCopyableBindings.Add(CopyableBinding);
}
else if (NewObject->IsA<UMovieSceneCustomBinding>())
{
NewCustomBindings.Add(Cast<UMovieSceneCustomBinding>(NewObject));
}
else
{
NewSpawnableObjectTemplates.Add(NewObject);
}
}
public:
TArray<UMovieSceneCopyableBinding*> NewCopyableBindings;
TArray<UObject*> NewSpawnableObjectTemplates;
TArray<UMovieSceneCustomBinding*> NewCustomBindings;
private:
ISequencer* Sequencer;
};
void ImportObjectBindingsFromText(ISequencer& InSequencer, const FString& TextToImport, /*out*/ TArray<UMovieSceneCopyableBinding*>& ImportedObjects)
{
UPackage* TempPackage = NewObject<UPackage>(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<UMovieSceneSpawnableBindingBase>(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<ISequencer> 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<UClass>())
{
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<const TSubclassOf<UMovieSceneCustomBinding>> PrioritySortedCustomBindingTypes;
if (Sequencer)
{
PrioritySortedCustomBindingTypes = Sequencer->GetSupportedCustomBindingTypes();
}
else
{
static TArray<const TSubclassOf<UMovieSceneCustomBinding>> CachedCustomBindingTypes;
static bool CustomBindingTypesCached = false;
if (!CustomBindingTypesCached)
{
CustomBindingTypesCached = true;
MovieSceneHelpers::GetPrioritySortedCustomBindingTypes(CachedCustomBindingTypes);
}
PrioritySortedCustomBindingTypes = CachedCustomBindingTypes;
}
for (const TSubclassOf<UMovieSceneCustomBinding>& 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<UMovieSceneSpawnableBindingBase>();
const bool bIsCustomReplaceableBinding = CustomBindingType->IsChildOf<UMovieSceneReplaceableBindingBase>();
if ((bSpawnable && bIsCustomSpawnableBinding) ||
(bReplaceable && bIsCustomReplaceableBinding) ||
(!bSpawnable && !bReplaceable && !bIsCustomSpawnableBinding && !bIsCustomReplaceableBinding))
{
if (UMovieSceneCustomBinding* CustomBindingCDO = CustomBindingType ? CustomBindingType->GetDefaultObject<UMovieSceneCustomBinding>() : 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<AActor>(SpawnedObject))
{
Sequencer->OnActorAddedToSequencer().Broadcast(Actor, NewID);
}
Sequencer->OnAddBinding(NewID, OwnerMovieScene);
}
}
return NewID;
}
return FGuid();
}
FGuid CreateGenericBinding(TSharedPtr<ISequencer> 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<ISequencerModule>("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<AActor>())
{
// 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<UClass>(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<TPair<UObject*, FString>> 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<IObjectSchema> 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<AActor>(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<ISequencer> Sequencer, FMovieScenePasteBindingsParams PasteBindingsParams, TArray<FMovieSceneBindingProxy>& OutBindings, TArray<FNotificationInfo>& 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<FGuid, FGuid> OldToNewGuidMap;
TArray<FGuid> PossessableGuids;
TArray<TArray<FString> > PossessableObjectNames;
TArray<FGuid> SpawnableGuids;
TMap<FGuid, UMovieSceneFolder*> GuidToFolderMap;
TArray<FMovieSceneBinding> BindingsPasted;
const int NumTargets = FMath::Max(1, PasteBindingsParams.Bindings.Num());
for (int32 TargetIndex = 0; TargetIndex < NumTargets; ++TargetIndex)
{
TArray<UMovieSceneCopyableBinding*> ImportedBindings;
ImportObjectBindingsFromText(Sequencer.Get(), TextToImport, ImportedBindings);
if (ImportedBindings.Num() == 0)
{
return false;
}
TArray<UObject*> 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<UMovieSceneCustomBinding>(StaticDuplicateObject(CustomBinding, MovieScene));
// Need to re-copy the object template to avoid private object issues
if (UMovieSceneSpawnableBindingBase* SpawnableBinding = Cast<UMovieSceneSpawnableBindingBase>(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<UMovieSceneReplaceableBindingBase>(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<UObject*> ObjectsToBind;
UObject* ResolutionContext = FindResolutionContext(Sequencer
, *MovieScene->GetTypedOuter<UMovieSceneSequence>()
, *MovieScene
, Possessable->GetParent()
, Sequencer->GetPlaybackContext());
if (World)
{
for (TActorIterator<AActor> 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<UObject*> SelectedObjects;
for (UObject* ObjectToBind : ObjectsToBind)
{
if (AActor* Actor = Cast<AActor>(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<AActor>(*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<FGuid, FGuid>& OldToNewGuid : OldToNewGuidMap)
{
if (OldToNewGuid.Value == PossessableGuids[PossessableGuidIndex])
{
OldToNewGuid.Value = NewGuid;
}
}
PossessableGuids[PossessableGuidIndex] = NewGuid;
}
}
};
for (TActorIterator<AActor> 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<FName, TObjectPtr<AActor>>& 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<AActor>(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<UObject>(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<UMovieSceneSection*> 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<UE::MovieScene::FFixedObjectBindingID, UE::MovieScene::FFixedObjectBindingID> OldFixedToNewFixedMap;
TSharedRef<UE::MovieScene::FSharedPlaybackState> SharedPlaybackState = Sequencer->GetSharedPlaybackState();
for (TPair<FGuid, FGuid> 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<ISequencer> Sequencer, const FString& TextToImport)
{
FObjectBindingTextFactory ObjectBindingFactory(Sequencer.Get());
return ObjectBindingFactory.CanCreateObjectsFromText(TextToImport);
}
TArray<FString> FSequencerUtilities::GetPasteBindingsObjectNames(TSharedRef<ISequencer> Sequencer, const FString& TextToImport)
{
TArray<FString> ObjectNames;
TArray<UMovieSceneCopyableBinding*> 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<ISequencer> Sequencer, UObject& InObject, const UE::Sequencer::FCreateBindingParams& InParams)
{
UMovieSceneSequence* OwnerSequence = Sequencer->GetFocusedMovieSceneSequence();
UMovieScene* OwnerMovieScene = OwnerSequence->GetMovieScene();
AActor* Actor = Cast<AActor>(&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<AActor>(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<ISequencer> 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<UObject*, TInlineAllocator<1>> 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<ISequencer> Sequencer, UObject& InObject, const UE::Sequencer::FCreateBindingParams& InParams)
{
return CreateOrReplaceBinding(Sequencer.ToSharedPtr(), Sequencer->GetFocusedMovieSceneSequence(), &InObject, InParams);
}
FGuid FSequencerUtilities::CreateOrReplaceBinding(TSharedRef<ISequencer> Sequencer, UObject* InObject, const UE::Sequencer::FCreateBindingParams& InParams)
{
return CreateOrReplaceBinding(Sequencer.ToSharedPtr(), Sequencer->GetFocusedMovieSceneSequence(), InObject, InParams);
}
FGuid FSequencerUtilities::CreateOrReplaceBinding(TSharedPtr<ISequencer> 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<UMovieSceneFolder>(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<ACameraActor>(InObject))
{
NewCameraAdded(Sequencer.ToSharedRef(), NewCamera, BindingGuid);
}
Sequencer->OnAddBinding(BindingGuid, OwnerMovieScene);
}
return BindingGuid;
}
void FSequencerUtilities::UpdateBindingIDs(TSharedRef<ISequencer> Sequencer, FGuid OldGuid, FGuid NewGuid)
{
UMovieSceneCompiledDataManager* CompiledDataManager = FindObject<UMovieSceneCompiledDataManager>(GetTransientPackage(), TEXT("SequencerCompiledDataManager"));
if (!CompiledDataManager)
{
CompiledDataManager = NewObject<UMovieSceneCompiledDataManager>(GetTransientPackage(), "SequencerCompiledDataManager");
}
if (!CompiledDataManager)
{
return;
}
const FMovieSceneSequenceHierarchy* Hierarchy = CompiledDataManager->FindHierarchy(Sequencer->GetEvaluationTemplate().GetCompiledDataID());
FMovieSceneSequenceIDRef FocusedGuid = Sequencer->GetFocusedTemplateID();
TMap<UE::MovieScene::FFixedObjectBindingID, UE::MovieScene::FFixedObjectBindingID> OldFixedToNewFixedMap;
OldFixedToNewFixedMap.Add(UE::MovieScene::FFixedObjectBindingID(OldGuid, FocusedGuid), UE::MovieScene::FFixedObjectBindingID(NewGuid, FocusedGuid));
TSharedRef<UE::MovieScene::FSharedPlaybackState> 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<FMovieSceneSequenceID, FMovieSceneSubSequenceData>& 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<ISequencer> 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<TWeakObjectPtr<>> 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<FGuid>& 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<FGuid> NewComponentGuids;
// Handle components
AActor* ActorToReplace = Cast<AActor>(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<FString, UActorComponent*> ComponentNameToComponent;
for (UActorComponent* Component : Actor->GetComponents())
{
ComponentNameToComponent.Add(Component->GetName(), Component);
}
TMap<FGuid, UActorComponent*> 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<FGuid, UActorComponent*> 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<UMovieSceneFolder*> 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<ISequencer> Sequencer, const TArray<AActor*>& Actors, const FMovieSceneBindingProxy& ObjectBinding)
{
FScopedTransaction AddActorsToBinding(LOCTEXT("AddActorsToBinding", "Add Actors to Binding"));
TArray<UObject*> ObjectsToAdd;
Algo::Transform(Actors, ObjectsToAdd, [](AActor* Actor) { return Actor;});
AddObjectsToBinding(Sequencer, ObjectsToAdd, ObjectBinding, Sequencer->GetPlaybackContext());
}
void FSequencerUtilities::AddObjectsToBinding(TSharedRef<ISequencer> InSequencer, const TArray<UObject*>& 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<TWeakObjectPtr<>> ObjectsInCurrentSequence = InSequencer->FindObjectsInCurrentSequence(Guid);
for (TWeakObjectPtr<> Ptr : ObjectsInCurrentSequence)
{
if (const UObject* Object = Cast<AActor>(Ptr.Get()))
{
ObjectClass = Object->GetClass();
++ValidObjectCount;
}
}
Sequence->Modify();
MovieScene->Modify();
TArray<UObject*> 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<AActor>(AddedObjects[0]))
{
PossessableName = Actor->GetActorLabel();
}
Possessable->SetName(PossessableName);
}
Possessable->SetPossessedObjectClass(ObjectClass);
}
}
InSequencer->RestorePreAnimatedState();
InSequencer->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemsChanged);
}
void FSequencerUtilities::ReplaceBindingWithActors(TSharedRef<ISequencer> Sequencer, const TArray<AActor*>& Actors, const FMovieSceneBindingProxy& ObjectBinding)
{
FScopedTransaction ReplaceBindingWithActors(LOCTEXT("ReplaceBindingWithActors", "Replace Binding with Actors"));
FGuid Guid = ObjectBinding.BindingID;
TArray<AActor*> ExistingActors;
for (TWeakObjectPtr<> Ptr : Sequencer->FindObjectsInCurrentSequence(Guid))
{
if (AActor* Actor = Cast<AActor>(Ptr.Get()))
{
if (!Actors.Contains(Actor))
{
ExistingActors.Add(Actor);
}
}
}
RemoveActorsFromBinding(Sequencer, ExistingActors, ObjectBinding);
TArray<AActor*> NewActors;
for (AActor* NewActor : Actors)
{
if (!ExistingActors.Contains(NewActor))
{
NewActors.Add(NewActor);
}
}
AddActorsToBinding(Sequencer, NewActors, ObjectBinding);
}
void FSequencerUtilities::RemoveActorsFromBinding(TSharedRef<ISequencer> Sequencer, const TArray<AActor*>& 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<AActor>(Ptr.Get()))
{
ActorClass = Actor->GetClass();
++NumRuntimeObjects;
}
}
FScopedTransaction RemoveSelectedFromBinding(LOCTEXT("RemoveSelectedFromBinding", "Remove Selected from Binding"));
TArray<UObject*> ObjectsToRemove;
for (AActor* ActorToRemove : Actors)
{
// Restore state on any components
for (UActorComponent* Component : TInlineComponentArray<UActorComponent*>(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<ISequencer> Sequencer)
{
StaticCastSharedPtr<FSequencer>(Sequencer.ToSharedPtr())->SaveCurrentMovieSceneAs();
}
void FSequencerUtilities::SynchronizeExternalSelectionWithSequencerSelection (TSharedRef<ISequencer> Sequencer)
{
StaticCastSharedPtr<FSequencer>(Sequencer.ToSharedPtr())->SynchronizeExternalSelectionWithSequencerSelection();
}
TRange<FFrameNumber> FSequencerUtilities::GetTimeBounds(TSharedRef<ISequencer> Sequencer)
{
return StaticCastSharedPtr<FSequencer>(Sequencer.ToSharedPtr())->GetTimeBounds();
}
void FSequencerUtilities::AddChangeClassMenu(FMenuBuilder& MenuBuilder, TSharedRef<ISequencer> Sequencer, const TArray<FSequencerChangeBindingInfo>& Bindings, TFunction<void()> 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<const FMovieSceneBindingReference> 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<UMovieSceneCustomBinding> CustomBinding;
};
TSharedRef<FCustomBindingClassFilter> ClassFilter = MakeShared<FCustomBindingClassFilter>();
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<FClassViewerModule>("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<ISequencer> Sequencer, const TArray<FSequencerChangeBindingInfo>& Bindings, TFunction<void()> OnBindingChanged)
{
UMovieScene* MovieScene = Sequencer->GetFocusedMovieSceneSequence()->GetMovieScene();
FScopedTransaction Transaction(LOCTEXT("ChangeClass", "Change Class"));
MovieScene->Modify();
TValueOrError<FNewSpawnable, FText> 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<ISequencer> 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<const FMovieSceneBindingReference> BindingReferencesList = BindingReferences->GetReferences(BindingGuid);
if (BindingReferencesList.IsValidIndex(BindingIndex) && BindingReferencesList[BindingIndex].CustomBinding != nullptr)
{
return true;
}
}
return false;
}
bool FSequencerUtilities::CanConvertToCustomBinding(TSharedRef<ISequencer> Sequencer, FGuid BindingGuid, TSubclassOf<UMovieSceneCustomBinding> 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<UMovieSceneCustomBinding>()->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<const FMovieSceneBindingReference> 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<UMovieSceneCustomBinding>()->SupportsConversionFromBinding(*CurrentBindingReference, CurrentBoundObject))
{
return true;
}
}
}
}
return false;
}
UMovieSceneSequence* FSequencerUtilities::GetMovieSceneSequence(TSharedPtr<ISequencer>& 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<void()> StartupComplete)
{
auto RegisterWatcher = [this, StartupCompleteFunc = MoveTemp(StartupComplete)]()
{
ISequencerModule& SequencerModule = FModuleManager::Get().LoadModuleChecked<ISequencerModule>("Sequencer");
SequencerModule.RegisterOnSequencerCreated(
FOnSequencerCreated::FDelegate::CreateRaw(this, &FOpenSequencerWatcher::OnSequencerCreated));
StartupCompleteFunc();
};
if (GEngine)
{
RegisterWatcher();
}
else
{
FCoreDelegates::OnFEngineLoopInitComplete.AddLambda(RegisterWatcher);
}
}
void FOpenSequencerWatcher::OnSequencerCreated(TSharedRef<ISequencer> InSequencer)
{
FOpenSequencerData OpenSequencer;
OpenSequencer.WeakSequencer = TWeakPtr<ISequencer>(InSequencer);
OpenSequencer.OnCloseEventHandle = InSequencer->OnCloseEvent().AddRaw(this, &FOpenSequencerWatcher::OnSequencerClosed);
OpenSequencers.Add(MoveTemp(OpenSequencer));
}
void FOpenSequencerWatcher::OnSequencerClosed(TSharedRef<ISequencer> InSequencer)
{
OpenSequencers.RemoveAll([SequencerObject=&InSequencer.Get()](const FOpenSequencerData& Data)
{
return Data.WeakSequencer.HasSameObject(SequencerObject);
});
}
#undef LOCTEXT_NAMESPACE