// Copyright Epic Games, Inc. All Rights Reserved. #include "AnimTimelineClipboard.h" #include "AnimTimelineTrack.h" #include "AnimTimelineTrack_Curve.h" #include "UnrealExporter.h" #include "Exporters/Exporter.h" #include "HAL/PlatformApplicationMisc.h" #include "Factories.h" #include "Animation/AnimData/AnimDataModel.h" #include "Animation/Skeleton.h" #include "Animation/AnimSequenceBase.h" FAnimationCurveIdentifier UAnimCurveBaseCopyObject::GetAnimationCurveIdentifier() const { FAnimationCurveIdentifier OutAnimationCurveIdentifier; OutAnimationCurveIdentifier.Axis = Axis; OutAnimationCurveIdentifier.Channel = Channel; OutAnimationCurveIdentifier.CurveName = CurveName; OutAnimationCurveIdentifier.CurveType = CurveType; return OutAnimationCurveIdentifier; } bool UAnimTimelineClipboardContent::AreAllCurvesOfTrackType(const ERawCurveTrackTypes Type) const { for (const TObjectPtr& CurveCopyObj : Curves) { if (CurveCopyObj->CurveType != Type) { return false; } } return !Curves.IsEmpty(); } bool UAnimTimelineClipboardContent::IsEmpty() const { return Curves.IsEmpty(); } UAnimTimelineClipboardContent* UAnimTimelineClipboardContent::Create() { return NewObject(GetTransientPackage()); } void FAnimTimelineClipboardUtilities::CopyContentToClipboard(UAnimTimelineClipboardContent* Content) { // Clear the mark state for saving. UnMarkAllObjects(EObjectMark(OBJECTMARK_TagExp | OBJECTMARK_TagImp)); // Export the clipboard to text. FStringOutputDevice Archive; const FExportObjectInnerContext Context; UExporter::ExportToOutputDevice(&Context, Content, nullptr, Archive, TEXT("copy"), 0, PPF_ExportsNotFullyQualified | PPF_Copy | PPF_Delimited, false, Content->GetOuter()); FPlatformApplicationMisc::ClipboardCopy(*Archive); } class FAnimCurvesClipboardObjectTextFactory : public FCustomizableTextObjectFactory { public: FAnimCurvesClipboardObjectTextFactory() : FCustomizableTextObjectFactory(GWarn) , AnimationCurvesClipboard(nullptr) { } UAnimTimelineClipboardContent* AnimationCurvesClipboard; protected: virtual bool CanCreateClass(UClass* InObjectClass, bool& bOmitSubObjs) const override { if (InObjectClass->IsChildOf(UAnimTimelineClipboardContent::StaticClass())) { return true; } return false; } virtual void ProcessConstructedObject(UObject* CreatedObject) override { if (CreatedObject->IsA()) { AnimationCurvesClipboard = CastChecked(CreatedObject); } } }; bool FAnimTimelineClipboardUtilities::CanPasteContentToClipboard(const FString& TextToImport) { const FAnimCurvesClipboardObjectTextFactory ClipboardContentFactory; return ClipboardContentFactory.CanCreateObjectsFromText(TextToImport); } const UAnimTimelineClipboardContent* FAnimTimelineClipboardUtilities::GetContentFromClipboard() { // Get the text from the clipboard. FString ClipboardText; FPlatformApplicationMisc::ClipboardPaste(ClipboardText); // Try to create curves clipboard content from text. if (CanPasteContentToClipboard(ClipboardText)) { FAnimCurvesClipboardObjectTextFactory ClipboardContentFactory; ClipboardContentFactory.ProcessBuffer(GetTransientPackage(), RF_Transactional, ClipboardText); return ClipboardContentFactory.AnimationCurvesClipboard; } return nullptr; } UAnimCurveBaseCopyObject::UAnimCurveBaseCopyObject() : CurveName(NAME_None), CurveType(ERawCurveTrackTypes::RCT_MAX), Channel(ETransformCurveChannel::Invalid), Axis(EVectorCurveChannel::Invalid), OriginName(NAME_None) { } bool FAnimTimelineClipboardUtilities::CopySelectedTracksToClipboard(const TSet>& CurveTracks, UAnimTimelineClipboardContent* ClipboardContent) { check(ClipboardContent != nullptr); const int32 StartSize = ClipboardContent->Curves.Num(); for (const TSharedRef& SelectedTrack : CurveTracks) { SelectedTrack->Copy(ClipboardContent); } return StartSize != ClipboardContent->Curves.Num(); } bool FAnimTimelineClipboardUtilities::CanOverwriteSelectedCurveDataFromClipboard(const TSet>& SelectedTracks) { const UAnimTimelineClipboardContent* ClipboardContent = FAnimTimelineClipboardUtilities::GetContentFromClipboard(); // Validate clipboard if (ClipboardContent && !ClipboardContent->IsEmpty()) { // Exit on multiple curves selected or no curves selected if (SelectedTracks.Num() != 1) { return false; } const TSharedRef Track = *SelectedTracks.CreateConstIterator(); if (Track->IsA()) { const FAnimTimelineTrack_Curve & CurveTrack = Track->As(); // Get current track information FName CurveName; ERawCurveTrackTypes CurveType; int32 CurveIndex; CurveTrack.GetCurveEditInfo(0 /* Unused */, CurveName, CurveType, CurveIndex); // Handle special case of pasting float curve into subtrack const bool bIsPastingFloatTrackIntoSubtrack = (!CurveTrack.GetCurves().IsEmpty() && ClipboardContent->AreAllCurvesOfTrackType(ERawCurveTrackTypes::RCT_Float)); // Allow pasting of content into current track const bool bDoesSelectedTrackMatchClipboardTrack = ClipboardContent->AreAllCurvesOfTrackType(CurveType); return (bIsPastingFloatTrackIntoSubtrack || bDoesSelectedTrackMatchClipboardTrack) && ClipboardContent->Curves.Num() == 1; } } // Invalid clipboard return false; } void FAnimTimelineClipboardUtilities::OverwriteSelectedCurveDataFromClipboard(const UAnimTimelineClipboardContent* ClipboardContent, const TSet>& CurveTracks, UAnimSequenceBase* InTargetSequence) { check(ClipboardContent != nullptr); check(InTargetSequence != nullptr) IAnimationDataController& Controller = InTargetSequence->GetController(); // TODO: Allow for multiple curve pasting. Currently only single curve selection is supported. Need to sort selected curves or create array. if (const TSharedRef& Track = *CurveTracks.CreateConstIterator(); Track->IsA()) { IAnimationDataController::FScopedBracket ScopedBracket(Controller, NSLOCTEXT("AnimTimelineClipboardUtilities", "PasteCurveKeys_Bracket", "Paste Animation Curve Keys")); const FAnimTimelineTrack_Curve & CurveTrack = Track->As(); // Get selected curve track information FName SelectedCurveTrackName; ERawCurveTrackTypes SelectedCurveTrackType; // The value is supposed to always be RCT_TRANSFORM as mentioned in FAnimTimelineTrack_Curve but this is not true. int32 SelectedCurveTrackIndex; CurveTrack.GetCurveEditInfo(0 /* Unused */, SelectedCurveTrackName, SelectedCurveTrackType, SelectedCurveTrackIndex); FAnimationCurveIdentifier SelectedCurveTrackIdentifier(SelectedCurveTrackName, SelectedCurveTrackType); // Special case for pasting into child tracks for vectors tracks inside a transform track if (!CurveTrack.GetCurves().IsEmpty() && SelectedCurveTrackType == ERawCurveTrackTypes::RCT_Transform && ClipboardContent->AreAllCurvesOfTrackType(ERawCurveTrackTypes::RCT_Float)) { const int VectorFloatDataIndex = SelectedCurveTrackIndex % 3; // 0 = X, 1 = Y, 2 = Z const int TransformDataIndex = SelectedCurveTrackIndex / 3; // 0 = Translation, 1 = Rotation, 2 = Scale // Ensure identifier information matches SelectedCurveTrackIdentifier.Channel = static_cast(TransformDataIndex); // Update animation model data if (const FTransformCurve* SelectedTransform = Controller.GetModel()->FindTransformCurve(SelectedCurveTrackIdentifier)) { FTransformCurve TempTransformCurve; // Get new transform with incoming data { TempTransformCurve.CopyCurve(*SelectedTransform); FVectorCurve* NewVectorCurve = TempTransformCurve.GetVectorCurveByIndex(TransformDataIndex); NewVectorCurve->FloatCurves[VectorFloatDataIndex] = CastChecked(ClipboardContent->Curves[0])->Curve.FloatCurve; } // Update transform { TArray TimeKeys; TArray TransformValues; TempTransformCurve.GetKeys(TimeKeys, TransformValues); Controller.SetTransformCurveKeys(SelectedCurveTrackIdentifier, TransformValues, TimeKeys); } } return; } // Update curve data for float and transform tracks if (ClipboardContent->AreAllCurvesOfTrackType(SelectedCurveTrackType)) { if (SelectedCurveTrackType == ERawCurveTrackTypes::RCT_Float) { const UFloatCurveCopyObject* FloatCurve = Cast(ClipboardContent->Curves[0]); Controller.SetCurveKeys(SelectedCurveTrackIdentifier, FloatCurve->Curve.FloatCurve.GetConstRefOfKeys()); Controller.SetCurveFlags(SelectedCurveTrackIdentifier, FloatCurve->Curve.GetCurveTypeFlags()); Controller.SetCurveComment(SelectedCurveTrackIdentifier, FloatCurve->Curve.Comment); } else if (SelectedCurveTrackType == ERawCurveTrackTypes::RCT_Transform) { const UTransformCurveCopyObject* TransformCurve = Cast(ClipboardContent->Curves[0]); TArray TimeKeys; TArray TransformValues; TransformCurve->Curve.GetKeys(TimeKeys, TransformValues); // TODO: Optimize this if possible! Controller.SetTransformCurveKeys(SelectedCurveTrackIdentifier, TransformValues, TimeKeys); Controller.SetCurveFlags(SelectedCurveTrackIdentifier, TransformCurve->Curve.GetCurveTypeFlags()); Controller.SetCurveComment(SelectedCurveTrackIdentifier, TransformCurve->Curve.Comment); } } } } void FAnimTimelineClipboardUtilities::OverwriteOrAddCurvesFromClipboardContent(const UAnimTimelineClipboardContent* ClipboardContent, UAnimSequenceBase* InTargetSequence) { check(InTargetSequence != nullptr) check(ClipboardContent != nullptr) USkeleton * Skeleton = InTargetSequence->GetSkeleton(); check(Skeleton != nullptr) IAnimationDataController& Controller = InTargetSequence->GetController(); if (ClipboardContent->Curves.Num()) { IAnimationDataController::FScopedBracket ScopedBracket(Controller, NSLOCTEXT("AnimTimelineClipboardUtilities", "PasteCurves_Bracket", "Paste Animation Curves")); for (const TObjectPtr& InCurveCopyObj : ClipboardContent->Curves) { // Exit on types not supported by animation controller if (InCurveCopyObj->CurveType != ERawCurveTrackTypes::RCT_Float && InCurveCopyObj->CurveType != ERawCurveTrackTypes::RCT_Transform) { UE_LOG(LogAnimation, Warning, TEXT("Attempting to paste curve { %s } of unsupported type { %s } by animation controller. "), *InCurveCopyObj->CurveName.ToString(), *UEnum::GetValueAsString(InCurveCopyObj->CurveType)); continue; } // Ensure we are not pasting data into the same curve we copying data from if (InCurveCopyObj->OriginName.Compare(Controller.GetModelInterface().GetObject()->GetOuter()->GetFName()) == 0 && Controller.GetModel()->FindCurve(InCurveCopyObj->GetAnimationCurveIdentifier())) { // Do nothing and exit. UE_LOG(LogAnimation, Warning, TEXT("Attempting to paste already existing curve: %s"), *InCurveCopyObj->CurveName.ToString()); } else { FAnimationCurveIdentifier CurveIdentifier = InCurveCopyObj->GetAnimationCurveIdentifier(); // Create curve with clipboard information if needed if (!Controller.GetModel()->FindCurve(CurveIdentifier)) { Controller.AddCurve(CurveIdentifier); } if (InCurveCopyObj->CurveType == ERawCurveTrackTypes::RCT_Float) { const UFloatCurveCopyObject* FloatCopyObject = Cast(InCurveCopyObj); Controller.SetCurveKeys(CurveIdentifier, FloatCopyObject->Curve.FloatCurve.GetConstRefOfKeys()); Controller.SetCurveFlags(CurveIdentifier, FloatCopyObject->Curve.GetCurveTypeFlags()); Controller.SetCurveComment(CurveIdentifier, FloatCopyObject->Curve.Comment); } else if (InCurveCopyObj->CurveType == ERawCurveTrackTypes::RCT_Transform) { const UTransformCurveCopyObject* TransformCopyObject = Cast(InCurveCopyObj); TArray TimeKeys; TArray TransformValues; TransformCopyObject->Curve.GetKeys(TimeKeys, TransformValues); // TODO: Optimize this if possible! Controller.SetTransformCurveKeys(CurveIdentifier, TransformValues, TimeKeys); Controller.SetCurveFlags(CurveIdentifier, TransformCopyObject->Curve.GetCurveTypeFlags()); Controller.SetCurveComment(CurveIdentifier, TransformCopyObject->Curve.Comment); } } } } }