// Copyright Epic Games, Inc. All Rights Reserved. #include "FoliageEditUtility.h" #include "AssetRegistry/AssetRegistryModule.h" #include "Containers/Array.h" #include "Containers/Map.h" #include "Containers/Set.h" #include "Dialogs/DlgPickAssetPath.h" #include "Editor.h" #include "Editor/EditorEngine.h" #include "Engine/Level.h" #include "Engine/World.h" #include "EngineUtils.h" #include "FileHelpers.h" #include "FoliageType.h" #include "Framework/Notifications/NotificationManager.h" #include "HAL/Platform.h" #include "HAL/PlatformCrt.h" #include "HAL/PlatformMisc.h" #include "InstancedFoliage.h" #include "InstancedFoliageActor.h" #include "Internationalization/Internationalization.h" #include "Internationalization/Text.h" #include "LevelUtils.h" #include "Misc/AssertionMacros.h" #include "Misc/PackageName.h" #include "ScopedTransaction.h" #include "Serialization/Archive.h" #include "Templates/Casts.h" #include "Templates/SharedPointer.h" #include "Templates/Tuple.h" #include "Templates/UniqueObj.h" #include "Templates/UnrealTemplate.h" #include "UObject/Object.h" #include "UObject/ObjectMacros.h" #include "UObject/ObjectPtr.h" #include "UObject/Package.h" #include "UObject/UObjectGlobals.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/Notifications/SNotificationList.h" #include "IContentBrowserSingleton.h" #include "ContentBrowserModule.h" #define LOCTEXT_NAMESPACE "FoliageEdMode" UFoliageType* FFoliageEditUtility::SaveFoliageTypeObject(UFoliageType* InFoliageType, bool bInPlaceholderAsset) { UFoliageType* TypeToSave = nullptr; if (!InFoliageType->IsAsset()) { FString PackageName; FString AssetName = InFoliageType->GetDefaultNewAssetName(); UObject* FoliageSource = InFoliageType->GetSource(); if (FoliageSource) { // Avoid using source name if this is a placeholder asset which is going to be replaced if (!bInPlaceholderAsset) { AssetName = FoliageSource->GetName() + TEXT("_FoliageType"); } // Build default settings asset name and path PackageName = FPackageName::GetLongPackagePath(FoliageSource->GetOutermost()->GetName()) + TEXT("/") + AssetName; } FSaveAssetDialogConfig SaveAssetDialogConfig; SaveAssetDialogConfig.DialogTitleOverride = LOCTEXT("SaveAssetDialogTitle", "Save Asset As"); SaveAssetDialogConfig.DefaultPath = FPaths::GetPath(PackageName); SaveAssetDialogConfig.DefaultAssetName = AssetName; SaveAssetDialogConfig.ExistingAssetPolicy = ESaveAssetDialogExistingAssetPolicy::Disallow; FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); FString SaveObjectPath = ContentBrowserModule.Get().CreateModalSaveAssetDialog(SaveAssetDialogConfig); if(!SaveObjectPath.IsEmpty()) { FSoftObjectPath SoftObjectPath(SaveObjectPath); TypeToSave = DuplicateFoliageTypeToNewPackage(SoftObjectPath.GetLongPackageName(), InFoliageType); } } else { TypeToSave = InFoliageType; } // Save to disk if (TypeToSave) { TArray PackagesToSave; PackagesToSave.Add(TypeToSave->GetOutermost()); const bool bCheckDirty = false; const bool bPromptToSave = false; FEditorFileUtils::EPromptReturnCode ReturnValue = FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, bCheckDirty, bPromptToSave); if (ReturnValue != FEditorFileUtils::PR_Success) { TypeToSave = nullptr; } } return TypeToSave; } UFoliageType* FFoliageEditUtility::DuplicateFoliageTypeToNewPackage(const FString& InPackageName, UFoliageType* InFoliageType) { UPackage* Package = CreatePackage(*InPackageName); UFoliageType* TypeToSave = nullptr; // We should not save a copy of this duplicate into the transaction buffer as it's an asset. Save and restore Transactional flag EObjectFlags Transactional = InFoliageType->HasAnyFlags(RF_Transactional) ? RF_Transactional : RF_NoFlags; InFoliageType->ClearFlags(Transactional); TypeToSave = Cast(StaticDuplicateObject(InFoliageType, Package, *FPackageName::GetLongPackageAssetName(InPackageName))); InFoliageType->SetFlags(Transactional); TypeToSave->SetFlags(RF_Standalone | RF_Public | Transactional); TypeToSave->Modify(); // Notify the asset registry FAssetRegistryModule::AssetCreated(TypeToSave); return TypeToSave; } void FFoliageEditUtility::ReplaceFoliageTypeObject(UWorld* InWorld, UFoliageType* OldType, UFoliageType* NewType) { FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "FoliageMode_ReplaceSettingsObject", "Foliage Editing: Replace Settings Object")); for (TActorIterator It(InWorld); It; ++It) { AInstancedFoliageActor* IFA = *It; IFA->Modify(); TUniqueObj OldInfo; if (IFA->RemoveFoliageInfoAndCopyValue(OldType, OldInfo)) { // Old component needs to go if (OldInfo->IsInitialized()) { OldInfo->Uninitialize(); } // Append instances if new foliage type is already exists in this actor // Otherwise just replace key entry for instances FFoliageInfo* NewInfo = IFA->FindInfo(NewType); if (NewInfo) { NewInfo->Instances.Append(OldInfo->Instances); NewInfo->ReallocateClusters(NewType); } else { // Make sure if type changes we have proper implementation TUniqueObj& NewFoliageInfo = IFA->AddFoliageInfo(NewType, MoveTemp(OldInfo)); NewFoliageInfo->ReallocateClusters(NewType); } } } } void FFoliageEditUtility::MoveActorFoliageInstancesToLevel(ULevel* InTargetLevel, AActor* InIFA) { // Can't move into a locked level if (FLevelUtils::IsLevelLocked(InTargetLevel)) { FNotificationInfo NotificatioInfo(NSLOCTEXT("UnrealEd", "CannotMoveFoliageIntoLockedLevel", "Cannot move the selected foliage into a locked level")); NotificatioInfo.bUseThrobber = false; FSlateNotificationManager::Get().AddNotification(NotificatioInfo)->SetCompletionState(SNotificationItem::CS_Fail); return; } // Get a world context UWorld* World = InTargetLevel->OwningWorld; bool PromptToMoveFoliageTypeToAsset = World->GetStreamingLevels().Num() > 0; bool ShouldPopulateMeshList = false; const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "MoveSelectedFoliageToSelectedLevel", "Move Selected Foliage to Level"), !GEditor->IsTransactionActive()); // Iterate over all foliage actors in the world and move selected instances to a foliage actor in the target level const int32 NumLevels = World->GetNumLevels(); for (int32 LevelIdx = 0; LevelIdx < NumLevels; ++LevelIdx) { ULevel* Level = World->GetLevel(LevelIdx); if (Level != InTargetLevel) { AInstancedFoliageActor* IFA = AInstancedFoliageActor::GetInstancedFoliageActorForLevel(Level, /*bCreateIfNone*/ false); if (IFA == nullptr) { continue; } if (InIFA && IFA != InIFA) { continue; } bool CanMoveInstanceType = true; TMap InstancesFoliageType = IFA->GetAllInstancesFoliageType(); for (auto& MeshPair : InstancesFoliageType) { if (MeshPair.Key != nullptr && MeshPair.Value != nullptr && !MeshPair.Key->IsAsset()) { // Keep previous selection TSet PreviousSelectionSet = MeshPair.Value->SelectedIndices; TArray PreviousSelectionArray; PreviousSelectionArray.Reserve(PreviousSelectionSet.Num()); for (int32& Value : PreviousSelectionSet) { PreviousSelectionArray.Add(Value); } UFoliageType* NewFoliageType = SaveFoliageTypeObject(MeshPair.Key); if (NewFoliageType != nullptr && NewFoliageType != MeshPair.Key) { ReplaceFoliageTypeObject(World, MeshPair.Key, NewFoliageType); } CanMoveInstanceType = NewFoliageType != nullptr; if (NewFoliageType != nullptr) { // Restore previous selection for move operation FFoliageInfo* MeshInfo = IFA->FindInfo(NewFoliageType); MeshInfo->SelectInstances(true, PreviousSelectionArray); } } } // Update our actor if we saved some foliage type as asset if (CanMoveInstanceType) { IFA = AInstancedFoliageActor::GetInstancedFoliageActorForLevel(Level, /*bCreateIfNone*/ false); ensure(IFA != nullptr); IFA->MoveAllInstancesToLevel(InTargetLevel); } if (InIFA) { return; } } } } #undef LOCTEXT_NAMESPACE