Files
UnrealEngine/Engine/Source/Editor/UnrealEd/Private/EditorLevelUtils.cpp
2025-05-18 13:04:45 +08:00

1514 lines
49 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
EditorLevelUtils.cpp: Editor-specific level management routines
=============================================================================*/
#include "EditorLevelUtils.h"
#include "Misc/MessageDialog.h"
#include "Misc/ScopedSlowTask.h"
#include "UObject/GarbageCollection.h"
#include "UObject/Class.h"
#include "UObject/Package.h"
#include "Engine/EngineTypes.h"
#include "GameFramework/Actor.h"
#include "Engine/World.h"
#include "Model.h"
#include "Engine/Brush.h"
#include "Editor/EditorEngine.h"
#include "Editor/UnrealEdEngine.h"
#include "Factories/WorldFactory.h"
#include "Editor/GroupActor.h"
#include "EngineGlobals.h"
#include "UObject/UObjectHash.h"
#include "UObject/UObjectIterator.h"
#include "UObject/ReferenceChainSearch.h"
#include "GameFramework/WorldSettings.h"
#include "Engine/LevelStreaming.h"
#include "Engine/Selection.h"
#include "Editor.h"
#include "Editor/Transactor.h"
#include "EditorModeManager.h"
#include "EditorModes.h"
#include "FileHelpers.h"
#include "UnrealEdGlobals.h"
#include "EditorSupportDelegates.h"
#include "BusyCursor.h"
#include "LevelUtils.h"
#include "Layers/LayersSubsystem.h"
#include "ScopedTransaction.h"
#include "ActorEditorUtils.h"
#include "ContentStreaming.h"
#include "PackageTools.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "Engine/LevelStreamingVolume.h"
#include "Components/ModelComponent.h"
#include "Misc/RuntimeErrors.h"
#include "HAL/PlatformApplicationMisc.h"
#include "HAL/IConsoleManager.h"
#include "IAssetTools.h"
#include "AssetToolsModule.h"
#include "Dialogs/Dialogs.h"
#include "Algo/AnyOf.h"
#include "Elements/Framework/EngineElementsLibrary.h"
#include "Elements/Interfaces/TypedElementWorldInterface.h"
DEFINE_LOG_CATEGORY(LogLevelTools);
#define LOCTEXT_NAMESPACE "EditorLevelUtils"
UEditorLevelUtils::FCanMoveActorToLevelDelegate UEditorLevelUtils::CanMoveActorToLevelDelegate;
UEditorLevelUtils::FOnMoveActorsToLevelEvent UEditorLevelUtils::OnMoveActorsToLevelEvent;
int32 UEditorLevelUtils::MoveActorsToLevel(const TArray<AActor*>& ActorsToMove, ULevelStreaming* DestStreamingLevel, bool bWarnAboutReferences, bool bWarnAboutRenaming)
{
return MoveActorsToLevel(ActorsToMove, DestStreamingLevel ? DestStreamingLevel->GetLoadedLevel() : nullptr, bWarnAboutReferences, bWarnAboutRenaming, /*bMoveAllOrFail*/false);
}
int32 UEditorLevelUtils::MoveActorsToLevel(const TArray<AActor*>& ActorsToMove, ULevel* DestLevel, bool bWarnAboutReferences, bool bWarnAboutRenaming, bool bMoveAllOrFail, TArray<AActor*>* OutActors /*=nullptr*/)
{
const bool bMoveActors = true;
return CopyOrMoveActorsToLevel(ActorsToMove, DestLevel, bMoveActors, bWarnAboutReferences, bWarnAboutRenaming, bMoveAllOrFail, OutActors);
}
int32 UEditorLevelUtils::CopyActorsToLevel(const TArray<AActor*>& ActorsToMove, ULevel* DestLevel, bool bWarnAboutReferences, bool bWarnAboutRenaming, bool bMoveAllOrFail, TArray<AActor*>* OutActors /*=nullptr*/)
{
const bool bMoveActors = false;
return CopyOrMoveActorsToLevel(ActorsToMove, DestLevel, bMoveActors, bWarnAboutReferences, bWarnAboutRenaming, bMoveAllOrFail, OutActors);
}
int32 UEditorLevelUtils::CopyOrMoveActorsToLevel(const TArray<AActor*>& ActorsToMove, ULevel* DestLevel, bool bMoveActors, bool bWarnAboutReferences /*= true*/, bool bWarnAboutRenaming /*= true*/, bool bMoveAllOrFail /*= false*/, TArray<AActor*>* OutActors /*=nullptr*/)
{
int32 NumMovedActors = 0;
if (DestLevel)
{
UWorld* OwningWorld = DestLevel->OwningWorld;
// Backup the current contents of the clipboard string as we'll be using cut/paste features to move actors
// between levels and this will trample over the clipboard data.
FString OriginalClipboardContent;
FPlatformApplicationMisc::ClipboardPaste(OriginalClipboardContent);
// The final list of actors to move after invalid actors were removed
TArray<AActor*> FinalMoveList;
TArray<TWeakObjectPtr<AActor>> FinalWeakMoveList;
FinalMoveList.Reserve(ActorsToMove.Num());
bool bIsDestLevelLocked = FLevelUtils::IsLevelLocked(DestLevel);
int32 ActorCount = 0;
if (!bIsDestLevelLocked)
{
for (AActor* CurActor : ActorsToMove)
{
if (!CurActor)
{
continue;
}
ActorCount++;
bool bIsSourceLevelLocked = FLevelUtils::IsLevelLocked(CurActor);
if (!bIsSourceLevelLocked)
{
if (CurActor->GetLevel() != DestLevel)
{
bool bCanMove = true;
CanMoveActorToLevelDelegate.Broadcast(CurActor, DestLevel, bCanMove);
if (bCanMove)
{
FinalMoveList.Add(CurActor);
}
}
else
{
UE_LOG(LogLevelTools, Warning, TEXT("%s is already in the destination level so it was ignored"), *CurActor->GetName());
}
}
else
{
UE_LOG(LogLevelTools, Error, TEXT("The source level '%s' is locked so actors could not be moved"), *CurActor->GetLevel()->GetName());
}
}
}
else
{
UE_LOG(LogLevelTools, Error, TEXT("The destination level '%s' is locked so actors could not be moved"), *DestLevel->GetName());
}
if (FinalMoveList.Num() > 0 && (!bMoveAllOrFail || FinalMoveList.Num() == ActorCount))
{
TArray<TTuple<FSoftObjectPath, FSoftObjectPath>> ActorPathMapping;
GEditor->SelectNone(false, true, false);
USelection* ActorSelection = GEditor->GetSelectedActors();
ActorSelection->BeginBatchSelectOperation();
FinalWeakMoveList.Reserve(FinalMoveList.Num());
for (AActor* Actor : FinalMoveList)
{
FinalWeakMoveList.Add(Actor);
check(Actor->CopyPasteId == INDEX_NONE);
Actor->CopyPasteId = ActorPathMapping.Add(TTuple<FSoftObjectPath, FSoftObjectPath>(FSoftObjectPath(Actor), FSoftObjectPath()));
GEditor->SelectActor(Actor, true, false);
}
ActorSelection->EndBatchSelectOperation(false);
if (GEditor->GetSelectedActorCount() > 0)
{
// Start the transaction
FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "MoveSelectedActorsToSelectedLevel", "Move Actors To Level"));
// Broadcast event
if(bMoveActors)
{
OnMoveActorsToLevelEvent.Broadcast(ActorsToMove, DestLevel);
}
// Cache the old level
ULevel* OldCurrentLevel = OwningWorld->GetCurrentLevel();
FString DestinationData;
GEditor->CopySelectedActorsToClipboard(OwningWorld, bMoveActors, bMoveActors, bWarnAboutReferences, &DestinationData);
if (!DestinationData.IsEmpty())
{
// Set the new level and force it visible while we do the paste
const bool bLevelVisible = DestLevel->bIsVisible;
if (!bLevelVisible)
{
UEditorLevelUtils::SetLevelVisibility(DestLevel, true, false);
}
OwningWorld->SetCurrentLevel(DestLevel);
bool bSelectionChanged = false;
FDelegateHandle SelectionChangedEvent = USelection::SelectionChangedEvent.AddLambda([&bSelectionChanged](UObject* Object) { bSelectionChanged = true; });
const bool bDuplicate = false;
const bool bOffsetLocations = false;
const bool bWarnIfHidden = false;
GEditor->edactPasteSelected(OwningWorld, bDuplicate, bOffsetLocations, bWarnIfHidden, &DestinationData);
USelection::SelectionChangedEvent.Remove(SelectionChangedEvent);
// Restore the original current level
OwningWorld->SetCurrentLevel(OldCurrentLevel);
if (bSelectionChanged)
{
// Build a remapping of old to new names so we can do a fixup
for (FSelectionIterator It(GEditor->GetSelectedActorIterator()); It; ++It)
{
AActor* Actor = static_cast<AActor*>(*It);
if (ActorPathMapping.IsValidIndex(Actor->CopyPasteId))
{
TTuple<FSoftObjectPath, FSoftObjectPath>& Tuple = ActorPathMapping[Actor->CopyPasteId];
check(Tuple.Value.IsNull());
Tuple.Value = FSoftObjectPath(Actor);
if (OutActors)
{
OutActors->Add(Actor);
}
}
else
{
UE_LOG(LogLevelTools, Error, TEXT("Cannot find remapping for moved actor ID %s, any soft references pointing to it will be broken!"), *Actor->GetPathName());
}
// Reset CopyPasteId on new actors
Actor->CopyPasteId = INDEX_NONE;
}
// Only do Asset Rename on Move (Copy should not affect existing references)
if (bMoveActors)
{
FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>(TEXT("AssetTools"));
TArray<FAssetRenameData> RenameData;
for (TTuple<FSoftObjectPath, FSoftObjectPath>& Pair : ActorPathMapping)
{
if (Pair.Value.IsValid())
{
RenameData.Add(FAssetRenameData(Pair.Key, Pair.Value, true));
}
}
if (RenameData.Num() > 0)
{
if (bWarnAboutRenaming)
{
AssetToolsModule.Get().RenameAssetsWithDialog(RenameData);
}
else
{
AssetToolsModule.Get().RenameAssets(RenameData);
}
}
}
// Restore new level visibility to previous state
if (!bLevelVisible)
{
UEditorLevelUtils::SetLevelVisibility(DestLevel, false, false);
}
}
}
// The moved (pasted) actors will now be selected
NumMovedActors += FinalMoveList.Num();
}
for (TWeakObjectPtr<AActor> ActorPtr : FinalWeakMoveList)
{
// It is possible a GC happens because of RenameAssets being called so here we want to update the CopyPasteId only on reachable actors
if (AActor* Actor = ActorPtr.Get(/*bEvenIfPendingKill=*/true))
{
check(Actor->CopyPasteId != INDEX_NONE);
// Reset CopyPasteId on source actors
Actor->CopyPasteId = INDEX_NONE;
}
}
}
// Restore the original clipboard contents
FPlatformApplicationMisc::ClipboardCopy(*OriginalClipboardContent);
}
return NumMovedActors;
}
int32 UEditorLevelUtils::MoveSelectedActorsToLevel(ULevelStreaming* DestStreamingLevel, bool bWarnAboutReferences)
{
ensureAsRuntimeWarning(DestStreamingLevel != nullptr);
return DestStreamingLevel ? MoveSelectedActorsToLevel(DestStreamingLevel->GetLoadedLevel(), bWarnAboutReferences) : 0;
}
int32 UEditorLevelUtils::MoveSelectedActorsToLevel(ULevel* DestLevel, bool bWarnAboutReferences)
{
if (ensureAsRuntimeWarning(DestLevel != nullptr))
{
TArray<AActor*> ActorsToMove;
for (FSelectionIterator It(GEditor->GetSelectedActorIterator()); It; ++It)
{
if (AActor* Actor = Cast<AActor>(*It))
{
ActorsToMove.Add(Actor);
}
}
return MoveActorsToLevel(ActorsToMove, DestLevel, bWarnAboutReferences);
}
return 0;
}
ULevel* UEditorLevelUtils::AddLevelsToWorld(UWorld* InWorld, TArray<FString> PackageNames, TSubclassOf<ULevelStreaming> LevelStreamingClass)
{
if (!ensure(InWorld))
{
return nullptr;
}
FScopedSlowTask SlowTask(static_cast<float>(PackageNames.Num()), LOCTEXT("AddLevelsToWorldTask", "Adding Levels to World"));
SlowTask.MakeDialog();
// Sort the level packages alphabetically by name.
PackageNames.Sort();
// Fire ULevel::LevelDirtiedEvent when falling out of scope.
FScopedLevelDirtied LevelDirtyCallback;
// Try to add the levels that were specified in the dialog.
ULevel* NewLevel = nullptr;
for (const FString& PackageName : PackageNames)
{
SlowTask.EnterProgressFrame();
if (ULevelStreaming* NewStreamingLevel = AddLevelToWorld_Internal(InWorld, FAddLevelToWorldParams(LevelStreamingClass, *PackageName)))
{
NewLevel = NewStreamingLevel->GetLoadedLevel();
if (NewLevel)
{
LevelDirtyCallback.Request();
}
}
} // for each file
// Set the last loaded level to be the current level
if (NewLevel)
{
if (InWorld->SetCurrentLevel(NewLevel))
{
FEditorDelegates::NewCurrentLevel.Broadcast();
}
}
if (!IsRunningCommandlet())
{
// For safety
if (GLevelEditorModeTools().IsModeActive(FBuiltinEditorModes::EM_Landscape))
{
GLevelEditorModeTools().ActivateDefaultMode();
}
}
// Broadcast the levels have changed (new style)
InWorld->BroadcastLevelsChanged();
FEditorDelegates::RefreshLevelBrowser.Broadcast();
if (GUnrealEd)
{
// Update volume actor visibility for each viewport since we loaded a level which could potentially contain volumes
GUnrealEd->UpdateVolumeActorVisibility(nullptr);
}
return NewLevel;
}
ULevelStreaming* UEditorLevelUtils::AddLevelToWorld(UWorld* InWorld, const TCHAR* LevelPackageName, TSubclassOf<ULevelStreaming> LevelStreamingClass, const FTransform& LevelTransform)
{
FAddLevelToWorldParams Params(LevelStreamingClass, LevelPackageName);
Params.Transform = LevelTransform;
return AddLevelToWorld(InWorld, Params);
}
ULevelStreaming* UEditorLevelUtils::AddLevelToWorld(UWorld* InWorld, const FAddLevelToWorldParams& InParams)
{
if (!ensure(InWorld))
{
return nullptr;
}
FScopedSlowTask SlowTask(0, LOCTEXT("AddLevelToWorldTask", "Adding Level to World"));
SlowTask.MakeDialog();
// Fire ULevel::LevelDirtiedEvent when falling out of scope.
FScopedLevelDirtied LevelDirtyCallback;
// Try to add the levels that were specified in the dialog.
ULevel* NewLevel = nullptr;
ULevelStreaming* NewStreamingLevel = AddLevelToWorld_Internal(InWorld, InParams);
// Broadcast the levels have changed (new style)
InWorld->BroadcastLevelsChanged();
FEditorDelegates::RefreshLevelBrowser.Broadcast();
if (NewStreamingLevel)
{
NewLevel = NewStreamingLevel->GetLoadedLevel();
if (NewLevel)
{
LevelDirtyCallback.Request();
// Set the loaded level to be the current level
if (InWorld->SetCurrentLevel(NewLevel))
{
FEditorDelegates::NewCurrentLevel.Broadcast();
}
}
}
if (!IsRunningCommandlet())
{
// For safety
if (GLevelEditorModeTools().IsModeActive(FBuiltinEditorModes::EM_Landscape))
{
GLevelEditorModeTools().ActivateDefaultMode();
}
}
// Update volume actor visibility for each viewport since we loaded a level which could potentially contain volumes
if (GUnrealEd)
{
GUnrealEd->UpdateVolumeActorVisibility(nullptr);
}
return NewStreamingLevel;
}
ULevelStreaming* UEditorLevelUtils::AddLevelToWorld_Internal(UWorld* InWorld, const FAddLevelToWorldParams& InParams)
{
ULevel* NewLevel = nullptr;
ULevelStreaming* StreamingLevel = nullptr;
bool bIsPersistentLevel = (InWorld->PersistentLevel->GetOutermost()->GetFName() == InParams.PackageName);
if (bIsPersistentLevel || FLevelUtils::FindStreamingLevel(InWorld, InParams.PackageName))
{
// Do nothing if the level already exists in the world.
const FText MessageText = FText::Format(NSLOCTEXT("UnrealEd", "LevelAlreadyExistsInWorld", "A level with that name ({0}) already exists in the world."), FText::FromString(InParams.PackageName.ToString()));
FSuppressableWarningDialog::FSetupInfo Info(MessageText, LOCTEXT("AddLevelToWorld_Title", "Add Level"), "LevelAlreadyExistsInWorldWarning");
Info.ConfirmText = LOCTEXT("AlreadyExist_Ok", "Ok");
FSuppressableWarningDialog RemoveLevelWarning(Info);
RemoveLevelWarning.ShowModal();
}
else
{
// If the selected class is still NULL or the selected class is abstract, abort the operation.
if (InParams.LevelStreamingClass == nullptr || InParams.LevelStreamingClass->HasAnyClassFlags(CLASS_Abstract))
{
return nullptr;
}
const FScopedBusyCursor BusyCursor;
StreamingLevel = NewObject<ULevelStreaming>(InWorld, InParams.LevelStreamingClass, NAME_None, RF_NoFlags, NULL);
// Associate a package name.
StreamingLevel->SetWorldAssetByPackageName(InParams.PackageName);
StreamingLevel->LevelTransform = InParams.Transform;
// Seed the level's draw color.
StreamingLevel->LevelColor = FLinearColor::MakeRandomColor();
// Callback to allow initialization
if (InParams.LevelStreamingCreatedCallback)
{
InParams.LevelStreamingCreatedCallback(StreamingLevel);
}
// Add the new level to world.
InWorld->AddStreamingLevel(StreamingLevel);
// Refresh just the newly created level.
TArray<ULevelStreaming*> LevelsForRefresh;
LevelsForRefresh.Add(StreamingLevel);
InWorld->RefreshStreamingLevels(LevelsForRefresh);
if (!StreamingLevel->HasAnyFlags(RF_Transient))
{
InWorld->MarkPackageDirty();
}
NewLevel = StreamingLevel->GetLoadedLevel();
if (NewLevel != nullptr)
{
EditorLevelUtils::SetLevelVisibility(NewLevel, true, true);
// Levels migrated from other projects may fail to load their world settings
// If so we create a new AWorldSettings actor here.
if (NewLevel->GetWorldSettings(false) == nullptr)
{
UWorld* SubLevelWorld = CastChecked<UWorld>(NewLevel->GetOuter());
FActorSpawnParameters SpawnInfo;
SpawnInfo.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
SpawnInfo.Name = GEngine->WorldSettingsClass->GetFName();
AWorldSettings* NewWorldSettings = SubLevelWorld->SpawnActor<AWorldSettings>(GEngine->WorldSettingsClass, SpawnInfo);
NewLevel->SetWorldSettings(NewWorldSettings);
}
}
}
if (NewLevel) // if the level was successfully added
{
FEditorDelegates::OnAddLevelToWorld.Broadcast(NewLevel);
}
return StreamingLevel;
}
ULevelStreaming* UEditorLevelUtils::SetStreamingClassForLevel(ULevelStreaming* InLevel, TSubclassOf<ULevelStreaming> LevelStreamingClass)
{
check(InLevel);
const FScopedBusyCursor BusyCursor;
// Cache off the package name, as it will be lost when unloading the level
const FName CachedPackageName = InLevel->GetWorldAssetPackageFName();
// First hide and remove the level if it exists
ULevel* Level = InLevel->GetLoadedLevel();
check(Level);
SetLevelVisibility(Level, false, false);
check(Level->OwningWorld);
UWorld* World = Level->OwningWorld;
World->RemoveStreamingLevel(InLevel);
// re-add the level with the desired streaming class
AddLevelToWorld(World, *(CachedPackageName.ToString()), LevelStreamingClass);
// Transfer level streaming settings
ULevelStreaming* NewStreamingLevel = FLevelUtils::FindStreamingLevel(Level);
if (NewStreamingLevel)
{
NewStreamingLevel->LevelTransform = InLevel->LevelTransform;
NewStreamingLevel->EditorStreamingVolumes = InLevel->EditorStreamingVolumes;
NewStreamingLevel->MinTimeBetweenVolumeUnloadRequests = InLevel->MinTimeBetweenVolumeUnloadRequests;
NewStreamingLevel->LevelColor = InLevel->LevelColor;
NewStreamingLevel->Keywords = InLevel->Keywords;
NewStreamingLevel->SetFolderPath(InLevel->GetFolderPath());
}
return NewStreamingLevel;
}
void UEditorLevelUtils::MakeLevelCurrent(ULevel* InLevel, bool bEvenIfLocked)
{
if (ensureAsRuntimeWarning(InLevel != nullptr))
{
// Locked levels can't be made current.
if (bEvenIfLocked || !FLevelUtils::IsLevelLocked(InLevel))
{
// Make current broadcast if it changed
if (InLevel->OwningWorld->SetCurrentLevel(InLevel))
{
FEditorDelegates::NewCurrentLevel.Broadcast();
}
// Deselect all selected builder brushes.
bool bDeselectedSomething = false;
for (FSelectionIterator It(GEditor->GetSelectedActorIterator()); It; ++It)
{
AActor* Actor = static_cast<AActor*>(*It);
checkSlow(Actor->IsA(AActor::StaticClass()));
ABrush* Brush = Cast< ABrush >(Actor);
if (Brush && FActorEditorUtils::IsABuilderBrush(Actor))
{
GEditor->SelectActor(Actor, /*bInSelected=*/ false, /*bNotify=*/ false);
bDeselectedSomething = true;
}
}
// Send a selection change callback if necessary.
if (bDeselectedSomething)
{
GEditor->NoteSelectionChange();
}
// Force the current level to be visible.
SetLevelVisibility(InLevel, true, false);
}
else
{
FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "Error_OperationDisallowedOnLockedLevelMakeLevelCurrent", "MakeLevelCurrent: The requested operation could not be completed because the level is locked."));
}
}
}
void UEditorLevelUtils::MakeLevelCurrent(ULevelStreaming* InStreamingLevel)
{
if (ensureAsRuntimeWarning(InStreamingLevel != nullptr))
{
MakeLevelCurrent(InStreamingLevel->GetLoadedLevel());
}
}
bool UEditorLevelUtils::PrivateRemoveInvalidLevelFromWorld(ULevelStreaming* InLevelStreaming)
{
bool bRemovedLevelStreaming = false;
if (InLevelStreaming)
{
check(InLevelStreaming->GetLoadedLevel() == NULL); // This method is designed to be used to remove left over references to null levels
InLevelStreaming->Modify();
// Disassociate the level from the volume.
for (ALevelStreamingVolume* LevelStreamingVolume : InLevelStreaming->EditorStreamingVolumes)
{
if (LevelStreamingVolume)
{
LevelStreamingVolume->Modify(true);
LevelStreamingVolume->StreamingLevelNames.Remove(InLevelStreaming->GetWorldAssetPackageFName());
}
}
// Disassociate the volumes from the level.
InLevelStreaming->EditorStreamingVolumes.Empty();
if (UWorld* OwningWorld = InLevelStreaming->GetWorld())
{
OwningWorld->RemoveStreamingLevel(InLevelStreaming);
OwningWorld->RefreshStreamingLevels({});
bRemovedLevelStreaming = true;
}
}
return bRemovedLevelStreaming;
}
bool UEditorLevelUtils::RemoveInvalidLevelFromWorld(ULevelStreaming* InLevelStreaming)
{
bool bRemoveSuccessful = PrivateRemoveInvalidLevelFromWorld(InLevelStreaming);
if (bRemoveSuccessful)
{
// Redraw the main editor viewports.
FEditorSupportDelegates::RedrawAllViewports.Broadcast();
// Broadcast the levels have changed (new style)
InLevelStreaming->GetWorld()->BroadcastLevelsChanged();
FEditorDelegates::RefreshLevelBrowser.Broadcast();
// Update selection for any selected actors that were in the level and are no longer valid
GEditor->NoteSelectionChange();
// Collect garbage to clear out the destroyed level
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
}
return bRemoveSuccessful;
}
ULevelStreaming* UEditorLevelUtils::CreateNewStreamingLevel(TSubclassOf<ULevelStreaming> LevelStreamingClass, const FString& PackagePath /*= TEXT("")*/, bool bMoveSelectedActorsIntoNewLevel /*= false*/)
{
FString Filename;
if (PackagePath.IsEmpty() || FPackageName::TryConvertLongPackageNameToFilename(PackagePath, Filename, FPackageName::GetMapPackageExtension()))
{
if (ensureAsRuntimeWarning(LevelStreamingClass.Get() != nullptr))
{
bool bUseSaveAs = PackagePath.IsEmpty();
return CreateNewStreamingLevelForWorld(*GEditor->GetEditorWorldContext().World(), LevelStreamingClass, Filename, bMoveSelectedActorsIntoNewLevel, nullptr, bUseSaveAs);
}
}
return nullptr;
}
namespace UE::EditorLevelUtils::Private
{
ULevel* GetPersistentLevelForNewStreamingLevel(UWorld* NewLevelWorld, UWorld* InTemplateWorld)
{
check(NewLevelWorld || InTemplateWorld);
return NewLevelWorld ? NewLevelWorld->PersistentLevel : InTemplateWorld->PersistentLevel;
}
UWorld* GetWorldForNewStreamingLevel(UWorld* InTemplateWorld, bool bCreateWorldPartition, bool bEnableWorldPartitionStreaming)
{
if (!InTemplateWorld)
{
// Create a new world
UWorldFactory* Factory = NewObject<UWorldFactory>();
Factory->bCreateWorldPartition = bCreateWorldPartition;
Factory->bEnableWorldPartitionStreaming = bEnableWorldPartitionStreaming;
Factory->WorldType = EWorldType::Inactive;
UPackage* Pkg = CreatePackage( NULL);
FName WorldName(TEXT("Untitled"));
EObjectFlags Flags = RF_Public | RF_Standalone;
UWorld* NewLevelWorld = CastChecked<UWorld>(Factory->FactoryCreateNew(UWorld::StaticClass(), Pkg, WorldName, Flags, NULL, GWarn));
if (NewLevelWorld)
{
FAssetRegistryModule::AssetCreated(NewLevelWorld);
}
return NewLevelWorld;
}
return nullptr;
}
};
ULevelStreaming* UEditorLevelUtils::CreateNewStreamingLevelForWorld(UWorld& InWorld, TSubclassOf<ULevelStreaming> LevelStreamingClass, const FString& DefaultFilename /* = TEXT( "" ) */, bool bMoveSelectedActorsIntoNewLevel /* = false */, UWorld* InTemplateWorld /* = nullptr */, bool bInUseSaveAs /*= true*/, TFunction<void(ULevel*)> InPreSaveLevelOperation /* = TFunction<void(ULevel*)>()*/, const FTransform& InTransform /* = FTransform::Identity */)
{
TArray<AActor*> ActorsToMove;
if (bMoveSelectedActorsIntoNewLevel)
{
ActorsToMove.Reserve(GEditor->GetSelectedActorCount());
for (FSelectionIterator It(GEditor->GetSelectedActorIterator()); It; ++It)
{
if (AActor* Actor = Cast<AActor>(*It))
{
ActorsToMove.Add(Actor);
}
}
}
return CreateNewStreamingLevelForWorld(InWorld, LevelStreamingClass, /*bUseExternalActors=*/false, DefaultFilename, &ActorsToMove, InTemplateWorld, bInUseSaveAs, /*bIsPartitioned=*/false, InPreSaveLevelOperation, InTransform);
}
ULevelStreaming* UEditorLevelUtils::CreateNewStreamingLevelForWorld(UWorld& InWorld, TSubclassOf<ULevelStreaming> LevelStreamingClass, bool bUseExternalActors, const FString& DefaultFilename /* = TEXT("") */, const TArray<AActor*>* ActorsToMove /* = nullptr */, UWorld* InTemplateWorld /* = nullptr */, bool bInUseSaveAs /*= true*/, bool bIsPartitioned /*= false*/, TFunction<void(ULevel*)> InPreSaveLevelOperation /* = TFunction<void(ULevel*)>()*/, const FTransform& InTransform /* = FTransform::Identity */)
{
FCreateNewStreamingLevelForWorldParams CreateParams(LevelStreamingClass, DefaultFilename);
CreateParams.bUseExternalActors = bUseExternalActors;
CreateParams.ActorsToMove = ActorsToMove;
CreateParams.TemplateWorld = InTemplateWorld;
CreateParams.bUseSaveAs = bInUseSaveAs;
CreateParams.bCreateWorldPartition = bIsPartitioned;
CreateParams.PreSaveLevelCallback = InPreSaveLevelOperation;
CreateParams.Transform = InTransform;
return CreateNewStreamingLevelForWorld(InWorld, CreateParams);
}
ULevelStreaming* UEditorLevelUtils::CreateNewStreamingLevelForWorld(UWorld& InWorld, const FCreateNewStreamingLevelForWorldParams& InCreateParams)
{
// Editor modes cannot be active when any level saving occurs.
if (!IsRunningCommandlet())
{
GLevelEditorModeTools().DeactivateAllModes();
}
using namespace UE::EditorLevelUtils::Private;
// Make sure we reenable the default mode on exit
ON_SCOPE_EXIT
{
if (!IsRunningCommandlet())
{
GLevelEditorModeTools().ActivateDefaultMode();
}
};
// This is the world we are adding the new level to
UWorld* WorldToAddLevelTo = &InWorld;
// This is the new streaming level's world not the persistent level world
UWorld* NewLevelWorld = GetWorldForNewStreamingLevel(InCreateParams.TemplateWorld, InCreateParams.bCreateWorldPartition, InCreateParams.bEnableWorldPartitionStreaming);
ULevel* PersistentLevel = GetPersistentLevelForNewStreamingLevel(NewLevelWorld, InCreateParams.TemplateWorld);
// No need to convert actors in partitioned worlds as they are already external
if (InCreateParams.bUseExternalActors && !InCreateParams.bCreateWorldPartition)
{
PersistentLevel->ConvertAllActorsToPackaging(true);
PersistentLevel->bUseExternalActors = true;
}
check(InCreateParams.bCreateWorldPartition == PersistentLevel->bIsPartitioned);
if (InCreateParams.PreSaveLevelCallback)
{
// Call lambda before saving level
InCreateParams.PreSaveLevelCallback(PersistentLevel);
}
bool bNewWorldSaved = false;
FString NewPackageName = InCreateParams.DefaultFilename;
if (InCreateParams.bUseSaveAs)
{
bNewWorldSaved = FEditorFileUtils::SaveLevelAs(PersistentLevel, &NewPackageName);
}
else
{
bNewWorldSaved = FEditorFileUtils::SaveLevel(PersistentLevel, InCreateParams.DefaultFilename, &NewPackageName);
}
if (bNewWorldSaved && !NewPackageName.IsEmpty())
{
NewPackageName = FPackageName::FilenameToLongPackageName(NewPackageName);
// Find or Load package and re-assign NewLevelWorld in case it was duplicated by Save
UPackage* NewPackage = LoadPackage(nullptr, *NewPackageName, LOAD_None);
if (NewPackage)
{
NewLevelWorld = UWorld::FindWorldInPackage(NewPackage);
}
}
// If the new world was saved successfully, import it as a streaming level.
ULevelStreaming* NewStreamingLevel = nullptr;
ULevel* NewLevel = nullptr;
if (bNewWorldSaved)
{
// Make sure to uninitialize the world since the level will be used as a streaming level
// This will make sure that the initialization order will be respected.
// One example is world partition initialization done inside ULevel::OnLevelLoaded.
if (NewLevelWorld && NewLevelWorld->bIsWorldInitialized)
{
NewLevelWorld->CleanupWorld();
}
FAddLevelToWorldParams AddLevelToWorldParams(InCreateParams.LevelStreamingClass, *NewPackageName);
AddLevelToWorldParams.Transform = InCreateParams.Transform;
AddLevelToWorldParams.LevelStreamingCreatedCallback = InCreateParams.LevelStreamingCreatedCallback;
NewStreamingLevel = AddLevelToWorld(WorldToAddLevelTo, AddLevelToWorldParams);
if (NewStreamingLevel != nullptr)
{
NewLevel = NewStreamingLevel->GetLoadedLevel();
// If we are moving the selected actors to the new level move them now
if (InCreateParams.ActorsToMove)
{
MoveActorsToLevel(*InCreateParams.ActorsToMove, NewLevel);
}
// Finally make the new level the current one
if (WorldToAddLevelTo->SetCurrentLevel(NewLevel))
{
FEditorDelegates::NewCurrentLevel.Broadcast();
}
}
}
// Broadcast the levels have changed (new style)
WorldToAddLevelTo->BroadcastLevelsChanged();
FEditorDelegates::RefreshLevelBrowser.Broadcast();
return NewStreamingLevel;
}
bool UEditorLevelUtils::RemoveLevelsFromWorld(TArray<ULevel*> InLevels, bool bClearSelection, bool bResetTransBuffer)
{
// Check that all levels can be removed first
if (!InLevels.Num() || Algo::AnyOf(InLevels, [](ULevel* LevelToRemove)
{
if (!LevelToRemove || LevelToRemove->IsPersistentLevel())
{
return true;
}
if (FLevelUtils::IsLevelLocked(LevelToRemove))
{
FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "Error_OperationDisallowedOnLockedLevelRemoveLevelFromWorld", "RemoveLevelFromWorld: The requested operation could not be completed because the level is locked."));
return true;
}
return false;
}))
{
return false;
}
TSet<UWorld*> ChangedWorlds;
TArray<UPackage*> PackagesToUnload;
PackagesToUnload.Reserve(InLevels.Num());
TArray<FName> PackageNames;
PackageNames.Reserve(InLevels.Num());
ULayersSubsystem* Layers = GEditor->GetEditorSubsystem<ULayersSubsystem>();
// Mark all Levels as being removed to prevent iteration on them while we are removing all of them.
// PrivateRemoveLevelFromWorld will end up calling UWorld::UpdateStreamingState.
// This can broadcast levels change events to listeners. By setting bIsBeingRemoved = true we make sure those levels do not get iterated and cause issues.
for (ULevel* Level : InLevels)
{
Level->bIsBeingRemoved = true;
}
for (ULevel* Level : InLevels)
{
Layers->RemoveLevelLayerInformation(Level);
GEditor->CloseEditedWorldAssets(CastChecked<UWorld>(Level->GetOuter()));
UWorld* OwningWorld = Level->OwningWorld;
const bool bRemovingCurrentLevel = Level->IsCurrentLevel();
PrivateRemoveLevelFromWorld(Level);
if (bRemovingCurrentLevel)
{
// we must set a new level. It must succeed
const bool bEvenIfLocked = true;
MakeLevelCurrent(OwningWorld->PersistentLevel, bEvenIfLocked);
}
FEditorSupportDelegates::PrepareToCleanseEditorObject.Broadcast(Level);
PrivateDestroyLevel(Level);
PackagesToUnload.Add(Level->GetOutermost());
// Keep Names in other list because unload of package will make the PackagesToUnload invalid.
PackageNames.Add(Level->GetOutermost()->GetFName());
ChangedWorlds.Add(OwningWorld);
Level->bIsBeingRemoved = false;
}
FText TransResetText(LOCTEXT("RemoveLevelTransReset", "Removing Levels from World"));
if (bResetTransBuffer && GEditor->Trans)
{
GEditor->Trans->Reset(TransResetText);
}
UPackageTools::FUnloadPackageParams UnloadParams(PackagesToUnload);
UnloadParams.bResetTransBuffer = bResetTransBuffer;
const bool bUnloadResult = UPackageTools::UnloadPackages(UnloadParams);
if (!bUnloadResult && !UnloadParams.OutErrorMessage.IsEmpty())
{
FMessageDialog::Open(EAppMsgType::Ok, UnloadParams.OutErrorMessage);
}
// Redraw the main editor viewports.
FEditorSupportDelegates::RedrawAllViewports.Broadcast();
for (UWorld* ChangedWorld : ChangedWorlds)
{
// Broadcast the levels have changed (new style)
ChangedWorld->BroadcastLevelsChanged();
}
FEditorDelegates::RefreshLevelBrowser.Broadcast();
// Reset transaction buffer and run GC to clear out the destroyed level
GEditor->Cleanse(bClearSelection, false, TransResetText, bResetTransBuffer);
auto CheckPackage = [](const TArray<FName>& PackageNames, EPrintStaleReferencesOptions Options)
{
// Check Package no longer exists
for (const FName& LevelPackageName : PackageNames)
{
// Ensure that world was removed
UPackage* LevelPackage = FindObjectFast<UPackage>(NULL, LevelPackageName);
if (LevelPackage != nullptr)
{
UWorld* TheWorld = UWorld::FindWorldInPackage(LevelPackage->GetOutermost());
if (TheWorld != nullptr)
{
FReferenceChainSearch::FindAndPrintStaleReferencesToObject(TheWorld, Options);
return false;
}
}
}
return true;
};
// Check that packages no longer exist
EPrintStaleReferencesOptions PrintStaleReferencesOptions = EPrintStaleReferencesOptions::Log;
if (bResetTransBuffer)
{
PrintStaleReferencesOptions = UObjectBaseUtility::IsGarbageEliminationEnabled() ? EPrintStaleReferencesOptions::Fatal : (EPrintStaleReferencesOptions::Error | EPrintStaleReferencesOptions::Ensure);
}
bool bFailed = !CheckPackage(PackageNames, PrintStaleReferencesOptions);
if (bFailed && ((PrintStaleReferencesOptions & EPrintStaleReferencesOptions::Fatal) != EPrintStaleReferencesOptions::Fatal))
{
// We tried avoiding clearing the Transaction buffer but it failed. Plan B.
GEditor->Cleanse(bClearSelection, false, TransResetText, true);
CheckPackage(PackageNames, PrintStaleReferencesOptions);
}
return true;
}
bool UEditorLevelUtils::RemoveLevelFromWorld(ULevel* InLevel, bool bClearSelection, bool bResetTransBuffer)
{
return RemoveLevelsFromWorld({ InLevel }, bClearSelection, bResetTransBuffer);
}
void UEditorLevelUtils::PrivateRemoveLevelFromWorld(ULevel* InLevel)
{
bool bIsTransientLevelStreaming = false;
if (ULevelStreaming* StreamingLevel = ULevelStreaming::FindStreamingLevel(InLevel))
{
bIsTransientLevelStreaming = StreamingLevel->HasAnyFlags(RF_Transient);
StreamingLevel->MarkAsGarbage();
InLevel->OwningWorld->RemoveStreamingLevel(StreamingLevel);
InLevel->OwningWorld->RefreshStreamingLevels({});
}
else if (InLevel->bIsVisible)
{
InLevel->OwningWorld->RemoveFromWorld(InLevel);
check(InLevel->bIsVisible == false);
}
if (FLevelCollection* LC = InLevel->GetCachedLevelCollection())
{
LC->RemoveLevel(InLevel);
}
InLevel->ReleaseRenderingResources();
IStreamingManager::Get().RemoveLevel(InLevel);
UWorld* World = InLevel->OwningWorld;
if (World->ContainsLevel(InLevel))
{
// Manually call level removal world delegates PreLevelRemovedFromWorld/LevelRemovedFromWorld to simulate what UWorld::RemoveFromWorld does.
FWorldDelegates::PreLevelRemovedFromWorld.Broadcast(InLevel, World);
if (World->RemoveLevel(InLevel))
{
FWorldDelegates::LevelRemovedFromWorld.Broadcast(InLevel, World);
}
}
if (InLevel->bIsLightingScenario)
{
World->PropagateLightingScenarioChange();
}
InLevel->ClearLevelComponents();
// remove all group actors from the world in the level we are removing
// otherwise, this will cause group actors to not be garbage collected
for (int32 GroupIndex = World->ActiveGroupActors.Num() - 1; GroupIndex >= 0; --GroupIndex)
{
AGroupActor* GroupActor = Cast<AGroupActor>(World->ActiveGroupActors[GroupIndex]);
if (GroupActor && GroupActor->IsInLevel(InLevel))
{
World->ActiveGroupActors.RemoveAt(GroupIndex);
}
}
// Mark all model components as pending kill so GC deletes references to them.
for (UModelComponent* ModelComponent : InLevel->ModelComponents)
{
if (ModelComponent != nullptr)
{
ModelComponent->MarkAsGarbage();
}
}
// Mark all actors and their components as pending kill so GC will delete references to them.
for (AActor* Actor : InLevel->Actors)
{
if (Actor != nullptr)
{
const bool bModify = false;
Actor->MarkComponentsAsGarbage(bModify);
Actor->MarkAsGarbage();
}
}
if (!bIsTransientLevelStreaming)
{
World->MarkPackageDirty();
}
World->BroadcastLevelsChanged();
}
void UEditorLevelUtils::PrivateDestroyLevel(ULevel* InLevel)
{
UWorld* World = InLevel->OwningWorld;
UObject* Outer = InLevel->GetOuter();
// Call cleanup on the outer world of the level so external hooks can be properly released, so that unloading the package isn't prevented.
UWorld* OuterWorld = Cast<UWorld>(Outer);
if (OuterWorld && OuterWorld != World)
{
OuterWorld->CleanupWorld();
}
Outer->MarkAsGarbage();
InLevel->MarkAsGarbage();
Outer->ClearFlags(RF_Public | RF_Standalone);
UPackage* Package = InLevel->GetOutermost();
// We want to unconditionally destroy the level, so clear the dirty flag here so it can be unloaded successfully
if (Package->IsDirty())
{
Package->SetDirtyFlag(false);
}
}
void UEditorLevelUtils::DeselectAllSurfacesInLevel(ULevel* InLevel)
{
if (InLevel)
{
UModel* Model = InLevel->Model;
for (int32 SurfaceIndex = 0; SurfaceIndex < Model->Surfs.Num(); ++SurfaceIndex)
{
FBspSurf& Surf = Model->Surfs[SurfaceIndex];
if (Surf.PolyFlags & PF_Selected)
{
Model->ModifySurf(SurfaceIndex, false);
Surf.PolyFlags &= ~PF_Selected;
}
}
}
}
void UEditorLevelUtils::SetLevelVisibilityTemporarily(ULevel* Level, bool bShouldBeVisible)
{
// Nothing to do
if (Level == NULL)
{
return;
}
// Set the visibility of each actor in the p-level
for (decltype(Level->Actors)::TIterator ActorIter(Level->Actors); ActorIter; ++ActorIter)
{
AActor* CurActor = *ActorIter;
if (CurActor && !FActorEditorUtils::IsABuilderBrush(CurActor) && CurActor->bHiddenEdLevel == bShouldBeVisible)
{
CurActor->bHiddenEdLevel = !bShouldBeVisible;
CurActor->MarkComponentsRenderStateDirty();
}
}
// Set the visibility of each BSP surface in the p-level
UModel* CurLevelModel = Level->Model;
if (CurLevelModel)
{
for (TArray<FBspSurf>::TIterator SurfaceIterator(CurLevelModel->Surfs); SurfaceIterator; ++SurfaceIterator)
{
FBspSurf& CurSurf = *SurfaceIterator;
CurSurf.bHiddenEdLevel = !bShouldBeVisible;
}
}
// Add/remove model components from the scene
for (int32 ComponentIndex = 0; ComponentIndex < Level->ModelComponents.Num(); ComponentIndex++)
{
UModelComponent* CurLevelModelCmp = Level->ModelComponents[ComponentIndex];
if (CurLevelModelCmp)
{
CurLevelModelCmp->MarkRenderStateDirty();
}
}
Level->GetWorld()->SendAllEndOfFrameUpdates();
Level->bIsVisible = bShouldBeVisible;
if (Level->bIsLightingScenario)
{
Level->OwningWorld->PropagateLightingScenarioChange();
}
}
void SetLevelVisibilityNoGlobalUpdateInternal(ULevel* Level, const bool bShouldBeVisible, const bool bForceLayersVisible, const ELevelVisibilityDirtyMode ModifyMode)
{
// Nothing to do
if (Level == NULL)
{
return;
}
// Handle the case of the p-level
// The p-level can't be unloaded, so its actors/BSP should just be temporarily hidden/unhidden
// Also, intentionally do not force layers visible for the p-level
if (Level->IsPersistentLevel())
{
// Create a transaction so we can undo the visibility toggle
const FScopedTransaction Transaction(LOCTEXT("ToggleLevelVisibility", "Toggle Level Visibility"));
if (Level->bIsVisible != bShouldBeVisible && ModifyMode == ELevelVisibilityDirtyMode::ModifyOnChange)
{
Level->Modify();
}
// Set the visibility of each actor in the p-level
for (decltype(Level->Actors)::TIterator PLevelActorIter(Level->Actors); PLevelActorIter; ++PLevelActorIter)
{
AActor* CurActor = *PLevelActorIter;
if (CurActor && !FActorEditorUtils::IsABuilderBrush(CurActor) && CurActor->bHiddenEdLevel == bShouldBeVisible)
{
if (ModifyMode == ELevelVisibilityDirtyMode::ModifyOnChange)
{
CurActor->Modify(false);
}
CurActor->bHiddenEdLevel = !bShouldBeVisible;
CurActor->RegisterAllComponents();
CurActor->MarkComponentsRenderStateDirty();
}
}
// Set the visibility of each BSP surface in the p-level
UModel* CurLevelModel = Level->Model;
if (CurLevelModel)
{
if (ModifyMode == ELevelVisibilityDirtyMode::ModifyOnChange)
{
CurLevelModel->Modify(false);
}
for (TArray<FBspSurf>::TIterator SurfaceIterator(CurLevelModel->Surfs); SurfaceIterator; ++SurfaceIterator)
{
FBspSurf& CurSurf = *SurfaceIterator;
CurSurf.bHiddenEdLevel = !bShouldBeVisible;
}
}
// Add/remove model components from the scene
for (int32 ComponentIndex = 0; ComponentIndex < Level->ModelComponents.Num(); ComponentIndex++)
{
UModelComponent* CurLevelModelCmp = Level->ModelComponents[ComponentIndex];
if (CurLevelModelCmp)
{
if (bShouldBeVisible)
{
CurLevelModelCmp->RegisterComponentWithWorld(Level->OwningWorld);
}
else if (!bShouldBeVisible && CurLevelModelCmp->IsRegistered())
{
CurLevelModelCmp->UnregisterComponent();
}
}
}
Level->GetWorld()->OnLevelsChanged().Broadcast();
}
else
{
ULevelStreaming* StreamingLevel = NULL;
if (Level->OwningWorld == NULL || Level->OwningWorld->PersistentLevel != Level)
{
StreamingLevel = FLevelUtils::FindStreamingLevel(Level);
}
// Create a transaction so we can undo the visibility toggle
const FScopedTransaction Transaction(LOCTEXT("ToggleLevelVisibility", "Toggle Level Visibility"));
// Handle the case of a streaming level
if (StreamingLevel)
{
if (ModifyMode == ELevelVisibilityDirtyMode::ModifyOnChange)
{
// We need to set the RF_Transactional to make a streaming level serialize itself. so store the original ones, set the flag, and put the original flags back when done
EObjectFlags cachedFlags = StreamingLevel->GetFlags();
StreamingLevel->SetFlags(RF_Transactional);
StreamingLevel->Modify();
StreamingLevel->SetFlags(cachedFlags);
}
// Set the visibility state for this streaming level.
StreamingLevel->SetShouldBeVisibleInEditor(bShouldBeVisible);
}
ULayersSubsystem* Layers = GEditor->GetEditorSubsystem<ULayersSubsystem>();
if (!bShouldBeVisible)
{
Layers->RemoveLevelLayerInformation(Level);
}
// UpdateLevelStreaming sets Level->bIsVisible directly, so we need to make sure it gets saved to the transaction buffer.
if (Level->bIsVisible != bShouldBeVisible && ModifyMode == ELevelVisibilityDirtyMode::ModifyOnChange)
{
Level->Modify();
}
if (StreamingLevel)
{
Level->OwningWorld->FlushLevelStreaming();
// In the Editor we expect this operation will complete in a single call
check(Level->bIsVisible == bShouldBeVisible);
}
else if (Level->OwningWorld != NULL)
{
// In case we level has no associated StreamingLevel, remove or add to world directly
if (bShouldBeVisible)
{
if (!Level->bIsVisible)
{
Level->OwningWorld->AddToWorld(Level);
}
}
else
{
Level->OwningWorld->RemoveFromWorld(Level);
}
// In the Editor we expect this operation will complete in a single call
check(Level->bIsVisible == bShouldBeVisible);
}
if (bShouldBeVisible)
{
Layers->AddLevelLayerInformation(Level);
}
// Force the level's layers to be visible, if desired
FEditorSupportDelegates::RedrawAllViewports.Broadcast();
// Iterate over the level's actors, making a list of their layers and unhiding the layers.
auto& Actors = Level->Actors;
for (int32 ActorIndex = 0; ActorIndex < Actors.Num(); ++ActorIndex)
{
AActor* Actor = Actors[ActorIndex];
if (Actor)
{
bool bModified = false;
if (bShouldBeVisible && bForceLayersVisible &&
Layers->IsActorValidForLayer(Actor))
{
// Make the actor layer visible, if it's not already.
if (Actor->bHiddenEdLayer)
{
if (ModifyMode == ELevelVisibilityDirtyMode::ModifyOnChange)
{
bModified = Actor->Modify(false);
}
Actor->bHiddenEdLayer = false;
}
const bool bIsVisible = true;
Layers->SetLayersVisibility(Actor->Layers, bIsVisible);
}
// Set the visibility of each actor in the streaming level
if (!FActorEditorUtils::IsABuilderBrush(Actor) && Actor->bHiddenEdLevel == bShouldBeVisible)
{
if (!bModified && ModifyMode == ELevelVisibilityDirtyMode::ModifyOnChange)
{
bModified = Actor->Modify(false);
}
Actor->bHiddenEdLevel = !bShouldBeVisible;
if (bShouldBeVisible)
{
Actor->ReregisterAllComponents();
}
else
{
Actor->UnregisterAllComponents();
}
}
}
}
}
Level->bIsVisible = bShouldBeVisible;
// If the level is being hidden, deselect actors and surfaces that belong to this level. (Part 1/2)
if (!bShouldBeVisible && ModifyMode == ELevelVisibilityDirtyMode::ModifyOnChange)
{
if (UTypedElementSelectionSet* ActorSelectionSet = GEditor->GetSelectedActors()->GetElementSelectionSet())
{
TArray<FTypedElementHandle> LevelElementHandles;
ActorSelectionSet->ForEachSelectedElement<ITypedElementWorldInterface>(
[Level, &LevelElementHandles](const TTypedElement<ITypedElementWorldInterface>& Element)
{
if (Element.GetOwnerLevel() == Level)
{
LevelElementHandles.Add(Element);
}
return true;
});
ActorSelectionSet->DeselectElements(LevelElementHandles, FTypedElementSelectionOptions());
}
UEditorLevelUtils::DeselectAllSurfacesInLevel(Level);
}
if (Level->bIsLightingScenario)
{
Level->OwningWorld->PropagateLightingScenarioChange();
}
}
void UEditorLevelUtils::SetLevelVisibility(ULevel* Level, const bool bShouldBeVisible, const bool bForceLayersVisible, const ELevelVisibilityDirtyMode ModifyMode)
{
TArray<ULevel*> Levels({ Level });
TArray<bool> bTheyShouldBeVisible({ bShouldBeVisible });
SetLevelsVisibility(Levels, bTheyShouldBeVisible, bForceLayersVisible, ModifyMode);
}
void UEditorLevelUtils::SetLevelsVisibility(const TArray<ULevel*>& Levels, const TArray<bool>& bTheyShouldBeVisible, const bool bForceLayersVisible, const ELevelVisibilityDirtyMode ModifyMode)
{
// Nothing to do
if (Levels.Num() == 0 || Levels.Num() != bTheyShouldBeVisible.Num())
{
return;
}
// Perform SetLevelVisibilityNoGlobalUpdateInternal for each Level
for (int32 LevelIndex = 0; LevelIndex < Levels.Num(); ++LevelIndex)
{
ULevel* Level = Levels[LevelIndex];
if (Level)
{
SetLevelVisibilityNoGlobalUpdateInternal(Level, bTheyShouldBeVisible[LevelIndex], bForceLayersVisible, ModifyMode);
}
}
// If at least 1 persistent level, then RedrawAllViewports.Broadcast
for (int32 LevelIndex = 0; LevelIndex < Levels.Num(); ++LevelIndex)
{
ULevel* Level = Levels[LevelIndex];
if (Level && Level->IsPersistentLevel())
{
FEditorSupportDelegates::RedrawAllViewports.Broadcast();
break;
}
}
// If at least 1 level becomes visible, force layers to update their actor status
// Otherwise, changes made on the layers for actors belonging to a non-visible level would not work
{
for (int32 LevelIndex = 0; LevelIndex < bTheyShouldBeVisible.Num(); ++LevelIndex)
{
if (bTheyShouldBeVisible[LevelIndex])
{
// Equivalent to GEditor->GetEditorSubsystem<ULayersSubsystem>()->UpdateAllActorsVisibilityDefault();
FEditorDelegates::RefreshLayerBrowser.Broadcast();
break;
}
}
}
// Notify the Scene Outliner, as new Actors may be present in the world.
GEngine->BroadcastLevelActorListChanged();
// If the level is being hidden, deselect actors and surfaces that belong to this level. (Part 2/2)
if (ModifyMode == ELevelVisibilityDirtyMode::ModifyOnChange)
{
for (int32 LevelIndex = 0; LevelIndex < bTheyShouldBeVisible.Num(); ++LevelIndex)
{
if (!bTheyShouldBeVisible[LevelIndex])
{
// Tell the editor selection status was changed.
GEditor->NoteSelectionChange();
break;
}
}
}
}
void UEditorLevelUtils::ForEachWorlds(UWorld* InWorld, TFunctionRef<bool(UWorld*)> Operation, bool bIncludeInWorld, bool bOnlyEditorVisible)
{
if (!InWorld)
{
return;
}
if (bIncludeInWorld)
{
if (!Operation(InWorld))
{
return;
}
}
// Iterate over the world's level array to find referenced levels ("worlds"). We don't
for (ULevelStreaming* StreamingLevel : InWorld->GetStreamingLevels())
{
if (StreamingLevel)
{
// If we asked for only sub-levels that are editor-visible, then limit our results appropriately
bool bShouldAlwaysBeLoaded = false; // Cast< ULevelStreamingAlwaysLoaded >( StreamingLevel ) != NULL;
if (!bOnlyEditorVisible || bShouldAlwaysBeLoaded || StreamingLevel->GetShouldBeVisibleInEditor())
{
const ULevel* Level = StreamingLevel->GetLoadedLevel();
// This should always be the case for valid level names as the Editor preloads all packages.
if (Level)
{
// Newer levels have their packages' world as the outer.
UWorld* World = Cast<UWorld>(Level->GetOuter());
if (World)
{
if (!Operation(World))
{
return;
}
}
}
}
}
}
// Levels can be loaded directly without StreamingLevel facilities
for (ULevel* Level : InWorld->GetLevels())
{
if (Level)
{
// Newer levels have their packages' world as the outer.
UWorld* World = Cast<UWorld>(Level->GetOuter());
if (World)
{
if (!Operation(World))
{
return;
}
}
}
}
}
void UEditorLevelUtils::GetWorlds(UWorld* InWorld, TArray<UWorld*>& OutWorlds, bool bIncludeInWorld, bool bOnlyEditorVisible)
{
OutWorlds.Empty();
ForEachWorlds(InWorld, [&OutWorlds](UWorld* World) { OutWorlds.AddUnique(World); return true; }, bIncludeInWorld, bOnlyEditorVisible);
}
const TArray<ULevel*> UEditorLevelUtils::GetLevels(UWorld* World)
{
if (!World)
{
return TArray<ULevel*>();
}
return World->GetLevels();
}
#undef LOCTEXT_NAMESPACE