// 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 "UnrealEdGlobals.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 "Engine/BrushBuilder.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 "Serialization/ObjectWriter.h" #include "Serialization/ObjectReader.h" #include "IAssetTools.h" #include "AssetToolsModule.h" #include "AssetSelection.h" #include "Framework/Application/SlateApplication.h" #include "Framework/Notifications/NotificationManager.h" #include "Widgets/Notifications/SNotificationList.h" #include "Elements/Framework/EngineElementsLibrary.h" #include "Elements/Framework/TypedElementSelectionSet.h" #include "EdMode.h" #include "Subsystems/BrushEditingSubsystem.h" #include "Misc/ScopedSlowTask.h" #include "Subsystems/AssetEditorSubsystem.h" #include "SceneInterface.h" #include "SDeleteReferencedActorDialog.h" #include "Elements/Interfaces/TypedElementDataStorageCompatibilityInterface.h" #include "Elements/Common/EditorDataStorageFeatures.h" #include "Elements/Common/EditorDataStorageFeatures.h" #include "Elements/Columns/TypedElementVisibilityColumns.h" #include "Elements/Columns/TypedElementMiscColumns.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 = FVector3f::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; GetSelectedComponents()->GetSelectedObjects(SelectedComponents); CopyComponents(SelectedComponents, DestinationData); } else { // Copy selected actors TArray SelectedActors; GetSelectedActors()->GetSelectedObjects(SelectedActors); CopyActors(SelectedActors, InWorld, DestinationData); } } void UUnrealEdEngine::CopyComponents(const TArray& InComponentsToCopy, FString* DestinationData) const { // Remove: // - Components that cannot be copied. TArray ComponentsToCopy = InComponentsToCopy; ComponentsToCopy.RemoveAll([](UActorComponent* InComponent) { return !FComponentEditorUtils::CanCopyComponent(InComponent); }); FComponentEditorUtils::CopyComponents(ComponentsToCopy, DestinationData); } void UUnrealEdEngine::CopyActors(const TArray& InActorsToCopy, UWorld* InWorld, FString* DestinationData) const { // Remove: // - Actors belonging to prefabs unless all actors in the prefab are selected. // - Builder brushes. // - World Settings. TArray ActorsToCopy = InActorsToCopy; { bool bSomeSelectedActorsNotInCurrentLevel = false; ActorsToCopy.RemoveAll([&bSomeSelectedActorsNotInCurrentLevel](AActor* InActor) { // Remove any selected builder brushes. ABrush* Brush = Cast(InActor); if (Brush && FActorEditorUtils::IsABuilderBrush(Brush)) { return true; } // Remove world settings if (InActor->IsA()) { return true; } // If any selected actors are not in the current level, warn the user that some actors will not be copied. if (!bSomeSelectedActorsNotInCurrentLevel && !InActor->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.")); } return false; }); } // Filter out other actors. OnFilterCopiedActors().Broadcast(ActorsToCopy); // Export the actors. FStringOutputDevice Ar; const FSelectedActorExportObjectInnerContext Context(ActorsToCopy); const bool bExportSucceeded = UExporter::ExportToOutputDevice(&Context, InWorld, NULL, Ar, TEXT("copy"), 0, PPF_DeepCompareInstances | PPF_ExportsNotFullyQualified); if (bExportSucceeded) { if (DestinationData) { *DestinationData = MoveTemp(Ar); } else { FPlatformApplicationMisc::ClipboardCopy(*Ar); } } } bool UUnrealEdEngine::WarnIfDestinationLevelIsHidden( UWorld* InWorld ) const { 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, const FString* SourceData) { if (GetSelectedComponentCount() > 0) { AActor* SelectedActor = CastChecked(*GetSelectedActorIterator()); TArray PastedComponents; PasteComponents(PastedComponents, SelectedActor, bWarnIfHidden, SourceData); if (PastedComponents.Num() > 0) { // Select the new clones USelection* ComponentSelection = GetSelectedComponents(); ComponentSelection->Modify(); ComponentSelection->BeginBatchSelectOperation(); ComponentSelection->DeselectAll(); for (UActorComponent* PastedComponent : PastedComponents) { GEditor->SelectComponent(PastedComponent, true, false, true); } ComponentSelection->EndBatchSelectOperation(true); } } else { TArray PastedActors; PasteActors(PastedActors, InWorld, bOffsetLocations ? GEditor->GetGridLocationOffset(/*bUniformOffset*/true) : FVector::ZeroVector, bDuplicate, bWarnIfHidden, SourceData); if (PastedActors.Num() > 0) { // Select the new clones USelection* ActorSelection = GetSelectedActors(); ActorSelection->Modify(); ActorSelection->BeginBatchSelectOperation(); ActorSelection->DeselectAll(); for (AActor* PastedActor : PastedActors) { GEditor->SelectActor(PastedActor, true, false, true); } ActorSelection->EndBatchSelectOperation(true); // Note the selection change. This will also redraw level viewports and update the pivot. NoteSelectionChange(); } } } void UUnrealEdEngine::PasteComponents(TArray& OutPastedComponents, AActor* TargetActor, const bool bWarnIfHidden, const FString* SourceData) { OutPastedComponents.Reset(); // Check and warn if the user is trying to paste to a hidden level. This will return if they wish to abort the process... if (bWarnIfHidden && WarnIfDestinationLevelIsHidden(TargetActor->GetWorld())) { return; } FComponentEditorUtils::PasteComponents(OutPastedComponents, TargetActor, TargetActor->GetRootComponent(), SourceData); if (OutPastedComponents.Num() > 0) { // Make sure all the SCS trees have a chance to update FLevelEditorModule& LevelEditor = FModuleManager::LoadModuleChecked("LevelEditor"); LevelEditor.BroadcastComponentsEdited(); } } void UUnrealEdEngine::PasteActors(TArray& OutPastedActors, UWorld* InWorld, const FVector& LocationOffset, bool bDuplicate, bool bWarnIfHidden, const FString* SourceData) { OutPastedActors.Reset(); // Check and warn if the user is trying to paste to a hidden level. This will return if they wish to abort the process... if (bWarnIfHidden && WarnIfDestinationLevelIsHidden(InWorld)) { return; } OnPasteActorsBeginDelegate.Broadcast(); ON_SCOPE_EXIT{ OnPasteActorsEndDelegate.Broadcast(OutPastedActors); }; const FScopedBusyCursor BusyCursor; FCachedActorLabels ActorLabels(InWorld); // Get pasted text. FString PasteString; if (SourceData) { PasteString = *SourceData; } else { FPlatformApplicationMisc::ClipboardPaste(PasteString); } const TCHAR* Paste = *PasteString; USelection* ActorSelection = GetSelectedActors(); // FactoryCreateText will mutate the selection, so cache it here so we can restore it later TArray OriginalSelectionState; FObjectWriter(ActorSelection, OriginalSelectionState); // 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. TStrongObjectPtr 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; // FactoryCreateText set the selection to the new actors, so copy that into OutPastedActors and restore the original selection ActorSelection->GetSelectedObjects(OutPastedActors); FObjectReader(ActorSelection, OriginalSelectionState); // 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(); bool bRebuildBSP = false; for (AActor* Actor : OutPastedActors) { if (!bRebuildBSP) { if (ABrush* Brush = Cast(Actor)) { bRebuildBSP = Brush->IsStaticBrush(); } } if (!LocationOffset.IsZero()) { // 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 AActor* ParentActor = Actor->GetAttachParentActor(); if (!ParentActor) { Actor->TeleportTo(Actor->GetActorLocation() + LocationOffset, Actor->GetActorRotation(), false, true); } else if(!OutPastedActors.Contains(ParentActor)) { FName SocketName = Actor->GetAttachParentSocketName(); Actor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); Actor->TeleportTo(Actor->GetActorLocation() + LocationOffset, Actor->GetActorRotation(), false, true); Actor->AttachToActor(ParentActor, FAttachmentTransformRules::KeepWorldTransform, SocketName); } } if (!GetDefault()->bAvoidRelabelOnPasteSelected) { // Re-label duplicated actors so that labels become unique FActorLabelUtilities::SetActorLabelUnique(Actor, Actor->GetActorLabel(), &ActorLabels); ActorLabels.Add(Actor->GetActorLabel()); } else { // Make sure to broadcast label change after actor import as it may change between spawn and import FCoreDelegates::OnActorLabelChanged.Broadcast(Actor); } 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(); } if (bRebuildBSP) { RebuildAlteredBSP(); } } namespace { /** * 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 @see PasteActors. */ void DuplicateActorsToLevel(TArray& OutNewActors, ULevel* DestLevel, const FVector& LocationOffset) { // 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; } FString ScratchData; // Copy actors from src level { // Cache the current source level ULevel* CurrentSrcLevel = SrcLevel->OwningWorld->GetCurrentLevel(); SrcLevel->OwningWorld->SetCurrentLevel(SrcLevel); GUnrealEd->CopyActors(Actors, SrcLevel->OwningWorld, &ScratchData); SrcLevel->OwningWorld->SetCurrentLevel(CurrentSrcLevel); } // Paste to the dest level { ULevel* CurrentDestLevel = DestLevel->OwningWorld->GetCurrentLevel(); DestLevel->OwningWorld->SetCurrentLevel(DestLevel); TArray PastedActors; GUnrealEd->PasteActors(PastedActors, DestLevel->OwningWorld, LocationOffset, /*bDuplicate*/true, /*bWarnIfHidden*/true, &ScratchData); DestLevel->OwningWorld->SetCurrentLevel(CurrentDestLevel); // We generate new seeds when we duplicate for (AActor* Actor : PastedActors) { Actor->SeedAllRandomStreams(); } OutNewActors.Append(MoveTemp(PastedActors)); } } }; } void UUnrealEdEngine::edactDuplicateSelected( ULevel* InLevel, bool bOffsetLocations ) { if (GetSelectedComponentCount() > 0) { USelection* ComponentSelection = GetSelectedComponents(); TArray SelectedComponents; ComponentSelection->GetSelectedObjects(SelectedComponents); TArray NewComponents; DuplicateComponents(SelectedComponents, NewComponents); if (NewComponents.Num() > 0) { // Select the new clones ComponentSelection->Modify(); ComponentSelection->BeginBatchSelectOperation(); ComponentSelection->DeselectAll(); for (UActorComponent* NewComponent : NewComponents) { GEditor->SelectComponent(NewComponent, true, false); } ComponentSelection->EndBatchSelectOperation(true); } } else { USelection* ActorSelection = GetSelectedActors(); TArray SelectedActors; ActorSelection->GetSelectedObjects(SelectedActors); TArray NewActors; DuplicateActors(SelectedActors, NewActors, InLevel, bOffsetLocations ? GEditor->GetGridLocationOffset(/*bUniformOffset*/false) : FVector::ZeroVector); if (NewActors.Num() > 0) { // Select the new clones ActorSelection->Modify(); ActorSelection->BeginBatchSelectOperation(); ActorSelection->DeselectAll(); for (AActor* Actor : NewActors) { GEditor->SelectActor(Actor, true, false); } ActorSelection->EndBatchSelectOperation(true); // Note the selection change. This will also redraw level viewports and update the pivot. NoteSelectionChange(); GLevelEditorModeTools().ActorsDuplicatedNotify(SelectedActors, NewActors, bOffsetLocations); } } } void UUnrealEdEngine::DuplicateComponents(const TArray& InComponentsToDuplicate, TArray& OutNewComponents) { OutNewComponents.Reset(InComponentsToDuplicate.Num()); for (UActorComponent* Component : InComponentsToDuplicate) { if (FComponentEditorUtils::CanCopyComponent(Component)) { if (UActorComponent* NewComponent = FComponentEditorUtils::DuplicateComponent(Component)) { OutNewComponents.Add(NewComponent); } } } if (OutNewComponents.Num() > 0) { // Make sure all the SCS trees have a chance to update FLevelEditorModule& LevelEditor = FModuleManager::LoadModuleChecked("LevelEditor"); LevelEditor.BroadcastComponentsEdited(); } } void UUnrealEdEngine::DuplicateActors(const TArray& InActorsToDuplicate, TArray& OutNewActors, ULevel* InLevel, const FVector& LocationOffset) { OutNewActors.Reset(InActorsToDuplicate.Num()); const FScopedBusyCursor BusyCursor; // Create per-level job lists. TMap> DuplicateJobs; for (AActor* Actor : InActorsToDuplicate) { ULevel* SrcLevel = Actor->GetLevel(); TSharedPtr Job = DuplicateJobs.FindRef(SrcLevel); if (!Job) { // Allocate a new job for the level. Job = DuplicateJobs.Add(SrcLevel, MakeShared()); Job->SrcLevel = SrcLevel; } Job->Actors.Add(Actor); } // For each level, select the actors in that level and copy-paste into the destination level. for (const auto& DuplicateJobPair : DuplicateJobs) { FDuplicateJob& Job = *DuplicateJobPair.Value; Job.DuplicateActorsToLevel(OutNewActors, InLevel, LocationOffset); } // Finally, cleanup. DuplicateJobs.Reset(); } bool UUnrealEdEngine::CanDeleteSelectedActors( const UWorld* InWorld, const bool bStopAtFirst, const bool bLogUndeletable, TArray* OutDeletableActors ) const { // 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()) ); bool bDeletable = false; FText CannotDeleteReason; if (CanDeleteActor(Actor, &CannotDeleteReason)) { 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("CannotDeleteActor", "Cannot delete 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::CanDeleteComponent(const UActorComponent* InComponent, FText* OutReason) const { if (!InComponent->IsEditableWhenInherited()) { if (OutReason) { *OutReason = LOCTEXT("CanDeleteComponent_Error_ComponentNotEditableWhenInherited", "Can't delete non-editable component."); } return false; } if (!FComponentEditorUtils::CanDeleteComponent(InComponent)) { if (OutReason) { *OutReason = LOCTEXT("CanDeleteComponent_Error_InvalidComponent", "Can't delete non-instanced components or the scene root."); } return false; } return true; } bool UUnrealEdEngine::CanDeleteActor(const AActor* InActor, FText* OutReason) const { if (!InActor->HasAllFlags(RF_Transactional)) { if (OutReason) { *OutReason = LOCTEXT("CanDeleteActor_Error_NotTransactional", "Can't delete non-transactional actors."); } return false; } { FText TmpReason; if (!InActor->CanDeleteSelectedActor(TmpReason)) { if (OutReason) { *OutReason = TmpReason; } return false; } } return true; } bool UUnrealEdEngine::ShouldAbortComponentDeletion(const TArray& InComponentsToDelete, FText* OutReason) const { for (UActorComponent* Component : InComponentsToDelete) { AActor* OwnerActor = Component->GetOwner(); if (OwnerActor && FLevelUtils::IsLevelLocked(OwnerActor)) { if (OutReason) { *OutReason = FText::Format(LOCTEXT("ShouldAbortComponentDeletion_Error_LevelLockedDuringComponentDeletion", "Cannot delete component '{0}' because its owner level is locked."), FText::FromString(Component->GetPathName())); } return true; } } return false; } bool UUnrealEdEngine::ShouldAbortActorDeletion(const TArray& InActorsToDelete, FText* OutReason) const { for (AActor* Actor : InActorsToDelete) { if (FLevelUtils::IsLevelLocked(Actor)) { if (OutReason) { *OutReason = FText::Format(LOCTEXT("ShouldAbortActorDeletion_LevelLockedDuringActorDeletion", "Cannot delete actor '{0}' because its owner level is locked."), FText::FromString(Actor->GetPathName())); } return true; } } return false; } bool UUnrealEdEngine::DeleteComponents(const TArray& InComponentsToDelete, UTypedElementSelectionSet* InSelectionSet, const bool bVerifyDeletionCanHappen) { if (bVerifyDeletionCanHappen) { FText ErrorMsg; if (ShouldAbortComponentDeletion(InComponentsToDelete, &ErrorMsg)) { FSlateNotificationManager::Get().AddNotification(FNotificationInfo(ErrorMsg)); return false; } } const double StartSeconds = FPlatformTime::Seconds(); FSlateApplication::Get().CancelDragDrop(); // Get a list of all the deletable components TArray EditableComponentsToDelete; EditableComponentsToDelete.Reserve(InComponentsToDelete.Num()); for (UActorComponent* ComponentToDelete : InComponentsToDelete) { FText ErrorMsg; if (!CanDeleteComponent(ComponentToDelete, &ErrorMsg)) { UE_LOG(LogEditorActor, Log, TEXT("Cannot delete component %s: %s"), *ComponentToDelete->GetFullName(), *ErrorMsg.ToString()); continue; } // Modify the actor that owns the component if (AActor* OwnerActor = ComponentToDelete->GetOwner()) { OwnerActor->Modify(); } EditableComponentsToDelete.Add(ComponentToDelete); } if (EditableComponentsToDelete.Num() == 0) { return false; } TUniquePtr LegacySyncBatch = MakeUnique(*InSelectionSet->GetElementList()); const FTypedElementSelectionOptions SelectionOptions = FTypedElementSelectionOptions() .SetAllowHidden(true) .SetAllowGroups(false) .SetWarnIfLocked(false) .SetChildElementInclusionMethod(ETypedElementChildInclusionMethod::Recursive); // Clear the selection of any components we may delete for (UActorComponent* ComponentToDelete : EditableComponentsToDelete) { if (FTypedElementHandle ComponentHandle = UEngineElementsLibrary::AcquireEditorComponentElementHandle(ComponentToDelete, /*bAllowCreate*/false)) { InSelectionSet->DeselectElement(ComponentHandle, SelectionOptions); } } // Delete the components UActorComponent* ComponentToSelect = nullptr; const int32 NumDeletedComponents = FComponentEditorUtils::DeleteComponents(EditableComponentsToDelete, ComponentToSelect); if (NumDeletedComponents > 0) { // Make sure all the SCS trees have a chance to rebuild FLevelEditorModule& LevelEditor = FModuleManager::LoadModuleChecked("LevelEditor"); LevelEditor.BroadcastComponentsEdited(); // Remove all references to destroyed components once at the end, instead of once for each component destroyed CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); } // Update the editor component selection if possible if (ComponentToSelect) { InSelectionSet->SelectElement(UEngineElementsLibrary::AcquireEditorComponentElementHandle(ComponentToSelect), SelectionOptions); } // Make sure the selection changed event fires so the SCS trees can update their selection LegacySyncBatch->ForceDirty(); LegacySyncBatch.Reset(); NoteSelectionChange(); UE_LOG(LogEditorActor, Log, TEXT("Deleted %d Components (%3.3f secs)"), NumDeletedComponents, FPlatformTime::Seconds() - StartSeconds); return NumDeletedComponents > 0; } bool UUnrealEdEngine::DeleteActors(const TArray& InActorsToDelete, UWorld* InWorld, UTypedElementSelectionSet* InSelectionSet, const bool bVerifyDeletionCanHappen, const bool bWarnAboutReferences, const bool bWarnAboutSoftReferences) { TRACE_CPUPROFILER_EVENT_SCOPE(UUnrealEdEngine::DeleteActors); if (bVerifyDeletionCanHappen) { // TODO: Bubble up error message? FText ErrorMsg; if (ShouldAbortActorDeletion(InActorsToDelete, &ErrorMsg)) { FSlateNotificationManager::Get().AddNotification(FNotificationInfo(ErrorMsg)); 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; // Fire ULevel::LevelDirtiedEvent when falling out of scope. FScopedLevelDirtied LevelDirtyCallback; // Get a list of all the deletable actors TArray ActorsToDelete; TArray ActorsToDeletePaths; ActorsToDelete.Reserve(InActorsToDelete.Num()); ActorsToDeletePaths.Reserve(InActorsToDelete.Num()); for (AActor* ActorToDelete : InActorsToDelete) { FText ErrorMsg; if (!CanDeleteActor(ActorToDelete, &ErrorMsg)) { // If this was a temporary editor preview actor, go ahead and delete it now if (ActorToDelete->bIsEditorPreviewActor) { InWorld->DestroyActor(ActorToDelete); } else { UE_LOG(LogEditorActor, Log, TEXT("Cannot delete actor %s: %s"), *ActorToDelete->GetFullName(), *ErrorMsg.ToString()); } continue; } ActorsToDelete.Add(ActorToDelete); ActorsToDeletePaths.Add(ActorToDelete); } TMap> ReferencingActorsMap; TMap> SoftReferencingObjectsMap; { TArray ClassTypesToIgnore; ClassesToIgnoreDeleteReferenceWarning.AddUnique(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. ClassesToIgnoreDeleteReferenceWarning.AddUnique(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,MutableView(ClassesToIgnoreDeleteReferenceWarning), ReferencingActorsMap); if (bWarnAboutSoftReferences) { FScopedSlowTask SlowTask(static_cast(ActorsToDeletePaths.Num()), LOCTEXT("ComputeActorSoftReferences", "Computing References")); SlowTask.MakeDialogDelayed(1.0f); FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked(TEXT("AssetTools")); AssetToolsModule.Get().FindSoftReferencesToObjects(ActorsToDeletePaths, SoftReferencingObjectsMap); } } } // Maintain a list of levels that have already been Modify()'d so that each level is modified 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; bool bRequestedDeleteAllByGroup = false; int32 DeleteCount = 0; TUniquePtr LegacySyncBatch = MakeUnique(*InSelectionSet->GetElementList()); const FTypedElementSelectionOptions SelectionOptions = FTypedElementSelectionOptions() .SetAllowHidden(true) .SetAllowGroups(false) .SetWarnIfLocked(false) .SetChildElementInclusionMethod(ETypedElementChildInclusionMethod::Recursive); ULayersSubsystem* LayersSubsystem = GEditor->GetEditorSubsystem(); for (AActor* Actor : ActorsToDelete) { //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 bReferencedByGroupActor = false; bool bReferencedBySoftReference = false; TArray* SoftReferencingObjects = nullptr; const UPackage* ActorPackage = Actor->GetPackage(); if (bWarnAboutSoftReferences) { SoftReferencingObjects = SoftReferencingObjectsMap.Find(Actor); if (SoftReferencingObjects) { // Remove any references from object types marked to be ignored for (int32 i = SoftReferencingObjects->Num() - 1; i >= 0; --i) { const UObject* SoftReferencingObject = (*SoftReferencingObjects)[i]; if (const AActor* ReferencingActor = Cast(SoftReferencingObject); ReferencingActor && ActorsToDelete.Contains(ReferencingActor)) { SoftReferencingObjects->RemoveAt(i); } // Object is inside the actor being deleted, so reference should be ignored (check that they share the same package because that could mean we are in a special case where warnings are needed) else if (SoftReferencingObject->IsInOuter(Actor) && SoftReferencingObject->GetPackage() == ActorPackage) { SoftReferencingObjects->RemoveAt(i); } else { for (const TObjectPtr& ClassToIgnore : ClassesToIgnoreDeleteReferenceWarning) { if (SoftReferencingObject->IsA(ClassToIgnore)) { SoftReferencingObjects->RemoveAt(i); break; } } } } bReferencedBySoftReference = SoftReferencingObjects->Num() > 0; } } // 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 || ActorsToDelete.Contains(ReferencingActor)) { continue; } else if (Cast(ReferencingActor)) { bReferencedByLODActor = true; } else if (Cast(ReferencingActor)) { bReferencedByGroupActor = 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 || bReferencedByGroupActor) { if ((bReferencedByLevelScript && !bRequestedDeleteAllByLevel) || (bReferencedByActor && !bRequestedDeleteAllByActor) || (bReferencedBySoftReference && !bRequestedDeleteAllBySoftReference) || (bReferencedByGroupActor && !bRequestedDeleteAllByGroup)) { FText ConfirmDelete; // We separate groups and other actors, so we can visually display them separately in the list TArray> GroupActorReferencers; TArray> OtherReferencers; if (ReferencingActors != nullptr) { for (AActor* ReferencingActor : *ReferencingActors) { if (ReferencingActor->IsA()) { continue; } const FString ActorLabel = FString::Printf(TEXT("%s"), *ReferencingActor->GetActorLabel()); if (ReferencingActor->IsA()) { GroupActorReferencers.Add(MakeShared(FText::FromString(ActorLabel))); } else if (!ReferencingActor->IsA()) { OtherReferencers.Add(MakeShared(FText::FromString(ActorLabel))); } } } OtherReferencers.Append(GroupActorReferencers); if (bReferencedBySoftReference) { for (UObject* ReferencingObject : *SoftReferencingObjects) { FString ActorLabel; if (AActor* ReferencingActor = Cast(ReferencingObject)) { ActorLabel = FString::Printf(TEXT("%s in %s (soft reference)"), *ReferencingActor->GetActorLabel(), *FPackageName::GetLongPackageAssetName(ReferencingActor->GetOutermost()->GetName())); } else { ActorLabel = FString::Printf(TEXT("%s (soft reference)"), *ReferencingObject->GetPathName()); } OtherReferencers.Add(MakeShared(FText::FromString(ActorLabel))); } } EDeletedActorReferenceTypes DeletedActorReferenceTypes = EDeletedActorReferenceTypes::None; if (bReferencedByGroupActor) { EnumAddFlags(DeletedActorReferenceTypes, EDeletedActorReferenceTypes::Group); } if (bReferencedByActor || bReferencedBySoftReference) { EnumAddFlags(DeletedActorReferenceTypes, EDeletedActorReferenceTypes::ActorOrAsset); } if (bReferencedByLevelScript) { EnumAddFlags(DeletedActorReferenceTypes, EDeletedActorReferenceTypes::LevelBlueprint); } const double DialogStartSeconds = FPlatformTime::Seconds(); const bool bCanShowApplyToAll = ActorsToDelete.Num() > 1; TSharedRef ConfirmDialog = SNew(SDeleteReferencedActorDialog) .Referencers(OtherReferencers) .ShowApplyToAll(bCanShowApplyToAll) .ActorToDeleteLabel(Actor->GetActorLabel()) .ReferenceTypes(DeletedActorReferenceTypes); uint32 Result = ConfirmDialog->ShowModal(); DialogWaitingSeconds += FPlatformTime::Seconds() - DialogStartSeconds; if (ConfirmDialog->GetApplyToAll()) { if (Result == 0) { bRequestedDeleteAllByLevel |= bReferencedByLevelScript; bRequestedDeleteAllByActor |= bReferencedByActor; bRequestedDeleteAllBySoftReference |= bReferencedBySoftReference; bRequestedDeleteAllByGroup |= bReferencedByGroupActor; } else { break; } } else if (Result == 1) { continue; } } if (bReferencedByLevelScript) { FBlueprintEditorUtils::ModifyActorReferencedGraphNodes(LSB, Actor); } if (bReferencedByActor || bReferencedByLODActor || bReferencedByGroupActor) { check(ReferencingActors != nullptr); for (AActor* ReferencingActor : *ReferencingActors) { 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< ABrush >(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(false); 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. if (FTypedElementHandle ActorHandle = UEngineElementsLibrary::AcquireEditorActorElementHandle(Actor, /*bAllowCreate*/false)) { InSelectionSet->DeselectElement(ActorHandle, SelectionOptions); } UEngineElementsLibrary::UnregisterActorElement(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 a transient actor, or an external actor when the level is in `use external actors` mode. bool bShouldDirty = !Actor->HasAllFlags(RF_Transient) && (!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); LegacySyncBatch.Reset(); NoteSelectionChange(); // If any brush actors were modified, update the Bsp in the appropriate levels if (LevelsToRebuildBSP.Num()) { FlushRenderingCommands(); for (ULevel* Level : LevelsToRebuildBSP) { GEditor->RebuildLevel(*Level); } } 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::edactDeleteSelected( UWorld* InWorld, bool bVerifyDeletionCanHappen, bool bWarnAboutReferences, bool bWarnAboutSoftReferences) { if (GetSelectedComponentCount() > 0) { // Delete selected components TArray SelectedComponents; GetSelectedComponents()->GetSelectedObjects(SelectedComponents); return DeleteComponents(SelectedComponents, GetSelectedComponents()->GetElementSelectionSet(), bVerifyDeletionCanHappen); } else { // Delete selected actors TArray SelectedActors; GetSelectedActors()->GetSelectedObjects(SelectedActors); return DeleteActors(SelectedActors, InWorld, GetSelectedActors()->GetElementSelectionSet(), bVerifyDeletionCanHappen, bWarnAboutReferences, bWarnAboutSoftReferences); } } bool UUnrealEdEngine::ShouldAbortActorDeletion() const { TArray SelectedActors; GetSelectedActors()->GetSelectedObjects(SelectedActors); FText ErrorMsg; if (ShouldAbortActorDeletion(SelectedActors, &ErrorMsg)) { FSlateNotificationManager::Get().AddNotification(FNotificationInfo(ErrorMsg)); return true; } return false; } 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< ABrush >( 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 ) { if( NewBrush->GetBrushBuilder() ) { FActorLabelUtilities::SetActorLabelUnique(NewBrush, FText::Format(NSLOCTEXT("UnrealEd", "BrushName", "{0} Brush"), FText::FromString(NewBrush->GetBrushBuilder()->GetClass()->GetDescription())).ToString()); } SrcBrush->MarkPackageDirty(); NewBrush->MarkPackageDirty(); LevelDirtyCallback.Request(); NewBrush->Modify(false); 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->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< ABrush >( 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(); } static void SetActorVisibility(AActor* Actor, bool bVisible) { using namespace UE::Editor::DataStorage; // 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); if (const ICompatibilityProvider* DataStoreCompat = GetDataStorageFeature(CompatibilityFeatureName)) { RowHandle Row = DataStoreCompat->FindRowWithCompatibleObject(Actor); if (ICoreProvider* DataStore = GetMutableDataStorageFeature(StorageFeatureName)) { FVisibleInEditorColumn* VisibilityColumn = DataStore->GetColumn(Row); if (VisibilityColumn) { VisibilityColumn->bIsVisibleInEditor = bVisible; } DataStore->AddColumn(Row); } } } 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 ]; SetActorVisibility(Actor, false); 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->IsActorOrSelectionParentSelected() && !Actor->IsHiddenEd() ) { SetActorVisibility(Actor, false); } } // 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() ) { SetActorVisibility(Actor, true); } } // 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->IsActorOrSelectionParentSelected() || (CurSurface.PolyFlags & PF_Selected); if (bSelected && !CurSurface.IsHiddenEdAtStartup() && !CurSurface.IsHiddenEd()) { CurLevelModel.Modify(false); 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(false); 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->IsActorOrSelectionParentSelected() || (CurSurface.PolyFlags & PF_Selected); if (bSelected && CurSurface.IsHiddenEdAtStartup()) { CurLevelModel.Modify(false); 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->IsActorOrSelectionParentSelected() && 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 ]; SetActorVisibility(Actor, true); } } // 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; ChildIdxGetActorCount(); 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< AActor* >& OutActors ) { TArray< AActor* > 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< AActor* > SelectedActors; CurrentSelection->GetSelectedObjects< AActor >( 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< AActor* > 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( !IsValid(Actor) ) { 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()->GetSkeletalMeshAsset()); SelectedWorlds.AddUnique(Actor->GetWorld()); } // Look for Pawn APawn* Pawn = Cast(Actor); if (Pawn) { USkeletalMeshComponent* PawnSkeletalMesh = Pawn->FindComponentByClass(); if (PawnSkeletalMesh) { bSelectPawns = true; SelectedMeshes.AddUnique(PawnSkeletalMesh->GetSkeletalMeshAsset()); 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()->GetSkeletalMeshAsset()) ) { bSelectActor = true; } } if(bSelectPawns) { APawn* Pawn = Cast(Actor); if (Pawn) { USkeletalMeshComponent* PawnSkeletalMesh = Pawn->FindComponentByClass(); if (PawnSkeletalMesh && SelectedMeshes.Contains(PawnSkeletalMesh->GetSkeletalMeshAsset()) ) { 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< ABrush >( Actor ); if ( Brush ) { LevelDirtyCallback.Request(); Brush->PreEditChange(NULL); Brush->Modify(false); //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< ABrush >( Actor ); if ( Brush ) { LevelDirtyCallback.Request(); Brush->PreEditChange(NULL); Brush->Modify(false); 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; PolyIdxElement.Num(); PolyIdx++ ) { FPoly* Poly = &Polys->Element[PolyIdx]; for( int32 VertIdx=0; VertIdxVertices.Num(); VertIdx++ ) { const float GridSize = GetGridSize(); // Snap each vertex to the nearest grid. const FVector3f Vertex = Poly->Vertices[VertIdx]; const FVector VertexWorld = BrushTransform.TransformPosition((FVector)Vertex); const FVector3f VertexSnapped(FMath::RoundToFloat(VertexWorld.X / GridSize) * GridSize, FMath::RoundToFloat(VertexWorld.Y / GridSize) * GridSize, FMath::RoundToFloat(VertexWorld.Z / GridSize) * GridSize); const FVector VertexSnappedLocal = BrushTransform.InverseTransformPosition((FVector)VertexSnapped); Poly->Vertices[VertIdx] = (FVector3f)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