// Copyright Epic Games, Inc. All Rights Reserved. #include "CoreMinimal.h" #include "Misc/MessageDialog.h" #include "Modules/ModuleManager.h" #include "UObject/ObjectMacros.h" #include "UObject/Object.h" #include "UObject/GarbageCollection.h" #include "Templates/SubclassOf.h" #include "Engine/EngineTypes.h" #include "Engine/Level.h" #include "Components/ActorComponent.h" #include "GameFramework/Actor.h" #include "GameFramework/Pawn.h" #include "Engine/World.h" #include "AI/NavigationSystemBase.h" #include "Components/LightComponent.h" #include "Model.h" #include "Exporters/Exporter.h" #include "Components/SkeletalMeshComponent.h" #include "Engine/Brush.h" #include "Editor/EditorEngine.h" #include "Editor/UnrealEdEngine.h" #include "Factories/LevelFactory.h" #include "Editor/GroupActor.h" #include "Animation/SkeletalMeshActor.h" #include "Particles/Emitter.h" #include "Misc/FeedbackContext.h" #include "UObject/UObjectIterator.h" #include "UObject/PropertyPortFlags.h" #include "GameFramework/WorldSettings.h" #include "Engine/LevelScriptActor.h" #include "Engine/Light.h" #include "Engine/StaticMeshActor.h" #include "Components/ChildActorComponent.h" #include "Engine/Polys.h" #include "Kismet2/ComponentEditorUtils.h" #include "Engine/Selection.h" #include "EngineUtils.h" #include "EditorModeManager.h" #include "EditorModes.h" #include "Dialogs/Dialogs.h" #include "ScopedTransaction.h" #include "Engine/LevelStreaming.h" #include "LevelUtils.h" #include "BusyCursor.h" #include "BSPOps.h" #include "EditorLevelUtils.h" #include "Kismet2/BlueprintEditorUtils.h" #include "LevelEditorViewport.h" #include "Layers/LayersSubsystem.h" #include "ActorEditorUtils.h" #include "Particles/ParticleSystemComponent.h" #include "UnrealExporter.h" #include "LevelEditor.h" #include "Engine/LODActor.h" #include "Settings/LevelEditorMiscSettings.h" #include "Settings/EditorProjectSettings.h" #include "ActorGroupingUtils.h" #include "HAL/PlatformApplicationMisc.h" #include "IAssetTools.h" #include "AssetToolsModule.h" #include "AssetSelection.h" #include "Framework/Application/SlateApplication.h" #include "EdMode.h" #include "Subsystems/BrushEditingSubsystem.h" #include "Misc/ScopedSlowTask.h" #include "Subsystems/AssetEditorSubsystem.h" #define LOCTEXT_NAMESPACE "UnrealEd.EditorActor" DEFINE_LOG_CATEGORY_STATIC(LogEditorActor, Log, All); static int32 RecomputePoly(ABrush* InOwner, FPoly* Poly) { // force recalculation of normal, and texture U and V coordinates in FPoly::Finalize() Poly->Normal = FVector::ZeroVector; return Poly->Finalize(InOwner, 0); } /*----------------------------------------------------------------------------- Actor adding/deleting functions. -----------------------------------------------------------------------------*/ void UUnrealEdEngine::edactCopySelected(UWorld* InWorld, FString* DestinationData) { if (GetSelectedComponentCount() > 0) { // Copy selected components TArray SelectedComponents; for (FSelectionIterator It(GetSelectedComponentIterator()); It; ++It) { SelectedComponents.Add(CastChecked(*It)); } FComponentEditorUtils::CopyComponents(SelectedComponents); } else { // Copy Actors // Before copying, deselect: // - Actors belonging to prefabs unless all actors in the prefab are selected. // - Builder brushes. // - World Settings. TArray ActorsToDeselect; bool bSomeSelectedActorsNotInCurrentLevel = false; for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It) { AActor* Actor = static_cast(*It); checkSlow(Actor->IsA(AActor::StaticClass())); // Deselect any selected builder brushes. ABrush* Brush = Cast(Actor); const bool bActorIsBuilderBrush = (Brush && FActorEditorUtils::IsABuilderBrush(Brush)); if (bActorIsBuilderBrush) { ActorsToDeselect.Add(Actor); } // Deselect world settings if (Actor->IsA(AWorldSettings::StaticClass())) { ActorsToDeselect.Add(Actor); } // If any selected actors are not in the current level, warn the user that some actors will not be copied. if (!bSomeSelectedActorsNotInCurrentLevel && !Actor->GetLevel()->IsCurrentLevel()) { bSomeSelectedActorsNotInCurrentLevel = true; FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "CopySelectedActorsInNonCurrentLevel", "Some selected actors are not in the current level and will not be copied.")); } } const FScopedBusyCursor BusyCursor; for (int32 ActorIndex = 0; ActorIndex < ActorsToDeselect.Num(); ++ActorIndex) { AActor* Actor = ActorsToDeselect[ActorIndex]; GetSelectedActors()->Deselect(Actor); } // Export the actors. FStringOutputDevice Ar; const FSelectedActorExportObjectInnerContext Context; UExporter::ExportToOutputDevice(&Context, InWorld, NULL, Ar, TEXT("copy"), 0, PPF_DeepCompareInstances | PPF_ExportsNotFullyQualified); FPlatformApplicationMisc::ClipboardCopy(*Ar); if (DestinationData) { *DestinationData = MoveTemp(Ar); } } } /** * Creates offsets for locations based on the editor grid size and active viewport. */ static FVector CreateLocationOffset(bool bDuplicate, bool bOffsetLocations) { const float Offset = static_cast(bOffsetLocations ? GEditor->GetGridSize() : 0); FVector LocationOffset(Offset, Offset, Offset); if (bDuplicate && GCurrentLevelEditingViewportClient) { switch (GCurrentLevelEditingViewportClient->ViewportType) { case LVT_OrthoXZ: LocationOffset = FVector(Offset, 0.f, Offset); break; case LVT_OrthoYZ: LocationOffset = FVector(0.f, Offset, Offset); break; default: LocationOffset = FVector(Offset, Offset, 0.f); break; } } return LocationOffset; } bool UUnrealEdEngine::WarnIfDestinationLevelIsHidden(UWorld* InWorld) { bool bShouldLoadHiddenLevels = true; bool bShowPasteHiddenWarning = true; TArray Levels; TArray bTheyShouldBeVisible; // prepare the warning dialog FSuppressableWarningDialog::FSetupInfo Info(LOCTEXT("Warning_PasteWarningBody", "You are trying to paste to a hidden level.\nSuppressing this will default to Do Not Paste"), LOCTEXT("Warning_PasteWarningHeader", "Pasting To Hidden Level"), "PasteHiddenWarning"); Info.ConfirmText = LOCTEXT("Warning_PasteContinue", "Unhide Level and paste"); Info.CancelText = LOCTEXT("Warning_PasteCancel", "Do not paste"); // check streaming levels first for (ULevelStreaming* StreamedLevel: InWorld->GetStreamingLevels()) { // this is the active level - check if it is visible if (StreamedLevel && StreamedLevel->GetShouldBeVisibleInEditor() == false) { ULevel* Level = StreamedLevel->GetLoadedLevel(); if (Level && Level->IsCurrentLevel()) { if (bShowPasteHiddenWarning) { bShowPasteHiddenWarning = false; // the streamed level is not visible - check what the user wants to do bShouldLoadHiddenLevels = (FSuppressableWarningDialog(Info).ShowModal() == FSuppressableWarningDialog::Confirm); } if (bShouldLoadHiddenLevels) { Levels.Add(Level); bTheyShouldBeVisible.Add(true); } } } } // now check the active level (this handles the persistent level also) if (bShouldLoadHiddenLevels) { if (FLevelUtils::IsLevelVisible(InWorld->GetCurrentLevel()) == false) { if (bShowPasteHiddenWarning) { bShowPasteHiddenWarning = false; // the streamed level is not visible - check what the user wants to do bShouldLoadHiddenLevels = (FSuppressableWarningDialog(Info).ShowModal() == FSuppressableWarningDialog::Confirm); } if (bShouldLoadHiddenLevels) { Levels.Add(InWorld->GetCurrentLevel()); bTheyShouldBeVisible.Add(true); } } } // For efficiency, set visibility of all levels at once if (Levels.Num() > 0) { EditorLevelUtils::SetLevelsVisibility(Levels, bTheyShouldBeVisible, true); } return !bShouldLoadHiddenLevels; } void UUnrealEdEngine::edactPasteSelected(UWorld* InWorld, bool bDuplicate, bool bOffsetLocations, bool bWarnIfHidden, FString* SourceData) { // check and warn if the user is trying to paste to a hidden level. This will return if he wishes to abort the process if (bWarnIfHidden && WarnIfDestinationLevelIsHidden(InWorld) == true) { return; } if (GetSelectedComponentCount() > 0) { AActor* SelectedActor = CastChecked(*GetSelectedActorIterator()); TArray PastedComponents; FComponentEditorUtils::PasteComponents(PastedComponents, SelectedActor, SelectedActor->GetRootComponent()); if (PastedComponents.Num() > 0) { // Make sure all the SCS trees have a chance to update FLevelEditorModule& LevelEditor = FModuleManager::LoadModuleChecked("LevelEditor"); LevelEditor.BroadcastComponentsEdited(); // Select the new clones USelection* ComponentSelection = GetSelectedComponents(); ComponentSelection->Modify(false); ComponentSelection->BeginBatchSelectOperation(); ComponentSelection->DeselectAll(); for (UActorComponent* PastedComp: PastedComponents) { GEditor->SelectComponent(PastedComp, true, false); } ComponentSelection->EndBatchSelectOperation(true); } } else { const FScopedBusyCursor BusyCursor; // Create a location offset. const FVector LocationOffset = CreateLocationOffset(bDuplicate, bOffsetLocations); FCachedActorLabels ActorLabels(InWorld); // Transact the current selection set. USelection* SelectedActors = GetSelectedActors(); SelectedActors->Modify(); // Get pasted text. FString PasteString; if (SourceData) { PasteString = *SourceData; } else { FPlatformApplicationMisc::ClipboardPaste(PasteString); } const TCHAR* Paste = *PasteString; // Turn off automatic BSP update while pasting to save rebuilding geometry potentially multiple times const bool bBSPAutoUpdate = GetDefault()->bBSPAutoUpdate; GetMutableDefault()->bBSPAutoUpdate = false; // Import the actors. ULevelFactory* Factory = NewObject(); Factory->FactoryCreateText(ULevel::StaticClass(), InWorld->GetCurrentLevel(), InWorld->GetCurrentLevel()->GetFName(), RF_Transactional, NULL, bDuplicate ? TEXT("move") : TEXT("paste"), Paste, Paste + FCString::Strlen(Paste), GWarn); // Reinstate old BSP update setting, and force a rebuild - any levels whose geometry has changed while pasting will be rebuilt GetMutableDefault()->bBSPAutoUpdate = bBSPAutoUpdate; RebuildAlteredBSP(); // Fire ULevel::LevelDirtiedEvent when falling out of scope. FScopedLevelDirtied LevelDirtyCallback; // Update the actors' locations and update the global list of visible layers. ULayersSubsystem* LayersSubsystem = GEditor->GetEditorSubsystem(); for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It) { AActor* Actor = static_cast(*It); checkSlow(Actor->IsA(AActor::StaticClass())); // We only want to offset the location if this actor is the root of a selected attachment hierarchy // Offsetting children of an attachment hierarchy would cause them to drift away from the node they're attached to // as the offset would effectively get applied twice const AActor* const ParentActor = Actor->GetAttachParentActor(); const FVector& ActorLocationOffset = (ParentActor && ParentActor->IsSelected()) ? FVector::ZeroVector : LocationOffset; // Offset the actor's location. Actor->TeleportTo(Actor->GetActorLocation() + ActorLocationOffset, Actor->GetActorRotation(), false, true); if (!GetDefault()->bAvoidRelabelOnPasteSelected) { // Re-label duplicated actors so that labels become unique FActorLabelUtilities::SetActorLabelUnique(Actor, Actor->GetActorLabel(), &ActorLabels); ActorLabels.Add(Actor->GetActorLabel()); } LayersSubsystem->InitializeNewActorLayers(Actor); // Ensure any layers this actor belongs to are visible LayersSubsystem->SetLayersVisibility(Actor->Layers, true); Actor->CheckDefaultSubobjects(); Actor->InvalidateLightingCache(); // Call PostEditMove to update components, etc. Actor->PostEditMove(true); Actor->PostDuplicate(EDuplicateMode::Normal); Actor->CheckDefaultSubobjects(); // Request saves/refreshes. Actor->MarkPackageDirty(); LevelDirtyCallback.Request(); } // Note the selection change. This will also redraw level viewports and update the pivot. NoteSelectionChange(); } } namespace DuplicateSelectedActors { /** * A collection of actors to duplicate and prefabs to instance that all belong to the same level. */ class FDuplicateJob { public: /** A list of actors to duplicate. */ TArray Actors; /** The source level that all actors in the Actors array come from. */ ULevel* SrcLevel; /** * Duplicate the job's actors to the specified destination level. The new actors * are appended to the specified output lists of actors. * * @param OutNewActors [out] Newly created actors are appended to this list. * @param DestLevel The level to duplicate the actors in this job to. * @param bOffsetLocations Passed to edactPasteSelected; true if new actor locations should be offset. */ void DuplicateActorsToLevel(TArray& OutNewActors, ULevel* DestLevel, bool bOffsetLocations) { // Check neither level is locked if (FLevelUtils::IsLevelLocked(SrcLevel)) { UE_LOG(LogEditorActor, Warning, TEXT("DuplicateActorsToLevel: The requested operation could not be completed because the level is locked.")); return; } TArray ActorTransforms; for (AActor* Actor: Actors) { ActorTransforms.Add(Actor->GetActorTransform()); } if (!ActorPlacementUtils::IsLevelValidForActorPlacement(DestLevel, ActorTransforms)) { return; } // Cache the current source level ULevel* OldLevel = SrcLevel->OwningWorld->GetCurrentLevel(); // Set the selection set to be precisely the actors belonging to this job. SrcLevel->OwningWorld->SetCurrentLevel(SrcLevel); GEditor->SelectNone(false, true); for (int32 ActorIndex = 0; ActorIndex < Actors.Num(); ++ActorIndex) { AActor* Actor = Actors[ActorIndex]; GEditor->SelectActor(Actor, true, false, true); } FString ScratchData; // Copy actors from src level. GEditor->edactCopySelected(SrcLevel->OwningWorld, &ScratchData); // Restore source level SrcLevel->OwningWorld->SetCurrentLevel(OldLevel); // Cache the current dest level OldLevel = DestLevel->OwningWorld->GetCurrentLevel(); // Paste to the dest level. { FLevelPartitionOperationScope LevelPartitionScope(DestLevel); DestLevel->OwningWorld->SetCurrentLevel(LevelPartitionScope.GetLevel()); GEditor->edactPasteSelected(DestLevel->OwningWorld, true, bOffsetLocations, true, &ScratchData); // Restore dest level DestLevel->OwningWorld->SetCurrentLevel(OldLevel); } // The selection set will be the newly created actors; copy them over to the output array. for (FSelectionIterator It(GEditor->GetSelectedActorIterator()); It; ++It) { AActor* Actor = static_cast(*It); checkSlow(Actor->IsA(AActor::StaticClass())); OutNewActors.Add(Actor); } } }; } // namespace DuplicateSelectedActors void UUnrealEdEngine::edactDuplicateSelected(ULevel* InLevel, bool bOffsetLocations) { int32 NumSelectedComponents = GetSelectedComponentCount(); if (NumSelectedComponents > 0) { TArray NewComponentClones; NewComponentClones.Reserve(NumSelectedComponents); // Duplicate selected components for (FSelectionIterator It(GetSelectedComponentIterator()); It; ++It) { UActorComponent* Component = CastChecked(*It); if (FComponentEditorUtils::CanCopyComponent(Component)) { if (UActorComponent* Clone = FComponentEditorUtils::DuplicateComponent(Component)) { NewComponentClones.Add(Clone); } } } if (NewComponentClones.Num() > 0) { // Make sure all the SCS trees have a chance to update FLevelEditorModule& LevelEditor = FModuleManager::LoadModuleChecked("LevelEditor"); LevelEditor.BroadcastComponentsEdited(); // Select the new clones USelection* ComponentSelection = GetSelectedComponents(); ComponentSelection->Modify(false); ComponentSelection->BeginBatchSelectOperation(); ComponentSelection->DeselectAll(); for (UActorComponent* Clone: NewComponentClones) { GEditor->SelectComponent(Clone, true, false); } ComponentSelection->EndBatchSelectOperation(true); } } else { using namespace DuplicateSelectedActors; const FScopedBusyCursor BusyCursor; GetSelectedActors()->Modify(); // Create per-level job lists. typedef TMap DuplicateJobMap; DuplicateJobMap DuplicateJobs; // Build set of selected actors before duplication TArray PreDuplicateSelection; // Add selected actors to the per-level job lists. bool bHaveActorLocation = false; FVector AnyActorLocation = FVector::ZeroVector; for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It) { AActor* Actor = static_cast(*It); checkSlow(Actor->IsA(AActor::StaticClass())); if (!bHaveActorLocation) { bHaveActorLocation = true; AnyActorLocation = Actor->GetActorLocation(); } PreDuplicateSelection.Add(Actor); ULevel* OldLevel = Actor->GetLevel(); FDuplicateJob** Job = DuplicateJobs.Find(OldLevel); if (Job) { (*Job)->Actors.Add(Actor); } else { // Allocate a new job for the level. FDuplicateJob* NewJob = new FDuplicateJob; NewJob->SrcLevel = OldLevel; NewJob->Actors.Add(Actor); DuplicateJobs.Add(OldLevel, NewJob); } } UWorld* World = InLevel->OwningWorld; ULevel* DesiredLevel = InLevel; USelection* SelectedActors = GetSelectedActors(); SelectedActors->BeginBatchSelectOperation(); SelectedActors->Modify(); // For each level, select the actors in that level and copy-paste into the destination level. TArray NewActors; for (DuplicateJobMap::TIterator It(DuplicateJobs); It; ++It) { FDuplicateJob* Job = It.Value(); check(Job); Job->DuplicateActorsToLevel(NewActors, InLevel, bOffsetLocations); } // Select any newly created actors and prefabs. SelectNone(false, true); for (int32 ActorIndex = 0; ActorIndex < NewActors.Num(); ++ActorIndex) { AActor* Actor = NewActors[ActorIndex]; SelectActor(Actor, true, false); } SelectedActors->EndBatchSelectOperation(); NoteSelectionChange(); // Finally, cleanup. for (DuplicateJobMap::TIterator It(DuplicateJobs); It; ++It) { FDuplicateJob* Job = It.Value(); delete Job; } // Build set of selected actors after duplication TArray PostDuplicateSelection; for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It) { AActor* Actor = static_cast(*It); checkSlow(Actor->IsA(AActor::StaticClass())); // We generate new seeds when we duplicate Actor->SeedAllRandomStreams(); PostDuplicateSelection.Add(Actor); } GLevelEditorModeTools().ActorsDuplicatedNotify(PreDuplicateSelection, PostDuplicateSelection, bOffsetLocations); } } bool UUnrealEdEngine::CanDeleteSelectedActors(const UWorld* InWorld, const bool bStopAtFirst, const bool bLogUndeletable, TArray* OutDeletableActors) const { // Iterate over all levels and create a list of world infos. TArray WorldSettingss; for (int32 LevelIndex = 0; LevelIndex < InWorld->GetNumLevels(); ++LevelIndex) { ULevel* Level = InWorld->GetLevel(LevelIndex); WorldSettingss.Add(Level->GetWorldSettings()); } // Iterate over selected actors and assemble a list of actors to delete. bool bContainsDeletable = false; for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It) { AActor* Actor = static_cast(*It); checkSlow(Actor->IsA(AActor::StaticClass())); // Only delete transactional actors that aren't a level's builder brush or worldsettings. bool bDeletable = false; FText CannotDeleteReason; if (Actor->HasAllFlags(RF_Transactional) && Actor->CanDeleteSelectedActor(CannotDeleteReason)) { // TODO: The Brush and WorldSettings logic should be moved to use the CanDeleteSelectedActor virtual ABrush* Brush = Cast(Actor); const bool bIsDefaultBrush = Brush && FActorEditorUtils::IsABuilderBrush(Brush); if (!bIsDefaultBrush) { const bool bIsWorldSettings = Actor->IsA(AWorldSettings::StaticClass()) && WorldSettingss.Contains(static_cast(Actor)); if (!bIsWorldSettings) { bContainsDeletable = true; bDeletable = true; } } } // Can this actor be deleted if (bDeletable) { if (OutDeletableActors) { OutDeletableActors->Add(Actor); } if (bStopAtFirst) { break; // Did we only want to know if ANY of the actors were deletable } } else if (bLogUndeletable) { FFormatNamedArguments Arguments; Arguments.Add(TEXT("Name"), FText::FromString(Actor->GetFullName())); FText LogText; if (CannotDeleteReason.IsEmpty()) { LogText = FText::Format(LOCTEXT("CannotDeleteSpecialActor", "Cannot delete special actor {Name}"), Arguments); } else { Arguments.Add(TEXT("Reason"), CannotDeleteReason); LogText = FText::Format(LOCTEXT("CannotDeleteActorWithReason", "Cannot delete actor {Name} - {Reason}"), Arguments); } UE_LOG(LogEditorActor, Log, TEXT("%s"), *LogText.ToString()); } } return bContainsDeletable; } bool UUnrealEdEngine::edactDeleteSelected(UWorld* InWorld, bool bVerifyDeletionCanHappen, bool bWarnAboutReferences, bool bWarnAboutSoftReferences) { if (bVerifyDeletionCanHappen) { // Provide the option to abort the delete if (ShouldAbortActorDeletion()) { return false; } } const double StartSeconds = FPlatformTime::Seconds(); // Aggregate the time we waited on user input to remove it from the total time double DialogWaitingSeconds = 0; FSlateApplication::Get().CancelDragDrop(); if (GetSelectedComponentCount() > 0) { TArray SelectedEditableComponents; for (FSelectedEditableComponentIterator It(GetSelectedEditableComponentIterator()); It; ++It) { SelectedEditableComponents.Add(CastChecked(*It)); } if (SelectedEditableComponents.Num() > 0) { // Modify the actor that owns the selected components check(GetSelectedActorCount() == 1); (*GetSelectedActorIterator())->Modify(); // Delete the selected components UActorComponent* ComponentToSelect = nullptr; int32 NumDeletedComponents = FComponentEditorUtils::DeleteComponents(SelectedEditableComponents, ComponentToSelect); if (NumDeletedComponents > 0) { // Make sure all the SCS trees have a chance to rebuild FLevelEditorModule& LevelEditor = FModuleManager::LoadModuleChecked("LevelEditor"); LevelEditor.BroadcastComponentsEdited(); // Update the editor component selection if possible if (ComponentToSelect) { USelection* ComponentSelection = GetSelectedComponents(); ComponentSelection->Modify(false); ComponentSelection->BeginBatchSelectOperation(); ComponentSelection->DeselectAll(); GEditor->SelectComponent(ComponentToSelect, true, false); // Make sure the selection changed event fires so the SCS trees can update their selection ComponentSelection->MarkBatchDirty(); ComponentSelection->EndBatchSelectOperation(true); // Notify the level editor of the new component selection NoteSelectionChange(); } UE_LOG(LogEditorActor, Log, TEXT("Deleted %d Components (%3.3f secs)"), NumDeletedComponents, FPlatformTime::Seconds() - StartSeconds); return true; } } return false; } GetSelectedActors()->Modify(); // Fire ULevel::LevelDirtiedEvent when falling out of scope. FScopedLevelDirtied LevelDirtyCallback; // Get a list of all the deletable actors in the selection TArray ActorsToDelete; CanDeleteSelectedActors(InWorld, false, true, &ActorsToDelete); // Maintain a list of levels that have already been Modify()'d so that each level // is modify'd only once. TArray LevelsAlreadyModified; // A list of levels that will need their Bsp updated after the deletion is complete TSet LevelsToRebuildBSP; TSet LevelsToRebuildNavigation; bool bRequestedDeleteAllByLevel = false; bool bRequestedDeleteAllByActor = false; bool bRequestedDeleteAllBySoftReference = false; EAppMsgType::Type MessageType = ActorsToDelete.Num() > 1 ? EAppMsgType::YesNoYesAllNoAll : EAppMsgType::YesNo; int32 DeleteCount = 0; USelection* SelectedActors = GetSelectedActors(); TMap> ReferencingActorsMap; TMap> SoftReferencingObjectsMap; TArray ClassTypesToIgnore; ClassTypesToIgnore.Add(ALevelScriptActor::StaticClass()); // The delete warning is meant for actor references that affect gameplay. Group actors do not affect gameplay and should not show up as a warning. ClassTypesToIgnore.Add(AGroupActor::StaticClass()); // If we want to warn about references to the actors to be deleted, it is a lot more efficient to query // the world first and build a map of actors referenced by other actors. We can then quickly look this up later on in the loop. if (bWarnAboutReferences) { FBlueprintEditorUtils::GetActorReferenceMap(InWorld, ClassTypesToIgnore, ReferencingActorsMap); if (bWarnAboutSoftReferences) { FScopedSlowTask SlowTask(ActorsToDelete.Num(), LOCTEXT("ComputeActorSoftReferences", "Computing References")); SlowTask.MakeDialogDelayed(1.0f); FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked(TEXT("AssetTools")); for (int32 ActorIndex = 0; ActorIndex < ActorsToDelete.Num(); ++ActorIndex) { SlowTask.EnterProgressFrame(); TArray SoftReferencingObjects; AActor* Actor = ActorsToDelete[ActorIndex]; AssetToolsModule.Get().FindSoftReferencesToObject(Actor, SoftReferencingObjects); if (SoftReferencingObjects.Num() > 0) { SoftReferencingObjectsMap.Add(Actor, SoftReferencingObjects); } } } } ULayersSubsystem* LayersSubsystem = GEditor->GetEditorSubsystem(); for (int32 ActorIndex = 0; ActorIndex < ActorsToDelete.Num(); ++ActorIndex) { AActor* Actor = ActorsToDelete[ActorIndex]; // If actor is referenced by script, ask user if they really want to delete ULevelScriptBlueprint* LSB = Actor->GetLevel()->GetLevelScriptBlueprint(true); // Get the array of actors that reference this actor from the cached map we built above. TArray* ReferencingActors = nullptr; if (bWarnAboutReferences) { ReferencingActors = ReferencingActorsMap.Find(Actor); } TArray ReferencedToActorsFromLevelScriptArray; FBlueprintEditorUtils::FindReferencesToActorFromLevelScript(LSB, Actor, ReferencedToActorsFromLevelScriptArray); bool bReferencedByLevelScript = bWarnAboutReferences && (nullptr != LSB && ReferencedToActorsFromLevelScriptArray.Num() > 0); bool bReferencedByActor = false; bool bReferencedByLODActor = false; bool bReferencedBySoftReference = false; TArray* SoftReferencingObjects = nullptr; if (bWarnAboutSoftReferences) { SoftReferencingObjects = SoftReferencingObjectsMap.Find(Actor); if (SoftReferencingObjects) { bReferencedBySoftReference = true; } } // If there are any referencing actors, make sure that they are reference types that we care about. if (ReferencingActors != nullptr) { for (AActor* ReferencingActor: (*ReferencingActors)) { // Skip to next if we are referencing ourselves if (ReferencingActor == Actor) { continue; } else if (Cast(ReferencingActor)) { bReferencedByLODActor = true; } else { // If the referencing actor is a child actor that is referencing us, do not treat it // as referencing for the purposes of warning about deletion UChildActorComponent* ParentComponent = ReferencingActor->GetParentComponent(); if (ParentComponent == nullptr || ParentComponent->GetOwner() != Actor) { bReferencedByActor = true; FText ActorReferencedMessage = FText::Format(LOCTEXT("ActorDeleteReferencedMessage", "Actor {0} is referenced by {1}."), FText::FromString(Actor->GetActorLabel()), FText::FromString(ReferencingActor->GetActorLabel())); UE_LOG(LogEditorActor, Log, TEXT("%s"), *ActorReferencedMessage.ToString()); } } } } // We have references from one or more sources, prompt the user for feedback. if (bReferencedByLevelScript || bReferencedByActor || bReferencedBySoftReference || bReferencedByLODActor) { if ((bReferencedByLevelScript && !bRequestedDeleteAllByLevel) || (bReferencedByActor && !bRequestedDeleteAllByActor) || (bReferencedBySoftReference && !bRequestedDeleteAllBySoftReference)) { FText ConfirmDelete; FString LevelScriptReferenceString; for (int32 i = 0; i < ReferencedToActorsFromLevelScriptArray.Num(); ++i) { LevelScriptReferenceString += ReferencedToActorsFromLevelScriptArray[i]->GetFindReferenceSearchString(); if (bReferencedByLevelScript && bReferencedByActor) { LevelScriptReferenceString += TEXT(" (Level Blueprint)"); } LevelScriptReferenceString += TEXT("\n"); } LevelScriptReferenceString.TrimEndInline(); FString ActorReferenceString; if (ReferencingActors != nullptr) { for (int32 i = 0; i < ReferencingActors->Num(); ++i) { ActorReferenceString += (*ReferencingActors)[i]->GetActorLabel(); if (bReferencedByLevelScript && bReferencedByActor) { ActorReferenceString += TEXT(" (Other Actor)"); } ActorReferenceString += TEXT("\n"); } } if (bReferencedBySoftReference) { for (UObject* ReferencingObject: *SoftReferencingObjects) { if (AActor* ReferencingActor = Cast(ReferencingObject)) { ActorReferenceString += FString::Printf(TEXT("(Soft) Actor %s in %s\n"), *ReferencingActor->GetActorLabel(), *FPackageName::GetLongPackageAssetName(ReferencingActor->GetOutermost()->GetName())); } else { ActorReferenceString += FString::Printf(TEXT("(Soft) Object %s\n"), *ReferencingObject->GetPathName()); } } } ActorReferenceString.TrimEndInline(); if (bReferencedByLevelScript && (bReferencedByActor || bReferencedBySoftReference)) { ConfirmDelete = FText::Format(LOCTEXT("ConfirmDeleteActorReferenceByScriptAndActor", "Actor {0} is referenced by the level blueprint and other Actors/Objects.\nDo you really want to delete it? This will break references.\n\nReference List:\n\n{1}\n{2}"), FText::FromString(Actor->GetActorLabel()), FText::FromString(LevelScriptReferenceString), FText::FromString(ActorReferenceString)); } else if (bReferencedByLevelScript) { ConfirmDelete = FText::Format(LOCTEXT("ConfirmDeleteActorReferencedByScript", "Actor {0} is referenced by the level blueprint.\nDo you really want to delete it? This will break references.\n\nReference List:\n\n{1}"), FText::FromString(Actor->GetActorLabel()), FText::FromString(LevelScriptReferenceString)); } else { ConfirmDelete = FText::Format(LOCTEXT("ConfirmDeleteActorReferencedByActor", "Actor {0} is referenced by other Actors/Objects.\nDo you really want to delete it? This will break references.\n\nReference List:\n\n{1}"), FText::FromString(Actor->GetActorLabel()), FText::FromString(ActorReferenceString)); } const double DialogStartSeconds = FPlatformTime::Seconds(); int32 Result = FMessageDialog::Open(MessageType, ConfirmDelete); DialogWaitingSeconds += FPlatformTime::Seconds() - DialogStartSeconds; if (Result == EAppReturnType::YesAll) { bRequestedDeleteAllByLevel |= bReferencedByLevelScript; bRequestedDeleteAllByActor |= bReferencedByActor; bRequestedDeleteAllBySoftReference |= bReferencedBySoftReference; } else if (Result == EAppReturnType::NoAll) { break; } else if (Result == EAppReturnType::No || Result == EAppReturnType::Cancel) { continue; } } if (bReferencedByLevelScript) { FBlueprintEditorUtils::ModifyActorReferencedGraphNodes(LSB, Actor); } if (bReferencedByActor || bReferencedByLODActor) { check(ReferencingActors != nullptr); for (int32 ReferencingActorIndex = 0; ReferencingActorIndex < ReferencingActors->Num(); ReferencingActorIndex++) { AActor* ReferencingActor = (*ReferencingActors)[ReferencingActorIndex]; ReferencingActor->Modify(); ALODActor* LODActor = Cast(ReferencingActor); // it's possible other actor is referencing this if (LODActor) { LODActor->RemoveSubActor(Actor); FText SubActorRemovedMessage = FText::Format(LOCTEXT("LODActorSubActorDeletedMessage", "Sub Actor '{0}' was removed from LODActor '{1}'."), FText::FromString(Actor->GetActorLabel()), FText::FromString(ReferencingActor->GetActorLabel())); UE_LOG(LogEditorActor, Log, TEXT("%s"), *SubActorRemovedMessage.ToString()); } } } } bool bRebuildNavigation = false; ABrush* Brush = Cast(Actor); if (Brush && !FActorEditorUtils::IsABuilderBrush(Brush)) // Track whether or not a brush actor was deleted. { ULevel* BrushLevel = Actor->GetLevel(); if (BrushLevel && !Brush->IsVolumeBrush()) { BrushLevel->Model->Modify(); LevelsToRebuildBSP.Add(BrushLevel); // Rebuilding bsp will also take care of navigation LevelsToRebuildNavigation.Remove(BrushLevel); } else if (BrushLevel && !LevelsToRebuildBSP.Contains(BrushLevel)) { LevelsToRebuildNavigation.Add(BrushLevel); } } // If the actor about to be deleted is in a group, be sure to remove it from the group AGroupActor* ActorParentGroup = AGroupActor::GetParentForActor(Actor); if (ActorParentGroup) { ActorParentGroup->Remove(*Actor); } // Remove actor from all asset editors GEditor->GetEditorSubsystem()->RemoveAssetFromAllEditors(Actor); // Mark the actor's level as dirty. Actor->MarkPackageDirty(); LevelDirtyCallback.Request(); // Deselect the Actor. SelectedActors->Deselect(Actor); // Modify the level. Each level is modified only once. // @todo DB: Shouldn't this be calling UWorld::ModifyLevel? ULevel* Level = Actor->GetLevel(); if (LevelsAlreadyModified.Find(Level) == INDEX_NONE) { LevelsAlreadyModified.Add(Level); // Don't mark the level dirty when deleting external actors and the level is in `use external actors` mode. bool bShouldDirty = !(Actor->IsPackageExternal() && Level->IsUsingExternalActors()); Level->Modify(bShouldDirty); } UE_LOG(LogEditorActor, Log, TEXT("Deleted Actor: %s"), *Actor->GetClass()->GetName()); // Destroy actor and clear references. LayersSubsystem->DisassociateActorFromLayers(Actor); bool WasDestroyed = Actor->GetWorld()->EditorDestroyActor(Actor, false); checkf(WasDestroyed, TEXT("Failed to destroy Actor %s (%s)"), *Actor->GetClass()->GetName(), *Actor->GetActorLabel()); DeleteCount++; } // Remove all references to destroyed actors once at the end, instead of once for each Actor destroyed.. CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); // If any brush actors were modified, update the Bsp in the appropriate levels if (LevelsToRebuildBSP.Num()) { FlushRenderingCommands(); for (ULevel* Level: LevelsToRebuildBSP) { GEditor->RebuildLevel(*Level); } } NoteSelectionChange(); if (LevelsToRebuildNavigation.Num()) { for (ULevel* Level: LevelsToRebuildNavigation) { if (Level) { FNavigationSystem::UpdateLevelCollision(*Level); } } } if (LevelsToRebuildBSP.Num() || LevelsToRebuildNavigation.Num()) { RedrawLevelEditingViewports(); ULevel::LevelDirtiedEvent.Broadcast(); } UE_LOG(LogEditorActor, Log, TEXT("Deleted %d Actors (%3.3f secs)"), DeleteCount, (FPlatformTime::Seconds() - StartSeconds) - DialogWaitingSeconds); return true; } bool UUnrealEdEngine::ShouldAbortActorDeletion() const { bool bResult = false; // Can't delete actors if Matinee is open. const FText ErrorMsg = NSLOCTEXT("UnrealEd", "Error_WrongModeForActorDeletion", "Cannot delete actor while Matinee is open"); if (!GLevelEditorModeTools().EnsureNotInMode(FBuiltinEditorModes::EM_InterpEdit, ErrorMsg, true)) { bResult = true; } if (!bResult) { for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It) { AActor* Actor = static_cast(*It); checkSlow(Actor->IsA(AActor::StaticClass())); ULevel* ActorLevel = Actor->GetLevel(); if (FLevelUtils::IsLevelLocked(ActorLevel)) { UE_LOG(LogEditorActor, Warning, TEXT("Cannot perform action on actor %s because the actor's level is locked"), *Actor->GetName()); bResult = true; break; } } } return bResult; } void UUnrealEdEngine::edactReplaceSelectedBrush(UWorld* InWorld) { // Make a list of brush actors to replace. ABrush* DefaultBrush = InWorld->GetDefaultBrush(); TArray BrushesToReplace; for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It) { AActor* Actor = static_cast(*It); checkSlow(Actor->IsA(AActor::StaticClass())); ABrush* Brush = Cast(Actor); if (Brush && Actor->HasAnyFlags(RF_Transactional) && Actor != DefaultBrush) { BrushesToReplace.Add(Brush); } } // Fire ULevel::LevelDirtiedEvent when falling out of scope. FScopedLevelDirtied LevelDirtyCallback; USelection* SelectedActors = GetSelectedActors(); SelectedActors->BeginBatchSelectOperation(); SelectedActors->Modify(); // Replace brushes. ULayersSubsystem* LayersSubsystem = GEditor->GetEditorSubsystem(); for (int32 BrushIndex = 0; BrushIndex < BrushesToReplace.Num(); ++BrushIndex) { ABrush* SrcBrush = BrushesToReplace[BrushIndex]; ABrush* NewBrush = FBSPOps::csgAddOperation(DefaultBrush, SrcBrush->PolyFlags, (EBrushType)SrcBrush->BrushType); if (NewBrush) { SrcBrush->MarkPackageDirty(); NewBrush->MarkPackageDirty(); LevelDirtyCallback.Request(); NewBrush->Modify(); NewBrush->Layers.Append(SrcBrush->Layers); NewBrush->CopyPosRotScaleFrom(SrcBrush); NewBrush->PostEditMove(true); SelectActor(SrcBrush, false, false); SelectActor(NewBrush, true, false); LayersSubsystem->DisassociateActorFromLayers(SrcBrush); InWorld->EditorDestroyActor(SrcBrush, true); } } SelectedActors->EndBatchSelectOperation(); NoteSelectionChange(); } AActor* UUnrealEdEngine::ReplaceActor(AActor* CurrentActor, UClass* NewActorClass, UObject* Archetype, bool bNoteSelectionChange) { FVector SpawnLoc = CurrentActor->GetActorLocation(); FRotator SpawnRot = CurrentActor->GetActorRotation(); FActorSpawnParameters SpawnInfo; SpawnInfo.Template = Cast(Archetype); SpawnInfo.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; AActor* NewActor = CurrentActor->GetWorld()->SpawnActor(NewActorClass, &SpawnLoc, &SpawnRot, SpawnInfo); if (NewActor) { NewActor->Modify(); ULayersSubsystem* LayersSubsystem = GEditor->GetEditorSubsystem(); LayersSubsystem->InitializeNewActorLayers(NewActor); const bool bCurrentActorSelected = GetSelectedActors()->IsSelected(CurrentActor); if (bCurrentActorSelected) { // The source actor was selected, so deselect the old actor and select the new one. GetSelectedActors()->Modify(); SelectActor(NewActor, bCurrentActorSelected, false); SelectActor(CurrentActor, false, false); } { LayersSubsystem->DisassociateActorFromLayers(NewActor); NewActor->Layers.Empty(); LayersSubsystem->AddActorToLayers(NewActor, CurrentActor->Layers); NewActor->SetActorLabel(CurrentActor->GetActorLabel()); NewActor->Tags = CurrentActor->Tags; NewActor->EditorReplacedActor(CurrentActor); } LayersSubsystem->DisassociateActorFromLayers(CurrentActor); CurrentActor->GetWorld()->EditorDestroyActor(CurrentActor, true); // Note selection change if necessary and requested. if (bCurrentActorSelected && bNoteSelectionChange) { NoteSelectionChange(); } // whenever selection changes, recompute whether the selection contains a locked actor bCheckForLockActors = true; // whenever selection changes, recompute whether the selection contains a world info actor bCheckForWorldSettingsActors = true; } return NewActor; } void UUnrealEdEngine::edactReplaceSelectedNonBrushWithClass(UClass* Class) { // Make a list of actors to replace. TArray ActorsToReplace; for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It) { AActor* Actor = static_cast(*It); checkSlow(Actor->IsA(AActor::StaticClass())); ABrush* Brush = Cast(Actor); if (!Brush && Actor->HasAnyFlags(RF_Transactional)) { ActorsToReplace.Add(Actor); } } // Fire ULevel::LevelDirtiedEvent when falling out of scope. FScopedLevelDirtied LevelDirtyCallback; // Replace actors. for (int32 i = 0; i < ActorsToReplace.Num(); ++i) { AActor* SrcActor = ActorsToReplace[i]; AActor* NewActor = ReplaceActor(SrcActor, Class, NULL, false); if (NewActor) { NewActor->MarkPackageDirty(); LevelDirtyCallback.Request(); } } NoteSelectionChange(); } void UUnrealEdEngine::edactReplaceClassWithClass(UWorld* InWorld, UClass* SrcClass, UClass* DstClass) { // Make a list of actors to replace. TArray ActorsToReplace; for (TActorIterator It(InWorld, SrcClass); It; ++It) { AActor* Actor = *It; if (Actor->HasAnyFlags(RF_Transactional)) { ActorsToReplace.Add(Actor); } } // Fires ULevel::LevelDirtiedEvent when falling out of scope. FScopedLevelDirtied LevelDirtyCallback; // Replace actors. for (int32 i = 0; i < ActorsToReplace.Num(); ++i) { AActor* SrcActor = ActorsToReplace[i]; AActor* NewActor = ReplaceActor(SrcActor, DstClass, NULL, false); if (NewActor) { NewActor->MarkPackageDirty(); LevelDirtyCallback.Request(); } } NoteSelectionChange(); } void UUnrealEdEngine::edactHideSelected(UWorld* InWorld) { // Assemble a list of actors to hide. TArray ActorsToHide; for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It) { AActor* Actor = static_cast(*It); checkSlow(Actor->IsA(AActor::StaticClass())); // Don't consider already hidden actors or the builder brush if (!FActorEditorUtils::IsABuilderBrush(Actor) && !Actor->IsHiddenEd()) { ActorsToHide.Add(Actor); } } // Hide the actors that were selected and deselect them in the process if (ActorsToHide.Num() > 0) { USelection* SelectedActors = GetSelectedActors(); SelectedActors->Modify(); for (int32 ActorIndex = 0; ActorIndex < ActorsToHide.Num(); ++ActorIndex) { AActor* Actor = ActorsToHide[ActorIndex]; // Save the actor to the transaction buffer to support undo/redo, but do // not call Modify, as we do not want to dirty the actor's package and // we're only editing temporary, transient values SaveToTransactionBuffer(Actor, false); Actor->SetIsTemporarilyHiddenInEditor(true); SelectedActors->Deselect(Actor); } NoteSelectionChange(); } // Iterate through all of the BSP models and hide any that were selected (deselecting them in the process) if (InWorld) { for (TArray::TConstIterator LevelIterator = InWorld->GetLevels().CreateConstIterator(); LevelIterator; ++LevelIterator) { UModel& CurLevelModel = *((*LevelIterator)->Model); for (TArray::TIterator SurfaceIterator(CurLevelModel.Surfs); SurfaceIterator; ++SurfaceIterator) { FBspSurf& CurSurface = *SurfaceIterator; if ((CurSurface.PolyFlags & PF_Selected) && !CurSurface.IsHiddenEd()) { CurLevelModel.ModifySurf(SurfaceIterator.GetIndex(), false); // Deselect the surface and mark it as hidden to the editor CurSurface.PolyFlags &= ~PF_Selected; CurSurface.bHiddenEdTemporary = true; } } } } RedrawLevelEditingViewports(); } void UUnrealEdEngine::edactHideUnselected(UWorld* InWorld) { // Iterate through all of the actors and hide the ones which are not selected and are not already hidden for (FActorIterator It(InWorld); It; ++It) { AActor* Actor = *It; if (!FActorEditorUtils::IsABuilderBrush(Actor) && !Actor->IsSelected() && !Actor->IsHiddenEd()) { // Save the actor to the transaction buffer to support undo/redo, but do // not call Modify, as we do not want to dirty the actor's package and // we're only editing temporary, transient values SaveToTransactionBuffer(Actor, false); Actor->SetIsTemporarilyHiddenInEditor(true); } } // Iterate through all of the BSP models and hide the ones which are not selected and are not already hidden if (InWorld) { for (TArray::TConstIterator LevelIterator = InWorld->GetLevels().CreateConstIterator(); LevelIterator; ++LevelIterator) { UModel& CurLevelModel = *((*LevelIterator)->Model); for (TArray::TIterator SurfaceIterator(CurLevelModel.Surfs); SurfaceIterator; ++SurfaceIterator) { FBspSurf& CurSurface = *SurfaceIterator; // Only modify surfaces that aren't selected and aren't already hidden if (!(CurSurface.PolyFlags & PF_Selected) && !CurSurface.IsHiddenEd()) { CurLevelModel.ModifySurf(SurfaceIterator.GetIndex(), false); CurSurface.bHiddenEdTemporary = true; } } } } RedrawLevelEditingViewports(); } void UUnrealEdEngine::edactUnHideAll(UWorld* InWorld) { // Iterate through all of the actors and unhide them for (FActorIterator It(InWorld); It; ++It) { AActor* Actor = *It; if (!FActorEditorUtils::IsABuilderBrush(Actor) && Actor->IsTemporarilyHiddenInEditor()) { // Save the actor to the transaction buffer to support undo/redo, but do // not call Modify, as we do not want to dirty the actor's package and // we're only editing temporary, transient values SaveToTransactionBuffer(Actor, false); Actor->SetIsTemporarilyHiddenInEditor(false); } } // Iterate through all of the BSP models and unhide them if they are already hidden if (InWorld) { for (TArray::TConstIterator LevelIterator = InWorld->GetLevels().CreateConstIterator(); LevelIterator; ++LevelIterator) { UModel& CurLevelModel = *((*LevelIterator)->Model); for (TArray::TIterator SurfaceIterator(CurLevelModel.Surfs); SurfaceIterator; ++SurfaceIterator) { FBspSurf& CurSurface = *SurfaceIterator; if (CurSurface.bHiddenEdTemporary) { CurLevelModel.ModifySurf(SurfaceIterator.GetIndex(), false); CurSurface.bHiddenEdTemporary = false; } } } } RedrawLevelEditingViewports(); } void UUnrealEdEngine::edactHideSelectedStartup(UWorld* InWorld) { // Fires ULevel::LevelDirtiedEvent when falling out of scope. FScopedLevelDirtied LevelDirtyCallback; // Iterate through all of the selected actors for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It) { AActor* Actor = static_cast(*It); checkSlow(Actor->IsA(AActor::StaticClass())); // Set the actor to hide at editor startup, if it's not already set that way if (!FActorEditorUtils::IsABuilderBrush(Actor) && !Actor->IsHiddenEd() && !Actor->IsHiddenEdAtStartup()) { Actor->Modify(); Actor->bHiddenEd = true; LevelDirtyCallback.Request(); } } if (InWorld) { // Iterate through all of the selected BSP surfaces for (TArray::TConstIterator LevelIterator = InWorld->GetLevels().CreateConstIterator(); LevelIterator; ++LevelIterator) { UModel& CurLevelModel = *((*LevelIterator)->Model); for (TArray::TIterator SurfaceIterator(CurLevelModel.Surfs); SurfaceIterator; ++SurfaceIterator) { FBspSurf& CurSurface = *SurfaceIterator; // Set the BSP surface to hide at editor startup, if it's not already set that way const bool bSelected = CurSurface.Actor->IsSelected() || (CurSurface.PolyFlags & PF_Selected); if (bSelected && !CurSurface.IsHiddenEdAtStartup() && !CurSurface.IsHiddenEd()) { CurLevelModel.Modify(); CurLevelModel.ModifySurf(SurfaceIterator.GetIndex(), false); CurSurface.PolyFlags |= PF_HiddenEd; LevelDirtyCallback.Request(); } } } } RedrawLevelEditingViewports(); } void UUnrealEdEngine::edactUnHideAllStartup(UWorld* InWorld) { // Fires ULevel::LevelDirtiedEvent when falling out of scope. FScopedLevelDirtied LevelDirtyCallback; // Iterate over all actors for (FActorIterator It(InWorld); It; ++It) { AActor* Actor = static_cast(*It); checkSlow(Actor->IsA(AActor::StaticClass())); // If the actor is set to be hidden at editor startup, change it so that it will be shown at startup if (!FActorEditorUtils::IsABuilderBrush(Actor) && Actor->IsHiddenEdAtStartup()) { Actor->Modify(); Actor->bHiddenEd = false; LevelDirtyCallback.Request(); } } if (InWorld) { // Iterate over all BSP surfaces for (TArray::TConstIterator LevelIterator = InWorld->GetLevels().CreateConstIterator(); LevelIterator; ++LevelIterator) { UModel& CurLevelModel = *((*LevelIterator)->Model); for (TArray::TIterator SurfaceIterator(CurLevelModel.Surfs); SurfaceIterator; ++SurfaceIterator) { FBspSurf& CurSurface = *SurfaceIterator; // If the BSP surface is set to be hidden at editor startup, change it so that it will be shown at startup if (CurSurface.IsHiddenEdAtStartup()) { CurLevelModel.Modify(); CurLevelModel.ModifySurf(SurfaceIterator.GetIndex(), false); CurSurface.PolyFlags &= ~PF_HiddenEd; LevelDirtyCallback.Request(); } } } } RedrawLevelEditingViewports(); } void UUnrealEdEngine::edactUnHideSelectedStartup(UWorld* InWorld) { // Fires ULevel::LevelDirtiedEvent when falling out of scope. FScopedLevelDirtied LevelDirtyCallback; // Iterate over all selected actors for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It) { AActor* Actor = static_cast(*It); checkSlow(Actor->IsA(AActor::StaticClass())); // Mark the selected actor as showing at editor startup if it was currently set to be hidden if (!FActorEditorUtils::IsABuilderBrush(Actor) && Actor->IsHiddenEdAtStartup()) { Actor->Modify(); Actor->bHiddenEd = false; LevelDirtyCallback.Request(); } } if (InWorld) { // Iterate over all selected BSP surfaces for (TArray::TConstIterator LevelIterator = InWorld->GetLevels().CreateConstIterator(); LevelIterator; ++LevelIterator) { UModel& CurLevelModel = *((*LevelIterator)->Model); for (TArray::TIterator SurfaceIterator(CurLevelModel.Surfs); SurfaceIterator; ++SurfaceIterator) { FBspSurf& CurSurface = *SurfaceIterator; // Mark the selected BSP surface as showing at editor startup if it was currently set to be hidden const bool bSelected = CurSurface.Actor->IsSelected() || (CurSurface.PolyFlags & PF_Selected); if (bSelected && CurSurface.IsHiddenEdAtStartup()) { CurLevelModel.Modify(); CurLevelModel.ModifySurf(SurfaceIterator.GetIndex(), false); CurSurface.PolyFlags &= ~PF_HiddenEd; LevelDirtyCallback.Request(); } } } } RedrawLevelEditingViewports(); } void UUnrealEdEngine::edactUnhideSelected(UWorld* InWorld) { // Assemble a list of actors to hide. TArray ActorsToShow; for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It) { AActor* Actor = static_cast(*It); checkSlow(Actor->IsA(AActor::StaticClass())); // Don't consider already visible actors or the builder brush if (!FActorEditorUtils::IsABuilderBrush(Actor) && Actor->IsHiddenEd()) { ActorsToShow.Add(Actor); } } // Show the actors that were selected if (ActorsToShow.Num() > 0) { USelection* SelectedActors = GetSelectedActors(); SelectedActors->Modify(); for (int32 ActorIndex = 0; ActorIndex < ActorsToShow.Num(); ++ActorIndex) { AActor* Actor = ActorsToShow[ActorIndex]; // Save the actor to the transaction buffer to support undo/redo, but do // not call Modify, as we do not want to dirty the actor's package and // we're only editing temporary, transient values SaveToTransactionBuffer(Actor, false); Actor->SetIsTemporarilyHiddenInEditor(false); } } // Iterate through all of the BSP models and show any that were selected if (InWorld) { for (TArray::TConstIterator LevelIterator = InWorld->GetLevels().CreateConstIterator(); LevelIterator; ++LevelIterator) { UModel& CurLevelModel = *((*LevelIterator)->Model); for (TArray::TIterator SurfaceIterator(CurLevelModel.Surfs); SurfaceIterator; ++SurfaceIterator) { FBspSurf& CurSurface = *SurfaceIterator; if ((CurSurface.PolyFlags & PF_Selected) && !CurSurface.IsHiddenEd()) { CurLevelModel.ModifySurf(SurfaceIterator.GetIndex(), false); CurSurface.bHiddenEdTemporary = false; } } } } RedrawLevelEditingViewports(); } void UUnrealEdEngine::CreateBSPVisibilityMap(UWorld* InWorld, TMap>& OutBSPMap, bool& bOutAllVisible) { // Start out true, we do not know otherwise. bOutAllVisible = true; // Iterate through all of the BSP models and any that are visible to the list. if (InWorld) { for (TArray::TConstIterator LevelIterator = InWorld->GetLevels().CreateConstIterator(); LevelIterator; ++LevelIterator) { UModel& CurLevelModel = *((*LevelIterator)->Model); for (TArray::TIterator SurfaceIterator(CurLevelModel.Surfs); SurfaceIterator; ++SurfaceIterator) { FBspSurf& CurSurface = *SurfaceIterator; // If the surface is visible, we will want to add it to the map. if (CurSurface.bHiddenEdTemporary == false) { // First check if we have already added our surface's brush actor to the map. TArray* BrushPolyList = OutBSPMap.Find(CurSurface.Actor); if (BrushPolyList) { // We found the brush actor on the list, so add our polygon ID to the list. BrushPolyList->Add(CurSurface.iBrushPoly); } else { // The brush actor has not been added to the map, add it. OutBSPMap.Add(CurSurface.Actor, TArray()); // Grab the list out and add our brush poly to it. BrushPolyList = OutBSPMap.Find(CurSurface.Actor); BrushPolyList->Add(CurSurface.iBrushPoly); } } else { // We found one that is not visible, so they are not ALL visible. We will continue to map out geometry to come up with a complete Visibility map. bOutAllVisible = false; } } } } } void UUnrealEdEngine::MakeBSPMapVisible(const TMap>& InBSPMap, UWorld* InWorld) { // Iterate through all of the BSP models and show any that were selected if (InWorld) { for (TArray::TConstIterator LevelIterator = InWorld->GetLevels().CreateConstIterator(); LevelIterator; ++LevelIterator) { UModel& CurLevelModel = *((*LevelIterator)->Model); for (TArray::TIterator SurfaceIterator(CurLevelModel.Surfs); SurfaceIterator; ++SurfaceIterator) { FBspSurf& CurSurface = *SurfaceIterator; // Check if we can find the surface's actor in the map. const TArray* BrushPolyList = InBSPMap.Find(CurSurface.Actor); if (BrushPolyList) { // We have the list of brush polygons that are visible, check if the current one is on the list. if (BrushPolyList->FindByKey(CurSurface.iBrushPoly)) { // Make the surface visible. CurSurface.bHiddenEdTemporary = false; } else { // The brush poly was not in the map, so it should be hidden. CurSurface.bHiddenEdTemporary = true; } } else { // There was no brush poly list, that means no polygon on this brush was visible, make this surface hidden. CurSurface.bHiddenEdTemporary = true; } } } } } AActor* UUnrealEdEngine::GetDesiredAttachmentState(TArray& OutNewChildren) { // Get the selection set (first one will be the new base) AActor* NewBase = NULL; OutNewChildren.Empty(); for (FSelectionIterator It(GEditor->GetSelectedActorIterator()); It; ++It) { AActor* SelectedActor = Cast(*It); if (SelectedActor) { OutNewChildren.AddUnique(SelectedActor); } } // Last element of the array becomes new base if (OutNewChildren.Num() > 0) { NewBase = OutNewChildren.Pop(); } return NewBase; } void UUnrealEdEngine::AttachSelectedActors() { const FScopedTransaction Transaction(NSLOCTEXT("Editor", "UndoAction_PerformAttachment", "Attach actors")); // Get what we want attachment to be TArray NewChildren; AActor* NewBase = GetDesiredAttachmentState(NewChildren); if (NewBase && NewBase->GetRootComponent() && (NewChildren.Num() > 0)) { // Do the actual base change for (int32 ChildIdx = 0; ChildIdx < NewChildren.Num(); ChildIdx++) { AActor* Child = NewChildren[ChildIdx]; if (Child) { ParentActors(NewBase, Child, NAME_None); } } RedrawLevelEditingViewports(); } } void UUnrealEdEngine::edactSelectAll(UWorld* InWorld) { // If there are a lot of actors to process, pop up a warning "are you sure?" box int32 NumActors = InWorld->GetActorCount(); bool bShowProgress = false; if (NumActors >= EditorActorSelectionDefs::MaxActorsToSelectBeforeWarning) { bShowProgress = true; const FText ConfirmText = FText::Format(NSLOCTEXT("UnrealEd", "Warning_ManyActorsForSelect", "There are {0} actors in the world. Are you sure you want to select them all?"), FText::AsNumber(NumActors)); FSuppressableWarningDialog::FSetupInfo Info(ConfirmText, NSLOCTEXT("UnrealEd", "Warning_ManyActors", "Warning: Many Actors"), "Warning_ManyActors"); Info.ConfirmText = NSLOCTEXT("ModalDialogs", "SelectAllConfirm", "Select All"); Info.CancelText = NSLOCTEXT("ModalDialogs", "SelectAllCancel", "Cancel"); FSuppressableWarningDialog ManyActorsWarning(Info); if (ManyActorsWarning.ShowModal() == FSuppressableWarningDialog::Cancel) { return; } } if (bShowProgress) { GWarn->BeginSlowTask(LOCTEXT("BeginSelectAllActorsTaskStatusMessage", "Selecting All Actors"), true); } // Add all selected actors' layer name to the LayerArray. USelection* SelectedActors = GetSelectedActors(); SelectedActors->BeginBatchSelectOperation(); SelectedActors->Modify(); for (FActorIterator It(InWorld); It; ++It) { AActor* Actor = *It; if (!Actor->IsSelected() && !Actor->IsHiddenEd() && Actor->IsSelectable()) { SelectActor(Actor, 1, 0); } } // Iterate through all of the BSP models and select them if they are not hidden if (InWorld) { for (TArray::TConstIterator LevelIterator = InWorld->GetLevels().CreateConstIterator(); LevelIterator; ++LevelIterator) { UModel& CurLevelModel = *((*LevelIterator)->Model); for (TArray::TIterator SurfaceIterator(CurLevelModel.Surfs); SurfaceIterator; ++SurfaceIterator) { FBspSurf& CurSurface = *SurfaceIterator; if (!CurSurface.IsHiddenEd()) { CurLevelModel.ModifySurf(SurfaceIterator.GetIndex(), false); CurSurface.PolyFlags |= PF_Selected; } } } } SelectedActors->EndBatchSelectOperation(); NoteSelectionChange(); if (bShowProgress) { GWarn->EndSlowTask(); } } void UUnrealEdEngine::edactSelectInvert(UWorld* InWorld) { // If there are a lot of actors to process, pop up a warning "are you sure?" box int32 NumActors = InWorld->GetActorCount(); bool bShowProgress = false; if (NumActors >= EditorActorSelectionDefs::MaxActorsToSelectBeforeWarning) { bShowProgress = true; const FText ConfirmText = FText::Format(NSLOCTEXT("UnrealEd", "Warning_ManyActorsForInvertSelect", "There are {0} actors in the world. Are you sure you want to invert selection on them all?"), FText::AsNumber(NumActors)); FSuppressableWarningDialog::FSetupInfo Info(ConfirmText, NSLOCTEXT("UnrealEd", "Warning_ManyActors", "Warning: Many Actors"), "Warning_ManyActors"); Info.ConfirmText = NSLOCTEXT("ModalDialogs", "InvertSelectionConfirm", "Invert Selection"); Info.CancelText = NSLOCTEXT("ModalDialogs", "InvertSelectionCancel", "Cancel"); FSuppressableWarningDialog ManyActorsWarning(Info); if (ManyActorsWarning.ShowModal() == FSuppressableWarningDialog::Cancel) { return; } } if (bShowProgress) { GWarn->BeginSlowTask(LOCTEXT("BeginInvertingActorSelectionTaskMessage", "Inverting Selected Actors"), true); } USelection* SelectedActors = GetSelectedActors(); SelectedActors->BeginBatchSelectOperation(); SelectedActors->Modify(); // Iterate through all of the actors and select them if they are not currently selected (and not hidden) // or deselect them if they are currently selected // Turn off Grouping during this process to avoid double toggling of selected actors via group selection const bool bGroupingActiveSaved = UActorGroupingUtils::IsGroupingActive(); UActorGroupingUtils::SetGroupingActive(false); for (FActorIterator It(InWorld); It; ++It) { AActor* Actor = *It; if (!FActorEditorUtils::IsABuilderBrush(Actor) && !Actor->IsHiddenEd()) { SelectActor(Actor, !Actor->IsSelected(), false); } } // Restore bGroupingActive to its original value UActorGroupingUtils::SetGroupingActive(bGroupingActiveSaved); // Iterate through all of the BSP models and select them if they are not currently selected (and not hidden) // or deselect them if they are currently selected if (InWorld) { for (TArray::TConstIterator LevelIterator = InWorld->GetLevels().CreateConstIterator(); LevelIterator; ++LevelIterator) { UModel& CurLevelModel = *((*LevelIterator)->Model); for (TArray::TIterator SurfaceIterator(CurLevelModel.Surfs); SurfaceIterator; ++SurfaceIterator) { FBspSurf& CurSurface = *SurfaceIterator; if (!CurSurface.IsHiddenEd()) { CurLevelModel.ModifySurf(SurfaceIterator.GetIndex(), false); CurSurface.PolyFlags ^= PF_Selected; } } } } SelectedActors->EndBatchSelectOperation(); NoteSelectionChange(); if (bShowProgress) { GWarn->EndSlowTask(); } } static void GetAttachedActors(AActor* Actor, bool bRecurseChildren, TSet& OutActors) { TArray ChildrenActors; Actor->GetAttachedActors(ChildrenActors); for (AActor* ChildActor: ChildrenActors) { OutActors.Add(ChildActor); if (bRecurseChildren) { GetAttachedActors(ChildActor, bRecurseChildren, OutActors); } } } void UUnrealEdEngine::edactSelectAllChildren(bool bRecurseChildren) { USelection* CurrentSelection = GetSelectedActors(); TArray SelectedActors; CurrentSelection->GetSelectedObjects(SelectedActors); CurrentSelection->BeginBatchSelectOperation(); CurrentSelection->Modify(); // Turn off Grouping during this process to avoid double toggling of selected actors via group selection const bool bGroupingActiveSaved = UActorGroupingUtils::IsGroupingActive(); UActorGroupingUtils::SetGroupingActive(false); // Iterate through all the selected actors and select their children if they are not currently selected TSet ActorsToSelect; for (AActor* Actor: SelectedActors) { // Don't recurse through the same actor twice if (!bRecurseChildren || !ActorsToSelect.Contains(Actor)) { GetAttachedActors(Actor, bRecurseChildren, ActorsToSelect); } } for (AActor* Actor: ActorsToSelect) { if (!FActorEditorUtils::IsABuilderBrush(Actor) && !Actor->IsSelected()) { // Select actor even if hidden SelectActor(Actor, true, false, true); } } // Restore bGroupingActive to its original value UActorGroupingUtils::SetGroupingActive(bGroupingActiveSaved); CurrentSelection->EndBatchSelectOperation(); NoteSelectionChange(); } void UUnrealEdEngine::edactSelectOfClass(UWorld* InWorld, UClass* Class) { USelection* SelectedActors = GetSelectedActors(); SelectedActors->BeginBatchSelectOperation(); SelectedActors->Modify(); for (TActorIterator It(InWorld, Class); It; ++It) { AActor* Actor = *It; if (Actor->GetClass() == Class && !Actor->IsSelected() && !Actor->IsHiddenEd()) { // Selection by class not permitted for actors belonging to prefabs. // Selection by class not permitted for builder brushes. if (!FActorEditorUtils::IsABuilderBrush(Actor)) { SelectActor(Actor, 1, 0); } } } SelectedActors->EndBatchSelectOperation(); NoteSelectionChange(); } void UUnrealEdEngine::edactSelectOfClassAndArchetype(UWorld* InWorld, const TSubclassOf InClass, const UObject* InArchetype) { USelection* SelectedActors = GetSelectedActors(); SelectedActors->BeginBatchSelectOperation(); SelectedActors->Modify(); // Select all actors with of the provided class and archetype, assuming they aren't already selected, // aren't hidden in the editor, aren't a member of a prefab, and aren't builder brushes for (TActorIterator ActorIter(InWorld, InClass); ActorIter; ++ActorIter) { AActor* CurActor = *ActorIter; if (CurActor->GetClass() == InClass && CurActor->GetArchetype() == InArchetype && !CurActor->IsSelected() && !CurActor->IsHiddenEd() && !FActorEditorUtils::IsABuilderBrush(CurActor)) { SelectActor(CurActor, true, false); } } SelectedActors->EndBatchSelectOperation(); NoteSelectionChange(); } void UUnrealEdEngine::edactSelectSubclassOf(UWorld* InWorld, UClass* Class) { USelection* SelectedActors = GetSelectedActors(); SelectedActors->BeginBatchSelectOperation(); SelectedActors->Modify(); for (TActorIterator It(InWorld, Class); It; ++It) { AActor* Actor = *It; if (!Actor->IsSelected() && !Actor->IsHiddenEd()) { // Selection by class not permitted for actors belonging to prefabs. // Selection by class not permitted for builder brushes. if (!FActorEditorUtils::IsABuilderBrush(Actor)) { SelectActor(Actor, 1, 0); } } } SelectedActors->EndBatchSelectOperation(); NoteSelectionChange(); } void UUnrealEdEngine::edactSelectDeleted(UWorld* InWorld) { USelection* SelectedActors = GetSelectedActors(); SelectedActors->BeginBatchSelectOperation(); SelectedActors->Modify(); bool bSelectionChanged = false; for (FActorIterator It(InWorld); It; ++It) { AActor* Actor = *It; if (!Actor->IsSelected() && !Actor->IsHiddenEd()) { if (Actor->IsPendingKill()) { bSelectionChanged = true; SelectActor(Actor, 1, 0); } } } SelectedActors->EndBatchSelectOperation(); if (bSelectionChanged) { NoteSelectionChange(); } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // Select matching static meshes. // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// namespace { /** * Information about an actor and its static mesh. */ class FStaticMeshActor { public: /** Non-NULL if the actor is a static mesh. */ AStaticMeshActor* StaticMeshActor; /** Non-NULL if the actor has a static mesh. */ UStaticMesh* StaticMesh; FStaticMeshActor() : StaticMeshActor(NULL), StaticMesh(NULL) {} bool IsStaticMeshActor() const { return StaticMeshActor != NULL; } bool HasStaticMesh() const { return StaticMesh != NULL; } /** * Extracts the static mesh information from the specified actor. */ static bool GetStaticMeshInfoFromActor(AActor* Actor, FStaticMeshActor& OutStaticMeshActor) { OutStaticMeshActor.StaticMeshActor = Cast(Actor); if (OutStaticMeshActor.IsStaticMeshActor()) { if (OutStaticMeshActor.StaticMeshActor->GetStaticMeshComponent()) { OutStaticMeshActor.StaticMesh = OutStaticMeshActor.StaticMeshActor->GetStaticMeshComponent()->GetStaticMesh(); } } return OutStaticMeshActor.HasStaticMesh(); } }; } // namespace void UUnrealEdEngine::edactSelectMatchingStaticMesh(bool bAllClasses) { TArray StaticMeshActors; TArray SelectedWorlds; // Make a list of selected actors with static meshes. for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It) { AActor* Actor = static_cast(*It); checkSlow(Actor->IsA(AActor::StaticClass())); FStaticMeshActor ActorInfo; if (FStaticMeshActor::GetStaticMeshInfoFromActor(Actor, ActorInfo)) { if (ActorInfo.IsStaticMeshActor()) { StaticMeshActors.Add(ActorInfo); SelectedWorlds.AddUnique(Actor->GetWorld()); } } } if (SelectedWorlds.Num() == 0) { UE_LOG(LogEditorActor, Log, TEXT("No worlds found in edactSelectMatchingStaticMesh")); return; } // Make sure we have only 1 valid world check(SelectedWorlds.Num() == 1); USelection* SelectedActors = GetSelectedActors(); SelectedActors->BeginBatchSelectOperation(); SelectedActors->Modify(); // Loop through all non-hidden actors in visible levels, selecting those that have one of the // static meshes in the list. for (FActorIterator It(SelectedWorlds[0]); It; ++It) { AActor* Actor = *It; if (!Actor->IsHiddenEd()) { FStaticMeshActor ActorInfo; if (FStaticMeshActor::GetStaticMeshInfoFromActor(Actor, ActorInfo)) { bool bSelectActor = false; if (bAllClasses || ActorInfo.IsStaticMeshActor()) { for (int32 i = 0; i < StaticMeshActors.Num(); ++i) { if (StaticMeshActors[i].StaticMesh == ActorInfo.StaticMesh) { bSelectActor = true; break; } } } if (bSelectActor) { SelectActor(Actor, true, false); } } } } SelectedActors->EndBatchSelectOperation(); NoteSelectionChange(); } void UUnrealEdEngine::edactSelectMatchingSkeletalMesh(bool bAllClasses) { TArray SelectedMeshes; bool bSelectSkelMeshActors = false; bool bSelectPawns = false; TArray SelectedWorlds; // Make a list of skeletal meshes of selected actors, and note what classes we have selected. for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It) { AActor* Actor = static_cast(*It); checkSlow(Actor->IsA(AActor::StaticClass())); // Look for SkelMeshActor ASkeletalMeshActor* SkelMeshActor = Cast(Actor); if (SkelMeshActor && SkelMeshActor->GetSkeletalMeshComponent()) { bSelectSkelMeshActors = true; SelectedMeshes.AddUnique(SkelMeshActor->GetSkeletalMeshComponent()->SkeletalMesh); SelectedWorlds.AddUnique(Actor->GetWorld()); } // Look for Pawn APawn* Pawn = Cast(Actor); if (Pawn) { USkeletalMeshComponent* PawnSkeletalMesh = Pawn->FindComponentByClass(); if (PawnSkeletalMesh) { bSelectPawns = true; SelectedMeshes.AddUnique(PawnSkeletalMesh->SkeletalMesh); SelectedWorlds.AddUnique(Actor->GetWorld()); } } } if (SelectedWorlds.Num() == 0) { UE_LOG(LogEditorActor, Log, TEXT("No worlds found in edactSelectMatchingSkeletalMesh")); return; } // Make sure we have only 1 valid world check(SelectedWorlds.Num() == 1); // If desired, select all class types if (bAllClasses) { bSelectSkelMeshActors = true; bSelectPawns = true; } USelection* SelectedActors = GetSelectedActors(); SelectedActors->BeginBatchSelectOperation(); SelectedActors->Modify(); // Loop through all non-hidden actors in visible levels, selecting those that have one of the skeletal meshes in the list. for (FActorIterator It(SelectedWorlds[0]); It; ++It) { AActor* Actor = *It; if (!Actor->IsHiddenEd()) { bool bSelectActor = false; if (bSelectSkelMeshActors) { ASkeletalMeshActor* SkelMeshActor = Cast(Actor); if (SkelMeshActor && SkelMeshActor->GetSkeletalMeshComponent() && SelectedMeshes.Contains(SkelMeshActor->GetSkeletalMeshComponent()->SkeletalMesh)) { bSelectActor = true; } } if (bSelectPawns) { APawn* Pawn = Cast(Actor); if (Pawn) { USkeletalMeshComponent* PawnSkeletalMesh = Pawn->FindComponentByClass(); if (PawnSkeletalMesh && SelectedMeshes.Contains(PawnSkeletalMesh->SkeletalMesh)) { bSelectActor = true; } } } if (bSelectActor) { SelectActor(Actor, true, false); } } } SelectedActors->EndBatchSelectOperation(); NoteSelectionChange(); } void UUnrealEdEngine::edactSelectMatchingMaterial() { // Set for fast lookup of used materials. TSet MaterialsInSelection; TArray SelectedWorlds; // For each selected actor, find all the materials used by this actor. for (FSelectionIterator ActorItr(GetSelectedActorIterator()); ActorItr; ++ActorItr) { AActor* CurrentActor = Cast(*ActorItr); if (CurrentActor) { // Find the materials by iterating over every primitive component. for (UActorComponent* Component: CurrentActor->GetComponents()) { if (UPrimitiveComponent* CurrentComponent = Cast(Component)) { TArray UsedMaterials; CurrentComponent->GetUsedMaterials(UsedMaterials); MaterialsInSelection.Append(UsedMaterials); SelectedWorlds.AddUnique(CurrentActor->GetWorld()); } } } } if (SelectedWorlds.Num() == 0) { UE_LOG(LogEditorActor, Log, TEXT("No worlds found in edactSelectMatchingMaterial")); return; } // Make sure we have only 1 valid world check(SelectedWorlds.Num() == 1); USelection* SelectedActors = GetSelectedActors(); SelectedActors->BeginBatchSelectOperation(); SelectedActors->Modify(); // Now go over every actor and see if any of the actors are using any of the materials that // we found above. for (FActorIterator ActorIt(SelectedWorlds[0]); ActorIt; ++ActorIt) { AActor* Actor = *ActorIt; // Do not bother checking hidden actors if (!Actor->IsHiddenEd()) { TInlineComponentArray PrimitiveComponents; Actor->GetComponents(PrimitiveComponents); const int32 NumComponents = PrimitiveComponents.Num(); for (int32 ComponentIndex = 0; ComponentIndex < NumComponents; ++ComponentIndex) { UPrimitiveComponent* CurrentComponent = PrimitiveComponents[ComponentIndex]; TArray UsedMaterials; CurrentComponent->GetUsedMaterials(UsedMaterials); const int32 NumMaterials = UsedMaterials.Num(); // Iterate over every material we found so far and see if its in the list of materials used by selected actors. for (int32 MatIndex = 0; MatIndex < NumMaterials; ++MatIndex) { UMaterialInterface* Material = UsedMaterials[MatIndex]; // Is this material used by currently selected actors? if (MaterialsInSelection.Find(Material)) { SelectActor(Actor, true, false); // We dont need to continue searching as this actor has already been selected MatIndex = NumMaterials; ComponentIndex = NumComponents; } } } } } SelectedActors->EndBatchSelectOperation(); NoteSelectionChange(); } void UUnrealEdEngine::edactSelectMatchingEmitter() { TArray SelectedParticleSystemTemplates; TArray SelectedWorlds; // Check all of the currently selected actors to find the relevant particle system templates to use to match for (FSelectionIterator SelectedIterator(GetSelectedActorIterator()); SelectedIterator; ++SelectedIterator) { AActor* Actor = static_cast(*SelectedIterator); checkSlow(Actor->IsA(AActor::StaticClass())); AEmitter* Emitter = Cast(Actor); if (Emitter && Emitter->GetParticleSystemComponent() && Emitter->GetParticleSystemComponent()->Template) { SelectedParticleSystemTemplates.AddUnique(Emitter->GetParticleSystemComponent()->Template); SelectedWorlds.AddUnique(Actor->GetWorld()); } } if (SelectedWorlds.Num() == 0) { UE_LOG(LogEditorActor, Log, TEXT("No worlds found in edactSelectMatchingEmitter")); return; } // Make sure we have only 1 valid world check(SelectedWorlds.Num() == 1); USelection* SelectedActors = GetSelectedActors(); SelectedActors->BeginBatchSelectOperation(); SelectedActors->Modify(); // Iterate over all of the non-hidden actors, selecting those who have a particle system template that matches one from the previously-found list for (TActorIterator ActorIterator(SelectedWorlds[0]); ActorIterator; ++ActorIterator) { AEmitter* ActorAsEmitter = *ActorIterator; if (!ActorAsEmitter->IsHiddenEd()) { if (ActorAsEmitter->GetParticleSystemComponent() && SelectedParticleSystemTemplates.Contains(ActorAsEmitter->GetParticleSystemComponent()->Template)) { SelectActor(ActorAsEmitter, true, false); } } } SelectedActors->EndBatchSelectOperation(); NoteSelectionChange(); } void UUnrealEdEngine::edactSelectRelevantLights(UWorld* InWorld) { TArray RelevantLightList; // Make a list of selected actors with static meshes. for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It) { AActor* Actor = static_cast(*It); checkSlow(Actor->IsA(AActor::StaticClass())); if (Actor->GetLevel()->IsCurrentLevel()) { // Gather static lighting info from each of the actor's components. for (UActorComponent* Component: Actor->GetComponents()) { UPrimitiveComponent* Primitive = Cast(Component); if (Primitive && Primitive->IsRegistered()) { TArray RelevantLightComponents; InWorld->Scene->GetRelevantLights(Primitive, &RelevantLightComponents); for (int32 LightComponentIndex = 0; LightComponentIndex < RelevantLightComponents.Num(); LightComponentIndex++) { const ULightComponent* LightComponent = RelevantLightComponents[LightComponentIndex]; ALight* LightOwner = Cast(LightComponent->GetOwner()); if (LightOwner) { RelevantLightList.AddUnique(LightOwner); } } } } } } USelection* SelectedActors = GetSelectedActors(); SelectedActors->BeginBatchSelectOperation(); SelectedActors->Modify(); SelectNone(false, true); UE_LOG(LogEditorActor, Log, TEXT("Found %d relevant lights!"), RelevantLightList.Num()); for (int32 LightIdx = 0; LightIdx < RelevantLightList.Num(); LightIdx++) { ALight* Light = RelevantLightList[LightIdx]; if (Light) { SelectActor(Light, true, false); UE_LOG(LogEditorActor, Log, TEXT("\t%s"), *(Light->GetPathName())); } } SelectedActors->EndBatchSelectOperation(); NoteSelectionChange(); } void UUnrealEdEngine::edactAlignOrigin() { // Fires ULevel::LevelDirtiedEvent when falling out of scope. FScopedLevelDirtied LevelDirtyCallback; // Apply transformations to all selected brushes. for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It) { AActor* Actor = static_cast(*It); checkSlow(Actor->IsA(AActor::StaticClass())); ABrush* Brush = Cast(Actor); if (Brush) { LevelDirtyCallback.Request(); Brush->PreEditChange(NULL); Brush->Modify(); // Snap the location of the brush to the grid FVector BrushLocation = Brush->GetActorLocation(); BrushLocation.X = FMath::RoundToFloat(BrushLocation.X / GetGridSize()) * GetGridSize(); BrushLocation.Y = FMath::RoundToFloat(BrushLocation.Y / GetGridSize()) * GetGridSize(); BrushLocation.Z = FMath::RoundToFloat(BrushLocation.Z / GetGridSize()) * GetGridSize(); Brush->SetActorLocation(BrushLocation, false); // Update EditorMode locations to match the new brush location FEditorModeTools& Tools = GLevelEditorModeTools(); Tools.SetPivotLocation(Brush->GetActorLocation(), true); Brush->Brush->BuildBound(); Brush->PostEditChange(); } } } void UUnrealEdEngine::edactAlignVertices() { // Fires ULevel::LevelDirtiedEvent when falling out of scope. FScopedLevelDirtied LevelDirtyCallback; // Before aligning verts, align the origin with the grid edactAlignOrigin(); // Apply transformations to all selected brushes. for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It) { AActor* Actor = static_cast(*It); checkSlow(Actor->IsA(AActor::StaticClass())); ABrush* Brush = Cast(Actor); if (Brush) { LevelDirtyCallback.Request(); Brush->PreEditChange(NULL); Brush->Modify(); FVector BrushLocation = Brush->GetActorLocation(); const FTransform BrushTransform = Brush->GetRootComponent()->GetComponentTransform(); // Snap each vertex in the brush to an integer grid. UPolys* Polys = Brush->Brush->Polys; for (int32 PolyIdx = 0; PolyIdx < Polys->Element.Num(); PolyIdx++) { FPoly* Poly = &Polys->Element[PolyIdx]; for (int32 VertIdx = 0; VertIdx < Poly->Vertices.Num(); VertIdx++) { const float GridSize = GetGridSize(); // Snap each vertex to the nearest grid. const FVector Vertex = Poly->Vertices[VertIdx]; const FVector VertexWorld = BrushTransform.TransformPosition(Vertex); const FVector VertexSnapped(FMath::RoundToFloat(VertexWorld.X / GridSize) * GridSize, FMath::RoundToFloat(VertexWorld.Y / GridSize) * GridSize, FMath::RoundToFloat(VertexWorld.Z / GridSize) * GridSize); const FVector VertexSnappedLocal = BrushTransform.InverseTransformPosition(VertexSnapped); Poly->Vertices[VertIdx] = VertexSnappedLocal; } // If the snapping resulted in an off plane polygon, triangulate it to compensate. if (!Poly->IsCoplanar() || !Poly->IsConvex()) { FPoly BadPoly = *Poly; // Remove the bad poly Polys->Element.RemoveAt(PolyIdx); // Triangulate the bad poly TArray Triangles; if (BadPoly.Triangulate(Brush, Triangles) > 0) { // Add all new triangles to the brush for (int32 TriIdx = 0; TriIdx < Triangles.Num(); ++TriIdx) { Polys->Element.Add(Triangles[TriIdx]); } } PolyIdx = -1; } else { if (RecomputePoly(Brush, &Polys->Element[PolyIdx]) == -2) { PolyIdx = -1; } if (UBrushEditingSubsystem* BrushSubsystem = GEditor->GetEditorSubsystem()) { BrushSubsystem->UpdateGeometryFromBrush(Brush); } } } Brush->Brush->BuildBound(); Brush->PostEditChange(); } } } #undef LOCTEXT_NAMESPACE