// Copyright Epic Games, Inc. All Rights Reserved. #include "MovieSceneToolHelpers.h" #include "ActorForWorldTransforms.h" #include "Animation/AnimationSettings.h" #include "MovieSceneToolsModule.h" #include "MovieScene.h" #include "Layout/Margin.h" #include "Misc/App.h" #include "Misc/Paths.h" #include "UObject/UObjectIterator.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/Text/STextBlock.h" #include "AssetRegistry/AssetData.h" #include "Containers/ArrayView.h" #include "ISequencer.h" #include "Modules/ModuleManager.h" #include "Framework/Application/SlateApplication.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Widgets/Input/SComboBox.h" #include "ScopedTransaction.h" #include "Styling/AppStyle.h" #include "Compilation/MovieSceneCompiledDataManager.h" #include "EditorDirectories.h" #include "Sections/MovieSceneDoubleSection.h" #include "Tracks/MovieSceneDoubleTrack.h" #include "Sections/MovieSceneFloatSection.h" #include "Tracks/MovieSceneFloatTrack.h" #include "Tracks/MovieSceneCameraCutTrack.h" #include "Sections/MovieScene3DTransformSection.h" #include "Sections/MovieScene3DConstraintSection.h" #include "Tracks/MovieScene3DTransformTrack.h" #include "Sections/MovieSceneCinematicShotSection.h" #include "LevelSequence.h" #include "AssetRegistry/AssetRegistryModule.h" #include "DesktopPlatformModule.h" #include "Framework/Notifications/NotificationManager.h" #include "Widgets/Notifications/SNotificationList.h" #include "MovieSceneTranslatorEDL.h" #include "MessageLogModule.h" #include "IMessageLogListing.h" #include "FbxImporter.h" #include "MovieSceneToolsProjectSettings.h" #include "MovieSceneToolsUserSettings.h" #include "CineCameraActor.h" #include "CineCameraComponent.h" #include "Math/UnitConversion.h" #include "PropertyEditorModule.h" #include "IDetailsView.h" #include "Widgets/Input/SButton.h" #include "Editor.h" #include "Editor/EditorEngine.h" #include "LevelEditorViewport.h" #include "AssetToolsModule.h" #include "Channels/MovieSceneChannelProxy.h" #include "Evaluation/MovieSceneEvaluationTrack.h" #include "Evaluation/MovieSceneEvaluationTemplateInstance.h" #include "IMovieScenePlayer.h" #include "Tracks/MovieSceneCinematicShotTrack.h" #include "Tracks/MovieSceneCameraCutTrack.h" #include "Tracks/MovieScene3DConstraintTrack.h" #include "Sections/MovieSceneCameraCutSection.h" #include "Engine/LevelStreaming.h" #include "FbxExporter.h" #include "Serialization/ObjectWriter.h" #include "Serialization/ObjectReader.h" #include "AnimationRecorder.h" #include "Components/SkeletalMeshComponent.h" #include "ILiveLinkClient.h" #include "LiveLinkPresetTypes.h" #include "LiveLinkSourceSettings.h" #include "Features/IModularFeatures.h" #include "Tracks/MovieSceneSpawnTrack.h" #include "Sections/MovieSceneSpawnSection.h" #include "Tracks/MovieSceneSubTrack.h" #include "Sections/MovieSceneSubSection.h" #include "Exporters/AnimSeqExportOption.h" #include "Widgets/Input/NumericTypeInterface.h" #include "FrameNumberDetailsCustomization.h" #include "PropertyEditorDelegates.h" #include "INodeAndChannelMappings.h" #include "BakingAnimationKeySettings.h" #include "Channels/MovieSceneChannelTraits.h" #include "Channels/MovieSceneIntegerChannel.h" #include "Channels/MovieSceneByteChannel.h" #include "Channels/MovieSceneFloatChannel.h" #include "Channels/MovieSceneDoubleChannel.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "EntitySystem/Interrogation/MovieSceneInterrogationLinker.h" #include "ConstraintsManager.h" #include "IMovieScenePlaybackClient.h" #include "SequencerChannelInterface.h" #include "Constraints/MovieSceneConstraintChannelHelper.inl" #include "Exporters/FbxExportOption.h" #include "Bindings/MovieSceneSpawnableBinding.h" #include "Tracks/MovieSceneBindingLifetimeTrack.h" #include "Sections/MovieSceneBindingLifetimeSection.h" #include "GameFramework/Character.h" #include "Systems/MovieSceneTransformOriginSystem.h" #include "Tracks/IMovieSceneTransformOrigin.h" /* FSkelMeshRecorder ***********/ void FSkelMeshRecorderState::Init(USkeletalMeshComponent* InComponent) { SkelComp = InComponent; if (InComponent) { CachedSkelCompForcedLodModel = InComponent->GetForcedLOD(); InComponent->SetForcedLOD(1); // turn off URO and make sure we always update even if out of view bCachedEnableUpdateRateOptimizations = InComponent->bEnableUpdateRateOptimizations; CachedVisibilityBasedAnimTickOption = InComponent->VisibilityBasedAnimTickOption; InComponent->bEnableUpdateRateOptimizations = false; InComponent->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::AlwaysTickPoseAndRefreshBones; } } void FSkelMeshRecorderState::FinishRecording() { if (SkelComp.IsValid()) { // restore force lod setting SkelComp->SetForcedLOD(CachedSkelCompForcedLodModel); // restore update flags SkelComp->bEnableUpdateRateOptimizations = bCachedEnableUpdateRateOptimizations; SkelComp->VisibilityBasedAnimTickOption = CachedVisibilityBasedAnimTickOption; } } /* MovieSceneToolHelpers *****************************************************************************/ void MovieSceneToolHelpers::TrimSection(const TSet& Sections, FQualifiedFrameTime Time, bool bTrimLeft, bool bDeleteKeys) { for (UMovieSceneSection* Section : Sections) { if (Section) { Section->TrimSection(Time, bTrimLeft, bDeleteKeys); } } } bool MovieSceneToolHelpers::CanTrimSectionLeft(const TSet& Sections, FQualifiedFrameTime Time) { for (UMovieSceneSection* Section : Sections) { if (Section) { TRange Range = Section->GetRange(); TArray > Splits = Range.Split(Time.Time.FrameNumber); if (Splits.Num() > 0) { if (Splits[0] == Range) // can't split { continue; } else { TRange Head = Splits[0]; if (!Head.IsEmpty()) { return true; } } } } } return false; } bool MovieSceneToolHelpers::CanTrimSectionRight(const TSet& Sections, FQualifiedFrameTime Time) { for (UMovieSceneSection* Section : Sections) { if (Section) { TRange Range = Section->GetRange(); TArray > Splits = Range.Split(Time.Time.FrameNumber); if (Splits.Num() > 0) { if (Splits[0] == Range) // can't split { continue; } else { TRange Tail = Splits.Last(); if (!Tail.IsEmpty()) { return true; } } } } } return false; } void MovieSceneToolHelpers::TrimOrExtendSection(UMovieSceneTrack* Track, TOptional SpecifiedRowIndex, FQualifiedFrameTime Time, bool bTrimOrExtendLeft, bool bDeleteKeys) { Track->Modify(); int32 StartRowIndex = SpecifiedRowIndex.IsSet() ? SpecifiedRowIndex.GetValue() : 0; int32 EndRowIndex = SpecifiedRowIndex.IsSet() ? SpecifiedRowIndex.GetValue() : Track->GetMaxRowIndex(); for (int32 RowIndex = StartRowIndex; RowIndex <= EndRowIndex; ++RowIndex) { // First, trim all intersecting sections bool bAnyIntersects = false; for (UMovieSceneSection* Section : Track->GetAllSections()) { if (Section->GetRowIndex() == RowIndex && Section->HasStartFrame() && Section->HasEndFrame() && Section->GetRange().Contains(Time.Time.GetFrame())) { Section->TrimSection(Time, bTrimOrExtendLeft, bDeleteKeys); bAnyIntersects = true; } } // If there aren't any intersects, extend the closest start/end if (!bAnyIntersects) { UMovieSceneSection* ClosestSection = nullptr; TOptional MinDiff; for (UMovieSceneSection* Section : Track->GetAllSections()) { if (Section->GetRowIndex() == RowIndex) { if (bTrimOrExtendLeft) { if (Section->HasStartFrame()) { FFrameNumber StartFrame = Section->GetInclusiveStartFrame(); if (StartFrame > Time.Time.GetFrame()) { FFrameNumber Diff = StartFrame - Time.Time.GetFrame(); if (!MinDiff.IsSet() || Diff < MinDiff.GetValue()) { ClosestSection = Section; MinDiff = Diff; } } } } else { if (Section->HasEndFrame()) { FFrameNumber EndFrame = Section->GetExclusiveEndFrame(); if (EndFrame < Time.Time.GetFrame()) { FFrameNumber Diff = Time.Time.GetFrame() - EndFrame; if (!MinDiff.IsSet() || Diff < MinDiff.GetValue()) { ClosestSection = Section; MinDiff = Diff; } } } } } } if (ClosestSection) { ClosestSection->Modify(); if (bTrimOrExtendLeft) { ClosestSection->SetStartFrame(Time.Time.GetFrame()); } else { ClosestSection->SetEndFrame(Time.Time.GetFrame()); } } } } } void MovieSceneToolHelpers::SplitSection(const TSet& Sections, FQualifiedFrameTime Time, bool bDeleteKeys) { for (UMovieSceneSection* Section : Sections) { if (Section) { Section->SplitSection(Time, bDeleteKeys); } } } bool MovieSceneToolHelpers::CanSplitSection(const TSet& Sections, FQualifiedFrameTime Time) { for (UMovieSceneSection* Section : Sections) { if (Section) { TRange Range = Section->GetRange(); TArray > Splits = Range.Split(Time.Time.FrameNumber); if (Splits.Num() > 0) { if (Splits[0] == Range) // can't split { continue; } else if (Splits.Num() == 2) { TRange Head = Splits[0]; TRange Tail = Splits[1]; if (!Head.IsEmpty() && !Tail.IsEmpty()) { return true; } } } } } return false; } FTransform MovieSceneToolHelpers::GetTransformOriginForFocusedSequence(TSharedPtr InSequencer) { FTransform TransformOrigin; const IMovieScenePlaybackClient* Client = InSequencer->GetPlaybackClient(); const UObject* InstanceData = Client ? Client->GetInstanceData() : nullptr; const IMovieSceneTransformOrigin* RawInterface = Cast(InstanceData); const bool bHasInterface = RawInterface || (InstanceData && InstanceData->GetClass()->ImplementsInterface(UMovieSceneTransformOrigin::StaticClass())); if (bHasInterface) { // Retrieve the current origin TransformOrigin = RawInterface ? RawInterface->GetTransformOrigin() : IMovieSceneTransformOrigin::Execute_BP_GetTransformOrigin(InstanceData); } const TArray& Hierarchy = InSequencer->GetSubSequenceHierarchy(); const FMovieSceneRootEvaluationTemplateInstance& EvaluationTemplate = InSequencer->GetEvaluationTemplate(); const UMovieSceneEntitySystemLinker* EntityLinker = EvaluationTemplate.GetEntitySystemLinker(); if(!EntityLinker) { return TransformOrigin; } const UMovieSceneTransformOriginSystem* TransformOriginSystem = EntityLinker->FindSystem(); if(!TransformOriginSystem) { return TransformOrigin; } const TMap SequenceIDToInstanceHandle = TransformOriginSystem->GetSequenceIDToInstanceHandle(); // Transform Origins will be pre-multiplied at this step, so only retrieve the entry for the currently focused sub-sequence. if(Hierarchy.Num()) { const FMovieSceneSequenceID CurrentSequence = Hierarchy.Last(); if(SequenceIDToInstanceHandle.Contains(CurrentSequence)) { const UE::MovieScene::FInstanceHandle CurrentHandle = SequenceIDToInstanceHandle[CurrentSequence]; // Overrides the origin only if entry is present TransformOriginSystem->GetTransformOrigin(CurrentHandle, TransformOrigin); } } return TransformOrigin; } bool MovieSceneToolHelpers::ParseShotName(const FString& InShotName, FString& ShotPrefix, uint32& ShotNumber, uint32& TakeNumber, uint32& ShotNumberDigits, uint32& TakeNumberDigits) { // Parse a shot name // // sht010: // ShotPrefix = sht // ShotNumber = 10 // TakeNumber = 1 (default) // // sp020_002 // ShotPrefix = sp // ShotNumber = 20 // TakeNumber = 2 // // If the shot prefix is known and is already at the beginning of the shot name, remove it and parse the rest // // sq050_sh0010_01 // ShotPrefix = sq050_sh, ie. parse(0010_01) // ShotNumber = 10 // TakeNumber = 1 FString ShotName = InShotName; ShotName.RemoveFromStart(ShotPrefix); const UMovieSceneToolsProjectSettings* ProjectSettings = GetDefault(); uint32 FirstShotNumberIndex = INDEX_NONE; uint32 LastShotNumberIndex = INDEX_NONE; bool bInShotNumber = false; uint32 FirstTakeNumberIndex = INDEX_NONE; uint32 LastTakeNumberIndex = INDEX_NONE; bool bInTakeNumber = false; bool bFoundTakeSeparator = false; TOptional ParsedTakeNumber; TakeNumber = ProjectSettings->FirstTakeNumber; for (int32 CharIndex = 0; CharIndex < ShotName.Len(); ++CharIndex) { if (FChar::IsDigit(ShotName[CharIndex])) { // Find shot number indices if (FirstShotNumberIndex == INDEX_NONE) { bInShotNumber = true; FirstShotNumberIndex = CharIndex; } if (bInShotNumber) { LastShotNumberIndex = CharIndex; } if (FirstShotNumberIndex != INDEX_NONE && LastShotNumberIndex != INDEX_NONE) { if (bFoundTakeSeparator) { // Find take number indices if (FirstTakeNumberIndex == INDEX_NONE) { bInTakeNumber = true; FirstTakeNumberIndex = CharIndex; } if (bInTakeNumber) { LastTakeNumberIndex = CharIndex; } } } } if (FirstShotNumberIndex != INDEX_NONE && LastShotNumberIndex != INDEX_NONE) { if (ShotName[CharIndex] == ProjectSettings->TakeSeparator[0]) { bFoundTakeSeparator = true; ShotNumberDigits = LastShotNumberIndex - FirstShotNumberIndex + 1; } } } if (FirstShotNumberIndex != INDEX_NONE) { if (FirstShotNumberIndex != 0) { ShotPrefix = ShotName.Left(FirstShotNumberIndex); } ShotNumber = FCString::Atoi(*ShotName.Mid(FirstShotNumberIndex, LastShotNumberIndex-FirstShotNumberIndex+1)); } if (FirstTakeNumberIndex != INDEX_NONE) { FString TakeStr = ShotName.Mid(FirstTakeNumberIndex, LastTakeNumberIndex-FirstTakeNumberIndex+1); if (TakeStr.IsNumeric()) { ParsedTakeNumber = FCString::Atoi(*TakeStr); TakeNumberDigits = TakeStr.Len(); } } // If take number wasn't found, start over with the original shot name, search backwards to find the first take separator and assume [shot prefix]_[take number] // if (!ParsedTakeNumber.IsSet()) { ShotName = InShotName; int32 LastSlashPos = ShotName.Find(ProjectSettings->TakeSeparator, ESearchCase::IgnoreCase, ESearchDir::FromEnd); if (LastSlashPos != INDEX_NONE) { if (LastSlashPos != 0) { ShotPrefix = ShotName.Left(LastSlashPos); } FString TakeStr = ShotName.RightChop(LastSlashPos + ProjectSettings->TakeSeparator.Len()); if (TakeStr.IsNumeric()) { ShotNumber = INDEX_NONE; // Nullify the shot number since we only have a shot prefix TakeNumber = FCString::Atoi(*TakeStr); TakeNumberDigits = ShotName.Len() - (LastSlashPos + ProjectSettings->TakeSeparator.Len()); return true; } } } if (ParsedTakeNumber.IsSet()) { TakeNumber = ParsedTakeNumber.GetValue(); } if (FirstShotNumberIndex == INDEX_NONE) { ShotPrefix = InShotName; ShotNumber = INDEX_NONE; // Nullify the shot number since we only have a shot prefix TakeNumber = 0; TakeNumberDigits = ProjectSettings->TakeNumDigits; return true; } return FirstShotNumberIndex != INDEX_NONE; } FString MovieSceneToolHelpers::ComposeShotName(const FString& ShotPrefix, uint32 ShotNumber, uint32 TakeNumber, uint32 ShotNumberDigits, uint32 TakeNumberDigits) { const UMovieSceneToolsProjectSettings* ProjectSettings = GetDefault(); FString ShotName = ShotPrefix; if (ShotNumber != INDEX_NONE) { ShotName += FString::Printf(TEXT("%0*d"), ShotNumberDigits, ShotNumber); } if (TakeNumber != INDEX_NONE) { FString TakeFormat = TEXT("%0") + FString::Printf(TEXT("%d"), TakeNumberDigits) + TEXT("d"); ShotName += ProjectSettings->TakeSeparator; ShotName += FString::Printf(TEXT("%0*d"), TakeNumberDigits, TakeNumber); } return ShotName; } bool IsPackageNameUnique(const TArray& ObjectList, FString& NewPackageName) { for (auto AssetObject : ObjectList) { if (AssetObject.PackageName.ToString() == NewPackageName) { return false; } } return true; } FString MovieSceneToolHelpers::GenerateNewShotPath(UMovieScene* SequenceMovieScene, FString& NewShotName) { const UMovieSceneToolsProjectSettings* ProjectSettings = GetDefault(); return GenerateNewSubsequencePath(SequenceMovieScene, ProjectSettings->ShotDirectory, NewShotName); } FString MovieSceneToolHelpers::GenerateNewSubsequencePath(UMovieScene * SequenceMovieScene, const FString& SubsequenceDirectory, FString &NewShotName) { const UMovieSceneToolsProjectSettings* ProjectSettings = GetDefault(); FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); TArray ObjectList; AssetRegistryModule.Get().GetAssetsByClass(ULevelSequence::StaticClass()->GetClassPathName(), ObjectList); UObject* SequenceAsset = SequenceMovieScene->GetOuter(); UPackage* SequencePackage = SequenceAsset->GetOutermost(); FString SequencePackageName = SequencePackage->GetName(); // ie. /Game/cine/max/root int32 LastSlashPos = SequencePackageName.Find(TEXT("/"), ESearchCase::IgnoreCase, ESearchDir::FromEnd); FString SequencePath = SequencePackageName.Left(LastSlashPos); FString NewShotPrefix; uint32 NewShotNumber = INDEX_NONE; uint32 NewTakeNumber = INDEX_NONE; uint32 ShotNumberDigits = ProjectSettings->ShotNumDigits; uint32 TakeNumberDigits = ProjectSettings->TakeNumDigits; ParseShotName(NewShotName, NewShotPrefix, NewShotNumber, NewTakeNumber, ShotNumberDigits, TakeNumberDigits); FString NewDirectory = ComposeShotName(NewShotPrefix, NewShotNumber, INDEX_NONE, ShotNumberDigits, TakeNumberDigits); FString NewPath = SequencePath; FString Directory = SubsequenceDirectory; if (!Directory.IsEmpty()) { NewPath /= Directory; } NewPath /= NewDirectory; // put this in the shot directory, ie. /Game/cine/max/shots/shot0010 // Make sure this shot path is unique FString NewPackageName = NewPath; NewPackageName /= NewShotName; // ie. /Game/cine/max/shots/shot0010/shot0010_001 if (!IsPackageNameUnique(ObjectList, NewPackageName)) { while (1) { NewShotNumber += ProjectSettings->ShotIncrement; NewShotName = ComposeShotName(NewShotPrefix, NewShotNumber, NewTakeNumber, ShotNumberDigits, TakeNumberDigits); NewDirectory = ComposeShotName(NewShotPrefix, NewShotNumber, INDEX_NONE, ShotNumberDigits, TakeNumberDigits); NewPath = SequencePath; if (!Directory.IsEmpty()) { NewPath /= Directory; } NewPath /= NewDirectory; NewPackageName = NewPath; NewPackageName /= NewShotName; if (IsPackageNameUnique(ObjectList, NewPackageName)) { break; } } } return NewPath; } FString MovieSceneToolHelpers::GenerateNewShotName(const TArray& AllSections, FFrameNumber Time) { const UMovieSceneToolsProjectSettings* ProjectSettings = GetDefault(); return GenerateNewSubsequenceName(AllSections, ProjectSettings->ShotPrefix, Time); } FString MovieSceneToolHelpers::GenerateNewSubsequenceName(const TArray&AllSections, const FString& SubsequencePrefix, FFrameNumber Time) { const UMovieSceneToolsProjectSettings* ProjectSettings = GetDefault(); UMovieSceneSubSection* CurrentSection = Cast(MovieSceneHelpers::FindSectionAtTime(AllSections, Time)); UMovieSceneSubSection* NextSection = Cast(MovieSceneHelpers::FindNextSection(AllSections, Time)); if (!CurrentSection) { CurrentSection = Cast(MovieSceneHelpers::FindPreviousSection(AllSections, Time)); } if (!NextSection) { NextSection = CurrentSection; } FString NextSectionName = NextSection && NextSection->GetSequence() ? NextSection->GetSequence()->GetName() : FString(); FString CurrentSectionName = CurrentSection && CurrentSection->GetSequence() ? CurrentSection->GetSequence()->GetName() : FString(); // This is the first or last shot if ((CurrentSection == nullptr && NextSection) || (CurrentSection != nullptr && CurrentSection == NextSection)) { FString NextShotPrefix = SubsequencePrefix; uint32 NextShotNumber = ProjectSettings->FirstShotNumber; uint32 NextTakeNumber = ProjectSettings->FirstTakeNumber; uint32 ShotNumberDigits = ProjectSettings->ShotNumDigits; uint32 TakeNumberDigits = ProjectSettings->TakeNumDigits; if (ParseShotName(NextSectionName, NextShotPrefix, NextShotNumber, NextTakeNumber, ShotNumberDigits, TakeNumberDigits)) { // Valid shot number if (NextShotNumber != INDEX_NONE) { uint32 NewShotNumber = NextShotNumber + ProjectSettings->ShotIncrement; return ComposeShotName(NextShotPrefix, NewShotNumber, ProjectSettings->FirstTakeNumber, ShotNumberDigits, TakeNumberDigits); } // No shot number, but valid take number else if (NextTakeNumber != INDEX_NONE) { uint32 NewTakeNumber = NextTakeNumber + ProjectSettings->ShotIncrement; return ComposeShotName(NextShotPrefix, INDEX_NONE, NewTakeNumber, ShotNumberDigits, TakeNumberDigits); } } } // This is in between two shots else if (CurrentSection && NextSection) { FString CurrentShotPrefix = SubsequencePrefix; uint32 CurrentShotNumber = ProjectSettings->FirstShotNumber; uint32 CurrentTakeNumber = ProjectSettings->FirstTakeNumber; uint32 CurrentShotNumberDigits = ProjectSettings->ShotNumDigits; uint32 CurrentTakeNumberDigits = ProjectSettings->TakeNumDigits; FString NextShotPrefix = SubsequencePrefix; uint32 NextShotNumber = ProjectSettings->FirstShotNumber; uint32 NextTakeNumber = ProjectSettings->FirstTakeNumber; uint32 NextShotNumberDigits = ProjectSettings->ShotNumDigits; uint32 NextTakeNumberDigits = ProjectSettings->TakeNumDigits; if (ParseShotName(CurrentSectionName, CurrentShotPrefix, CurrentShotNumber, CurrentTakeNumber, CurrentShotNumberDigits, CurrentTakeNumberDigits) && ParseShotName(NextSectionName, NextShotPrefix, NextShotNumber, NextTakeNumber, NextShotNumberDigits, NextTakeNumberDigits)) { // Valid shot numbers if (NextShotNumber != INDEX_NONE && CurrentShotNumber != INDEX_NONE) { uint32 NewShotNumber = CurrentShotNumber + ProjectSettings->ShotIncrement; if (NextShotNumber - CurrentShotNumber > 1) { NewShotNumber = CurrentShotNumber + ( (NextShotNumber - CurrentShotNumber) / 2); // what if we can't find one? or conflicts with another? } return ComposeShotName(CurrentShotPrefix, NewShotNumber, ProjectSettings->FirstTakeNumber, CurrentShotNumberDigits, CurrentTakeNumberDigits); // use before or next shot? } // No shot numbers, but valid take numbers else if (NextTakeNumber != INDEX_NONE && CurrentTakeNumber != INDEX_NONE) { uint32 NewTakeNumber = CurrentTakeNumber + ProjectSettings->ShotIncrement; if (NextTakeNumber - CurrentTakeNumber > 1) { NewTakeNumber = CurrentTakeNumber + ( (NextTakeNumber - CurrentTakeNumber) / 2); // what if we can't find one? or conflicts with another? } return ComposeShotName(CurrentShotPrefix, INDEX_NONE, NewTakeNumber, CurrentShotNumberDigits, CurrentTakeNumberDigits); // use before or next shot? } } } // Default case return ComposeShotName(SubsequencePrefix, ProjectSettings->FirstShotNumber, ProjectSettings->FirstTakeNumber, ProjectSettings->ShotNumDigits, ProjectSettings->TakeNumDigits); } UMovieSceneSequence* MovieSceneToolHelpers::CreateSequence(FString& NewSequenceName, FString& NewSequencePath, UMovieSceneSubSection* SectionToDuplicate) { // Create a new level sequence asset with the appropriate name IAssetTools& AssetTools = FModuleManager::GetModuleChecked("AssetTools").Get(); UObject* NewAsset = nullptr; for (TObjectIterator It; It; ++It) { UClass* CurrentClass = *It; if (CurrentClass->IsChildOf(UFactory::StaticClass()) && !(CurrentClass->HasAnyClassFlags(CLASS_Abstract))) { UFactory* Factory = Cast(CurrentClass->GetDefaultObject()); if (Factory->CanCreateNew() && Factory->ImportPriority >= 0 && Factory->SupportedClass == ULevelSequence::StaticClass()) { if (SectionToDuplicate != nullptr) { NewAsset = AssetTools.DuplicateAssetWithDialog(NewSequenceName, NewSequencePath, SectionToDuplicate->GetSequence()); } else { NewAsset = AssetTools.CreateAssetWithDialog(NewSequenceName, NewSequencePath, ULevelSequence::StaticClass(), Factory); } break; } } } if (NewAsset == nullptr) { return nullptr; } return Cast(NewAsset); } void MovieSceneToolHelpers::GatherTakes(const UMovieSceneSection* Section, TArray& AssetData, uint32& OutCurrentTakeNumber) { const UMovieSceneSubSection* SubSection = Cast(Section); if (SubSection->GetSequence() == nullptr) { return; } if (FMovieSceneToolsModule::Get().GatherTakes(Section, AssetData, OutCurrentTakeNumber)) { return; } FAssetData ShotData(SubSection->GetSequence()->GetOuter()); FString ShotPackagePath = ShotData.PackagePath.ToString(); FString ShotPrefix; uint32 ShotNumber = INDEX_NONE; OutCurrentTakeNumber = INDEX_NONE; uint32 ShotNumberDigits = 0; uint32 TakeNumberDigits = 0; FString SequenceName = SubSection->GetSequence()->GetName(); ParseShotName(SequenceName, ShotPrefix, ShotNumber, OutCurrentTakeNumber, ShotNumberDigits, TakeNumberDigits); // Gather up all level sequence assets FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); TArray ObjectList; AssetRegistryModule.Get().GetAssetsByClass(ULevelSequence::StaticClass()->GetClassPathName(), ObjectList); for (auto AssetObject : ObjectList) { FString AssetPackagePath = AssetObject.PackagePath.ToString(); if (AssetPackagePath == ShotPackagePath) { FString AssetShotPrefix; uint32 AssetShotNumber = INDEX_NONE; uint32 AssetTakeNumber = INDEX_NONE; ParseShotName(AssetObject.AssetName.ToString(), AssetShotPrefix, AssetShotNumber, AssetTakeNumber, ShotNumberDigits, TakeNumberDigits); if (AssetShotPrefix == ShotPrefix && AssetShotNumber == ShotNumber) { AssetData.Add(AssetObject); } } } } bool MovieSceneToolHelpers::GetTakeNumber(const UMovieSceneSection* Section, FAssetData AssetData, uint32& OutTakeNumber) { if (FMovieSceneToolsModule::Get().GetTakeNumber(Section, AssetData, OutTakeNumber)) { return true; } const UMovieSceneSubSection* SubSection = Cast(Section); FAssetData ShotData(SubSection->GetSequence()->GetOuter()); FString ShotPackagePath = ShotData.PackagePath.ToString(); int32 ShotLastSlashPos = INDEX_NONE; ShotPackagePath.FindLastChar(TCHAR('/'), ShotLastSlashPos); ShotPackagePath.LeftInline(ShotLastSlashPos, EAllowShrinking::No); FString ShotPrefix; uint32 ShotNumber = INDEX_NONE; uint32 TakeNumberDummy = INDEX_NONE; uint32 ShotNumberDigits = 0; uint32 TakeNumberDigits = 0; FString SequenceName = SubSection->GetSequence()->GetName(); ParseShotName(SequenceName, ShotPrefix, ShotNumber, TakeNumberDummy, ShotNumberDigits, TakeNumberDigits); // Gather up all level sequence assets FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); TArray ObjectList; AssetRegistryModule.Get().GetAssetsByClass(ULevelSequence::StaticClass()->GetClassPathName(), ObjectList); for (auto AssetObject : ObjectList) { if (AssetObject == AssetData) { FString AssetPackagePath = AssetObject.PackagePath.ToString(); int32 AssetLastSlashPos = INDEX_NONE; AssetPackagePath.FindLastChar(TCHAR('/'), AssetLastSlashPos); AssetPackagePath.LeftInline(AssetLastSlashPos, EAllowShrinking::No); if (AssetPackagePath == ShotPackagePath) { FString AssetShotPrefix; uint32 AssetShotNumber = INDEX_NONE; uint32 AssetTakeNumber = INDEX_NONE; ParseShotName(AssetObject.AssetName.ToString(), AssetShotPrefix, AssetShotNumber, AssetTakeNumber, ShotNumberDigits, TakeNumberDigits); if (AssetShotPrefix == ShotPrefix && AssetShotNumber == ShotNumber) { OutTakeNumber = AssetTakeNumber; return true; } } } } return false; } bool MovieSceneToolHelpers::SetTakeNumber(const UMovieSceneSection* Section, uint32 InTakeNumber) { return FMovieSceneToolsModule::Get().SetTakeNumber(Section, InTakeNumber); } int32 MovieSceneToolHelpers::FindAvailableRowIndex(UMovieSceneTrack* InTrack, UMovieSceneSection* InSection, const TArray& SectionsToDisregard) { for (int32 RowIndex = 0; RowIndex <= InTrack->GetMaxRowIndex(); ++RowIndex) { bool bFoundIntersect = false; for (UMovieSceneSection* Section : InTrack->GetAllSections()) { if (SectionsToDisregard.Contains(Section)) { continue; } if (!Section->HasStartFrame() || !Section->HasEndFrame() || !InSection->HasStartFrame() || !InSection->HasEndFrame()) { bFoundIntersect = true; break; } if (Section != InSection && Section->GetRowIndex() == RowIndex && Section->GetRange().Overlaps(InSection->GetRange())) { bFoundIntersect = true; break; } } if (!bFoundIntersect) { return RowIndex; } } return InTrack->GetMaxRowIndex() + 1; } bool MovieSceneToolHelpers::OverlapsSection(UMovieSceneTrack* InTrack, UMovieSceneSection* InSection, const TArray& SectionsToDisregard) { for (UMovieSceneSection* Section : InTrack->GetAllSections()) { if (SectionsToDisregard.Contains(Section)) { continue; } if (!Section->HasStartFrame() || !Section->HasEndFrame() || !InSection->HasStartFrame() || !InSection->HasEndFrame()) { return true; } if (Section != InSection && Section->GetRange().Overlaps(InSection->GetRange())) { return true; } } return false; } TSharedRef MovieSceneToolHelpers::MakeEnumComboBox(const UEnum* InEnum, TAttribute InCurrentValue, SEnumComboBox::FOnEnumSelectionChanged InOnSelectionChanged) { return SNew(SEnumComboBox, InEnum) .CurrentValue(InCurrentValue) .ContentPadding(FMargin(2, 0)) .Font(FAppStyle::GetFontStyle("Sequencer.AnimationOutliner.RegularFont")) .OnEnumSelectionChanged(InOnSelectionChanged); } bool MovieSceneToolHelpers::ShowImportEDLDialog(UMovieScene* InMovieScene, FFrameRate InFrameRate, FString InOpenDirectory) { TArray OpenFilenames; IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); bool bOpen = false; if (DesktopPlatform) { FString ExtensionStr; ExtensionStr += TEXT("CMX 3600 EDL (*.edl)|*.edl|"); bOpen = DesktopPlatform->OpenFileDialog( FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr), NSLOCTEXT("MovieSceneToolHelpers", "ImportEDL", "Import EDL from...").ToString(), InOpenDirectory, TEXT(""), *ExtensionStr, EFileDialogFlags::None, OpenFilenames ); } if (!bOpen) { return false; } if (!OpenFilenames.Num()) { return false; } const FScopedTransaction Transaction( NSLOCTEXT( "MovieSceneTools", "ImportEDLTransaction", "Import EDL" ) ); return MovieSceneTranslatorEDL::ImportEDL(InMovieScene, InFrameRate, OpenFilenames[0]); } bool MovieSceneToolHelpers::ShowExportEDLDialog(const UMovieScene* InMovieScene, FFrameRate InFrameRate, FString InSaveDirectory, int32 InHandleFrames, FString InMovieExtension) { TArray SaveFilenames; FString SequenceName = InMovieScene->GetOuter()->GetName(); // Pop open a dialog to request the location of the edl IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); bool bSave = false; if (DesktopPlatform) { FString ExtensionStr; ExtensionStr += TEXT("CMX 3600 EDL (*.edl)|*.edl|"); ExtensionStr += TEXT("RV (*.rv)|*.rv|"); bSave = DesktopPlatform->SaveFileDialog( FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr), NSLOCTEXT("MovieSceneTools", "ExportEDL", "Export EDL to...").ToString(), InSaveDirectory, SequenceName + TEXT(".edl"), *ExtensionStr, EFileDialogFlags::None, SaveFilenames ); } if (!bSave) { return false; } if (!SaveFilenames.Num()) { return false; } if (MovieSceneTranslatorEDL::ExportEDL(InMovieScene, InFrameRate, SaveFilenames[0], InHandleFrames, InMovieExtension)) { const FString AbsoluteFilename = FPaths::ConvertRelativePathToFull(SaveFilenames[0]); const FString SaveDirectory = FPaths::GetPath(AbsoluteFilename); FNotificationInfo NotificationInfo(NSLOCTEXT("MovieSceneTools", "EDLExportFinished", "EDL Export finished")); NotificationInfo.ExpireDuration = 5.f; NotificationInfo.Hyperlink = FSimpleDelegate::CreateStatic( [](FString InDirectory) { FPlatformProcess::ExploreFolder( *InDirectory ); }, SaveDirectory); NotificationInfo.HyperlinkText = NSLOCTEXT("MovieSceneTools", "OpenEDLExportFolder", "Open EDL Export Folder..."); FSlateNotificationManager::Get().AddNotification(NotificationInfo); return true; } return false; } bool MovieSceneToolHelpers::MovieSceneTranslatorImport(FMovieSceneImporter* InImporter, UMovieScene* InMovieScene, FFrameRate InFrameRate, FString InOpenDirectory) { TArray OpenFilenames; IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); bool bOpen = false; if (DesktopPlatform) { FString FileTypeDescription = InImporter->GetFileTypeDescription().ToString(); FString DialogTitle = InImporter->GetDialogTitle().ToString(); bOpen = DesktopPlatform->OpenFileDialog( FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr), DialogTitle, InOpenDirectory, TEXT(""), FileTypeDescription, EFileDialogFlags::None, OpenFilenames ); } if (!bOpen || !OpenFilenames.Num()) { return false; } FScopedTransaction Transaction(InImporter->GetTransactionDescription()); TSharedRef ImportContext(new FMovieSceneTranslatorContext); ImportContext->Init(); bool bSuccess = InImporter->Import(InMovieScene, InFrameRate, OpenFilenames[0], ImportContext); // Display any messages in context MovieSceneTranslatorLogMessages(InImporter, ImportContext, true); // Roll back transaction when import fails. if (!bSuccess) { Transaction.Cancel(); } return bSuccess; } bool MovieSceneToolHelpers::MovieSceneTranslatorExport(FMovieSceneExporter* InExporter, const UMovieScene* InMovieScene, const FMovieSceneCaptureSettings& Settings) { if (InExporter == nullptr || InMovieScene == nullptr) { return false; } FString SaveDirectory = FPaths::ConvertRelativePathToFull(Settings.OutputDirectory.Path); int32 HandleFrames = Settings.HandleFrames; // @todo: generate filename based on filename format, currently outputs {shot}.avi FString FilenameFormat = Settings.OutputFormat; FFrameRate FrameRate = Settings.GetFrameRate(); uint32 ResX = Settings.Resolution.ResX; uint32 ResY = Settings.Resolution.ResY; FString MovieExtension = Settings.MovieExtension; TArray SaveFilenames; FString SequenceName = InMovieScene->GetOuter()->GetName(); // Pop open a dialog to request the location of the edl IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); bool bSave = false; if (DesktopPlatform) { FString FileTypeDescription = InExporter->GetFileTypeDescription().ToString(); FString DialogTitle = InExporter->GetDialogTitle().ToString(); FString FileExtension = InExporter->GetDefaultFileExtension().ToString(); bSave = DesktopPlatform->SaveFileDialog( FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr), DialogTitle, SaveDirectory, SequenceName + TEXT(".") + FileExtension, FileTypeDescription, EFileDialogFlags::None, SaveFilenames ); } if (!bSave || !SaveFilenames.Num()) { return false; } TSharedRef ExportContext(new FMovieSceneTranslatorContext); ExportContext->Init(); bool bSuccess = InExporter->Export(InMovieScene, FilenameFormat, FrameRate, ResX, ResY, HandleFrames, SaveFilenames[0], ExportContext, MovieExtension); // Display any messages in context MovieSceneTranslatorLogMessages(InExporter, ExportContext, true); if (bSuccess) { const FString AbsoluteFilename = FPaths::ConvertRelativePathToFull(SaveFilenames[0]); const FString ActualSaveDirectory = FPaths::GetPath(AbsoluteFilename); FNotificationInfo NotificationInfo(InExporter->GetNotificationExportFinished()); NotificationInfo.ExpireDuration = 5.f; NotificationInfo.Hyperlink = FSimpleDelegate::CreateStatic([](FString InDirectory) { FPlatformProcess::ExploreFolder(*InDirectory); }, ActualSaveDirectory); NotificationInfo.HyperlinkText = InExporter->GetNotificationHyperlinkText(); FSlateNotificationManager::Get().AddNotification(NotificationInfo); } return bSuccess; } void MovieSceneToolHelpers::MovieSceneTranslatorLogMessages(FMovieSceneTranslator *InTranslator, TSharedRef InContext, bool bDisplayMessages) { if (InTranslator == nullptr || InContext->GetMessages().Num() == 0) { return; } // Clear any old messages after an import or export const FName LogTitle = InTranslator->GetMessageLogWindowTitle(); FMessageLogModule& MessageLogModule = FModuleManager::LoadModuleChecked("MessageLog"); TSharedRef LogListing = MessageLogModule.GetLogListing(LogTitle); LogListing->SetLabel(InTranslator->GetMessageLogLabel()); LogListing->ClearMessages(); for (TSharedRef Message : InContext->GetMessages()) { LogListing->AddMessage(Message); } if (bDisplayMessages) { MessageLogModule.OpenMessageLog(LogTitle); } } void MovieSceneToolHelpers::MovieSceneTranslatorLogOutput(FMovieSceneTranslator *InTranslator, TSharedRef InContext) { if (InTranslator == nullptr || InContext->GetMessages().Num() == 0) { return; } for (TSharedRef Message : InContext->GetMessages()) { if (Message->GetSeverity() == EMessageSeverity::Error) { UE_LOG(LogMovieScene, Error, TEXT("%s"), *Message->ToText().ToString()); } else if (Message->GetSeverity() == EMessageSeverity::Warning) { UE_LOG(LogMovieScene, Warning, TEXT("%s"), *Message->ToText().ToString()); } } } static FGuid GetHandleToObject(UObject* InObject, UMovieSceneSequence* InSequence, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID, bool bCreateIfMissing = true) { UMovieScene* MovieScene = InSequence->GetMovieScene(); // Attempt to resolve the object through the movie scene instance first, FGuid PropertyOwnerGuid = FGuid(); if (InObject != nullptr && !MovieScene->IsReadOnly()) { FGuid ObjectGuid = Player->FindObjectId(*InObject, TemplateID); if (ObjectGuid.IsValid()) { // Check here for spawnable otherwise spawnables get recreated as possessables, which doesn't make sense FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(ObjectGuid); if (Spawnable) { PropertyOwnerGuid = ObjectGuid; } else { FMovieScenePossessable* Possessable = MovieScene->FindPossessable(ObjectGuid); if (Possessable) { PropertyOwnerGuid = ObjectGuid; } } } } if (PropertyOwnerGuid.IsValid()) { return PropertyOwnerGuid; } if (bCreateIfMissing) { // Otherwise, create a possessable for this object. Note this will handle creating the parent possessables if this is a component. PropertyOwnerGuid = Player->CreateBinding(InSequence, InObject); } return PropertyOwnerGuid; } template bool ImportFBXPropertyToPropertyTrack(UMovieScene* MovieScene, const FGuid PropertyOwnerGuid, const FMovieSceneToolsFbxSettings& FbxSetting, FRichCurve& Source) { TrackType* PropertyTrack = MovieScene->FindTrack(PropertyOwnerGuid, *FbxSetting.PropertyPath.PropertyName); if (!PropertyTrack) { MovieScene->Modify(); PropertyTrack = MovieScene->AddTrack(PropertyOwnerGuid); PropertyTrack->SetPropertyNameAndPath(*FbxSetting.PropertyPath.PropertyName, *FbxSetting.PropertyPath.PropertyName); } if (!PropertyTrack) { return false; } PropertyTrack->Modify(); PropertyTrack->RemoveAllAnimationData(); FFrameRate FrameRate = PropertyTrack->template GetTypedOuter()->GetTickResolution(); bool bSectionAdded = false; UMovieSceneSection* Section = Cast(PropertyTrack->FindOrAddSection(0, bSectionAdded)); if (!Section) { return false; } Section->Modify(); if (bSectionAdded) { Section->SetRange(TRange::All()); } ChannelType* Channel = Section->GetChannelProxy().GetChannel(0); TMovieSceneChannelData ChannelData = Channel->GetData(); ChannelData.Reset(); double DecimalRate = FrameRate.AsDecimal(); for (auto SourceIt = Source.GetKeyHandleIterator(); SourceIt; ++SourceIt) { FRichCurveKey &Key = Source.GetKey(*SourceIt); float ArriveTangent = Key.ArriveTangent; FKeyHandle PrevKeyHandle = Source.GetPreviousKey(*SourceIt); if (Source.IsKeyHandleValid(PrevKeyHandle)) { FRichCurveKey &PrevKey = Source.GetKey(PrevKeyHandle); ArriveTangent = ArriveTangent / (Key.Time - PrevKey.Time); } float LeaveTangent = Key.LeaveTangent; FKeyHandle NextKeyHandle = Source.GetNextKey(*SourceIt); if (Source.IsKeyHandleValid(NextKeyHandle)) { FRichCurveKey &NextKey = Source.GetKey(NextKeyHandle); LeaveTangent = LeaveTangent / (NextKey.Time - Key.Time); } FFrameNumber KeyTime = (Key.Time * FrameRate).RoundToFrame(); MovieSceneToolHelpers::SetOrAddKey(ChannelData, KeyTime, Key.Value, ArriveTangent, LeaveTangent, Key.InterpMode, Key.TangentMode, FrameRate, Key.TangentWeightMode, Key.ArriveTangentWeight, Key.LeaveTangentWeight); } Channel->AutoSetTangents(); const UMovieSceneUserImportFBXSettings* ImportFBXSettings = GetDefault(); if (ImportFBXSettings->bReduceKeys) { FKeyDataOptimizationParams Params; Params.Tolerance = ImportFBXSettings->ReduceKeysTolerance; Params.DisplayRate = FrameRate; Params.bAutoSetInterpolation = true; //we use this to perform the AutoSetTangents after the keys are reduced. Channel->Optimize(Params); } return true; } // Static function to handle applying uniform scale data to keys inside of camera imports that are distance based. void ApplyUniformScaleToCameraKeys(const FString& AnimatedPropertyName, FRichCurve& Source) { const UMovieSceneUserImportFBXSettings* ImportFBXSettings = GetDefault(); // This is the only property right now. if (AnimatedPropertyName == "FocusDistance") { for (int i=0; iImportUniformScale; } } }; bool ImportFBXProperty(FString NodeName, FString AnimatedPropertyName, FGuid ObjectBinding, UnFbx::FFbxCurvesAPI& CurveAPI, UMovieSceneSequence* InSequence, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID) { UMovieScene* MovieScene = InSequence->GetMovieScene(); const int32 ChannelIndex = 0; const int32 CompositeIndex = 0; FRichCurve Source; const bool bNegative = false; CurveAPI.GetCurveDataForSequencer(NodeName, AnimatedPropertyName, ChannelIndex, CompositeIndex, Source, bNegative); // Modify distance based camera curve info to account for Uniform Scale. ApplyUniformScaleToCameraKeys(AnimatedPropertyName, Source); // First, see if any of the custom importers can import this named property if (FMovieSceneToolsModule::Get().ImportAnimatedProperty(AnimatedPropertyName, Source, ObjectBinding, MovieScene)) { return true; } const UMovieSceneToolsProjectSettings* ProjectSettings = GetDefault(); TArrayView> BoundObjects = Player->FindBoundObjects(ObjectBinding, TemplateID); for (auto FbxSetting : ProjectSettings->FbxSettings) { if (FCString::Strcmp(*FbxSetting.FbxPropertyName.ToUpper(), *AnimatedPropertyName.ToUpper()) != 0) { continue; } for (TWeakObjectPtr<>& WeakObject : BoundObjects) { UObject* FoundObject = WeakObject.Get(); if (!FoundObject) { continue; } UObject* PropertyOwner = FoundObject; if (!FbxSetting.PropertyPath.ComponentName.IsEmpty()) { PropertyOwner = FindObjectFast(FoundObject, *FbxSetting.PropertyPath.ComponentName); } if (!PropertyOwner) { continue; } FGuid PropertyOwnerGuid = GetHandleToObject(PropertyOwner, InSequence, Player, TemplateID); if (!PropertyOwnerGuid.IsValid()) { continue; } if (!PropertyOwnerGuid.IsValid()) { continue; } bool bImported = false; switch (FbxSetting.PropertyType) { case EMovieSceneToolsPropertyTrackType::FloatTrack: bImported = ImportFBXPropertyToPropertyTrack(MovieScene, PropertyOwnerGuid, FbxSetting, Source); break; case EMovieSceneToolsPropertyTrackType::DoubleTrack: bImported = ImportFBXPropertyToPropertyTrack(MovieScene, PropertyOwnerGuid, FbxSetting, Source); break; } if (bImported) { return true; } } } return false; } void MovieSceneToolHelpers::LockCameraActorToViewport(const TSharedPtr& Sequencer, ACameraActor* CameraActor) { Sequencer->SetPerspectiveViewportCameraCutEnabled(false); // Lock the viewport to this camera if (GCurrentLevelEditingViewportClient && CameraActor && CameraActor->GetLevel()) { GCurrentLevelEditingViewportClient->SetCinematicActorLock(nullptr); GCurrentLevelEditingViewportClient->SetActorLock(CameraActor); GCurrentLevelEditingViewportClient->bLockedCameraView = true; GCurrentLevelEditingViewportClient->UpdateViewForLockedActor(); GCurrentLevelEditingViewportClient->Invalidate(); } } void MovieSceneToolHelpers::CreateCameraCutSectionForCamera(UMovieScene* OwnerMovieScene, FGuid CameraGuid, FFrameNumber FrameNumber) { // If there's a cinematic shot track, no need to set this camera to a shot UMovieSceneTrack* CinematicShotTrack = OwnerMovieScene->FindTrack(UMovieSceneCinematicShotTrack::StaticClass()); if (CinematicShotTrack) { return; } UMovieSceneTrack* CameraCutTrack = OwnerMovieScene->GetCameraCutTrack(); // If there's a camera cut track with at least one section, no need to change the section if (CameraCutTrack && CameraCutTrack->GetAllSections().Num() > 0) { return; } if (!CameraCutTrack) { CameraCutTrack = OwnerMovieScene->AddCameraCutTrack(UMovieSceneCameraCutTrack::StaticClass()); } if (CameraCutTrack) { UMovieSceneSection* Section = MovieSceneHelpers::FindSectionAtTime(CameraCutTrack->GetAllSections(), FrameNumber); UMovieSceneCameraCutSection* CameraCutSection = Cast(Section); if (CameraCutSection) { CameraCutSection->Modify(); CameraCutSection->SetCameraGuid(CameraGuid); } else { CameraCutTrack->Modify(); UMovieSceneCameraCutSection* NewSection = Cast(CameraCutTrack->CreateNewSection()); NewSection->SetRange(OwnerMovieScene->GetPlaybackRange()); NewSection->SetCameraGuid(CameraGuid); CameraCutTrack->AddSection(*NewSection); } } } template void ImportTransformChannelToBezierChannel(const FRichCurve& Source, ChannelType* Dest, FFrameRate DestFrameRate, bool bNegateTangents, bool bClearChannel,FFrameNumber StartFrame = 0, bool bNegateValue = false) { // If there are no keys, don't clear the existing channel if (!Source.GetNumKeys()) { return; } TMovieSceneChannelData ChannelData = Dest->GetData(); if (bClearChannel) { ChannelData.Reset(); } for (auto SourceIt = Source.GetKeyHandleIterator(); SourceIt; ++SourceIt) { const FRichCurveKey Key = Source.GetKey(*SourceIt); float ArriveTangent = Key.ArriveTangent; float LeaveTangent = Key.LeaveTangent; if (bNegateTangents) { ArriveTangent = -ArriveTangent; LeaveTangent = -LeaveTangent; } FFrameNumber KeyTime = (Key.Time * DestFrameRate).RoundToFrame(); float Value = !bNegateValue ? Key.Value : -Key.Value; MovieSceneToolHelpers::SetOrAddKey(ChannelData, KeyTime + StartFrame, Value, ArriveTangent, LeaveTangent, Key.InterpMode, Key.TangentMode, DestFrameRate, Key.TangentWeightMode, Key.ArriveTangentWeight, Key.LeaveTangentWeight); } Dest->AutoSetTangents(); const UMovieSceneUserImportFBXSettings* ImportFBXSettings = GetDefault(); if (ImportFBXSettings->bReduceKeys) { FKeyDataOptimizationParams Params; Params.Tolerance = ImportFBXSettings->ReduceKeysTolerance; Params.DisplayRate = DestFrameRate; Dest->Optimize(Params); } } void ImportTransformChannelToDouble(const FRichCurve& Source, FMovieSceneDoubleChannel* Dest, FFrameRate DestFrameRate, bool bNegateTangents, bool bClearChannel, FFrameNumber StartFrame = 0, bool bNegateValue = false) { ImportTransformChannelToBezierChannel(Source, Dest, DestFrameRate, bNegateTangents, bClearChannel, StartFrame, bNegateValue); } void ImportTransformChannelToFloat(const FRichCurve& Source, FMovieSceneFloatChannel* Dest, FFrameRate DestFrameRate, bool bNegateTangents, bool bClearChannel, FFrameNumber StartFrame = 0, bool bNegateValue = false) { ImportTransformChannelToBezierChannel(Source, Dest, DestFrameRate, bNegateTangents, bClearChannel, StartFrame, bNegateValue); } void ImportTransformChannelToBool(const FRichCurve& Source, FMovieSceneBoolChannel* Dest, FFrameRate DestFrameRate, bool bClearChannel, FFrameNumber StartFrame) { // If there are no keys, don't clear the existing channel if (!Source.GetNumKeys()) { return; } TMovieSceneChannelData ChannelData = Dest->GetData(); if (bClearChannel) { ChannelData.Reset(); } for (auto SourceIt = Source.GetKeyHandleIterator(); SourceIt; ++SourceIt) { const FRichCurveKey Key = Source.GetKey(*SourceIt); bool bValue = Key.Value != 0.0f ? true : false; FFrameNumber KeyTime = (Key.Time * DestFrameRate).RoundToFrame(); KeyTime += StartFrame; if (ChannelData.FindKey(KeyTime) == INDEX_NONE) { ChannelData.AddKey(KeyTime, bValue); } //todo need to do a set here? } } void ImportTransformChannelToEnum(const FRichCurve& Source, FMovieSceneByteChannel* Dest, FFrameRate DestFrameRate, bool bClearChannel, FFrameNumber StartFrame) { // If there are no keys, don't clear the existing channel if (!Source.GetNumKeys()) { return; } TMovieSceneChannelData ChannelData = Dest->GetData(); if (bClearChannel) { ChannelData.Reset(); } for (auto SourceIt = Source.GetKeyHandleIterator(); SourceIt; ++SourceIt) { const FRichCurveKey Key = Source.GetKey(*SourceIt); uint8 Value = (uint8)Key.Value; FFrameNumber KeyTime = (Key.Time * DestFrameRate).RoundToFrame(); KeyTime += StartFrame; if (ChannelData.FindKey(KeyTime) == INDEX_NONE) { ChannelData.AddKey(KeyTime, Value); } //todo need to do a set here? } } void ImportTransformChannelToInteger(const FRichCurve& Source, FMovieSceneIntegerChannel* Dest, FFrameRate DestFrameRate, bool bClearChannel, FFrameNumber StartFrame) { // If there are no keys, don't clear the existing channel if (!Source.GetNumKeys()) { return; } TMovieSceneChannelData ChannelData = Dest->GetData(); if (bClearChannel) { ChannelData.Reset(); } for (auto SourceIt = Source.GetKeyHandleIterator(); SourceIt; ++SourceIt) { const FRichCurveKey Key = Source.GetKey(*SourceIt); int32 Value = (int32)Key.Value; FFrameNumber KeyTime = (Key.Time * DestFrameRate).RoundToFrame(); KeyTime += StartFrame; if (ChannelData.FindKey(KeyTime) == INDEX_NONE) { ChannelData.AddKey(KeyTime, Value); } //todo need to do a set here? } } void SetChannelValue(FMovieSceneDoubleChannel* DoubleChannel, FMovieSceneFloatChannel* FloatChannel, FMovieSceneBoolChannel *BoolChannel, FMovieSceneByteChannel *EnumChannel, FMovieSceneIntegerChannel* IntegerChannel, const FFrameRate& FrameRate, const FFrameNumber& StartFrame, FControlRigChannelEnum ChannelEnum, UMovieSceneUserImportFBXControlRigSettings* ImportFBXControlRigSettings, const FTransform& DefaultTransform, const FRichCurve& TranslationX, const FRichCurve& TranslationY, const FRichCurve& TranslationZ, const FRichCurve& EulerRotationX, const FRichCurve& EulerRotationY, const FRichCurve& EulerRotationZ, const FRichCurve& ScaleX, const FRichCurve& ScaleY, const FRichCurve& ScaleZ) { const FRichCurve* TransformCurves[9] = {&TranslationX, &TranslationY, &TranslationZ, &EulerRotationX, &EulerRotationY, &EulerRotationZ, &ScaleX, &ScaleY, &ScaleZ}; FVector Location = DefaultTransform.GetLocation(), Rotation = DefaultTransform.GetRotation().Euler(), Scale3D = DefaultTransform.GetScale3D(); const double DefaultTransformValues[9] = {Location[0], Location[1], Location[2], Rotation[0], Rotation[1], Rotation[2], Scale3D[0], Scale3D[1], Scale3D[2]}; const FRichCurve *RichCurve = &TranslationX; float DefaultValue = Location.X; bool bNegate = false; uint8 ChannelIndex = 0; // Default Channel is TX if (const FControlToTransformMappings* ChannelMapping = ImportFBXControlRigSettings->ControlChannelMappings.FindByPredicate( [ChannelEnum](const FControlToTransformMappings& Mapping) { return ChannelEnum == Mapping.ControlChannel; })) { bNegate = ChannelMapping->bNegate; ChannelIndex = (uint8)ChannelMapping->FBXChannel; } else { const uint8 ChannelEnumIndex = (uint8)ChannelEnum; // 2DX, Bool and other default to TX if (ChannelEnum == FControlRigChannelEnum::Vector2DY) { ChannelIndex = 1; } else if (ChannelEnumIndex >= (uint8)FControlRigChannelEnum::PositionX && ChannelEnumIndex <= (uint8)FControlRigChannelEnum::ScaleZ) { ChannelIndex = ChannelEnumIndex - (uint8)FControlRigChannelEnum::PositionX; } } if (ChannelIndex < 9) { DefaultValue = DefaultTransformValues[ChannelIndex]; RichCurve = TransformCurves[ChannelIndex]; } if (ChannelEnum == FControlRigChannelEnum::Bool && BoolChannel) { const bool Default = DefaultValue != 0.0 ? !bNegate : bNegate; BoolChannel->SetDefault(Default); ImportTransformChannelToBool(*RichCurve, BoolChannel, FrameRate, false, StartFrame); } else if (ChannelEnum == FControlRigChannelEnum::Enum && EnumChannel) { const uint8 Default = (uint8)(bNegate ? -DefaultValue : DefaultValue); EnumChannel->SetDefault(Default); ImportTransformChannelToEnum(*RichCurve, EnumChannel, FrameRate, false, StartFrame); } else if (ChannelEnum == FControlRigChannelEnum::Integer && IntegerChannel) { const int32 Default = (int32)(bNegate ? -DefaultValue : DefaultValue); IntegerChannel->SetDefault(Default); ImportTransformChannelToInteger(*RichCurve, IntegerChannel, FrameRate, false, StartFrame); } else if (FloatChannel || DoubleChannel) { if (*RichCurve == TranslationY) { bNegate = !bNegate; } const float Default = bNegate ? -DefaultValue : DefaultValue; if (FloatChannel) { FloatChannel->SetDefault(Default); ImportTransformChannelToFloat(*RichCurve, FloatChannel, FrameRate, bNegate, false, StartFrame, bNegate); } else if (DoubleChannel) { DoubleChannel->SetDefault(Default); ImportTransformChannelToDouble(*RichCurve, DoubleChannel, FrameRate, bNegate, false, StartFrame, bNegate); } } } static bool ImportFBXNonTransformCurvesToChannels(const FString& NodeName, const UMovieSceneUserImportFBXSettings* ImportFBXSettings,UMovieSceneUserImportFBXControlRigSettings* ImportFBXControlRigSettings, const FFrameNumber StartFrame, const FFrameRate FrameRate, FRigControlFBXNodeAndChannels& NodeAndChannels, UnFbx::FFbxCurvesAPI& CurveAPI) { FbxNode* Node = CurveAPI.Scene->GetRootNode()->FindChild(TCHAR_TO_UTF8(*NodeName)); if (!Node) { return false; } FRichCurve EmptyDefaultCurve; FbxProperty Property = Node->FindProperty(TCHAR_TO_UTF8(*NodeAndChannels.ControlName.ToString())); if (!Property.IsValid()) { return false; } EmptyDefaultCurve.SetDefaultValue(Property.Get()); TMap Curves; CurveAPI.GetConvertedNonTransformCurveData(NodeName, true, ImportFBXSettings->ImportUniformScale, Curves); FRichCurve* Curve = Curves.Find(NodeAndChannels.ControlName); if (!Curve) { Curve = &EmptyDefaultCurve; } const float CurveDefault = Curve->GetDefaultValue(); auto ShouldNegateChannel = [ImportFBXControlRigSettings](FControlRigChannelEnum ChannelType) { const FControlToTransformMappings* ControlMapping = ImportFBXControlRigSettings->ControlChannelMappings.FindByPredicate( [ChannelType](const FControlToTransformMappings& Mapping) { return Mapping.ControlChannel == ChannelType; } ); return ControlMapping && ControlMapping->bNegate; }; if (!NodeAndChannels.BoolChannels.IsEmpty()) { const bool bNegate = ShouldNegateChannel(FControlRigChannelEnum::Bool); const bool ChannelDefault = bNegate ? CurveDefault == 0.0 : CurveDefault != 0.0; NodeAndChannels.BoolChannels[0]->SetDefault(ChannelDefault); ImportTransformChannelToBool(*Curve, NodeAndChannels.BoolChannels[0], FrameRate, false, StartFrame); } else if (!NodeAndChannels.EnumChannels.IsEmpty()) { const bool bNegate = ShouldNegateChannel(FControlRigChannelEnum::Enum); const uint8 ChannelDefault = bNegate ? -(uint8)CurveDefault : (uint8)CurveDefault; NodeAndChannels.EnumChannels[0]->SetDefault(ChannelDefault); ImportTransformChannelToEnum(*Curve, NodeAndChannels.EnumChannels[0], FrameRate, false, StartFrame); } else if (!NodeAndChannels.IntegerChannels.IsEmpty()) { const bool bNegate = ShouldNegateChannel(FControlRigChannelEnum::Integer); const int32 ChannelDefault = bNegate ? -(int32)CurveDefault : (int32)CurveDefault; NodeAndChannels.IntegerChannels[0]->SetDefault(ChannelDefault); ImportTransformChannelToInteger(*Curve, NodeAndChannels.IntegerChannels[0], FrameRate, false, StartFrame); } else if (!NodeAndChannels.FloatChannels.IsEmpty()) { const bool bNegate = ShouldNegateChannel(FControlRigChannelEnum::Integer); const float ChannelDefault = bNegate ? -CurveDefault : CurveDefault; NodeAndChannels.FloatChannels[0]->SetDefault(ChannelDefault); ImportTransformChannelToFloat(*Curve, NodeAndChannels.FloatChannels[0], FrameRate, false, false, StartFrame, bNegate); } else { return false; } return true; } //if one channel goes to Y //if two channel go to X Y //if three channel to to x y z // if 9 due full static bool ImportFBXTransformToChannels(FString NodeName, const UMovieSceneUserImportFBXSettings* ImportFBXSettings,UMovieSceneUserImportFBXControlRigSettings* ImportFBXControlRigSettings, const FFrameNumber StartFrame, const FFrameRate FrameRate, FRigControlFBXNodeAndChannels& NodeAndChannels, UnFbx::FFbxCurvesAPI& CurveAPI) { TArray& DoubleChannels = NodeAndChannels.DoubleChannels; TArray& FloatChannels = NodeAndChannels.FloatChannels; TArray& BoolChannels = NodeAndChannels.BoolChannels; TArray& EnumChannels = NodeAndChannels.EnumChannels; TArray& IntegerChannels = NodeAndChannels.IntegerChannels; // Look for transforms explicitly FRichCurve Transform[9]; FTransform DefaultTransform; const bool bUseSequencerCurve = true; CurveAPI.GetConvertedTransformCurveData(NodeName, Transform[0], Transform[1], Transform[2], Transform[3], Transform[4], Transform[5], Transform[6], Transform[7], Transform[8], DefaultTransform, bUseSequencerCurve, ImportFBXSettings->ImportUniformScale); FVector Location = DefaultTransform.GetLocation(), Rotation = DefaultTransform.GetRotation().Euler(), Scale3D = DefaultTransform.GetScale3D(); const double DefaultTransformValues[] = {Location[0], Location[1], Location[2], Rotation[0], Rotation[1], Rotation[2], Scale3D[0], Scale3D[1], Scale3D[2]}; //For non-transforms we need to re-negate the Y since it happens automatically(todo double check.). //But then if we negate we need to re-re-negate... so leave it alone. if (BoolChannels.Num() == 1) { FControlRigChannelEnum Channel = FControlRigChannelEnum::Bool; SetChannelValue(nullptr, nullptr, BoolChannels[0], nullptr,nullptr, FrameRate, StartFrame, Channel, ImportFBXControlRigSettings, DefaultTransform, Transform[0], Transform[1], Transform[2], Transform[3], Transform[4], Transform[5], Transform[6], Transform[7], Transform[8]); } if (EnumChannels.Num() == 1) { FControlRigChannelEnum Channel = FControlRigChannelEnum::Enum; SetChannelValue(nullptr, nullptr, nullptr, EnumChannels[0], nullptr, FrameRate, StartFrame, Channel, ImportFBXControlRigSettings, DefaultTransform, Transform[0], Transform[1], Transform[2], Transform[3], Transform[4], Transform[5], Transform[6], Transform[7], Transform[8]); } if (IntegerChannels.Num() == 1) { FControlRigChannelEnum Channel = FControlRigChannelEnum::Integer; SetChannelValue(nullptr, nullptr, nullptr, nullptr, IntegerChannels[0], FrameRate, StartFrame, Channel, ImportFBXControlRigSettings,DefaultTransform, Transform[0], Transform[1], Transform[2], Transform[3], Transform[4], Transform[5], Transform[6], Transform[7], Transform[8]); } int ChannelEnumIndex = INDEX_NONE; if (FloatChannels.Num() == 1) { ChannelEnumIndex = (int)FControlRigChannelEnum::Float; } else if (FloatChannels.Num() == 2) { ChannelEnumIndex = (int)FControlRigChannelEnum::Vector2DX; } else if (FloatChannels.Num() == 3) { if (NodeAndChannels.ControlType == FFBXControlRigTypeProxyEnum::Position) { ChannelEnumIndex = (int)FControlRigChannelEnum::PositionX; } else if (NodeAndChannels.ControlType == FFBXControlRigTypeProxyEnum::Rotator) { ChannelEnumIndex = (int)FControlRigChannelEnum::RotatorX; } else if (NodeAndChannels.ControlType == FFBXControlRigTypeProxyEnum::Scale) { ChannelEnumIndex = (int)FControlRigChannelEnum::ScaleX; } } if (ChannelEnumIndex != INDEX_NONE) { for (int i = 0; i < FloatChannels.Num(); i++) { const FControlRigChannelEnum ChannelEnum = (FControlRigChannelEnum)(ChannelEnumIndex + i); SetChannelValue(nullptr, FloatChannels[i], nullptr, nullptr, nullptr, FrameRate, StartFrame, ChannelEnum, ImportFBXControlRigSettings, DefaultTransform, Transform[0], Transform[1], Transform[2], Transform[3], Transform[4], Transform[5], Transform[6], Transform[7], Transform[8]); } } else if (FloatChannels.Num() == 9 || FloatChannels.Num() == 6) { for (int i = 0; i < FloatChannels.Num(); i++) { FloatChannels[i]->SetDefault(DefaultTransformValues[i]); ChannelEnumIndex = (int)FControlRigChannelEnum::PositionX; bool bNegate = false; const FControlRigChannelEnum ChannelEnum = (FControlRigChannelEnum)(ChannelEnumIndex + i); if (const FControlToTransformMappings* ChannelMapping = ImportFBXControlRigSettings->ControlChannelMappings.FindByPredicate( [ChannelEnum](const FControlToTransformMappings& Mapping) { return ChannelEnum == Mapping.ControlChannel; })) { bNegate = ChannelMapping->bNegate; } ImportTransformChannelToFloat(Transform[i], FloatChannels[i], FrameRate, bNegate, false, StartFrame, bNegate); } } ChannelEnumIndex = INDEX_NONE; if (DoubleChannels.Num() == 1) { ChannelEnumIndex = (int)FControlRigChannelEnum::Float; } else if (DoubleChannels.Num() == 2) { ChannelEnumIndex = (int)FControlRigChannelEnum::Vector2DX; } else if (DoubleChannels.Num() == 3) { if (NodeAndChannels.ControlType == FFBXControlRigTypeProxyEnum::Position) { ChannelEnumIndex = (int)FControlRigChannelEnum::PositionX; } else if (NodeAndChannels.ControlType == FFBXControlRigTypeProxyEnum::Rotator) { ChannelEnumIndex = (int)FControlRigChannelEnum::RotatorX; } else if (NodeAndChannels.ControlType == FFBXControlRigTypeProxyEnum::Scale) { ChannelEnumIndex = (int)FControlRigChannelEnum::ScaleX; } } if (ChannelEnumIndex != INDEX_NONE) { for (int i = 0; i < DoubleChannels.Num(); i++) { const FControlRigChannelEnum ChannelEnum = (FControlRigChannelEnum)(ChannelEnumIndex + i); SetChannelValue(DoubleChannels[i], nullptr, nullptr, nullptr, nullptr, FrameRate, StartFrame, ChannelEnum, ImportFBXControlRigSettings, DefaultTransform, Transform[0], Transform[1], Transform[2], Transform[3], Transform[4], Transform[5], Transform[6], Transform[7], Transform[8]); } } else if (DoubleChannels.Num() == 9 || DoubleChannels.Num() == 6) { for (int i = 0; i < DoubleChannels.Num(); i++) { DoubleChannels[i]->SetDefault(DefaultTransformValues[i]); ImportTransformChannelToDouble(Transform[i], DoubleChannels[i], FrameRate, false, false, StartFrame); } } return true; } static FString GetNewString(const FString& InString, UMovieSceneUserImportFBXControlRigSettings* ImportFBXControlRigSettings) { FString NewString = InString; int Index = INDEX_NONE; if (ImportFBXControlRigSettings->bStripNamespace && InString.FindLastChar(':', Index)) { NewString.RightChopInline(Index + 1); } for (const FControlFindReplaceString& FindReplace : ImportFBXControlRigSettings->FindAndReplaceStrings) { NewString.ReplaceInline(*FindReplace.Find, *FindReplace.Replace); //ignores tupe } return NewString; } static void PrepForInsertReplaceAnimation(bool bInsert, const FRigControlFBXNodeAndChannels& NodeAndChannel, FFrameNumber FrameToInsertOrReplace, FFrameNumber StartFrame, FFrameNumber EndFrame) { TArray Channels; for (FMovieSceneDoubleChannel* DChannel : NodeAndChannel.DoubleChannels) { Channels.Add(DChannel); } for (FMovieSceneFloatChannel* FChannel : NodeAndChannel.FloatChannels) { Channels.Add(FChannel); } for (FMovieSceneBoolChannel* BChannel : NodeAndChannel.BoolChannels) { Channels.Add(BChannel); } for (FMovieSceneByteChannel* EChannel : NodeAndChannel.EnumChannels) { Channels.Add(EChannel); } for (FMovieSceneIntegerChannel* IChannel : NodeAndChannel.IntegerChannels) { Channels.Add(IChannel); } FFrameNumber Diff = EndFrame - StartFrame; FrameToInsertOrReplace += StartFrame; if (bInsert) { for (FMovieSceneChannel* Channel : Channels) { TArray KeyTimes; TArray Handles; Channel->GetKeys(TRange(), &KeyTimes, &Handles); for (int32 Index = 0; Index < KeyTimes.Num(); Index++) { FFrameNumber FrameNumber = KeyTimes[Index]; if (FrameNumber >= FrameToInsertOrReplace) { FrameNumber += Diff; KeyTimes[Index] += Diff; } } Channel->SetKeyTimes(Handles, KeyTimes); } } else //we replace the animation by first deleting keys in the interval { for (FMovieSceneChannel* Channel : Channels) { TArray KeyTimes; TArray Handles; Channel->GetKeys(TRange(), &KeyTimes, &Handles); TArray HandlesToDelete; for (int32 Index = 0; Index < KeyTimes.Num(); Index++) { FFrameNumber FrameNumber = KeyTimes[Index]; if (FrameNumber >= FrameToInsertOrReplace && FrameNumber <= (FrameToInsertOrReplace + EndFrame)) { HandlesToDelete.Add(Handles[Index]); } } Channel->DeleteKeys(HandlesToDelete); } } } class SControlRigImportFBXSettings : public SCompoundWidget { SLATE_BEGIN_ARGS(SControlRigImportFBXSettings) {} SLATE_ARGUMENT(FString, ImportFilename) SLATE_END_ARGS() ~SControlRigImportFBXSettings() { if (NodeAndChannels != nullptr) { delete NodeAndChannels; } } void Construct(const FArguments& InArgs, const TSharedRef& InSequencer) { FPropertyEditorModule& PropertyEditor = FModuleManager::LoadModuleChecked("PropertyEditor"); FDetailsViewArgs DetailsViewArgs; DetailsViewArgs.bShowOptions = false; DetailsViewArgs.bAllowSearch = false; DetailsViewArgs.bShowPropertyMatrixButton = false; DetailsViewArgs.bUpdatesFromSelection = false; DetailsViewArgs.bLockable = false; DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea; DetailsViewArgs.ViewIdentifier = "Import FBX Settings"; DetailView = PropertyEditor.CreateDetailView(DetailsViewArgs); Sequencer = InSequencer; DetailView->RegisterInstancedCustomPropertyTypeLayout("FrameNumber", FOnGetPropertyTypeCustomizationInstance::CreateSP(InSequencer, &ISequencer::MakeFrameNumberDetailsCustomization)); ChildSlot [ SNew(SVerticalBox) + SVerticalBox::Slot() [ DetailView.ToSharedRef() ] + SVerticalBox::Slot() .AutoHeight() [ SNew(SComboButton) .HasDownArrow(true) .OnGetMenuContent(this, &SControlRigImportFBXSettings::HandlePresetMenuContent) .ButtonContent() [ SNew(STextBlock) .Text(NSLOCTEXT("MovieSceneTools", "ControlMappingPresets", "Control Mapping Presets")) .ToolTipText(NSLOCTEXT("MovieSceneTools", "SetControlMappingFromAPreset", "Set Control Mappings From A Preset")) ] ] + SVerticalBox::Slot() .AutoHeight() .HAlign(HAlign_Right) .Padding(5.f) [ SNew(SButton) .ContentPadding(FMargin(10, 5)) .Text(NSLOCTEXT("MovieSceneTools", "ImportFBXButtonText", "Import")) .OnClicked(this, &SControlRigImportFBXSettings::OnImportFBXClicked) ] ]; ImportFilename = InArgs._ImportFilename; NodeAndChannels = nullptr; UMovieSceneUserImportFBXControlRigSettings* ImportFBXSettings = GetMutableDefault(); DetailView->SetObject(ImportFBXSettings); } TSharedRef HandlePresetMenuContent() { FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, nullptr); MenuBuilder.AddMenuEntry( NSLOCTEXT("MovieSceneTools", "DefaultControlMappings", "Default Control Mappings"), NSLOCTEXT("MovieSceneTools", "DefaultControlMappings_Tooltip", "Use Default Control Mappings Preset"), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &SControlRigImportFBXSettings::SetPresets,false) ), NAME_None, EUserInterfaceActionType::Button ); MenuBuilder.AddMenuEntry( NSLOCTEXT("MovieSceneTools", "MetaHumanControlMappings", "MetaHuman Control Mappings"), NSLOCTEXT("MovieSceneTools", "MetaHumanControlMappings_Tooltip", "Use MetaHuman Control Mappings Preset"), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &SControlRigImportFBXSettings::SetPresets,true) ), NAME_None, EUserInterfaceActionType::Button ); return MenuBuilder.MakeWidget(); } void SetNodeNames(const TArray& NodeNames) { UMovieSceneUserImportFBXControlRigSettings* ImportFBXSettings = GetMutableDefault(); if (ImportFBXSettings) { ImportFBXSettings->ImportedNodeNames = NodeNames; } } void SetFrameRate(const FString& InFrameRate) { UMovieSceneUserImportFBXControlRigSettings* ImportFBXSettings = GetMutableDefault(); if (ImportFBXSettings) { ImportFBXSettings->ImportedFrameRate = InFrameRate; } } void SetStartTime(FFrameNumber StartTime) { UMovieSceneUserImportFBXControlRigSettings* ImportFBXSettings = GetMutableDefault(); if (ImportFBXSettings) { ImportFBXSettings->ImportedStartTime = StartTime; ImportFBXSettings->StartTimeRange = StartTime; } } void SetEndTime(FFrameNumber EndTime) { UMovieSceneUserImportFBXControlRigSettings* ImportFBXSettings = GetMutableDefault(); if (ImportFBXSettings) { ImportFBXSettings->ImportedEndTime = EndTime; ImportFBXSettings->EndTimeRange = EndTime; } } void SetFileName(const FString& FileName) { UMovieSceneUserImportFBXControlRigSettings* ImportFBXSettings = GetMutableDefault(); if (ImportFBXSettings) { ImportFBXSettings->ImportedFileName = FileName; } } void SetNodeAndChannels(TArray* InNodeAndChannels) { NodeAndChannels = InNodeAndChannels; } private: FReply OnImportFBXClicked() { if (!Sequencer.IsValid() || !NodeAndChannels) { return FReply::Unhandled(); } UMovieSceneUserImportFBXControlRigSettings* ImportFBXControlRigSettings = GetMutableDefault(); TArray SelectedControlNames; for (const FRigControlFBXNodeAndChannels& NodeAndChannel : *NodeAndChannels) { if (NodeAndChannel.MovieSceneTrack) { INodeAndChannelMappings* ChannelMapping = Cast(NodeAndChannel.MovieSceneTrack); if (ChannelMapping) { TArray LocalControls; ChannelMapping->GetSelectedNodes(LocalControls); SelectedControlNames.Append(LocalControls); } } } const bool bValid = MovieSceneToolHelpers::ImportFBXIntoControlRigChannels( Sequencer.Pin()->GetFocusedMovieSceneSequence()->GetMovieScene(), ImportFilename, ImportFBXControlRigSettings, NodeAndChannels,SelectedControlNames, Sequencer.Pin()->GetFocusedTickResolution()); const TSharedPtr Window = FSlateApplication::Get().FindWidgetWindow(AsShared()); if (Window.IsValid()) { Window->RequestDestroyWindow(); } if (bValid) { Sequencer.Pin()->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded); } return bValid ? FReply::Handled() : FReply::Unhandled(); } void SetPresets(const bool bMetaHuman) const { UMovieSceneUserImportFBXControlRigSettings* ImportFBXControlRigSettings = GetMutableDefault(); ImportFBXControlRigSettings->LoadControlMappingsFromPreset(bMetaHuman); } TSharedPtr DetailView; FString ImportFilename; TArray* NodeAndChannels = nullptr; TWeakPtr Sequencer; }; class SControlRigExportFBXSettings : public SCompoundWidget { SLATE_BEGIN_ARGS(SControlRigExportFBXSettings) {} SLATE_ARGUMENT(FString, ExportFilename) SLATE_END_ARGS() void Construct(const FArguments& InArgs, const TSharedRef& InSequencer, TWeakObjectPtr InTrack) { Sequencer = InSequencer; Track = InTrack; UMovieSceneUserExportFBXControlRigSettings* ExportFBXSettings = GetMutableDefault(); ExportFBXSettings->ExportFileName = InArgs._ExportFilename; FPropertyEditorModule& PropertyEditor = FModuleManager::LoadModuleChecked("PropertyEditor"); FDetailsViewArgs DetailsViewArgs; DetailsViewArgs.bShowOptions = false; DetailsViewArgs.bAllowSearch = false; DetailsViewArgs.bShowPropertyMatrixButton = false; DetailsViewArgs.bUpdatesFromSelection = false; DetailsViewArgs.bLockable = false; DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea; DetailsViewArgs.ViewIdentifier = "Export FBX Settings"; DetailView = PropertyEditor.CreateDetailView(DetailsViewArgs); DetailView->SetObject(ExportFBXSettings); ChildSlot [ SNew(SVerticalBox) + SVerticalBox::Slot() [ DetailView.ToSharedRef() ] + SVerticalBox::Slot() .AutoHeight() [ SNew(SComboButton) .HasDownArrow(true) .OnGetMenuContent(this, &SControlRigExportFBXSettings::HandlePresetMenuContent) .ButtonContent() [ SNew(STextBlock) .Text(NSLOCTEXT("MovieSceneTools", "ControlMappingPresets", "Control Mapping Presets")) .ToolTipText(NSLOCTEXT("MovieSceneTools", "SetControlMappingFromAPreset", "Set Control Mappings From A Preset")) ] ] + SVerticalBox::Slot() .AutoHeight() .HAlign(HAlign_Right) .Padding(5.f) [ SNew(SButton) .ContentPadding(FMargin(10, 5)) .Text(NSLOCTEXT("MovieSceneTools", "ExportFBXButtonText", "Export")) .OnClicked(this, &SControlRigExportFBXSettings::OnExportFBXClicked) ] ]; } TSharedRef HandlePresetMenuContent() { FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, nullptr); MenuBuilder.AddMenuEntry( NSLOCTEXT("MovieSceneTools", "EmptyControlMappings", "Empty Control Mappings"), NSLOCTEXT("MovieSceneTools", "EmptyControlMappings_Tooltip", "Do not use Control Mappings. Bool, Enum, Int and Float channels will be exported as attributes of the corresponding data type"), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &SControlRigExportFBXSettings::ClearPresets) ), NAME_None, EUserInterfaceActionType::Button ); MenuBuilder.AddMenuEntry( NSLOCTEXT("MovieSceneTools", "DefaultControlMappings", "Default Control Mappings"), NSLOCTEXT("MovieSceneTools", "DefaultControlMappings_Tooltip", "Use Default Control Mappings Preset"), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &SControlRigExportFBXSettings::SetPresets, false) ), NAME_None, EUserInterfaceActionType::Button ); MenuBuilder.AddMenuEntry( NSLOCTEXT("MovieSceneTools", "MetaHumanControlMappings", "MetaHuman Control Mappings"), NSLOCTEXT("MovieSceneTools", "MetaHumanControlMappings_Tooltip", "Use MetaHuman Control Mappings Preset"), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &SControlRigExportFBXSettings::SetPresets, true) ), NAME_None, EUserInterfaceActionType::Button ); return MenuBuilder.MakeWidget(); } private: FReply OnExportFBXClicked() { if (!Sequencer.IsValid() || !Track.IsValid()) { return FReply::Unhandled(); } UMovieSceneUserExportFBXControlRigSettings* ExportFBXControlRigSettings = GetMutableDefault(); TArray SelectedControlNames; if (Track.IsValid()) { if (INodeAndChannelMappings* ChannelMapping = Cast(Track.Get())) { ChannelMapping->GetSelectedNodes(SelectedControlNames); } } FMovieSceneSequenceTransform RootToLocalTransform; if (Sequencer.IsValid()) { RootToLocalTransform = Sequencer.Pin()->GetFocusedMovieSceneSequenceTransform(); } const bool bValid = MovieSceneToolHelpers::ExportFBXFromControlRigChannels( Track.Get()->GetSectionToKey(), ExportFBXControlRigSettings, SelectedControlNames, RootToLocalTransform); if (bValid) { FNotificationInfo Info(NSLOCTEXT("MovieSceneTools", "ExportControlRigFBXSucceeded", "FBX Export Succeeded.")); Info.Hyperlink = FSimpleDelegate::CreateStatic([](FString InFilename) { FPlatformProcess::ExploreFolder(*InFilename); }, ExportFBXControlRigSettings->ExportFileName); Info.HyperlinkText = FText::FromString(ExportFBXControlRigSettings->ExportFileName); Info.ExpireDuration = 5.0f; FSlateNotificationManager::Get().AddNotification(Info)->SetCompletionState(SNotificationItem::CS_Success); } else { FNotificationInfo Info(NSLOCTEXT("MovieSceneTools", "ExportControlRigFBXFailed", "FBX Export Failed.")); Info.ExpireDuration = 5.0f; FSlateNotificationManager::Get().AddNotification(Info)->SetCompletionState(SNotificationItem::CS_Fail); } const TSharedPtr Window = FSlateApplication::Get().FindWidgetWindow(AsShared()); if (Window.IsValid()) { Window->RequestDestroyWindow(); } return bValid ? FReply::Handled() : FReply::Unhandled(); } void SetPresets(const bool bMetaHuman) const { UMovieSceneUserExportFBXControlRigSettings* ExportFBXControlRigSettings = GetMutableDefault(); ExportFBXControlRigSettings->LoadControlMappingsFromPreset(bMetaHuman); } void ClearPresets() const { UMovieSceneUserExportFBXControlRigSettings* ExportFBXControlRigSettings = GetMutableDefault(); ExportFBXControlRigSettings->ControlChannelMappings.Empty(); } TSharedPtr DetailView; TWeakPtr Sequencer; TWeakObjectPtr Track; }; bool MovieSceneToolHelpers::ImportFBXIntoControlRigChannels(UMovieScene* MovieScene,const FString& ImportFilename, UMovieSceneUserImportFBXControlRigSettings* ImportFBXControlRigSettings, TArray* NodeAndChannels, const TArray& SelectedControlNames, FFrameRate FrameRate) { UnFbx::FFbxImporter* FbxImporter = UnFbx::FFbxImporter::GetInstance(); bool bValid = true; UnFbx::FBXImportOptions* ImportOptions = FbxImporter->GetImportOptions(); bool bOldbConvertScene = ImportOptions->bConvertScene; bool bOldbConvertSceneUnit = ImportOptions->bConvertSceneUnit; bool bOldbForceFrontXAxis = ImportOptions->bForceFrontXAxis; float OldUniformScale = ImportOptions->ImportUniformScale; EFBXAnimationLengthImportType OldAnimLengthType = ImportOptions->AnimationLengthImportType; ImportOptions->bConvertScene = true; ImportOptions->bConvertSceneUnit = ImportFBXControlRigSettings->bConvertSceneUnit; ImportOptions->bForceFrontXAxis = ImportFBXControlRigSettings->bForceFrontXAxis; ImportOptions->ImportUniformScale = ImportFBXControlRigSettings->ImportUniformScale; ImportOptions->AnimationLengthImportType = FBXALIT_ExportedTime; const FString FileExtension = FPaths::GetExtension(ImportFilename); if (!FbxImporter->ImportFromFile(*ImportFilename, FileExtension, true)) { // Log the error message and fail the import. FbxImporter->ReleaseScene(); bValid = false; } else { const FScopedTransaction Transaction(NSLOCTEXT("MovieSceneTools", "ImportFBXControlRigTransaction", "Import FBX Onto Control Rig")); UMovieSceneUserImportFBXSettings* CurrentImportFBXSettings = GetMutableDefault(); TArray OriginalSettings; FObjectWriter ObjWriter(CurrentImportFBXSettings, OriginalSettings); CurrentImportFBXSettings->bMatchByNameOnly = false; CurrentImportFBXSettings->bConvertSceneUnit = ImportFBXControlRigSettings->bConvertSceneUnit; CurrentImportFBXSettings->bForceFrontXAxis = ImportFBXControlRigSettings->bForceFrontXAxis; CurrentImportFBXSettings->ImportUniformScale = ImportFBXControlRigSettings->ImportUniformScale; CurrentImportFBXSettings->bCreateCameras = false; CurrentImportFBXSettings->bReduceKeys = false; CurrentImportFBXSettings->ReduceKeysTolerance = 0.01f; UnFbx::FFbxCurvesAPI CurveAPI; FbxImporter->PopulateAnimatedCurveData(CurveAPI); TArray AllNodeNames; CurveAPI.GetAllNodeNameArray(AllNodeNames); //if matching selected remove out the non-selected if (ImportFBXControlRigSettings->bImportOntoSelectedControls) { NodeAndChannels->RemoveAll([SelectedControlNames](const FRigControlFBXNodeAndChannels& A) { return !SelectedControlNames.Contains(A.ControlName); } ); } FFrameNumber FrameToInsertOrReplace = ImportFBXControlRigSettings->TimeToInsertOrReplaceAnimation; FFrameNumber StartFrame = ImportFBXControlRigSettings->StartTimeRange; FFrameNumber EndFrame = ImportFBXControlRigSettings->EndTimeRange; // We'll be looking for timecode properties on all of the nodes in the FBX being // considered for import, and the first one found will be used to populate the // timecode source of all modified movie scene sections. FName TCHourAttrName(TEXT("TCHour")); FName TCMinuteAttrName(TEXT("TCMinute")); FName TCSecondAttrName(TEXT("TCSecond")); FName TCFrameAttrName(TEXT("TCFrame")); if (const UAnimationSettings* AnimationSettings = UAnimationSettings::Get()) { TCHourAttrName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.HourAttributeName; TCMinuteAttrName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.MinuteAttributeName; TCSecondAttrName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.SecondAttributeName; TCFrameAttrName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.FrameAttributeName; } const TArray TimecodePropertyNames = { TCHourAttrName.ToString(), TCMinuteAttrName.ToString(), TCSecondAttrName.ToString(), TCFrameAttrName.ToString() }; FTimecode Timecode; bool bFoundTimecode = false; TSet AllModifiedSections; for (const FString& NodeName : AllNodeNames) { const FString NewNodeName = GetNewString(*NodeName, ImportFBXControlRigSettings); TSet ModifiedSections; for (FRigControlFBXNodeAndChannels& NodeAndChannel : *NodeAndChannels) { if (NodeAndChannel.NodeName.Equals(NewNodeName, ESearchCase::IgnoreCase)) { if (NodeAndChannel.MovieSceneTrack) { UMovieSceneSection* SectionToModify = NodeAndChannel.MovieSceneTrack->GetSectionToKey(); if (!SectionToModify && NodeAndChannel.MovieSceneTrack->GetAllSections().Num() > 0) { SectionToModify = NodeAndChannel.MovieSceneTrack->GetAllSections()[0]; } if (SectionToModify && !ModifiedSections.Contains(SectionToModify)) { SectionToModify->SetFlags(RF_Transactional); SectionToModify->Modify(); ModifiedSections.Add(SectionToModify); AllModifiedSections.Add(SectionToModify); } } PrepForInsertReplaceAnimation(ImportFBXControlRigSettings->bInsertAnimation, NodeAndChannel, FrameToInsertOrReplace, StartFrame, EndFrame); FFBXControlRigTypeProxyEnum ControlType = NodeAndChannel.ControlType; bool bTryImportNonTransformCurves = ControlType == FFBXControlRigTypeProxyEnum::Bool || ControlType == FFBXControlRigTypeProxyEnum::Float || ControlType == FFBXControlRigTypeProxyEnum::Integer; // If the node channels could not be imported from custom FBX attribute curves, try importing it them from the FBX transform channels, using the corresponding mapping if (!bTryImportNonTransformCurves || !ImportFBXNonTransformCurvesToChannels( NodeName, CurrentImportFBXSettings, ImportFBXControlRigSettings, FrameToInsertOrReplace, FrameRate, NodeAndChannel, CurveAPI)) { ImportFBXTransformToChannels(NodeName, CurrentImportFBXSettings, ImportFBXControlRigSettings, FrameToInsertOrReplace, FrameRate, NodeAndChannel, CurveAPI); } } } // We consider *all* nodes in the FBX when looking for authored timecode properties, // not just the ones that map to a particular node with channels. if (!bFoundTimecode) { TArray AnimatedPropertyNames; CurveAPI.GetNodeAnimatedPropertyNameArray(NodeName, AnimatedPropertyNames); for (const FString& AnimatedPropertyName : AnimatedPropertyNames) { if (!TimecodePropertyNames.Contains(AnimatedPropertyName)) { continue; } bFoundTimecode = true; const int32 ChannelIndex = 0; const int32 CompositeIndex = 0; FRichCurve PropertyCurve; const bool bNegative = false; CurveAPI.GetCurveDataForSequencer(NodeName, AnimatedPropertyName, ChannelIndex, CompositeIndex, PropertyCurve, bNegative); const float EvalTime = 0.0f; const float DefaultValue = 0.0f; const float Value = PropertyCurve.Eval(EvalTime, DefaultValue); const int32 IntValue = FMath::TruncToInt(Value); const FName AnimatedPropertyNameAsFName(AnimatedPropertyName); if (AnimatedPropertyNameAsFName.IsEqual(TCHourAttrName)) { Timecode.Hours = IntValue; } else if (AnimatedPropertyNameAsFName.IsEqual(TCMinuteAttrName)) { Timecode.Minutes = IntValue; } else if (AnimatedPropertyNameAsFName.IsEqual(TCSecondAttrName)) { Timecode.Seconds = IntValue; } else if (AnimatedPropertyNameAsFName.IsEqual(TCFrameAttrName)) { Timecode.Frames = IntValue; } } } } if (bFoundTimecode) { for (UMovieSceneSection* Section : AllModifiedSections) { Section->TimecodeSource = FMovieSceneTimecodeSource(Timecode); } } // restore FObjectReader ObjReader(GetMutableDefault(), OriginalSettings); FbxImporter->ReleaseScene(); } ImportOptions->AnimationLengthImportType = OldAnimLengthType; ImportOptions->bConvertScene = bOldbConvertScene; ImportOptions->bConvertSceneUnit = bOldbConvertSceneUnit; ImportOptions->bForceFrontXAxis = bOldbForceFrontXAxis; ImportOptions->ImportUniformScale = OldUniformScale;; return bValid; } bool MovieSceneToolHelpers::ExportFBXFromControlRigChannels(const UMovieSceneSection* Section, const UMovieSceneUserExportFBXControlRigSettings* ExportFBXControlRigSettings, const TArray& SelectedControlNames, const FMovieSceneSequenceTransform& RootToLocalTransform) { if (!Section) { return false; } UnFbx::FFbxExporter* Exporter = UnFbx::FFbxExporter::GetInstance(); UFbxExportOption* ExportOptions = Exporter->GetExportOptions(); ExportOptions->FbxExportCompatibility = ExportFBXControlRigSettings->FbxExportCompatibility; ExportOptions->bASCII = ExportFBXControlRigSettings->bASCII; ExportOptions->bForceFrontXAxis = ExportFBXControlRigSettings->bForceFrontXAxis; ExportOptions->bExportLocalTime = ExportFBXControlRigSettings->bExportLocalTime; Exporter->CreateDocument(); Exporter->SetTransformBaking(true); Exporter->SetKeepHierarchy(true); TArray ChannelsMapping; // Map to make it easier to convert from Control Rig Channel Enum to Control Rig Type Proxy + Transform Channel Index const TMap> ProxyMapping = { { FControlRigChannelEnum::Vector2DX, { FFBXControlRigTypeProxyEnum::Vector2D, 0 } }, { FControlRigChannelEnum::Vector2DY, { FFBXControlRigTypeProxyEnum::Vector2D, 1 } }, { FControlRigChannelEnum::PositionX, { FFBXControlRigTypeProxyEnum::Position, 0 } }, { FControlRigChannelEnum::PositionY, { FFBXControlRigTypeProxyEnum::Position, 1 } }, { FControlRigChannelEnum::PositionZ, { FFBXControlRigTypeProxyEnum::Position, 2 } }, { FControlRigChannelEnum::RotatorX, { FFBXControlRigTypeProxyEnum::Rotator, 0 } }, { FControlRigChannelEnum::RotatorY, { FFBXControlRigTypeProxyEnum::Rotator, 1 } }, { FControlRigChannelEnum::RotatorZ, { FFBXControlRigTypeProxyEnum::Rotator, 2 } }, { FControlRigChannelEnum::ScaleX, { FFBXControlRigTypeProxyEnum::Scale, 0 } }, { FControlRigChannelEnum::ScaleY, { FFBXControlRigTypeProxyEnum::Scale, 1 } }, { FControlRigChannelEnum::ScaleZ, { FFBXControlRigTypeProxyEnum::Scale, 2 } } }; for (const FControlToTransformMappings& ControlMapping : ExportFBXControlRigSettings->ControlChannelMappings) { FControlRigFbxNodeMapping ChannelMapping; if (ControlMapping.ControlChannel == FControlRigChannelEnum::Bool) { ChannelMapping.ChannelType = FMovieSceneBoolChannel::StaticStruct()->GetFName(); ChannelMapping.ControlType = FFBXControlRigTypeProxyEnum::Bool; } else if (ControlMapping.ControlChannel == FControlRigChannelEnum::Enum) { ChannelMapping.ChannelType = FMovieSceneByteChannel::StaticStruct()->GetFName(); ChannelMapping.ControlType = FFBXControlRigTypeProxyEnum::Integer; } else if (ControlMapping.ControlChannel == FControlRigChannelEnum::Integer) { ChannelMapping.ChannelType = FMovieSceneIntegerChannel::StaticStruct()->GetFName(); ChannelMapping.ControlType = FFBXControlRigTypeProxyEnum::Integer; } else { ChannelMapping.ChannelType = FMovieSceneFloatChannel::StaticStruct()->GetFName(); // Convert Control Channel Type to Control Type and Transform Component Index (0-2) if (const TPair* Mapping = ProxyMapping.Find(ControlMapping.ControlChannel)) { ChannelMapping.ControlType = Mapping->Key; ChannelMapping.ChannelAttrIndex = Mapping->Value; } else { ChannelMapping.ControlType = FFBXControlRigTypeProxyEnum::Float; } } // Convert FBXChannel Type to Transform Component Index (0-8) ChannelMapping.FbxAttrIndex = (uint8)ControlMapping.FBXChannel; ChannelMapping.bNegate = ControlMapping.bNegate; ChannelsMapping.Add(ChannelMapping); } // Filter only selected controls if required TArray ControlsFilter; if (ExportFBXControlRigSettings->bExportOnlySelectedControls) { ControlsFilter = SelectedControlNames; } Exporter->ExportControlRigSection(Section, ChannelsMapping, ControlsFilter, RootToLocalTransform); // Save to disk Exporter->WriteToFile(*ExportFBXControlRigSettings->ExportFileName); return true; } bool MovieSceneToolHelpers::ImportFBXIntoControlRigChannelsWithDialog(const TSharedRef& InSequencer,TArray* NodeAndChannels) { TArray OpenFilenames; IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); bool bOpen = false; if (DesktopPlatform) { FString ExtensionStr; ExtensionStr += TEXT("FBX (*.fbx)|*.fbx|"); bOpen = DesktopPlatform->OpenFileDialog( FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr), NSLOCTEXT("MovieSceneTools", "ImportFBX", "Import FBX from...").ToString(), FEditorDirectories::Get().GetLastDirectory(ELastDirectory::FBX), TEXT(""), *ExtensionStr, EFileDialogFlags::None, OpenFilenames ); } if (!bOpen) { return false; } if (!OpenFilenames.Num()) { return false; } const FText TitleText = NSLOCTEXT("MovieSceneTools", "ImportFBXTitleOnToControlRig", "Import FBX Onto Control Rig"); // Create the window to choose our options TSharedRef Window = SNew(SWindow) .Title(TitleText) .HasCloseButton(true) .SizingRule(ESizingRule::UserSized) .ClientSize(FVector2D(500.f, 700.f)) .AutoCenter(EAutoCenter::PreferredWorkArea) .SupportsMinimize(false); TSharedRef DialogWidget = SNew(SControlRigImportFBXSettings, InSequencer) .ImportFilename(OpenFilenames[0]); UnFbx::FFbxImporter* FbxImporter = UnFbx::FFbxImporter::GetInstance(); UnFbx::FBXImportOptions* ImportOptions = FbxImporter->GetImportOptions(); EFBXAnimationLengthImportType AnimLengthType = ImportOptions->AnimationLengthImportType; ImportOptions->AnimationLengthImportType = FBXALIT_ExportedTime; const FString FileExtension = FPaths::GetExtension(OpenFilenames[0]); if (!FbxImporter->ImportFromFile(*OpenFilenames[0], FileExtension, true)) { ImportOptions->AnimationLengthImportType = AnimLengthType; if (NodeAndChannels) { delete NodeAndChannels; } FbxImporter->ReleaseScene(); return false; } UnFbx::FFbxCurvesAPI CurveAPI; FbxImporter->PopulateAnimatedCurveData(CurveAPI); TArray AllNodeNames; CurveAPI.GetAllNodeNameArray(AllNodeNames); FbxAnimStack* AnimStack = FbxImporter->Scene->GetMember(0); FbxTimeSpan TimeSpan = FbxImporter->GetAnimationTimeSpan(FbxImporter->Scene->GetRootNode(), AnimStack); ImportOptions->AnimationLengthImportType = AnimLengthType; FbxImporter->ReleaseScene(); DialogWidget->SetFileName(OpenFilenames[0]); FString FrameRateStr = FString::Printf(TEXT("%.2f"), FbxImporter->GetOriginalFbxFramerate()); DialogWidget->SetFrameRate(FrameRateStr); FFrameRate FrameRate = InSequencer->GetFocusedTickResolution(); FFrameNumber StartTime = FrameRate.AsFrameNumber(TimeSpan.GetStart().GetSecondDouble()); FFrameNumber EndTime = FrameRate.AsFrameNumber(TimeSpan.GetStop().GetSecondDouble()); DialogWidget->SetStartTime(StartTime); DialogWidget->SetEndTime(EndTime); DialogWidget->SetNodeNames(AllNodeNames); DialogWidget->SetNodeAndChannels(NodeAndChannels); Window->SetContent(DialogWidget); FSlateApplication::Get().AddWindow(Window); return true; } bool MovieSceneToolHelpers::ExportFBXFromControlRigChannelsWithDialog(const TSharedRef& InSequencer, UMovieSceneTrack* Track) { FString ExportFilename; if (IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get()) { TArray SavedFiles; const bool bFilePicked = DesktopPlatform->SaveFileDialog( FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr), NSLOCTEXT("MovieSceneTools", "ExportControlRigFBX", "Export Control Rig FBX" ).ToString(), *(FEditorDirectories::Get().GetLastDirectory(ELastDirectory::FBX)), TEXT(""), TEXT("FBX document|*.fbx"), EFileDialogFlags::None, SavedFiles); if (!bFilePicked || SavedFiles.IsEmpty()) { return false; } ExportFilename = SavedFiles[0]; FEditorDirectories::Get().SetLastDirectory(ELastDirectory::FBX, FPaths::GetPath(ExportFilename)); } const FText TitleText = NSLOCTEXT("MovieSceneTools", "ExportFBXFromControlRigTitle", "Export FBX from Control Rig"); // Create the window to choose our options const TSharedRef Window = SNew(SWindow) .Title(TitleText) .HasCloseButton(true) .SizingRule(ESizingRule::UserSized) .ClientSize(FVector2D(450.0f, 300.0f)) .AutoCenter(EAutoCenter::PreferredWorkArea) .SupportsMinimize(false); const TSharedRef DialogWidget = SNew(SControlRigExportFBXSettings, InSequencer, Track) .ExportFilename(ExportFilename); Window->SetContent(DialogWidget); FSlateApplication::Get().AddWindow(Window); return true; } double UnwindKeyValue(const double OldValue, double NewValue) { while (NewValue - OldValue > 180.0f) { NewValue -= 360.0f; } while (NewValue - OldValue < -180.0f) { NewValue += 360.0f; } return NewValue; } void AddCorrectedKey(const FMovieSceneDoubleValue& CorrectedValue, const FFrameTime Time, TMovieSceneChannelData ChannelData) { const int32 KeyIndex = ChannelData.FindKey(Time.RoundToFrame()); if (KeyIndex != INDEX_NONE) { FMovieSceneDoubleValue& KeyValue = ChannelData.GetValues()[KeyIndex]; KeyValue.Value = CorrectedValue.Value; // Tangents need to be set to auto, so they can be corrected later, as their imported values may no longer be valid after correcting the transform if (KeyValue.TangentMode != RCTM_Auto && KeyValue.TangentMode != RCTM_SmartAuto) { KeyValue.TangentMode = RCTM_Auto; } } else { MovieSceneToolHelpers::SetOrAddKey(ChannelData, Time.RoundToFrame(), CorrectedValue); } } void CorrectForTransformOrigin(IMovieScenePlayer* Player, UMovieScene3DTransformSection* TransformSection, ISequencer* Sequencer) { FMovieSceneChannelProxy& SectionChannelProxy = TransformSection->GetChannelProxy(); TMovieSceneChannelHandle DoubleChannels[] = { SectionChannelProxy.GetChannelByName("Location.X"), SectionChannelProxy.GetChannelByName("Location.Y"), SectionChannelProxy.GetChannelByName("Location.Z"), SectionChannelProxy.GetChannelByName("Rotation.X"), SectionChannelProxy.GetChannelByName("Rotation.Y"), SectionChannelProxy.GetChannelByName("Rotation.Z") }; FFrameRate FrameRate = TransformSection->GetTypedOuter()->GetTickResolution(); // If importing from the Sequencer editor, retrieve the focused subsequence hierarchy, in order to get the subsequence transform origin overrides. // If not importing from the Sequencer editor, assume the transform is being imported onto the root sequence. TArray SubsequenceHierarchy = Sequencer ? Sequencer->GetSubSequenceHierarchy() : TArray { MovieSceneSequenceID::Root }; UE::MovieScene::FSystemInterrogator Interrogator; const FMovieSceneRootEvaluationTemplateInstance& RootInstance = Player->GetEvaluationTemplate(); const FMovieSceneSequenceHierarchy* Hierarchy = RootInstance.GetCompiledDataManager()->FindHierarchy(RootInstance.GetCompiledDataID()); // Sub tracks can only be interrogated if the Sequencer and Hierarchy are valid (i.e. the import is triggered from the editor), // but the instance data can still be interrogated if they aren't, so this step can be safely skipped when importing from scripting. if (Sequencer && Hierarchy) { Interrogator.SetHierarchy(const_cast(Hierarchy)); FMovieSceneSequenceID OwningSequenceID = MovieSceneSequenceID::Root; for (const FMovieSceneSequenceID SubSequenceID : SubsequenceHierarchy) { if (const UMovieSceneSubSection* SubSection = Sequencer->FindSubSection(SubSequenceID)) { if (UMovieSceneTrack* SubTrack = Cast(SubSection->GetOuter())) { Interrogator.ImportTrack(SubTrack, UE::MovieScene::FInterrogationChannel::FromIndex(SubSection->GetRowIndex()), OwningSequenceID); } } OwningSequenceID = SubSequenceID; } } TSet Times; for (int32 ChannelIndex = 0; ChannelIndex < 6; ++ChannelIndex) { TMovieSceneChannelHandle DoubleChannel = DoubleChannels[ChannelIndex]; for (FFrameNumber Time : DoubleChannel.Get()->GetTimes()) { Times.Add(Time); } } TArray TimesElements = Times.Array(); TimesElements.Sort(); for (FFrameNumber Time : TimesElements) { Interrogator.AddInterrogation(Time); } const IMovieScenePlaybackClient* Client = Player->GetPlaybackClient(); const UObject* InstanceData = Client ? Client->GetInstanceData() : nullptr; TArray TransformOrigins; TArray CorrectedTransforms; Interrogator.Update(); Interrogator.QueryTransformOrigins(TransformOrigins, SubsequenceHierarchy, InstanceData); // Gather the corrected transforms for (int32 TransformOriginIndex = 0; TransformOriginIndex < TransformOrigins.Num(); ++TransformOriginIndex) { FFrameTime Time = TimesElements[TransformOriginIndex]; FTransform TransformOrigin = TransformOrigins[TransformOriginIndex]; double LocationX = 0.0; double LocationY = 0.0; double LocationZ = 0.0; double RotationX = 0.0; double RotationY = 0.0; double RotationZ = 0.0; if (DoubleChannels[0].Get()) { DoubleChannels[0].Get()->Evaluate(Time, LocationX); } if (DoubleChannels[1].Get()) { DoubleChannels[1].Get()->Evaluate(Time, LocationY); } if (DoubleChannels[2].Get()) { DoubleChannels[2].Get()->Evaluate(Time, LocationZ); } if (DoubleChannels[3].Get()) { DoubleChannels[3].Get()->Evaluate(Time, RotationX); } if (DoubleChannels[4].Get()) { DoubleChannels[4].Get()->Evaluate(Time, RotationY); } if (DoubleChannels[5].Get()) { DoubleChannels[5].Get()->Evaluate(Time, RotationZ); } FVector EvaluatedLocation = FVector(LocationX, LocationY, LocationZ); FRotator EvaluatedRotation = FRotator(RotationY, RotationZ, RotationX); FTransform EvaluatedTransform = FTransform(EvaluatedRotation, EvaluatedLocation); EvaluatedTransform *= TransformOrigin.Inverse(); CorrectedTransforms.Add(EvaluatedTransform); } TOptional PreviousRotationX; TOptional PreviousRotationY; TOptional PreviousRotationZ; // Apply the correct transforms. This has to be done after gathering, so the corrected transforms don't interfere with evaluation. for (int32 CorrectedTransformIndex = 0; CorrectedTransformIndex < CorrectedTransforms.Num(); ++CorrectedTransformIndex) { FTransform CorrectedTransform = CorrectedTransforms[CorrectedTransformIndex]; FFrameTime Time = TimesElements[CorrectedTransformIndex]; if (DoubleChannels[0].Get()) { TMovieSceneChannelData ChannelData = DoubleChannels[0].Get()->GetData(); AddCorrectedKey(FMovieSceneDoubleValue(CorrectedTransform.GetTranslation().X), Time, ChannelData); } if (DoubleChannels[1].Get()) { TMovieSceneChannelData ChannelData = DoubleChannels[1].Get()->GetData(); AddCorrectedKey(FMovieSceneDoubleValue(CorrectedTransform.GetTranslation().Y), Time, ChannelData); } if (DoubleChannels[2].Get()) { TMovieSceneChannelData ChannelData = DoubleChannels[2].Get()->GetData(); AddCorrectedKey(FMovieSceneDoubleValue(CorrectedTransform.GetTranslation().Z), Time, ChannelData); } if (DoubleChannels[3].Get()) { TMovieSceneChannelData ChannelData = DoubleChannels[3].Get()->GetData(); FMovieSceneDoubleValue CorrectedValue = FMovieSceneDoubleValue(CorrectedTransform.GetRotation().Rotator().Roll); if (PreviousRotationX.IsSet()) { CorrectedValue.Value = UnwindKeyValue(PreviousRotationX.GetValue(), CorrectedValue.Value); } PreviousRotationX = CorrectedValue.Value; AddCorrectedKey(CorrectedValue, Time, ChannelData); } if (DoubleChannels[4].Get()) { TMovieSceneChannelData ChannelData = DoubleChannels[4].Get()->GetData(); FMovieSceneDoubleValue CorrectedValue = FMovieSceneDoubleValue(CorrectedTransform.GetRotation().Rotator().Pitch); if (PreviousRotationY.IsSet()) { CorrectedValue.Value = UnwindKeyValue(PreviousRotationY.GetValue(), CorrectedValue.Value); } PreviousRotationY = CorrectedValue.Value; AddCorrectedKey(CorrectedValue, Time, ChannelData); } if (DoubleChannels[5].Get()) { TMovieSceneChannelData ChannelData = DoubleChannels[5].Get()->GetData(); FMovieSceneDoubleValue CorrectedValue = FMovieSceneDoubleValue(CorrectedTransform.GetRotation().Rotator().Yaw); if (PreviousRotationZ.IsSet()) { CorrectedValue.Value = UnwindKeyValue(PreviousRotationZ.GetValue(), CorrectedValue.Value); } PreviousRotationZ = CorrectedValue.Value; AddCorrectedKey(CorrectedValue, Time, ChannelData); } } for (TMovieSceneChannelHandle DoubleChannel : DoubleChannels) { const UMovieSceneUserImportFBXSettings* ImportFBXSettings = GetDefault(); if (ImportFBXSettings->bReduceKeys) { FKeyDataOptimizationParams Params; Params.Tolerance = ImportFBXSettings->ReduceKeysTolerance; Params.DisplayRate = FrameRate; DoubleChannel.Get()->Optimize(Params); } DoubleChannel.Get()->AutoSetTangents(); } } bool ImportFBXTransform(FString NodeName, FGuid ObjectBinding, UnFbx::FFbxCurvesAPI& CurveAPI, UMovieSceneSequence* InSequence, IMovieScenePlayer* Player, ISequencer* Sequencer) { UMovieScene* MovieScene = InSequence->GetMovieScene(); const UMovieSceneUserImportFBXSettings* ImportFBXSettings = GetDefault(); // Look for transforms explicitly FRichCurve Translation[3]; FRichCurve EulerRotation[3]; FRichCurve Scale[3]; FTransform DefaultTransform; const bool bUseSequencerCurve = true; CurveAPI.GetConvertedTransformCurveData(NodeName, Translation[0], Translation[1], Translation[2], EulerRotation[0], EulerRotation[1], EulerRotation[2], Scale[0], Scale[1], Scale[2], DefaultTransform, bUseSequencerCurve, ImportFBXSettings->ImportUniformScale); UMovieScene3DTransformTrack* TransformTrack = MovieScene->FindTrack(ObjectBinding); if (!TransformTrack) { MovieScene->Modify(); TransformTrack = MovieScene->AddTrack(ObjectBinding); } TransformTrack->Modify(); bool bSectionAdded = false; UMovieScene3DTransformSection* TransformSection = Cast(TransformTrack->FindSection(0)); if (TransformSection && !ImportFBXSettings->bReplaceTransformTrack) { TransformSection = Cast(TransformTrack->CreateNewSection()); TransformSection->SetRowIndex(TransformTrack->GetMaxRowIndex()+1); TransformTrack->AddSection(*TransformSection); bSectionAdded = true; } else { TransformSection = Cast(TransformTrack->FindOrAddSection(0, bSectionAdded)); } if (!TransformSection) { return false; } TransformSection->Modify(); FFrameRate FrameRate = TransformSection->GetTypedOuter()->GetTickResolution(); if (bSectionAdded) { TransformSection->SetRange(TRange::All()); } FVector Location = DefaultTransform.GetLocation(), Rotation = DefaultTransform.GetRotation().Euler(), Scale3D = DefaultTransform.GetScale3D(); FMovieSceneChannelProxy& SectionChannelProxy = TransformSection->GetChannelProxy(); TMovieSceneChannelHandle DoubleChannels[] = { SectionChannelProxy.GetChannelByName("Location.X"), SectionChannelProxy.GetChannelByName("Location.Y"), SectionChannelProxy.GetChannelByName("Location.Z"), SectionChannelProxy.GetChannelByName("Rotation.X"), SectionChannelProxy.GetChannelByName("Rotation.Y"), SectionChannelProxy.GetChannelByName("Rotation.Z"), SectionChannelProxy.GetChannelByName("Scale.X"), SectionChannelProxy.GetChannelByName("Scale.Y"), SectionChannelProxy.GetChannelByName("Scale.Z") }; if (DoubleChannels[0].Get()) { DoubleChannels[0].Get()->SetDefault(Location.X); ImportTransformChannelToDouble(Translation[0], DoubleChannels[0].Get(), FrameRate, false, true); } if (DoubleChannels[1].Get()) { DoubleChannels[1].Get()->SetDefault(Location.Y); ImportTransformChannelToDouble(Translation[1], DoubleChannels[1].Get(), FrameRate, false, true); } if (DoubleChannels[2].Get()) { DoubleChannels[2].Get()->SetDefault(Location.Z); ImportTransformChannelToDouble(Translation[2], DoubleChannels[2].Get(), FrameRate, false, true); } if (DoubleChannels[3].Get()) { DoubleChannels[3].Get()->SetDefault(Rotation.X); ImportTransformChannelToDouble(EulerRotation[0], DoubleChannels[3].Get(), FrameRate, false, true); } if (DoubleChannels[4].Get()) { DoubleChannels[4].Get()->SetDefault(Rotation.Y); ImportTransformChannelToDouble(EulerRotation[1], DoubleChannels[4].Get(), FrameRate, false, true); } if (DoubleChannels[5].Get()) { DoubleChannels[5].Get()->SetDefault(Rotation.Z); ImportTransformChannelToDouble(EulerRotation[2], DoubleChannels[5].Get(), FrameRate, false, true); } if (DoubleChannels[6].Get()) { DoubleChannels[6].Get()->SetDefault(Scale3D.X); ImportTransformChannelToDouble(Scale[0], DoubleChannels[6].Get(), FrameRate, false, true); } if (DoubleChannels[7].Get()) { DoubleChannels[7].Get()->SetDefault(Scale3D.Y); ImportTransformChannelToDouble(Scale[1], DoubleChannels[7].Get(), FrameRate, false, true); } if (DoubleChannels[8].Get()) { DoubleChannels[8].Get()->SetDefault(Scale3D.Z); ImportTransformChannelToDouble(Scale[2], DoubleChannels[8].Get(), FrameRate, false, true); } if(ImportFBXSettings->bCorrectForTransformOrigin) { CorrectForTransformOrigin(Player, TransformSection, Sequencer); } return true; } bool MovieSceneToolHelpers::ImportFBXNode(FString NodeName, UnFbx::FFbxCurvesAPI& CurveAPI, UMovieSceneSequence* InSequence, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID, FGuid ObjectBinding, ISequencer* Sequencer) { // Look for animated float properties TArray AnimatedPropertyNames; CurveAPI.GetNodeAnimatedPropertyNameArray(NodeName, AnimatedPropertyNames); for (auto AnimatedPropertyName : AnimatedPropertyNames) { ImportFBXProperty(NodeName, AnimatedPropertyName, ObjectBinding, CurveAPI, InSequence, Player, TemplateID); } ImportFBXTransform(NodeName, ObjectBinding, CurveAPI, InSequence, Player, Sequencer); // Custom static string properties TArray > CustomPropertyPairs; CurveAPI.GetCustomStringPropertyArray(NodeName, CustomPropertyPairs); for (TPair& CustomProperty : CustomPropertyPairs) { FMovieSceneToolsModule::Get().ImportStringProperty(CustomProperty.Key, CustomProperty.Value, ObjectBinding, InSequence->GetMovieScene()); } return true; } void MovieSceneToolHelpers::GetCameras( FbxNode* Parent, TArray& Cameras ) { FbxCamera* Camera = Parent->GetCamera(); if( Camera ) { Cameras.Add(Camera); } int32 NodeCount = Parent->GetChildCount(); for ( int32 NodeIndex = 0; NodeIndex < NodeCount; ++NodeIndex ) { FbxNode* Child = Parent->GetChild( NodeIndex ); GetCameras(Child, Cameras); } } FbxCamera* FindCamera( FbxNode* Parent ) { FbxCamera* Camera = Parent->GetCamera(); if( !Camera ) { int32 NodeCount = Parent->GetChildCount(); for ( int32 NodeIndex = 0; NodeIndex < NodeCount && !Camera; ++NodeIndex ) { FbxNode* Child = Parent->GetChild( NodeIndex ); Camera = Child->GetCamera(); } } return Camera; } FbxNode* RetrieveObjectFromName(const TCHAR* ObjectName, FbxNode* Root) { if (!Root) { return nullptr; } for (int32 ChildIndex=0;ChildIndexGetChildCount();++ChildIndex) { FbxNode* Node = Root->GetChild(ChildIndex); if (Node) { FString NodeName = FString(Node->GetName()); if ( !FCString::Strcmp(ObjectName,UTF8_TO_TCHAR(Node->GetName()))) { return Node; } if (FbxNode* NextNode = RetrieveObjectFromName(ObjectName,Node)) { return NextNode; } } } return nullptr; } void MovieSceneToolHelpers::CopyCameraProperties(FbxCamera* CameraNode, AActor* InCameraActor) { float FieldOfView; float FocalLength; if (CameraNode->GetApertureMode() == FbxCamera::eFocalLength) { FocalLength = CameraNode->FocalLength.Get(); FieldOfView = CameraNode->ComputeFieldOfView(FocalLength); } else { FieldOfView = CameraNode->FieldOfView.Get(); FocalLength = CameraNode->ComputeFocalLength(FieldOfView); } float ApertureWidth = CameraNode->GetApertureWidth(); float ApertureHeight = CameraNode->GetApertureHeight(); UCameraComponent* CameraComponent = nullptr; if (ACineCameraActor* CineCameraActor = Cast(InCameraActor)) { CameraComponent = CineCameraActor->GetCineCameraComponent(); UCineCameraComponent* CineCameraComponent = CineCameraActor->GetCineCameraComponent(); CineCameraComponent->Filmback.SensorWidth = FUnitConversion::Convert(ApertureWidth, EUnit::Inches, EUnit::Millimeters); CineCameraComponent->Filmback.SensorHeight = FUnitConversion::Convert(ApertureHeight, EUnit::Inches, EUnit::Millimeters); CineCameraComponent->FocusSettings.ManualFocusDistance = CameraNode->FocusDistance; if (FocalLength < CineCameraComponent->LensSettings.MinFocalLength) { CineCameraComponent->LensSettings.MinFocalLength = FocalLength; } if (FocalLength > CineCameraComponent->LensSettings.MaxFocalLength) { CineCameraComponent->LensSettings.MaxFocalLength = FocalLength; } CineCameraComponent->CurrentFocalLength = FocalLength; } else if (ACameraActor* CameraActor = Cast(InCameraActor)) { CameraComponent = CameraActor->GetCameraComponent(); } if (!CameraComponent) { return; } CameraComponent->SetProjectionMode(CameraNode->ProjectionType.Get() == FbxCamera::ePerspective ? ECameraProjectionMode::Perspective : ECameraProjectionMode::Orthographic); CameraComponent->SetAspectRatio(CameraNode->AspectWidth.Get() / CameraNode->AspectHeight.Get()); CameraComponent->SetOrthoNearClipPlane(CameraNode->NearPlane.Get()); CameraComponent->SetOrthoFarClipPlane(CameraNode->FarPlane.Get()); CameraComponent->SetOrthoWidth(CameraNode->OrthoZoom.Get()); CameraComponent->SetFieldOfView(FieldOfView); } FString MovieSceneToolHelpers::GetCameraName(FbxCamera* InCamera) { FbxNode* CameraNode = InCamera->GetNode(); if (CameraNode) { return CameraNode->GetName(); } return InCamera->GetName(); } void MovieSceneToolHelpers::ImportFBXCameraToExisting(UnFbx::FFbxImporter* FbxImporter, UMovieSceneSequence* InSequence, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID, TMap& InObjectBindingMap, bool bMatchByNameOnly, bool bNotifySlate) { if (FApp::IsUnattended() || GIsRunningUnattendedScript) { bNotifySlate = false; } UMovieScene* MovieScene = InSequence->GetMovieScene(); for (auto InObjectBinding : InObjectBindingMap) { TArrayView> BoundObjects = Player->FindBoundObjects(InObjectBinding.Key,TemplateID); FString ObjectName = InObjectBinding.Value; FbxCamera* CameraNode = nullptr; FbxNode* Node = RetrieveObjectFromName(*ObjectName, FbxImporter->Scene->GetRootNode()); if (Node) { CameraNode = FindCamera(Node); } if (!CameraNode) { if (bMatchByNameOnly) { if (bNotifySlate) { FNotificationInfo Info(FText::Format(NSLOCTEXT("MovieSceneTools", "NoMatchingCameraError", "Failed to find any matching camera for {0}"), FText::FromString(ObjectName))); Info.ExpireDuration = 5.0f; FSlateNotificationManager::Get().AddNotification(Info)->SetCompletionState(SNotificationItem::CS_Fail); } continue; } CameraNode = FindCamera(FbxImporter->Scene->GetRootNode()); if (CameraNode) { if (bNotifySlate) { FString CameraName = GetCameraName(CameraNode); FNotificationInfo Info(FText::Format(NSLOCTEXT("MovieSceneTools", "NoMatchingCameraWarning", "Failed to find any matching camera for {0}. Importing onto first camera from fbx {1}"), FText::FromString(ObjectName), FText::FromString(CameraName))); Info.ExpireDuration = 5.0f; FSlateNotificationManager::Get().AddNotification(Info)->SetCompletionState(SNotificationItem::CS_None); } } } if (!CameraNode) { continue; } float FieldOfView; float FocalLength; if (CameraNode->GetApertureMode() == FbxCamera::eFocalLength) { FocalLength = CameraNode->FocalLength.Get(); FieldOfView = CameraNode->ComputeFieldOfView(FocalLength); } else { FieldOfView = CameraNode->FieldOfView.Get(); FocalLength = CameraNode->ComputeFocalLength(FieldOfView); } for (TWeakObjectPtr<>& WeakObject : BoundObjects) { UObject* FoundObject = WeakObject.Get(); if (FoundObject && FoundObject->GetClass()->IsChildOf(ACameraActor::StaticClass())) { CopyCameraProperties(CameraNode, Cast(FoundObject)); UCameraComponent* CameraComponent = nullptr; FName TrackName; float TrackValue; if (ACineCameraActor* CineCameraActor = Cast(FoundObject)) { CameraComponent = CineCameraActor->GetCineCameraComponent(); TrackName = TEXT("CurrentFocalLength"); TrackValue = FocalLength; } else if (ACameraActor* CameraActor = Cast(FoundObject)) { CameraComponent = CameraActor->GetCameraComponent(); TrackName = TEXT("FieldOfView"); TrackValue = FieldOfView; } else { continue; } // Set the default value of the current focal length or field of view section //FGuid PropertyOwnerGuid = Player->GetHandleToObject(CameraComponent); FGuid PropertyOwnerGuid = GetHandleToObject(CameraComponent, InSequence, Player, TemplateID); if (!PropertyOwnerGuid.IsValid()) { continue; } // If copying properties to a spawnable object, the template object must be updated MovieSceneHelpers::CopyObjectTemplate(InSequence, InObjectBinding.Key, FoundObject, Player->GetSharedPlaybackState()); UMovieSceneFloatTrack* FloatTrack = MovieScene->FindTrack(PropertyOwnerGuid, TrackName); if (FloatTrack) { FloatTrack->Modify(); FloatTrack->RemoveAllAnimationData(); bool bSectionAdded = false; UMovieSceneFloatSection* FloatSection = Cast(FloatTrack->FindOrAddSection(0, bSectionAdded)); if (!FloatSection) { continue; } FloatSection->Modify(); if (bSectionAdded) { FloatSection->SetRange(TRange::All()); } FloatSection->GetChannelProxy().GetChannel(0)->SetDefault(TrackValue); } } } } } void ImportFBXCamera(UnFbx::FFbxImporter* FbxImporter, UMovieSceneSequence* InSequence, ISequencer& InSequencer, TMap& InObjectBindingMap, bool bMatchByNameOnly, bool bCreateCameras) { bool bNotifySlate = !FApp::IsUnattended() && !GIsRunningUnattendedScript; UMovieScene* MovieScene = InSequence->GetMovieScene(); TArray AllCameras; MovieSceneToolHelpers::GetCameras(FbxImporter->Scene->GetRootNode(), AllCameras); if (AllCameras.Num() == 0) { return; } if (bCreateCameras) { UWorld* World = GCurrentLevelEditingViewportClient ? GCurrentLevelEditingViewportClient->GetWorld() : nullptr; // Find unmatched cameras TArray UnmatchedCameras; for (auto Camera : AllCameras) { FString NodeName = MovieSceneToolHelpers::GetCameraName(Camera); bool bMatched = false; for (auto InObjectBinding : InObjectBindingMap) { FString ObjectName = InObjectBinding.Value; if (ObjectName == NodeName) { // Look for a valid bound object, otherwise need to create a new camera and assign this binding to it bool bFoundBoundObject = false; TArrayView> BoundObjects = InSequencer.FindBoundObjects(InObjectBinding.Key, InSequencer.GetFocusedTemplateID()); for (auto BoundObject : BoundObjects) { if (BoundObject.IsValid()) { bFoundBoundObject = true; break; } } if (!bFoundBoundObject) { if (bNotifySlate) { FNotificationInfo Info(FText::Format(NSLOCTEXT("MovieSceneTools", "NoBoundObjectsError", "Existing binding has no objects. Creating a new camera and binding for {0}"), FText::FromString(ObjectName))); Info.ExpireDuration = 5.0f; FSlateNotificationManager::Get().AddNotification(Info)->SetCompletionState(SNotificationItem::CS_Fail); } } } } if (!bMatched) { UnmatchedCameras.Add(Camera); } } // If there are new cameras, clear the object binding map so that we're only assigning values to the newly created cameras if (UnmatchedCameras.Num() != 0) { InObjectBindingMap.Reset(); bMatchByNameOnly = true; } // Add any unmatched cameras for (auto UnmatchedCamera : UnmatchedCameras) { FString CameraName = MovieSceneToolHelpers::GetCameraName(UnmatchedCamera); AActor* NewCamera = nullptr; if (UnmatchedCamera->GetApertureMode() == FbxCamera::eFocalLength) { FActorSpawnParameters SpawnParams; NewCamera = World->SpawnActor(SpawnParams); NewCamera->SetActorLabel(*CameraName); } else { FActorSpawnParameters SpawnParams; NewCamera = World->SpawnActor(SpawnParams); NewCamera->SetActorLabel(*CameraName); } // Copy camera properties before adding default tracks so that initial camera properties match and can be restored after sequencer finishes MovieSceneToolHelpers::CopyCameraProperties(UnmatchedCamera, NewCamera); TArray > NewCameras; NewCameras.Add(NewCamera); TArray NewCameraGuids = InSequencer.AddActors(NewCameras); if (NewCameraGuids.Num()) { InObjectBindingMap.Add(NewCameraGuids[0]); InObjectBindingMap[NewCameraGuids[0]] = CameraName; } } } MovieSceneToolHelpers::ImportFBXCameraToExisting(FbxImporter, InSequence, &InSequencer, InSequencer.GetFocusedTemplateID(), InObjectBindingMap, bMatchByNameOnly, true); } FGuid FindCameraGuid(FbxCamera* Camera, TMap& InObjectBindingMap) { FString CameraName = MovieSceneToolHelpers::GetCameraName(Camera); for (auto& Pair : InObjectBindingMap) { if (Pair.Value == CameraName) { return Pair.Key; } } return FGuid(); } UMovieSceneCameraCutTrack* GetCameraCutTrack(UMovieScene* InMovieScene) { // Get the camera cut UMovieSceneTrack* CameraCutTrack = InMovieScene->GetCameraCutTrack(); if (CameraCutTrack == nullptr) { InMovieScene->Modify(); CameraCutTrack = InMovieScene->AddCameraCutTrack(UMovieSceneCameraCutTrack::StaticClass()); } return CastChecked(CameraCutTrack); } void ImportCameraCut(UnFbx::FFbxImporter* FbxImporter, UMovieScene* InMovieScene, TMap& InObjectBindingMap) { // Find a camera switcher FbxCameraSwitcher* CameraSwitcher = FbxImporter->Scene->GlobalCameraSettings().GetCameraSwitcher(); if (CameraSwitcher == nullptr) { return; } // Get the animation layer FbxAnimStack* AnimStack = FbxImporter->Scene->GetMember(0); if (AnimStack == nullptr) { return; } FbxAnimLayer* AnimLayer = AnimStack->GetMember(0); if (AnimLayer == nullptr) { return; } // The camera switcher camera index refer to depth-first found order of the camera in the FBX TArray AllCameras; MovieSceneToolHelpers::GetCameras(FbxImporter->Scene->GetRootNode(), AllCameras); UMovieSceneCameraCutTrack* CameraCutTrack = GetCameraCutTrack(InMovieScene); FFrameRate FrameRate = CameraCutTrack->GetTypedOuter()->GetTickResolution(); FbxAnimCurve* AnimCurve = CameraSwitcher->CameraIndex.GetCurve(AnimLayer); if (AnimCurve) { for (int i = 0; i < AnimCurve->KeyGetCount(); ++i) { FbxAnimCurveKey key = AnimCurve->KeyGet(i); int value = (int)key.GetValue() - 1; if (value >= 0 && value < AllCameras.Num()) { FGuid CameraGuid = FindCameraGuid(AllCameras[value], InObjectBindingMap); if (CameraGuid != FGuid()) { CameraCutTrack->AddNewCameraCut(UE::MovieScene::FRelativeObjectBindingID(CameraGuid), (key.GetTime().GetSecondDouble() * FrameRate).RoundToFrame()); } } } } } class SMovieSceneImportFBXSettings : public SCompoundWidget, public FGCObject { SLATE_BEGIN_ARGS(SMovieSceneImportFBXSettings) {} SLATE_ARGUMENT(FString, ImportFilename) SLATE_ARGUMENT(UMovieSceneSequence*, Sequence) SLATE_ARGUMENT(ISequencer*, Sequencer) SLATE_END_ARGS() void Construct(const FArguments& InArgs) { FPropertyEditorModule& PropertyEditor = FModuleManager::LoadModuleChecked("PropertyEditor"); FDetailsViewArgs DetailsViewArgs; DetailsViewArgs.bShowOptions = false; DetailsViewArgs.bAllowSearch = false; DetailsViewArgs.bShowPropertyMatrixButton = false; DetailsViewArgs.bUpdatesFromSelection = false; DetailsViewArgs.bLockable = false; DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea; DetailsViewArgs.ViewIdentifier = "Import FBX Settings"; DetailView = PropertyEditor.CreateDetailView(DetailsViewArgs); ChildSlot [ SNew(SVerticalBox) + SVerticalBox::Slot() [ DetailView.ToSharedRef() ] + SVerticalBox::Slot() .AutoHeight() .HAlign(HAlign_Right) .Padding(5.f) [ SNew(SButton) .ContentPadding(FMargin(10, 5)) .Text(NSLOCTEXT("MovieSceneTools", "ImportFBXButtonText", "Import")) .OnClicked(this, &SMovieSceneImportFBXSettings::OnImportFBXClicked) ] ]; ImportFilename = InArgs._ImportFilename; Sequence = InArgs._Sequence; Sequencer = InArgs._Sequencer; UMovieSceneUserImportFBXSettings* ImportFBXSettings = GetMutableDefault(); DetailView->SetObject(ImportFBXSettings); } virtual void AddReferencedObjects( FReferenceCollector& Collector ) override { Collector.AddReferencedObject(Sequence); } virtual FString GetReferencerName() const override { return TEXT("SMovieSceneImportFBXSettings"); } void SetObjectBindingMap(const TMap& InObjectBindingMap) { ObjectBindingMap = InObjectBindingMap; } void SetCreateCameras(TOptional bInCreateCameras) { bCreateCameras = bInCreateCameras; } private: FReply OnImportFBXClicked() { UMovieSceneUserImportFBXSettings* ImportFBXSettings = GetMutableDefault(); FEditorDirectories::Get().SetLastDirectory( ELastDirectory::FBX, FPaths::GetPath( ImportFilename ) ); // Save path as default for next time. if (!Sequence || !Sequence->GetMovieScene() || Sequence->GetMovieScene()->IsReadOnly()) { return FReply::Unhandled(); } FFBXInOutParameters InOutParams; if (!MovieSceneToolHelpers::ReadyFBXForImport(ImportFilename, ImportFBXSettings,InOutParams)) { return FReply::Unhandled(); } const FScopedTransaction Transaction(NSLOCTEXT("MovieSceneTools", "ImportFBXTransaction", "Import FBX")); UnFbx::FFbxImporter* FbxImporter = UnFbx::FFbxImporter::GetInstance(); bool bMatchByNameOnly = ImportFBXSettings->bMatchByNameOnly; if (ObjectBindingMap.Num() == 1 && bMatchByNameOnly) { UE_LOG(LogMovieScene, Display, TEXT("Fbx Import: Importing onto one selected binding, disabling match by name only.")); bMatchByNameOnly = false; } // Import static cameras first ImportFBXCamera(FbxImporter, Sequence, *Sequencer, ObjectBindingMap, bMatchByNameOnly, bCreateCameras.IsSet() ? bCreateCameras.GetValue() : ImportFBXSettings->bCreateCameras); UWorld* World = Sequencer->GetPlaybackContext()->GetWorld(); bool bValid = MovieSceneToolHelpers::ImportFBXIfReady(World, Sequence, Sequencer, Sequencer->GetFocusedTemplateID(), ObjectBindingMap, ImportFBXSettings, InOutParams, Sequencer); Sequencer->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded); TSharedPtr Window = FSlateApplication::Get().FindWidgetWindow(AsShared()); if ( Window.IsValid() ) { Window->RequestDestroyWindow(); } return bValid ? FReply::Handled() : FReply::Unhandled(); } TSharedPtr DetailView; FString ImportFilename; TObjectPtr Sequence; ISequencer* Sequencer; TMap ObjectBindingMap; TOptional bCreateCameras; }; bool MovieSceneToolHelpers::ReadyFBXForImport(const FString& ImportFilename, UMovieSceneUserImportFBXSettings* ImportFBXSettings, FFBXInOutParameters& OutParams) { UnFbx::FFbxImporter* FbxImporter = UnFbx::FFbxImporter::GetInstance(); UnFbx::FBXImportOptions* ImportOptions = FbxImporter->GetImportOptions(); OutParams.bConvertSceneBackup = ImportOptions->bConvertScene; OutParams.bConvertSceneUnitBackup = ImportOptions->bConvertSceneUnit; OutParams.bForceFrontXAxisBackup = ImportOptions->bForceFrontXAxis; OutParams.ImportUniformScaleBackup = ImportOptions->ImportUniformScale; ImportOptions->bIsImportCancelable = false; ImportOptions->bConvertScene = true; ImportOptions->bConvertSceneUnit = ImportFBXSettings->bConvertSceneUnit; ImportOptions->bForceFrontXAxis = ImportFBXSettings->bForceFrontXAxis; ImportOptions->ImportUniformScale = ImportFBXSettings->ImportUniformScale; const FString FileExtension = FPaths::GetExtension(ImportFilename); if (!FbxImporter->ImportFromFile(*ImportFilename, FileExtension, true)) { // Log the error message and fail the import. FbxImporter->ReleaseScene(); ImportOptions->bConvertScene = OutParams.bConvertSceneBackup; ImportOptions->bConvertSceneUnit = OutParams.bConvertSceneUnitBackup; ImportOptions->bForceFrontXAxis = OutParams.bForceFrontXAxisBackup; ImportOptions->ImportUniformScale = OutParams.ImportUniformScaleBackup; return false; } return true; } bool ImportFBXOntoControlRigs(UWorld* World, UMovieScene* MovieScene, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID, TMap& ObjectBindingMap, const TArray& ControRigControlNames , UMovieSceneUserImportFBXSettings* ImportFBXSettings, UMovieSceneUserImportFBXControlRigSettings* Settings) { UMovieSceneUserImportFBXSettings* CurrentImportFBXSettings = GetMutableDefault(); TArray OriginalSettings; FObjectWriter ObjWriter(CurrentImportFBXSettings, OriginalSettings); CurrentImportFBXSettings->bMatchByNameOnly = ImportFBXSettings->bMatchByNameOnly; CurrentImportFBXSettings->bForceFrontXAxis = ImportFBXSettings->bForceFrontXAxis; CurrentImportFBXSettings->bCreateCameras = ImportFBXSettings->bCreateCameras; CurrentImportFBXSettings->bReduceKeys = ImportFBXSettings->bReduceKeys; CurrentImportFBXSettings->ReduceKeysTolerance = ImportFBXSettings->ReduceKeysTolerance; CurrentImportFBXSettings->bConvertSceneUnit = ImportFBXSettings->bConvertSceneUnit; CurrentImportFBXSettings->ImportUniformScale = ImportFBXSettings->ImportUniformScale; UnFbx::FFbxImporter* FbxImporter = UnFbx::FFbxImporter::GetInstance(); return true; } bool MovieSceneToolHelpers::ImportFBXIfReady(UWorld* World, UMovieSceneSequence* Sequence, IMovieScenePlayer* Player, FMovieSceneSequenceIDRef TemplateID, TMap& ObjectBindingMap, UMovieSceneUserImportFBXSettings* ImportFBXSettings, const FFBXInOutParameters& InParams, ISequencer* Sequencer) { UMovieScene* MovieScene = Sequence->GetMovieScene(); UMovieSceneUserImportFBXSettings* CurrentImportFBXSettings = GetMutableDefault(); TArray OriginalSettings; FObjectWriter ObjWriter(CurrentImportFBXSettings, OriginalSettings); CurrentImportFBXSettings->bMatchByNameOnly = ImportFBXSettings->bMatchByNameOnly; CurrentImportFBXSettings->bForceFrontXAxis = ImportFBXSettings->bForceFrontXAxis; CurrentImportFBXSettings->bCreateCameras = ImportFBXSettings->bCreateCameras; CurrentImportFBXSettings->bReduceKeys = ImportFBXSettings->bReduceKeys; CurrentImportFBXSettings->ReduceKeysTolerance = ImportFBXSettings->ReduceKeysTolerance; CurrentImportFBXSettings->bConvertSceneUnit = ImportFBXSettings->bConvertSceneUnit; CurrentImportFBXSettings->ImportUniformScale = ImportFBXSettings->ImportUniformScale; CurrentImportFBXSettings->bReplaceTransformTrack = ImportFBXSettings->bReplaceTransformTrack; CurrentImportFBXSettings->bCorrectForTransformOrigin = ImportFBXSettings->bCorrectForTransformOrigin; UnFbx::FFbxImporter* FbxImporter = UnFbx::FFbxImporter::GetInstance(); UnFbx::FFbxCurvesAPI CurveAPI; FbxImporter->PopulateAnimatedCurveData(CurveAPI); TArray AllNodeNames; CurveAPI.GetAllNodeNameArray(AllNodeNames); // Import a camera cut track if cams were created, do it after populating curve data ensure only one animation layer, if any ImportCameraCut(FbxImporter, MovieScene, ObjectBindingMap); FString RootNodeName = FbxImporter->Scene->GetRootNode()->GetName(); // First try matching by name for (int32 NodeIndex = 0; NodeIndex < AllNodeNames.Num(); ) { FString NodeName = AllNodeNames[NodeIndex]; if (RootNodeName == NodeName) { ++NodeIndex; continue; } bool bFoundMatch = false; for (auto It = ObjectBindingMap.CreateConstIterator(); It; ++It) { if (FCString::Strcmp(*It.Value().ToUpper(), *NodeName.ToUpper()) == 0) { MovieSceneToolHelpers::ImportFBXNode(NodeName, CurveAPI, Sequence, Player, TemplateID, It.Key(), Sequencer); ObjectBindingMap.Remove(It.Key()); AllNodeNames.RemoveAt(NodeIndex); bFoundMatch = true; break; } } if (bFoundMatch) { continue; } ++NodeIndex; } // Otherwise, get the first available node that hasn't been imported onto yet if (!ImportFBXSettings->bMatchByNameOnly) { for (int32 NodeIndex = 0; NodeIndex < AllNodeNames.Num(); ) { FString NodeName = AllNodeNames[NodeIndex]; if (RootNodeName == NodeName) { ++NodeIndex; continue; } auto It = ObjectBindingMap.CreateConstIterator(); if (It) { MovieSceneToolHelpers::ImportFBXNode(NodeName, CurveAPI, Sequence, Player, TemplateID, It.Key(), Sequencer); UE_LOG(LogMovieScene, Warning, TEXT("Fbx Import: Failed to find any matching node for (%s). Defaulting to first available (%s)."), *NodeName, *It.Value()); ObjectBindingMap.Remove(It.Key()); AllNodeNames.RemoveAt(NodeIndex); continue; } ++NodeIndex; } } for (FString NodeName : AllNodeNames) { UE_LOG(LogMovieScene, Warning, TEXT("Fbx Import: Failed to find any matching node for (%s)."), *NodeName); } // restore FObjectReader ObjReader(GetMutableDefault(), OriginalSettings); FbxImporter->ReleaseScene(); UnFbx::FBXImportOptions* ImportOptions = FbxImporter->GetImportOptions(); ImportOptions->bConvertScene = InParams.bConvertSceneBackup; ImportOptions->bConvertSceneUnit = InParams.bConvertSceneUnitBackup; ImportOptions->bForceFrontXAxis = InParams.bForceFrontXAxisBackup; ImportOptions->ImportUniformScale = InParams.ImportUniformScaleBackup; return true; } bool MovieSceneToolHelpers::ImportFBXWithDialog(UMovieSceneSequence* InSequence, ISequencer& InSequencer, const TMap& InObjectBindingMap, TOptional bCreateCameras) { TArray OpenFilenames; IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); bool bOpen = false; if (DesktopPlatform) { FString ExtensionStr; ExtensionStr += TEXT("FBX (*.fbx)|*.fbx|"); bOpen = DesktopPlatform->OpenFileDialog( FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr), NSLOCTEXT("MovieSceneTools", "ImportFBX", "Import FBX from...").ToString(), FEditorDirectories::Get().GetLastDirectory(ELastDirectory::FBX), TEXT(""), *ExtensionStr, EFileDialogFlags::None, OpenFilenames ); } if (!bOpen) { return false; } if (!OpenFilenames.Num()) { return false; } const FText TitleText = NSLOCTEXT("MovieSceneTools", "ImportFBXTitle", "Import FBX"); // Create the window to choose our options TSharedRef Window = SNew(SWindow) .Title(TitleText) .HasCloseButton(true) .SizingRule(ESizingRule::UserSized) .ClientSize(FVector2D(450.0f, 300.0f)) .AutoCenter(EAutoCenter::PreferredWorkArea) .SupportsMinimize(false); TSharedRef DialogWidget = SNew(SMovieSceneImportFBXSettings) .ImportFilename(OpenFilenames[0]) .Sequence(InSequence) .Sequencer(&InSequencer); DialogWidget->SetObjectBindingMap(InObjectBindingMap); DialogWidget->SetCreateCameras(bCreateCameras); Window->SetContent(DialogWidget); FSlateApplication::Get().AddWindow(Window); return true; } bool MovieSceneToolHelpers::HasHiddenMobility(const UClass* ObjectClass) { if (ObjectClass) { static const FName NAME_HideCategories(TEXT("HideCategories")); if (ObjectClass->HasMetaData(NAME_HideCategories)) { if (ObjectClass->GetMetaData(NAME_HideCategories).Contains(TEXT("Mobility"))) { return true; } } } return false; } const FMovieSceneEvaluationTrack* MovieSceneToolHelpers::GetEvaluationTrack(ISequencer *Sequencer, const FGuid& TrackSignature) { FMovieSceneRootEvaluationTemplateInstance& Instance = Sequencer->GetEvaluationTemplate(); FMovieSceneCompiledDataID SubDataID = Instance.GetCompiledDataManager()->GetSubDataID(Instance.GetCompiledDataID(), Sequencer->GetFocusedTemplateID()); { const FMovieSceneEvaluationTemplate* Template = SubDataID.IsValid() ? Instance.GetCompiledDataManager()->FindTrackTemplate(SubDataID) : nullptr; const FMovieSceneEvaluationTrack* EvalTrack = Template ? Template->FindTrack(TrackSignature) : nullptr; if (EvalTrack) { return EvalTrack; } } return nullptr; } void ExportLevelMesh(UnFbx::FFbxExporter* Exporter, ULevel* Level, IMovieScenePlayer* Player, const TArray& Bindings, INodeNameAdapter& NodeNameAdapter, FMovieSceneSequenceIDRef& Template) { // Get list of actors based upon bindings... const bool bSelectedOnly = (Bindings.Num()) != 0; const bool bSaveAnimSeq = false; //force off saving any AnimSequences since this can conflict when we export the level sequence animations. TArray ActorToExport; int32 ActorCount = Level->Actors.Num(); for (int32 ActorIndex = 0; ActorIndex < ActorCount; ++ActorIndex) { AActor* Actor = Level->Actors[ActorIndex]; if (Actor != NULL) { FGuid ExistingGuid = Player->FindObjectId(*Actor, Template); if (ExistingGuid.IsValid() && (!bSelectedOnly || Bindings.Contains(ExistingGuid))) { ActorToExport.Add(Actor); } } } // Export the persistent level and all of it's actors Exporter->ExportLevelMesh(Level, !bSelectedOnly, ActorToExport, NodeNameAdapter, bSaveAnimSeq); } //5.5 deprecrated bool MovieSceneToolHelpers::ExportFBX(UWorld* World, UMovieScene* MovieScene, IMovieScenePlayer* Player, const TArray& Bindings, const TArray& Tracks, INodeNameAdapter& NodeNameAdapter, FMovieSceneSequenceIDRef& Template, const FString& InFBXFileName, FMovieSceneSequenceTransform& RootToLocalTransform) { if (MovieScene) { if (UMovieSceneSequence* MovieSceneSequence = MovieScene->GetTypedOuter< UMovieSceneSequence>()) { FAnimExportSequenceParameters AESP; AESP.MovieSceneSequence = MovieSceneSequence; AESP.RootMovieSceneSequence = MovieSceneSequence; AESP.Player = Player; AESP.RootToLocalTransform = RootToLocalTransform; return MovieSceneToolHelpers::ExportFBX(World, AESP, Bindings, Tracks, NodeNameAdapter, Template, InFBXFileName); } } return false; } bool MovieSceneToolHelpers::ExportFBX(UWorld* World, const FAnimExportSequenceParameters& AESP, const TArray& Bindings, const TArray& Tracks, INodeNameAdapter& NodeNameAdapter, FMovieSceneSequenceIDRef& Template, const FString& InFBXFileName) { UnFbx::FFbxExporter* Exporter = UnFbx::FFbxExporter::GetInstance(); Exporter->CreateDocument(); Exporter->SetTransformBaking(false); Exporter->SetKeepHierarchy(true); ExportLevelMesh(Exporter, World->PersistentLevel, AESP.Player, Bindings, NodeNameAdapter, Template); // Export streaming levels and actors for (ULevelStreaming* StreamingLevel : World->GetStreamingLevels()) { if (StreamingLevel) { if (ULevel* Level = StreamingLevel->GetLoadedLevel()) { ExportLevelMesh(Exporter, Level, AESP.Player, Bindings, NodeNameAdapter, Template); } } } Exporter->ExportLevelSequence(AESP.MovieSceneSequence, AESP.RootMovieSceneSequence, Bindings, AESP.Player, NodeNameAdapter, Template, AESP.RootToLocalTransform); //Export given tracks Exporter->ExportLevelSequenceTracks(AESP.MovieSceneSequence, AESP.RootMovieSceneSequence, AESP.Player, Template, nullptr, nullptr, Tracks, AESP.RootToLocalTransform); // Save to disk Exporter->WriteToFile(*InFBXFileName); return true; } static void TickLiveLink(ILiveLinkClient* LiveLinkClient, TMap& SourceAndMode) { //This first bit lookes for a Sequencer Live Link Source which can show up any frame and we need to set it to Latest mode if (LiveLinkClient) { TArray Sources = LiveLinkClient->GetSources(); for (const FGuid& Guid : Sources) { FText SourceTypeText = LiveLinkClient->GetSourceType(Guid); FString SourceTypeStr = SourceTypeText.ToString(); if (SourceTypeStr.Contains(TEXT("Sequencer Live Link"))) { ULiveLinkSourceSettings* Settings = LiveLinkClient->GetSourceSettings(Guid); if (Settings) { if (Settings->Mode != ELiveLinkSourceMode::Latest) { SourceAndMode.Add(Guid, Settings->Mode); Settings->Mode = ELiveLinkSourceMode::Latest; } } } } LiveLinkClient->ForceTick(); } } static void TickFrame(const FFrameNumber& FrameNumber,float DeltaTime, UMovieScene* MovieScene, UnFbx::FLevelSequenceAnimTrackAdapter& AnimTrackAdapter, const TArray& BakeHelpers, const TArray< USkeletalMeshComponent*>& SkelMeshComps, ILiveLinkClient* LiveLinkClient, TMap& SourceAndMode) { //Begin records a frame so need to set things up first for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers) { if (BakeHelper) { BakeHelper->PreEvaluation(MovieScene, FrameNumber); } } // This will call UpdateSkelPose on the skeletal mesh component to move bones based on animations in the sequence int32 Index = FrameNumber.Value; AnimTrackAdapter.UpdateAnimation(Index); UWorld* World = GCurrentLevelEditingViewportClient ? GCurrentLevelEditingViewportClient->GetWorld() : nullptr; if (World) { const FConstraintsManagerController& Controller = FConstraintsManagerController::Get(World); Controller.EvaluateAllConstraints(); } for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers) { if (BakeHelper) { BakeHelper->PostEvaluation(MovieScene, FrameNumber); } } //Live Link source can show up at any time so we unfortunately need to check for it TickLiveLink(LiveLinkClient, SourceAndMode); // Update space bases so new animation position has an effect. for (USkeletalMeshComponent* SkelMeshComp : SkelMeshComps) { SkelMeshComp->SetForcedLOD(1); //we need to do this every tick since a restore state may happen while evaluating and bummp us out ot a different LOD SkelMeshComp->TickAnimation(DeltaTime, false); SkelMeshComp->RefreshBoneTransforms(); SkelMeshComp->RefreshFollowerComponents(); SkelMeshComp->UpdateComponentToWorld(); SkelMeshComp->FinalizeBoneTransform(); SkelMeshComp->MarkRenderTransformDirty(); SkelMeshComp->MarkRenderDynamicDataDirty(); } } bool MovieSceneToolHelpers::BakeToSkelMeshToCallbacks(UMovieScene* MovieScene, IMovieScenePlayer* Player, USkeletalMeshComponent* InSkelMeshComp, FMovieSceneSequenceIDRef& Template, FMovieSceneSequenceTransform& RootToLocalTransform, UAnimSeqExportOption* ExportOptions, FInitAnimationCB InitCallback, FStartAnimationCB StartCallback, FTickAnimationCB TickCallback, FEndAnimationCB EndCallback) { return false; } bool MovieSceneToolHelpers::BakeToSkelMeshToCallbacks(const FAnimExportSequenceParameters& AESP, USkeletalMeshComponent* InSkelMeshComp, UAnimSeqExportOption* ExportOptions, FInitAnimationCB InitCallback, FStartAnimationCB StartCallback, FTickAnimationCB TickCallback, FEndAnimationCB EndCallback) { UMovieScene* MovieScene = AESP.MovieSceneSequence->GetMovieScene(); TArray< USkeletalMeshComponent*> SkelMeshComps; if (!ExportOptions) { UE_LOG(LogMovieScene, Warning, TEXT(" MovieSceneToolHelpers::BakeToSkelMesh functions require a valid AnimSeqExportOption")); return false; } if (ExportOptions->bEvaluateAllSkeletalMeshComponents) { AActor* Actor = InSkelMeshComp->GetTypedOuter(); if (Actor) { Actor->GetComponents(SkelMeshComps, false); } } else { SkelMeshComps.Add(InSkelMeshComp); } //if we have no allocated bone space transforms something wrong so try to recalc them,only need to do this on the recorded skelmesh if (InSkelMeshComp->GetBoneSpaceTransforms().Num() <= 0) { InSkelMeshComp->RecalcRequiredBones(0); if (InSkelMeshComp->GetBoneSpaceTransforms().Num() <= 0) { UE_LOG(LogMovieScene, Error, TEXT("Error Ba")); return false; } } UnFbx::FLevelSequenceAnimTrackAdapter::FAnimTrackSettings AnimTrackSettings; AnimTrackSettings.MovieScenePlayer = AESP.Player; AnimTrackSettings.MovieSceneSequence = AESP.MovieSceneSequence; AnimTrackSettings.RootMovieSceneSequence = AESP.RootMovieSceneSequence; AnimTrackSettings.RootToLocalTransform = AESP.RootToLocalTransform; AnimTrackSettings.bForceUseOfMovieScenePlaybackRange = AESP.bForceUseOfMovieScenePlaybackRange; UnFbx::FLevelSequenceAnimTrackAdapter AnimTrackAdapter(AnimTrackSettings); if (ExportOptions->bUseCustomTimeRange) { const FFrameRate TickResolution = MovieScene->GetTickResolution(); const FFrameRate DisplayResolution = ExportOptions->CustomDisplayRate; const FFrameNumber StartFrameInTick = FFrameRate::TransformTime(FFrameTime(ExportOptions->CustomStartFrame), DisplayResolution, TickResolution).FloorToFrame(); FFrameNumber EndFrameInTick = FFrameRate::TransformTime(FFrameTime(ExportOptions->CustomEndFrame), DisplayResolution, TickResolution).CeilToFrame(); if (EndFrameInTick < StartFrameInTick) { EndFrameInTick = StartFrameInTick; } AnimTrackAdapter.SetRange(StartFrameInTick,EndFrameInTick); } if (ExportOptions->bUseCustomFrameRate) { AnimTrackAdapter.SetFrameRate(ExportOptions->CustomFrameRate); } int32 LocalStartFrame = AnimTrackAdapter.GetLocalStartFrame(); int32 AnimationLength = AnimTrackAdapter.GetLength(); float FrameRate = AnimTrackAdapter.GetFrameRate(); float DeltaTime = 1.0f / FrameRate; //If we are running with a live link track we need to do a few things. // 1. First test to see if we have one, only way to really do that is to see if we have a source that has the `Sequencer Live Link Track`. We also evalute the first frame in case we are out of range and the sources aren't created yet. // 2. Make sure Sequencer.AlwaysSendInterpolated.LiveLink is non-zero, and then set it back to zero if it's not. // 3. For each live link sequencer source we need to set the ELiveLinkSourceMode to Latest so that we just get the latest and don't use engine/timecode for any interpolation. ILiveLinkClient* LiveLinkClient = nullptr; IModularFeatures& ModularFeatures = IModularFeatures::Get(); TMap SourceAndMode; if (ModularFeatures.IsModularFeatureAvailable(ILiveLinkClient::ModularFeatureName)) { LiveLinkClient = &ModularFeatures.GetModularFeature(ILiveLinkClient::ModularFeatureName); } TOptional SequencerAlwaysSenedLiveLinkInterpolated; IConsoleVariable* CVarAlwaysSendInterpolatedLiveLink = IConsoleManager::Get().FindConsoleVariable(TEXT("Sequencer.AlwaysSendInterpolatedLiveLink")); if (CVarAlwaysSendInterpolatedLiveLink) { SequencerAlwaysSenedLiveLinkInterpolated = CVarAlwaysSendInterpolatedLiveLink->GetInt(); CVarAlwaysSendInterpolatedLiveLink->Set(1, ECVF_SetByConsole); } const TArray& BakeHelpers = FMovieSceneToolsModule::Get().GetAnimationBakeHelpers(); for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers) { if (BakeHelper) { BakeHelper->StartBaking(MovieScene); } } InitCallback.ExecuteIfBound(); //if we have delay frames run them first at the LocalStartFrame - ExportOptions->WarmupFrames; if (ExportOptions->DelayBeforeStart > 0) { FFrameNumber FrameNumber = FFrameNumber(LocalStartFrame) - ExportOptions->WarmUpFrames; for (int32 Index = 0; Index < ExportOptions->DelayBeforeStart; ++Index) { TickFrame(FrameNumber, DeltaTime, MovieScene, AnimTrackAdapter, BakeHelpers, SkelMeshComps, LiveLinkClient, SourceAndMode); } } //if we have warmup frames if (ExportOptions->WarmUpFrames > 0) { for (int32 Index = -ExportOptions->WarmUpFrames.Value; Index < 0; ++Index) { FFrameNumber FrameNumber = FFrameNumber(LocalStartFrame) - FFrameNumber(Index); TickFrame(FrameNumber, DeltaTime, MovieScene, AnimTrackAdapter, BakeHelpers, SkelMeshComps, LiveLinkClient, SourceAndMode); } } //BeginRecording, which happens in the Start Callback below, records a frame so need to set things up first at first frame, even if we had any delay or warmup TickFrame(LocalStartFrame, DeltaTime, MovieScene, AnimTrackAdapter, BakeHelpers, SkelMeshComps, LiveLinkClient, SourceAndMode); StartCallback.ExecuteIfBound(LocalStartFrame); for (int32 FrameCount = 1; FrameCount <= AnimationLength; ++FrameCount) { int32 LocalIndex = LocalStartFrame + FrameCount; FFrameNumber LocalFrame(LocalIndex); TickFrame(LocalFrame, DeltaTime, MovieScene, AnimTrackAdapter, BakeHelpers, SkelMeshComps, LiveLinkClient, SourceAndMode); TickCallback.ExecuteIfBound(DeltaTime, LocalFrame); } for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers) { if (BakeHelper) { BakeHelper->StopBaking(MovieScene); } } EndCallback.ExecuteIfBound(); //now do any sequencer live link cleanup if (LiveLinkClient) { for (TPair& Item : SourceAndMode) { ULiveLinkSourceSettings* Settings = LiveLinkClient->GetSourceSettings(Item.Key); if (Settings) { Settings->Mode = Item.Value; } } } if (SequencerAlwaysSenedLiveLinkInterpolated.IsSet() && CVarAlwaysSendInterpolatedLiveLink) { CVarAlwaysSendInterpolatedLiveLink->Set(0, ECVF_SetByConsole); } return true; } //5.5 deprecated bool MovieSceneToolHelpers::ExportToAnimSequence(UAnimSequence* AnimSequence, UAnimSeqExportOption* ExportOptions, UMovieScene* MovieScene, IMovieScenePlayer* Player, USkeletalMeshComponent* SkelMesh, FMovieSceneSequenceIDRef& Template, FMovieSceneSequenceTransform& RootToLocalTransform) { if (MovieScene) { FAnimExportSequenceParameters AESP; AESP.Player = Player; AESP.RootToLocalTransform = RootToLocalTransform; UMovieSceneSequence* OwnerSceneSequence = MovieScene->GetTypedOuter(); AESP.MovieSceneSequence = OwnerSceneSequence; AESP.RootMovieSceneSequence = OwnerSceneSequence; return MovieSceneToolHelpers::ExportToAnimSequence(AnimSequence, ExportOptions, AESP, SkelMesh); } else { return false; } } /** * Retrieves a string from the MovieSceneSequence corresponding to the source takename. * * It tries to find the UTakeMetaData slate via reflection, since UTakeMetaData is defined in a plugin. * If that fails, it returns the name of the MovieSceneSequence. */ static FString GetSlateFromMovieSceneSequence(UMovieSceneSequence* MovieSceneSequence) { if (!MovieSceneSequence) { return FString(); } ULevelSequence* LevelSequence = Cast(MovieSceneSequence); if (!LevelSequence) { return MovieSceneSequence->GetName(); } // Find the UTakeMetaData class dynamically, since it is defined in a plugin. static UClass* TakeMetaDataClass = FindObject(nullptr, TEXT("/Script/TakesCore.TakeMetaData"), false); if (!TakeMetaDataClass) { return LevelSequence->GetName(); } if (UObject* MetaDataObject = LevelSequence->FindMetaDataByClass(TakeMetaDataClass)) { if (const FProperty* SlateProperty = TakeMetaDataClass->FindPropertyByName("Slate")) { if (const FStrProperty* SlateStrProperty = CastField(SlateProperty)) { return SlateStrProperty->GetPropertyValue_InContainer(MetaDataObject); } else { UE_LOG(LogMovieScene, Error, TEXT("TakesMetaData::Slate property was found, but it unexpectedly was not a String property.")); } } else { UE_LOG(LogMovieScene, Error, TEXT("TakesMetaData class was found, but it unexpectedly did not have a Slate property.")); } } return LevelSequence->GetName(); } bool MovieSceneToolHelpers::ExportToAnimSequence(UAnimSequence* AnimSequence, UAnimSeqExportOption* ExportOptions, const FAnimExportSequenceParameters& AESP, USkeletalMeshComponent* SkelMeshComp) { if (AnimSequence == nullptr || ExportOptions == nullptr || AESP.MovieSceneSequence == nullptr || AESP.RootMovieSceneSequence == nullptr || SkelMeshComp == nullptr) { UE_LOG(LogMovieScene, Error, TEXT("MovieSceneToolHelpers::ExportToAnimSequence All parameters must be valid.")); return false; } FAnimRecorderInstance AnimationRecorder; const FFrameRate SampleRate = ExportOptions->bUseCustomFrameRate ? ExportOptions->CustomFrameRate : AESP.MovieSceneSequence->GetMovieScene()->GetDisplayRate(); // Used by function that retrieves the current timecode FQualifiedFrameTime CurrentTimecode; // Prefer the rate overriden by the user, otherwise use the project setting. if (ExportOptions->bTimecodeRateOverride) { CurrentTimecode.Rate = ExportOptions->OverrideTimecodeRate; } else if (GEngine) { CurrentTimecode.Rate = GEngine->GenerateDefaultTimecodeFrameRate; } FInitAnimationCB InitCallback = FInitAnimationCB::CreateLambda([&AnimationRecorder, SampleRate, ExportOptions, SkelMeshComp, AnimSequence] { FAnimationRecordingSettings RecordingSettings; RecordingSettings.SampleFrameRate = SampleRate; RecordingSettings.Interpolation = ExportOptions->Interpolation; RecordingSettings.InterpMode = ExportOptions->CurveInterpolation; if (ExportOptions->CurveInterpolation == ERichCurveInterpMode::RCIM_Constant || ExportOptions->CurveInterpolation == ERichCurveInterpMode::RCIM_None) { RecordingSettings.TangentMode = ERichCurveTangentMode::RCTM_None; } else { RecordingSettings.TangentMode = ERichCurveTangentMode::RCTM_Auto; } RecordingSettings.Length = 0; RecordingSettings.bRemoveRootAnimation = false; RecordingSettings.bCheckDeltaTimeAtBeginning = false; RecordingSettings.bRecordTransforms = ExportOptions->bExportTransforms; RecordingSettings.bRecordMorphTargets = ExportOptions->bExportMorphTargets; RecordingSettings.bRecordAttributeCurves = ExportOptions->bExportAttributeCurves; RecordingSettings.bRecordMaterialCurves = ExportOptions->bExportMaterialCurves; RecordingSettings.bRecordInWorldSpace = ExportOptions->bRecordInWorldSpace; RecordingSettings.IncludeAnimationNames = ExportOptions->IncludeAnimationNames; RecordingSettings.ExcludeAnimationNames = ExportOptions->ExcludeAnimationNames; RecordingSettings.bTransactRecording = ExportOptions->bTransactRecording; AnimationRecorder.Init(SkelMeshComp, AnimSequence, nullptr, RecordingSettings); }); FStartAnimationCB StartCallback = FStartAnimationCB::CreateLambda([SkelMeshComp, &AnimationRecorder, &CurrentTimecode, &SampleRate, ExportOptions](FFrameNumber FrameNumber) { if (ExportOptions->bBakeTimecode) { ensureMsgf(AnimationRecorder.Recorder.IsValid(), TEXT("Unable to bake timecode due to Invalid Recorder.")); if (AnimationRecorder.Recorder.IsValid()) { // Update the time because AnimationRecorder.BeginRecording() will key the first frame. CurrentTimecode.Time = FQualifiedFrameTime(FFrameTime(FrameNumber), SampleRate).ConvertTo(CurrentTimecode.Rate); AnimationRecorder.Recorder->SetCurrentFrameTimeGetter([&CurrentTimecode]() -> TOptional { return CurrentTimecode; }); } } AnimationRecorder.BeginRecording(); SkelMeshComp->UpdateLODStatus(); }); FTickAnimationCB TickCallback = FTickAnimationCB::CreateLambda([&AnimationRecorder, &CurrentTimecode, SampleRate, ExportOptions](float DeltaTime, FFrameNumber FrameNumber) { // Update current timecode, which will be read during AnimationRecorder via the custom current time getter. if (ExportOptions->bBakeTimecode) { CurrentTimecode.Time = FQualifiedFrameTime(FFrameTime(FrameNumber), SampleRate).ConvertTo(CurrentTimecode.Rate); } AnimationRecorder.Update(DeltaTime); }); FEndAnimationCB EndCallback = FEndAnimationCB::CreateLambda([&AnimationRecorder, ExportOptions, AnimSequence, SkelMeshComp, AESP] { if (ExportOptions->bBakeTimecode) { FProcessRecordedTimeParams TimeParams; // Use the timecode attribute names in the animation settings for consistency. if (const UAnimationSettings* AnimationSettings = UAnimationSettings::Get()) { TimeParams.HoursName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.HourAttributeName.ToString(); TimeParams.MinutesName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.MinuteAttributeName.ToString(); TimeParams.SecondsName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.SecondAttributeName.ToString(); TimeParams.FramesName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.FrameAttributeName.ToString(); TimeParams.SubFramesName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.SubframeAttributeName.ToString(); TimeParams.RateName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.RateAttributeName.ToString(); TimeParams.SlateName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.TakenameAttributeName.ToString(); TimeParams.Slate = GetSlateFromMovieSceneSequence(AESP.MovieSceneSequence); } AnimationRecorder.ProcessRecordedTimes(AnimSequence, SkelMeshComp, FTimecodeBoneMethod(), TimeParams); } const bool bShowAnimationAssetCreatedToast = false; AnimationRecorder.FinishRecording(bShowAnimationAssetCreatedToast); // Clean up the getter since it references a local variable and AnimationRecorder.Recorder is managed by a shared pointer. if (ExportOptions->bBakeTimecode && AnimationRecorder.Recorder.IsValid()) { AnimationRecorder.Recorder->SetCurrentFrameTimeGetter([]() -> TOptional { return FApp::GetCurrentFrameTime(); }); } }); MovieSceneToolHelpers::BakeToSkelMeshToCallbacks(AESP, SkelMeshComp, ExportOptions, InitCallback, StartCallback, TickCallback, EndCallback); return AnimSequence->GetDataModel()->HasBeenPopulated(); } FSpawnableRestoreState::FSpawnableRestoreState(UMovieScene* MovieScene) : FSpawnableRestoreState(MovieScene, nullptr) { } FSpawnableRestoreState::FSpawnableRestoreState(UMovieScene* MovieScene, TSharedPtr InSharedPlaybackState) : bWasChanged(false) , WeakMovieScene(MovieScene) , SharedPlaybackState(InSharedPlaybackState) { auto SaveStateForSpawnable = [this](FGuid BindingGuid, ESpawnOwnership PreviousSpawnOwnership, TFunction SpawnOwnershipSetter) { // Spawnable could be in a subscene, so temporarily override it to persist throughout SpawnOwnershipMap.Add(BindingGuid, PreviousSpawnOwnership); SpawnOwnershipSetter(ESpawnOwnership::RootSequence); UMovieSceneSpawnTrack* SpawnTrack = WeakMovieScene->FindTrack(BindingGuid); if (SpawnTrack && SpawnTrack->GetAllSections().Num() > 0) { // Start a transaction that will be undone later for the modifications to the spawn track if (!bWasChanged) { GEditor->BeginTransaction(NSLOCTEXT("MovieSceneToolHelpers", "SpwanableRestoreState", "SpawnableRestoreState")); } bWasChanged = true; UMovieSceneSpawnSection* SpawnSection = Cast(SpawnTrack->GetAllSections()[0]); SpawnSection->Modify(); SpawnSection->GetChannel().Reset(); SpawnSection->GetChannel().SetDefault(true); } UMovieSceneBindingLifetimeTrack* BindingLifetimeTrack = WeakMovieScene->FindTrack(BindingGuid); if (BindingLifetimeTrack && BindingLifetimeTrack->GetAllSections().Num() > 0) { // Start a transaction that will be undone later for the modifications to the binding lifetime track if (!bWasChanged) { GEditor->BeginTransaction(NSLOCTEXT("MovieSceneToolHelpers", "SpwanableRestoreState", "SpawnableRestoreState")); } bWasChanged = true; BindingLifetimeTrack->Modify(); BindingLifetimeTrack->RemoveAllAnimationData(); // Add a single infinite section UMovieSceneSection* NewSection = NewObject(BindingLifetimeTrack, UMovieSceneBindingLifetimeSection::StaticClass(), NAME_None, RF_Transactional); check(NewSection); NewSection->SetRange(TRange::All()); BindingLifetimeTrack->AddSection(*NewSection); } }; for (int32 SpawnableIndex = 0; SpawnableIndex < WeakMovieScene->GetSpawnableCount(); ++SpawnableIndex) { FMovieSceneSpawnable& Spawnable = WeakMovieScene->GetSpawnable(SpawnableIndex); SaveStateForSpawnable(Spawnable.GetGuid(), Spawnable.GetSpawnOwnership(), [&Spawnable](ESpawnOwnership SpawnOwnership) { Spawnable.SetSpawnOwnership(SpawnOwnership);}); } TSharedPtr SharedPlaybackStateToUse = SharedPlaybackState; if (!SharedPlaybackStateToUse) { SharedPlaybackStateToUse = MovieSceneHelpers::CreateTransientSharedPlaybackState(GEditor->GetEditorWorldContext().World(), WeakMovieScene->GetTypedOuter()); } ensure(SharedPlaybackStateToUse.IsValid()); if (UMovieSceneSequence* Sequence = WeakMovieScene->GetTypedOuter()) { if (FMovieSceneBindingReferences* BindingReferences = Sequence->GetBindingReferences()) { for (FMovieSceneBindingReference& BindingReference : BindingReferences->GetAllReferences()) { if (BindingReference.CustomBinding) { if (UMovieSceneSpawnableBindingBase* SpawnableBinding = BindingReference.CustomBinding->AsSpawnable(SharedPlaybackStateToUse.ToSharedRef())) { SaveStateForSpawnable(BindingReference.ID, SpawnableBinding->SpawnOwnership, [SpawnableBinding](ESpawnOwnership SpawnOwnership) { SpawnableBinding->SpawnOwnership = SpawnOwnership; }); } } } } } if (bWasChanged) { GEditor->EndTransaction(); } } FSpawnableRestoreState::~FSpawnableRestoreState() { if (!bWasChanged || !WeakMovieScene.IsValid()) { return; } // Restore spawnable owners for (int32 SpawnableIndex = 0; SpawnableIndex < WeakMovieScene->GetSpawnableCount(); ++SpawnableIndex) { FMovieSceneSpawnable& Spawnable = WeakMovieScene->GetSpawnable(SpawnableIndex); if (ESpawnOwnership* SpawnOwnership = SpawnOwnershipMap.Find(Spawnable.GetGuid())) { Spawnable.SetSpawnOwnership(*SpawnOwnership); } } TSharedPtr SharedPlaybackStateToUse = SharedPlaybackState; if (!SharedPlaybackStateToUse) { // Supporting deprecated path where we have no SharedPlaybackState UE::MovieScene::FSharedPlaybackStateCreateParams CreateParams; CreateParams.PlaybackContext = GEditor->GetEditorWorldContext().World(); UMovieSceneSequence* ThisSequence = WeakMovieScene->GetTypedOuter(); SharedPlaybackStateToUse = MakeShared(*ThisSequence, CreateParams); FMovieSceneEvaluationState State; SharedPlaybackStateToUse->AddCapabilityRaw(&State); State.AssignSequence(MovieSceneSequenceID::Root, *ThisSequence, SharedPlaybackStateToUse.ToSharedRef()); } ensure(SharedPlaybackStateToUse.IsValid()); if (UMovieSceneSequence* Sequence = WeakMovieScene->GetTypedOuter()) { if (FMovieSceneBindingReferences* BindingReferences = Sequence->GetBindingReferences()) { for (FMovieSceneBindingReference& BindingReference : BindingReferences->GetAllReferences()) { if (BindingReference.CustomBinding) { if (UMovieSceneSpawnableBindingBase* SpawnableBinding = BindingReference.CustomBinding->AsSpawnable(SharedPlaybackStateToUse.ToSharedRef())) { if (ESpawnOwnership* SpawnOwnership = SpawnOwnershipMap.Find(BindingReference.ID)) { SpawnableBinding->SpawnOwnership = *SpawnOwnership; } } } } } } // Restore modified spawned sections bool bOrigSquelchTransactionNotification = GEditor->bSquelchTransactionNotification; GEditor->bSquelchTransactionNotification = true; GEditor->UndoTransaction(false); GEditor->bSquelchTransactionNotification = bOrigSquelchTransactionNotification; } void MovieSceneToolHelpers::GetParents(TArray& Parents, const UObject* InObject) { const AActor* Actor = Cast(InObject); if (Actor) { Parents.Emplace(Actor); const AActor* ParentActor = Actor->GetAttachParentActor(); if (ParentActor) { GetParents(Parents, ParentActor); } } } /** This is not that scalable moving forward with stuff like the control rig , need a better caching solution there */ bool MovieSceneToolHelpers::GetParentTM(FTransform& CurrentRefTM, const TSharedPtr& Sequencer, UObject* ParentObject, FFrameTime KeyTime) { UMovieSceneSequence* Sequence = Sequencer->GetFocusedMovieSceneSequence(); if (!Sequence) { return false; } FGuid ObjectBinding = Sequencer->FindCachedObjectId(*ParentObject, Sequencer->GetFocusedTemplateID()); if (!ObjectBinding.IsValid()) { return false; } const FMovieSceneBinding* Binding = Sequence->GetMovieScene()->FindBinding(ObjectBinding); if (!Binding) { return false; } //TODO this doesn't handle blended sections at all for (const UMovieSceneTrack* Track : Binding->GetTracks()) { const UMovieScene3DTransformTrack* TransformTrack = Cast(Track); if (!TransformTrack) { continue; } //we used to loop between sections here and only evaluate if we are in a section, this will give us wrong transfroms though //when in between or outside of the section range. We still want to evaluate, though it is heavy. const FMovieSceneEvaluationTrack* EvalTrack = MovieSceneToolHelpers::GetEvaluationTrack(Sequencer.Get(), TransformTrack->GetSignature()); if (EvalTrack) { FVector ParentKeyPos; FRotator ParentKeyRot; GetLocationAtTime(EvalTrack, ParentObject, KeyTime, ParentKeyPos, ParentKeyRot, Sequencer); CurrentRefTM = FTransform(ParentKeyRot, ParentKeyPos); return true; } } return false; } FTransform MovieSceneToolHelpers::GetRefFrameFromParents(const TSharedPtr& Sequencer, const TArray& Parents, FFrameTime KeyTime) { FTransform RefTM = FTransform::Identity; FTransform ParentRefTM = FTransform::Identity; for (const UObject* Object : Parents) { const AActor* Actor = Cast(Object); if (Actor != nullptr) { if (Actor->GetRootComponent() != nullptr && Actor->GetRootComponent()->GetAttachParent() != nullptr) { //Always get local ref tm since we don't know which parent is in the sequencer or not. if (!GetParentTM(ParentRefTM, Sequencer, Actor->GetRootComponent()->GetAttachParent()->GetOwner(), KeyTime)) { AActor* Parent = Actor->GetRootComponent()->GetAttachParent()->GetOwner(); if (Parent && Parent->GetRootComponent()) { ParentRefTM = Parent->GetRootComponent()->GetRelativeTransform(); } else { continue; } } RefTM = ParentRefTM * RefTM; } } else { const USceneComponent* SceneComponent = Cast(Object); FTransform CurrentRefTM = FTransform::Identity; UObject* ParentObject = SceneComponent->GetAttachParent() == SceneComponent->GetOwner()->GetRootComponent() ? static_cast(SceneComponent->GetOwner()) : SceneComponent->GetAttachParent(); if (SceneComponent->GetAttachParent() != nullptr) { if (!GetParentTM(CurrentRefTM, Sequencer, ParentObject, KeyTime)) { CurrentRefTM = RefTM * SceneComponent->GetAttachParent()->GetRelativeTransform(); } } RefTM = CurrentRefTM * RefTM; } } return RefTM; } void MovieSceneToolHelpers::GetLocationAtTime(const FMovieSceneEvaluationTrack* Track, UObject* Object, FFrameTime KeyTime, FVector& KeyPos, FRotator& KeyRot, const TSharedPtr& Sequencer) { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // TODO: Reimplement trajectory rendering // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ UE_MOVIESCENE_TODO(Reimplement trajectory rendering) } USkeletalMeshComponent* MovieSceneToolHelpers::AcquireSkeletalMeshFromObject(UObject* BoundObject) { if (AActor* Actor = Cast(BoundObject)) { for (UActorComponent* Component : Actor->GetComponents()) { if (USkeletalMeshComponent* SkeletalMeshComp = Cast(Component)) { return SkeletalMeshComp; } } } else if (USkeletalMeshComponent* SkeletalMeshComponent = Cast(BoundObject)) { if (SkeletalMeshComponent->GetSkeletalMeshAsset()) { return SkeletalMeshComponent; } } return nullptr; } static void GetSequenceSceneComponentWorldTransforms(USceneComponent* SceneComponent, IMovieScenePlayer* Player, UMovieSceneSequence* InSequence, FMovieSceneSequenceIDRef Template, const TArray& Frames, TArray& OutTransforms) { // Hack: static system interrogator for now to avoid re-allocating UObjects all the time static UE::MovieScene::FSystemInterrogator Interrogator; Interrogator.Reset(); Interrogator.ImportTransformHierarchy(SceneComponent, Player, Template); if (Frames[0] < Frames[Frames.Num() - 1]) { for (const FFrameNumber& FrameNumber : Frames) { const FFrameTime FrameTime(FrameNumber); Interrogator.AddInterrogation(FrameTime); } Interrogator.Update(); Interrogator.QueryWorldSpaceTransforms(SceneComponent, OutTransforms); } else //time is backward we need to do swap things around { for (int32 Index = Frames.Num() - 1; Index >= 0; --Index) { const FFrameTime FrameTime(Frames[Index]); Interrogator.AddInterrogation(FrameTime); } Interrogator.Update(); TArray WorldTransforms; Interrogator.QueryWorldSpaceTransforms(SceneComponent, WorldTransforms); OutTransforms.SetNum(0); OutTransforms.Reserve(WorldTransforms.Num()); for (int32 Index2 = WorldTransforms.Num() - 1; Index2 >= 0; --Index2) { OutTransforms.Add(WorldTransforms[Index2]); } } Interrogator.Reset(); } static void GetSequencerActorWorldTransforms(IMovieScenePlayer* Player, FMovieSceneSequenceTransform RootToLocalTransform, UMovieSceneSequence* InSequence, FMovieSceneSequenceIDRef Template, const FActorForWorldTransforms& ActorSelection, const TArray& Frames, TArray& OutTransforms) { USceneComponent* SceneComponent = nullptr; AActor* Actor = ActorSelection.Actor.Get(); if (Actor) { SceneComponent = Actor->GetRootComponent(); } else { SceneComponent = ActorSelection.Component.IsValid() ? Cast(ActorSelection.Component.Get()) : nullptr; if (SceneComponent) { Actor = SceneComponent->GetTypedOuter(); } } if(Actor && SceneComponent) { USkeletalMeshComponent* SkelMeshComp = Cast(SceneComponent); if (!SkelMeshComp) { SkelMeshComp = MovieSceneToolHelpers::AcquireSkeletalMeshFromObject(Actor); } if (UMovieScene* MovieScene = InSequence->GetMovieScene()) { OutTransforms.SetNum(Frames.Num()); FFrameRate TickResolution = MovieScene->GetTickResolution(); FFrameRate DisplayRate = MovieScene->GetDisplayRate(); const TArray& BakeHelpers = FMovieSceneToolsModule::Get().GetAnimationBakeHelpers(); for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers) { if (BakeHelper) { BakeHelper->StartBaking(MovieScene); } } for (int32 Index = 0; Index < Frames.Num(); ++Index) { FFrameNumber FrameNumber = Frames[Index]; FFrameTime GlobalTime = RootToLocalTransform.Inverse().TryTransformTime(FrameNumber).Get(FrameNumber); //player evals in root time so need to go back to it. for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers) { if (BakeHelper) { BakeHelper->PreEvaluation(MovieScene, FrameNumber); } } FMovieSceneContext Context = FMovieSceneContext(FMovieSceneEvaluationRange(GlobalTime, TickResolution), Player->GetPlaybackStatus()).SetHasJumped(true); if (Index == 0) // similar with baking first time in we need to evaluate twice (think due to double buffering that happens with skel mesh components). { Player->GetEvaluationTemplate().EvaluateSynchronousBlocking(Context); } Player->GetEvaluationTemplate().EvaluateSynchronousBlocking(Context); const FConstraintsManagerController& Controller = FConstraintsManagerController::Get(Actor->GetWorld()); Controller.EvaluateAllConstraints(); for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers) { if (BakeHelper) { BakeHelper->PostEvaluation(MovieScene, FrameNumber); } } AActor* Parent = ActorSelection.Actor.Get(); while (Parent) { TArray MeshComps; Parent->GetComponents(MeshComps, true); for (USkeletalMeshComponent* MeshComp : MeshComps) { MeshComp->TickAnimation(0.03f, false); MeshComp->RefreshBoneTransforms(); MeshComp->RefreshFollowerComponents(); MeshComp->UpdateComponentToWorld(); MeshComp->FinalizeBoneTransform(); MeshComp->MarkRenderTransformDirty(); MeshComp->MarkRenderDynamicDataDirty(); } Parent = Parent->GetAttachParentActor(); } OutTransforms[Index] = (SkelMeshComp && ActorSelection.SocketName != NAME_None) ? SkelMeshComp->GetSocketTransform(ActorSelection.SocketName) : SceneComponent->GetComponentToWorld(); } for (IMovieSceneToolsAnimationBakeHelper* BakeHelper : BakeHelpers) { if (BakeHelper) { BakeHelper->StopBaking(MovieScene); } } } } } static void GetNonSequencerActorWorldTransforms(IMovieScenePlayer* Player, FMovieSceneSequenceTransform RootToLocalTransform, UMovieSceneSequence* InSequence, FMovieSceneSequenceIDRef Template, const FActorForWorldTransforms& ActorSelection, const TArray& Frames, TArray& OutTransforms) { FName SocketName = ActorSelection.SocketName; AActor* Actor = ActorSelection.Actor.Get(); FActorForWorldTransforms NewActorSelection = ActorSelection; FTransform WorldTransform = FTransform::Identity; if (Actor) { do { FGuid ActorHandle = GetHandleToObject(Actor, InSequence, Player, Template,false); if (ActorHandle.IsValid() && Frames.Num() > 0) { if (InSequence->GetMovieScene()->FindTrack(ActorHandle)) { GetSequencerActorWorldTransforms(Player, RootToLocalTransform, InSequence, Template, NewActorSelection, Frames, OutTransforms); for (FTransform& OutTransform : OutTransforms) { OutTransform = WorldTransform * OutTransform; } return; } } if (Actor->GetRootComponent()->DoesSocketExist(SocketName)) { WorldTransform = WorldTransform * Actor->GetRootComponent()->GetSocketTransform(SocketName); } else { WorldTransform = WorldTransform * Actor->GetRootComponent()->GetRelativeTransform(); } SocketName = Actor->GetAttachParentSocketName(); Actor = Actor->GetAttachParentActor(); NewActorSelection.Actor = Actor; if (Actor) { NewActorSelection.SocketName = SocketName; } } while (Actor); //if we get to here world transform is what we want } int32 NumFrames = Frames.Num() > 0 ? Frames.Num() : 1; //if no frames passed in we actually have one OutTransforms.SetNumUninitialized(NumFrames); for (FTransform& OutTransform : OutTransforms) { OutTransform = WorldTransform; } } void MovieSceneToolHelpers::GetActorWorldTransforms(ISequencer* Sequencer, const FActorForWorldTransforms& ActorSelection, const TArray& Frames, TArray& OutWorldTransforms) { FMovieSceneSequenceIDRef Template = Sequencer->GetFocusedTemplateID(); const bool bAvoidEvaluates = (Frames.Num() == 0) || (Frames.Num() == 1 && Frames[0] == Sequencer->GetLocalTime().Time.RoundToFrame()); FGuid ObjectHandle = Sequencer->GetHandleToObject(ActorSelection.Actor.Get(), false); if (!bAvoidEvaluates && ObjectHandle.IsValid()) { if (Sequencer->GetFocusedMovieSceneSequence()->GetMovieScene()->FindTrack(ObjectHandle)) { GetSequencerActorWorldTransforms(Sequencer, Sequencer->GetFocusedMovieSceneSequenceTransform(), Sequencer->GetFocusedMovieSceneSequence(), Template, ActorSelection, Frames, OutWorldTransforms); return; } } ObjectHandle = Sequencer->GetHandleToObject(ActorSelection.Component.Get(), false); if (!bAvoidEvaluates && ObjectHandle.IsValid()) { if (Sequencer->GetFocusedMovieSceneSequence()->GetMovieScene()->FindTrack(ObjectHandle)) { GetSequencerActorWorldTransforms(Sequencer, Sequencer->GetFocusedMovieSceneSequenceTransform(), Sequencer->GetFocusedMovieSceneSequence(), Template, ActorSelection, Frames, OutWorldTransforms); return; } } if (bAvoidEvaluates) { TArray NoFrame; //this will make sure we don't evaluate GetNonSequencerActorWorldTransforms(Sequencer, Sequencer->GetFocusedMovieSceneSequenceTransform(), Sequencer->GetFocusedMovieSceneSequence(), Template, ActorSelection, NoFrame, OutWorldTransforms); } else { GetNonSequencerActorWorldTransforms(Sequencer, Sequencer->GetFocusedMovieSceneSequenceTransform(), Sequencer->GetFocusedMovieSceneSequence(), Template, ActorSelection, Frames, OutWorldTransforms); } } bool MovieSceneToolHelpers::IsValidAsset(UMovieSceneSequence* Sequence, const FAssetData& InAssetData) { if (!Sequence) { return false; } FAssetReferenceFilterContext AssetReferenceFilterContext; AssetReferenceFilterContext.AddReferencingAsset(FAssetData(Sequence)); TSharedPtr AssetReferenceFilter = GEditor->MakeAssetReferenceFilter(AssetReferenceFilterContext); FText FailureReason; if (AssetReferenceFilter.IsValid() && !AssetReferenceFilter->PassesFilter(InAssetData, &FailureReason)) { UE_LOG(LogMovieScene, Warning, TEXT("%s cannot reference because: %s"), *GetNameSafe(Sequence), *FailureReason.ToString()); return false; } return true; } void MovieSceneToolHelpers::SetOrAddKey(TMovieSceneChannelData& ChannelData, FFrameNumber Time, float Value, const EMovieSceneKeyInterpolation Interpolation) { int32 ExistingIndex = ChannelData.FindKey(Time); if (ExistingIndex != INDEX_NONE) { FMovieSceneFloatValue& FloatValue = ChannelData.GetValues()[ExistingIndex]; //-V758 FloatValue.Value = Value; } else { FMovieSceneFloatValue NewKey(Value); ERichCurveTangentWeightMode WeightedMode = RCTWM_WeightedNone; NewKey.InterpMode = ERichCurveInterpMode::RCIM_Cubic; NewKey.TangentMode = ERichCurveTangentMode::RCTM_Auto; NewKey.Tangent.ArriveTangent = 0.0f; NewKey.Tangent.LeaveTangent = 0.0f; NewKey.Tangent.TangentWeightMode = WeightedMode; NewKey.Tangent.ArriveTangentWeight = 0.0f; NewKey.Tangent.LeaveTangentWeight = 0.0f; switch (Interpolation) { case EMovieSceneKeyInterpolation::SmartAuto: { NewKey.InterpMode = ERichCurveInterpMode::RCIM_Cubic; NewKey.TangentMode = ERichCurveTangentMode::RCTM_SmartAuto; } break; case EMovieSceneKeyInterpolation::Auto: { NewKey.InterpMode = ERichCurveInterpMode::RCIM_Cubic; NewKey.TangentMode = ERichCurveTangentMode::RCTM_Auto; } break; case EMovieSceneKeyInterpolation::User: { NewKey.InterpMode = ERichCurveInterpMode::RCIM_Cubic; NewKey.TangentMode = ERichCurveTangentMode::RCTM_User; } break; case EMovieSceneKeyInterpolation::Break: { NewKey.InterpMode = ERichCurveInterpMode::RCIM_Cubic; NewKey.TangentMode = ERichCurveTangentMode::RCTM_Auto; } break; case EMovieSceneKeyInterpolation::Linear: { NewKey.InterpMode = ERichCurveInterpMode::RCIM_Linear; } break; case EMovieSceneKeyInterpolation::Constant: { NewKey.InterpMode = ERichCurveInterpMode::RCIM_Constant; } break; } ChannelData.AddKey(Time, NewKey); } } void MovieSceneToolHelpers::SetOrAddKey(TMovieSceneChannelData& ChannelData, FFrameNumber Time, const FMovieSceneFloatValue& Value) { int32 ExistingIndex = ChannelData.FindKey(Time); if (ExistingIndex != INDEX_NONE) { FMovieSceneFloatValue& FloatValue = ChannelData.GetValues()[ExistingIndex]; //-V758 FloatValue.Value = Value.Value; } else { ChannelData.AddKey(Time, Value); } } void MovieSceneToolHelpers::SetOrAddKey(TMovieSceneChannelData& ChannelData, FFrameNumber Time, float Value, float ArriveTangent, float LeaveTangent, ERichCurveInterpMode InterpMode, ERichCurveTangentMode TangentMode, FFrameRate FrameRate, ERichCurveTangentWeightMode WeightedMode, float ArriveTangentWeight, float LeaveTangentWeight) { FMovieSceneFloatValue NewKey(Value); NewKey.InterpMode = InterpMode; NewKey.TangentMode = TangentMode; NewKey.Tangent.ArriveTangent = ArriveTangent / FrameRate.AsDecimal(); NewKey.Tangent.LeaveTangent = LeaveTangent / FrameRate.AsDecimal(); NewKey.Tangent.TangentWeightMode = WeightedMode; NewKey.Tangent.ArriveTangentWeight = ArriveTangentWeight; NewKey.Tangent.LeaveTangentWeight = LeaveTangentWeight; ChannelData.AddKey(Time, NewKey); MovieSceneToolHelpers::SetOrAddKey(ChannelData, Time, NewKey); } void MovieSceneToolHelpers::SetOrAddKey(TMovieSceneChannelData& ChannelData, FFrameNumber Time, double Value, float ArriveTangent, float LeaveTangent, ERichCurveInterpMode InterpMode, ERichCurveTangentMode TangentMode, FFrameRate FrameRate, ERichCurveTangentWeightMode WeightedMode, float ArriveTangentWeight, float LeaveTangentWeight) { FMovieSceneDoubleValue NewKey(Value); NewKey.InterpMode = InterpMode; NewKey.TangentMode = TangentMode; NewKey.Tangent.ArriveTangent = ArriveTangent / FrameRate.AsDecimal(); NewKey.Tangent.LeaveTangent = LeaveTangent / FrameRate.AsDecimal(); NewKey.Tangent.TangentWeightMode = WeightedMode; NewKey.Tangent.ArriveTangentWeight = ArriveTangentWeight; NewKey.Tangent.LeaveTangentWeight = LeaveTangentWeight; MovieSceneToolHelpers::SetOrAddKey(ChannelData, Time, NewKey); } void MovieSceneToolHelpers::SetOrAddKey(TMovieSceneChannelData& ChannelData, FFrameNumber Time, FMovieSceneDoubleValue Value) { int32 ExistingIndex = ChannelData.FindKey(Time); if (ExistingIndex != INDEX_NONE) { FMovieSceneDoubleValue& DoubleValue = ChannelData.GetValues()[ExistingIndex]; //-V758 DoubleValue.Value = Value.Value; } else { ChannelData.AddKey(Time, Value); } } void MovieSceneToolHelpers::SetOrAddKey(TMovieSceneChannelData& ChannelData, FFrameNumber Time, double Value, const EMovieSceneKeyInterpolation Interpolation) { int32 ExistingIndex = ChannelData.FindKey(Time); if (ExistingIndex != INDEX_NONE) { FMovieSceneDoubleValue& DoubleValue = ChannelData.GetValues()[ExistingIndex]; //-V758 DoubleValue.Value = Value; } else { FMovieSceneDoubleValue NewKey(Value); ERichCurveTangentWeightMode WeightedMode = RCTWM_WeightedNone; NewKey.InterpMode = ERichCurveInterpMode::RCIM_Cubic; NewKey.TangentMode = ERichCurveTangentMode::RCTM_Auto; NewKey.Tangent.ArriveTangent = 0.0f; NewKey.Tangent.LeaveTangent = 0.0f; NewKey.Tangent.TangentWeightMode = WeightedMode; NewKey.Tangent.ArriveTangentWeight = 0.0f; NewKey.Tangent.LeaveTangentWeight = 0.0f; switch (Interpolation) { case EMovieSceneKeyInterpolation::SmartAuto: { NewKey.InterpMode = ERichCurveInterpMode::RCIM_Cubic; NewKey.TangentMode = ERichCurveTangentMode::RCTM_SmartAuto; } break; case EMovieSceneKeyInterpolation::Auto: { NewKey.InterpMode = ERichCurveInterpMode::RCIM_Cubic; NewKey.TangentMode = ERichCurveTangentMode::RCTM_Auto; } break; case EMovieSceneKeyInterpolation::User: { NewKey.InterpMode = ERichCurveInterpMode::RCIM_Cubic; NewKey.TangentMode = ERichCurveTangentMode::RCTM_User; } break; case EMovieSceneKeyInterpolation::Break: { NewKey.InterpMode = ERichCurveInterpMode::RCIM_Cubic; NewKey.TangentMode = ERichCurveTangentMode::RCTM_Auto; } break; case EMovieSceneKeyInterpolation::Linear: { NewKey.InterpMode = ERichCurveInterpMode::RCIM_Linear; } break; case EMovieSceneKeyInterpolation::Constant: { NewKey.InterpMode = ERichCurveInterpMode::RCIM_Constant; } break; } ChannelData.AddKey(Time, NewKey); } } void MovieSceneToolHelpers::GetActorWorldTransforms(IMovieScenePlayer* Player, UMovieSceneSequence* InSequence, FMovieSceneSequenceIDRef Template, const FActorForWorldTransforms& ActorSelection, const TArray& Frames, TArray& OutWorldTransforms) { FGuid ActorHandle = GetHandleToObject(ActorSelection.Actor.Get(), InSequence, Player, Template,false); FGuid ComponentHandle = GetHandleToObject(ActorSelection.Component.Get(), InSequence, Player, Template,false); FMovieSceneSequenceTransform RootToLocalTransform; if (ActorHandle.IsValid() || ComponentHandle.IsValid()) { //we can have handles but if they don't have a transform track the interrogator will return identity bool bHaveTransformTrack = false; if (ActorHandle.IsValid()) { if (InSequence->GetMovieScene()->FindTrack(ActorHandle)) { bHaveTransformTrack = true; } } if (ComponentHandle.IsValid()) { if (InSequence->GetMovieScene()->FindTrack(ComponentHandle)) { bHaveTransformTrack = true; } } if (bHaveTransformTrack) { GetSequencerActorWorldTransforms(Player, RootToLocalTransform,InSequence, Template, ActorSelection, Frames, OutWorldTransforms); } else { GetNonSequencerActorWorldTransforms(Player, RootToLocalTransform, InSequence, Template, ActorSelection, Frames, OutWorldTransforms); } } else { GetNonSequencerActorWorldTransforms(Player, RootToLocalTransform, InSequence, Template, ActorSelection, Frames, OutWorldTransforms); } } void MovieSceneToolHelpers::GetActorParents(const FActorForWorldTransforms& ActorSelection, TArray& OutParentActors) { if (ActorSelection.Actor.IsValid()) { //has component so parent is next component if (ActorSelection.Component.IsValid() && ActorSelection.Component->GetAttachParent()) { FActorForWorldTransforms OutParentActor; OutParentActor.Actor = ActorSelection.Actor; OutParentActor.Component = ActorSelection.Component->GetAttachParent(); OutParentActors.AddUnique(OutParentActor); GetActorParents(OutParentActor, OutParentActors); } else if (AActor* ParentActor = ActorSelection.Actor->GetAttachParentActor()) { FActorForWorldTransforms OutParentActor; OutParentActor.Actor = ParentActor; OutParentActors.AddUnique(OutParentActor); GetActorParents(OutParentActor, OutParentActors); } } } void MovieSceneToolHelpers::GetActorParentsWithAttachments(ISequencer* Sequencer, const FActorForWorldTransforms& ActorSelection, TArray& OutParentActors) { if (Sequencer == nullptr) { return; } UMovieScene* FocusedMovieScene = Sequencer->GetFocusedMovieSceneSequence()->GetMovieScene(); if (!FocusedMovieScene) { return; } if (ActorSelection.Actor.IsValid()) { FGuid Guid; if (ActorSelection.Component.Get()) { Guid = Sequencer->GetHandleToObject(ActorSelection.Component.Get(), false); } if (!Guid.IsValid()) { Guid = Sequencer->GetHandleToObject(ActorSelection.Actor.Get(), false); } if (Guid.IsValid()) { for (UMovieSceneTrack* Track : FocusedMovieScene->FindTracks(UMovieScene3DConstraintTrack::StaticClass(), Guid)) { if (UMovieScene3DConstraintTrack* ConstraintTrack = Cast(Track)) { for (UMovieSceneSection* ConstraintSection : ConstraintTrack->GetAllSections()) { FMovieSceneObjectBindingID ConstraintBindingID = (Cast(ConstraintSection))->GetConstraintBindingID(); if (auto BoundObjectsView = ConstraintBindingID.ResolveBoundObjects(Sequencer->GetFocusedTemplateID(), *Sequencer); BoundObjectsView.Num()) { TWeakObjectPtr<> ParentObject = BoundObjectsView[0]; FActorForWorldTransforms OutParentActor; OutParentActor.Actor = Cast(ParentObject.Get());; OutParentActors.AddUnique(OutParentActor); GetActorParents(OutParentActor, OutParentActors); } } } } } else { //has component so parent is next component if (ActorSelection.Component.IsValid() && ActorSelection.Component->GetAttachParent()) { FActorForWorldTransforms OutParentActor; OutParentActor.Actor = ActorSelection.Actor; OutParentActor.Component = ActorSelection.Component->GetAttachParent(); OutParentActors.AddUnique(OutParentActor); GetActorParents(OutParentActor, OutParentActors); } else if (AActor* ParentActor = ActorSelection.Actor->GetAttachParentActor()) { FActorForWorldTransforms OutParentActor; OutParentActor.Actor = ParentActor; OutParentActors.AddUnique(OutParentActor); GetActorParents(OutParentActor, OutParentActors); } } } } void MovieSceneToolHelpers::GetActorsAndParentsKeyFrames(ISequencer* Sequencer, const FActorForWorldTransforms& InActor, const FFrameNumber& StartFrame, const FFrameNumber& EndFrame, TSortedMap& OutFrameMap) { TArray FramesToUse; TArray ParentActors; ParentActors.Add(InActor); GetActorParentsWithAttachments(Sequencer, InActor, ParentActors); for (FActorForWorldTransforms& Actor : ParentActors) { FGuid Guid; if (Actor.Component.Get()) { Guid = Sequencer->GetHandleToObject(Actor.Component.Get(), false); } if (!Guid.IsValid()) { Guid = Sequencer->GetHandleToObject(Actor.Actor.Get(), false); } if (Guid.IsValid()) { if (UMovieScene3DTransformSection* TransformSection = MovieSceneToolHelpers::GetTransformSection(Sequencer, Guid)) { TArrayView Channels = TransformSection->GetChannelProxy().GetChannels(); TArray TransformFrameTimes = FMovieSceneConstraintChannelHelper::GetTransformTimes( Channels, StartFrame, EndFrame); for (FFrameNumber& FrameNumber : TransformFrameTimes) { OutFrameMap.Add(FrameNumber,FrameNumber); } } } } } void MovieSceneToolHelpers::CalculateFramesBetween( const UMovieScene* MovieScene, FFrameNumber StartFrame, FFrameNumber EndFrame, int32 FrameInc, TArray& OutFrames) { const bool bReverse = StartFrame > EndFrame; if (bReverse) { Swap(StartFrame, EndFrame); } //if the end frame is the last frame, we move it back one tick otherwise it's possible when evaluating it won't evaluate. if (EndFrame == MovieScene->GetPlaybackRange().GetUpperBoundValue()) { --EndFrame; } const FFrameRate TickResolution = MovieScene->GetTickResolution(); const FFrameRate DisplayResolution = MovieScene->GetDisplayRate(); const FFrameNumber StartTimeInDisplay = FFrameRate::TransformTime(FFrameTime(StartFrame), TickResolution, DisplayResolution).FloorToFrame(); const FFrameNumber EndTimeInDisplay = FFrameRate::TransformTime(FFrameTime(EndFrame), TickResolution, DisplayResolution).CeilToFrame(); OutFrames.Reserve(EndTimeInDisplay.Value - StartTimeInDisplay.Value + 1); if (bReverse) { int32 FrameIndex = 0; for (FFrameNumber DisplayFrameNumber = EndTimeInDisplay; DisplayFrameNumber >= StartTimeInDisplay; --DisplayFrameNumber) { if (FrameIndex % FrameInc == 0) { FFrameNumber TickFrameNumber = FFrameRate::TransformTime(FFrameTime(DisplayFrameNumber), DisplayResolution, TickResolution).FrameNumber; OutFrames.Add(TickFrameNumber); } ++FrameIndex; } } else { int32 FrameIndex = 0; for (FFrameNumber DisplayFrameNumber = StartTimeInDisplay; DisplayFrameNumber <= EndTimeInDisplay; ++DisplayFrameNumber) { if (FrameIndex % FrameInc == 0) { FFrameNumber TickFrameNumber = FFrameRate::TransformTime(FFrameTime(DisplayFrameNumber), DisplayResolution, TickResolution).FrameNumber; OutFrames.Add(TickFrameNumber); } ++FrameIndex; } } } bool MovieSceneToolHelpers::OptimizeSection(const FKeyDataOptimizationParams& InParams, UMovieSceneSection* InSection) { TArrayView FloatChannels = InSection->GetChannelProxy().GetChannels(); for (FMovieSceneFloatChannel* Channel : FloatChannels) { Channel->Optimize(InParams); } TArrayView DoubleChannels = InSection->GetChannelProxy().GetChannels(); for (FMovieSceneDoubleChannel* Channel : DoubleChannels) { Channel->Optimize(InParams); } return true; } void MovieSceneToolHelpers::SplitSectionsByBlendType(EMovieSceneBlendType BlendType, const TArray& InSections, TArray& Sections, TArray& OutBlendSections) { for (UMovieSceneSection* InSection : InSections) { if (InSection && InSection->IsActive() && InSection->GetBlendType().IsValid()) { if (InSection->GetBlendType().Get() == BlendType) { OutBlendSections.Add(InSection); } else { Sections.Add(InSection); } } } } bool MovieSceneToolHelpers::CollapseSection(TSharedPtr& SequencerPtr, UMovieSceneTrack* OwnerTrack, TArray Sections, const FBakingAnimationKeySettings& InSettings) { if (SequencerPtr.IsValid() && Sections.Num() > 1 && OwnerTrack) { TRange Range(InSettings.StartFrame, InSettings.EndFrame); //we get the first absolute section or if no absolute sections, first additive, // that's the one we collapse onto UMovieSceneSection* BaseSection = nullptr; FScopedTransaction Transaction(NSLOCTEXT("MovieSceneTools", "CollapseAllSections", "Collapse All Sections")); bool bTrackModified = false; for (int32 SectionIndex = Sections.Num() - 1; SectionIndex > 0; --SectionIndex) { BaseSection = Sections[SectionIndex - 1]; UMovieSceneSection* Section = Sections[SectionIndex]; if (BaseSection && Section && BaseSection->GetBlendType().IsValid() && Section->GetBlendType().IsValid()) { if (bTrackModified == false) { OwnerTrack->Modify(); bTrackModified = true; } BaseSection->Modify(); TArray TrackSections = OwnerTrack->GetAllSections(); if (Section->IsActive())//active sections merge them { TArrayView BaseFloatChannels = BaseSection->GetChannelProxy().GetChannels(); TArrayView BaseDoubleChannels = BaseSection->GetChannelProxy().GetChannels(); if (BaseDoubleChannels.Num() > 0) { const int StartIndex = 0; const int EndIndex = BaseDoubleChannels.Num() - 1; MovieSceneToolHelpers::MergeSections(BaseSection, Section, StartIndex, EndIndex, Range, TrackSections); } else if (BaseFloatChannels.Num() > 0) { const int StartIndex = 0; const int EndIndex = BaseFloatChannels.Num() - 1; MovieSceneToolHelpers::MergeSections(BaseSection, Section,StartIndex, EndIndex, Range, TrackSections); } } //if merging override onto an additive we change it to an additive if (Section->GetBlendType().Get() == EMovieSceneBlendType::Override && (BaseSection->GetBlendType().Get() == EMovieSceneBlendType::Additive)) { BaseSection->SetBlendType(EMovieSceneBlendType::Override); } TArray AllSections = OwnerTrack->GetAllSections(); int32 TrackSectionIndex = INDEX_NONE; if (AllSections.Find(Section, TrackSectionIndex)) { if (TrackSectionIndex != INDEX_NONE) { OwnerTrack->RemoveSectionAt(TrackSectionIndex); } } } } if (InSettings.bReduceKeys && BaseSection) { FKeyDataOptimizationParams Params; Params.bAutoSetInterpolation = true; Params.Tolerance = InSettings.Tolerance; FMovieSceneChannelProxy& ChannelProxy = BaseSection->GetChannelProxy(); TArrayView FloatChannels = ChannelProxy.GetChannels(); for (FMovieSceneFloatChannel* Channel : FloatChannels) { Channel->Optimize(Params); //should also auto tangent } } //reset everything back SequencerPtr->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded); } return true; } UMovieScene3DTransformSection* MovieSceneToolHelpers::GetTransformSection( const ISequencer* InSequencer, const FGuid& InGuid, const FTransform& InDefaultTransform) { if (!InSequencer || !InSequencer->GetFocusedMovieSceneSequence()) { return nullptr; } UMovieScene* MovieScene = InSequencer->GetFocusedMovieSceneSequence()->GetMovieScene(); if (!MovieScene) { return nullptr; } UMovieScene3DTransformTrack* TransformTrack = MovieScene->FindTrack(InGuid); if (!TransformTrack) { return nullptr; } UMovieScene3DTransformSection* TransformSection = Cast(TransformTrack->GetSectionToKey()); if (TransformSection) { return TransformSection; } TransformSection = TransformTrack->GetAllSections().Num() > 0 ? Cast(TransformTrack->GetAllSections()[0]) : nullptr; if (TransformSection) { return TransformSection; } //there really should be a section to key or at least one section, if not then we go through creating it. TransformTrack->Modify(); static constexpr FFrameNumber Frame0; bool bSectionAdded = false; TransformSection = Cast(TransformTrack->FindOrAddSection(Frame0, bSectionAdded)); if (!TransformSection) { return nullptr; } TransformSection->Modify(); if (bSectionAdded) { TransformSection->SetRange(TRange::All()); const FVector Location0 = InDefaultTransform.GetLocation(); const FRotator Rotation0 = InDefaultTransform.GetRotation().Rotator(); const FVector Scale3D0 = InDefaultTransform.GetScale3D(); FMovieSceneChannelProxy& SectionChannelProxy = TransformSection->GetChannelProxy(); TMovieSceneChannelHandle DoubleChannels[] = { SectionChannelProxy.GetChannelByName("Location.X"), SectionChannelProxy.GetChannelByName("Location.Y"), SectionChannelProxy.GetChannelByName("Location.Z"), SectionChannelProxy.GetChannelByName("Rotation.X"), SectionChannelProxy.GetChannelByName("Rotation.Y"), SectionChannelProxy.GetChannelByName("Rotation.Z"), SectionChannelProxy.GetChannelByName("Scale.X"), SectionChannelProxy.GetChannelByName("Scale.Y"), SectionChannelProxy.GetChannelByName("Scale.Z") }; if (DoubleChannels[0].Get()) { DoubleChannels[0].Get()->SetDefault(Location0.X); } if (DoubleChannels[1].Get()) { DoubleChannels[1].Get()->SetDefault(Location0.Y); } if (DoubleChannels[2].Get()) { DoubleChannels[2].Get()->SetDefault(Location0.Z); } if (DoubleChannels[3].Get()) { DoubleChannels[3].Get()->SetDefault(Rotation0.Roll); } if (DoubleChannels[4].Get()) { DoubleChannels[4].Get()->SetDefault(Rotation0.Pitch); } if (DoubleChannels[5].Get()) { DoubleChannels[5].Get()->SetDefault(Rotation0.Yaw); } if (DoubleChannels[6].Get()) { DoubleChannels[6].Get()->SetDefault(Scale3D0.X); } if (DoubleChannels[7].Get()) { DoubleChannels[7].Get()->SetDefault(Scale3D0.Y); } if (DoubleChannels[8].Get()) { DoubleChannels[8].Get()->SetDefault(Scale3D0.Z); } } return TransformSection; } bool MovieSceneToolHelpers::AddTransformKeys( const UMovieScene3DTransformSection* InTransformSection, const TArray& Frames, const TArray& InLocalTransforms, const EMovieSceneTransformChannel& InChannels) { if (!InTransformSection) { return false; } if (Frames.IsEmpty() || Frames.Num() != InLocalTransforms.Num()) { return false; } auto GetValue = [](const uint32 Index, const FVector& InLocation, const FRotator& InRotation, const FVector& InScale) { switch (Index) { case 0: return InLocation.X; case 1: return InLocation.Y; case 2: return InLocation.Z; case 3: return InRotation.Roll; case 4: return InRotation.Pitch; case 5: return InRotation.Yaw; case 6: return InScale.X; case 7: return InScale.Y; case 8: return InScale.Z; default: ensure(false); break; } return 0.0; }; const bool bKeyTranslation = EnumHasAllFlags(InChannels, EMovieSceneTransformChannel::Translation); const bool bKeyRotation = EnumHasAllFlags(InChannels, EMovieSceneTransformChannel::Rotation); const bool bKeyScale = EnumHasAllFlags(InChannels, EMovieSceneTransformChannel::Scale); TArray ChannelsIndexToKey; if (bKeyTranslation) { ChannelsIndexToKey.Append({ 0,1,2 }); } if (bKeyRotation) { ChannelsIndexToKey.Append({ 3,4,5 }); } if (bKeyScale) { ChannelsIndexToKey.Append({ 6,7,8 }); } FMovieSceneChannelProxy& SectionChannelProxy = InTransformSection->GetChannelProxy(); TMovieSceneChannelHandle DoubleChannels[] = { SectionChannelProxy.GetChannelByName("Location.X"), SectionChannelProxy.GetChannelByName("Location.Y"), SectionChannelProxy.GetChannelByName("Location.Z"), SectionChannelProxy.GetChannelByName("Rotation.X"), SectionChannelProxy.GetChannelByName("Rotation.Y"), SectionChannelProxy.GetChannelByName("Rotation.Z"), SectionChannelProxy.GetChannelByName("Scale.X"), SectionChannelProxy.GetChannelByName("Scale.Y"), SectionChannelProxy.GetChannelByName("Scale.Z") }; // set default const FTransform& LocalTransform0 = InLocalTransforms[0]; const FVector Location0 = LocalTransform0.GetLocation(); const FRotator Rotation0 = LocalTransform0.GetRotation().Rotator(); const FVector Scale3D0 = LocalTransform0.GetScale3D(); for (int32 ChannelIndex = 0; ChannelIndex < 9; ChannelIndex++) { if (DoubleChannels[ChannelIndex].Get()) { if (!DoubleChannels[ChannelIndex].Get()->GetDefault().IsSet()) { const double Value = GetValue(ChannelIndex, Location0, Rotation0, Scale3D0); DoubleChannels[ChannelIndex].Get()->SetDefault(Value); } } } // add keys for (int32 Index = 0; Index < Frames.Num(); ++Index) { const FFrameNumber& Frame = Frames[Index]; const FTransform& LocalTransform = InLocalTransforms[Index]; const FVector Location = LocalTransform.GetLocation(); const FRotator Rotation = LocalTransform.GetRotation().Rotator(); const FVector Scale3D = LocalTransform.GetScale3D(); for (const int32 ChannelIndex : ChannelsIndexToKey) { if (DoubleChannels[ChannelIndex].Get()) { const double Value = GetValue(ChannelIndex, Location, Rotation, Scale3D); TMovieSceneChannelData ChannelData = DoubleChannels[ChannelIndex].Get()->GetData(); MovieSceneToolHelpers::SetOrAddKey(ChannelData, Frame, Value); } } } //need to handle euler flips.. 3,4,5 is rotation if (bKeyRotation) { //figure out start/endframe, frames may not be in order FFrameNumber StartTime = Frames[0], EndTime = Frames[0]; for (const FFrameNumber& Frame : Frames) { if (Frame < StartTime) { StartTime = Frame; } else if (Frame > EndTime) { EndTime = Frame; } } TSortedMap FrameSet; TRange WithinRange(0, 0); WithinRange.SetLowerBoundValue(StartTime); WithinRange.SetUpperBoundValue(EndTime); TArray KeyTimes; TArray KeyHandles; for (int32 ChannelIndex = 3; ChannelIndex <=5; ++ChannelIndex) { KeyTimes.SetNum(0); KeyHandles.SetNum(0); if (DoubleChannels[ChannelIndex].Get()) { TMovieSceneChannelData ChannelData = DoubleChannels[ChannelIndex].Get()->GetData(); ChannelData.GetKeys(WithinRange, &KeyTimes, &KeyHandles); for (int32 KeyIndex = 0; KeyIndex < KeyTimes.Num() - 1; ++KeyIndex) { const int32 ValueIndex = ChannelData.GetIndex(KeyHandles[KeyIndex]); const int32 NextValueIndex = ChannelData.GetIndex(KeyHandles[KeyIndex + 1]); TArrayView Values = DoubleChannels[ChannelIndex].Get()->GetValues(); //re-get the array since the winding may change values double Value = Values[ValueIndex].Value; double NextValue = Values[NextValueIndex].Value; FMath::WindRelativeAnglesDegrees(Value,NextValue); AssignValue(DoubleChannels[ChannelIndex].Get(), KeyHandles[KeyIndex + 1], NextValue); } } } } //now we need to set auto tangents for (const int32 ChannelIndex : ChannelsIndexToKey) { if (DoubleChannels[ChannelIndex].Get()) { DoubleChannels[ChannelIndex].Get()->AutoSetTangents(); } } return true; } void AddUnwoundKey(FMovieSceneDoubleChannel& Channel, FFrameNumber Time, double Value) { int32 Index = Channel.AddLinearKey(Time, Value); TArrayView Values = Channel.GetData().GetValues(); if (Index >= 1) { const double PreviousValue = Values[Index - 1].Value; double NewValue = Value; while (NewValue - PreviousValue > 180.0f) { NewValue -= 360.f; } while (NewValue - PreviousValue < -180.0f) { NewValue += 360.f; } Values[Index].Value = NewValue; } } void MovieSceneToolHelpers::ImportAnimSequenceTransforms(const TSharedRef& Sequencer, const FAssetData& Asset, UMovieScene3DTransformTrack* TransformTrack) { if (!IsValid(TransformTrack)) { return; } FSlateApplication::Get().DismissAllMenus(); FQualifiedFrameTime CurrentTime = Sequencer->GetLocalTime(); UAnimSequence* AnimSequence = Cast(Asset.GetAsset()); // find object binding to recover any component transforms we need to incorporate (for characters) FTransform InvComponentTransform; UMovieSceneSequence* MovieSceneSequence = Sequencer->GetFocusedMovieSceneSequence(); if(MovieSceneSequence) { UMovieScene* MovieScene = MovieSceneSequence->GetMovieScene(); if(MovieScene) { FGuid ObjectBinding; if(MovieScene->FindTrackBinding(*TransformTrack, ObjectBinding)) { const UClass* ObjectClass = nullptr; if(FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(ObjectBinding)) { ObjectClass = Spawnable->GetObjectTemplate()->GetClass(); } else if(FMovieScenePossessable* Possessable = MovieScene->FindPossessable(ObjectBinding)) { ObjectClass = Possessable->GetPossessedObjectClass(); } if(ObjectClass) { const ACharacter* Character = Cast(ObjectClass->GetDefaultObject(false)); if(Character) { const USkeletalMeshComponent* SkeletalMeshComponent = Character->GetMesh(); FTransform MeshRelativeTransform = SkeletalMeshComponent->GetRelativeTransform(); InvComponentTransform = MeshRelativeTransform.GetRelativeTransform(SkeletalMeshComponent->GetOwner()->GetTransform()).Inverse(); } } } } } if (AnimSequence && AnimSequence->GetDataModel()->GetNumBoneTracks() > 0) { const FScopedTransaction Transaction( NSLOCTEXT( "Sequencer", "ImportAnimSequenceTransforms", "Import Anim Sequence Transforms" ) ); TransformTrack->Modify(); UMovieScene3DTransformSection* Section = Cast(TransformTrack->CreateNewSection()); Section->SetBlendType(EMovieSceneBlendType::Additive); Section->SetMask(EMovieSceneTransformChannel::Translation | EMovieSceneTransformChannel::Rotation); FFrameRate TickResolution = Section->GetTypedOuter()->GetTickResolution(); // We know that there aren't any channel overrides (all 9 channels are double channels) because we're working // on a brand new transform section. TArrayView DoubleChannels = Section->GetChannelProxy().GetChannels(); // Set default translation and rotation for (int32 Index = 0; Index < 6; ++Index) { DoubleChannels[Index]->SetDefault(0.f); } // Set default scale for (int32 Index = 6; Index < 9; ++Index) { DoubleChannels[Index]->SetDefault(1.f); } TransformTrack->AddSection(*Section); if (Section->TryModify()) { struct FTempTransformKey { FTransform Transform; FRotator WoundRotation; float Time; }; float MinTime = FLT_MAX; TArray TempKeys; TArray BoneTrackNames; AnimSequence->GetDataModelInterface()->GetBoneTrackNames(BoneTrackNames); TArray BoneTransforms; AnimSequence->GetDataModelInterface()->GetBoneTrackTransforms(BoneTrackNames[0], BoneTransforms); for(int32 KeyIndex = 0; KeyIndex < BoneTransforms.Num(); KeyIndex++) { FTempTransformKey TempKey; TempKey.Time = AnimSequence->GetTimeAtFrame(KeyIndex); TempKey.Transform = BoneTransforms[KeyIndex]; // apply component transform if any TempKey.Transform = InvComponentTransform * TempKey.Transform; TempKey.WoundRotation = TempKey.Transform.GetRotation().Rotator(); TempKeys.Add(TempKey); MinTime = FMath::Min(MinTime, TempKey.Time); } int32 TransformCount = TempKeys.Num(); for(int32 TransformIndex = 0; TransformIndex < TransformCount - 1; TransformIndex++) { FRotator& Rotator = TempKeys[TransformIndex].WoundRotation; FRotator& NextRotator = TempKeys[TransformIndex + 1].WoundRotation; FMath::WindRelativeAnglesDegrees(Rotator.Pitch, NextRotator.Pitch); FMath::WindRelativeAnglesDegrees(Rotator.Yaw, NextRotator.Yaw); FMath::WindRelativeAnglesDegrees(Rotator.Roll, NextRotator.Roll); } FFrameNumber MinKeyTime = (MinTime * TickResolution).RoundToFrame(); TRange Range = Section->GetRange(); for(const FTempTransformKey& TempKey : TempKeys) { FFrameNumber KeyTime = (TempKey.Time * TickResolution).RoundToFrame(); KeyTime = CurrentTime.Time.FrameNumber + (KeyTime - MinKeyTime); Range = TRange::Hull(Range, TRange(KeyTime)); const FVector3f Translation = (FVector3f)TempKey.Transform.GetTranslation(); const FVector3f Rotation = (FVector3f)TempKey.WoundRotation.Euler(); const FVector3f Scale = (FVector3f)TempKey.Transform.GetScale3D(); TArrayView Channels = Section->GetChannelProxy().GetChannels(); Channels[0]->AddLinearKey(KeyTime, Translation.X); Channels[1]->AddLinearKey(KeyTime, Translation.Y); Channels[2]->AddLinearKey(KeyTime, Translation.Z); AddUnwoundKey(*Channels[3], KeyTime, Rotation.X); AddUnwoundKey(*Channels[4], KeyTime, Rotation.Y); AddUnwoundKey(*Channels[5], KeyTime, Rotation.Z); Channels[6]->AddLinearKey(KeyTime, Scale.X); Channels[7]->AddLinearKey(KeyTime, Scale.Y); Channels[8]->AddLinearKey(KeyTime, Scale.Z); } Section->SetRange(Range); Section->SetRowIndex(FindAvailableRowIndex(TransformTrack, Section)); Sequencer->NotifyMovieSceneDataChanged( EMovieSceneDataChangeType::MovieSceneStructureItemAdded ); } } } void MovieSceneToolHelpers::ImportAnimSequenceTransformsEnterPressed(const TSharedRef& Sequencer, const TArray& Asset, UMovieScene3DTransformTrack* TransformTrack) { if (Asset.Num() > 0) { ImportAnimSequenceTransforms(Sequencer, Asset[0].GetAsset(), TransformTrack); } }