Files
UnrealEngine/Engine/Source/Editor/UnrealEd/Private/Kismet2/ComponentEditorUtils.cpp
2025-05-18 13:04:45 +08:00

1263 lines
45 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Kismet2/ComponentEditorUtils.h"
#include "HAL/FileManager.h"
#include "Misc/Paths.h"
#include "UObject/PropertyPortFlags.h"
#include "Textures/SlateIcon.h"
#include "Framework/Commands/UIAction.h"
#include "Styling/AppStyle.h"
#include "Components/PrimitiveComponent.h"
#include "Components/MeshComponent.h"
#include "Exporters/Exporter.h"
#include "MaterialDomain.h"
#include "Materials/Material.h"
#include "Components/DecalComponent.h"
#include "ScopedTransaction.h"
#include "EdGraphSchema_K2.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "Factories.h"
#include "UnrealExporter.h"
#include "Framework/Commands/GenericCommands.h"
#include "SourceCodeNavigation.h"
#include "Styling/SlateIconFinder.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Editor.h"
#include "Editor/EditorEngine.h"
#include "Editor/UnrealEdEngine.h"
#include "Preferences/UnrealEdOptions.h"
#include "UnrealEdGlobals.h"
#include "HAL/PlatformApplicationMisc.h"
#include "ToolMenus.h"
#include "Kismet2/ComponentEditorContextMenuContex.h"
#include "Subsystems/AssetEditorSubsystem.h"
#define LOCTEXT_NAMESPACE "ComponentEditorUtils"
// Text object factory for pasting components
struct FComponentObjectTextFactory : public FCustomizableTextObjectFactory
{
// Child->Parent name map
TMap<FName, FName> ParentMap;
// Name->Instance object mapping
TMap<FName, UActorComponent*> NewObjectMap;
// Determine whether or not scene components in the new object set can be attached to the given scene root component
bool CanAttachComponentsTo(const USceneComponent* InRootComponent)
{
check(InRootComponent);
// For each component in the set, check against the given root component and break if we fail to validate
bool bCanAttachToRoot = true;
for (auto NewComponentIt = NewObjectMap.CreateConstIterator(); NewComponentIt && bCanAttachToRoot; ++NewComponentIt)
{
// If this is a scene component, and it does not already have a parent within the set
const USceneComponent* SceneComponent = Cast<USceneComponent>(NewComponentIt->Value);
if (SceneComponent && !ParentMap.Contains(SceneComponent->GetFName()))
{
// Determine if we are allowed to attach the scene component to the given root component
bCanAttachToRoot = InRootComponent->CanAttachAsChild(SceneComponent, NAME_None)
&& SceneComponent->Mobility >= InRootComponent->Mobility
&& ( !InRootComponent->IsEditorOnly() || SceneComponent->IsEditorOnly() );
}
}
return bCanAttachToRoot;
}
// Constructs a new object factory from the given text buffer
static TSharedRef<FComponentObjectTextFactory> Get(const FString& InTextBuffer, bool bPasteAsArchetypes = false)
{
// Construct a new instance
TSharedPtr<FComponentObjectTextFactory> FactoryPtr = MakeShareable(new FComponentObjectTextFactory());
check(FactoryPtr.IsValid());
// Create new objects if we're allowed to
if (FactoryPtr->CanCreateObjectsFromText(InTextBuffer))
{
EObjectFlags ObjectFlags = RF_Transactional;
if (bPasteAsArchetypes)
{
ObjectFlags |= RF_ArchetypeObject | RF_Public;
}
// Use the transient package initially for creating the objects, since the variable name is used when copying
FactoryPtr->ProcessBuffer(GetTransientPackage(), ObjectFlags, InTextBuffer);
}
return FactoryPtr.ToSharedRef();
}
virtual ~FComponentObjectTextFactory() {}
protected:
// Constructor; protected to only allow this class to instance itself
FComponentObjectTextFactory()
:FCustomizableTextObjectFactory(GWarn)
{
}
// FCustomizableTextObjectFactory implementation
virtual bool CanCreateClass(UClass* ObjectClass, bool& bOmitSubObjs) const override
{
// Allow actor component types to be created
bool bCanCreate = ObjectClass && ObjectClass->IsChildOf(UActorComponent::StaticClass());
if (!bCanCreate)
{
// Also allow Blueprint-able actor types to pass, in order to enable proper creation of actor component types as subobjects. The actor instance will be discarded after processing.
bCanCreate = ObjectClass && ObjectClass->IsChildOf(AActor::StaticClass()) && FKismetEditorUtilities::CanCreateBlueprintOfClass(ObjectClass);
}
else
{
// Actor component classes should not be abstract and must also be tagged as BlueprintSpawnable
bCanCreate = FKismetEditorUtilities::IsClassABlueprintSpawnableComponent(ObjectClass);
}
return bCanCreate;
}
virtual void ProcessConstructedObject(UObject* NewObject) override
{
check(NewObject);
TInlineComponentArray<UActorComponent*> ActorComponents;
if (UActorComponent* NewActorComponent = Cast<UActorComponent>(NewObject))
{
ActorComponents.Add(NewActorComponent);
}
else if (AActor* NewActor = Cast<AActor>(NewObject))
{
if (USceneComponent* RootComponent = NewActor->GetRootComponent())
{
RootComponent->SetWorldLocationAndRotationNoPhysics(FVector(0.f),FRotator(0.f));
}
NewActor->GetComponents(ActorComponents);
}
for(UActorComponent* ActorComponent : ActorComponents)
{
// Add it to the new object map
NewObjectMap.Add(ActorComponent->GetFName(), ActorComponent);
// If this is a scene component and it has a parent
USceneComponent* SceneComponent = Cast<USceneComponent>(ActorComponent);
if (SceneComponent && SceneComponent->GetAttachParent())
{
// Add an entry to the child->parent name map
ParentMap.Add(ActorComponent->GetFName(), SceneComponent->GetAttachParent()->GetFName());
// Clear this so it isn't used when constructing the new SCS node
SceneComponent->SetupAttachment(nullptr);
}
}
}
// FCustomizableTextObjectFactory (end)
};
bool FComponentEditorUtils::CanEditComponentInstance(const UActorComponent* ActorComp, const UActorComponent* ParentSceneComp, bool bAllowUserContructionScript)
{
// Exclude nested DSOs attached to BP-constructed instances, which are not mutable.
return (ActorComp != nullptr
&& (!ActorComp->IsVisualizationComponent())
&& (ActorComp->CreationMethod != EComponentCreationMethod::UserConstructionScript || bAllowUserContructionScript)
&& (ParentSceneComp == nullptr || !ParentSceneComp->IsCreatedByConstructionScript() || !ActorComp->HasAnyFlags(RF_DefaultSubObject)))
&& (ActorComp->CreationMethod != EComponentCreationMethod::Native || FComponentEditorUtils::GetPropertyForEditableNativeComponent(ActorComp));
}
FProperty* FComponentEditorUtils::GetPropertyForEditableNativeComponent(const UActorComponent* NativeComponent)
{
// A native component can be edited if it is bound to a member variable and that variable is marked as visible in the editor
// Note: We aren't concerned with whether the component is marked editable - the component itself is responsible for determining which of its properties are editable
UObject* ComponentOuter = (NativeComponent ? NativeComponent->GetOuter() : nullptr);
UClass* OwnerClass = (ComponentOuter ? ComponentOuter->GetClass() : nullptr);
if (OwnerClass != nullptr)
{
for (TFieldIterator<FObjectProperty> It(OwnerClass); It; ++It)
{
FObjectProperty* ObjectProp = *It;
// Must be visible - note CPF_Edit is set for all properties that should be visible, not just those that are editable
if ((ObjectProp->PropertyFlags & (CPF_Edit)) == 0)
{
continue;
}
UObject* Object = ObjectProp->GetObjectPropertyValue(ObjectProp->ContainerPtrToValuePtr<void>(ComponentOuter));
if (Object != nullptr && Object->GetFName() == NativeComponent->GetFName())
{
return ObjectProp;
}
}
// We have to check for array properties as well because they are not FObjectProperties and we want to be able to
// edit the inside of it
if (ComponentOuter != nullptr)
{
for (TFieldIterator<FArrayProperty> PropIt(OwnerClass, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt)
{
FArrayProperty* TestProperty = *PropIt;
void* ArrayPropInstAddress = TestProperty->ContainerPtrToValuePtr<void>(ComponentOuter);
// Ensure that this property is valid
FObjectProperty* ArrayEntryProp = CastField<FObjectProperty>(TestProperty->Inner);
if ((ArrayEntryProp == nullptr) || !ArrayEntryProp->PropertyClass->IsChildOf<UActorComponent>() || ((TestProperty->PropertyFlags & CPF_Edit) == 0))
{
continue;
}
// For each object in this array
FScriptArrayHelper ArrayHelper(TestProperty, ArrayPropInstAddress);
for (int32 ComponentIndex = 0; ComponentIndex < ArrayHelper.Num(); ++ComponentIndex)
{
// If this object is the native component we are looking for, then we should be allowed to edit it
const uint8* ArrayValRawPtr = ArrayHelper.GetRawPtr(ComponentIndex);
UObject* ArrayElement = ArrayEntryProp->GetObjectPropertyValue(ArrayValRawPtr);
if (ArrayElement != nullptr && ArrayElement->GetFName() == NativeComponent->GetFName())
{
return ArrayEntryProp;
}
}
}
// Check for map properties as well
for (TFieldIterator<FMapProperty> PropIt(OwnerClass, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt)
{
FMapProperty* TestProperty = *PropIt;
void* MapPropInstAddress = TestProperty->ContainerPtrToValuePtr<void>(ComponentOuter);
// Ensure that this property is valid and that it is marked as visible in the editor
FObjectProperty* MapValProp = CastField<FObjectProperty>(TestProperty->ValueProp);
if ((MapValProp == nullptr) || !MapValProp->PropertyClass->IsChildOf<UActorComponent>() || ((TestProperty->PropertyFlags & CPF_Edit) == 0))
{
continue;
}
FScriptMapHelper MapHelper(TestProperty, MapPropInstAddress);
for (FScriptMapHelper::FIterator It(MapHelper); It; ++It)
{
// For each value in the map (don't bother checking the keys, they won't be what the user can edit in this case)
const uint8* MapValueData = MapHelper.GetValuePtr(It);
UObject* ValueElement = MapValProp->GetObjectPropertyValue(MapValueData);
if (ValueElement != nullptr && ValueElement->GetFName() == NativeComponent->GetFName())
{
return MapValProp;
}
}
}
// Finally we should check for set properties
for (TFieldIterator<FSetProperty> PropIt(OwnerClass, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt)
{
FSetProperty* TestProperty = *PropIt;
void* SetPropInstAddress = TestProperty->ContainerPtrToValuePtr<void>(ComponentOuter);
// Ensure that this property is valid and that it is marked visible
FObjectProperty* SetValProp = CastField<FObjectProperty>(TestProperty->ElementProp);
if ((SetValProp == nullptr) || !SetValProp->PropertyClass->IsChildOf<UActorComponent>() || ((TestProperty->PropertyFlags & CPF_Edit) == 0))
{
continue;
}
// For each item in the set
FScriptSetHelper SetHelper(TestProperty, SetPropInstAddress);
for (FScriptSetHelper::FIterator It(SetHelper); It; ++It)
{
const uint8* SetValData = SetHelper.GetElementPtr(It);
UObject* SetValueElem = SetValProp->GetObjectPropertyValue(SetValData);
if (SetValueElem != nullptr && SetValueElem->GetFName() == NativeComponent->GetFName())
{
return SetValProp;
}
}
}
}
}
return nullptr;
}
bool FComponentEditorUtils::IsValidVariableNameString(const UActorComponent* InComponent, const FString& InString)
{
// First test to make sure the string is not empty and does not equate to the DefaultSceneRoot node name
bool bIsValid = !InString.IsEmpty() && !InString.Equals(USceneComponent::GetDefaultSceneRootVariableName().ToString());
if(bIsValid && InComponent != NULL)
{
// Next test to make sure the string doesn't conflict with the format that MakeUniqueObjectName() generates
const FString ClassNameThatWillBeUsedInGenerator = FBlueprintEditorUtils::GetClassNameWithoutSuffix(InComponent->GetClass());
const FString MakeUniqueObjectNamePrefix = FString::Printf(TEXT("%s_"), *ClassNameThatWillBeUsedInGenerator);
if(InString.StartsWith(MakeUniqueObjectNamePrefix))
{
bIsValid = !InString.Replace(*MakeUniqueObjectNamePrefix, TEXT("")).IsNumeric();
}
}
return bIsValid;
}
bool FComponentEditorUtils::IsComponentNameAvailable(const FString& InString, UObject* ComponentOwner, const UActorComponent* ComponentToIgnore)
{
UObject* Object = FindObjectFast<UObject>(ComponentOwner, *InString);
bool bNameIsAvailable = Object == nullptr || Object == ComponentToIgnore;
return bNameIsAvailable;
}
FString FComponentEditorUtils::GenerateValidVariableName(TSubclassOf<UActorComponent> ComponentClass, AActor* ComponentOwner)
{
check(ComponentOwner);
FString ComponentTypeName = FBlueprintEditorUtils::GetClassNameWithoutSuffix(ComponentClass);
// Strip off 'Component' if the class ends with that. It just looks better in the UI.
FString SuffixToStrip( TEXT( "Component" ) );
if( ComponentTypeName.EndsWith( SuffixToStrip ) )
{
ComponentTypeName.LeftInline( ComponentTypeName.Len() - SuffixToStrip.Len(), EAllowShrinking::No );
}
// Strip off 'Actor' if the class ends with that so as not to confuse actors with components
SuffixToStrip = TEXT( "Actor" );
if( ComponentTypeName.EndsWith( SuffixToStrip ) )
{
ComponentTypeName.LeftInline( ComponentTypeName.Len() - SuffixToStrip.Len(), EAllowShrinking::No );
}
// Try to create a name without any numerical suffix first
int32 Counter = 1;
FString ComponentInstanceName = ComponentTypeName;
while (!IsComponentNameAvailable(ComponentInstanceName, ComponentOwner))
{
// Assign the lowest possible numerical suffix
ComponentInstanceName = FString::Printf(TEXT("%s%d"), *ComponentTypeName, Counter++);
}
return ComponentInstanceName;
}
FString FComponentEditorUtils::GenerateValidVariableNameFromAsset(UObject* Asset, AActor* ComponentOwner)
{
int32 Counter = 1;
FString AssetName = Asset->GetName();
if (UClass* Class = Cast<UClass>(Asset))
{
if (!Class->HasAnyClassFlags(CLASS_CompiledFromBlueprint))
{
AssetName.RemoveFromEnd(TEXT("Component"));
}
else
{
AssetName.RemoveFromEnd("_C");
}
}
else if (UActorComponent* Comp = Cast <UActorComponent>(Asset))
{
AssetName.RemoveFromEnd(UActorComponent::ComponentTemplateNameSuffix);
}
// Try to create a name without any numerical suffix first
FString NewName = AssetName;
auto BuildNewName = [&Counter, &AssetName]()
{
return FString::Printf(TEXT("%s%d"), *AssetName, Counter++);
};
if (ComponentOwner)
{
// If a desired name is supplied then walk back and find any numeric suffix so we can increment it nicely
int32 Index = AssetName.Len();
while (Index > 0 && AssetName[Index - 1] >= '0' && AssetName[Index - 1] <= '9')
{
--Index;
}
if (Index < AssetName.Len())
{
FString NumericSuffix = AssetName.RightChop(Index);
Counter = FCString::Atoi(*NumericSuffix);
NumericSuffix = FString::Printf(TEXT("%d"), Counter); // Restringify the counter to account for leading 0s that we don't want to remove
AssetName.RemoveAt(AssetName.Len() - NumericSuffix.Len(), NumericSuffix.Len(), EAllowShrinking::No);
++Counter;
NewName = BuildNewName();
}
while (!IsComponentNameAvailable(NewName, ComponentOwner))
{
NewName = BuildNewName();
}
}
return NewName;
}
USceneComponent* FComponentEditorUtils::FindClosestParentInList(UActorComponent* ChildComponent, const TArray<UActorComponent*>& ComponentList)
{
// Find the most recent parent that is part of the ComponentList
if (USceneComponent* ChildAsScene = Cast<USceneComponent>(ChildComponent))
{
for (USceneComponent* Parent = ChildAsScene->GetAttachParent(); Parent != nullptr; Parent = Parent->GetAttachParent())
{
if (ComponentList.Contains(Parent))
{
return Parent;
}
}
}
return nullptr;
}
bool FComponentEditorUtils::CanCopyComponent(const UActorComponent* ComponentToCopy)
{
if (ComponentToCopy != nullptr && ComponentToCopy->GetFName() != USceneComponent::GetDefaultSceneRootVariableName())
{
UClass* ComponentClass = ComponentToCopy->GetClass();
check(ComponentClass != nullptr);
// Component class cannot be abstract and must also be tagged as BlueprintSpawnable
return FKismetEditorUtilities::IsClassABlueprintSpawnableComponent(ComponentClass);
}
return false;
}
bool FComponentEditorUtils::CanCopyComponents(const TArray<UActorComponent*>& ComponentsToCopy)
{
bool bCanCopy = ComponentsToCopy.Num() > 0;
if (bCanCopy)
{
for (int32 i = 0; i < ComponentsToCopy.Num() && bCanCopy; ++i)
{
// Check for the default scene root; that cannot be copied/duplicated
UActorComponent* Component = ComponentsToCopy[i];
bCanCopy = CanCopyComponent(Component);
}
}
return bCanCopy;
}
void FComponentEditorUtils::CopyComponents(const TArray<UActorComponent*>& ComponentsToCopy, FString* DestinationData)
{
FStringOutputDevice Archive;
// Clear the mark state for saving.
UnMarkAllObjects(EObjectMark(OBJECTMARK_TagExp | OBJECTMARK_TagImp));
// Duplicate the selected component templates into temporary objects that we can modify
TMap<FName, FName> ParentMap;
TMap<FName, UActorComponent*> ObjectMap;
for (UActorComponent* Component : ComponentsToCopy)
{
// Duplicate the component into a temporary object
UObject* DuplicatedComponent = StaticDuplicateObject(Component, GetTransientPackage());
if (DuplicatedComponent)
{
// If the duplicated component is a scene component, wipe its attach parent (to prevent log warnings for referencing a private object in an external package)
if (USceneComponent* DuplicatedCompAsSceneComp = Cast<USceneComponent>(DuplicatedComponent))
{
DuplicatedCompAsSceneComp->SetupAttachment(nullptr);
AActor* Owner = Component->GetOwner();
if (Owner && Component == Owner->GetRootComponent())
{
DuplicatedCompAsSceneComp->SetRelativeTransform_Direct(FTransform::Identity);
}
}
// Find the closest parent component of the current component within the list of components to copy
USceneComponent* ClosestSelectedParent = FindClosestParentInList(Component, ComponentsToCopy);
if (ClosestSelectedParent)
{
// If the parent is included in the list, record it into the node->parent map
ParentMap.Add(Component->GetFName(), ClosestSelectedParent->GetFName());
}
// Record the temporary object into the name->object map
ObjectMap.Add(Component->GetFName(), CastChecked<UActorComponent>(DuplicatedComponent));
}
}
const FExportObjectInnerContext Context;
// Export the component object(s) to text for copying
for (const TPair<FName, UActorComponent*>& ObjectPair : ObjectMap)
{
// Get the component object to be copied
UActorComponent* ComponentToCopy = ObjectPair.Value;
check(ComponentToCopy);
// If this component object had a parent within the selected set
if (ParentMap.Contains(ComponentToCopy->GetFName()))
{
// Get the name of the parent component
FName ParentName = ParentMap[ComponentToCopy->GetFName()];
if (ObjectMap.Contains(ParentName))
{
// Ensure that this component is a scene component
USceneComponent* SceneComponent = Cast<USceneComponent>(ComponentToCopy);
if (SceneComponent)
{
// Set the attach parent to the matching parent object in the temporary set. This allows us to preserve hierarchy in the copied set.
SceneComponent->SetupAttachment(Cast<USceneComponent>(ObjectMap[ParentName]));
}
}
}
// Export the component object to the given string
UExporter::ExportToOutputDevice(&Context, ComponentToCopy, NULL, Archive, TEXT("copy"), 0, PPF_ExportsNotFullyQualified | PPF_Copy | PPF_Delimited, false, ComponentToCopy->GetOuter());
}
// Copy text to clipboard
if (DestinationData)
{
*DestinationData = MoveTemp(Archive);
}
else
{
FPlatformApplicationMisc::ClipboardCopy(*Archive);
}
}
bool FComponentEditorUtils::CanPasteComponents(const USceneComponent* RootComponent, bool bOverrideCanAttach, bool bPasteAsArchetypes, const FString* SourceData)
{
FString ClipboardContent;
if (SourceData)
{
ClipboardContent = *SourceData;
}
else
{
FPlatformApplicationMisc::ClipboardPaste(ClipboardContent);
}
// Obtain the component object text factory for the clipboard content and return whether or not we can use it
TSharedRef<FComponentObjectTextFactory> Factory = FComponentObjectTextFactory::Get(ClipboardContent, bPasteAsArchetypes);
return Factory->NewObjectMap.Num() > 0 && ( bOverrideCanAttach || Factory->CanAttachComponentsTo(RootComponent) );
}
void FComponentEditorUtils::PasteComponents(TArray<UActorComponent*>& OutPastedComponents, AActor* TargetActor, USceneComponent* TargetComponent, const FString* SourceData)
{
check(TargetActor);
// Get the text from the clipboard
FString TextToImport;
if (SourceData)
{
TextToImport = *SourceData;
}
else
{
FPlatformApplicationMisc::ClipboardPaste(TextToImport);
}
// Get a new component object factory for the clipboard content
TSharedRef<FComponentObjectTextFactory> Factory = FComponentObjectTextFactory::Get(TextToImport);
TargetActor->Modify();
USceneComponent* TargetParent = nullptr;
if (TargetComponent)
{
checkSlow(TargetActor == TargetComponent->GetOwner());
if (TargetActor->GetRootComponent() == TargetComponent)
{
TargetParent = TargetComponent;
}
else
{
TargetParent = TargetComponent->GetAttachParent();
}
}
for (const TPair<FName, UActorComponent*>& NewObjectPair : Factory->NewObjectMap)
{
// Get the component object instance
UActorComponent* NewActorComponent = NewObjectPair.Value;
check(NewActorComponent);
// Relocate the instance from the transient package to the Actor and assign it a unique object name
FString NewComponentName = FComponentEditorUtils::GenerateValidVariableNameFromAsset(NewActorComponent, TargetActor);
NewActorComponent->Rename(*NewComponentName, TargetActor, REN_DontCreateRedirectors | REN_DoNotDirty);
if (USceneComponent* NewSceneComponent = Cast<USceneComponent>(NewActorComponent))
{
// Default to attaching to the target component's parent if possible, otherwise attach to the root
USceneComponent* NewComponentParent = TargetParent ? TargetParent : TargetActor->GetRootComponent();
// Check to see if there's an entry for the current component in the set of parent components
if (Factory->ParentMap.Contains(NewObjectPair.Key))
{
// Get the parent component name
FName ParentName = Factory->ParentMap[NewObjectPair.Key];
if (Factory->NewObjectMap.Contains(ParentName))
{
// The parent should by definition be a scene component
NewComponentParent = CastChecked<USceneComponent>(Factory->NewObjectMap[ParentName]);
}
}
//@todo: Fix pasting when the pasted component was a root
//NewSceneComponent->UpdateComponentToWorld();
if (NewComponentParent)
{
// Reattach the current node to the parent node
NewSceneComponent->AttachToComponent(NewComponentParent, FAttachmentTransformRules::KeepRelativeTransform);
}
else
{
// There is no root component and this component isn't the child of another component in the map, so make it the root
TargetActor->SetRootComponent(NewSceneComponent);
}
}
TargetActor->AddInstanceComponent(NewActorComponent);
NewActorComponent->RegisterComponent();
OutPastedComponents.Add(NewActorComponent);
}
// Rerun construction scripts
TargetActor->RerunConstructionScripts();
}
void FComponentEditorUtils::GetComponentsFromClipboard(TMap<FName, FName>& OutParentMap, TMap<FName, UActorComponent*>& OutNewObjectMap, bool bGetComponentsAsArchetypes)
{
// Get the text from the clipboard
FString TextToImport;
FPlatformApplicationMisc::ClipboardPaste(TextToImport);
// Get a new component object factory for the clipboard content
TSharedRef<FComponentObjectTextFactory> Factory = FComponentObjectTextFactory::Get(TextToImport, bGetComponentsAsArchetypes);
// Return the created component mappings
OutParentMap = MoveTemp(Factory->ParentMap);
OutNewObjectMap = MoveTemp(Factory->NewObjectMap);
}
bool FComponentEditorUtils::CanDeleteComponent(const UActorComponent* ComponentToDelete)
{
// We can't delete non-instance components or the default scene root
return ComponentToDelete->CreationMethod == EComponentCreationMethod::Instance
&& ComponentToDelete->GetFName() != USceneComponent::GetDefaultSceneRootVariableName();
}
bool FComponentEditorUtils::CanDeleteComponents(const TArray<UActorComponent*>& ComponentsToDelete)
{
for (const UActorComponent* ComponentToDelete : ComponentsToDelete)
{
if (!CanDeleteComponent(ComponentToDelete))
{
return false;
}
}
return true;
}
int32 FComponentEditorUtils::DeleteComponents(const TArray<UActorComponent*>& ComponentsToDelete, UActorComponent*& OutComponentToSelect)
{
int32 NumDeletedComponents = 0;
TArray<AActor*> ActorsToReconstruct;
for (UActorComponent* ComponentToDelete : ComponentsToDelete)
{
if (ComponentToDelete->CreationMethod != EComponentCreationMethod::Instance)
{
// We can only delete instance components, so retain selection on the un-deletable component
OutComponentToSelect = ComponentToDelete;
continue;
}
AActor* Owner = ComponentToDelete->GetOwner();
check(Owner != nullptr);
// If necessary, determine the component that should be selected following the deletion of the indicated component
if (!OutComponentToSelect || ComponentToDelete == OutComponentToSelect)
{
USceneComponent* RootComponent = Owner->GetRootComponent();
if (RootComponent != ComponentToDelete)
{
// Worst-case, the root can be selected
OutComponentToSelect = RootComponent;
if (USceneComponent* ComponentToDeleteAsSceneComp = Cast<USceneComponent>(ComponentToDelete))
{
if (USceneComponent* ParentComponent = ComponentToDeleteAsSceneComp->GetAttachParent())
{
// The component to delete has a parent, so we select that in the absence of an appropriate sibling
OutComponentToSelect = ParentComponent;
// Try to select the sibling that immediately precedes the component to delete
TArray<USceneComponent*> Siblings;
ParentComponent->GetChildrenComponents(false, Siblings);
for (int32 i = 0; i < Siblings.Num() && ComponentToDelete != Siblings[i]; ++i)
{
if (IsValid(Siblings[i]))
{
OutComponentToSelect = Siblings[i];
}
}
}
}
else
{
// For a non-scene component, try to select the preceding non-scene component
for (UActorComponent* Component : Owner->GetComponents())
{
if (Component != nullptr)
{
if (Component == ComponentToDelete)
{
break;
}
else if (!Component->IsA<USceneComponent>())
{
OutComponentToSelect = Component;
}
}
}
}
}
else
{
OutComponentToSelect = nullptr;
}
}
// Defer reconstruction
ActorsToReconstruct.AddUnique(Owner);
// Actually delete the component
ComponentToDelete->Modify();
ComponentToDelete->DestroyComponent(true);
NumDeletedComponents++;
}
// Non-native components will be reinstanced, so we have to update the ptr after reconstruction
// in order to avoid pointing at an invalid (trash) instance after re-running construction scripts.
FName ComponentToSelectName;
const AActor* ComponentToSelectOwner = nullptr;
if (OutComponentToSelect && OutComponentToSelect->CreationMethod != EComponentCreationMethod::Native)
{
// Keep track of the pending selection's name and owner
ComponentToSelectName = OutComponentToSelect->GetFName();
ComponentToSelectOwner = OutComponentToSelect->GetOwner();
// Reset the ptr value - we'll reassign it after reconstruction
OutComponentToSelect = nullptr;
}
// Reconstruct owner instance(s) after deletion
for(AActor* ActorToReconstruct : ActorsToReconstruct)
{
check(ActorToReconstruct != nullptr);
ActorToReconstruct->RerunConstructionScripts();
// If this actor matches the owner of the component to be selected, find the new instance of the component in the actor
if (ComponentToSelectName != NAME_None && OutComponentToSelect == nullptr && ActorToReconstruct == ComponentToSelectOwner)
{
TInlineComponentArray<UActorComponent*> ActorComponents;
ActorToReconstruct->GetComponents(ActorComponents);
for (UActorComponent* ActorComponent : ActorComponents)
{
if (ActorComponent->GetFName() == ComponentToSelectName)
{
OutComponentToSelect = ActorComponent;
break;
}
}
}
}
return NumDeletedComponents;
}
UActorComponent* FComponentEditorUtils::DuplicateComponent(UActorComponent* TemplateComponent)
{
check(TemplateComponent);
UActorComponent* NewCloneComponent = nullptr;
AActor* Actor = TemplateComponent->GetOwner();
if (!TemplateComponent->IsVisualizationComponent() && Actor)
{
Actor->Modify();
FName NewComponentName = *FComponentEditorUtils::GenerateValidVariableNameFromAsset(TemplateComponent, Actor);
bool bKeepWorldLocationOnAttach = false;
const bool bTemplateTransactional = TemplateComponent->HasAllFlags(RF_Transactional);
TemplateComponent->SetFlags(RF_Transactional);
NewCloneComponent = DuplicateObject<UActorComponent>(TemplateComponent, Actor, NewComponentName );
NewCloneComponent->ClearFlags(RF_DefaultSubObject);
if (!bTemplateTransactional)
{
TemplateComponent->ClearFlags(RF_Transactional);
}
USceneComponent* NewSceneComponent = Cast<USceneComponent>(NewCloneComponent);
if (NewSceneComponent)
{
// Ensure the clone doesn't think it has children
FDirectAttachChildrenAccessor::Get(NewSceneComponent).Empty();
// If the clone is a scene component without an attach parent, attach it to the root (can happen when duplicating the root component)
if (!NewSceneComponent->GetAttachParent())
{
USceneComponent* RootComponent = Actor->GetRootComponent();
check(RootComponent);
// GetComponentTransform() is not a UPROPERTY, so make sure the clone has calculated it properly before attachment
NewSceneComponent->SetRelativeTransform_Direct(FTransform::Identity);
NewSceneComponent->UpdateComponentToWorld();
NewSceneComponent->SetupAttachment(RootComponent);
}
}
NewCloneComponent->OnComponentCreated();
// Add to SerializedComponents array so it gets saved
Actor->AddInstanceComponent(NewCloneComponent);
// Register the new component
NewCloneComponent->RegisterComponent();
// Rerun construction scripts
Actor->RerunConstructionScripts();
}
return NewCloneComponent;
}
void FComponentEditorUtils::AdjustComponentDelta(const USceneComponent* Component, FVector& Drag, FRotator& Rotation)
{
if (const USceneComponent* ParentSceneComp = Component->GetAttachParent())
{
const FTransform ParentToWorldSpace = ParentSceneComp->GetSocketTransform(Component->GetAttachSocketName());
if (!Component->IsUsingAbsoluteLocation())
{
//transform the drag vector in relative to the parent transform
Drag = ParentToWorldSpace.InverseTransformVectorNoScale(Drag);
//Now that we have a global drag we can apply the parent scale
Drag = Drag * ParentToWorldSpace.Inverse().GetScale3D();
}
if (!Component->IsUsingAbsoluteRotation())
{
Rotation = ( ParentToWorldSpace.Inverse().GetRotation() * Rotation.Quaternion() * ParentToWorldSpace.GetRotation() ).Rotator();
}
}
}
void FComponentEditorUtils::BindComponentSelectionOverride(USceneComponent* SceneComponent, bool bBind)
{
if (SceneComponent)
{
// If the scene component is a primitive component, set the override for it
UPrimitiveComponent* PrimitiveComponent = Cast<UPrimitiveComponent>(SceneComponent);
if (PrimitiveComponent && PrimitiveComponent->SelectionOverrideDelegate.IsBound() != bBind)
{
if (bBind)
{
PrimitiveComponent->SelectionOverrideDelegate = UPrimitiveComponent::FSelectionOverride::CreateUObject(GUnrealEd, &UUnrealEdEngine::IsComponentSelected);
}
else
{
PrimitiveComponent->SelectionOverrideDelegate.Unbind();
}
}
else
{
TArray<UPrimitiveComponent*> ComponentsToBind;
if (UChildActorComponent* ChildActorComponent = Cast<UChildActorComponent>(SceneComponent))
{
if (AActor* ChildActor = ChildActorComponent->GetChildActor())
{
ChildActor->GetComponents(ComponentsToBind,true);
}
}
// Otherwise, make sure the override is set properly on any attached editor-only primitive components (ex: billboards)
for (USceneComponent* Component : SceneComponent->GetAttachChildren())
{
if (UPrimitiveComponent* PrimComponent = Cast<UPrimitiveComponent>(Component))
{
if (PrimComponent->IsEditorOnly())
{
ComponentsToBind.Add(PrimComponent);
}
}
}
for (UPrimitiveComponent* PrimComponent : ComponentsToBind)
{
if (PrimComponent->SelectionOverrideDelegate.IsBound() != bBind)
{
if (bBind)
{
PrimComponent->SelectionOverrideDelegate = UPrimitiveComponent::FSelectionOverride::CreateUObject(GUnrealEd, &UUnrealEdEngine::IsComponentSelected);
}
else
{
PrimComponent->SelectionOverrideDelegate.Unbind();
}
}
}
}
}
}
bool FComponentEditorUtils::AttemptApplyMaterialToComponent(USceneComponent* SceneComponent, UMaterialInterface* MaterialToApply, int32 OptionalMaterialSlot)
{
bool bResult = false;
UMeshComponent* MeshComponent = Cast<UMeshComponent>(SceneComponent);
UDecalComponent* DecalComponent = Cast<UDecalComponent>(SceneComponent);
UMaterial* BaseMaterial = MaterialToApply->GetBaseMaterial();
bool bCanApplyToComponent = DecalComponent || ( MeshComponent && BaseMaterial && BaseMaterial->MaterialDomain != MD_DeferredDecal && BaseMaterial->MaterialDomain != MD_UI );
// We can only apply a material to a mesh or a decal
if (bCanApplyToComponent && (MeshComponent || DecalComponent) )
{
bResult = true;
const FScopedTransaction Transaction(LOCTEXT("DropTarget_UndoSetComponentMaterial", "Assign Material to Component (Drag and Drop)"));
FProperty* Property = FindFProperty<FProperty>(SceneComponent->GetClass(), MeshComponent ? "OverrideMaterials" : "DecalMaterial");
SceneComponent->Modify();
SceneComponent->PreEditChange(Property);
if (MeshComponent)
{
// OK, we need to figure out how many material slots this mesh component/static mesh has.
// Start with the actor's material count, then drill into the static/skeletal mesh to make sure
// we have the right total.
int32 MaterialCount = FMath::Max(MeshComponent->OverrideMaterials.Num(), MeshComponent->GetNumMaterials());
// Do we have an overridable material at the appropriate slot?
if (MaterialCount > 0 && OptionalMaterialSlot < MaterialCount)
{
if (OptionalMaterialSlot == -1)
{
// Apply the material to every slot.
for (int32 CurMaterialIndex = 0; CurMaterialIndex < MaterialCount; ++CurMaterialIndex)
{
MeshComponent->SetMaterial(CurMaterialIndex, MaterialToApply);
}
}
else
{
// Apply only to the indicated slot.
MeshComponent->SetMaterial(OptionalMaterialSlot, MaterialToApply);
}
}
}
else
{
DecalComponent->SetMaterial(0, MaterialToApply);
}
SceneComponent->MarkRenderStateDirty();
FPropertyChangedEvent PropertyChangedEvent(Property, EPropertyChangeType::ValueSet, { SceneComponent });
SceneComponent->PostEditChangeProperty(PropertyChangedEvent);
GEditor->OnSceneMaterialsModified();
}
return bResult;
}
FName FComponentEditorUtils::FindVariableNameGivenComponentInstance(const UActorComponent* ComponentInstance)
{
check(ComponentInstance != nullptr);
// When names mismatch, try finding a differently named variable pointing to the the component (the mismatch should only be possible for native components)
auto FindPropertyReferencingComponent = [](const UActorComponent* Component) -> FProperty*
{
if (AActor* OwnerActor = Component->GetOwner())
{
UClass* OwnerClass = OwnerActor->GetClass();
AActor* OwnerCDO = CastChecked<AActor>(OwnerClass->GetDefaultObject());
check(OwnerCDO->HasAnyFlags(RF_ClassDefaultObject));
for (TFieldIterator<FObjectProperty> PropIt(OwnerClass, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt)
{
FObjectProperty* TestProperty = *PropIt;
if (Component->GetClass()->IsChildOf(TestProperty->PropertyClass))
{
void* TestPropertyInstanceAddress = TestProperty->ContainerPtrToValuePtr<void>(OwnerCDO);
UObject* ObjectPointedToByProperty = TestProperty->GetObjectPropertyValue(TestPropertyInstanceAddress);
if (ObjectPointedToByProperty == Component)
{
// This property points to the component archetype, so it's an anchor even if it was named wrong
return TestProperty;
}
}
}
for (TFieldIterator<FArrayProperty> PropIt(OwnerClass, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt)
{
FArrayProperty* TestProperty = *PropIt;
void* ArrayPropInstAddress = TestProperty->ContainerPtrToValuePtr<void>(OwnerCDO);
FObjectProperty* ArrayEntryProp = CastField<FObjectProperty>(TestProperty->Inner);
if ((ArrayEntryProp == nullptr) || !ArrayEntryProp->PropertyClass->IsChildOf<UActorComponent>())
{
continue;
}
FScriptArrayHelper ArrayHelper(TestProperty, ArrayPropInstAddress);
for (int32 ComponentIndex = 0; ComponentIndex < ArrayHelper.Num(); ++ComponentIndex)
{
UObject* ArrayElement = ArrayEntryProp->GetObjectPropertyValue(ArrayHelper.GetRawPtr(ComponentIndex));
if (ArrayElement == Component)
{
return TestProperty;
}
}
}
}
return nullptr;
};
// First see if the name just works
if (AActor* OwnerActor = ComponentInstance->GetOwner())
{
UClass* OwnerActorClass = OwnerActor->GetClass();
if (FObjectProperty* TestProperty = FindFProperty<FObjectProperty>(OwnerActorClass, ComponentInstance->GetFName()))
{
if (ComponentInstance->GetClass()->IsChildOf(TestProperty->PropertyClass))
{
return TestProperty->GetFName();
}
}
if (FProperty* ReferencingProp = FindPropertyReferencingComponent(ComponentInstance))
{
return ReferencingProp->GetFName();
}
}
if (UActorComponent* Archetype = Cast<UActorComponent>(ComponentInstance->GetArchetype()))
{
if (FProperty* ReferencingProp = FindPropertyReferencingComponent(Archetype))
{
return ReferencingProp->GetFName();
}
}
return NAME_None;
}
void FComponentEditorUtils::FillComponentContextMenuOptions(UToolMenu* Menu, const TArray<UActorComponent*>& SelectedComponents)
{
// Basic commands
{
FToolMenuSection& Section = Menu->AddSection("EditComponent", LOCTEXT("EditComponentHeading", "Edit"));
Section.AddMenuEntry(FGenericCommands::Get().Cut);
Section.AddMenuEntry(FGenericCommands::Get().Copy);
Section.AddMenuEntry(FGenericCommands::Get().Paste);
Section.AddMenuEntry(FGenericCommands::Get().Duplicate);
Section.AddMenuEntry(FGenericCommands::Get().Delete);
Section.AddMenuEntry(FGenericCommands::Get().Rename);
}
if (SelectedComponents.Num() == 1)
{
UActorComponent* Component = SelectedComponents[0];
if (Component->GetClass()->ClassGeneratedBy)
{
{
FToolMenuSection& Section = Menu->AddSection("ComponentAsset", LOCTEXT("ComponentAssetHeading", "Asset"));
Section.AddMenuEntry(
"GoToBlueprintForComponent",
FText::Format(LOCTEXT("GoToBlueprintForComponent", "Edit {0}"), FText::FromString(Component->GetClass()->ClassGeneratedBy->GetName())),
LOCTEXT("EditBlueprintForComponent_ToolTip", "Edits the Blueprint Class that defines this component."),
FSlateIconFinder::FindIconForClass(Component->GetClass()),
FUIAction(
FExecuteAction::CreateStatic(&FComponentEditorUtils::OnEditBlueprintComponent, Component->GetClass()->ClassGeneratedBy.Get()),
FCanExecuteAction()));
Section.AddMenuEntry(
"GoToAssetForComponent",
LOCTEXT("GoToAssetForComponent", "Find Class in Content Browser"),
LOCTEXT("GoToAssetForComponent_ToolTip", "Summons the content browser and goes to the class for this component."),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "SystemWideCommands.FindInContentBrowser"),
FUIAction(
FExecuteAction::CreateStatic(&FComponentEditorUtils::OnGoToComponentAssetInBrowser, Component->GetClass()->ClassGeneratedBy.Get()),
FCanExecuteAction()));
}
}
else
{
if (ensure(GUnrealEd) && GUnrealEd->GetUnrealEdOptions()->IsCPPAllowed())
{
FToolMenuSection& Section = Menu->AddSection("ComponentCode", LOCTEXT("ComponentCodeHeading", "C++"));
if (FSourceCodeNavigation::IsCompilerAvailable())
{
FString ClassHeaderPath;
if (FSourceCodeNavigation::FindClassHeaderPath(Component->GetClass(), ClassHeaderPath) && IFileManager::Get().FileSize(*ClassHeaderPath) != INDEX_NONE)
{
const FString CodeFileName = FPaths::GetCleanFilename(*ClassHeaderPath);
Section.AddMenuEntry(
"GoToCodeForComponent",
FText::Format(LOCTEXT("GoToCodeForComponent", "Open {0}"), FText::FromString(CodeFileName)),
FText::Format(LOCTEXT("GoToCodeForComponent_ToolTip", "Opens the header file for this component ({0}) in a code editing program"), FText::FromString(CodeFileName)),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateStatic(&FComponentEditorUtils::OnOpenComponentCodeFile, ClassHeaderPath),
FCanExecuteAction()));
}
Section.AddMenuEntry(
"GoToAssetForComponent",
LOCTEXT("GoToAssetForComponent", "Find Class in Content Browser"),
LOCTEXT("GoToAssetForComponent_ToolTip", "Summons the content browser and goes to the class for this component."),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "SystemWideCommands.FindInContentBrowser"),
FUIAction(
FExecuteAction::CreateStatic(&FComponentEditorUtils::OnGoToComponentAssetInBrowser, (UObject*)Component->GetClass()),
FCanExecuteAction()));
}
}
}
}
}
UActorComponent* FComponentEditorUtils::FindMatchingComponent(const UActorComponent* ComponentInstance, const TInlineComponentArray<UActorComponent*>& ComponentList)
{
if (ComponentInstance == nullptr)
{
return nullptr;
}
TInlineComponentArray<UActorComponent*> FoundComponents;
UActorComponent* LastFoundComponent = nullptr;
for (UActorComponent* Component : ComponentList)
{
// Early out on pointer match
if (ComponentInstance == Component)
{
return Component;
}
if (ComponentInstance->GetFName() == Component->GetFName())
{
FoundComponents.Add(Component);
LastFoundComponent = Component;
}
}
// No match or 1 match avoid sorting
if (FoundComponents.Num() <= 1)
{
return LastFoundComponent;
}
if (const USceneComponent* CurrentSceneComponent = Cast<USceneComponent>(ComponentInstance))
{
// Sort by matching hierarchy
FoundComponents.Sort([&](const UActorComponent& ComponentA, const UActorComponent& ComponentB)
{
const USceneComponent* SceneComponentA = Cast<USceneComponent>(&ComponentA);
const USceneComponent* SceneComponentB = Cast<USceneComponent>(&ComponentB);
if (SceneComponentB == nullptr)
{
return true;
}
else if (SceneComponentA == nullptr)
{
return false;
}
const USceneComponent* AttachParentA = SceneComponentA->GetAttachParent();
const USceneComponent* AttachParentB = SceneComponentB->GetAttachParent();
const USceneComponent* CurrentParent = CurrentSceneComponent->GetAttachParent();
// No parents...
if (CurrentParent == nullptr)
{
return AttachParentA == nullptr;
}
bool MatchA = AttachParentA != nullptr && AttachParentA->GetFName() == CurrentParent->GetFName();
bool MatchB = AttachParentB != nullptr && AttachParentB->GetFName() == CurrentParent->GetFName();
while (MatchA && MatchB)
{
AttachParentA = AttachParentA->GetAttachParent();
AttachParentB = AttachParentB->GetAttachParent();
CurrentParent = CurrentParent->GetAttachParent();
if (CurrentParent == nullptr)
{
return AttachParentA == nullptr;
}
MatchA = AttachParentA != nullptr && AttachParentA->GetFName() == CurrentParent->GetFName();
MatchB = AttachParentB != nullptr && AttachParentB->GetFName() == CurrentParent->GetFName();
}
return MatchA;
});
}
return FoundComponents[0];
}
FComponentReference FComponentEditorUtils::MakeComponentReference(const AActor* InExpectedComponentOwner, const UActorComponent* InComponent)
{
FComponentReference Result;
if (InComponent)
{
AActor* ComponentOwner = InComponent->GetOwner();
const AActor* Owner = InExpectedComponentOwner;
if (ComponentOwner && ComponentOwner != Owner)
{
Result.OtherActor = ComponentOwner;
Owner = ComponentOwner;
}
if (InComponent->CreationMethod == EComponentCreationMethod::Native || InComponent->CreationMethod == EComponentCreationMethod::SimpleConstructionScript)
{
Result.ComponentProperty = FComponentEditorUtils::FindVariableNameGivenComponentInstance(InComponent);
}
if (Result.ComponentProperty.IsNone() && InComponent->CreationMethod != EComponentCreationMethod::UserConstructionScript)
{
Result.PathToComponent = InComponent->GetPathName(ComponentOwner);
}
}
return Result;
}
void FComponentEditorUtils::OnGoToComponentAssetInBrowser(UObject* Asset)
{
TArray<UObject*> Objects;
Objects.Add(Asset);
GEditor->SyncBrowserToObjects(Objects);
}
void FComponentEditorUtils::OnOpenComponentCodeFile(const FString CodeFileName)
{
const FString AbsoluteHeaderPath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*CodeFileName);
FSourceCodeNavigation::OpenSourceFile(AbsoluteHeaderPath);
}
void FComponentEditorUtils::OnEditBlueprintComponent(UObject* Blueprint)
{
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(Blueprint);
}
#undef LOCTEXT_NAMESPACE