// Copyright Epic Games, Inc. All Rights Reserved. #include "Editor/GroupActor.h" #include "Misc/MessageDialog.h" #include "Editor/UnrealEdEngine.h" #include "Engine/Selection.h" #include "EditorModeManager.h" #include "EditorModes.h" #include "UnrealEdGlobals.h" #include "SceneInterface.h" #include "SceneView.h" #include "ScopedTransaction.h" #include "LevelEditorViewport.h" #include "Layers/LayersSubsystem.h" #include "ActorGroupingUtils.h" #include "Elements/Framework/EngineElementsLibrary.h" #include "Elements/Framework/TypedElementSelectionSet.h" const FLinearColor BOXCOLOR_LOCKEDGROUPS( 0.0f, 1.0f, 0.0f ); const FLinearColor BOXCOLOR_UNLOCKEDGROUPS( 1.0f, 0.0f, 0.0f ); AGroupActor::AGroupActor(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { bLocked = true; USceneComponent* GroupComponent = CreateDefaultSubobject(TEXT("GroupComponent")); RootComponent = GroupComponent; } void AGroupActor::PostActorCreated() { // Cache our newly created group if( !GetWorld()->IsPlayInEditor() && !IsRunningCommandlet() && GIsEditor ) { GetWorld()->ActiveGroupActors.AddUnique(this); } Super::PostActorCreated(); } void AGroupActor::PostLoad() { GetLevel()->ConditionalPostLoad(); if( !GetWorld()->IsPlayInEditor() && !IsRunningCommandlet() && GIsEditor ) { // Cache group on de-serialization GetWorld()->ActiveGroupActors.AddUnique(this); // Fix up references for GetParentForActor() for(int32 i=0; iGroupActor = this; } } } Super::PostLoad(); } void AGroupActor::PostEditChangeProperty( FPropertyChangedEvent& PropertyChangedEvent ) { // Re-instate group as active if it had children after undo/redo if(GroupActors.Num() || SubGroups.Num()) { GetWorld()->ActiveGroupActors.AddUnique(this); } else // Otherwise, attempt to remove them { GetWorld()->ActiveGroupActors.Remove(this); } Super::PostEditChangeProperty(PropertyChangedEvent); } void AGroupActor::PostEditUndo() { Super::PostEditUndo(); if (!IsValidChecked(this)) { GetWorld()->ActiveGroupActors.RemoveSwap(this); } else { // Cache group on de-serialization GetWorld()->ActiveGroupActors.AddUnique(this); // Fix up references for GetParentForActor() for (int32 i = 0; i < GroupActors.Num(); ++i) { if (GroupActors[i] != NULL) { GroupActors[i]->GroupActor = this; } } } } bool AGroupActor::IsSelected() const { // Group actors can only count as 'selected' if they are locked return (IsLocked() && HasSelectedActors()) || Super::IsSelected(); } void AGroupActor::ForEachActorInGroup(TFunctionRef InCallback) { for (AActor* Actor : GroupActors) { if (Actor) { InCallback(Actor, this); } } for (AGroupActor* SubGroup : SubGroups) { if (SubGroup) { SubGroup->ForEachActorInGroup(InCallback); } } InCallback(this, this); } namespace GroupActorHelpers { bool ActorHasParentInGroup(const AActor* Actor, const AGroupActor* GroupActor) { check(Actor && GroupActor); // Check that we've not got a parent attachment within the group. if (USceneComponent* RootComponent = Actor->GetRootComponent()) { for (const AActor* OtherActor : GroupActor->GroupActors) { if (OtherActor && OtherActor != Actor) { USceneComponent* OtherRootComponent = OtherActor->GetRootComponent(); if (OtherRootComponent && RootComponent->IsAttachedTo(OtherRootComponent)) { // We do have parent so don't apply the delta - our parent object will apply it instead. return true; } } } } return false; } bool ActorHasParentInSelection(const AActor* Actor, FTypedElementListConstRef SelectionSet) { check(Actor); for (const AActor* ParentActor = Actor->GetAttachParentActor(); ParentActor; ParentActor = ParentActor->GetAttachParentActor()) { FTypedElementHandle ParentActorElementHandle = UEngineElementsLibrary::AcquireEditorActorElementHandle(ParentActor, /*bAllowCreate*/false); if (ParentActorElementHandle && SelectionSet->Contains(ParentActorElementHandle)) { return true; } } return false; } } // namespace GroupActorHelpers void AGroupActor::ForEachMovableActorInGroup(const UTypedElementSelectionSet* InSelectionSet, TFunctionRef InCallback) { const UTypedElementSelectionSet* SelectionSet = InSelectionSet ? InSelectionSet : GEditor->GetSelectedActors()->GetElementSelectionSet(); for (AActor* Actor : GroupActors) { if (Actor) { // Check that we've not got a parent attachment within the group/selection const bool bCanApplyDelta = !GroupActorHelpers::ActorHasParentInGroup(Actor, this) && !GroupActorHelpers::ActorHasParentInSelection(Actor, SelectionSet->GetElementList()); if (bCanApplyDelta) { InCallback(Actor, this); } } } for (AGroupActor* SubGroup : SubGroups) { if (SubGroup) { SubGroup->ForEachMovableActorInGroup(SelectionSet, InCallback); } } InCallback(this, this); } void AGroupActor::GroupApplyDelta(const FVector& InDrag, const FRotator& InRot, const FVector& InScale ) { ForEachMovableActorInGroup(nullptr, [&InDrag, &InRot, &InScale](AActor* InGroupedActor, AGroupActor* InGroupActor) { GEditor->ApplyDeltaToActor(InGroupActor, true, &InDrag, &InRot, &InScale); }); } void AGroupActor::SetIsTemporarilyHiddenInEditor( bool bIsHidden ) { Super::SetIsTemporarilyHiddenInEditor( bIsHidden ); for(int32 ActorIndex=0; ActorIndexSetIsTemporarilyHiddenInEditor(bIsHidden); } } for(int32 SubGroupIndex=0; SubGroupIndexSetIsTemporarilyHiddenInEditor(bIsHidden); } } } void AGroupActor::GetActorBounds(bool bOnlyCollidingComponents, FVector& Origin, FVector& BoxExtent, bool bIncludeFromChildActors) const { FBox Bounds = GetComponentsBoundingBox(!bOnlyCollidingComponents);; for(int32 ActorIndex=0; ActorIndexGetActorBounds(bOnlyCollidingComponents, ActorOrigin, ActorBoxExtent, bIncludeFromChildActors); Bounds += FBox(ActorOrigin - ActorBoxExtent, ActorOrigin + ActorBoxExtent); } } for(int32 SubGroupIndex=0; SubGroupIndexGetActorBounds(bOnlyCollidingComponents, SubGroupOrigin, SubGroupBoxExtent, bIncludeFromChildActors); Bounds += FBox(SubGroupOrigin - SubGroupBoxExtent, SubGroupOrigin + SubGroupBoxExtent); } } // To keep consistency with the other GetBounds functions, transform our result into an origin / extent formatting Bounds.GetCenterAndExtents(Origin, BoxExtent); } #if WITH_EDITOR void AGroupActor::GetStreamingBounds(FBox& OutRuntimeBounds, FBox& OutEditorBounds) const { Super::GetStreamingBounds(OutRuntimeBounds, OutEditorBounds); for (AActor* Actor : GroupActors) { if (Actor) { FBox RuntimeBounds; FBox EditorBounds; Actor->GetStreamingBounds(RuntimeBounds, EditorBounds); OutRuntimeBounds += RuntimeBounds; OutEditorBounds += EditorBounds; } } for (AGroupActor* SubGroupActor : SubGroups) { if (SubGroupActor) { FBox RuntimeBounds; FBox EditorBounds; SubGroupActor->GetStreamingBounds(RuntimeBounds, EditorBounds); OutRuntimeBounds += RuntimeBounds; OutEditorBounds += EditorBounds; } } } #endif void GetBoundingVectorsForGroup(AGroupActor* GroupActor, FViewport* Viewport, FVector& OutVectorMin, FVector& OutVectorMax) { // Draw a bounding box for grouped actors using the vector range we can gather from any child actors (including subgroups) OutVectorMin = FVector(BIG_NUMBER); OutVectorMax = FVector(-BIG_NUMBER); // Grab all actors for this group, including those within subgroups TArray ActorsInGroup; GroupActor->GetGroupActors(ActorsInGroup, true); // Loop through and collect each actor, using their bounding box to create the bounds for this group for(int32 ActorIndex = 0; ActorIndex < ActorsInGroup.Num(); ++ActorIndex) { AActor* Actor = ActorsInGroup[ActorIndex]; uint64 HiddenClients = Actor->HiddenEditorViews; bool bActorHiddenForViewport = false; if(!Actor->IsHiddenEd()) { if(Viewport) { for(int32 ViewIndex=0; ViewIndexGetLevelViewportClients().Num(); ++ViewIndex) { // If the current viewport is hiding this actor, don't draw brackets around it if(Viewport->GetClient() == GEditor->GetLevelViewportClients()[ViewIndex] && HiddenClients & ((uint64)1 << ViewIndex)) { bActorHiddenForViewport = true; break; } } } if(!bActorHiddenForViewport) { FBox ActorBox = Actor->GetComponentsBoundingBox( true ); // MinVector OutVectorMin.X = FMath::Min( ActorBox.Min.X, OutVectorMin.X ); OutVectorMin.Y = FMath::Min( ActorBox.Min.Y, OutVectorMin.Y ); OutVectorMin.Z = FMath::Min( ActorBox.Min.Z, OutVectorMin.Z ); // MaxVector OutVectorMax.X = FMath::Max( ActorBox.Max.X, OutVectorMax.X ); OutVectorMax.Y = FMath::Max( ActorBox.Max.Y, OutVectorMax.Y ); OutVectorMax.Z = FMath::Max( ActorBox.Max.Z, OutVectorMax.Z ); } } } } /** * Draw brackets around all given groups * @param PDI FPrimitiveDrawInterface used to draw lines in active viewports * @param Viewport Current viewport being rendered * @param InGroupList Array of groups to draw brackets for */ void PrivateDrawBracketsForGroups( FPrimitiveDrawInterface* PDI, FViewport* Viewport, const TArray& InGroupList ) { // Loop through each given group and draw all subgroups and actors for(int32 GroupIndex=0; GroupIndexGetWorld() == PDI->View->Family->Scene->GetWorld() ) { const FLinearColor GROUP_COLOR = GroupActor->IsLocked() ? BOXCOLOR_LOCKEDGROUPS : BOXCOLOR_UNLOCKEDGROUPS; FVector MinVector; FVector MaxVector; GetBoundingVectorsForGroup( GroupActor, Viewport, MinVector, MaxVector ); // Create a bracket offset to pad the space between brackets and actor(s) and determine the length of our corner axises float BracketOffset = FVector::Dist(MinVector, MaxVector) * 0.1f; MinVector = MinVector - BracketOffset; MaxVector = MaxVector + BracketOffset; // Calculate bracket corners based on min/max vectors TArray BracketCorners; // Bottom Corners BracketCorners.Add(FVector(MinVector.X, MinVector.Y, MinVector.Z)); BracketCorners.Add(FVector(MinVector.X, MaxVector.Y, MinVector.Z)); BracketCorners.Add(FVector(MaxVector.X, MaxVector.Y, MinVector.Z)); BracketCorners.Add(FVector(MaxVector.X, MinVector.Y, MinVector.Z)); // Top Corners BracketCorners.Add(FVector(MinVector.X, MinVector.Y, MaxVector.Z)); BracketCorners.Add(FVector(MinVector.X, MaxVector.Y, MaxVector.Z)); BracketCorners.Add(FVector(MaxVector.X, MaxVector.Y, MaxVector.Z)); BracketCorners.Add(FVector(MaxVector.X, MinVector.Y, MaxVector.Z)); for(int32 BracketCornerIndex=0; BracketCornerIndexDrawLine( CORNER, FVector(CORNER.X + (BracketOffset * DIR_X), CORNER.Y, CORNER.Z), GROUP_COLOR, SDPG_Foreground ); PDI->DrawLine( CORNER, FVector(CORNER.X, CORNER.Y + (BracketOffset * DIR_Y), CORNER.Z), GROUP_COLOR, SDPG_Foreground ); PDI->DrawLine( CORNER, FVector(CORNER.X, CORNER.Y, CORNER.Z + (BracketOffset * DIR_Z)), GROUP_COLOR, SDPG_Foreground ); } // Recurse through to any subgroups TArray SubGroupsInGroup; GroupActor->GetSubGroups(SubGroupsInGroup); PrivateDrawBracketsForGroups(PDI, Viewport, SubGroupsInGroup); } } } void AGroupActor::DrawBracketsForGroups( FPrimitiveDrawInterface* PDI, FViewport* Viewport, bool bMustBeSelected/*=true*/ ) { // Don't draw group actor brackets in game view if (Viewport->GetClient()->IsInGameView()) { return; } if( UActorGroupingUtils::IsGroupingActive() ) { check(PDI); UWorld* EditorWorld = GEditor->GetEditorWorldContext().World(); if (EditorWorld) { TRACE_CPUPROFILER_EVENT_SCOPE(AGroupActor::DrawBracketsForGroup); TArray GroupsToDraw; for(int32 GroupIndex=0; GroupIndex < EditorWorld->ActiveGroupActors.Num(); ++GroupIndex) { AGroupActor* GroupActor = Cast(EditorWorld->ActiveGroupActors[GroupIndex]); if (GroupActor != NULL) { if (bMustBeSelected) { // If we're only drawing for selected group, grab only those that have currently selected actors if (GroupActor->HasSelectedActors()) { // We want to start drawing groups from the highest root level. // Subgroups will be propagated through during the draw code. GroupActor = AGroupActor::GetRootForActor(GroupActor); GroupsToDraw.Add(GroupActor); } } else { // Otherwise, just add all group actors GroupsToDraw.Add(GroupActor); } } } PrivateDrawBracketsForGroups(PDI, Viewport, GroupsToDraw); } } } /** * Checks to see if the given GroupActor has any parents in the given Array. * @param InGroupActor Group to check lineage * @param InGroupArray Array to search for the given group's parent * @return True if a parent was found. */ bool GroupHasParentInArray(AGroupActor* InGroupActor, TArray& InGroupArray) { check(InGroupActor); AGroupActor* CurrentParentNode = AGroupActor::GetParentForActor(InGroupActor); // Use a cursor pointer to continually move up from our starting pointer (InGroupActor) through the hierarchy until // we find a valid parent in the given array, or run out of nodes. while( CurrentParentNode ) { if(InGroupArray.Contains(CurrentParentNode)) { return true; } CurrentParentNode = AGroupActor::GetParentForActor(CurrentParentNode); } return false; } void AGroupActor::RemoveSubGroupsFromArray(TArray& GroupArray) { for(int32 GroupIndex=0; GroupIndex(InActor); AGroupActor* IteratingNode = InGroupActor == NULL ? AGroupActor::GetParentForActor(InActor) : InGroupActor; while( IteratingNode ) { if ( (!bMustBeLocked || IteratingNode->IsLocked()) && (!bMustBeSelected || IteratingNode->HasSelectedActors()) && (!bMustBeUnlocked || !IteratingNode->IsLocked()) && (!bMustBeUnselected || !IteratingNode->HasSelectedActors()) ) { RootNode = IteratingNode; } IteratingNode = AGroupActor::GetParentForActor(IteratingNode); } return RootNode; } AGroupActor* AGroupActor::GetParentForActor(AActor* InActor) { return Cast(InActor->GroupActor); } const int32 AGroupActor::NumActiveGroups( bool bSelected/*=false*/, bool bDeepSearch/*=true*/ ) { UWorld* EditorWorld = GEditor->GetEditorWorldContext().World(); if (EditorWorld) { if(!bSelected) { return EditorWorld->ActiveGroupActors.Num(); } int32 ActiveSelectedGroups = 0; for(int32 GroupIdx=0; GroupIdx < EditorWorld->ActiveGroupActors.Num(); ++GroupIdx ) { AGroupActor* GroupActor = Cast(EditorWorld->ActiveGroupActors[GroupIdx]); if( GroupActor != NULL ) { if(GroupActor->HasSelectedActors(bDeepSearch)) { ++ActiveSelectedGroups; } } } return ActiveSelectedGroups; } return 0; } void AGroupActor::AddSelectedActorsToSelectedGroup() { UWorld* EditorWorld = GEditor->GetEditorWorldContext().World(); if (EditorWorld) { int32 SelectedGroupIndex = -1; for(int32 GroupIdx=0; GroupIdx < EditorWorld->ActiveGroupActors.Num(); ++GroupIdx ) { AGroupActor* GroupActor = Cast(EditorWorld->ActiveGroupActors[GroupIdx]); if( GroupActor != NULL ) { if(GroupActor->HasSelectedActors(false)) { // Assign the index of the selected group. // If this is the second group we find, too many groups are selected, return. if( SelectedGroupIndex == -1 ) { SelectedGroupIndex = GroupIdx; } else { return; } } } } AGroupActor* SelectedGroup = SelectedGroupIndex != -1 ? Cast(EditorWorld->ActiveGroupActors[SelectedGroupIndex]) : nullptr; if( SelectedGroup != nullptr ) { ULevel* GroupLevel = SelectedGroup->GetLevel(); // We've established that only one group is selected, so we can just call Add on all these actors. // Any actors already in the group will be ignored. TArray ActorsToAdd; bool bActorsInSameLevel = true; for ( FSelectionIterator It( GEditor->GetSelectedActorIterator() ) ; It ; ++It ) { AActor* Actor = CastChecked( *It ); if( Actor->GetLevel() == GroupLevel ) { ActorsToAdd.Add( Actor ); } else { bActorsInSameLevel = false; break; } } if( bActorsInSameLevel ) { if( ActorsToAdd.Num() > 0 ) { const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "Group_Add", "Add Actors to Group") ); for( int32 ActorIndex = 0; ActorIndex < ActorsToAdd.Num(); ++ActorIndex ) { if ( ActorsToAdd[ActorIndex] != SelectedGroup ) { SelectedGroup->Add( *ActorsToAdd[ActorIndex] ); } } SelectedGroup->CenterGroupLocation(); SelectedGroup->SetActorRotation(ActorsToAdd.Last()->GetActorRotation()); } } else { FMessageDialog::Open( EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "Group_CantCreateGroupMultipleLevels", "Can't group the selected actors because they are in different levels.") ); } } } } void AGroupActor::LockSelectedGroups() { UWorld* EditorWorld = GEditor->GetEditorWorldContext().World(); if (EditorWorld) { TArray GroupsToLock; for ( int32 GroupIndex=0; GroupIndexActiveGroupActors.Num(); ++GroupIndex ) { AGroupActor* GroupToLock = Cast(EditorWorld->ActiveGroupActors[GroupIndex]); if( GroupToLock != NULL ) { if( GroupToLock->HasSelectedActors(false) ) { // If our selected group is already locked, move up a level to add it's potential parent for locking if( GroupToLock->IsLocked() ) { AGroupActor* GroupParent = AGroupActor::GetParentForActor(GroupToLock); if(GroupParent && !GroupParent->IsLocked()) { GroupsToLock.AddUnique(GroupParent); } } else // if it's not locked, add it instead! { GroupsToLock.AddUnique(GroupToLock); } } } } if( GroupsToLock.Num() > 0 ) { const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "Group_Lock", "Lock Groups") ); for ( int32 GroupIndex=0; GroupIndexModify(); GroupToLock->Lock(); GEditor->SelectGroup(GroupToLock, false ); } GEditor->NoteSelectionChange(); } } } void AGroupActor::UnlockSelectedGroups() { UWorld* EditorWorld = GEditor->GetEditorWorldContext().World(); if (EditorWorld) { TArray GroupsToUnlock; for ( int32 GroupIndex=0; GroupIndexActiveGroupActors.Num(); ++GroupIndex ) { AGroupActor* GroupToUnlock = Cast(EditorWorld->ActiveGroupActors[GroupIndex]); if( GroupToUnlock != NULL ) { if( GroupToUnlock->IsSelected() ) { GroupsToUnlock.Add(GroupToUnlock); } } } // Only unlock topmost selected group(s) AGroupActor::RemoveSubGroupsFromArray(GroupsToUnlock); if( GroupsToUnlock.Num() > 0 ) { const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "Group_Unlock", "Unlock Groups") ); for ( int32 GroupIndex=0; GroupIndexModify(); GroupToUnlock->Unlock(); } GEditor->NoteSelectionChange(); } } } void AGroupActor::ToggleGroupMode() { UActorGroupingUtils::SetGroupingActive(!UActorGroupingUtils::IsGroupingActive()); // Update group selection in the editor to reflect the toggle SelectGroupsInSelection(); GEditor->RedrawAllViewports(); GEditor->SaveConfig(); } void AGroupActor::SelectGroupsInSelection() { if( UActorGroupingUtils::IsGroupingActive() ) { TArray GroupsToSelect; for ( FSelectionIterator It( GEditor->GetSelectedActorIterator() ) ; It ; ++It ) { AActor* Actor = static_cast( *It ); checkSlow( Actor->IsA(AActor::StaticClass()) ); AGroupActor* GroupActor = AGroupActor::GetRootForActor(Actor, true); if(GroupActor) { GroupsToSelect.AddUnique(GroupActor); } } // Select any groups from the currently selected actors for ( int32 GroupIndex=0; GroupIndexSelectGroup(GroupToSelect); } GEditor->NoteSelectionChange(); } } void AGroupActor::Lock() { bLocked = true; for(int32 SubGroupIdx=0; SubGroupIdx < SubGroups.Num(); ++SubGroupIdx ) { if( SubGroups[SubGroupIdx] != NULL ) { SubGroups[SubGroupIdx]->Lock(); } } } void AGroupActor::Add(AActor& InActor) { // See if the incoming actor already belongs to a group AGroupActor* InActorParent = AGroupActor::GetParentForActor(&InActor); if(InActorParent) // If so, detach it first { if(InActorParent == this) { return; } InActorParent->Modify(); InActorParent->Remove(InActor); } Modify(); AGroupActor* InGroupPtr = Cast(&InActor); if(InGroupPtr) { check(InGroupPtr != this); SubGroups.AddUnique(InGroupPtr); } else { GroupActors.AddUnique(&InActor); InActor.Modify(); InActor.GroupActor = this; } } void AGroupActor::Remove(AActor& InActor) { AGroupActor* InGroupPtr = Cast(&InActor); if(InGroupPtr && SubGroups.Contains(InGroupPtr)) { Modify(); SubGroups.Remove(InGroupPtr); } else if(GroupActors.Contains(&InActor)) { Modify(); GroupActors.Remove(&InActor); InActor.Modify(); InActor.GroupActor = NULL; } PostRemove(); } void AGroupActor::PostRemove() { // If all children have been removed (or only one subgroup remains), this group is no longer active. if( GroupActors.Num() == 0 && SubGroups.Num() <= 1 ) { // Remove any potentially remaining subgroups SubGroups.Empty(); // Destroy the actor and remove it from active groups AGroupActor* ParentGroup = AGroupActor::GetParentForActor(this); if(ParentGroup) { ParentGroup->Modify(); ParentGroup->Remove(*this); } UWorld* MyWorld = GetWorld(); if( MyWorld ) { // Group is no longer active MyWorld->ActiveGroupActors.Remove(this); MyWorld->ModifyLevel(GetLevel()); // Mark the group actor for removal MarkPackageDirty(); // If not currently garbage collecting (changing maps, saving, etc), remove the group immediately if(!IsGarbageCollecting()) { // Refresh all editor browsers after removal FScopedRefreshAllBrowsers LevelRefreshAllBrowsers; // Destroy group and clear references. GEditor->SelectActor( this, /*bSelected=*/ false, /*bNotify=*/ false ); ULayersSubsystem* LayersSubsystem = GEditor->GetEditorSubsystem(); LayersSubsystem->DisassociateActorFromLayers( this ); MyWorld->EditorDestroyActor( this, false ); LevelRefreshAllBrowsers.Request(); } } } } bool AGroupActor::Contains(AActor& InActor) const { AActor* InActorPtr = &InActor; AGroupActor* InGroupPtr = Cast(InActorPtr); if(InGroupPtr) { return SubGroups.Contains(InGroupPtr); } return GroupActors.Contains(InActorPtr); } bool AGroupActor::HasSelectedActors(bool bDeepSearch/*=true*/) const { for(int32 ActorIndex=0; ActorIndexIsSelected() ) { return true; } } } if(bDeepSearch) { for(int32 GroupIndex=0; GroupIndexHasSelectedActors(bDeepSearch) ) { return true; } } } } return false; } void AGroupActor::ClearAndRemove() { // Actors can potentially be NULL here. Some older maps can serialize invalid Actors // into GroupActors or SubGroups. for(int32 ActorIndex=0; ActorIndexNoteSelectionChange(); } void AGroupActor::GetGroupActors(TArray& OutGroupActors, bool bRecurse/*=false*/) const { if( bRecurse ) { for(int32 i=0; iGetGroupActors(OutGroupActors, bRecurse); } } } else { OutGroupActors.Empty(); } for(int32 i=0; i& OutSubGroups, bool bRecurse/*=false*/) const { if( bRecurse ) { for(int32 i=0; iGetSubGroups(OutSubGroups, bRecurse); } } } else { OutSubGroups.Empty(); } for(int32 i=0; i& OutChildren, bool bRecurse/*=false*/) const { GetGroupActors(OutChildren, bRecurse); TArray OutSubGroups; GetSubGroups(OutSubGroups, bRecurse); for(int32 SubGroupIdx=0; SubGroupIdx GetSelectedGroupActors(TArray> ActiveGroupActors) { TArray Result; for (const TObjectPtr& Actor : ActiveGroupActors) { if (AGroupActor* SelectedGroup = Cast(Actor)) { if (SelectedGroup->IsSelected()) { Result.Add(SelectedGroup); } } } return Result; } } bool AGroupActor::SelectedGroupNeedsFixup() { if (UWorld* EditorWorld = GEditor->GetEditorWorldContext().World()) { TArray SelectedGroupActors = UE::Editor::GroupActorUtil::GetSelectedGroupActors(EditorWorld->ActiveGroupActors); if (!SelectedGroupActors.IsEmpty()) { //check if there's at least 1 GroupActor contains a nullptr within it's list of Actors return SelectedGroupActors.ContainsByPredicate([](const AGroupActor* SelectedGroupActor) { if (SelectedGroupActor) { return SelectedGroupActor->GroupActors.ContainsByPredicate([](const TObjectPtr Actor) { return Actor == nullptr; }); } return false; }); } } return false; } // namespace UE::Editor::GroupActorUtil void AGroupActor::FixupGroupActor() { if (UWorld* EditorWorld = GEditor->GetEditorWorldContext().World()) { TArray SelectedGroupActors = UE::Editor::GroupActorUtil::GetSelectedGroupActors(EditorWorld->ActiveGroupActors); if (!SelectedGroupActors.IsEmpty()) { for (AGroupActor* SelectedGroupActor : SelectedGroupActors) { if (SelectedGroupActor && SelectedGroupActor->GroupActors.ContainsByPredicate([](const TObjectPtr Actor) { return Actor == nullptr; })) { //remove all nullptr entries in the GroupActors array. SelectedGroupActor->Modify(); SelectedGroupActor->GroupActors.RemoveAll([](const TObjectPtr Actor) { return Actor == nullptr; }); SelectedGroupActor->GroupActors.Shrink(); if (SelectedGroupActor->GroupActors.IsEmpty()) { SelectedGroupActor->PostRemove(); } } } } } }