void UFbxFactory::CleanUp() { UnFbx::FFbxImporter* FFbxImporter = UnFbx::FFbxImporter::GetInstance(); bDetectImportTypeOnImport = true; bShowOption = true; // load options if (FFbxImporter) { struct UnFbx::FBXImportOptions* ImportOptions = FFbxImporter->GetImportOptions(); if ( ImportOptions ) { ImportOptions->SkeletonForAnimation = NULL; ImportOptions->PhysicsAsset = NULL; } } }
void ImportStaticMeshLOD( UStaticMesh* BaseStaticMesh, const FString& Filename, int32 LODLevel ) { UE_LOG(LogExportMeshUtils, Log, TEXT("Fbx LOD loading")); // logger for all error/warnings // this one prints all messages that are stored in FFbxImporter // this function seems to get called outside of FBX factory UnFbx::FFbxImporter* FFbxImporter = UnFbx::FFbxImporter::GetInstance(); UnFbx::FFbxLoggerSetter Logger(FFbxImporter); // don't import materials UnFbx::FBXImportOptions* ImportOptions = FFbxImporter->GetImportOptions(); ImportOptions->bImportMaterials = false; ImportOptions->bImportTextures = false; if ( !FFbxImporter->ImportFromFile( *Filename, FPaths::GetExtension( Filename ) ) ) { // Log the error message and fail the import. // @todo verify if the message works FFbxImporter->FlushToTokenizedErrorMessage(EMessageSeverity::Error); } else { FFbxImporter->FlushToTokenizedErrorMessage(EMessageSeverity::Warning); bool bUseLODs = true; int32 MaxLODLevel = 0; TArray< TArray<FbxNode*>* > LODNodeList; TArray<FString> LODStrings; // Create a list of LOD nodes PopulateFBXStaticMeshLODList(FFbxImporter, FFbxImporter->Scene->GetRootNode(), LODNodeList, MaxLODLevel, bUseLODs); // No LODs, so just grab all of the meshes in the file if (MaxLODLevel == 0) { bUseLODs = false; MaxLODLevel = BaseStaticMesh->GetNumLODs(); // Create a list of meshes PopulateFBXStaticMeshLODList(FFbxImporter, FFbxImporter->Scene->GetRootNode(), LODNodeList, MaxLODLevel, bUseLODs); // Nothing found, error out if (LODNodeList.Num() == 0) { FFbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText(LOCTEXT("Prompt_NoMeshFound", "No meshes were found in file."))), FFbxErrors::Generic_Mesh_MeshNotFound); FFbxImporter->ReleaseScene(); return; } } // Display the LOD selection dialog if (LODLevel > BaseStaticMesh->GetNumLODs()) { // Make sure they don't manage to select a bad LOD index FFbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, FText::Format(LOCTEXT("Prompt_InvalidLODIndex", "Invalid mesh LOD index {0}, as no prior LOD index exists!"), FText::AsNumber(LODLevel))), FFbxErrors::Generic_Mesh_LOD_InvalidIndex); } else { // Import mesh UStaticMesh* TempStaticMesh = NULL; TempStaticMesh = (UStaticMesh*)FFbxImporter->ImportStaticMeshAsSingle(GetTransientPackage(), *(LODNodeList[bUseLODs? LODLevel: 0]), NAME_None, RF_NoFlags, NULL, BaseStaticMesh, LODLevel); // Add imported mesh to existing model if( TempStaticMesh ) { // Update mesh component BaseStaticMesh->MarkPackageDirty(); // Import worked FNotificationInfo NotificationInfo(FText::GetEmpty()); NotificationInfo.Text = FText::Format(LOCTEXT("LODImportSuccessful", "Mesh for LOD {0} imported successfully!"), FText::AsNumber(LODLevel)); NotificationInfo.ExpireDuration = 5.0f; FSlateNotificationManager::Get().AddNotification(NotificationInfo); } else { // Import failed FNotificationInfo NotificationInfo(FText::GetEmpty()); NotificationInfo.Text = FText::Format(LOCTEXT("LODImportFail", "Failed to import mesh for LOD {0}!"), FText::AsNumber( LODLevel )); NotificationInfo.ExpireDuration = 5.0f; FSlateNotificationManager::Get().AddNotification(NotificationInfo); } } // Cleanup for (int32 i = 0; i < LODNodeList.Num(); ++i) { delete LODNodeList[i]; } } FFbxImporter->ReleaseScene(); }
void SetImportOption(UFbxImportUI* ImportUI) { UnFbx::FFbxImporter* FFbxImporter = UnFbx::FFbxImporter::GetInstance(); UnFbx::FBXImportOptions* ImportOptions = FFbxImporter->GetImportOptions(); ApplyImportUIToImportOptions(ImportUI, *ImportOptions); }
void ImportSkeletalMeshLOD( class USkeletalMesh* SelectedSkelMesh, const FString& Filename, int32 LODLevel ) { // Check the file extension for FBX. Anything that isn't .FBX is rejected const FString FileExtension = FPaths::GetExtension(Filename); const bool bIsFBX = FCString::Stricmp(*FileExtension, TEXT("FBX")) == 0; if (bIsFBX) { #if WITH_APEX_CLOTHING FClothingBackup ClothingBackup; if(LODLevel == 0) { ApexClothingUtils::BackupClothingDataFromSkeletalMesh(SelectedSkelMesh, ClothingBackup); } #endif// #if WITH_APEX_CLOTHING UnFbx::FFbxImporter* FFbxImporter = UnFbx::FFbxImporter::GetInstance(); // don't import material and animation UnFbx::FBXImportOptions* ImportOptions = FFbxImporter->GetImportOptions(); ImportOptions->bImportMaterials = false; ImportOptions->bImportTextures = false; ImportOptions->bImportAnimations = false; if ( !FFbxImporter->ImportFromFile( *Filename, FPaths::GetExtension( Filename ) ) ) { // Log the error message and fail the import. FFbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, LOCTEXT("FBXImport_ParseFailed", "FBX file parsing failed.")), FFbxErrors::Generic_FBXFileParseFailed); } else { bool bUseLODs = true; int32 MaxLODLevel = 0; TArray< TArray<FbxNode*>* > MeshArray; TArray<FString> LODStrings; TArray<FbxNode*>* MeshObject = NULL;; // Populate the mesh array FFbxImporter->FillFbxSkelMeshArrayInScene(FFbxImporter->Scene->GetRootNode(), MeshArray, false); // Nothing found, error out if (MeshArray.Num() == 0) { FFbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, LOCTEXT("FBXImport_NoMesh", "No meshes were found in file.")), FFbxErrors::Generic_MeshNotFound); FFbxImporter->ReleaseScene(); return; } MeshObject = MeshArray[0]; // check if there is LODGroup for this skeletal mesh for (int32 j = 0; j < MeshObject->Num(); j++) { FbxNode* Node = (*MeshObject)[j]; if (Node->GetNodeAttribute() && Node->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eLODGroup) { // get max LODgroup level if (MaxLODLevel < (Node->GetChildCount() - 1)) { MaxLODLevel = Node->GetChildCount() - 1; } } } // No LODs found, switch to supporting a mesh array containing meshes instead of LODs if (MaxLODLevel == 0) { bUseLODs = false; MaxLODLevel = SelectedSkelMesh->LODInfo.Num(); } // Create LOD dropdown strings LODStrings.AddZeroed(MaxLODLevel + 1); LODStrings[0] = FString::Printf( TEXT("Base") ); for(int32 i = 1; i < MaxLODLevel + 1; i++) { LODStrings[i] = FString::Printf(TEXT("%d"), i); } int32 SelectedLOD = LODLevel; if (SelectedLOD > SelectedSkelMesh->LODInfo.Num()) { // Make sure they don't manage to select a bad LOD index FFbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, FText::Format(LOCTEXT("FBXImport_InvalidLODIdx", "Invalid mesh LOD index {0}, no prior LOD index exists"), FText::AsNumber(SelectedLOD))), FFbxErrors::Generic_Mesh_LOD_InvalidIndex); } else { TArray<FbxNode*> SkelMeshNodeArray; if (bUseLODs || ImportOptions->bImportMorph) { for (int32 j = 0; j < MeshObject->Num(); j++) { FbxNode* Node = (*MeshObject)[j]; if (Node->GetNodeAttribute() && Node->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eLODGroup) { if (Node->GetChildCount() > SelectedLOD) { SkelMeshNodeArray.Add(Node->GetChild(SelectedLOD)); } else // in less some LODGroups have less level, use the last level { SkelMeshNodeArray.Add(Node->GetChild(Node->GetChildCount() - 1)); } } else { SkelMeshNodeArray.Add(Node); } } } // Import mesh USkeletalMesh* TempSkelMesh = NULL; // @todo AssetImportData does this temp skeletal mesh need import data? UFbxSkeletalMeshImportData* TempAssetImportData = NULL; TempSkelMesh = (USkeletalMesh*)FFbxImporter->ImportSkeletalMesh(GetTransientPackage(), bUseLODs? SkelMeshNodeArray: *MeshObject, NAME_None, (EObjectFlags)0, TempAssetImportData); // Add imported mesh to existing model bool bImportSucceeded = false; if( TempSkelMesh ) { bImportSucceeded = FFbxImporter->ImportSkeletalMeshLOD(TempSkelMesh, SelectedSkelMesh, SelectedLOD); // Mark package containing skeletal mesh as dirty. SelectedSkelMesh->MarkPackageDirty(); } if(ImportOptions->bImportMorph) { FFbxImporter->ImportFbxMorphTarget(SkelMeshNodeArray, SelectedSkelMesh, SelectedSkelMesh->GetOutermost(), SelectedLOD); } if (bImportSucceeded) { // Notification of success FNotificationInfo NotificationInfo(FText::GetEmpty()); NotificationInfo.Text = FText::Format(NSLOCTEXT("UnrealEd", "LODImportSuccessful", "Mesh for LOD {0} imported successfully!"), FText::AsNumber(SelectedLOD)); NotificationInfo.ExpireDuration = 5.0f; FSlateNotificationManager::Get().AddNotification(NotificationInfo); } else { // Notification of failure FNotificationInfo NotificationInfo(FText::GetEmpty()); NotificationInfo.Text = FText::Format(NSLOCTEXT("UnrealEd", "LODImportFail", "Failed to import mesh for LOD {0}!"), FText::AsNumber(SelectedLOD)); NotificationInfo.ExpireDuration = 5.0f; FSlateNotificationManager::Get().AddNotification(NotificationInfo); } } // Cleanup for (int32 i=0; i<MeshArray.Num(); i++) { delete MeshArray[i]; } } FFbxImporter->ReleaseScene(); #if WITH_APEX_CLOTHING if(LODLevel == 0) { ApexClothingUtils::ReapplyClothingDataToSkeletalMesh(SelectedSkelMesh, ClothingBackup); } ApexClothingUtils::ReImportClothingSectionsFromClothingAsset(SelectedSkelMesh); #endif// #if WITH_APEX_CLOTHING } }
bool UnFbx::FFbxImporter::ImportAnimation(USkeleton* Skeleton, UAnimSequence * DestSeq, const FString& FileName, TArray<FbxNode*>& SortedLinks, TArray<FbxNode*>& NodeArray, FbxAnimStack* CurAnimStack, const int32 ResampleRate, const FbxTimeSpan AnimTimeSpan) { // @todo : the length might need to change w.r.t. sampling keys FbxTime SequenceLength = AnimTimeSpan.GetDuration(); float PreviousSequenceLength = DestSeq->SequenceLength; // if you have one pose(thus 0.f duration), it still contains animation, so we'll need to consider that as MINIMUM_ANIMATION_LENGTH time length DestSeq->SequenceLength = FGenericPlatformMath::Max<float>(SequenceLength.GetSecondDouble(), MINIMUM_ANIMATION_LENGTH); if(PreviousSequenceLength > MINIMUM_ANIMATION_LENGTH && DestSeq->RawCurveData.FloatCurves.Num() > 0) { // The sequence already existed when we began the import. We need to scale the key times for all curves to match the new // duration before importing over them. This is to catch any user-added curves float ScaleFactor = DestSeq->SequenceLength / PreviousSequenceLength; for(FFloatCurve& Curve : DestSeq->RawCurveData.FloatCurves) { Curve.FloatCurve.ScaleCurve(0.0f, ScaleFactor); } } if (ImportOptions->bDeleteExistingMorphTargetCurves) { for (int32 CurveIdx=0; CurveIdx<DestSeq->RawCurveData.FloatCurves.Num(); ++CurveIdx) { auto& Curve = DestSeq->RawCurveData.FloatCurves[CurveIdx]; if (Curve.GetCurveTypeFlag(ACF_DrivesMorphTarget)) { DestSeq->RawCurveData.FloatCurves.RemoveAt(CurveIdx, 1, false); --CurveIdx; } } DestSeq->RawCurveData.FloatCurves.Shrink(); } // // import blend shape curves // { GWarn->BeginSlowTask( LOCTEXT("BeginImportMorphTargetCurves", "Importing Morph Target Curves"), true); for ( int32 NodeIndex = 0; NodeIndex < NodeArray.Num(); NodeIndex++ ) { // consider blendshape animation curve FbxGeometry* Geometry = (FbxGeometry*)NodeArray[NodeIndex]->GetNodeAttribute(); if (Geometry) { int32 BlendShapeDeformerCount = Geometry->GetDeformerCount(FbxDeformer::eBlendShape); for(int32 BlendShapeIndex = 0; BlendShapeIndex<BlendShapeDeformerCount; ++BlendShapeIndex) { FbxBlendShape* BlendShape = (FbxBlendShape*)Geometry->GetDeformer(BlendShapeIndex, FbxDeformer::eBlendShape); const int32 BlendShapeChannelCount = BlendShape->GetBlendShapeChannelCount(); FString BlendShapeName = UTF8_TO_TCHAR(MakeName(BlendShape->GetName())); for(int32 ChannelIndex = 0; ChannelIndex<BlendShapeChannelCount; ++ChannelIndex) { FbxBlendShapeChannel* Channel = BlendShape->GetBlendShapeChannel(ChannelIndex); if(Channel) { FString ChannelName = UTF8_TO_TCHAR(MakeName(Channel->GetName())); // Maya adds the name of the blendshape and an underscore to the front of the channel name, so remove it if(ChannelName.StartsWith(BlendShapeName)) { ChannelName = ChannelName.Right(ChannelName.Len() - (BlendShapeName.Len()+1)); } FbxAnimCurve* Curve = Geometry->GetShapeChannel(BlendShapeIndex, ChannelIndex, (FbxAnimLayer*)CurAnimStack->GetMember(0)); if (Curve && Curve->KeyGetCount() > 0) { FFormatNamedArguments Args; Args.Add(TEXT("BlendShape"), FText::FromString(ChannelName)); const FText StatusUpate = FText::Format(LOCTEXT("ImportingMorphTargetCurvesDetail", "Importing Morph Target Curves [{BlendShape}]"), Args); GWarn->StatusUpdate(NodeIndex + 1, NodeArray.Num(), StatusUpate); // now see if we have one already exists. If so, just overwrite that. if not, add new one. ImportCurveToAnimSequence(DestSeq, *ChannelName, Curve, ACF_DrivesMorphTarget | ACF_TriggerEvent, AnimTimeSpan, 0.01f /** for some reason blend shape values are coming as 100 scaled **/); } } } } } } GWarn->EndSlowTask(); } // // importing custom attribute START // if (ImportOptions->bImportCustomAttribute) { GWarn->BeginSlowTask( LOCTEXT("BeginImportMorphTargetCurves", "Importing Custom Attirubte Curves"), true); const int32 TotalLinks = SortedLinks.Num(); int32 CurLinkIndex=0; for(auto Node: SortedLinks) { FbxProperty Property = Node->GetFirstProperty(); while (Property.IsValid()) { FbxAnimCurveNode* CurveNode = Property.GetCurveNode(); // do this if user defined and animated and leaf node if( CurveNode && Property.GetFlag(FbxPropertyAttr::eUserDefined) && CurveNode->IsAnimated() && IsSupportedCurveDataType(Property.GetPropertyDataType().GetType()) ) { FString CurveName = UTF8_TO_TCHAR(CurveNode->GetName()); UE_LOG(LogFbx, Log, TEXT("CurveName : %s"), *CurveName ); int32 TotalCount = CurveNode->GetChannelsCount(); for (int32 ChannelIndex=0; ChannelIndex<TotalCount; ++ChannelIndex) { FbxAnimCurve * AnimCurve = CurveNode->GetCurve(ChannelIndex); FString ChannelName = CurveNode->GetChannelName(ChannelIndex).Buffer(); if (AnimCurve) { FString FinalCurveName; if (TotalCount == 1) { FinalCurveName = CurveName; } else { FinalCurveName = CurveName + "_" + ChannelName; } FFormatNamedArguments Args; Args.Add(TEXT("CurveName"), FText::FromString(FinalCurveName)); const FText StatusUpate = FText::Format(LOCTEXT("ImportingCustomAttributeCurvesDetail", "Importing Custom Attribute [{CurveName}]"), Args); GWarn->StatusUpdate(CurLinkIndex + 1, TotalLinks, StatusUpate); ImportCurveToAnimSequence(DestSeq, FinalCurveName, AnimCurve, ACF_DefaultCurve, AnimTimeSpan); } } } Property = Node->GetNextProperty(Property); } CurLinkIndex++; } GWarn->EndSlowTask(); } // importing custom attribute END const bool bSourceDataExists = (DestSeq->SourceRawAnimationData.Num() > 0); TArray<AnimationTransformDebug::FAnimationTransformDebugData> TransformDebugData; int32 TotalNumKeys = 0; const FReferenceSkeleton& RefSkeleton = Skeleton->GetReferenceSkeleton(); // import animation { GWarn->BeginSlowTask( LOCTEXT("BeginImportAnimation", "Importing Animation"), true); TArray<struct FRawAnimSequenceTrack>& RawAnimationData = bSourceDataExists? DestSeq->SourceRawAnimationData : DestSeq->RawAnimationData; DestSeq->TrackToSkeletonMapTable.Empty(); DestSeq->AnimationTrackNames.Empty(); RawAnimationData.Empty(); TArray<FName> FbxRawBoneNames; FillAndVerifyBoneNames(Skeleton, SortedLinks, FbxRawBoneNames, FileName); UnFbx::FFbxImporter* FbxImporter = UnFbx::FFbxImporter::GetInstance(); const bool bPreserveLocalTransform = FbxImporter->GetImportOptions()->bPreserveLocalTransform; // Build additional transform matrix UFbxAnimSequenceImportData* TemplateData = Cast<UFbxAnimSequenceImportData>(DestSeq->AssetImportData); FbxAMatrix FbxAddedMatrix; BuildFbxMatrixForImportTransform(FbxAddedMatrix, TemplateData); FMatrix AddedMatrix = Converter.ConvertMatrix(FbxAddedMatrix); const int32 NumSamplingKeys = FMath::FloorToInt(AnimTimeSpan.GetDuration().GetSecondDouble() * ResampleRate); const FbxTime TimeIncrement = (NumSamplingKeys > 1)? AnimTimeSpan.GetDuration() / (NumSamplingKeys - 1) : AnimTimeSpan.GetDuration(); for(int32 SourceTrackIdx = 0; SourceTrackIdx < FbxRawBoneNames.Num(); ++SourceTrackIdx) { int32 NumKeysForTrack = 0; // see if it's found in Skeleton FName BoneName = FbxRawBoneNames[SourceTrackIdx]; int32 BoneTreeIndex = RefSkeleton.FindBoneIndex(BoneName); // update status FFormatNamedArguments Args; Args.Add(TEXT("TrackName"), FText::FromName(BoneName)); Args.Add(TEXT("TotalKey"), FText::AsNumber(NumSamplingKeys)); Args.Add(TEXT("TrackIndex"), FText::AsNumber(SourceTrackIdx+1)); Args.Add(TEXT("TotalTracks"), FText::AsNumber(FbxRawBoneNames.Num())); const FText StatusUpate = FText::Format(LOCTEXT("ImportingAnimTrackDetail", "Importing Animation Track [{TrackName}] ({TrackIndex}/{TotalTracks}) - TotalKey {TotalKey}"), Args); GWarn->StatusForceUpdate(SourceTrackIdx + 1, FbxRawBoneNames.Num(), StatusUpate); if (BoneTreeIndex!=INDEX_NONE) { bool bSuccess = true; FRawAnimSequenceTrack RawTrack; RawTrack.PosKeys.Empty(); RawTrack.RotKeys.Empty(); RawTrack.ScaleKeys.Empty(); AnimationTransformDebug::FAnimationTransformDebugData NewDebugData; FbxNode* Link = SortedLinks[SourceTrackIdx]; FbxNode * LinkParent = Link->GetParent(); for(FbxTime CurTime = AnimTimeSpan.GetStart(); CurTime <= AnimTimeSpan.GetStop(); CurTime += TimeIncrement) { // save global trasnform FbxAMatrix GlobalMatrix = Link->EvaluateGlobalTransform(CurTime); // we'd like to verify this before going to Transform. // currently transform has tons of NaN check, so it will crash there FMatrix GlobalUEMatrix = Converter.ConvertMatrix(GlobalMatrix); if (GlobalUEMatrix.ContainsNaN()) { bSuccess = false; AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("Error_InvalidTransform", "Track {0} contains invalid transform. Could not import the track."), FText::FromName(BoneName))), FFbxErrors::Animation_TransformError); break; } FTransform GlobalTransform = Converter.ConvertTransform(GlobalMatrix); if (GlobalTransform.ContainsNaN()) { bSuccess = false; AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("Error_InvalidUnrealTransform", "Track {0} did not yeild valid transform. Please report this to animation team."), FText::FromName(BoneName))), FFbxErrors::Animation_TransformError); break; } // debug data, including import transformation FTransform AddedTransform(AddedMatrix); NewDebugData.SourceGlobalTransform.Add(GlobalTransform * AddedTransform); FTransform LocalTransform; if( !bPreserveLocalTransform && LinkParent) { // I can't rely on LocalMatrix. I need to recalculate quaternion/scale based on global transform if Parent exists FbxAMatrix ParentGlobalMatrix = Link->GetParent()->EvaluateGlobalTransform(CurTime); FTransform ParentGlobalTransform = Converter.ConvertTransform(ParentGlobalMatrix); LocalTransform = GlobalTransform.GetRelativeTransform(ParentGlobalTransform); NewDebugData.SourceParentGlobalTransform.Add(ParentGlobalTransform); } else { FbxAMatrix& LocalMatrix = Link->EvaluateLocalTransform(CurTime); FbxVector4 NewLocalT = LocalMatrix.GetT(); FbxVector4 NewLocalS = LocalMatrix.GetS(); FbxQuaternion NewLocalQ = LocalMatrix.GetQ(); LocalTransform.SetTranslation(Converter.ConvertPos(NewLocalT)); LocalTransform.SetScale3D(Converter.ConvertScale(NewLocalS)); LocalTransform.SetRotation(Converter.ConvertRotToQuat(NewLocalQ)); NewDebugData.SourceParentGlobalTransform.Add(FTransform::Identity); } if(TemplateData && BoneTreeIndex == 0) { // If we found template data earlier, apply the import transform matrix to // the root track. LocalTransform.SetFromMatrix(LocalTransform.ToMatrixWithScale() * AddedMatrix); } if (LocalTransform.ContainsNaN()) { bSuccess = false; AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("Error_InvalidUnrealLocalTransform", "Track {0} did not yeild valid local transform. Please report this to animation team."), FText::FromName(BoneName))), FFbxErrors::Animation_TransformError); break; } RawTrack.ScaleKeys.Add(LocalTransform.GetScale3D()); RawTrack.PosKeys.Add(LocalTransform.GetTranslation()); RawTrack.RotKeys.Add(LocalTransform.GetRotation()); NewDebugData.RecalculatedLocalTransform.Add(LocalTransform); ++NumKeysForTrack; } if (bSuccess) { //add new track int32 NewTrackIdx = RawAnimationData.Add(RawTrack); DestSeq->AnimationTrackNames.Add(BoneName); NewDebugData.SetTrackData(NewTrackIdx, BoneTreeIndex, BoneName); // add mapping to skeleton bone track DestSeq->TrackToSkeletonMapTable.Add(FTrackToSkeletonMap(BoneTreeIndex)); TransformDebugData.Add(NewDebugData); } } TotalNumKeys = FMath::Max( TotalNumKeys, NumKeysForTrack ); } DestSeq->NumFrames = TotalNumKeys; GWarn->EndSlowTask(); } // compress animation { GWarn->BeginSlowTask( LOCTEXT("BeginCompressAnimation", "Compress Animation"), true); GWarn->StatusForceUpdate(1, 1, LOCTEXT("CompressAnimation", "Compressing Animation")); // if source data exists, you should bake it to Raw to apply if(bSourceDataExists) { DestSeq->BakeTrackCurvesToRawAnimation(); } else { // otherwise just compress DestSeq->PostProcessSequence(); } // run debug mode AnimationTransformDebug::OutputAnimationTransformDebugData(TransformDebugData, TotalNumKeys, RefSkeleton); GWarn->EndSlowTask(); } return true; }
EReimportResult::Type UReimportFbxSceneFactory::Reimport(UObject* Obj) { ReimportData = GetFbxSceneImportData(Obj); if (!ReimportData) { return EReimportResult::Failed; } //We will call other factory store the filename value since UFactory::CurrentFilename is static FbxImportFileName = ReimportData->SourceFbxFile; UnFbx::FFbxImporter* FbxImporter = UnFbx::FFbxImporter::GetInstance(); UnFbx::FFbxLoggerSetter Logger(FbxImporter); GWarn->BeginSlowTask(NSLOCTEXT("FbxSceneReImportFactory", "BeginReImportingFbxSceneTask", "ReImporting FBX scene"), true); GlobalImportSettings = FbxImporter->GetImportOptions(); //Fill the original options for (auto kvp : ReimportData->NameOptionsMap) { if (kvp.Key.Compare(DefaultOptionName) == 0) { SFbxSceneOptionWindow::CopyFbxOptionsToFbxOptions(kvp.Value, GlobalImportSettings); NameOptionsMap.Add(kvp.Key, GlobalImportSettings); } else { NameOptionsMap.Add(kvp.Key, kvp.Value); } } //Always convert the scene GlobalImportSettings->bConvertScene = true; GlobalImportSettings->bImportScene = ReimportData->bImportScene; //Read the fbx and store the hierarchy's information so we can reuse it after importing all the model in the fbx file if (!FbxImporter->ImportFromFile(*FbxImportFileName, FPaths::GetExtension(FbxImportFileName))) { // Log the error message and fail the import. GWarn->Log(ELogVerbosity::Error, FbxImporter->GetErrorMessage()); FbxImporter->ReleaseScene(); FbxImporter = nullptr; GWarn->EndSlowTask(); return EReimportResult::Failed; } FString PackageName = ""; Obj->GetOutermost()->GetName(PackageName); Path = FPaths::GetPath(PackageName); UnFbx::FbxSceneInfo SceneInfo; //Read the scene and found all instance with their scene information. FbxImporter->GetSceneInfo(FbxImportFileName, SceneInfo); //Convert old structure to the new scene export structure TSharedPtr<FFbxSceneInfo> SceneInfoPtr = ConvertSceneInfo(&SceneInfo); //Get import material info ExtractMaterialInfo(FbxImporter, SceneInfoPtr); if (!ReimportData->bCreateFolderHierarchy) { for (TSharedPtr<FFbxMeshInfo> MeshInfo : SceneInfoPtr->MeshInfo) { FString AssetName = Path + TEXT("/") + MeshInfo->Name; MeshInfo->SetOriginalImportPath(AssetName); FString OriginalFullImportName = PackageTools::SanitizePackageName(AssetName); OriginalFullImportName = OriginalFullImportName + TEXT(".") + PackageTools::SanitizePackageName(MeshInfo->Name); MeshInfo->SetOriginalFullImportName(OriginalFullImportName); } } else { TSet<uint64> AssetPathDone; FString AssetPath = Path; for (TSharedPtr<FFbxNodeInfo> NodeInfo : SceneInfoPtr->HierarchyInfo) { //Iterate the hierarchy and build the original path RecursivelyCreateOriginalPath(FbxImporter, NodeInfo, AssetPath, AssetPathDone); } } FillSceneHierarchyPath(SceneInfoPtr); FbxSceneReimportStatusMap MeshStatusMap; FbxSceneReimportStatusMap NodeStatusMap; bool bCanReimportHierarchy = ReimportData->HierarchyType == (int32)EFBXSceneOptionsCreateHierarchyType::FBXSOCHT_CreateBlueprint && !ReimportData->BluePrintFullName.IsEmpty(); if (!GetFbxSceneReImportOptions(FbxImporter , SceneInfoPtr , ReimportData->SceneInfoSourceData , GlobalImportSettings , SceneImportOptions , SceneImportOptionsStaticMesh , NameOptionsMap , MeshStatusMap , NodeStatusMap , bCanReimportHierarchy , Path)) { //User cancel the scene import FbxImporter->ReleaseScene(); FbxImporter = nullptr; GlobalImportSettings = nullptr; GWarn->EndSlowTask(); return EReimportResult::Cancelled; } GlobalImportSettingsReference = new UnFbx::FBXImportOptions(); SFbxSceneOptionWindow::CopyFbxOptionsToFbxOptions(GlobalImportSettings, GlobalImportSettingsReference); //Overwrite the reimport asset data with the new data ReimportData->SceneInfoSourceData = SceneInfoPtr; ReimportData->SourceFbxFile = FPaths::ConvertRelativePathToFull(FbxImportFileName); ReimportData->bImportScene = GlobalImportSettingsReference->bImportScene; //Copy the options map ReimportData->NameOptionsMap.Reset(); for (auto kvp : NameOptionsMap) { ReimportData->NameOptionsMap.Add(kvp.Key, kvp.Value); } FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry"); TArray<FAssetData> AssetDataToDelete; for (TSharedPtr<FFbxMeshInfo> MeshInfo : SceneInfoPtr->MeshInfo) { //Delete all the delete asset if (!MeshStatusMap.Contains(MeshInfo->OriginalImportPath)) { continue; } EFbxSceneReimportStatusFlags MeshStatus = *(MeshStatusMap.Find(MeshInfo->OriginalImportPath)); if ((MeshStatus & EFbxSceneReimportStatusFlags::Removed) == EFbxSceneReimportStatusFlags::None || (MeshStatus & EFbxSceneReimportStatusFlags::ReimportAsset) == EFbxSceneReimportStatusFlags::None) { continue; } //Make sure we load all package that will be deleted UPackage* PkgExist = LoadPackage(nullptr, *(MeshInfo->GetImportPath()), LOAD_Verify | LOAD_NoWarn); if (PkgExist == nullptr) { continue; } PkgExist->FullyLoad(); //Find the asset AssetDataToDelete.Add(AssetRegistryModule.Get().GetAssetByObjectPath(FName(*(MeshInfo->GetFullImportName())))); } AllNewAssets.Empty(); AssetToSyncContentBrowser.Empty(); EReimportResult::Type ReimportResult = EReimportResult::Succeeded; //Reimport and add asset for (TSharedPtr<FFbxMeshInfo> MeshInfo : SceneInfoPtr->MeshInfo) { if (!MeshStatusMap.Contains(MeshInfo->OriginalImportPath)) { continue; } EFbxSceneReimportStatusFlags MeshStatus = *(MeshStatusMap.Find(MeshInfo->OriginalImportPath)); //Set the import status for the next reimport MeshInfo->bImportAttribute = (MeshStatus & EFbxSceneReimportStatusFlags::ReimportAsset) != EFbxSceneReimportStatusFlags::None; if ((MeshStatus & EFbxSceneReimportStatusFlags::Removed) != EFbxSceneReimportStatusFlags::None || (MeshStatus & EFbxSceneReimportStatusFlags::ReimportAsset) == EFbxSceneReimportStatusFlags::None) { continue; } if (((MeshStatus & EFbxSceneReimportStatusFlags::Same) != EFbxSceneReimportStatusFlags::None || (MeshStatus & EFbxSceneReimportStatusFlags::Added) != EFbxSceneReimportStatusFlags::None) && (MeshStatus & EFbxSceneReimportStatusFlags::FoundContentBrowserAsset) != EFbxSceneReimportStatusFlags::None) { //Reimport over the old asset if (!MeshInfo->bIsSkelMesh) { ReimportResult = ReimportStaticMesh(FbxImporter, MeshInfo); } else { //TODO reimport skeletal mesh } } else if ((MeshStatus & EFbxSceneReimportStatusFlags::Added) != EFbxSceneReimportStatusFlags::None || (MeshStatus & EFbxSceneReimportStatusFlags::Same) != EFbxSceneReimportStatusFlags::None) { //Create a package for this node //Get Parent hierarchy name to create new package path ReimportResult = ImportStaticMesh(FbxImporter, MeshInfo, SceneInfoPtr); } } //Put back the default option in the static mesh import data, so next import will have those last import option SFbxSceneOptionWindow::CopyFbxOptionsToFbxOptions(GlobalImportSettingsReference, GlobalImportSettings); SFbxSceneOptionWindow::CopyFbxOptionsToStaticMeshOptions(GlobalImportSettingsReference, SceneImportOptionsStaticMesh); SceneImportOptionsStaticMesh->FillStaticMeshInmportData(StaticMeshImportData, SceneImportOptions); StaticMeshImportData->SaveConfig(); //Update the blueprint UBlueprint *ReimportBlueprint = nullptr; if (bCanReimportHierarchy && GlobalImportSettingsReference->bImportScene) { ReimportBlueprint = UpdateOriginalBluePrint(ReimportData->BluePrintFullName, &NodeStatusMap, SceneInfoPtr, ReimportData->SceneInfoSourceData, AssetDataToDelete); } //Remove the deleted meshinfo node from the reimport data TArray<TSharedPtr<FFbxMeshInfo>> ToRemoveHierarchyNode; for (TSharedPtr<FFbxMeshInfo> MeshInfo : ReimportData->SceneInfoSourceData->MeshInfo) { EFbxSceneReimportStatusFlags MeshStatus = *(MeshStatusMap.Find(MeshInfo->OriginalImportPath)); if ((MeshStatus & EFbxSceneReimportStatusFlags::Removed) != EFbxSceneReimportStatusFlags::None) { ToRemoveHierarchyNode.Add(MeshInfo); } } for (TSharedPtr<FFbxMeshInfo> MeshInfo : ToRemoveHierarchyNode) { ReimportData->SceneInfoSourceData->MeshInfo.Remove(MeshInfo); } ReimportData->Modify(); ReimportData->PostEditChange(); //Make sure the content browser is in sync before we delete FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked<FContentBrowserModule>("ContentBrowser"); ContentBrowserModule.Get().SyncBrowserToAssets(AssetToSyncContentBrowser); if (AssetDataToDelete.Num() > 0) { bool AbortDelete = false; if (ReimportBlueprint != nullptr) { //Save the blueprint to avoid reference from the old blueprint FAssetData ReimportBlueprintAsset(ReimportBlueprint); TArray<UPackage*> Packages; Packages.Add(ReimportBlueprintAsset.GetPackage()); FEditorFileUtils::PromptForCheckoutAndSave(Packages, false, false); //Make sure the Asset registry is up to date after the save TArray<FString> Paths; Paths.Add(ReimportBlueprintAsset.PackagePath.ToString()); AssetRegistryModule.Get().ScanPathsSynchronous(Paths, true); } if (!AbortDelete) { //Delete the asset and use the normal dialog to make sure the user understand he will remove some content //The user can decide to cancel the delete or not. This will not interrupt the reimport process //The delete is done at the end because we want to remove the blueprint reference before deleting object ObjectTools::DeleteAssets(AssetDataToDelete, true); } } //Make sure the content browser is in sync ContentBrowserModule.Get().SyncBrowserToAssets(AssetToSyncContentBrowser); AllNewAssets.Empty(); GlobalImportSettings = nullptr; GlobalImportSettingsReference = nullptr; FbxImporter->ReleaseScene(); FbxImporter = nullptr; GWarn->EndSlowTask(); return EReimportResult::Succeeded; }