2019-12-27 09:26:59 -05:00
|
|
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
2018-05-29 14:30:28 -04:00
|
|
|
|
2018-05-14 17:38:22 -04:00
|
|
|
#include "SequencerTools.h"
|
2023-01-19 00:48:07 -05:00
|
|
|
#include "Animation/AnimSequence.h"
|
2022-10-26 12:57:32 -04:00
|
|
|
#include "Engine/SkeletalMesh.h"
|
2018-05-14 17:38:22 -04:00
|
|
|
#include "AutomatedLevelSequenceCapture.h"
|
2023-01-19 00:48:07 -05:00
|
|
|
#include "Channels/MovieSceneBoolChannel.h"
|
2020-01-27 20:11:15 -05:00
|
|
|
#include "LevelSequenceActor.h"
|
2023-01-19 00:48:07 -05:00
|
|
|
#include "Channels/MovieSceneEvent.h"
|
2019-05-30 15:52:55 -04:00
|
|
|
#include "FbxExporter.h"
|
2023-01-19 00:48:07 -05:00
|
|
|
#include "Channels/MovieSceneFloatChannel.h"
|
2019-05-30 15:52:55 -04:00
|
|
|
#include "MovieSceneToolsUserSettings.h"
|
2023-01-19 00:48:07 -05:00
|
|
|
#include "Channels/MovieSceneIntegerChannel.h"
|
2019-05-30 15:52:55 -04:00
|
|
|
#include "MovieSceneToolHelpers.h"
|
2023-01-19 00:48:07 -05:00
|
|
|
#include "Engine/World.h"
|
2020-09-24 00:43:27 -04:00
|
|
|
#include "MovieSceneEventUtils.h"
|
2023-01-19 00:48:07 -05:00
|
|
|
#include "INodeAndChannelMappings.h"
|
2020-09-24 00:43:27 -04:00
|
|
|
#include "MovieSceneSequenceEditor.h"
|
2023-01-19 00:48:07 -05:00
|
|
|
#include "LevelSequence.h"
|
2020-09-24 00:43:27 -04:00
|
|
|
#include "Sections/MovieSceneEventSectionBase.h"
|
2019-05-30 15:52:55 -04:00
|
|
|
#include "CineCameraActor.h"
|
2020-01-22 17:58:55 -05:00
|
|
|
#include "Components/SkeletalMeshComponent.h"
|
2023-01-19 00:48:07 -05:00
|
|
|
#include "MovieScenePossessable.h"
|
2020-02-14 11:53:25 -05:00
|
|
|
#include "ScopedTransaction.h"
|
2020-09-24 00:43:27 -04:00
|
|
|
#include "Exporters/AnimSeqExportOption.h"
|
|
|
|
|
|
|
|
|
|
#include "K2Node_CustomEvent.h"
|
|
|
|
|
#include "BlueprintFunctionNodeSpawner.h"
|
|
|
|
|
#include "BlueprintActionMenuItem.h"
|
|
|
|
|
#include "EdGraphSchema_K2.h"
|
2018-05-14 17:38:22 -04:00
|
|
|
|
2022-04-21 13:12:26 -04:00
|
|
|
#include "LevelSequenceAnimSequenceLink.h"
|
|
|
|
|
#include "AnimSequenceLevelSequenceLink.h"
|
|
|
|
|
|
2022-09-09 14:15:58 -04:00
|
|
|
#include "Compilation/MovieSceneCompiledDataManager.h"
|
2023-01-19 00:48:07 -05:00
|
|
|
#include "MovieSceneSpawnable.h"
|
|
|
|
|
#include "SequencerScriptingRange.h"
|
2022-09-09 14:15:58 -04:00
|
|
|
|
2022-09-28 01:06:15 -04:00
|
|
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(SequencerTools)
|
|
|
|
|
|
2018-05-14 17:38:22 -04:00
|
|
|
#define LOCTEXT_NAMESPACE "SequencerTools"
|
|
|
|
|
|
2019-01-10 17:26:53 -05:00
|
|
|
bool USequencerToolsFunctionLibrary::RenderMovie(UMovieSceneCapture* InCaptureSettings, FOnRenderMovieStopped OnFinishedCallback)
|
2018-05-14 17:38:22 -04:00
|
|
|
{
|
|
|
|
|
IMovieSceneCaptureDialogModule& MovieSceneCaptureModule = FModuleManager::Get().LoadModuleChecked<IMovieSceneCaptureDialogModule>("MovieSceneCaptureDialog");
|
|
|
|
|
|
|
|
|
|
// Because this comes from the Python/BP layer we need to soft-validate the state before we pass it onto functions that do a assert-based validation.
|
|
|
|
|
if (!InCaptureSettings)
|
|
|
|
|
{
|
|
|
|
|
FFrame::KismetExecutionMessage(TEXT("Cannot start Render Sequence to Movie with null capture settings."), ELogVerbosity::Error);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-21 14:03:54 -04:00
|
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
2018-05-14 17:38:22 -04:00
|
|
|
if (IsRenderingMovie())
|
|
|
|
|
{
|
|
|
|
|
FFrame::KismetExecutionMessage(TEXT("Capture already in progress."), ELogVerbosity::Error);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2023-04-21 14:03:54 -04:00
|
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
2018-05-14 17:38:22 -04:00
|
|
|
|
|
|
|
|
// If they're capturing a level sequence we'll do some additional checking as there are more parameters on the Automated Level Sequence capture.
|
|
|
|
|
UAutomatedLevelSequenceCapture* LevelSequenceCapture = Cast<UAutomatedLevelSequenceCapture>(InCaptureSettings);
|
|
|
|
|
if (LevelSequenceCapture)
|
|
|
|
|
{
|
|
|
|
|
if (!LevelSequenceCapture->LevelSequenceAsset.IsValid())
|
|
|
|
|
{
|
|
|
|
|
// UE_LOG(LogTemp, Warning, TEXT("No Level Sequence Asset specified in UAutomatedLevelSequenceCapture."));
|
|
|
|
|
FFrame::KismetExecutionMessage(TEXT("No Level Sequence Asset specified in UAutomatedLevelSequenceCapture."), ELogVerbosity::Error);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!LevelSequenceCapture->bUseCustomStartFrame && !LevelSequenceCapture->bUseCustomEndFrame)
|
|
|
|
|
{
|
|
|
|
|
// If they don't want to use a custom start/end frame we override the default values to be the length of the sequence, as the default is [0,1)
|
|
|
|
|
ULevelSequence* LevelSequence = Cast<ULevelSequence>(LevelSequenceCapture->LevelSequenceAsset.TryLoad());
|
|
|
|
|
if (!LevelSequence)
|
|
|
|
|
{
|
|
|
|
|
const FString ErrorMessage = FString::Printf(TEXT("Specified Level Sequence Asset failed to load. Specified Asset Path: %s"), *LevelSequenceCapture->LevelSequenceAsset.GetAssetPathString());
|
|
|
|
|
FFrame::KismetExecutionMessage(*ErrorMessage, ELogVerbosity::Error);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FFrameRate DisplayRate = LevelSequence->GetMovieScene()->GetDisplayRate();
|
|
|
|
|
FFrameRate TickResolution = LevelSequence->GetMovieScene()->GetTickResolution();
|
|
|
|
|
|
2019-04-19 12:53:47 -04:00
|
|
|
LevelSequenceCapture->Settings.CustomFrameRate = DisplayRate;
|
|
|
|
|
LevelSequenceCapture->Settings.bUseCustomFrameRate = true;
|
2018-05-14 17:38:22 -04:00
|
|
|
LevelSequenceCapture->Settings.bUseRelativeFrameNumbers = false;
|
|
|
|
|
TRange<FFrameNumber> Range = LevelSequence->GetMovieScene()->GetPlaybackRange();
|
|
|
|
|
|
2020-08-11 01:36:57 -04:00
|
|
|
FFrameNumber StartFrame = UE::MovieScene::DiscreteInclusiveLower(Range);
|
|
|
|
|
FFrameNumber EndFrame = UE::MovieScene::DiscreteExclusiveUpper(Range);
|
2018-05-14 17:38:22 -04:00
|
|
|
|
|
|
|
|
FFrameNumber RoundedStartFrame = FFrameRate::TransformTime(StartFrame, TickResolution, DisplayRate).CeilToFrame();
|
|
|
|
|
FFrameNumber RoundedEndFrame = FFrameRate::TransformTime(EndFrame, TickResolution, DisplayRate).CeilToFrame();
|
|
|
|
|
|
|
|
|
|
LevelSequenceCapture->CustomStartFrame = RoundedStartFrame;
|
|
|
|
|
LevelSequenceCapture->CustomEndFrame = RoundedEndFrame;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-10 17:26:53 -05:00
|
|
|
auto LocalCaptureStoppedCallback = [OnFinishedCallback](bool bSuccess)
|
|
|
|
|
{
|
|
|
|
|
OnFinishedCallback.ExecuteIfBound(bSuccess);
|
|
|
|
|
};
|
|
|
|
|
|
2018-05-14 17:38:22 -04:00
|
|
|
MovieSceneCaptureModule.StartCapture(InCaptureSettings);
|
2019-01-10 17:26:53 -05:00
|
|
|
MovieSceneCaptureModule.GetCurrentCapture()->CaptureStoppedDelegate.AddLambda(LocalCaptureStoppedCallback);
|
2018-05-14 17:38:22 -04:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void USequencerToolsFunctionLibrary::CancelMovieRender()
|
|
|
|
|
{
|
|
|
|
|
IMovieSceneCaptureDialogModule& MovieSceneCaptureModule = FModuleManager::Get().LoadModuleChecked<IMovieSceneCaptureDialogModule>("MovieSceneCaptureDialog");
|
|
|
|
|
TSharedPtr<FMovieSceneCaptureBase> CurrentCapture = MovieSceneCaptureModule.GetCurrentCapture();
|
|
|
|
|
if (CurrentCapture.IsValid())
|
|
|
|
|
{
|
|
|
|
|
// We just invoke the capture's Cancel function. This will cause a shut-down of the capture (the same as the UI)
|
|
|
|
|
// which will invoke all of the necessary callbacks as well. We don't null out CurrentCapture because that is done
|
|
|
|
|
// as the result of its shutdown callbacks.
|
|
|
|
|
CurrentCapture->Cancel();
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-05-30 15:52:55 -04:00
|
|
|
|
2022-06-24 19:54:28 -04:00
|
|
|
TArray<FSequencerBoundObjects> USequencerToolsFunctionLibrary::GetBoundObjects(UWorld* InWorld, ULevelSequence* InSequence, const TArray<FMovieSceneBindingProxy>& InBindings, const FSequencerScriptingRange& InRange)
|
2020-01-27 20:11:15 -05:00
|
|
|
{
|
|
|
|
|
ALevelSequenceActor* OutActor;
|
|
|
|
|
FMovieSceneSequencePlaybackSettings Settings;
|
|
|
|
|
FLevelSequenceCameraSettings CameraSettings;
|
|
|
|
|
|
|
|
|
|
ULevelSequencePlayer* Player = ULevelSequencePlayer::CreateLevelSequencePlayer(InWorld, InSequence, Settings, OutActor);
|
|
|
|
|
|
|
|
|
|
// Evaluation needs to occur in order to obtain spawnables
|
|
|
|
|
FFrameRate Resolution = InSequence->GetMovieScene()->GetTickResolution();
|
|
|
|
|
TRange<FFrameNumber> SpecifiedRange = InRange.ToNative(Resolution);
|
2021-04-08 14:32:07 -04:00
|
|
|
Player->SetPlaybackPosition(FMovieSceneSequencePlaybackParams(SpecifiedRange.GetLowerBoundValue().Value, EUpdatePositionMethod::Play));
|
2020-01-27 20:11:15 -05:00
|
|
|
|
|
|
|
|
FMovieSceneSequenceID SequenceId = Player->State.FindSequenceId(InSequence);
|
|
|
|
|
|
|
|
|
|
TArray<FSequencerBoundObjects> BoundObjects;
|
2022-06-24 19:54:28 -04:00
|
|
|
for (FMovieSceneBindingProxy Binding : InBindings)
|
2020-01-27 20:11:15 -05:00
|
|
|
{
|
2020-12-14 14:36:17 -04:00
|
|
|
FMovieSceneObjectBindingID ObjectBinding = UE::MovieScene::FFixedObjectBindingID(Binding.BindingID, SequenceId);
|
2020-01-27 20:11:15 -05:00
|
|
|
BoundObjects.Add(FSequencerBoundObjects(Binding, Player->GetBoundObjects(ObjectBinding)));
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-14 11:53:25 -05:00
|
|
|
Player->Stop();
|
2020-01-27 20:11:15 -05:00
|
|
|
InWorld->DestroyActor(OutActor);
|
|
|
|
|
return BoundObjects;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TArray<FSequencerBoundObjects> USequencerToolsFunctionLibrary::GetObjectBindings(UWorld* InWorld, ULevelSequence* InSequence, const TArray<UObject*>& InObjects, const FSequencerScriptingRange& InRange)
|
|
|
|
|
{
|
|
|
|
|
ALevelSequenceActor* OutActor;
|
|
|
|
|
FMovieSceneSequencePlaybackSettings Settings;
|
|
|
|
|
FLevelSequenceCameraSettings CameraSettings;
|
|
|
|
|
|
|
|
|
|
ULevelSequencePlayer* Player = ULevelSequencePlayer::CreateLevelSequencePlayer(InWorld, InSequence, Settings, OutActor);
|
|
|
|
|
|
|
|
|
|
FFrameRate Resolution = InSequence->GetMovieScene()->GetTickResolution();
|
|
|
|
|
TRange<FFrameNumber> SpecifiedRange = InRange.ToNative(Resolution);
|
2021-04-08 14:32:07 -04:00
|
|
|
Player->SetPlaybackPosition(FMovieSceneSequencePlaybackParams(SpecifiedRange.GetLowerBoundValue().Value, EUpdatePositionMethod::Play));
|
2020-01-27 20:11:15 -05:00
|
|
|
|
|
|
|
|
TArray<FSequencerBoundObjects> BoundObjects;
|
|
|
|
|
|
|
|
|
|
for (UObject* Object : InObjects)
|
|
|
|
|
{
|
|
|
|
|
TArray<FMovieSceneObjectBindingID> ObjectBindings = Player->GetObjectBindings(Object);
|
|
|
|
|
for (FMovieSceneObjectBindingID ObjectBinding : ObjectBindings)
|
|
|
|
|
{
|
2020-12-14 14:36:17 -04:00
|
|
|
FMovieSceneSequenceID SequenceID = ObjectBinding.ResolveSequenceID(MovieSceneSequenceID::Root, *Player);
|
2022-06-24 19:54:28 -04:00
|
|
|
FMovieSceneBindingProxy Binding(ObjectBinding.GetGuid(), Player->State.FindSequence(SequenceID));
|
2020-01-27 20:11:15 -05:00
|
|
|
BoundObjects.Add(FSequencerBoundObjects(Binding, TArray<UObject*>({ Object })));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-14 11:53:25 -05:00
|
|
|
Player->Stop();
|
2020-01-27 20:11:15 -05:00
|
|
|
InWorld->DestroyActor(OutActor);
|
|
|
|
|
return BoundObjects;
|
|
|
|
|
}
|
2019-05-30 15:52:55 -04:00
|
|
|
|
2022-06-24 19:54:28 -04:00
|
|
|
void GatherDescendantBindings(const FMovieSceneBindingProxy& Binding, UMovieSceneSequence* Sequence, TArray<FMovieSceneBindingProxy>& AllBindings)
|
2022-02-10 15:23:50 -05:00
|
|
|
{
|
|
|
|
|
UMovieScene* MovieScene = Sequence->GetMovieScene();
|
|
|
|
|
|
|
|
|
|
for (int32 Index = 0; Index < MovieScene->GetPossessableCount(); ++Index)
|
|
|
|
|
{
|
|
|
|
|
FMovieScenePossessable& Possessable = MovieScene->GetPossessable(Index);
|
|
|
|
|
if (Possessable.GetParent() == Binding.BindingID)
|
|
|
|
|
{
|
2022-06-24 19:54:28 -04:00
|
|
|
FMovieSceneBindingProxy ChildBinding(Possessable.GetGuid(), Sequence);
|
2022-02-10 15:23:50 -05:00
|
|
|
AllBindings.Add(ChildBinding);
|
|
|
|
|
GatherDescendantBindings(ChildBinding, Sequence, AllBindings);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-09 14:15:58 -04:00
|
|
|
bool ExportFBXInternal(const FSequencerExportFBXParams& InParams, UMovieSceneSequencePlayer* Player)
|
2019-05-30 15:52:55 -04:00
|
|
|
{
|
2022-09-09 14:15:58 -04:00
|
|
|
UWorld* World = InParams.World;
|
|
|
|
|
UMovieSceneSequence* Sequence = InParams.Sequence;
|
|
|
|
|
UMovieSceneSequence* RootSequence = InParams.RootSequence;
|
|
|
|
|
TArray<FMovieSceneBindingProxy> BindingProxies = InParams.Bindings;
|
Sequencer: Remove Master from Sequencer
- Remove all instances of Master from the UI - ie. Replace Create Master Sequence with Create Sequence with Shots
- Internally, remove Master - Master Tracks in the MovieScene can just be Tracks
Deprecations (Properties):
- UMovieScene:MasterTracks deprecated for Tracks
- UMovieSceneFolder:ChildMasterTracks deprecated for ChildTracks
- FLevelSequencePlayerSnapshot: MasterName, MasterTime for RootName, RootTime
- UMoviePipelineSetting_BlueprintBase: bIsValidOnMaster for bIsValidOnPrimary
Deprecations (Functions):
- UMovieScene: GetMasterTracks, FindMasterTracksByType, FindMasterTracksByExactType, AddMasterTrack, RemoveMasterTrack deprecated for GetTracks, FindTracksByType, FindTracksByExactType, AddTrack, RemoveTrack
- UMovieSceneFolder: GetChildMasterTracks, AddChildMasterTrack, RemoveChildMasterTrack deprecated for GetChildTracks, AddChildTracks, RemoveChildTrack
- FMovieSceneTrackEditor: FFindOrCreateMasterTrackResult, FindOrCreateMasterTrack deprecated for FindOrCreateRootTrackResult, FindOrCreateRootTrack
- ULevelSequenceDirector: GetMasterSequenceTime deprecated for ULevelSequenceDirector::GetRootSequenceTime
- UDaySequenceDirector: GetMasterSequenceTime deprecated for UDaySequenceDirector::GetRootSequenceTime
- UMoviePipelineSetting: IsValidOnMaster for IsValidOnPrimary
- UMoviePipelineBlueprintLibrary: GetMasterTimecode, GetMasterFrameNumber for GetRootTimecode, GetRootFrameNumber
- UMoviePipeline: GetPipelineMasterConfig for GetPipelinePrimaryConfig
- UMovieSceneAudioTrack: IsAMasterTrack
Classes Renamed:
- FAnimTypePreAnimatedStateMasterStorage for FAnimTypePreAnimatedStateRootStorage
- UMovieSceneMasterInstantiatorSystem for UMovieSceneRootInstantiatorSystem
- ULevelSequenceMasterSequenceSettings to ULevelSequenceSequenceWithShotsSetting
Note, the following instances of master still occur and a "KnownCase" has been added for them in CheckAcceptableWords.cs:
- Deprecated properties (MasterTracks -> Tracks) and functions (ie. GetMasterTracks -> GetTracks)
- masterclip in FCPXML since it's an industry standard term
- masteraudiosubmix in AudioCaptureProtocol
- mastersubmix in MoviePipelineAudioRendering
- masteertimecode, masterframenumber, masterconfig, validonmaster in MovieRenderPipeline
- mastersequencetime, mastertime, mastername in LevelSequence
#jira UE-158660
#preflight 634645e48a0a7b2adc5722e7
#rb matt.hoffman, andrew.rodham
[CL 22739302 by Max Chen in ue5-main branch]
2022-10-24 18:22:10 -04:00
|
|
|
TArray<UMovieSceneTrack*> Tracks = InParams.Tracks;
|
2022-09-09 14:15:58 -04:00
|
|
|
UFbxExportOption* OverrideOptions = InParams.OverrideOptions;
|
|
|
|
|
FString FBXFileName = InParams.FBXFileName;
|
|
|
|
|
|
2023-01-13 12:39:18 -05:00
|
|
|
if (!RootSequence)
|
|
|
|
|
{
|
|
|
|
|
RootSequence = Sequence;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!RootSequence)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-30 15:52:55 -04:00
|
|
|
UnFbx::FFbxExporter* Exporter = UnFbx::FFbxExporter::GetInstance();
|
|
|
|
|
//Show the fbx export dialog options
|
|
|
|
|
Exporter->SetExportOptionsOverride(OverrideOptions);
|
|
|
|
|
|
|
|
|
|
UMovieScene* MovieScene = Sequence->GetMovieScene();
|
2022-02-10 15:23:50 -05:00
|
|
|
|
2022-06-24 19:54:28 -04:00
|
|
|
TArray<FMovieSceneBindingProxy> AllBindings;
|
2022-09-09 14:15:58 -04:00
|
|
|
for (const FMovieSceneBindingProxy& Binding : BindingProxies)
|
2022-02-10 15:23:50 -05:00
|
|
|
{
|
|
|
|
|
AllBindings.Add(Binding);
|
|
|
|
|
|
|
|
|
|
GatherDescendantBindings(Binding, Sequence, AllBindings);
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-30 15:52:55 -04:00
|
|
|
TArray<FGuid> Bindings;
|
2022-06-24 19:54:28 -04:00
|
|
|
for (const FMovieSceneBindingProxy& Proxy : AllBindings)
|
2019-05-30 15:52:55 -04:00
|
|
|
{
|
|
|
|
|
if (Proxy.Sequence == Sequence)
|
|
|
|
|
{
|
|
|
|
|
Bindings.Add(Proxy.BindingID);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-09 14:15:58 -04:00
|
|
|
Player->State.AssignSequence(MovieSceneSequenceID::Root, *RootSequence, *Player);
|
|
|
|
|
FMovieSceneSequenceIDRef Template = Player->State.FindSequenceId(Sequence);
|
2022-07-20 12:55:33 -04:00
|
|
|
UnFbx::FFbxExporter::FLevelSequenceNodeNameAdapter NodeNameAdapter(MovieScene, Player, Template);
|
2022-09-09 14:15:58 -04:00
|
|
|
|
|
|
|
|
FMovieSceneSequenceHierarchy Hierarchy = FMovieSceneSequenceHierarchy();
|
|
|
|
|
UMovieSceneCompiledDataManager::CompileHierarchy(RootSequence, &Hierarchy, EMovieSceneServerClientMask::All);
|
|
|
|
|
|
|
|
|
|
const FMovieSceneSubSequenceData* SubSequenceData = Hierarchy.FindSubData(Template);
|
2019-10-01 20:41:42 -04:00
|
|
|
FMovieSceneSequenceTransform RootToLocalTransform;
|
2022-09-09 14:15:58 -04:00
|
|
|
|
|
|
|
|
FFrameTime StartTime = FFrameRate::TransformTime(UE::MovieScene::DiscreteInclusiveLower(MovieScene->GetPlaybackRange()).Value, MovieScene->GetTickResolution(), MovieScene->GetDisplayRate());
|
|
|
|
|
|
|
|
|
|
if (SubSequenceData)
|
|
|
|
|
{
|
|
|
|
|
RootToLocalTransform = SubSequenceData->RootToSequenceTransform;
|
2023-08-01 06:45:24 -04:00
|
|
|
StartTime = UE::MovieScene::DiscreteInclusiveLower(SubSequenceData->PlayRange.Value) * RootToLocalTransform.InverseNoLooping();
|
2022-09-09 14:15:58 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool bDidExport = false;
|
2020-09-24 00:43:27 -04:00
|
|
|
|
2020-02-14 11:53:25 -05:00
|
|
|
{
|
Sequencer- Refactor Bindings to support inherited Custom Bindings.
With this checkin, the binding system inside Level Sequences has been refactored to allow C++ custom binding classes inheriting from a new class, UMovieSceneCustomBinding. Custom bindings are held as instanced UObjects inside FMovieSceneBindingReference, alongside the Universal Object Locator. In the case where a custom binding has been specified, the UOL will be ignored.
The intention with this change is to allow developers to create their own arbitrary methods of binding any UObject to a track inside Sequencer. Examples of this include:
* Runtime dynamic bindings to spawned NPC or player characters (e.g. in UEFN) with a different spawned preview character
* Blueprint-specified dynamic bindings
* Spawnable bindings based on something other than a AActor template
* Scene Graph/Prefabs
Alongside the base UMovieSceneCustomBinding class, a new hierarchy of spawnable custom binding classes has been created as the basis for allowing spawnable bindings that don't require object templates, and may instead require other asset references, for example UEFN NPC character spawnables requiring an NPC Character Definition asset.
This hierarchy is currently. UMovieSceneCustomBinding <- UMovieSceneSpawnableBindingBase <- UMovieSceneSpawnableActorBindingBase <- UMovieSceneSpawnableActorBinding.
This hierarchy allows for spawnables of non-actor types, spawnables of actor types not based on templates, and finally UMovieSceneSpawnableActorBinding, which is a re-implementation of FMovieSceneSpawnable inside the custom binding system.
Going forward, we will be moving to deprecate FMovieSceneSpawnable in UE Sequencer in favor of the custom spawnable binding types. Sequences with FMovieSceneSpawnables will continue to function as normal, but new spawnables in new sequences will use UMovieSceneSpawnableActorBinding under the hood. These will technically have FMovieScenePossessable structs created for them for now rather than FMovieSceneSpawnable structs. This should be mostly invisible to the user.
In addition, I've added a separate hierarchy of custom bindings called 'Replaceable' bindings. A 'Replaceable' is a binding type that will expect in runtime to always be overridden by a different object that may not be available in editor. UMovieSceneReplaceableBinding will hold in EDITORONLY_DATA a UMovieSceneSpawnableBindingBase to provide a preview while within Sequencer. Included in this is a UMovieSceneReplaceableActorBinding, which uses the UMovieSceneSpawnableActorBinding internally to spawn its preview, and has no implementation to find the actor during runtime- it relies on the user overriding the actor binding using the existing BindingOverrides method on LevelSequenceActor.
Next Steps:
* UEFN custom bindings will be built upon this system, to allow UEFN users multiple ways to override bindings.
* UEFN custom spawnable bindings will be built upon this system, to allow UEFN users a way to spawn actors and objects within Sequencer with their lifetime managed by Sequencer
* With this change, we have a cleaner way of incorporating the 'dynamic binding' work that uses the Director Blueprint to create a spawnable or possessable. This should be incorporated into the UCustomBinding hierarchy.
* Once Verse supports it, we should add Verse custom binding logic
* We need to add a Universal Object Locator type that can reference any other binding in the sequence hierarchy. This will allow us to adapt the existing 'Spawnable Parent' possessable binding logic to reference any other type of binding, not just spawnable ones.
* Eventually we will fully deprecate FMovieSceneSpawnable
Further notes:
* A previous imagined version of this system used the cache as a way to allow locators to spawn actors and register them with Sequencer. This change removes this code.
* Custom Spawnable Bindings use the Binding Lifetime Track to determine their spawn lifetime rather than the old spawn track. This unifies the concept of the lifetime of an object binding within sequencer for both spawnables and possessables.
* As FMovieScenePossessable and FMovieSceneBindingReferences allows multiple bindings per guid, this change now allows multiple spawnables to be spawned and controlled by a single binding track. However, some of the editor tooling code doesn't currently handle this properly and at times assumes a single binding. This should be resolved one way or the other.
* The UX for this allows UCustomBindings to handle creating bindings from any dragged in object using a priority system- Sequencer will ask all classes in priority order if they support binding creation from that object.
* The UX supports unique overlay icons and tooltips for different types of bindings
* The UX allows each binding type to decide whether it can convert a current binding into its own type and then do so
* Currently there is some manual instanced detail customization for UMovieSceneCustomSpawnableActorBinding. We use instanced customization so that the UX has the sequencer context. This should both be improved (to allow ways to choose a class/template via this UI), as well as extended so that clients outside of the Movie Scene plugin can register instanced detail customizations for their own binding types.
* Where possible, Sequencer APIs around spawnables have been refactored to take guids rather than FMovieSceneSpawnable references. In particular, custom spawnables do still use the spawn register, but do not use actor spawners- instead the UMovieSceneCustomBinding class is given the responsiblity of spawning/destroying the object/actor. This created an awkward API for USD export, which had overridden the spawn register to unhide/hide actors rather than spawning/destroying them. This could use some more work. MetaHumanPerformanceExportUtils will also need to be refactored at some point to use the custom spawnable binding system.
* FSequencerUtilities::CreateBinding and CreateGenericBinding has been refactored to accept binding parameters and now supports creating custom bindings including spawnables, as well as replacing existing bindings and adding additional bindings to an existing guid.
[REVIEW] [at]ue-sequencer
#jira UE-199299, FORT-582815, UE-209594
#rb Andrew.Rodham, daniel.coelho, Max.Chen
[CL 32218187 by david bromberg in ue5-main branch]
2024-03-13 12:48:46 -04:00
|
|
|
FSpawnableRestoreState SpawnableRestoreState(MovieScene, Player->GetSharedPlaybackState().ToSharedPtr());
|
2020-02-14 11:53:25 -05:00
|
|
|
|
|
|
|
|
if (SpawnableRestoreState.bWasChanged)
|
|
|
|
|
{
|
|
|
|
|
// Evaluate at the beginning of the subscene time to ensure that spawnables are created before export
|
2022-09-06 15:41:15 -04:00
|
|
|
Player->SetPlaybackPosition(FMovieSceneSequencePlaybackParams(StartTime, EUpdatePositionMethod::Play));
|
2020-02-14 11:53:25 -05:00
|
|
|
}
|
|
|
|
|
|
2024-03-26 19:50:55 -04:00
|
|
|
FAnimExportSequenceParameters AESP;
|
|
|
|
|
AESP.Player = Player;
|
|
|
|
|
AESP.RootToLocalTransform = RootToLocalTransform;
|
|
|
|
|
AESP.MovieSceneSequence = Sequence;
|
|
|
|
|
AESP.RootMovieSceneSequence = RootSequence;
|
|
|
|
|
bDidExport = MovieSceneToolHelpers::ExportFBX(World, AESP, Bindings, Tracks, NodeNameAdapter, Template, FBXFileName);
|
2020-02-14 11:53:25 -05:00
|
|
|
}
|
2020-09-24 00:43:27 -04:00
|
|
|
|
2020-02-14 11:53:25 -05:00
|
|
|
Player->Stop();
|
2019-05-30 15:52:55 -04:00
|
|
|
Exporter->SetExportOptionsOverride(nullptr);
|
2020-02-14 11:53:25 -05:00
|
|
|
|
2019-05-30 15:52:55 -04:00
|
|
|
return bDidExport;
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-09 14:15:58 -04:00
|
|
|
bool USequencerToolsFunctionLibrary::ExportLevelSequenceFBX(const FSequencerExportFBXParams& InParams)
|
2021-03-05 19:27:14 -04:00
|
|
|
{
|
|
|
|
|
ALevelSequenceActor* OutActor;
|
|
|
|
|
FMovieSceneSequencePlaybackSettings Settings;
|
|
|
|
|
FLevelSequenceCameraSettings CameraSettings;
|
2023-01-13 12:39:18 -05:00
|
|
|
|
|
|
|
|
ULevelSequence* RootSequence = InParams.RootSequence;
|
|
|
|
|
if (!RootSequence)
|
|
|
|
|
{
|
|
|
|
|
RootSequence = InParams.Sequence;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!RootSequence)
|
|
|
|
|
{
|
|
|
|
|
FFrame::KismetExecutionMessage(TEXT("Cannot export level sequence. Sequence is invalid."), ELogVerbosity::Error);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ULevelSequencePlayer* Player = ULevelSequencePlayer::CreateLevelSequencePlayer(InParams.World, RootSequence, Settings, OutActor);
|
2022-09-09 14:15:58 -04:00
|
|
|
|
|
|
|
|
bool bSuccess = ExportFBXInternal(InParams, Player);
|
|
|
|
|
InParams.World->DestroyActor(OutActor);
|
2021-03-05 19:27:14 -04:00
|
|
|
|
|
|
|
|
return bSuccess;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 19:54:28 -04:00
|
|
|
static USkeletalMeshComponent* GetSkelMeshComponent(IMovieScenePlayer* Player, const FMovieSceneBindingProxy& Binding)
|
2020-01-22 17:58:55 -05:00
|
|
|
{
|
|
|
|
|
FMovieSceneSequenceIDRef Template = MovieSceneSequenceID::Root;
|
|
|
|
|
for (TWeakObjectPtr<UObject> RuntimeObject : Player->FindBoundObjects(Binding.BindingID, Template))
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
if (AActor* Actor = Cast<AActor>(RuntimeObject.Get()))
|
|
|
|
|
{
|
|
|
|
|
for (UActorComponent* Component : Actor->GetComponents())
|
|
|
|
|
{
|
|
|
|
|
if (USkeletalMeshComponent* SkeletalMeshComp = Cast<USkeletalMeshComponent>(Component))
|
|
|
|
|
{
|
|
|
|
|
return SkeletalMeshComp;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (USkeletalMeshComponent* SkeletalMeshComponent = Cast<USkeletalMeshComponent>(RuntimeObject.Get()))
|
|
|
|
|
{
|
2022-08-15 09:26:50 -04:00
|
|
|
if (SkeletalMeshComponent->GetSkeletalMeshAsset())
|
2020-01-22 17:58:55 -05:00
|
|
|
{
|
|
|
|
|
return SkeletalMeshComponent;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 19:54:28 -04:00
|
|
|
bool USequencerToolsFunctionLibrary::ExportAnimSequence(UWorld* World, ULevelSequence* Sequence, UAnimSequence* AnimSequence, UAnimSeqExportOption* ExportOptions,const FMovieSceneBindingProxy& Binding, bool bCreateLink)
|
2020-01-22 17:58:55 -05:00
|
|
|
{
|
|
|
|
|
UMovieScene* MovieScene = Sequence->GetMovieScene();
|
|
|
|
|
if (Binding.Sequence != Sequence || !AnimSequence)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2022-07-20 12:55:33 -04:00
|
|
|
|
2020-01-22 17:58:55 -05:00
|
|
|
ALevelSequenceActor* OutActor;
|
|
|
|
|
FMovieSceneSequencePlaybackSettings Settings;
|
|
|
|
|
FLevelSequenceCameraSettings CameraSettings;
|
|
|
|
|
FMovieSceneSequenceIDRef Template = MovieSceneSequenceID::Root;
|
|
|
|
|
FMovieSceneSequenceTransform RootToLocalTransform;
|
|
|
|
|
ULevelSequencePlayer* Player = ULevelSequencePlayer::CreateLevelSequencePlayer(World, Sequence, Settings, OutActor);
|
|
|
|
|
|
|
|
|
|
bool bResult = false;
|
2021-06-22 00:27:54 -04:00
|
|
|
|
2020-01-22 17:58:55 -05:00
|
|
|
{
|
Sequencer- Refactor Bindings to support inherited Custom Bindings.
With this checkin, the binding system inside Level Sequences has been refactored to allow C++ custom binding classes inheriting from a new class, UMovieSceneCustomBinding. Custom bindings are held as instanced UObjects inside FMovieSceneBindingReference, alongside the Universal Object Locator. In the case where a custom binding has been specified, the UOL will be ignored.
The intention with this change is to allow developers to create their own arbitrary methods of binding any UObject to a track inside Sequencer. Examples of this include:
* Runtime dynamic bindings to spawned NPC or player characters (e.g. in UEFN) with a different spawned preview character
* Blueprint-specified dynamic bindings
* Spawnable bindings based on something other than a AActor template
* Scene Graph/Prefabs
Alongside the base UMovieSceneCustomBinding class, a new hierarchy of spawnable custom binding classes has been created as the basis for allowing spawnable bindings that don't require object templates, and may instead require other asset references, for example UEFN NPC character spawnables requiring an NPC Character Definition asset.
This hierarchy is currently. UMovieSceneCustomBinding <- UMovieSceneSpawnableBindingBase <- UMovieSceneSpawnableActorBindingBase <- UMovieSceneSpawnableActorBinding.
This hierarchy allows for spawnables of non-actor types, spawnables of actor types not based on templates, and finally UMovieSceneSpawnableActorBinding, which is a re-implementation of FMovieSceneSpawnable inside the custom binding system.
Going forward, we will be moving to deprecate FMovieSceneSpawnable in UE Sequencer in favor of the custom spawnable binding types. Sequences with FMovieSceneSpawnables will continue to function as normal, but new spawnables in new sequences will use UMovieSceneSpawnableActorBinding under the hood. These will technically have FMovieScenePossessable structs created for them for now rather than FMovieSceneSpawnable structs. This should be mostly invisible to the user.
In addition, I've added a separate hierarchy of custom bindings called 'Replaceable' bindings. A 'Replaceable' is a binding type that will expect in runtime to always be overridden by a different object that may not be available in editor. UMovieSceneReplaceableBinding will hold in EDITORONLY_DATA a UMovieSceneSpawnableBindingBase to provide a preview while within Sequencer. Included in this is a UMovieSceneReplaceableActorBinding, which uses the UMovieSceneSpawnableActorBinding internally to spawn its preview, and has no implementation to find the actor during runtime- it relies on the user overriding the actor binding using the existing BindingOverrides method on LevelSequenceActor.
Next Steps:
* UEFN custom bindings will be built upon this system, to allow UEFN users multiple ways to override bindings.
* UEFN custom spawnable bindings will be built upon this system, to allow UEFN users a way to spawn actors and objects within Sequencer with their lifetime managed by Sequencer
* With this change, we have a cleaner way of incorporating the 'dynamic binding' work that uses the Director Blueprint to create a spawnable or possessable. This should be incorporated into the UCustomBinding hierarchy.
* Once Verse supports it, we should add Verse custom binding logic
* We need to add a Universal Object Locator type that can reference any other binding in the sequence hierarchy. This will allow us to adapt the existing 'Spawnable Parent' possessable binding logic to reference any other type of binding, not just spawnable ones.
* Eventually we will fully deprecate FMovieSceneSpawnable
Further notes:
* A previous imagined version of this system used the cache as a way to allow locators to spawn actors and register them with Sequencer. This change removes this code.
* Custom Spawnable Bindings use the Binding Lifetime Track to determine their spawn lifetime rather than the old spawn track. This unifies the concept of the lifetime of an object binding within sequencer for both spawnables and possessables.
* As FMovieScenePossessable and FMovieSceneBindingReferences allows multiple bindings per guid, this change now allows multiple spawnables to be spawned and controlled by a single binding track. However, some of the editor tooling code doesn't currently handle this properly and at times assumes a single binding. This should be resolved one way or the other.
* The UX for this allows UCustomBindings to handle creating bindings from any dragged in object using a priority system- Sequencer will ask all classes in priority order if they support binding creation from that object.
* The UX supports unique overlay icons and tooltips for different types of bindings
* The UX allows each binding type to decide whether it can convert a current binding into its own type and then do so
* Currently there is some manual instanced detail customization for UMovieSceneCustomSpawnableActorBinding. We use instanced customization so that the UX has the sequencer context. This should both be improved (to allow ways to choose a class/template via this UI), as well as extended so that clients outside of the Movie Scene plugin can register instanced detail customizations for their own binding types.
* Where possible, Sequencer APIs around spawnables have been refactored to take guids rather than FMovieSceneSpawnable references. In particular, custom spawnables do still use the spawn register, but do not use actor spawners- instead the UMovieSceneCustomBinding class is given the responsiblity of spawning/destroying the object/actor. This created an awkward API for USD export, which had overridden the spawn register to unhide/hide actors rather than spawning/destroying them. This could use some more work. MetaHumanPerformanceExportUtils will also need to be refactored at some point to use the custom spawnable binding system.
* FSequencerUtilities::CreateBinding and CreateGenericBinding has been refactored to accept binding parameters and now supports creating custom bindings including spawnables, as well as replacing existing bindings and adding additional bindings to an existing guid.
[REVIEW] [at]ue-sequencer
#jira UE-199299, FORT-582815, UE-209594
#rb Andrew.Rodham, daniel.coelho, Max.Chen
[CL 32218187 by david bromberg in ue5-main branch]
2024-03-13 12:48:46 -04:00
|
|
|
FSpawnableRestoreState SpawnableRestoreState(MovieScene, Player->GetSharedPlaybackState().ToSharedPtr());
|
2020-08-11 01:36:57 -04:00
|
|
|
|
|
|
|
|
if (SpawnableRestoreState.bWasChanged)
|
|
|
|
|
{
|
|
|
|
|
// Evaluate at the beginning of the subscene time to ensure that spawnables are created before export
|
2022-09-06 15:41:15 -04:00
|
|
|
FFrameTime StartTime = FFrameRate::TransformTime(UE::MovieScene::DiscreteInclusiveLower(MovieScene->GetPlaybackRange()).Value, MovieScene->GetTickResolution(), MovieScene->GetDisplayRate());
|
|
|
|
|
Player->SetPlaybackPosition(FMovieSceneSequencePlaybackParams(StartTime, EUpdatePositionMethod::Play));
|
2023-03-29 11:59:37 -04:00
|
|
|
|
|
|
|
|
// Cannot transact changes due to in-flight spawnable transaction
|
|
|
|
|
ExportOptions->bTransactRecording = false;
|
2020-08-11 01:36:57 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
USkeletalMeshComponent* SkeletalMeshComp = GetSkelMeshComponent(Player, Binding);
|
2022-08-15 09:26:50 -04:00
|
|
|
if (SkeletalMeshComp && SkeletalMeshComp->GetSkeletalMeshAsset() && SkeletalMeshComp->GetSkeletalMeshAsset()->GetSkeleton())
|
2020-08-11 01:36:57 -04:00
|
|
|
{
|
2022-08-15 09:26:50 -04:00
|
|
|
AnimSequence->SetSkeleton(SkeletalMeshComp->GetSkeletalMeshAsset()->GetSkeleton());
|
2024-03-26 19:50:55 -04:00
|
|
|
FAnimExportSequenceParameters AESQ;
|
|
|
|
|
AESQ.Player = Player;
|
|
|
|
|
AESQ.RootToLocalTransform = RootToLocalTransform;
|
|
|
|
|
AESQ.MovieSceneSequence = Sequence;
|
|
|
|
|
AESQ.RootMovieSceneSequence = Sequence;
|
|
|
|
|
bResult = MovieSceneToolHelpers::ExportToAnimSequence(AnimSequence, ExportOptions, AESQ, SkeletalMeshComp);
|
2020-08-11 01:36:57 -04:00
|
|
|
}
|
2020-01-22 17:58:55 -05:00
|
|
|
}
|
2020-08-11 01:36:57 -04:00
|
|
|
|
|
|
|
|
Player->Stop();
|
|
|
|
|
World->DestroyActor(OutActor);
|
|
|
|
|
|
2022-04-21 13:12:26 -04:00
|
|
|
//create the link to the anim sequence
|
|
|
|
|
if (bResult && bCreateLink)
|
|
|
|
|
{
|
2022-06-01 13:39:04 -04:00
|
|
|
return LinkAnimSequence(Sequence, AnimSequence, ExportOptions, Binding);
|
|
|
|
|
}
|
|
|
|
|
return bResult;
|
|
|
|
|
}
|
2022-07-07 23:37:28 -04:00
|
|
|
void USequencerToolsFunctionLibrary::ClearLinkedAnimSequences(ULevelSequence* LevelSequence)
|
|
|
|
|
{
|
|
|
|
|
if (LevelSequence)
|
|
|
|
|
{
|
|
|
|
|
if (IInterface_AssetUserData* LevelSequenceUserDataInterface = Cast< IInterface_AssetUserData >(LevelSequence))
|
|
|
|
|
{
|
|
|
|
|
ULevelSequenceAnimSequenceLink* LevelAnimLink = LevelSequenceUserDataInterface->GetAssetUserData< ULevelSequenceAnimSequenceLink >();
|
|
|
|
|
if (LevelAnimLink)
|
|
|
|
|
{
|
|
|
|
|
for (int32 Index = 0; Index < LevelAnimLink->AnimSequenceLinks.Num(); ++Index)
|
|
|
|
|
{
|
|
|
|
|
FLevelSequenceAnimSequenceLinkItem& LevelAnimLinkItem = LevelAnimLink->AnimSequenceLinks[Index];
|
|
|
|
|
if (UAnimSequence* AnimSequence = LevelAnimLinkItem.ResolveAnimSequence())
|
|
|
|
|
{
|
|
|
|
|
if (IInterface_AssetUserData* AnimAssetUserData = Cast< IInterface_AssetUserData >(AnimSequence))
|
|
|
|
|
{
|
2022-09-13 22:44:38 -04:00
|
|
|
UAnimSequenceLevelSequenceLink* AnimLevelLink = AnimAssetUserData->GetAssetUserData< UAnimSequenceLevelSequenceLink >();
|
|
|
|
|
if (AnimLevelLink)
|
|
|
|
|
{
|
|
|
|
|
ULevelSequence* AnimLevelSequence = AnimLevelLink->ResolveLevelSequence();
|
|
|
|
|
if (AnimLevelSequence && AnimLevelSequence == LevelSequence)
|
|
|
|
|
{
|
|
|
|
|
AnimAssetUserData->RemoveUserDataOfClass(UAnimSequenceLevelSequenceLink::StaticClass());
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-07-07 23:37:28 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
LevelSequenceUserDataInterface->RemoveUserDataOfClass(ULevelSequenceAnimSequenceLink::StaticClass());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-06-01 13:39:04 -04:00
|
|
|
|
2022-06-24 19:54:28 -04:00
|
|
|
bool USequencerToolsFunctionLibrary::LinkAnimSequence(ULevelSequence* Sequence, UAnimSequence* AnimSequence, const UAnimSeqExportOption* ExportOptions,const FMovieSceneBindingProxy& Binding)
|
2022-06-01 13:39:04 -04:00
|
|
|
{
|
|
|
|
|
if (!Sequence || !AnimSequence || !ExportOptions || Binding.Sequence != Sequence)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Sequence && Sequence->GetClass()->ImplementsInterface(UInterface_AssetUserData::StaticClass())
|
|
|
|
|
&& AnimSequence->GetClass()->ImplementsInterface(UInterface_AssetUserData::StaticClass()))
|
|
|
|
|
{
|
|
|
|
|
Sequence->Modify();
|
|
|
|
|
AnimSequence->Modify();
|
2022-04-21 13:12:26 -04:00
|
|
|
if (IInterface_AssetUserData* AnimAssetUserData = Cast< IInterface_AssetUserData >(AnimSequence))
|
|
|
|
|
{
|
|
|
|
|
UAnimSequenceLevelSequenceLink* AnimLevelLink = AnimAssetUserData->GetAssetUserData< UAnimSequenceLevelSequenceLink >();
|
|
|
|
|
if (!AnimLevelLink)
|
|
|
|
|
{
|
|
|
|
|
AnimLevelLink = NewObject<UAnimSequenceLevelSequenceLink>(AnimSequence, NAME_None, RF_Public | RF_Transactional);
|
|
|
|
|
AnimAssetUserData->AddAssetUserData(AnimLevelLink);
|
|
|
|
|
}
|
2022-06-01 13:39:04 -04:00
|
|
|
|
2022-04-21 13:12:26 -04:00
|
|
|
AnimLevelLink->SetLevelSequence(Sequence);
|
|
|
|
|
AnimLevelLink->SkelTrackGuid = Binding.BindingID;
|
|
|
|
|
}
|
|
|
|
|
if (IInterface_AssetUserData* AssetUserDataInterface = Cast< IInterface_AssetUserData >(Sequence))
|
|
|
|
|
{
|
|
|
|
|
bool bAddItem = true;
|
|
|
|
|
ULevelSequenceAnimSequenceLink* LevelAnimLink = AssetUserDataInterface->GetAssetUserData< ULevelSequenceAnimSequenceLink >();
|
|
|
|
|
if (LevelAnimLink)
|
|
|
|
|
{
|
|
|
|
|
for (FLevelSequenceAnimSequenceLinkItem& LevelAnimLinkItem : LevelAnimLink->AnimSequenceLinks)
|
|
|
|
|
{
|
2024-06-14 18:35:10 -04:00
|
|
|
if (LevelAnimLinkItem.IsEqual(Binding.BindingID, ExportOptions->bUseCustomTimeRange,
|
|
|
|
|
ExportOptions->CustomStartFrame, ExportOptions->CustomEndFrame, ExportOptions->CustomDisplayRate))
|
2022-04-21 13:12:26 -04:00
|
|
|
{
|
|
|
|
|
bAddItem = false;
|
|
|
|
|
UAnimSequence* OtherAnimSequence = LevelAnimLinkItem.ResolveAnimSequence();
|
2022-06-01 13:39:04 -04:00
|
|
|
|
2022-04-21 13:12:26 -04:00
|
|
|
if (OtherAnimSequence != AnimSequence)
|
|
|
|
|
{
|
|
|
|
|
if (IInterface_AssetUserData* OtherAnimAssetUserData = Cast< IInterface_AssetUserData >(OtherAnimSequence))
|
|
|
|
|
{
|
|
|
|
|
UAnimSequenceLevelSequenceLink* OtherAnimLevelLink = OtherAnimAssetUserData->GetAssetUserData< UAnimSequenceLevelSequenceLink >();
|
|
|
|
|
if (OtherAnimLevelLink)
|
|
|
|
|
{
|
2022-06-01 13:39:04 -04:00
|
|
|
OtherAnimSequence->Modify();
|
2022-04-21 13:12:26 -04:00
|
|
|
OtherAnimAssetUserData->RemoveUserDataOfClass(UAnimSequenceLevelSequenceLink::StaticClass());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
LevelAnimLinkItem.PathToAnimSequence = FSoftObjectPath(AnimSequence);
|
|
|
|
|
LevelAnimLinkItem.bExportMorphTargets = ExportOptions->bExportMorphTargets;
|
|
|
|
|
LevelAnimLinkItem.bExportAttributeCurves = ExportOptions->bExportAttributeCurves;
|
|
|
|
|
LevelAnimLinkItem.bExportMaterialCurves = ExportOptions->bExportMaterialCurves;
|
|
|
|
|
LevelAnimLinkItem.bExportTransforms = ExportOptions->bExportTransforms;
|
2022-10-15 02:15:53 -04:00
|
|
|
LevelAnimLinkItem.Interpolation = ExportOptions->Interpolation;
|
|
|
|
|
LevelAnimLinkItem.CurveInterpolation = ExportOptions->CurveInterpolation;
|
2022-04-21 13:12:26 -04:00
|
|
|
LevelAnimLinkItem.bRecordInWorldSpace = ExportOptions->bRecordInWorldSpace;
|
2022-06-23 12:57:35 -04:00
|
|
|
LevelAnimLinkItem.bEvaluateAllSkeletalMeshComponents = ExportOptions->bEvaluateAllSkeletalMeshComponents;
|
2024-06-14 18:35:10 -04:00
|
|
|
|
|
|
|
|
LevelAnimLinkItem.IncludeAnimationNames = ExportOptions->IncludeAnimationNames;
|
|
|
|
|
LevelAnimLinkItem.ExcludeAnimationNames = ExportOptions->ExcludeAnimationNames;
|
|
|
|
|
LevelAnimLinkItem.WarmUpFrames = ExportOptions->WarmUpFrames;
|
|
|
|
|
LevelAnimLinkItem.DelayBeforeStart = ExportOptions->DelayBeforeStart;
|
|
|
|
|
LevelAnimLinkItem.bUseCustomTimeRange = ExportOptions->bUseCustomTimeRange;
|
|
|
|
|
LevelAnimLinkItem.CustomStartFrame = ExportOptions->CustomStartFrame;
|
|
|
|
|
LevelAnimLinkItem.CustomEndFrame = ExportOptions->CustomEndFrame;
|
|
|
|
|
LevelAnimLinkItem.CustomDisplayRate = ExportOptions->CustomDisplayRate;
|
2022-04-21 13:12:26 -04:00
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
LevelAnimLink = NewObject<ULevelSequenceAnimSequenceLink>(Sequence, NAME_None, RF_Public | RF_Transactional);
|
|
|
|
|
}
|
|
|
|
|
if (bAddItem == true)
|
|
|
|
|
{
|
|
|
|
|
FLevelSequenceAnimSequenceLinkItem LevelAnimLinkItem;
|
|
|
|
|
LevelAnimLinkItem.SkelTrackGuid = Binding.BindingID;
|
|
|
|
|
LevelAnimLinkItem.PathToAnimSequence = FSoftObjectPath(AnimSequence);
|
|
|
|
|
LevelAnimLinkItem.bExportMorphTargets = ExportOptions->bExportMorphTargets;
|
|
|
|
|
LevelAnimLinkItem.bExportAttributeCurves = ExportOptions->bExportAttributeCurves;
|
2022-10-15 02:15:53 -04:00
|
|
|
LevelAnimLinkItem.Interpolation = ExportOptions->Interpolation;
|
|
|
|
|
LevelAnimLinkItem.CurveInterpolation = ExportOptions->CurveInterpolation;
|
2022-04-21 13:12:26 -04:00
|
|
|
LevelAnimLinkItem.bExportMaterialCurves = ExportOptions->bExportMaterialCurves;
|
|
|
|
|
LevelAnimLinkItem.bExportTransforms = ExportOptions->bExportTransforms;
|
|
|
|
|
LevelAnimLinkItem.bRecordInWorldSpace = ExportOptions->bRecordInWorldSpace;
|
2022-06-23 12:57:35 -04:00
|
|
|
LevelAnimLinkItem.bEvaluateAllSkeletalMeshComponents = ExportOptions->bEvaluateAllSkeletalMeshComponents;
|
2022-04-21 13:12:26 -04:00
|
|
|
|
2024-06-14 18:35:10 -04:00
|
|
|
LevelAnimLinkItem.IncludeAnimationNames = ExportOptions->IncludeAnimationNames;
|
|
|
|
|
LevelAnimLinkItem.ExcludeAnimationNames = ExportOptions->ExcludeAnimationNames;
|
|
|
|
|
LevelAnimLinkItem.WarmUpFrames = ExportOptions->WarmUpFrames;
|
|
|
|
|
LevelAnimLinkItem.DelayBeforeStart = ExportOptions->DelayBeforeStart;
|
|
|
|
|
LevelAnimLinkItem.bUseCustomTimeRange = ExportOptions->bUseCustomTimeRange;
|
|
|
|
|
LevelAnimLinkItem.CustomStartFrame = ExportOptions->CustomStartFrame;
|
|
|
|
|
LevelAnimLinkItem.CustomEndFrame = ExportOptions->CustomEndFrame;
|
|
|
|
|
LevelAnimLinkItem.CustomDisplayRate = ExportOptions->CustomDisplayRate;
|
|
|
|
|
|
2022-04-21 13:12:26 -04:00
|
|
|
LevelAnimLink->AnimSequenceLinks.Add(LevelAnimLinkItem);
|
|
|
|
|
AssetUserDataInterface->AddAssetUserData(LevelAnimLink);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-06-01 13:39:04 -04:00
|
|
|
return true;
|
2020-01-22 17:58:55 -05:00
|
|
|
}
|
|
|
|
|
|
2022-04-21 13:12:26 -04:00
|
|
|
UAnimSequenceLevelSequenceLink* USequencerToolsFunctionLibrary::GetLevelSequenceLinkFromAnimSequence(UAnimSequence* InAnimSequence)
|
|
|
|
|
{
|
|
|
|
|
if (IInterface_AssetUserData* AnimAssetUserData = Cast< IInterface_AssetUserData >(InAnimSequence))
|
|
|
|
|
{
|
|
|
|
|
UAnimSequenceLevelSequenceLink* AnimLevelLink = AnimAssetUserData->GetAssetUserData< UAnimSequenceLevelSequenceLink >();
|
|
|
|
|
return AnimLevelLink;
|
|
|
|
|
}
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
2019-05-30 15:52:55 -04:00
|
|
|
|
2022-04-21 13:12:26 -04:00
|
|
|
ULevelSequenceAnimSequenceLink* USequencerToolsFunctionLibrary::GetAnimSequenceLinkFromLevelSequence(ULevelSequence* InLevelSequence)
|
|
|
|
|
{
|
|
|
|
|
if (IInterface_AssetUserData* AssetUserDataInterface = Cast< IInterface_AssetUserData >(InLevelSequence))
|
|
|
|
|
{
|
|
|
|
|
ULevelSequenceAnimSequenceLink* LevelAnimLink = AssetUserDataInterface->GetAssetUserData< ULevelSequenceAnimSequenceLink >();
|
|
|
|
|
return LevelAnimLink;
|
|
|
|
|
}
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
2019-05-30 15:52:55 -04:00
|
|
|
|
2019-05-31 15:16:06 -04:00
|
|
|
TArray<FGuid> AddActors(UWorld* World, UMovieSceneSequence* InSequence, UMovieScene* InMovieScene, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID,const TArray<TWeakObjectPtr<AActor> >& InActors)
|
2019-05-30 15:52:55 -04:00
|
|
|
{
|
|
|
|
|
TArray<FGuid> PossessableGuids;
|
|
|
|
|
|
|
|
|
|
if (InMovieScene->IsReadOnly())
|
|
|
|
|
{
|
|
|
|
|
return PossessableGuids;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (TWeakObjectPtr<AActor> WeakActor : InActors)
|
|
|
|
|
{
|
|
|
|
|
if (AActor* Actor = WeakActor.Get())
|
|
|
|
|
{
|
|
|
|
|
FGuid ExistingGuid = Player->FindObjectId(*Actor, TemplateID);
|
|
|
|
|
if (!ExistingGuid.IsValid())
|
|
|
|
|
{
|
|
|
|
|
InMovieScene->Modify();
|
|
|
|
|
const FGuid PossessableGuid = InMovieScene->AddPossessable(Actor->GetActorLabel(), Actor->GetClass());
|
|
|
|
|
PossessableGuids.Add(PossessableGuid);
|
|
|
|
|
InSequence->BindPossessableObject(PossessableGuid, *Actor, World);
|
2023-11-27 09:05:23 -05:00
|
|
|
InMovieScene->FindPossessable(PossessableGuid)->FixupPossessedObjectClass(InSequence, World);
|
2019-05-30 15:52:55 -04:00
|
|
|
|
|
|
|
|
//TODO New to figure way to call void FLevelSequenceEditorToolkit::AddDefaultTracksForActor(AActor& Actor, const FGuid Binding)
|
|
|
|
|
|
|
|
|
|
if (Actor->IsA<ACameraActor>())
|
|
|
|
|
{
|
2021-06-07 20:09:45 -04:00
|
|
|
MovieSceneToolHelpers::CreateCameraCutSectionForCamera(InMovieScene, PossessableGuid, 0);
|
2019-05-30 15:52:55 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return PossessableGuids;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2019-05-31 15:16:06 -04:00
|
|
|
void ImportFBXCamera(UnFbx::FFbxImporter* FbxImporter, UWorld* World, UMovieSceneSequence* Sequence, UMovieScene* InMovieScene, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID, TMap<FGuid, FString>& InObjectBindingMap, bool bMatchByNameOnly, bool bCreateCameras)
|
2019-05-30 15:52:55 -04:00
|
|
|
{
|
|
|
|
|
if (bCreateCameras)
|
|
|
|
|
{
|
|
|
|
|
TArray<FbxCamera*> AllCameras;
|
2019-06-07 12:19:21 -04:00
|
|
|
MovieSceneToolHelpers::GetCameras(FbxImporter->Scene->GetRootNode(), AllCameras);
|
2019-05-30 15:52:55 -04:00
|
|
|
|
|
|
|
|
// Find unmatched cameras
|
|
|
|
|
TArray<FbxCamera*> UnmatchedCameras;
|
|
|
|
|
for (auto Camera : AllCameras)
|
|
|
|
|
{
|
2019-06-07 12:19:21 -04:00
|
|
|
FString NodeName = MovieSceneToolHelpers::GetCameraName(Camera);
|
2019-05-30 15:52:55 -04:00
|
|
|
|
|
|
|
|
bool bMatched = false;
|
|
|
|
|
for (auto InObjectBinding : InObjectBindingMap)
|
|
|
|
|
{
|
|
|
|
|
FString ObjectName = InObjectBinding.Value;
|
|
|
|
|
if (ObjectName == NodeName)
|
|
|
|
|
{
|
|
|
|
|
// Look for a valid bound object, otherwise need to create a new camera and assign this binding to it
|
|
|
|
|
bool bFoundBoundObject = false;
|
|
|
|
|
TArrayView<TWeakObjectPtr<>> BoundObjects = Player->FindBoundObjects(InObjectBinding.Key, TemplateID);
|
|
|
|
|
for (auto BoundObject : BoundObjects)
|
|
|
|
|
{
|
|
|
|
|
if (BoundObject.IsValid())
|
|
|
|
|
{
|
|
|
|
|
bFoundBoundObject = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!bMatched)
|
|
|
|
|
{
|
|
|
|
|
UnmatchedCameras.Add(Camera);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If there are new cameras, clear the object binding map so that we're only assigning values to the newly created cameras
|
|
|
|
|
if (UnmatchedCameras.Num() != 0)
|
|
|
|
|
{
|
|
|
|
|
InObjectBindingMap.Reset();
|
|
|
|
|
bMatchByNameOnly = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add any unmatched cameras
|
|
|
|
|
for (auto UnmatchedCamera : UnmatchedCameras)
|
|
|
|
|
{
|
2019-06-07 12:19:21 -04:00
|
|
|
FString CameraName = MovieSceneToolHelpers::GetCameraName(UnmatchedCamera);
|
2019-05-30 15:52:55 -04:00
|
|
|
|
|
|
|
|
AActor* NewCamera = nullptr;
|
|
|
|
|
if (UnmatchedCamera->GetApertureMode() == FbxCamera::eFocalLength)
|
|
|
|
|
{
|
|
|
|
|
FActorSpawnParameters SpawnParams;
|
|
|
|
|
NewCamera = World->SpawnActor<ACineCameraActor>(SpawnParams);
|
|
|
|
|
NewCamera->SetActorLabel(*CameraName);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
FActorSpawnParameters SpawnParams;
|
|
|
|
|
NewCamera = World->SpawnActor<ACameraActor>(SpawnParams);
|
|
|
|
|
NewCamera->SetActorLabel(*CameraName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Copy camera properties before adding default tracks so that initial camera properties match and can be restored after sequencer finishes
|
|
|
|
|
MovieSceneToolHelpers::CopyCameraProperties(UnmatchedCamera, NewCamera);
|
|
|
|
|
|
|
|
|
|
TArray<TWeakObjectPtr<AActor> > NewCameras;
|
|
|
|
|
NewCameras.Add(NewCamera);
|
|
|
|
|
TArray<FGuid> NewCameraGuids = AddActors(World, Sequence,InMovieScene, Player, TemplateID,NewCameras);
|
|
|
|
|
|
|
|
|
|
if (NewCameraGuids.Num())
|
|
|
|
|
{
|
|
|
|
|
InObjectBindingMap.Add(NewCameraGuids[0]);
|
|
|
|
|
InObjectBindingMap[NewCameraGuids[0]] = CameraName;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
//everything created now import it in.
|
2020-09-01 14:07:48 -04:00
|
|
|
MovieSceneToolHelpers::ImportFBXCameraToExisting(FbxImporter, Sequence, Player, TemplateID, InObjectBindingMap, bMatchByNameOnly, true);
|
2019-05-30 15:52:55 -04:00
|
|
|
}
|
|
|
|
|
|
2022-06-24 19:54:28 -04:00
|
|
|
bool ImportFBXInternal(UWorld* World, UMovieSceneSequence* Sequence, const TArray<FMovieSceneBindingProxy>& InBindings, UMovieSceneUserImportFBXSettings* ImportFBXSettings, const FString& ImportFilename, UMovieSceneSequencePlayer* Player)
|
2019-05-30 15:52:55 -04:00
|
|
|
{
|
|
|
|
|
UMovieScene* MovieScene = Sequence->GetMovieScene();
|
|
|
|
|
if (!MovieScene || MovieScene->IsReadOnly())
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 19:54:28 -04:00
|
|
|
TArray<FMovieSceneBindingProxy> AllBindings;
|
|
|
|
|
for (const FMovieSceneBindingProxy& Binding : InBindings)
|
2022-02-10 15:23:50 -05:00
|
|
|
{
|
|
|
|
|
AllBindings.Add(Binding);
|
|
|
|
|
|
|
|
|
|
GatherDescendantBindings(Binding, Sequence, AllBindings);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TMap<FGuid, FString> ObjectBindingMap;
|
2022-06-24 19:54:28 -04:00
|
|
|
for (const FMovieSceneBindingProxy& Binding : AllBindings)
|
2019-05-30 15:52:55 -04:00
|
|
|
{
|
|
|
|
|
FString Name = MovieScene->GetObjectDisplayName(Binding.BindingID).ToString();
|
|
|
|
|
ObjectBindingMap.Add(Binding.BindingID, Name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FFBXInOutParameters InOutParams;
|
|
|
|
|
if (!MovieSceneToolHelpers::ReadyFBXForImport(ImportFilename, ImportFBXSettings, InOutParams))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const bool bMatchByNameOnly = ImportFBXSettings->bMatchByNameOnly;
|
|
|
|
|
|
|
|
|
|
UnFbx::FFbxImporter* FbxImporter = UnFbx::FFbxImporter::GetInstance();
|
2020-08-11 01:36:57 -04:00
|
|
|
|
|
|
|
|
bool bResult = false;
|
|
|
|
|
FScopedTransaction ImportFBXTransaction(NSLOCTEXT("Sequencer", "ImportFBX", "Import FBX"));
|
|
|
|
|
{
|
Sequencer- Refactor Bindings to support inherited Custom Bindings.
With this checkin, the binding system inside Level Sequences has been refactored to allow C++ custom binding classes inheriting from a new class, UMovieSceneCustomBinding. Custom bindings are held as instanced UObjects inside FMovieSceneBindingReference, alongside the Universal Object Locator. In the case where a custom binding has been specified, the UOL will be ignored.
The intention with this change is to allow developers to create their own arbitrary methods of binding any UObject to a track inside Sequencer. Examples of this include:
* Runtime dynamic bindings to spawned NPC or player characters (e.g. in UEFN) with a different spawned preview character
* Blueprint-specified dynamic bindings
* Spawnable bindings based on something other than a AActor template
* Scene Graph/Prefabs
Alongside the base UMovieSceneCustomBinding class, a new hierarchy of spawnable custom binding classes has been created as the basis for allowing spawnable bindings that don't require object templates, and may instead require other asset references, for example UEFN NPC character spawnables requiring an NPC Character Definition asset.
This hierarchy is currently. UMovieSceneCustomBinding <- UMovieSceneSpawnableBindingBase <- UMovieSceneSpawnableActorBindingBase <- UMovieSceneSpawnableActorBinding.
This hierarchy allows for spawnables of non-actor types, spawnables of actor types not based on templates, and finally UMovieSceneSpawnableActorBinding, which is a re-implementation of FMovieSceneSpawnable inside the custom binding system.
Going forward, we will be moving to deprecate FMovieSceneSpawnable in UE Sequencer in favor of the custom spawnable binding types. Sequences with FMovieSceneSpawnables will continue to function as normal, but new spawnables in new sequences will use UMovieSceneSpawnableActorBinding under the hood. These will technically have FMovieScenePossessable structs created for them for now rather than FMovieSceneSpawnable structs. This should be mostly invisible to the user.
In addition, I've added a separate hierarchy of custom bindings called 'Replaceable' bindings. A 'Replaceable' is a binding type that will expect in runtime to always be overridden by a different object that may not be available in editor. UMovieSceneReplaceableBinding will hold in EDITORONLY_DATA a UMovieSceneSpawnableBindingBase to provide a preview while within Sequencer. Included in this is a UMovieSceneReplaceableActorBinding, which uses the UMovieSceneSpawnableActorBinding internally to spawn its preview, and has no implementation to find the actor during runtime- it relies on the user overriding the actor binding using the existing BindingOverrides method on LevelSequenceActor.
Next Steps:
* UEFN custom bindings will be built upon this system, to allow UEFN users multiple ways to override bindings.
* UEFN custom spawnable bindings will be built upon this system, to allow UEFN users a way to spawn actors and objects within Sequencer with their lifetime managed by Sequencer
* With this change, we have a cleaner way of incorporating the 'dynamic binding' work that uses the Director Blueprint to create a spawnable or possessable. This should be incorporated into the UCustomBinding hierarchy.
* Once Verse supports it, we should add Verse custom binding logic
* We need to add a Universal Object Locator type that can reference any other binding in the sequence hierarchy. This will allow us to adapt the existing 'Spawnable Parent' possessable binding logic to reference any other type of binding, not just spawnable ones.
* Eventually we will fully deprecate FMovieSceneSpawnable
Further notes:
* A previous imagined version of this system used the cache as a way to allow locators to spawn actors and register them with Sequencer. This change removes this code.
* Custom Spawnable Bindings use the Binding Lifetime Track to determine their spawn lifetime rather than the old spawn track. This unifies the concept of the lifetime of an object binding within sequencer for both spawnables and possessables.
* As FMovieScenePossessable and FMovieSceneBindingReferences allows multiple bindings per guid, this change now allows multiple spawnables to be spawned and controlled by a single binding track. However, some of the editor tooling code doesn't currently handle this properly and at times assumes a single binding. This should be resolved one way or the other.
* The UX for this allows UCustomBindings to handle creating bindings from any dragged in object using a priority system- Sequencer will ask all classes in priority order if they support binding creation from that object.
* The UX supports unique overlay icons and tooltips for different types of bindings
* The UX allows each binding type to decide whether it can convert a current binding into its own type and then do so
* Currently there is some manual instanced detail customization for UMovieSceneCustomSpawnableActorBinding. We use instanced customization so that the UX has the sequencer context. This should both be improved (to allow ways to choose a class/template via this UI), as well as extended so that clients outside of the Movie Scene plugin can register instanced detail customizations for their own binding types.
* Where possible, Sequencer APIs around spawnables have been refactored to take guids rather than FMovieSceneSpawnable references. In particular, custom spawnables do still use the spawn register, but do not use actor spawners- instead the UMovieSceneCustomBinding class is given the responsiblity of spawning/destroying the object/actor. This created an awkward API for USD export, which had overridden the spawn register to unhide/hide actors rather than spawning/destroying them. This could use some more work. MetaHumanPerformanceExportUtils will also need to be refactored at some point to use the custom spawnable binding system.
* FSequencerUtilities::CreateBinding and CreateGenericBinding has been refactored to accept binding parameters and now supports creating custom bindings including spawnables, as well as replacing existing bindings and adding additional bindings to an existing guid.
[REVIEW] [at]ue-sequencer
#jira UE-199299, FORT-582815, UE-209594
#rb Andrew.Rodham, daniel.coelho, Max.Chen
[CL 32218187 by david bromberg in ue5-main branch]
2024-03-13 12:48:46 -04:00
|
|
|
FSpawnableRestoreState SpawnableRestoreState(MovieScene, Player->GetSharedPlaybackState().ToSharedPtr());
|
2020-08-11 01:36:57 -04:00
|
|
|
|
|
|
|
|
if (SpawnableRestoreState.bWasChanged)
|
|
|
|
|
{
|
|
|
|
|
// Evaluate at the beginning of the subscene time to ensure that spawnables are created before export
|
2022-09-06 15:41:15 -04:00
|
|
|
FFrameTime StartTime = FFrameRate::TransformTime(UE::MovieScene::DiscreteInclusiveLower(MovieScene->GetPlaybackRange()).Value, MovieScene->GetTickResolution(), MovieScene->GetDisplayRate());
|
|
|
|
|
Player->SetPlaybackPosition(FMovieSceneSequencePlaybackParams(StartTime, EUpdatePositionMethod::Play));
|
2020-08-11 01:36:57 -04:00
|
|
|
}
|
2019-05-30 15:52:55 -04:00
|
|
|
|
2020-08-11 01:36:57 -04:00
|
|
|
ImportFBXCamera(FbxImporter, World, Sequence, MovieScene, Player, MovieSceneSequenceID::Root, ObjectBindingMap, bMatchByNameOnly, ImportFBXSettings->bCreateCameras);
|
2019-05-30 15:52:55 -04:00
|
|
|
|
2020-09-01 14:07:48 -04:00
|
|
|
bResult = MovieSceneToolHelpers::ImportFBXIfReady(World, Sequence, Player, MovieSceneSequenceID::Root, ObjectBindingMap, ImportFBXSettings, InOutParams);
|
2020-08-11 01:36:57 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Player->Stop();
|
|
|
|
|
return bResult;
|
2019-05-30 15:52:55 -04:00
|
|
|
}
|
|
|
|
|
|
2022-06-24 19:54:28 -04:00
|
|
|
bool USequencerToolsFunctionLibrary::ImportLevelSequenceFBX(UWorld* World, ULevelSequence* Sequence, const TArray<FMovieSceneBindingProxy>& InBindings, UMovieSceneUserImportFBXSettings* ImportFBXSettings, const FString& ImportFilename)
|
2021-03-05 19:27:14 -04:00
|
|
|
{
|
|
|
|
|
ALevelSequenceActor* OutActor;
|
|
|
|
|
FMovieSceneSequencePlaybackSettings Settings;
|
|
|
|
|
FLevelSequenceCameraSettings CameraSettings;
|
|
|
|
|
ULevelSequencePlayer* Player = ULevelSequencePlayer::CreateLevelSequencePlayer(World, Sequence, Settings, OutActor);
|
|
|
|
|
|
|
|
|
|
bool bSuccess = ImportFBXInternal(World, Sequence, InBindings, ImportFBXSettings, ImportFilename, Player);
|
|
|
|
|
World->DestroyActor(OutActor);
|
|
|
|
|
|
|
|
|
|
return bSuccess;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-24 00:43:27 -04:00
|
|
|
bool USequencerToolsFunctionLibrary::ImportFBXToControlRig(UWorld* World, ULevelSequence* Sequence, const FString& ControlRigTrackName, const TArray<FString>& ControlRigNames,
|
|
|
|
|
UMovieSceneUserImportFBXControlRigSettings* ImportFBXControlRigSettings,
|
|
|
|
|
const FString& ImportFilename)
|
|
|
|
|
{
|
|
|
|
|
UMovieScene* MovieScene = Sequence->GetMovieScene();
|
|
|
|
|
if (!MovieScene || MovieScene->IsReadOnly())
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2019-05-30 15:52:55 -04:00
|
|
|
|
2020-09-24 00:43:27 -04:00
|
|
|
bool bValid = false;
|
|
|
|
|
|
|
|
|
|
const TArray<FMovieSceneBinding>& Bindings = MovieScene->GetBindings();
|
|
|
|
|
for (const FMovieSceneBinding& Binding : Bindings)
|
|
|
|
|
{
|
|
|
|
|
if (Binding.GetName() == ControlRigTrackName)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
ALevelSequenceActor* OutActor;
|
|
|
|
|
FMovieSceneSequencePlaybackSettings Settings;
|
|
|
|
|
FLevelSequenceCameraSettings CameraSettings;
|
|
|
|
|
ULevelSequencePlayer* Player = ULevelSequencePlayer::CreateLevelSequencePlayer(World, Sequence, Settings, OutActor);
|
|
|
|
|
|
|
|
|
|
const TArray<UMovieSceneTrack*>& Tracks = Binding.GetTracks();
|
|
|
|
|
TArray<FName> SelectedControls;
|
|
|
|
|
for (UMovieSceneTrack* Track : Tracks)
|
|
|
|
|
{
|
|
|
|
|
INodeAndChannelMappings* ChannelMapping = Cast<INodeAndChannelMappings>(Track);
|
|
|
|
|
if (ChannelMapping)
|
|
|
|
|
{
|
2023-06-01 10:13:27 -04:00
|
|
|
TArray<FRigControlFBXNodeAndChannels>* NodeAndChannels = ChannelMapping->GetNodeAndChannelMappings(nullptr);
|
2020-09-24 00:43:27 -04:00
|
|
|
//use passed in controls for selected, actually selected controls should almost be empty anyway since we just loaded/set everything up.
|
|
|
|
|
for (const FString& StringName : ControlRigNames)
|
|
|
|
|
{
|
|
|
|
|
FName Name(*StringName);
|
|
|
|
|
SelectedControls.Add(Name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bValid = MovieSceneToolHelpers::ImportFBXIntoControlRigChannels(MovieScene,ImportFilename, ImportFBXControlRigSettings,
|
|
|
|
|
NodeAndChannels, SelectedControls, MovieScene->GetTickResolution());
|
|
|
|
|
|
|
|
|
|
if (NodeAndChannels)
|
|
|
|
|
{
|
|
|
|
|
delete NodeAndChannels;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return bValid;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-01 10:13:27 -04:00
|
|
|
bool USequencerToolsFunctionLibrary::ExportFBXFromControlRig(ULevelSequence* Sequence,
|
|
|
|
|
const FString& ActorWithControlRigTrack,
|
|
|
|
|
const UMovieSceneUserExportFBXControlRigSettings* ExportFBXControlRigSettings)
|
|
|
|
|
{
|
|
|
|
|
bool bValid = false;
|
|
|
|
|
|
|
|
|
|
if (!Sequence || !ExportFBXControlRigSettings)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UMovieScene* MovieScene = Sequence->GetMovieScene();
|
|
|
|
|
if (!MovieScene || MovieScene->IsReadOnly())
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const TArray<FMovieSceneBinding>& Bindings = MovieScene->GetBindings();
|
|
|
|
|
for (const FMovieSceneBinding& Binding : Bindings)
|
|
|
|
|
{
|
|
|
|
|
if (Binding.GetName() != ActorWithControlRigTrack)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (UMovieSceneTrack* Track : Binding.GetTracks())
|
|
|
|
|
{
|
|
|
|
|
INodeAndChannelMappings* ChannelMapping = Cast<INodeAndChannelMappings>(Track);
|
|
|
|
|
const UMovieSceneSection* Section = Track->GetSectionToKey();
|
|
|
|
|
if (!ChannelMapping || !Section)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TArray<FName> SelectedControls;
|
|
|
|
|
ChannelMapping->GetSelectedNodes(SelectedControls);
|
|
|
|
|
|
|
|
|
|
const FMovieSceneSequenceTransform RootToLocalTransform;
|
|
|
|
|
return MovieSceneToolHelpers::ExportFBXFromControlRigChannels(Section, ExportFBXControlRigSettings, SelectedControls, RootToLocalTransform);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-24 00:43:27 -04:00
|
|
|
|
|
|
|
|
FMovieSceneEvent USequencerToolsFunctionLibrary::CreateEvent(UMovieSceneSequence* InSequence, UMovieSceneEventSectionBase* InSection, const FSequencerQuickBindingResult& InEndpoint, const TArray<FString>& InPayload)
|
|
|
|
|
{
|
|
|
|
|
FMovieSceneEvent Event;
|
|
|
|
|
|
|
|
|
|
if (InEndpoint.EventEndpoint == nullptr)
|
|
|
|
|
{
|
|
|
|
|
FFrame::KismetExecutionMessage(TEXT("Invalid endpoint, event will not be initialized"), ELogVerbosity::Warning);
|
|
|
|
|
return Event;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UMovieScene* MovieScene = InSequence->GetMovieScene();
|
|
|
|
|
FGuid ObjectBindingID;
|
|
|
|
|
MovieScene->FindTrackBinding(*InSection->GetTypedOuter<UMovieSceneTrack>(), ObjectBindingID);
|
|
|
|
|
UClass* BoundObjectPinClass = nullptr;
|
|
|
|
|
if (FMovieScenePossessable* Possessable = MovieScene->FindPossessable(ObjectBindingID))
|
|
|
|
|
{
|
|
|
|
|
BoundObjectPinClass = const_cast<UClass*>(Possessable->GetPossessedObjectClass());
|
|
|
|
|
}
|
|
|
|
|
else if (FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(ObjectBindingID))
|
|
|
|
|
{
|
|
|
|
|
BoundObjectPinClass = Spawnable->GetObjectTemplate()->GetClass();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
InSection->Modify();
|
|
|
|
|
FMovieSceneEventUtils::BindEventSectionToBlueprint(InSection, InEndpoint.EventEndpoint->GetBlueprint());
|
|
|
|
|
|
2023-03-20 12:05:06 -04:00
|
|
|
UEdGraphPin* BoundObjectPin = FMovieSceneDirectorBlueprintUtils::FindCallTargetPin(InEndpoint.EventEndpoint, BoundObjectPinClass);
|
2020-09-24 00:43:27 -04:00
|
|
|
FMovieSceneEventUtils::SetEndpoint(&Event, InSection, InEndpoint.EventEndpoint, BoundObjectPin);
|
|
|
|
|
|
|
|
|
|
if (InEndpoint.PayloadNames.Num() != InPayload.Num())
|
|
|
|
|
{
|
|
|
|
|
const FString Message = FString::Printf(TEXT("Wrong number of payload values, expecting %i got %i"), InEndpoint.PayloadNames.Num(), InPayload.Num());
|
|
|
|
|
FFrame::KismetExecutionMessage(*Message, ELogVerbosity::Warning);
|
|
|
|
|
return Event;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int32 Index = 0; Index < InEndpoint.PayloadNames.Num(); Index++)
|
|
|
|
|
{
|
|
|
|
|
const FName PayloadName = FName(InEndpoint.PayloadNames[Index]);
|
|
|
|
|
if (!Event.PayloadVariables.Contains(PayloadName))
|
|
|
|
|
{
|
|
|
|
|
Event.PayloadVariables.Add(PayloadName);
|
|
|
|
|
Event.PayloadVariables[PayloadName].Value = InPayload[Index];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Event;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool USequencerToolsFunctionLibrary::IsEventEndpointValid(const FSequencerQuickBindingResult& InEndpoint)
|
|
|
|
|
{
|
|
|
|
|
return InEndpoint.EventEndpoint != nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
FSequencerQuickBindingResult USequencerToolsFunctionLibrary::CreateQuickBinding(UMovieSceneSequence* InSequence, UObject* InObject, const FString& InFunctionName, bool bCallInEditor)
|
|
|
|
|
{
|
|
|
|
|
FSequencerQuickBindingResult Result;
|
|
|
|
|
|
|
|
|
|
FMovieSceneSequenceEditor* SequenceEditor = FMovieSceneSequenceEditor::Find(InSequence);
|
|
|
|
|
if (!SequenceEditor)
|
|
|
|
|
{
|
|
|
|
|
return Result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UBlueprint* Blueprint = SequenceEditor->GetOrCreateDirectorBlueprint(InSequence);
|
|
|
|
|
if (!Blueprint)
|
|
|
|
|
{
|
|
|
|
|
return Result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UMovieScene* MovieScene = InSequence->GetMovieScene();
|
|
|
|
|
|
2023-03-20 12:05:06 -04:00
|
|
|
FMovieSceneDirectorBlueprintEndpointDefinition EndpointDefinition;
|
|
|
|
|
EndpointDefinition.EndpointType = EMovieSceneDirectorBlueprintEndpointType::Event;
|
|
|
|
|
EndpointDefinition.EndpointName = InFunctionName;
|
|
|
|
|
EndpointDefinition.PossibleCallTargetClass = InObject->GetClass();
|
|
|
|
|
EndpointDefinition.AddExtraOutputPin(InObject->GetName(), UEdGraphSchema_K2::PC_Object, InObject->GetClass());
|
2020-09-24 00:43:27 -04:00
|
|
|
UFunction* Function = InObject->GetClass()->FindFunctionByName(FName(InFunctionName));
|
|
|
|
|
if (Function == nullptr)
|
|
|
|
|
{
|
|
|
|
|
const FString Message = FString::Printf(TEXT("Cannot find function %s in class %s"), *(InFunctionName), *(InObject->GetClass()->GetName()));
|
|
|
|
|
FFrame::KismetExecutionMessage(*Message, ELogVerbosity::Warning);
|
|
|
|
|
return Result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UBlueprintFunctionNodeSpawner* BlueprintFunctionNodeSpawner = UBlueprintFunctionNodeSpawner::Create(Function);
|
|
|
|
|
FBlueprintActionMenuItem Action(BlueprintFunctionNodeSpawner);
|
|
|
|
|
|
2023-03-20 12:05:06 -04:00
|
|
|
UK2Node_CustomEvent* NewEventEndpoint = FMovieSceneDirectorBlueprintUtils::CreateEventEndpoint(Blueprint, EndpointDefinition);
|
2020-09-24 00:43:27 -04:00
|
|
|
NewEventEndpoint->bCallInEditor = bCallInEditor;
|
|
|
|
|
Result.EventEndpoint = NewEventEndpoint;
|
|
|
|
|
|
|
|
|
|
UEdGraphPin* ThenPin = NewEventEndpoint->FindPin(UEdGraphSchema_K2::PN_Then, EGPD_Output);
|
2023-03-20 12:05:06 -04:00
|
|
|
UEdGraphPin* BoundObjectPin = FMovieSceneDirectorBlueprintUtils::FindCallTargetPin(NewEventEndpoint, EndpointDefinition.PossibleCallTargetClass);
|
2020-09-24 00:43:27 -04:00
|
|
|
|
|
|
|
|
FVector2D NodePosition(NewEventEndpoint->NodePosX + 400.f, NewEventEndpoint->NodePosY);
|
|
|
|
|
UEdGraphNode* NewNode = Action.PerformAction(NewEventEndpoint->GetGraph(), BoundObjectPin ? BoundObjectPin : ThenPin, NodePosition);
|
|
|
|
|
|
|
|
|
|
if (NewNode == nullptr)
|
|
|
|
|
{
|
|
|
|
|
const FString Message = FString::Printf(TEXT("Failed creating blueprint event node for function %s"), *InFunctionName);
|
|
|
|
|
FFrame::KismetExecutionMessage(*Message, ELogVerbosity::Warning);
|
|
|
|
|
return Result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Link execution pins
|
|
|
|
|
UEdGraphPin* ExecPin = NewNode->FindPin(UEdGraphSchema_K2::PN_Execute, EGPD_Input);
|
|
|
|
|
if (ensure(ThenPin && ExecPin))
|
|
|
|
|
{
|
|
|
|
|
ThenPin->MakeLinkTo(ExecPin);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Link payload parameters' pins
|
|
|
|
|
UK2Node_EditablePinBase* EditableNode = Cast<UK2Node_EditablePinBase>(NewEventEndpoint);
|
|
|
|
|
if (EditableNode)
|
|
|
|
|
{
|
|
|
|
|
for (UEdGraphPin* PayloadPin : NewNode->Pins)
|
|
|
|
|
{
|
|
|
|
|
if (PayloadPin != BoundObjectPin && PayloadPin->Direction == EGPD_Input && PayloadPin->PinType.PinCategory != UEdGraphSchema_K2::PC_Exec && PayloadPin->LinkedTo.Num() == 0)
|
|
|
|
|
{
|
|
|
|
|
Result.PayloadNames.Add(PayloadPin->PinName.ToString());
|
|
|
|
|
|
|
|
|
|
UEdGraphPin* NewPin = EditableNode->CreateUserDefinedPin(PayloadPin->PinName, PayloadPin->PinType, EGPD_Output);
|
|
|
|
|
if (NewNode != NewEventEndpoint && NewPin)
|
|
|
|
|
{
|
|
|
|
|
NewPin->MakeLinkTo(PayloadPin);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Result;
|
|
|
|
|
}
|
2019-05-30 15:52:55 -04:00
|
|
|
|
|
|
|
|
|
2022-09-28 01:06:15 -04:00
|
|
|
#undef LOCTEXT_NAMESPACE // "SequencerTools"
|