// Copyright Epic Games, Inc. All Rights Reserved. #include "AssetSelection.h" #include "Engine/Level.h" #include "UObject/UnrealType.h" #include "GameFramework/Actor.h" #include "ActorFactories/ActorFactory.h" #include "Modules/ModuleManager.h" #include "GameFramework/Pawn.h" #include "Components/MeshComponent.h" #include "Components/StaticMeshComponent.h" #include "Settings/LevelEditorViewportSettings.h" #include "Engine/Brush.h" #include "Editor/GroupActor.h" #include "Animation/SkeletalMeshActor.h" #include "Particles/Emitter.h" #include "Engine/Light.h" #include "Engine/StaticMeshActor.h" #include "Components/DecalComponent.h" #include "Kismet2/ComponentEditorUtils.h" #include "Engine/Selection.h" #include "Editor.h" #include "Matinee/MatineeActor.h" #include "ScopedTransaction.h" #include "LevelUtils.h" #include "ComponentAssetBroker.h" #include "DragAndDrop/AssetDragDropOp.h" #include "DragAndDrop/CollectionDragDropOp.h" #include "AssetRegistryModule.h" #include "IContentBrowserSingleton.h" #include "ContentBrowserModule.h" #include "SnappingUtils.h" #include "ActorEditorUtils.h" #include "LevelEditorViewport.h" #include "LandscapeProxy.h" #include "Landscape.h" #include "Editor/ActorPositioning.h" #include "ObjectEditorUtils.h" #include "Framework/Notifications/NotificationManager.h" #include "Widgets/Notifications/SNotificationList.h" #include "Settings/LevelEditorMiscSettings.h" #include "Engine/LevelStreaming.h" #include "Engine/LevelBounds.h" #include "SourceControlHelpers.h" #include "ISourceControlModule.h" #include "ISourceControlProvider.h" #include "Misc/MessageDialog.h" namespace AssetSelectionUtils { bool IsClassPlaceable(const UClass* Class) { const bool bIsAddable = Class && !(Class->HasAnyClassFlags(CLASS_NotPlaceable | CLASS_Deprecated | CLASS_Abstract)) && Class->IsChildOf(AActor::StaticClass()); return bIsAddable; } // Blueprints handle their own Abstract flag, but we should check for deprecation and NotPlaceable here bool IsChildBlueprintPlaceable(const UClass* Class) { const bool bIsAddable = Class && !(Class->HasAnyClassFlags(CLASS_NotPlaceable | CLASS_Deprecated)) && Class->IsChildOf(AActor::StaticClass()); return bIsAddable; } void GetSelectedAssets(TArray& OutSelectedAssets) { // Add the selection from the content browser module FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); ContentBrowserModule.Get().GetSelectedAssets(OutSelectedAssets); } FSelectedActorInfo BuildSelectedActorInfo(const TArray& SelectedActors) { FSelectedActorInfo ActorInfo; if (SelectedActors.Num() > 0) { // Get the class type of the first actor. AActor* FirstActor = SelectedActors[0]; if (FirstActor && !FirstActor->IsTemplate()) { UClass* FirstClass = FirstActor->GetClass(); UObject* FirstArchetype = FirstActor->GetArchetype(); ActorInfo.bAllSelectedAreBrushes = Cast(FirstActor) != NULL; ActorInfo.SelectionClass = FirstClass; // Compare all actor types with the baseline. for (int32 ActorIndex = 0; ActorIndex < SelectedActors.Num(); ++ActorIndex) { AActor* CurrentActor = SelectedActors[ActorIndex]; if (CurrentActor->IsTemplate()) { continue; } ABrush* Brush = Cast(CurrentActor); if (!Brush) { ActorInfo.bAllSelectedAreBrushes = false; } else { if (!ActorInfo.bHaveBuilderBrush) { ActorInfo.bHaveBuilderBrush = FActorEditorUtils::IsABuilderBrush(Brush); } ActorInfo.bHaveBrush |= true; ActorInfo.bHaveBSPBrush |= (!Brush->IsVolumeBrush()); ActorInfo.bHaveVolume |= Brush->IsVolumeBrush(); } UClass* CurrentClass = CurrentActor->GetClass(); if (FirstClass != CurrentClass) { ActorInfo.bAllSelectedActorsOfSameType = false; ActorInfo.SelectionClass = NULL; FirstClass = NULL; } else { ActorInfo.SelectionClass = CurrentActor->GetClass(); } ++ActorInfo.NumSelected; if (ActorInfo.bAllSelectedActorsBelongToCurrentLevel) { const ULevel* ActorLevel = CurrentActor->GetLevel(); if (!ActorLevel || !ActorLevel->IsCurrentLevel()) { ActorInfo.bAllSelectedActorsBelongToCurrentLevel = false; } } if (ActorInfo.bAllSelectedActorsBelongToSameWorld) { if (!ActorInfo.SharedWorld) { ActorInfo.SharedWorld = CurrentActor->GetWorld(); check(ActorInfo.SharedWorld); } else { if (ActorInfo.SharedWorld != CurrentActor->GetWorld()) { ActorInfo.bAllSelectedActorsBelongToCurrentLevel = false; ActorInfo.SharedWorld = NULL; } } } // To prevent move to other level for Landscape if its components are distributed in streaming levels if (CurrentActor->IsA(ALandscape::StaticClass())) { ALandscape* Landscape = CastChecked(CurrentActor); if (!Landscape || !Landscape->HasAllComponent()) { if (!ActorInfo.bAllSelectedActorsBelongToCurrentLevel) { ActorInfo.bAllSelectedActorsBelongToCurrentLevel = true; } } } if (ActorInfo.bSelectedActorsBelongToSameLevel) { ULevel* ActorLevel = CurrentActor->GetLevel(); if (!ActorInfo.SharedLevel) { // This is the first selected actor we've encountered. ActorInfo.SharedLevel = ActorLevel; } else { // Does this actor's level match the others? if (ActorInfo.SharedLevel != ActorLevel) { ActorInfo.bSelectedActorsBelongToSameLevel = false; ActorInfo.SharedLevel = NULL; } } } AGroupActor* FoundGroup = Cast(CurrentActor); if (!FoundGroup) { FoundGroup = AGroupActor::GetParentForActor(CurrentActor); } if (FoundGroup) { if (!ActorInfo.bHaveSelectedSubGroup) { ActorInfo.bHaveSelectedSubGroup = AGroupActor::GetParentForActor(FoundGroup) != NULL; } if (!ActorInfo.bHaveSelectedLockedGroup) { ActorInfo.bHaveSelectedLockedGroup = FoundGroup->IsLocked(); } if (!ActorInfo.bHaveSelectedUnlockedGroup) { AGroupActor* FoundRoot = AGroupActor::GetRootForActor(CurrentActor); ActorInfo.bHaveSelectedUnlockedGroup = !FoundGroup->IsLocked() || (FoundRoot && !FoundRoot->IsLocked()); } } else { ++ActorInfo.NumSelectedUngroupedActors; } USceneComponent* RootComp = CurrentActor->GetRootComponent(); if (RootComp != nullptr && RootComp->GetAttachParent() != nullptr) { ActorInfo.bHaveAttachedActor = true; } for (UActorComponent* Component: CurrentActor->GetComponents()) { if (Component) { if (UStaticMeshComponent* SMComp = Cast(Component)) { if (SMComp->IsRegistered()) { ActorInfo.bHaveStaticMeshComponent = true; } } // Check for experimental/early-access classes in the component hierarchy bool bIsExperimental, bIsEarlyAccess; FString MostDerivedDevelopmentClassName; FObjectEditorUtils::GetClassDevelopmentStatus(Component->GetClass(), bIsExperimental, bIsEarlyAccess, MostDerivedDevelopmentClassName); ActorInfo.bHaveExperimentalClass |= bIsExperimental; ActorInfo.bHaveEarlyAccessClass |= bIsEarlyAccess; } } // Check for experimental/early-access classes in the actor hierarchy { bool bIsExperimental, bIsEarlyAccess; FString MostDerivedDevelopmentClassName; FObjectEditorUtils::GetClassDevelopmentStatus(CurrentClass, bIsExperimental, bIsEarlyAccess, MostDerivedDevelopmentClassName); ActorInfo.bHaveExperimentalClass |= bIsExperimental; ActorInfo.bHaveEarlyAccessClass |= bIsEarlyAccess; } if (CurrentActor->IsA(ALight::StaticClass())) { ActorInfo.bHaveLight = true; } if (CurrentActor->IsA(AStaticMeshActor::StaticClass())) { ActorInfo.bHaveStaticMesh = true; AStaticMeshActor* StaticMeshActor = CastChecked(CurrentActor); if (StaticMeshActor->GetStaticMeshComponent()) { UStaticMesh* StaticMesh = StaticMeshActor->GetStaticMeshComponent()->GetStaticMesh(); ActorInfo.bAllSelectedStaticMeshesHaveCollisionModels &= ((StaticMesh && StaticMesh->GetBodySetup()) ? true : false); } } if (CurrentActor->IsA(ASkeletalMeshActor::StaticClass())) { ActorInfo.bHaveSkeletalMesh = true; } if (CurrentActor->IsA(APawn::StaticClass())) { ActorInfo.bHavePawn = true; } if (CurrentActor->IsA(AEmitter::StaticClass())) { ActorInfo.bHaveEmitter = true; } if (CurrentActor->IsA(AMatineeActor::StaticClass())) { ActorInfo.bHaveMatinee = true; } if (CurrentActor->IsTemporarilyHiddenInEditor()) { ActorInfo.bHaveHidden = true; } if (CurrentActor->IsA(ALandscapeProxy::StaticClass())) { ActorInfo.bHaveLandscape = true; } // Find our counterpart actor AActor* EditorWorldActor = EditorUtilities::GetEditorWorldCounterpartActor(CurrentActor); if (EditorWorldActor != NULL) { // Just count the total number of actors with counterparts ++ActorInfo.NumSimulationChanges; } } if (ActorInfo.SelectionClass != NULL) { ActorInfo.SelectionStr = ActorInfo.SelectionClass->GetName(); } else { ActorInfo.SelectionStr = TEXT("Actor"); } } } // hack when only BSP is selected if (ActorInfo.SharedWorld == nullptr) { ActorInfo.SharedWorld = GWorld; } return ActorInfo; } FSelectedActorInfo GetSelectedActorInfo() { TArray SelectedActors; GEditor->GetSelectedActors()->GetSelectedObjects(SelectedActors); return BuildSelectedActorInfo(SelectedActors); } int32 GetNumSelectedSurfaces(UWorld* InWorld) { int32 NumSelectedSurfs = 0; UWorld* World = InWorld; if (!World) { World = GWorld; // Fallback to GWorld } if (World) { const int32 NumLevels = World->GetNumLevels(); for (int32 LevelIndex = 0; LevelIndex < NumLevels; LevelIndex++) { ULevel* Level = World->GetLevel(LevelIndex); UModel* Model = Level->Model; check(Model); const int32 NumSurfaces = Model->Surfs.Num(); // Count the number of selected surfaces for (int32 Surface = 0; Surface < NumSurfaces; ++Surface) { FBspSurf* Poly = &Model->Surfs[Surface]; if (Poly->PolyFlags & PF_Selected) { ++NumSelectedSurfs; } } } } return NumSelectedSurfs; } bool IsAnySurfaceSelected(UWorld* InWorld) { UWorld* World = InWorld; if (!World) { World = GWorld; // Fallback to GWorld } if (World) { const int32 NumLevels = World->GetNumLevels(); for (int32 LevelIndex = 0; LevelIndex < NumLevels; LevelIndex++) { ULevel* Level = World->GetLevel(LevelIndex); UModel* Model = Level->Model; check(Model); const int32 NumSurfaces = Model->Surfs.Num(); // Count the number of selected surfaces for (int32 Surface = 0; Surface < NumSurfaces; ++Surface) { FBspSurf* Poly = &Model->Surfs[Surface]; if (Poly->PolyFlags & PF_Selected) { return true; } } } } return false; } bool IsBuilderBrushSelected() { bool bHasBuilderBrushSelected = false; for (FSelectionIterator SelectionIter = GEditor->GetSelectedActorIterator(); SelectionIter; ++SelectionIter) { AActor* Actor = Cast(*SelectionIter); if (Actor && FActorEditorUtils::IsABuilderBrush(Actor)) { bHasBuilderBrushSelected = true; break; } } return bHasBuilderBrushSelected; } } // namespace AssetSelectionUtils namespace ActorPlacementUtils { bool IsLevelValidForActorPlacement(ULevel* InLevel, TArray& InActorTransforms) { if (FLevelUtils::IsLevelLocked(InLevel)) { FNotificationInfo Info(NSLOCTEXT("UnrealEd", "Error_OperationDisallowedOnLockedLevel", "The requested operation could not be completed because the level is locked.")); Info.ExpireDuration = 3.0f; FSlateNotificationManager::Get().AddNotification(Info); return false; } if (InLevel && InLevel->OwningWorld && InLevel->OwningWorld->GetStreamingLevels().Num() == 0) { // if this is the only level return true; } if (GIsRunningUnattendedScript) { // Don't prompt user for checks in unattended mode return true; } if (InLevel && GetDefault()->bPromptWhenAddingToLevelBeforeCheckout && SourceControlHelpers::IsAvailable()) { FString FileName = SourceControlHelpers::PackageFilename(InLevel->GetPathName()); // Query file state also checks the source control status FSourceControlStatePtr SCState = ISourceControlModule::Get().GetProvider().GetState(FileName, EStateCacheUsage::Use); if (!InLevel->bLevelOkayForPlacementWhileCheckedIn && !(SCState->IsCheckedOut() || SCState->IsAdded() || SCState->CanAdd() || SCState->IsUnknown())) { FText Title = NSLOCTEXT("UnrealEd", "LevelCheckout_Title", "Level Checkout Warning"); if (EAppReturnType::Ok != FMessageDialog::Open(EAppMsgType::OkCancel, NSLOCTEXT("UnrealEd", "LevelNotCheckedOutMsg", "This actor will be placed in a level that is in source control but not currently checked out. Continue?"), &Title)) { return false; } else { InLevel->bLevelOkayForPlacementWhileCheckedIn = true; } } } if (InLevel && InLevel->OwningWorld) { int32 LevelCount = InLevel->OwningWorld->GetStreamingLevels().Num(); int32 NumLockedLevels = 0; // Check for streaming level count b/c we know there is > 1 streaming level for (ULevelStreaming* StreamingLevel: InLevel->OwningWorld->GetStreamingLevels()) { StreamingLevel->bLocked ? NumLockedLevels++ : 0; } // If there is only one unlocked level, a) ours is the unlocked level b/c of the previous IsLevelLocked test and b) we shouldn't try to check for level bounds on the next test if (LevelCount - NumLockedLevels == 1) { return true; } } if (InLevel && GetDefault()->bPromptWhenAddingToLevelOutsideBounds) { FBox CurrentLevelBounds(ForceInit); if (InLevel->LevelBoundsActor.IsValid()) { CurrentLevelBounds = InLevel->LevelBoundsActor.Get()->GetComponentsBoundingBox(); } else { CurrentLevelBounds = ALevelBounds::CalculateLevelBounds(InLevel); } FVector BoundsExtent = CurrentLevelBounds.GetExtent(); if (BoundsExtent.X < GetDefault()->MinimumBoundsForCheckingSize.X && BoundsExtent.Y < GetDefault()->MinimumBoundsForCheckingSize.Y && BoundsExtent.Z < GetDefault()->MinimumBoundsForCheckingSize.Z) { return true; } FVector ExpandedScale = FVector(1.0f + (GetDefault()->PercentageThresholdForPrompt / 100.0f)); FTransform ExpandedScaleTransform = FTransform::Identity; ExpandedScaleTransform.SetScale3D(ExpandedScale); CurrentLevelBounds.TransformBy(ExpandedScaleTransform); for (int32 ActorTransformIndex = 0; ActorTransformIndex < InActorTransforms.Num(); ++ActorTransformIndex) { FTransform ActorTransform = InActorTransforms[ActorTransformIndex]; if (!CurrentLevelBounds.IsInsideOrOn(ActorTransform.GetLocation())) { FText Title = NSLOCTEXT("UnrealEd", "ActorPlacement_Title", "Actor Placement Warning"); if (EAppReturnType::Ok != FMessageDialog::Open(EAppMsgType::OkCancel, NSLOCTEXT("UnrealEd", "LevelBoundsMsg", "The actor will be placed outside the bounds of the current level. Continue?"), &Title)) { return false; } } } } return true; } } // namespace ActorPlacementUtils /** * Creates an actor using the specified factory. * * Does nothing if ActorClass is NULL. */ static AActor* PrivateAddActor(UObject* Asset, UActorFactory* Factory, bool SelectActor = true, EObjectFlags ObjectFlags = RF_Transactional, const FName Name = NAME_None) { if (!Factory) { return nullptr; } AActor* Actor = NULL; AActor* NewActorTemplate = Factory->GetDefaultActor(Asset); if (!NewActorTemplate) { return nullptr; } UWorld* OldWorld = nullptr; // The play world needs to be selected if it exists if (GIsEditor && GEditor->PlayWorld && !GIsPlayInEditorWorld) { OldWorld = SetPlayInEditorWorld(GEditor->PlayWorld); } // For Brushes/Volumes, use the default brush as the template rather than the factory default actor if (NewActorTemplate->IsA(ABrush::StaticClass()) && GWorld->GetDefaultBrush() != nullptr) { NewActorTemplate = GWorld->GetDefaultBrush(); } const FSnappedPositioningData PositioningData = FSnappedPositioningData(GCurrentLevelEditingViewportClient, GEditor->ClickLocation, GEditor->ClickPlane) .UseFactory(Factory) .UsePlacementExtent(NewActorTemplate->GetPlacementExtent()); FTransform ActorTransform = FActorPositioning::GetSnappedSurfaceAlignedTransform(PositioningData); if (GetDefault()->SnapToSurface.bEnabled) { // HACK: If we are aligning rotation to surfaces, we have to factor in the inverse of the actor's rotation and translation so that the resulting transform after SpawnActor is correct. if (auto* RootComponent = NewActorTemplate->GetRootComponent()) { RootComponent->UpdateComponentToWorld(); } FVector OrigActorScale3D = ActorTransform.GetScale3D(); ActorTransform = NewActorTemplate->GetTransform().Inverse() * ActorTransform; ActorTransform.SetScale3D(OrigActorScale3D); } // Do not fade snapping indicators over time if the viewport is not realtime bool bClearImmediately = !GCurrentLevelEditingViewportClient || !GCurrentLevelEditingViewportClient->IsRealtime(); FSnappingUtils::ClearSnappingHelpers(bClearImmediately); ULevel* DesiredLevel = GWorld->GetCurrentLevel(); // If DesireLevel is part of a LevelPartition find the proper DesiredLevel by asking the Partition if (const ILevelPartitionInterface* LevelPartition = DesiredLevel->GetLevelPartition()) { if (ULevel* SubLevel = LevelPartition->GetSubLevel(ActorTransform.GetLocation())) { DesiredLevel = SubLevel; } } bool bSpawnActor = true; if ((ObjectFlags & RF_Transactional) != 0) { TArray SpawningActorTransforms; SpawningActorTransforms.Add(ActorTransform); bSpawnActor = ActorPlacementUtils::IsLevelValidForActorPlacement(DesiredLevel, SpawningActorTransforms); } if (bSpawnActor) { FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "CreateActor", "Create Actor"), (ObjectFlags & RF_Transactional) != 0); // Create the actor. FActorSpawnParameters SpawnParams; SpawnParams.ObjectFlags = ObjectFlags; SpawnParams.Name = Name; Actor = Factory->CreateActor(Asset, DesiredLevel, ActorTransform, SpawnParams); if (Actor) { if (SelectActor) { GEditor->SelectNone(false, true); GEditor->SelectActor(Actor, true, true); } Actor->InvalidateLightingCache(); Actor->PostEditChange(); } GEditor->RedrawLevelEditingViewports(); } if (Actor) { Actor->MarkPackageDirty(); ULevel::LevelDirtiedEvent.Broadcast(); } // Restore the old world if there was one if (OldWorld) { RestoreEditorWorld(OldWorld); } return Actor; } namespace AssetUtil { TArray ExtractAssetDataFromDrag(const FDragDropEvent& DragDropEvent) { return ExtractAssetDataFromDrag(DragDropEvent.GetOperation()); } TArray ExtractAssetDataFromDrag(const TSharedPtr& Operation) { TArray DroppedAssetData; if (!Operation.IsValid()) { return DroppedAssetData; } if (Operation->IsOfType()) { TSharedPtr DragDropOp = StaticCastSharedPtr(Operation); if (DragDropOp->HasText()) { TArray DroppedAssetStrings; const TCHAR AssetDelimiter[] = {AssetMarshalDefs::AssetDelimiter, TEXT('\0')}; DragDropOp->GetText().ParseIntoArray(DroppedAssetStrings, AssetDelimiter, true); FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); IAssetRegistry& AssetRegistry = AssetRegistryModule.Get(); for (const FString& DroppedAssetString: DroppedAssetStrings) { if (DroppedAssetString.Len() < NAME_SIZE && FName::IsValidXName(DroppedAssetString, INVALID_OBJECTNAME_CHARACTERS INVALID_LONGPACKAGE_CHARACTERS)) { FAssetData AssetData = AssetRegistry.GetAssetByObjectPath(FName(*DroppedAssetString)); if (AssetData.IsValid()) { DroppedAssetData.Add(AssetData); } } } } } else if (Operation->IsOfType()) { TSharedPtr DragDropOp = StaticCastSharedPtr(Operation); DroppedAssetData.Append(DragDropOp->GetAssets()); } else if (Operation->IsOfType()) { TSharedPtr DragDropOp = StaticCastSharedPtr(Operation); DroppedAssetData.Append(DragDropOp->GetAssets()); } return DroppedAssetData; } FReply CanHandleAssetDrag(const FDragDropEvent& DragDropEvent) { TArray DroppedAssetData = ExtractAssetDataFromDrag(DragDropEvent); for (auto AssetIt = DroppedAssetData.CreateConstIterator(); AssetIt; ++AssetIt) { const FAssetData& AssetData = *AssetIt; if (AssetData.IsValid() && FComponentAssetBrokerage::GetPrimaryComponentForAsset(AssetData.GetClass())) { return FReply::Handled(); } } return FReply::Unhandled(); } } // namespace AssetUtil /* ========================================================================================================== FActorFactoryAssetProxy ========================================================================================================== */ void FActorFactoryAssetProxy::GenerateActorFactoryMenuItems(const FAssetData& AssetData, TArray* OutMenuItems, bool ExcludeStandAloneFactories) { FText UnusedErrorMessage; const FAssetData NoAssetData{}; for (int32 FactoryIdx = 0; FactoryIdx < GEditor->ActorFactories.Num(); FactoryIdx++) { UActorFactory* Factory = GEditor->ActorFactories[FactoryIdx]; const bool FactoryWorksWithoutAsset = Factory->CanCreateActorFrom(NoAssetData, UnusedErrorMessage); const bool FactoryWorksWithAsset = AssetData.IsValid() && Factory->CanCreateActorFrom(AssetData, UnusedErrorMessage); const bool FactoryWorks = FactoryWorksWithAsset || FactoryWorksWithoutAsset; if (FactoryWorks) { FMenuItem MenuItem = FMenuItem(Factory, NoAssetData); if (FactoryWorksWithAsset) { MenuItem = FMenuItem(Factory, AssetData); } if (FactoryWorksWithAsset || (!ExcludeStandAloneFactories && FactoryWorksWithoutAsset)) { OutMenuItems->Add(MenuItem); } } } } /** * Find the appropriate actor factory for an asset by type. * * @param AssetData contains information about an asset that to get a factory for * @param bRequireValidObject indicates whether a valid asset object is required. specify false to allow the asset * class's CDO to be used in place of the asset if no asset is part of the drag-n-drop * * @return the factory that is responsible for creating actors for the specified asset type. */ UActorFactory* FActorFactoryAssetProxy::GetFactoryForAsset(const FAssetData& AssetData, bool bRequireValidObject /*=false*/) { UObject* Asset = NULL; if (AssetData.IsAssetLoaded()) { Asset = AssetData.GetAsset(); } else if (!bRequireValidObject) { Asset = AssetData.GetClass()->GetDefaultObject(); } return FActorFactoryAssetProxy::GetFactoryForAssetObject(Asset); } /** * Find the appropriate actor factory for an asset. * * @param AssetObj The asset that to find the appropriate actor factory for * * @return The factory that is responsible for creating actors for the specified asset */ UActorFactory* FActorFactoryAssetProxy::GetFactoryForAssetObject(UObject* AssetObj) { UActorFactory* Result = NULL; // Attempt to find a factory that is capable of creating the asset const TArray& ActorFactories = GEditor->ActorFactories; FText UnusedErrorMessage; FAssetData AssetData(AssetObj); for (int32 FactoryIdx = 0; Result == NULL && FactoryIdx < ActorFactories.Num(); ++FactoryIdx) { UActorFactory* ActorFactory = ActorFactories[FactoryIdx]; // Check if the actor can be created using this factory, making sure to check for an asset to be assigned from the selector if (ActorFactory->CanCreateActorFrom(AssetData, UnusedErrorMessage)) { Result = ActorFactory; } } return Result; } AActor* FActorFactoryAssetProxy::AddActorForAsset(UObject* AssetObj, bool SelectActor, EObjectFlags ObjectFlags, UActorFactory* FactoryToUse /*= NULL*/, const FName Name) { AActor* Result = NULL; const FAssetData AssetData(AssetObj); FText UnusedErrorMessage; if (AssetObj != NULL) { // If a specific factory has been provided, verify its validity and then use it to create the actor if (FactoryToUse) { if (FactoryToUse->CanCreateActorFrom(AssetData, UnusedErrorMessage)) { Result = PrivateAddActor(AssetObj, FactoryToUse, SelectActor, ObjectFlags, Name); } } // If no specific factory has been provided, find the highest priority one that is valid for the asset and use // it to create the actor else { const TArray& ActorFactories = GEditor->ActorFactories; for (int32 FactoryIdx = 0; FactoryIdx < ActorFactories.Num(); FactoryIdx++) { UActorFactory* ActorFactory = ActorFactories[FactoryIdx]; // Check if the actor can be created using this factory, making sure to check for an asset to be assigned from the selector if (ActorFactory->CanCreateActorFrom(AssetData, UnusedErrorMessage)) { Result = PrivateAddActor(AssetObj, ActorFactory, SelectActor, ObjectFlags, Name); if (Result != NULL) { break; } } } } } return Result; } AActor* FActorFactoryAssetProxy::AddActorFromSelection(UClass* ActorClass, const FVector* ActorLocation, bool SelectActor, EObjectFlags ObjectFlags, UActorFactory* ActorFactory, const FName Name) { check(ActorClass != NULL); if (!ActorFactory) { // Look for an actor factory capable of creating actors of the actors type. ActorFactory = GEditor->FindActorFactoryForActorClass(ActorClass); } AActor* Result = NULL; FText ErrorMessage; if (ActorFactory) { UObject* TargetObject = GEditor->GetSelectedObjects()->GetTop(); if (TargetObject && ActorFactory->CanCreateActorFrom(FAssetData(TargetObject), ErrorMessage)) { // Attempt to add the actor Result = PrivateAddActor(TargetObject, ActorFactory, SelectActor, ObjectFlags); } } return Result; } /** * Determines if the provided actor is capable of having a material applied to it. * * @param TargetActor Actor to check for the validity of material application * * @return true if the actor is valid for material application; false otherwise */ bool FActorFactoryAssetProxy::IsActorValidForMaterialApplication(AActor* TargetActor) { bool bIsValid = false; //@TODO: PAPER2D: Extend this to support non mesh components (or make sprites a mesh component) // Check if the actor has a mesh or fog volume density. If so, it can likely have // a material applied to it. Otherwise, it cannot. if (TargetActor) { for (UActorComponent* Component: TargetActor->GetComponents()) { if (Cast(Component)) { bIsValid = true; break; } } } return bIsValid; } /** * Attempts to apply the material to the specified actor. * * @param TargetActor the actor to apply the material to * @param MaterialToApply the material to apply to the actor * @param OptionalMaterialSlot the material slot to apply to. * * @return true if the material was successfully applied to the actor */ bool FActorFactoryAssetProxy::ApplyMaterialToActor(AActor* TargetActor, UMaterialInterface* MaterialToApply, int32 OptionalMaterialSlot) { bool bResult = false; if (TargetActor != NULL && MaterialToApply != NULL) { ALandscapeProxy* Landscape = Cast(TargetActor); if (Landscape != NULL) { FProperty* MaterialProperty = FindFProperty(ALandscapeProxy::StaticClass(), "LandscapeMaterial"); Landscape->PreEditChange(MaterialProperty); Landscape->LandscapeMaterial = MaterialToApply; FPropertyChangedEvent PropertyChangedEvent(MaterialProperty); Landscape->PostEditChangeProperty(PropertyChangedEvent); bResult = true; } else { TArray EditableComponents; FActorEditorUtils::GetEditableComponents(TargetActor, EditableComponents); // Some actors could potentially have multiple mesh components, so we need to store all of the potentially valid ones // (or else perform special cases with IsA checks on the target actor) TArray FoundMeshComponents; // Find which mesh the user clicked on first. for (UActorComponent* Component: TargetActor->GetComponents()) { USceneComponent* SceneComp = Cast(Component); // Only apply the material to editable components. Components which are not exposed are not intended to be changed. if (SceneComp && EditableComponents.Contains(SceneComp)) { UMeshComponent* MeshComponent = Cast(SceneComp); if ((MeshComponent && MeshComponent->IsRegistered()) || SceneComp->IsA()) { // Intentionally do not break the loop here, as there could be potentially multiple mesh components FoundMeshComponents.AddUnique(SceneComp); } } } if (FoundMeshComponents.Num() > 0) { // Check each component that was found for (TArray::TConstIterator MeshCompIter(FoundMeshComponents); MeshCompIter; ++MeshCompIter) { USceneComponent* SceneComp = *MeshCompIter; bResult = FComponentEditorUtils::AttemptApplyMaterialToComponent(SceneComp, MaterialToApply, OptionalMaterialSlot); } } } } return bResult; } // EOF