// Copyright Epic Games, Inc. All Rights Reserved. #include "LevelInstanceEditorModule.h" #include "LevelInstanceActorDetails.h" #include "LevelInstancePivotDetails.h" #include "LevelInstanceSceneOutlinerColumn.h" #include "PackedLevelActorUtils.h" #include "LevelInstanceFilterPropertyTypeCustomization.h" #include "LevelInstance/LevelInstanceSubsystem.h" #include "LevelInstance/LevelInstanceInterface.h" #include "LevelInstance/LevelInstanceActor.h" #include "LevelInstance/LevelInstanceSettings.h" #include "PackedLevelActor/PackedLevelActor.h" #include "PackedLevelActor/PackedLevelActorBuilder.h" #include "LevelInstanceEditorSettings.h" #include "ToolMenus.h" #include "Editor.h" #include "EditorModeManager.h" #include "EditorModeRegistry.h" #include "FileHelpers.h" #include "LevelInstanceEditorMode.h" #include "LevelInstanceEditorModeCommands.h" #include "LevelEditorMenuContext.h" #include "ContentBrowserMenuContexts.h" #include "ContentBrowserModule.h" #include "IContentBrowserSingleton.h" #include "LevelEditor.h" #include "Engine/Selection.h" #include "PropertyEditorModule.h" #include "EditorLevelUtils.h" #include "Modules/ModuleManager.h" #include "Misc/MessageDialog.h" #include "NewLevelDialogModule.h" #include "Interfaces/IMainFrameModule.h" #include "Editor/EditorEngine.h" #include "AssetToolsModule.h" #include "IAssetTools.h" #include "Factories/BlueprintFactory.h" #include "ClassViewerModule.h" #include "ClassViewerFilter.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Misc/ScopeExit.h" #include "Widgets/Input/SNumericEntryBox.h" #include "Widgets/Input/SButton.h" #include "Widgets/SWindow.h" #include "SNewLevelInstanceDialog.h" #include "MessageLogModule.h" #include "Settings/EditorExperimentalSettings.h" #include "WorldPartition/WorldPartitionConverter.h" #include "WorldPartition/WorldPartitionActorLoaderInterface.h" #include "ScopedTransaction.h" #include "ISCSEditorUICustomization.h" #include "Tools/EdModeInteractiveToolsContext.h" #include "SceneOutlinerModule.h" #include "SceneOutlinerFwd.h" #include "Subsystems/BrowseToAssetOverrideSubsystem.h" IMPLEMENT_MODULE( FLevelInstanceEditorModule, LevelInstanceEditor ); #define LOCTEXT_NAMESPACE "LevelInstanceEditor" DEFINE_LOG_CATEGORY_STATIC(LogLevelInstanceEditor, Log, All); struct FLevelInstanceMenuUtils { static FToolMenuSection& CreateLevelSection(UToolMenu* Menu) { return CreateSection(Menu, FName("Level"), LOCTEXT("LevelSectionLabel", "Level")); } static FToolMenuSection& CreateCurrentEditSection(UToolMenu* Menu) { return CreateSection(Menu, FName("CurrentEdit"), LOCTEXT("CurrentEditSectionLabel", "Current Edit")); } static FToolMenuSection& CreateSection(UToolMenu* Menu, FName SectionName, const FText& SectionText) { FToolMenuSection* SectionPtr = Menu->FindSection(SectionName); if (!SectionPtr) { SectionPtr = &(Menu->AddSection(SectionName, SectionText)); } FToolMenuSection& Section = *SectionPtr; return Section; } static void CreateEditMenuEntry(FToolMenuSection& Section, ILevelInstanceInterface* LevelInstance, AActor* ContextActor, bool bSingleEntry) { FToolUIAction LevelInstanceEditAction; FText EntryDesc; AActor* LevelInstanceActor = CastChecked(LevelInstance); const bool bCanEdit = LevelInstance->CanEnterEdit(&EntryDesc); LevelInstanceEditAction.ExecuteAction.BindLambda([LevelInstance, ContextActor](const FToolMenuContext&) { LevelInstance->EnterEdit(ContextActor); }); LevelInstanceEditAction.CanExecuteAction.BindLambda([bCanEdit](const FToolMenuContext&) { return bCanEdit; }); FText EntryLabel = bSingleEntry ? LOCTEXT("EditLevelInstances", "Edit") : FText::FromString(LevelInstance->GetWorldAsset().GetAssetName()); if (bCanEdit) { FText EntryActionDesc = LOCTEXT("EditLevelInstancesPropertyTooltip", "Edit this level. Your changes will be applied to the level asset and to all other level instances based on it."); EntryDesc = FText::Format(LOCTEXT("LevelInstanceName", "{0}\n\nActor name: {1}\nAsset path: {2}"), EntryActionDesc, FText::FromString(LevelInstanceActor->GetActorLabel()), FText::FromString(LevelInstance->GetWorldAssetPackage())); } Section.AddMenuEntry(NAME_None, EntryLabel, EntryDesc, FSlateIcon(), LevelInstanceEditAction); } static void CreateEditSubMenu(UToolMenu* Menu, TArray LevelInstanceHierarchy, AActor* ContextActor) { FToolMenuSection& Section = Menu->AddSection(NAME_None, LOCTEXT("LevelInstanceContextEditSection", "Context")); for (ILevelInstanceInterface* LevelInstance : LevelInstanceHierarchy) { CreateEditMenuEntry(Section, LevelInstance, ContextActor, false); } } static void CreateEditPropertyOverridesMenuEntry(FToolMenuSection& Section, ILevelInstanceInterface* LevelInstance, AActor* ContextActor, bool bSingleEntry) { FToolUIAction LevelInstanceEditAction; FText EntryDesc; AActor* LevelInstanceActor = CastChecked(LevelInstance); const bool bCanEdit = LevelInstance->CanEnterEditPropertyOverrides(&EntryDesc); LevelInstanceEditAction.ExecuteAction.BindLambda([LevelInstance, ContextActor](const FToolMenuContext&) { LevelInstance->EnterEditPropertyOverrides(ContextActor); }); LevelInstanceEditAction.CanExecuteAction.BindLambda([bCanEdit](const FToolMenuContext&) { return bCanEdit; }); FText EntryLabel = bSingleEntry ? LOCTEXT("OverrideLevelInstances", "Override") : FText::FromString(LevelInstance->GetWorldAsset().GetAssetName()); if (bCanEdit) { FText EntryActionDesc = LOCTEXT("EditLevelInstancesPropertyOverridesTooltip", "Edit only this level instance, without changing the level asset or any other level instances."); EntryDesc = FText::Format(LOCTEXT("OverrideLevelInstanceName", "{0}\n\nActor name: {1}\nAsset path: {2}"), EntryActionDesc, FText::FromString(LevelInstanceActor->GetActorLabel()), FText::FromString(LevelInstance->GetWorldAssetPackage())); } Section.AddMenuEntry(NAME_None, EntryLabel, EntryDesc, FSlateIcon(), LevelInstanceEditAction); } static void CreateEditPropertyOverridesSubMenu(UToolMenu* Menu, TArray LevelInstanceHierarchy, AActor* ContextActor) { FToolMenuSection& Section = Menu->AddSection(NAME_None, LOCTEXT("LevelInstanceContextEditSection", "Context")); for (ILevelInstanceInterface* LevelInstance : LevelInstanceHierarchy) { CreateEditPropertyOverridesMenuEntry(Section, LevelInstance, ContextActor, false); } } static void MoveSelectionToLevelInstance(ILevelInstanceInterface* DestinationLevelInstance, const TArray& ActorsToMove) { DestinationLevelInstance->MoveActorsTo(ActorsToMove); } static void CreateEditMenu(UToolMenu* Menu, AActor* ContextActor) { if (ULevelInstanceSubsystem* LevelInstanceSubsystem = ContextActor->GetWorld()->GetSubsystem()) { TArray LevelInstanceHierarchy; LevelInstanceSubsystem->ForEachLevelInstanceAncestorsAndSelf(ContextActor, [&LevelInstanceHierarchy](ILevelInstanceInterface* AncestorLevelInstance) { LevelInstanceHierarchy.Add(AncestorLevelInstance); return true; }); // Don't create sub menu if only one Level Instance is available to edit if (LevelInstanceHierarchy.Num() == 1) { FToolMenuSection& Section = CreateLevelSection(Menu); CreateEditMenuEntry(Section, LevelInstanceHierarchy[0], ContextActor, true); } else if(LevelInstanceHierarchy.Num() > 1) { FToolMenuSection& Section = CreateLevelSection(Menu); Section.AddSubMenu( "EditLevelInstances", LOCTEXT("EditLevelInstances", "Edit"), LOCTEXT("EditLevelInstancesPropertyTooltip", "Edit this level. Your changes will be applied to the level asset and to all other level instances based on it."), FNewToolMenuDelegate::CreateStatic(&CreateEditSubMenu, MoveTemp(LevelInstanceHierarchy), ContextActor) ); } } } static void CreateEditPropertyOverridesMenu(UToolMenu* Menu, AActor* ContextActor) { if (!ULevelInstanceSettings::Get()->IsPropertyOverrideEnabled()) { return; } if (ULevelInstanceSubsystem* LevelInstanceSubsystem = ContextActor->GetWorld()->GetSubsystem()) { TArray LevelInstanceHierarchy; LevelInstanceSubsystem->ForEachLevelInstanceAncestorsAndSelf(ContextActor, [&LevelInstanceHierarchy](ILevelInstanceInterface* AncestorLevelInstance) { LevelInstanceHierarchy.Add(AncestorLevelInstance); return true; }); // Don't create sub menu if only one Level Instance is available to edit if (LevelInstanceHierarchy.Num() == 1) { FToolMenuSection& Section = CreateLevelSection(Menu); CreateEditPropertyOverridesMenuEntry(Section, LevelInstanceHierarchy[0], ContextActor, true); } else if (LevelInstanceHierarchy.Num() > 1) { FToolMenuSection& Section = CreateLevelSection(Menu); Section.AddSubMenu( "PropertyOverrideLevelInstances", LOCTEXT("EditLevelInstancesPropertyOverrides", "Override"), LOCTEXT("EditLevelInstancesPropertyOverridesTooltip", "Edit only this level instance, without changing the level asset or any other level instances."), FNewToolMenuDelegate::CreateStatic(&CreateEditPropertyOverridesSubMenu, MoveTemp(LevelInstanceHierarchy), ContextActor) ); } } } static void CreateSaveCancelMenu(UToolMenu* Menu, AActor* ContextActor) { ILevelInstanceInterface* LevelInstanceEdit = nullptr; if (ContextActor) { if (ULevelInstanceSubsystem* LevelInstanceSubsystem = ContextActor->GetWorld()->GetSubsystem()) { // Commit Property Overrides has priority LevelInstanceEdit = LevelInstanceSubsystem->GetEditingPropertyOverridesLevelInstance(); if (!LevelInstanceEdit) { LevelInstanceEdit = LevelInstanceSubsystem->GetEditingLevelInstance(); } } } // Commmit Property Overrides has priority if (!LevelInstanceEdit) { if (ULevelInstanceSubsystem* LevelInstanceSubsystem = GEditor->GetEditorWorldContext().World()->GetSubsystem()) { LevelInstanceEdit = LevelInstanceSubsystem->GetEditingPropertyOverridesLevelInstance(); } } // If no Property Overrides found try to find a regular Edit if (!LevelInstanceEdit) { if (ULevelInstanceSubsystem* LevelInstanceSubsystem = GEditor->GetEditorWorldContext().World()->GetSubsystem()) { LevelInstanceEdit = LevelInstanceSubsystem->GetEditingLevelInstance(); } } if (LevelInstanceEdit) { FToolMenuSection& Section = CreateCurrentEditSection(Menu); if (LevelInstanceEdit->IsEditingPropertyOverrides()) { FText CommitTooltip = LOCTEXT("LevelInstanceCommitPropertyOverridesTooltip", "Stop overriding this level instance and save any changes you've made."); const bool bCanCommit = LevelInstanceEdit->CanExitEditPropertyOverrides(/*bDiscardEdits=*/false, &CommitTooltip); FToolUIAction CommitAction; CommitAction.ExecuteAction.BindLambda([LevelInstanceEdit](const FToolMenuContext&) { LevelInstanceEdit->ExitEditPropertyOverrides(/*bDiscardEdits=*/false); }); CommitAction.CanExecuteAction.BindLambda([bCanCommit](const FToolMenuContext&) { return bCanCommit; }); Section.AddMenuEntry(NAME_None, LOCTEXT("LevelInstanceSavePropertyOverridesLabel", "Save Override(s)"), CommitTooltip, FSlateIcon(), CommitAction); FText DiscardTooltip = LOCTEXT("LevelInstanceDiscardPropertyOverridesTooltip", "Stop overriding this level instance and discard any changes you've made."); const bool bCanDiscard = LevelInstanceEdit->CanExitEditPropertyOverrides(/*bDiscardEdits=*/true, &DiscardTooltip); FToolUIAction DiscardAction; DiscardAction.ExecuteAction.BindLambda([LevelInstanceEdit](const FToolMenuContext&) { LevelInstanceEdit->ExitEditPropertyOverrides(/*bDiscardEdits=*/true); }); DiscardAction.CanExecuteAction.BindLambda([bCanDiscard](const FToolMenuContext&) { return bCanDiscard; }); Section.AddMenuEntry(NAME_None, LOCTEXT("LevelInstanceCancelPropertyOverridesLabel", "Cancel Override(s)"), DiscardTooltip, FSlateIcon(), DiscardAction); } else { FText CommitTooltip = LOCTEXT("LevelInstanceCommitTooltip", "Stop editing this level and save any changes you've made."); const bool bCanCommit = LevelInstanceEdit->CanExitEdit(/*bDiscardEdits=*/false, &CommitTooltip); FToolUIAction CommitAction; CommitAction.ExecuteAction.BindLambda([LevelInstanceEdit](const FToolMenuContext&) { LevelInstanceEdit->ExitEdit(/*bDiscardEdits=*/false); }); CommitAction.CanExecuteAction.BindLambda([bCanCommit](const FToolMenuContext&) { return bCanCommit; }); Section.AddMenuEntry(NAME_None, LOCTEXT("LevelInstanceSaveLabel", "Save"), CommitTooltip, FSlateIcon(), CommitAction); FText DiscardTooltip = LOCTEXT("LevelInstanceDiscardTooltip", "Stop editing this level and discard any changes you've made."); const bool bCanDiscard = LevelInstanceEdit->CanExitEdit(/*bDiscardEdits=*/true, &DiscardTooltip); FToolUIAction DiscardAction; DiscardAction.ExecuteAction.BindLambda([LevelInstanceEdit](const FToolMenuContext&) { LevelInstanceEdit->ExitEdit(/*bDiscardEdits=*/true); }); DiscardAction.CanExecuteAction.BindLambda([bCanDiscard](const FToolMenuContext&) { return bCanDiscard; }); Section.AddMenuEntry(NAME_None, LOCTEXT("LevelInstanceCancelLabel", "Cancel"), DiscardTooltip, FSlateIcon(), DiscardAction); } } } static UClass* GetDefaultLevelInstanceClass(ELevelInstanceCreationType CreationType) { if (CreationType == ELevelInstanceCreationType::PackedLevelActor) { return APackedLevelActor::StaticClass(); } ULevelInstanceEditorSettings* LevelInstanceEditorSettings = GetMutableDefault(); if (!LevelInstanceEditorSettings->LevelInstanceClassName.IsEmpty()) { UClass* LevelInstanceClass = LoadClass(nullptr, *LevelInstanceEditorSettings->LevelInstanceClassName, nullptr, LOAD_NoWarn); if (LevelInstanceClass && LevelInstanceClass->ImplementsInterface(ULevelInstanceInterface::StaticClass())) { return LevelInstanceClass; } } return ALevelInstance::StaticClass(); } static bool AreAllSelectedLevelInstancesRootSelections(const TArray& SelectedLevelInstances) { for(ILevelInstanceInterface* LevelInstance : SelectedLevelInstances) { if (CastChecked(LevelInstance)->GetSelectionParent() != nullptr) { return false; } } return true; } static void CreateLevelInstanceFromSelection(ULevelInstanceSubsystem* LevelInstanceSubsystem, ELevelInstanceCreationType CreationType, const TArray& ActorsToMove) { IMainFrameModule& MainFrameModule = FModuleManager::GetModuleChecked("MainFrame"); TSharedPtr NewLevelInstanceWindow = SNew(SWindow) .Title(FText::Format(LOCTEXT("NewLevelInstanceWindowTitle", "New {0}"), StaticEnum()->GetDisplayNameTextByValue((int64)CreationType))) .SupportsMinimize(false) .SupportsMaximize(false) .SizingRule(ESizingRule::Autosized); TSharedRef NewLevelInstanceDialog = SNew(SNewLevelInstanceDialog) .ParentWindow(NewLevelInstanceWindow) .PivotActors(ActorsToMove); const bool bForceExternalActors = LevelInstanceSubsystem->GetWorld()->IsPartitionedWorld(); FNewLevelInstanceParams& DialogParams = NewLevelInstanceDialog->GetCreationParams(); DialogParams.Type = CreationType; DialogParams.bAlwaysShowDialog = GetDefault()->bAlwaysShowDialog; DialogParams.PivotType = GetDefault()->PivotType; DialogParams.PivotActor = DialogParams.PivotType == ELevelInstancePivotType::Actor ? ActorsToMove[0] : nullptr; DialogParams.HideCreationType(); DialogParams.SetForceExternalActors(bForceExternalActors); NewLevelInstanceWindow->SetContent(NewLevelInstanceDialog); if (GetDefault()->bAlwaysShowDialog) { FSlateApplication::Get().AddModalWindow(NewLevelInstanceWindow.ToSharedRef(), MainFrameModule.GetParentWindow()); } if (!GetDefault()->bAlwaysShowDialog || NewLevelInstanceDialog->ClickedOk()) { FNewLevelInstanceParams CreationParams(NewLevelInstanceDialog->GetCreationParams()); ULevelInstanceEditorPerProjectUserSettings::UpdateFrom(CreationParams); FNewLevelDialogModule& NewLevelDialogModule = FModuleManager::LoadModuleChecked("NewLevelDialog"); FString TemplateMapPackage; bool bOutIsPartitionedWorld = false; const bool bShowPartitionedTemplates = false; ULevelInstanceEditorSettings* LevelInstanceEditorSettings = GetMutableDefault(); if (!LevelInstanceEditorSettings->TemplateMapInfos.Num() || NewLevelDialogModule.CreateAndShowTemplateDialog(MainFrameModule.GetParentWindow(), LOCTEXT("LevelInstanceTemplateDialog", "Choose Level Instance Template..."), GetMutableDefault()->TemplateMapInfos, TemplateMapPackage, bShowPartitionedTemplates, bOutIsPartitionedWorld)) { UPackage* TemplatePackage = !TemplateMapPackage.IsEmpty() ? LoadPackage(nullptr, *TemplateMapPackage, LOAD_None) : nullptr; CreationParams.TemplateWorld = TemplatePackage ? UWorld::FindWorldInPackage(TemplatePackage) : nullptr; CreationParams.LevelInstanceClass = GetDefaultLevelInstanceClass(CreationType); CreationParams.bEnableStreaming = LevelInstanceEditorSettings->bEnableStreaming; if (!LevelInstanceSubsystem->CreateLevelInstanceFrom(ActorsToMove, CreationParams)) { FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("CreateFromSelectionFailMsg", "Failed to create from selection. Check log for details."), LOCTEXT("CreateFromSelectionFailTitle", "Create from selection failed")); } } } } static void CreateCreateMenu(UToolMenu* ToolMenu, const TArray& ActorsToMove) { if (ULevelInstanceSubsystem* LevelInstanceSubsystem = GEditor->GetEditorWorldContext().World()->GetSubsystem()) { if (LevelInstanceSubsystem->CanCreateLevelInstanceFrom(ActorsToMove)) { FToolMenuSection& Section = ToolMenu->AddSection("ActorSelectionSectionName", LOCTEXT("ActorSelectionSectionLabel", "Actor Selection")); Section.AddMenuEntry( TEXT("CreateLevelInstance"), FText::Format(LOCTEXT("CreateFromSelectionLabel", "Create {0}..."), StaticEnum()->GetDisplayNameTextByValue((int64)ELevelInstanceCreationType::LevelInstance)), TAttribute(), FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.LevelInstance"), FExecuteAction::CreateLambda([LevelInstanceSubsystem, CopyActorsToMove = ActorsToMove] { CreateLevelInstanceFromSelection(LevelInstanceSubsystem, ELevelInstanceCreationType::LevelInstance, CopyActorsToMove); })); Section.AddMenuEntry( TEXT("CreatePackedLevelBlueprint"), FText::Format(LOCTEXT("CreateFromSelectionLabel", "Create {0}..."), StaticEnum()->GetDisplayNameTextByValue((int64)ELevelInstanceCreationType::PackedLevelActor)), TAttribute(), FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.PackedLevelActor"), FExecuteAction::CreateLambda([LevelInstanceSubsystem, CopyActorsToMove = ActorsToMove] { CreateLevelInstanceFromSelection(LevelInstanceSubsystem, ELevelInstanceCreationType::PackedLevelActor, CopyActorsToMove); })); } } } static void CreateBreakSubMenu(UToolMenu* Menu, const TArray& BreakableLevelInstances) { static int32 BreakLevels = 1; ULevelInstanceEditorPerProjectUserSettings* Settings = GetMutableDefault(); if (ULevelInstanceSubsystem* LevelInstanceSubsystem = GEditor->GetEditorWorldContext().World()->GetSubsystem()) { FToolMenuSection& Section = Menu->AddSection("Options", LOCTEXT("LevelInstanceBreakOptionsSection", "Options")); FToolMenuEntry OrganizeInFoldersEntry = FToolMenuEntry::InitMenuEntry( "OrganizeInFolders", LOCTEXT("OrganizeActorsInFolders", "Keep Folders"), LOCTEXT( "OrganizeActorsInFoldersTooltip", "When checked, actors remain in the same folder as the level instance " "and use the same folder structure." "\nWhen unchecked, actors are placed at the root of the current level's hierarchy." ), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([Settings]() { Settings->bKeepFoldersDuringBreak = !Settings->bKeepFoldersDuringBreak; }), FCanExecuteAction(), FGetActionCheckState::CreateLambda([Settings] { return Settings->bKeepFoldersDuringBreak ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; }) ), EUserInterfaceActionType::ToggleButton ); OrganizeInFoldersEntry.bShouldCloseWindowAfterMenuSelection = false; Section.AddEntry(OrganizeInFoldersEntry); TSharedRef MenuWidget = SNew(SBox) .Padding(FMargin(5, 2, 5, 0)) [ SNew(SNumericEntryBox) .MinValue(1) .Value_Lambda([]() { return BreakLevels; }) .OnValueChanged_Lambda([](int32 InValue) { BreakLevels = InValue; }) .ToolTipText(LOCTEXT("BreakLevelsTooltip", "Determines the depth of nested instances to break apart. Use 1 to break only the top level instance.")) .Label() [ SNumericEntryBox::BuildLabel(LOCTEXT("BreakDepthLabel", "Depth"), FLinearColor::White, FLinearColor::Transparent) ] ]; Section.AddEntry(FToolMenuEntry::InitWidget("SetBreakLevels", MenuWidget, FText::GetEmpty(), false)); Section.AddSeparator(NAME_None); FToolMenuEntry ExecuteEntry = FToolMenuEntry::InitMenuEntry( "ExecuteBreak", LOCTEXT("BreakLevelInstances_BreakLevelInstanceButton", "Break Level Instance(s)"), LOCTEXT("BreakLevelInstances_BreakLevelInstanceButtonTooltip", "Break apart the selected level instances using the settings above."), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([CopyBreakableLevelInstances = BreakableLevelInstances, LevelInstanceSubsystem, Settings]() { const FText LevelInstanceBreakWarning = FText::Format( LOCTEXT( "BreakingLevelInstance", "You are about to break {0} level instance(s). This action cannot be undone. Are you sure ?" ), FText::AsNumber(CopyBreakableLevelInstances.Num()) ); if (FMessageDialog::Open(EAppMsgType::YesNo, LevelInstanceBreakWarning, LOCTEXT("BreakingLevelInstanceTitle", "Break Level Instances")) == EAppReturnType::Yes) { ELevelInstanceBreakFlags Flags = ELevelInstanceBreakFlags::None; if (Settings->bKeepFoldersDuringBreak) { Flags |= ELevelInstanceBreakFlags::KeepFolders; } for (ILevelInstanceInterface* LevelInstance : CopyBreakableLevelInstances) { LevelInstanceSubsystem->BreakLevelInstance(LevelInstance, BreakLevels, nullptr, Flags); } } }) ), EUserInterfaceActionType::Button ); Section.AddEntry(ExecuteEntry); } } static void CreateBreakMenu(UToolMenu* Menu, const TArray& SelectedLevelInstances) { if(ULevelInstanceSubsystem* LevelInstanceSubsystem = GEditor->GetEditorWorldContext().World()->GetSubsystem()) { TArray BreakableLevelInstances; for (ILevelInstanceInterface* SelectedLevelInstance : SelectedLevelInstances) { if (LevelInstanceSubsystem->CanBreakLevelInstance(SelectedLevelInstance)) { BreakableLevelInstances.Add(SelectedLevelInstance); } } if (BreakableLevelInstances.Num()) { FToolMenuSection& Section = CreateLevelSection(Menu); Section.AddSubMenu( "BreakLevelInstances", LOCTEXT("BreakLevelInstances", "Break"), LOCTEXT("BreakLevelInstancesTooltip", "Break apart the selected level instances into their individual actors."), FNewToolMenuDelegate::CreateLambda([CopyOfBreakableLevelInstances = BreakableLevelInstances](UToolMenu* Menu) { CreateBreakSubMenu(Menu, CopyOfBreakableLevelInstances); })); } } } static void CreatePackedBlueprintMenu(UToolMenu* Menu, AActor* ContextActor) { if (ULevelInstanceSubsystem* LevelInstanceSubsystem = ContextActor->GetWorld()->GetSubsystem()) { ILevelInstanceInterface* ContextLevelInstance = nullptr; // Find the top level LevelInstance LevelInstanceSubsystem->ForEachLevelInstanceAncestorsAndSelf(ContextActor, [LevelInstanceSubsystem, ContextActor, &ContextLevelInstance](ILevelInstanceInterface* Ancestor) { if (CastChecked(Ancestor)->GetLevel() == ContextActor->GetWorld()->GetCurrentLevel()) { ContextLevelInstance = Ancestor; return false; } return true; }); if (ContextLevelInstance && !ContextLevelInstance->IsEditing()) { FToolMenuSection& Section = CreateLevelSection(Menu); ; if (APackedLevelActor* PackedLevelActor = Cast(ContextLevelInstance)) { if (TSoftObjectPtr BlueprintAsset = Cast(PackedLevelActor->GetClass()->ClassGeneratedBy.Get())) { FToolUIAction UIAction; UIAction.ExecuteAction.BindLambda([ContextLevelInstance, BlueprintAsset](const FToolMenuContext& MenuContext) { FPackedLevelActorUtils::CreateOrUpdateBlueprint(ContextLevelInstance->GetWorldAsset(), BlueprintAsset); }); UIAction.CanExecuteAction.BindLambda([](const FToolMenuContext& MenuContext) { return FPackedLevelActorUtils::CanPack() && GEditor->GetSelectedActorCount() > 0; }); Section.AddMenuEntry( "UpdatePackedBlueprint", LOCTEXT("UpdatePackedBlueprint", "Update Packed Blueprint"), TAttribute(), TAttribute(), UIAction); } } } } } static void CreateResetPropertyOverridesMenu(UToolMenu* Menu, const TArray& SelectedActors, const TArray& SelectedLevelInstances) { if (!ULevelInstanceSettings::Get()->IsPropertyOverrideEnabled()) { return; } if (ULevelInstanceSubsystem* LevelInstanceSubsystem = UWorld::GetSubsystem(GEditor->GetEditorWorldContext().World())) { if (SelectedLevelInstances.Num() > 0 && SelectedActors.Num() == SelectedLevelInstances.Num()) { bool bCanResetAllLevelInstances = true; for (ILevelInstanceInterface* SelectedLevelInstance : SelectedLevelInstances) { if (!LevelInstanceSubsystem->CanResetPropertyOverrides(SelectedLevelInstance)) { bCanResetAllLevelInstances = false; break; } } if (bCanResetAllLevelInstances) { FToolMenuSection& Section = CreateLevelSection(Menu); FToolUIAction UIAction; UIAction.ExecuteAction.BindLambda([LevelInstanceSubsystem, CopySelectedLevelInstance = SelectedLevelInstances](const FToolMenuContext& MenuContext) { for (ILevelInstanceInterface* LevelInstanceInterface : CopySelectedLevelInstance) { LevelInstanceSubsystem->ResetPropertyOverrides(LevelInstanceInterface); } }); Section.AddMenuEntry( "ResetLevelInstancePropertyOverrides", LOCTEXT("ResetLevelInstancePropertyOverrides", "Reset Overrides"), TAttribute(), TAttribute(), UIAction); return; } } if (SelectedActors.Num() > 0) { bool bCanResetAllActors = true; for (AActor* SelectedActor : SelectedActors) { if (!LevelInstanceSubsystem->CanResetPropertyOverridesForActor(SelectedActor)) { bCanResetAllActors = false; break; } } if (bCanResetAllActors) { FToolMenuSection& Section = CreateLevelSection(Menu); FToolUIAction UIAction; UIAction.ExecuteAction.BindLambda([LevelInstanceSubsystem, CopySelectedActors = SelectedActors](const FToolMenuContext& MenuContext) { FScopedTransaction ResetPropertyOverridesTransaction(LOCTEXT("ResetPropertyOverrides", "Reset Property Override(s)")); for (AActor* SelectedActor : CopySelectedActors) { LevelInstanceSubsystem->ResetPropertyOverridesForActor(SelectedActor); } }); Section.AddMenuEntry( "ResetLevelInstancePropertyOverrides", LOCTEXT("ResetLevelInstancePropertyOverrides", "Reset Overrides"), LOCTEXT("ResetLevelInstancePropertyOverridesTooltip", "Discard all overrides on the selected level instances, restoring them to match the level assets."), TAttribute(), UIAction); } } } } class FLevelInstanceClassFilter : public IClassViewerFilter { public: virtual bool IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const UClass* InClass, TSharedRef InFilterFuncs) override { return InClass && InClass->ImplementsInterface(ULevelInstanceInterface::StaticClass()) && InClass->IsNative() && !InClass->HasAnyClassFlags(CLASS_Deprecated); } virtual bool IsUnloadedClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const TSharedRef InUnloadedClassData, TSharedRef InFilterFuncs) override { return false; } }; static void CreateBlueprintFromWorld(UWorld* WorldAsset) { TSoftObjectPtr LevelInstancePtr(WorldAsset); int32 LastSlashIndex = 0; FString LongPackageName = LevelInstancePtr.GetLongPackageName(); LongPackageName.FindLastChar('/', LastSlashIndex); FString PackagePath = LongPackageName.Mid(0, LastSlashIndex == INDEX_NONE ? MAX_int32 : LastSlashIndex); FString AssetName = "BP_" + LevelInstancePtr.GetAssetName(); IAssetTools& AssetTools = FAssetToolsModule::GetModule().Get(); UBlueprintFactory* BlueprintFactory = NewObject(); BlueprintFactory->AddToRoot(); BlueprintFactory->OnConfigurePropertiesDelegate.BindLambda([](FClassViewerInitializationOptions* Options) { Options->bShowDefaultClasses = false; Options->bIsBlueprintBaseOnly = false; Options->InitiallySelectedClass = ALevelInstance::StaticClass(); Options->bIsActorsOnly = true; Options->ClassFilters.Add(MakeShareable(new FLevelInstanceClassFilter)); }); ON_SCOPE_EXIT { BlueprintFactory->OnConfigurePropertiesDelegate.Unbind(); BlueprintFactory->RemoveFromRoot(); }; if (UBlueprint* NewBlueprint = Cast(AssetTools.CreateAssetWithDialog(AssetName, PackagePath, UBlueprint::StaticClass(), BlueprintFactory, FName("Create LevelInstance Blueprint")))) { AActor* CDO = NewBlueprint->GeneratedClass->GetDefaultObject(); ILevelInstanceInterface* LevelInstanceCDO = CastChecked(CDO); LevelInstanceCDO->SetWorldAsset(LevelInstancePtr); FBlueprintEditorUtils::MarkBlueprintAsModified(NewBlueprint); if (NewBlueprint->GeneratedClass->IsChildOf()) { FPackedLevelActorUtils::UpdateBlueprint(NewBlueprint); } FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); TArray Assets; Assets.Add(NewBlueprint); ContentBrowserModule.Get().SyncBrowserToAssets(Assets); } } static void CreateBlueprintFromMenu(UToolMenu* Menu, FAssetData WorldAsset) { FToolMenuSection& Section = CreateLevelSection(Menu); FToolUIAction UIAction; UIAction.ExecuteAction.BindLambda([WorldAsset](const FToolMenuContext& MenuContext) { if (UWorld* World = Cast(WorldAsset.GetAsset())) { CreateBlueprintFromWorld(World); } }); Section.AddMenuEntry( "CreateLevelInstanceBlueprint", LOCTEXT("CreateLevelInstanceBlueprint", "New Blueprint..."), TAttribute(), FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.LevelInstance"), UIAction); } static void AddPartitionedStreamingSupportFromWorld(UWorld* WorldAsset) { if (WorldAsset->GetStreamingLevels().Num()) { FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("AddPartitionedLevelInstanceStreamingSupportError_SubLevels", "Cannot convert this world has it contains sublevels.")); return; } if (WorldAsset->WorldType != EWorldType::Inactive) { FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("AddPartitionedLevelInstanceStreamingSupportError_Loaded", "Cannot convert this world has it's already loaded in the editor.")); return; } bool bSuccess = false; UWorld* World = GEditor->GetEditorWorldContext().World(); ULevelInstanceSubsystem::ResetLoadersForWorldAsset(WorldAsset->GetPackage()->GetName()); FWorldPartitionConverter::FParameters Parameters; Parameters.bConvertSubLevels = false; Parameters.bEnableStreaming = false; Parameters.bUseActorFolders = true; if (FWorldPartitionConverter::Convert(WorldAsset, Parameters)) { TArray PackagesToSave = WorldAsset->PersistentLevel->GetLoadedExternalObjectPackages(); TSet PackagesToSaveSet(PackagesToSave); PackagesToSaveSet.Add(WorldAsset->GetPackage()); const bool bPromptUserToSave = false; const bool bSaveMapPackages = true; const bool bSaveContentPackages = true; const bool bFastSave = false; const bool bNotifyNoPackagesSaved = false; const bool bCanBeDeclined = true; if (FEditorFileUtils::SaveDirtyPackages(bPromptUserToSave, bSaveMapPackages, bSaveContentPackages, bFastSave, bNotifyNoPackagesSaved, bCanBeDeclined, nullptr, [&PackagesToSaveSet](UPackage* PackageToSave) { return !PackagesToSaveSet.Contains(PackageToSave); })) { bSuccess = true; for (UPackage* PackageToSave : PackagesToSave) { if (PackageToSave->IsDirty()) { UE_LOG(LogLevelInstanceEditor, Error, TEXT("Package '%s' failed to save"), *PackageToSave->GetName()); bSuccess = false; break; } } } } if (!bSuccess) { FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("AddPartitionedLevelInstanceStreamingSupportError", "An error occured when adding partitioned level instance streaming support, check logs for details..")); } } static void UpdatePackedBlueprintsFromMenu(UToolMenu* Menu, FAssetData WorldAsset) { FToolMenuSection& Section = CreateLevelSection(Menu); FToolUIAction UIAction; UIAction.CanExecuteAction.BindLambda([](const FToolMenuContext& MenuContext) { return FPackedLevelActorUtils::CanPack(); }); UIAction.ExecuteAction.BindLambda([WorldAsset](const FToolMenuContext& MenuContext) { FScopedSlowTask SlowTask(0.0f, LOCTEXT("UpdatePackedBlueprintsProgress", "Updating Packed Blueprints...")); TSet> BlueprintAssets; FPackedLevelActorUtils::GetPackedBlueprintsForWorldAsset(TSoftObjectPtr(WorldAsset.GetSoftObjectPath()), BlueprintAssets, false); TSharedPtr Builder = FPackedLevelActorBuilder::CreateDefaultBuilder(); for (TSoftObjectPtr BlueprintAsset : BlueprintAssets) { if (UBlueprint* Blueprint = BlueprintAsset.Get()) { Builder->UpdateBlueprint(Blueprint, false); } } }); Section.AddMenuEntry( "UpdatePackedBlueprintsFromMenu", LOCTEXT("UpdatePackedBlueprintsFromMenu", "Update Packed Blueprints"), TAttribute(), FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.PackedLevelActor"), UIAction ); } static void AddPartitionedStreamingSupportFromMenu(UToolMenu* Menu, FAssetData WorldAsset) { FName WorldAssetName = WorldAsset.PackageName; if (!ULevel::GetIsLevelPartitionedFromPackage(WorldAssetName)) { FToolMenuSection& Section = CreateLevelSection(Menu); FToolUIAction UIAction; UIAction.ExecuteAction.BindLambda([WorldAsset](const FToolMenuContext& MenuContext) { if (UWorld* World = Cast(WorldAsset.GetAsset())) { AddPartitionedStreamingSupportFromWorld(World); } }); Section.AddMenuEntry( "AddPartitionedStreamingSupportFromMenu", LOCTEXT("AddPartitionedStreamingSupportFromMenu", "Add Partitioned Streaming Support"), TAttribute(), TAttribute(), UIAction ); } } }; class FLevelInstanceActorDetailsSCSEditorUICustomization : public ISCSEditorUICustomization { public: static TSharedPtr GetInstance() { if (!Instance) { Instance = MakeShareable(new FLevelInstanceActorDetailsSCSEditorUICustomization()); } return Instance; } virtual bool HideComponentsTree(TArrayView Context) const override { return false; } virtual bool HideComponentsFilterBox(TArrayView Context) const override { return false; } virtual bool HideAddComponentButton(TArrayView Context) const override { return ShouldHide(Context); } virtual bool HideBlueprintButtons(TArrayView Context) const override { return ShouldHide(Context); } private: bool ShouldHide(TArrayView Context) const { for (const UObject* ContextObject : Context) { if (const AActor* ActorContext = Cast(ContextObject)) { if (ActorContext->IsInLevelInstance() && !ActorContext->IsInEditLevelInstance()) { return true; } } } return false; } static TSharedPtr Instance; bool bShouldHide = false; }; TSharedPtr FLevelInstanceActorDetailsSCSEditorUICustomization::Instance; void FLevelInstanceEditorModule::OnLevelEditorCreated(TSharedPtr InLevelEditor) { RegisterToFirstLevelEditor(); } void FLevelInstanceEditorModule::RegisterToFirstLevelEditor() { FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked("LevelEditor"); TSharedPtr FirstLevelEditor = LevelEditorModule.GetFirstLevelEditor(); if (FirstLevelEditor.IsValid()) { FirstLevelEditor->AddActorDetailsSCSEditorUICustomization(FLevelInstanceActorDetailsSCSEditorUICustomization::GetInstance()); FEditorModeTools& LevelEditorModeManager = FirstLevelEditor->GetEditorModeManager(); LevelEditorModeManager.OnEditorModeIDChanged().AddRaw(this, &FLevelInstanceEditorModule::OnEditorModeIDChanged); // Create a Behavior source for the default EdModeTools (when we aren't in the LevelInstanceEditorMode) DefaultBehaviorSource = ULevelInstanceEditorMode::CreateDefaultModeBehaviorSource(LevelEditorModeManager.GetInteractiveToolsContext()); LevelEditorModeManager.GetInteractiveToolsContext()->InputRouter->RegisterSource(DefaultBehaviorSource.GetInterface()); RegisterLevelInstanceColumn(); // Make sure to unregister because changing the layout will callback on this again. // // This works because we aren't actually hooking ourselves to the ILevelEditor but on managers that are shared by the different instances // of ILevelEditor. Ideally we could listen to an event when a ILevelEditor gets destroyed to unregister ourselves and continue to listen // to this event to re-register ourselves LevelEditorModule.OnLevelEditorCreated().RemoveAll(this); } } FName FLevelInstanceEditorModule::GetBrowseToLevelInstanceAsset(const UObject* Object) const { // Level instances browse to both the level instance asset and the current level asset by default, while we only want to browse to the former const ILevelInstanceInterface* LevelInstanceInterface = CastChecked(Object); return FName(LevelInstanceInterface->GetWorldAssetPackage()); } void FLevelInstanceEditorModule::StartupModule() { FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked("LevelEditor"); if (TSharedPtr FirstLevelEditor = LevelEditorModule.GetFirstLevelEditor()) { RegisterToFirstLevelEditor(); } else { LevelEditorModule.OnLevelEditorCreated().AddRaw(this, &FLevelInstanceEditorModule::OnLevelEditorCreated); } ExtendContextMenu(); FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked("PropertyEditor"); PropertyModule.RegisterCustomClassLayout("LevelInstance", FOnGetDetailCustomizationInstance::CreateStatic(&FLevelInstanceActorDetails::MakeInstance)); PropertyModule.RegisterCustomClassLayout("LevelInstancePivot", FOnGetDetailCustomizationInstance::CreateStatic(&FLevelInstancePivotDetails::MakeInstance)); PropertyModule.RegisterCustomPropertyTypeLayout("WorldPartitionActorFilter", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FLevelInstanceFilterPropertyTypeCustomization::MakeInstance, false), MakeShared(false)); PropertyModule.RegisterCustomPropertyTypeLayout("WorldPartitionActorFilter", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FLevelInstanceFilterPropertyTypeCustomization::MakeInstance, true), MakeShared(true)); PropertyModule.NotifyCustomizationModuleChanged(); // GEditor needs to be set before this module is loaded check(GEditor); GEditor->OnLevelActorDeleted().AddRaw(this, &FLevelInstanceEditorModule::OnLevelActorDeleted); EditorLevelUtils::CanMoveActorToLevelDelegate.AddRaw(this, &FLevelInstanceEditorModule::CanMoveActorToLevel); // Register actor descriptor loading filter class FLevelInstanceActorDescFilter : public IWorldPartitionActorLoaderInterface::FActorDescFilter { public: bool PassFilter(class UWorld* InWorld, const FWorldPartitionHandle& InHandle) override { if (UWorld* OwningWorld = InWorld->PersistentLevel->GetWorld()) { if (ULevelInstanceSubsystem* LevelInstanceSubsystem = OwningWorld->GetSubsystem()) { return LevelInstanceSubsystem->PassLevelInstanceFilter(InWorld, InHandle); } } return true; } // Leave [0, 19] for Game code virtual uint32 GetFilterPriority() const override { return 20; } virtual FText* GetFilterReason() const override { static FText UnloadedReason(LOCTEXT("LevelInstanceActorDescFilterReason", "Filtered")); return &UnloadedReason; } }; IWorldPartitionActorLoaderInterface::RegisterActorDescFilter(MakeShareable(new FLevelInstanceActorDescFilter())); FMessageLogModule& MessageLogModule = FModuleManager::LoadModuleChecked("MessageLog"); FMessageLogInitializationOptions InitOptions; InitOptions.bShowFilters = true; InitOptions.bShowPages = false; InitOptions.bAllowClear = true; MessageLogModule.RegisterLogListing("PackedLevelActor", LOCTEXT("PackedLevelActorLog", "Packed Level Actor Log"), InitOptions); FLevelInstanceEditorModeCommands::Register(); ULevelInstanceSubsystem::RegisterPrimitiveColorHandler(); if (UBrowseToAssetOverrideSubsystem* BrowseToAssetOverrideSubsystem = UBrowseToAssetOverrideSubsystem::Get()) { BrowseToAssetOverrideSubsystem->RegisterBrowseToAssetOverrideForInterface( FBrowseToAssetOverrideDelegate::CreateRaw(this, &FLevelInstanceEditorModule::GetBrowseToLevelInstanceAsset)); } } void FLevelInstanceEditorModule::ShutdownModule() { if (UBrowseToAssetOverrideSubsystem* BrowseToAssetOverrideSubsystem = UBrowseToAssetOverrideSubsystem::Get()) { BrowseToAssetOverrideSubsystem->UnregisterBrowseToAssetOverrideForInterface(); } ULevelInstanceSubsystem::UnregisterPrimitiveColorHandler(); if (FModuleManager::Get().IsModuleLoaded("LevelEditor")) { FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked("LevelEditor"); LevelEditorModule.OnLevelEditorCreated().RemoveAll(this); if (TSharedPtr FirstLevelEditor = LevelEditorModule.GetFirstLevelEditor()) { FirstLevelEditor->RemoveActorDetailsSCSEditorUICustomization(FLevelInstanceActorDetailsSCSEditorUICustomization::GetInstance()); if (!IsEngineExitRequested()) { FirstLevelEditor->GetEditorModeManager().OnEditorModeIDChanged().RemoveAll(this); FirstLevelEditor->GetEditorModeManager().GetInteractiveToolsContext()->InputRouter->DeregisterSource(DefaultBehaviorSource.GetInterface()); } } DefaultBehaviorSource = nullptr; UnregisterLevelInstanceColumn(); } if (GEditor) { GEditor->OnLevelActorDeleted().RemoveAll(this); } EditorLevelUtils::CanMoveActorToLevelDelegate.RemoveAll(this); } TSharedRef FLevelInstanceEditorModule::CreateLevelInstanceColumn(ISceneOutliner& SceneOutliner) const { return MakeShareable(new FLevelInstanceSceneOutlinerColumn(SceneOutliner)); } void FLevelInstanceEditorModule::RegisterLevelInstanceColumn() { if (GetDefault()->IsPropertyOverrideEnabled()) { FSceneOutlinerModule& SceneOutlinerModule = FModuleManager::LoadModuleChecked("SceneOutliner"); FSceneOutlinerColumnInfo ColumnInfo(ESceneOutlinerColumnVisibility::Invisible, 8, FCreateSceneOutlinerColumn::CreateRaw(this, &FLevelInstanceEditorModule::CreateLevelInstanceColumn), true, TOptional(), LOCTEXT("LevelInstanceColumnName", "Level Instance Overrides")); SceneOutlinerModule.RegisterDefaultColumnType(ColumnInfo); } } void FLevelInstanceEditorModule::UnregisterLevelInstanceColumn() { if (FSceneOutlinerModule* SceneOutlinerModulePtr = FModuleManager::GetModulePtr("SceneOutliner")) { SceneOutlinerModulePtr->UnRegisterColumnType(); } } void FLevelInstanceEditorModule::OnEditorModeIDChanged(const FEditorModeID& InModeID, bool bIsEnteringMode) { if (InModeID == ULevelInstanceEditorMode::EM_LevelInstanceEditorModeId && !bIsEnteringMode) { ExitEditorModeEvent.Broadcast(); } } void FLevelInstanceEditorModule::BroadcastTryExitEditorMode() { TryExitEditorModeEvent.Broadcast(); } void FLevelInstanceEditorModule::UpdateEditorMode(bool bActivated) { FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked("LevelEditor"); if (TSharedPtr FirstLevelEditor = LevelEditorModule.GetFirstLevelEditor()) { if (bActivated && !FirstLevelEditor->GetEditorModeManager().IsModeActive(ULevelInstanceEditorMode::EM_LevelInstanceEditorModeId)) { FirstLevelEditor->GetEditorModeManager().ActivateMode(ULevelInstanceEditorMode::EM_LevelInstanceEditorModeId); } else if (!bActivated && FirstLevelEditor->GetEditorModeManager().IsModeActive(ULevelInstanceEditorMode::EM_LevelInstanceEditorModeId)) { FirstLevelEditor->GetEditorModeManager().DeactivateMode(ULevelInstanceEditorMode::EM_LevelInstanceEditorModeId); } } } void FLevelInstanceEditorModule::OnLevelActorDeleted(AActor* Actor) { if (ULevelInstanceSubsystem* LevelInstanceSubsystem = Actor->GetWorld()->GetSubsystem()) { LevelInstanceSubsystem->OnActorDeleted(Actor); } } void FLevelInstanceEditorModule::CanMoveActorToLevel(const AActor* ActorToMove, const ULevel* DestLevel, bool& bOutCanMove) { if (UWorld* World = ActorToMove->GetWorld()) { if (ULevelInstanceSubsystem* LevelInstanceSubsystem = World->GetSubsystem()) { if (!LevelInstanceSubsystem->CanMoveActorToLevel(ActorToMove)) { bOutCanMove = false; return; } } } } void FLevelInstanceEditorModule::ExtendContextMenu() { if (UToolMenu* BuildMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.MainMenu.Build")) { FToolMenuSection& Section = BuildMenu->AddSection("LevelEditorLevelInstance", LOCTEXT("PackedLevelActorsHeading", "Packed Level Actor")); FUIAction PackAction( FExecuteAction::CreateLambda([]() { FPackedLevelActorUtils::PackAllLoadedActors(); }), FCanExecuteAction::CreateLambda([]() { return FPackedLevelActorUtils::CanPack(); }), FIsActionChecked(), FIsActionButtonVisible()); FToolMenuEntry& Entry = Section.AddMenuEntry(NAME_None, LOCTEXT("PackLevelActorsTitle", "Pack Level Actors"), LOCTEXT("PackLevelActorsTooltip", "Update packed level actor blueprints"), FSlateIcon(), PackAction, EUserInterfaceActionType::Button); } auto AddDynamicSection = [](UToolMenu* ToolMenu) { if (GEditor->GetPIEWorldContext()) { return; } if (GetDefault()->IsLevelInstanceDisabled()) { return; } // Build Selection for Menus TArray SelectedActors; TArray SelectedLevelInstances; SelectedActors.Reserve(GEditor->GetSelectedActorCount()); SelectedLevelInstances.Reserve(GEditor->GetSelectedActorCount()); for (FSelectionIterator It(GEditor->GetSelectedActorIterator()); It; ++It) { if (AActor* Actor = Cast(*It); IsValid(Actor)) { SelectedActors.Add(Actor); if (ILevelInstanceInterface* LevelInstance = Cast(Actor)) { SelectedLevelInstances.Add(LevelInstance); } } } // Some actions aren't allowed on non root selection Level Instances (Readonly Level Instances) const bool bAreAllSelectedLevelInstancesRootSelections = FLevelInstanceMenuUtils::AreAllSelectedLevelInstancesRootSelections(SelectedLevelInstances); if (ULevelEditorContextMenuContext* LevelEditorMenuContext = ToolMenu->Context.FindContext()) { // Use the actor under the cursor if available (e.g. right-click menu). // Otherwise use the first selected actor if there's one (e.g. Actor pulldown menu or outliner). AActor* ContextActor = LevelEditorMenuContext->HitProxyActor.Get(); if (!ContextActor && SelectedActors.Num() > 0) { ContextActor = SelectedActors[0]; } if (ContextActor) { // Allow Edit/Commmit on non root selected Level Instance FLevelInstanceMenuUtils::CreateEditMenu(ToolMenu, ContextActor); FLevelInstanceMenuUtils::CreateEditPropertyOverridesMenu(ToolMenu, ContextActor); FLevelInstanceMenuUtils::CreateSaveCancelMenu(ToolMenu, ContextActor); if (bAreAllSelectedLevelInstancesRootSelections) { FLevelInstanceMenuUtils::CreatePackedBlueprintMenu(ToolMenu, ContextActor); } } } if (bAreAllSelectedLevelInstancesRootSelections) { FLevelInstanceMenuUtils::CreateBreakMenu(ToolMenu, SelectedLevelInstances); FLevelInstanceMenuUtils::CreateCreateMenu(ToolMenu, SelectedActors); FLevelInstanceMenuUtils::CreateResetPropertyOverridesMenu(ToolMenu, SelectedActors, SelectedLevelInstances); } }; if (UToolMenu* ToolMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.ActorContextMenu.LevelSubMenu")) { ToolMenu->AddDynamicSection("LevelInstanceEditorModuleDynamicSection", FNewToolMenuDelegate::CreateLambda(AddDynamicSection)); } if (UToolMenu* ToolMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.LevelEditorSceneOutliner.ContextMenu.LevelSubMenu")) { ToolMenu->AddDynamicSection("LevelInstanceEditorModuleDynamicSection", FNewToolMenuDelegate::CreateLambda(AddDynamicSection)); } if (UToolMenu* WorldAssetMenu = UToolMenus::Get()->ExtendMenu("ContentBrowser.AssetContextMenu.World")) { FToolMenuSection& Section = WorldAssetMenu->AddDynamicSection("ActorLevelInstance", FNewToolMenuDelegate::CreateLambda([this](UToolMenu* ToolMenu) { if (GEditor->GetPIEWorldContext()) { return; } if (GetDefault()->IsLevelInstanceDisabled()) { return; } if (ToolMenu) { if (UContentBrowserAssetContextMenuContext* AssetMenuContext = ToolMenu->Context.FindContext()) { if (AssetMenuContext->SelectedAssets.Num() != 1) { return; } const FAssetData& WorldAsset = AssetMenuContext->SelectedAssets[0]; if (AssetMenuContext->SelectedAssets[0].IsInstanceOf()) { FLevelInstanceMenuUtils::CreateBlueprintFromMenu(ToolMenu, WorldAsset); FLevelInstanceMenuUtils::UpdatePackedBlueprintsFromMenu(ToolMenu, WorldAsset); FLevelInstanceMenuUtils::AddPartitionedStreamingSupportFromMenu(ToolMenu, WorldAsset); } } } }), FToolMenuInsert(NAME_None, EToolMenuInsertType::Default)); } } bool FLevelInstanceEditorModule::IsEditInPlaceStreamingEnabled() const { return GetDefault()->bIsEditInPlaceStreamingEnabled; } bool FLevelInstanceEditorModule::IsSubSelectionEnabled() const { return GetDefault()->bIsSubSelectionEnabled; } void FLevelInstanceEditorModule::AddReferencedObjects(FReferenceCollector& Collector) { DefaultBehaviorSource.AddReferencedObjects(Collector); } #undef LOCTEXT_NAMESPACE