// 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 ParentMap; // Name->Instance object mapping TMap 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(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 Get(const FString& InTextBuffer, bool bPasteAsArchetypes = false) { // Construct a new instance TSharedPtr 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 ActorComponents; if (UActorComponent* NewActorComponent = Cast(NewObject)) { ActorComponents.Add(NewActorComponent); } else if (AActor* NewActor = Cast(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(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 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(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 PropIt(OwnerClass, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt) { FArrayProperty* TestProperty = *PropIt; void* ArrayPropInstAddress = TestProperty->ContainerPtrToValuePtr(ComponentOuter); // Ensure that this property is valid FObjectProperty* ArrayEntryProp = CastField(TestProperty->Inner); if ((ArrayEntryProp == nullptr) || !ArrayEntryProp->PropertyClass->IsChildOf() || ((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 PropIt(OwnerClass, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt) { FMapProperty* TestProperty = *PropIt; void* MapPropInstAddress = TestProperty->ContainerPtrToValuePtr(ComponentOuter); // Ensure that this property is valid and that it is marked as visible in the editor FObjectProperty* MapValProp = CastField(TestProperty->ValueProp); if ((MapValProp == nullptr) || !MapValProp->PropertyClass->IsChildOf() || ((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 PropIt(OwnerClass, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt) { FSetProperty* TestProperty = *PropIt; void* SetPropInstAddress = TestProperty->ContainerPtrToValuePtr(ComponentOuter); // Ensure that this property is valid and that it is marked visible FObjectProperty* SetValProp = CastField(TestProperty->ElementProp); if ((SetValProp == nullptr) || !SetValProp->PropertyClass->IsChildOf() || ((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(ComponentOwner, *InString); bool bNameIsAvailable = Object == nullptr || Object == ComponentToIgnore; return bNameIsAvailable; } FString FComponentEditorUtils::GenerateValidVariableName(TSubclassOf 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(Asset)) { if (!Class->HasAnyClassFlags(CLASS_CompiledFromBlueprint)) { AssetName.RemoveFromEnd(TEXT("Component")); } else { AssetName.RemoveFromEnd("_C"); } } else if (UActorComponent* Comp = Cast (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& ComponentList) { // Find the most recent parent that is part of the ComponentList if (USceneComponent* ChildAsScene = Cast(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& 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& 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 ParentMap; TMap 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(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(DuplicatedComponent)); } } const FExportObjectInnerContext Context; // Export the component object(s) to text for copying for (const TPair& 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(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(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 Factory = FComponentObjectTextFactory::Get(ClipboardContent, bPasteAsArchetypes); return Factory->NewObjectMap.Num() > 0 && ( bOverrideCanAttach || Factory->CanAttachComponentsTo(RootComponent) ); } void FComponentEditorUtils::PasteComponents(TArray& 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 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& 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(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(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& OutParentMap, TMap& 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 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& ComponentsToDelete) { for (const UActorComponent* ComponentToDelete : ComponentsToDelete) { if (!CanDeleteComponent(ComponentToDelete)) { return false; } } return true; } int32 FComponentEditorUtils::DeleteComponents(const TArray& ComponentsToDelete, UActorComponent*& OutComponentToSelect) { int32 NumDeletedComponents = 0; TArray 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(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 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()) { 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 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(TemplateComponent, Actor, NewComponentName ); NewCloneComponent->ClearFlags(RF_DefaultSubObject); if (!bTemplateTransactional) { TemplateComponent->ClearFlags(RF_Transactional); } USceneComponent* NewSceneComponent = Cast(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(SceneComponent); if (PrimitiveComponent && PrimitiveComponent->SelectionOverrideDelegate.IsBound() != bBind) { if (bBind) { PrimitiveComponent->SelectionOverrideDelegate = UPrimitiveComponent::FSelectionOverride::CreateUObject(GUnrealEd, &UUnrealEdEngine::IsComponentSelected); } else { PrimitiveComponent->SelectionOverrideDelegate.Unbind(); } } else { TArray ComponentsToBind; if (UChildActorComponent* ChildActorComponent = Cast(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(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(SceneComponent); UDecalComponent* DecalComponent = Cast(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(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(OwnerClass->GetDefaultObject()); check(OwnerCDO->HasAnyFlags(RF_ClassDefaultObject)); for (TFieldIterator PropIt(OwnerClass, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt) { FObjectProperty* TestProperty = *PropIt; if (Component->GetClass()->IsChildOf(TestProperty->PropertyClass)) { void* TestPropertyInstanceAddress = TestProperty->ContainerPtrToValuePtr(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 PropIt(OwnerClass, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt) { FArrayProperty* TestProperty = *PropIt; void* ArrayPropInstAddress = TestProperty->ContainerPtrToValuePtr(OwnerCDO); FObjectProperty* ArrayEntryProp = CastField(TestProperty->Inner); if ((ArrayEntryProp == nullptr) || !ArrayEntryProp->PropertyClass->IsChildOf()) { 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(OwnerActorClass, ComponentInstance->GetFName())) { if (ComponentInstance->GetClass()->IsChildOf(TestProperty->PropertyClass)) { return TestProperty->GetFName(); } } if (FProperty* ReferencingProp = FindPropertyReferencingComponent(ComponentInstance)) { return ReferencingProp->GetFName(); } } if (UActorComponent* Archetype = Cast(ComponentInstance->GetArchetype())) { if (FProperty* ReferencingProp = FindPropertyReferencingComponent(Archetype)) { return ReferencingProp->GetFName(); } } return NAME_None; } void FComponentEditorUtils::FillComponentContextMenuOptions(UToolMenu* Menu, const TArray& 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& ComponentList) { if (ComponentInstance == nullptr) { return nullptr; } TInlineComponentArray 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(ComponentInstance)) { // Sort by matching hierarchy FoundComponents.Sort([&](const UActorComponent& ComponentA, const UActorComponent& ComponentB) { const USceneComponent* SceneComponentA = Cast(&ComponentA); const USceneComponent* SceneComponentB = Cast(&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 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()->OpenEditorForAsset(Blueprint); } #undef LOCTEXT_NAMESPACE