惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
CXSECURITY Database RSS Feed - CXSecurity.com
Stack Overflow Blog
Stack Overflow Blog
月光博客
月光博客
T
Threat Research - Cisco Blogs
小众软件
小众软件
有赞技术团队
有赞技术团队
酷 壳 – CoolShell
酷 壳 – CoolShell
Apple Machine Learning Research
Apple Machine Learning Research
C
Cyber Attacks, Cyber Crime and Cyber Security
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
T
Tailwind CSS Blog
Cisco Talos Blog
Cisco Talos Blog
V
V2EX
博客园 - 【当耐特】
C
Cybersecurity and Infrastructure Security Agency CISA
Hugging Face - Blog
Hugging Face - Blog
The Cloudflare Blog
The Last Watchdog
The Last Watchdog
Simon Willison's Weblog
Simon Willison's Weblog
T
Threatpost
S
Secure Thoughts
O
OpenAI News
P
Proofpoint News Feed
S
SegmentFault 最新的问题
Forbes - Security
Forbes - Security
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Application and Cybersecurity Blog
Application and Cybersecurity Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Last Week in AI
Last Week in AI
宝玉的分享
宝玉的分享
Scott Helme
Scott Helme
T
Tenable Blog
A
Arctic Wolf
L
LINUX DO - 热门话题
爱范儿
爱范儿
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
www.infosecurity-magazine.com
www.infosecurity-magazine.com
V
Visual Studio Blog
Hacker News: Ask HN
Hacker News: Ask HN
Hacker News - Newest:
Hacker News - Newest: "LLM"
腾讯CDC
博客园 - Franky
WordPress大学
WordPress大学
Know Your Adversary
Know Your Adversary
博客园_首页
雷峰网
雷峰网
IT之家
IT之家
PCI Perspectives
PCI Perspectives
L
LINUX DO - 最新话题
H
Heimdal Security Blog

博客园 - gearslogy

Houdini HDANC -> HDA Houdini NC HDA -> HDA PBRT 蒙特卡洛采样 BRDF是BSSRDF的一个特殊形式 来自伟大的AI PBRT中的RayDifferentials Unreal Fur 假毛发 草地 Grass PBRT v2中,隐士表面、三角形的dpdu dpdv dndu dndv 思考 Houdini Vulkan HeightBlend 高度混合 Indexmap Vulkan矩形绘制顺序小坑 C++ Runtime Reflection QML NextQT 随手 HDK门格海绵 clang reflection Master LLVM GEP 类型擦除TypeErase Qt问题记录 现代CPP设计模式 CPP2nd CRTP Facade 模式 billboard暴力实现 Fibonacci各种玩法 ubuntu升级编译器
UE5.7编辑器扩展
gearslogy · 2026-03-04 · via 博客园 - gearslogy

过年花了一周用AI + 教程:Create Custom Editor Tools by Vince Petrelli(崇高敬意) 快速搞了一波UE API.毕竟搞游戏的不会点UE API都不好意思打招呼

教程属于初级阶段,但是对于UE API学习是非常好的切入点

Delegate设计的还是很有意思,其实在当今,lambda已经可以打天下了。没必要走FOnDelegate::CreateXXX

Reflection 大大简化了代码

注:可能跟原教程代码不一样。

1: Slate GUI 

image

 AdvanceDeletionWidgets.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "Widgets/SCompoundWidget.h"
#include "TypeDefine.hpp"

class SAdvanceDeletionTab: public SCompoundWidget
{
    SLATE_BEGIN_ARGS(SAdvanceDeletionTab){}
        SLATE_ARGUMENT(FString, TestString)
        SLATE_ARGUMENT(ArrayAssetDataPtr , AssetsData)
    SLATE_END_ARGS()
public:
    void Construct(const FArguments&);
    
private:
    TArray<AssetDataPtr> storedAssetsData; 
    TArray<AssetDataPtr> displayedAssetsData;
    
    // SListView Create
    TSharedRef<SListView<AssetDataPtr>> ConstructListView();
    TSharedPtr<SListView<AssetDataPtr>> constructedListView;
    void refreshListView();
    void OnNavigateToContentView(AssetDataPtr adp);
    TSharedRef<ITableRow> OnGenerateRow(AssetDataPtr item, const TSharedRef<STableViewBase> & ownerTable);
    // CheckBox Create
    TArray<TSharedPtr<SCheckBox>> checkBoxes;
    TSharedRef<SCheckBox> ConstructCheckBox(const AssetDataPtr & adp);
    void OnCheckBoxStateChanged(ECheckBoxState newState, AssetDataPtr adp);
    
    // TextBlock Create
    TSharedRef<STextBlock> ConstructTextBlock(const FText &text, const FSlateFontInfo font);
    
    // ----------------- SComboBox Start--------------------
    TArray<StringPtr> ComboBoxMenuItems;
    TSharedRef<SComboBox<StringPtr>> ConstructComboBox();
    TSharedRef<SWidget> OnGenerateComboBoxWidget(StringPtr row);
    void OnComboBoxSelectionChanged(StringPtr selectedItem,  ESelectInfo::Type selectType);
    TSharedPtr<STextBlock> ComboBoxDisplayTextBlock;
    // ----------------- SComboBox end---------------------
    

    
    
    // ------------------DeleteAll SelectAll DeselectAll START----------------------------
    TSharedRef<SButton> ConstructDeleteAllButton();
    TSharedRef<SButton> ConstructSelectAllButton();
    TSharedRef<SButton> ConstructDeselectAllButton();
    TSharedRef<STextBlock> ConstructButtonContent(const FString &text);
    //event
    FReply OnDeleteAllButtonClicked();
    FReply OnSelectAllButtonClicked();
    FReply OnDeselectAllButtonClicked(); 
    TArray<AssetDataPtr> toDeleteAssets;  // GUI Selected Assets: from check box
    // ------------------DeleteAll SelectAll DeselectAll END ----------------------------
    
};

View Code

AdvanceDeletionWidgets.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "SlateWidgets/AdvanceDeletionWidgets.h"

#include "DebugHeader.hpp"
#include "SlateBasics.h"
#include "SuperManager.h"

#define LIST_ALL TEXT("List All Assets")
#define LIST_UNUSED TEXT("List Unused Assets")

void SAdvanceDeletionTab::Construct(const FArguments&InArgs)
{
    
    bCanSupportFocus = true;
    // -------- Data In --------
    FString testString = InArgs._TestString;
    storedAssetsData = InArgs._AssetsData;
    displayedAssetsData = storedAssetsData;
    // -------- Data In -----------
    
    
    // ------ Clear some Array ---------
    toDeleteAssets.Empty();
    checkBoxes.Empty();
    // ------ Clear some Array ---------
    
    
    // ------ ComboBoxMenus -------
    ComboBoxMenuItems.Add(MakeShared<FString>(LIST_ALL) );
    ComboBoxMenuItems.Add(MakeShared<FString>(LIST_UNUSED));
    // ------ ComboBoxMenus -------
    
    
    
    FSlateFontInfo titleFont = FCoreStyle::Get().GetFontStyle(FName("EmbossedText"));
    titleFont.Size = 30;
    
    ChildSlot[
        //SNew(STextBlock).Text(FText::FromString(testString))
        SNew(SVerticalBox)
        +SVerticalBox::Slot().AutoHeight()
        [
            SNew(STextBlock)
            .Text(FText::FromString(InArgs._TestString) )
            .Font(titleFont)
            .Justification(ETextJustify::Type::Center)
            .ColorAndOpacity(FColor::Yellow)
        ]

        // Drop And Help Text
        +SVerticalBox::Slot().AutoHeight()
        [
            SNew(SHorizontalBox)
            +SHorizontalBox::Slot().AutoWidth()
            [
                ConstructComboBox()
            ]
        ]
        // Asset List
        +SVerticalBox::Slot().VAlign(VAlign_Fill)
        [
            SNew(SScrollBox)
            +SScrollBox::Slot()
            [
                ConstructListView()
            ]
        ]
        // Button List
        +SVerticalBox::Slot().AutoHeight()
        [
            SNew(SHorizontalBox)
            +SHorizontalBox::Slot().Padding(5.0f)
            [
                ConstructDeleteAllButton()
            ]
            +SHorizontalBox::Slot().Padding(5.0f)
            [
                ConstructSelectAllButton()
            ]
            +SHorizontalBox::Slot().Padding(5.0f)
            [
                ConstructDeselectAllButton()
                
            ]
        ]
        
    ];
}

TSharedRef<SComboBox<StringPtr>> SAdvanceDeletionTab::ConstructComboBox()
{
    auto ret = SNew(SComboBox<StringPtr>)
        .OptionsSource(&ComboBoxMenuItems)
        .OnGenerateWidget(this, &SAdvanceDeletionTab::OnGenerateComboBoxWidget)
        .OnSelectionChanged(this, &SAdvanceDeletionTab::OnComboBoxSelectionChanged)
        [
            SAssignNew(ComboBoxDisplayTextBlock, STextBlock)
            .Text(FText::FromString("List Assets Option") )
        ];
    return ret;
}

TSharedRef<SWidget> SAdvanceDeletionTab::OnGenerateComboBoxWidget(StringPtr row){
    auto ret = SNew(STextBlock).Text(ConvertToText(*row.Get()) );
    return ret;
}

void SAdvanceDeletionTab::OnComboBoxSelectionChanged(StringPtr selectedItem, ESelectInfo::Type selectType)
{
    FSuperManagerModule & SuperMangerModule = FModuleManager::LoadModuleChecked<FSuperManagerModule>("SuperManager");
    FString selectedText = *selectedItem;
    ComboBoxDisplayTextBlock->SetText(ConvertToText(selectedText));

    if (selectedText.Equals(LIST_ALL,ESearchCase::CaseSensitive)){
        Print(FColor::Green, "Selected ALL TYPE" );
        displayedAssetsData = storedAssetsData;
        refreshListView();
        
    }
    
    if (selectedText.Equals(LIST_UNUSED,ESearchCase::CaseSensitive)){
        Print(FColor::Green, "Selected UNUSED TYPE" );
        SuperMangerModule.filterUnusedAssets(storedAssetsData, displayedAssetsData);
        refreshListView();
    }
    
}


TSharedRef<SListView<AssetDataPtr>> SAdvanceDeletionTab::ConstructListView(){
    auto view = SNew(SListView<AssetDataPtr>)
        .ListItemsSource(&displayedAssetsData)
        .OnGenerateRow(this, &SAdvanceDeletionTab::OnGenerateRow)
        .OnMouseButtonClick(this, &SAdvanceDeletionTab::OnNavigateToContentView);
    constructedListView = view.ToSharedPtr();
    return view;
}

void SAdvanceDeletionTab::OnNavigateToContentView(AssetDataPtr adp){
    FSuperManagerModule & SuperMangerModule = FModuleManager::LoadModuleChecked<FSuperManagerModule>("SuperManager");
    SuperMangerModule.SyncCBToClickedAssetAssetForAssetList(adp->GetSoftObjectPath().ToString() );
}

void SAdvanceDeletionTab::refreshListView(){
    // 1. Clear GUI selected assets that will to delete
    toDeleteAssets.Empty();
    // 2. Clear GUI Checked box
    checkBoxes.Empty();
    if (constructedListView.IsValid())
        constructedListView->RebuildList();
}


TSharedRef<ITableRow> SAdvanceDeletionTab::OnGenerateRow(AssetDataPtr item, const TSharedRef<STableViewBase>& ownerTable)
{    
    FSlateFontInfo classTextFont = FCoreStyle::Get().GetFontStyle(FName("EmbossedText"));
    auto assetNameFont = classTextFont;
    classTextFont.Size = 10;
    assetNameFont.Size = 15;
    
    // SHorizontalBox
    // CheckBox - AssetClassName - AssetName - Button
    const FText name = ConvertToText(item->AssetName);
    const FText className = ConvertToText(item->GetClass()->GetFName());
    
    TSharedRef<STableRow<AssetDataPtr>> ret  = SNew(STableRow<AssetDataPtr>, ownerTable).Padding(FMargin(5.f))
    [
        SNew(SHorizontalBox)
        +SHorizontalBox::Slot().HAlign(HAlign_Left).VAlign(VAlign_Center).FillWidth(0.05f)
        [
            ConstructCheckBox(item) // CheckBox
        ]
            
        +SHorizontalBox::Slot().HAlign(HAlign_Center).VAlign(VAlign_Fill).FillWidth(0.6f)
        [
            ConstructTextBlock(className, classTextFont) // class Name
        ]
        
        +SHorizontalBox::Slot().HAlign(HAlign_Left).VAlign(VAlign_Fill)
        [
            ConstructTextBlock(name, assetNameFont) // asset Name
        ]
    
        +SHorizontalBox::Slot().HAlign(HAlign_Right)
        [
            SNew(SButton).Text(FText::FromString("Delete") ).OnClicked_Lambda([this,item](){
                FSuperManagerModule & SuperMangerModule = FModuleManager::LoadModuleChecked<FSuperManagerModule>("SuperManager");
                const auto isDeleted = SuperMangerModule.removeSingleAsset(*item.Get());
                if (isDeleted){
                    if (storedAssetsData.Contains(item) ) storedAssetsData.Remove(item);
                    if (displayedAssetsData.Contains(item)) displayedAssetsData.Remove(item);
                    refreshListView();
                }
                return FReply::Handled();
            })
        ]
    ];
    return ret;
}

TSharedRef<SCheckBox> SAdvanceDeletionTab::ConstructCheckBox(const AssetDataPtr& adp){
    auto ret = SNew(SCheckBox)
        .Visibility(EVisibility::Visible)
        .Type(ESlateCheckBoxType::Type::CheckBox)
        .OnCheckStateChanged(this, &SAdvanceDeletionTab::OnCheckBoxStateChanged, adp);
    checkBoxes.Add(ret);
    return ret;
}

void SAdvanceDeletionTab::OnCheckBoxStateChanged(ECheckBoxState newState, AssetDataPtr adp){
    Print(FColor::Black, FString("Selection Changed:") + ConvertToFString(adp->AssetName) );
    switch (newState)
    {
    case ECheckBoxState::Checked:
        toDeleteAssets.AddUnique(adp);
        break;
    case ECheckBoxState::Unchecked:
        if (toDeleteAssets.Contains(adp)) toDeleteAssets.Remove(adp);
        break;
    case ECheckBoxState::Undetermined:
        break;
    }
}

TSharedRef<STextBlock> SAdvanceDeletionTab::ConstructTextBlock(const FText& text, const FSlateFontInfo font){
    return SNew(STextBlock).Text(text).Font(font).ColorAndOpacity(FColor::White);
}

// Event for ButtonTab : DeleteAll SelectAll DeselectAll
TSharedRef<SButton> SAdvanceDeletionTab::ConstructDeleteAllButton(){
    auto ret = SNew(SButton)
        .ContentPadding(FMargin(5.f))
        .OnClicked(this, &SAdvanceDeletionTab::OnDeleteAllButtonClicked);
    ret->SetContent(ConstructButtonContent("Delete All"));
    return ret;
}

TSharedRef<SButton> SAdvanceDeletionTab::ConstructSelectAllButton()
{
    auto ret = SNew(SButton)
        .ContentPadding(FMargin(5.f))
        .OnClicked(this, &SAdvanceDeletionTab::OnSelectAllButtonClicked);
    ret->SetContent(ConstructButtonContent("Select All"));
    return ret;
}

TSharedRef<SButton> SAdvanceDeletionTab::ConstructDeselectAllButton()
{
    auto ret = SNew(SButton)
        .ContentPadding(FMargin(5.f))
        .OnClicked(this, &SAdvanceDeletionTab::OnDeselectAllButtonClicked);
    ret->SetContent(ConstructButtonContent("Deselect All"));
    return ret;
}


FReply SAdvanceDeletionTab::OnDeleteAllButtonClicked(){
    if (toDeleteAssets.Num()==0){
        ShowMessageDialog("No Assets, Delete Error, Please select assets to delete", EAppMsgType::Ok);
    }
    TArray<FAssetData> willDeleteAssets; // delete this assets use FSuperManager
    for (const auto & item : toDeleteAssets){
        willDeleteAssets.Add(*item.Get());
    }
    FSuperManagerModule & SuperMangerModule = FModuleManager::LoadModuleChecked<FSuperManagerModule>("SuperManager");
    if (SuperMangerModule.removeMultiAssets(willDeleteAssets) )
    {
        // 1. Remove ListView SourceItem Data
        for (const auto & item : toDeleteAssets){
            if (storedAssetsData.Contains(item) ) storedAssetsData.Remove(item);
            if (displayedAssetsData.Contains(item) ) displayedAssetsData.Remove(item);
        }
        // 2. Refresh GUI
        refreshListView();
    }
    return FReply::Handled();
}

FReply SAdvanceDeletionTab::OnSelectAllButtonClicked(){
    for (auto &checkbox: checkBoxes)
    {
        if (not checkbox->IsChecked())
        {
            //checkbox->SetIsChecked(ECheckBoxState::Checked);
            checkbox->ToggleCheckedState();
        }
    }
    return FReply::Handled();
}

FReply SAdvanceDeletionTab::OnDeselectAllButtonClicked(){
    for (auto &checkbox: checkBoxes)
    {
        if (checkbox->IsChecked() )
        {
            //checkbox->SetIsChecked(ECheckBoxState::Unchecked);
            checkbox->ToggleCheckedState();
        }
    }
    return FReply::Handled();
}

TSharedRef<STextBlock> SAdvanceDeletionTab::ConstructButtonContent(const FString& text)
{
    FSlateFontInfo font = FCoreStyle::Get().GetFontStyle(FName("EmbossedText"));
    font.Size = 14;
    return SNew(STextBlock).Text(FText::FromString(text)).Font(font).Justification(ETextJustify::Center);
}

SuperManger.h

// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include "Modules/ModuleManager.h"
#include "TypeDefine.hpp"
#include "ActorActions/BuildActorActions.h"
class FSuperManagerModule : public IModuleInterface
{
public:

    /** IModuleInterface implementation */
    virtual void StartupModule() override;
    virtual void ShutdownModule() override;
    
    
public:
    bool removeSingleAsset(FAssetData ad);
    bool removeMultiAssets(TArray<FAssetData> ads);
    void SyncCBToClickedAssetAssetForAssetList(const FString &assetPathToSync);
    void filterUnusedAssets(const TArray<AssetDataPtr> &inAssets, TArray<AssetDataPtr> &outAssets);
    
private:
    void InitCBMenuExtension();
    TSharedRef<FExtender> CustomCBMenuExtension(const TArray<FString>& selectedPaths );
    void AddCBMenuEntry(FMenuBuilder & builder);
    void OnDeleteUnusedAsset();
    void OnDeleteEmptyFolder();
    
    // Deletion Tab
    void InitDeletionTab();
    void OnShowAdvancedDeletionTab();


private:
    TArray<FString> selectedPathsToDelete;
    
private:
    TArray<AssetDataPtr> buildSelectedAssetsData() const;
    BuildActorActions buildActorActions;

};

View Code

SuperManger.cpp

// Copyright Epic Games, Inc. All Rights Reserved.

#include "SuperManager.h"

#include "ContentBrowserModule.h"
#include "DebugHeader.hpp"
#include "EditorAssetLibrary.h"
#include "EditorUtilityLibrary.h"
#include "ObjectTools.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "SlateWidgets/AdvanceDeletionWidgets.h"
#include "CustomStyle/SuperManagerStyle.h"

#define LOCTEXT_NAMESPACE "FSuperManagerModule"

void FSuperManagerModule::StartupModule()
{
    FSuperManagerStyle::InitializeIcons();
    // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
    InitCBMenuExtension();
    InitDeletionTab();
    buildActorActions.Init();
}

void FSuperManagerModule::ShutdownModule()
{
    FSuperManagerStyle::Shutdown();
    buildActorActions.ShutDown();
    // This function may be called during shutdown to clean up your module.  For modules that support dynamic reloading,
    // we call this function before unloading the module.
}

void FSuperManagerModule::InitCBMenuExtension(){
    
    FContentBrowserModule & CtentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
    auto &extenders = CtentBrowserModule.GetAllPathViewContextMenuExtenders();
    /* Method 01
    FContentBrowserMenuExtender_SelectedPaths customCMenuDelegate;
    customCMenuDelegate.BindRaw(this , &FSuperManagerModule::CustomCBMenuExtension);
    extenders.Add(customCMenuDelegate);*/
    // Method 02
    extenders.Add(FContentBrowserMenuExtender_SelectedPaths::CreateRaw(this, 
        &FSuperManagerModule::CustomCBMenuExtension));
}

TSharedRef<FExtender> FSuperManagerModule::CustomCBMenuExtension(const TArray<FString>& selectedPaths)
{
    TSharedRef<FExtender> Extender {new FExtender()};
    if (selectedPaths.Num()>0){
        Print(FColor::Red, "Going to FSuperManagerModule::CustomCBMenuExtension");
        Extender->AddMenuExtension(FName{"Delete"},
            EExtensionHook::After, 
            TSharedPtr<FUICommandList>(),
            FMenuExtensionDelegate::CreateRaw(this, &FSuperManagerModule::AddCBMenuEntry));
        selectedPathsToDelete = selectedPaths;
    }
    return Extender;
}

void FSuperManagerModule::AddCBMenuEntry(FMenuBuilder& builder){
    builder.AddMenuEntry(FText::FromString("Delete Unused Asset"), 
        FText::FromString("Delete Unused Asset"),
        FSlateIcon(FSuperManagerStyle::StyleSetName, "ContentBrowser.DeleteUnusedAssets"),
        FExecuteAction::CreateRaw(this, &FSuperManagerModule::OnDeleteUnusedAsset )
        );
    // Lambda Method remove and use LoadModuleChecked
    builder.AddMenuEntry(FText::FromString("Delete Unused Asset method 2"), 
        FText::FromString("Delete Unused Asset 2"),
        FSlateIcon(FSuperManagerStyle::StyleSetName, "ContentBrowser.DeleteUnusedAssets2"),
        FExecuteAction::CreateLambda([this]
        {
            TArray<FAssetData> ToRemoveAssetData;
            if (selectedPathsToDelete.Num()!=1 ){
                ShowMessageDialog(TEXT("Delete Unused asset support one folder path"));
                return;
            }
            TArray<FString> assets = UEditorAssetLibrary::ListAssets(selectedPathsToDelete[0]);
            FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
            for (const auto &assetPath: assets)
            {
                TArray<FName> Referencers;
                const auto &assetData = UEditorAssetLibrary::FindAssetData(assetPath);
                AssetRegistryModule.Get().GetReferencers(assetData.PackageName, Referencers);
                Referencers.Remove(assetData.PackageName);
                
                if (Referencers.Num() == 0){
                    ToRemoveAssetData.Add(assetData);
                }
            }
            if (ToRemoveAssetData.Num() > 0)
                ObjectTools::DeleteAssets(ToRemoveAssetData);
            
        } ) );
    
    builder.AddMenuEntry(FText::FromString("DeleteEmptyFolder"),
        FText::FromString("Delete Empty Folder"),
        FSlateIcon(),
        FExecuteAction::CreateRaw(this, &FSuperManagerModule::OnDeleteEmptyFolder));
    
    // Our Slate Tab
    builder.AddMenuEntry(FText::FromString("Advanced Deletion"),
        FText::FromString("Advanced Assets Deletion"),
        FSlateIcon(FSuperManagerStyle::StyleSetName, "ContentBrowser.DeleteUnusedAssets2"),
        FExecuteAction::CreateRaw(this, &FSuperManagerModule::OnShowAdvancedDeletionTab));

    
}

void FSuperManagerModule::OnDeleteUnusedAsset()
{
    Print(FColor::Green, "Going to FSuperManagerModule::OnDeleteUnusedAsset");
    if (selectedPathsToDelete.Num()!=1 )
    {
        ShowMessageDialog(TEXT("Delete Unused asset support one folder path"));
        return;
    }
    TArray<FAssetData> ToRemoveAssetData;
        
    TArray<FString> assets = UEditorAssetLibrary::ListAssets(selectedPathsToDelete[0]);
    
    for (const auto &assetPath: assets){
        Print(FColor::Green, assetPath);
        if (assetPath.Contains("Developers") or assetPath.Contains("Collections"))
            continue;
        if (not UEditorAssetLibrary::DoesAssetExist (assetPath)) 
            continue;
        // NEED TO SAVE LEVEL, THEN CAN CHECK THE REF
        const auto &refs = UEditorAssetLibrary::FindPackageReferencersForAsset(assetPath);
        if ( refs.Num() ==0 ){
            // Remove unused asset
            const auto &assetData = UEditorAssetLibrary::FindAssetData(assetPath);
            ToRemoveAssetData.Add(assetData);
        }
    }
    if (ToRemoveAssetData.Num() > 0)
        ObjectTools::DeleteAssets(ToRemoveAssetData);
    
}

void FSuperManagerModule::OnDeleteEmptyFolder()
{
    Print(FColor::Green, "Going to FSuperManagerModule::OnDeleteEmptyFolder");
    if (selectedPathsToDelete.Num()!=1 ){
        ShowMessageDialog(TEXT("Delete Unused asset support one folder path"));
        return;
    }
    const auto assetPaths = UEditorAssetLibrary::ListAssets(selectedPathsToDelete[0], true , true);
    TArray<FString> messageWillDeletedFolders;
    FString message;
    for (const auto &folderPath: assetPaths){
        
        if (folderPath.Contains("Collections")) continue;
        if (folderPath.Contains("Developers")) continue;
        if (folderPath.Contains("_ExternalActors_")) continue;
        if (folderPath.Contains("_ExternalObjects_")) continue;
        Print(FColor::Green, folderPath + "--->:"+ FString::FromInt(UEditorAssetLibrary::DoesDirectoryExist(folderPath)));
        if (not UEditorAssetLibrary::DoesDirectoryExist(folderPath) ) continue;
        Print(FColor::Yellow, folderPath + ":"+ FString::FromInt(UEditorAssetLibrary::DoesDirectoryHaveAssets(folderPath)));
        
        const auto internalPaths = UEditorAssetLibrary::ListAssets(folderPath, false , false);
        if (internalPaths.Num() ==0 )
        {
            messageWillDeletedFolders.Add(folderPath);
            message.Append(folderPath);
            message.Append(TEXT("\n"));
        }
        if (messageWillDeletedFolders.Num()!= 0 )
        {
            auto retMsgType = ShowMessageDialog(message, EAppMsgType::YesNo);
            if (retMsgType != EAppReturnType::Yes) return ;
            for (const auto &toDel: messageWillDeletedFolders){
                UEditorAssetLibrary::DeleteAsset(toDel);
            }
        }
        
        
        /* ERROR BUG HERE
        if (not UEditorAssetLibrary::DoesDirectoryHaveAssets(folderPath))
        {
            messageWillDeletedFolders.Add(folderPath);
            message.Append(folderPath);
            message.Append(TEXT("\n"));
        }*/
        
    }
}

void FSuperManagerModule::InitDeletionTab(){
    FGlobalTabmanager::Get()->RegisterNomadTabSpawner(FName("AdvancedDeletionTab"), // ID
        FOnSpawnTab::CreateLambda([this](const FSpawnTabArgs &args){
            return SNew(SDockTab).TabRole(NomadTab)[
                    SNew(SAdvanceDeletionTab)
                .TestString("I 'm From SuperManager.cpp")
                .AssetsData(buildSelectedAssetsData())
            ];
        })
        )
    .SetDisplayName(FText::FromString(TEXT("NEXT Advanced Deletion Tab")))
    .SetIcon(FSlateIcon(FSuperManagerStyle::StyleSetName, "ContentBrowser.ADT"));
}

void FSuperManagerModule::OnShowAdvancedDeletionTab(){
    FGlobalTabmanager::Get()->TryInvokeTab(FName("AdvancedDeletionTab"));
}

TArray<AssetDataPtr> FSuperManagerModule::buildSelectedAssetsData() const
{
    TArray<AssetDataPtr> ret;
    if (selectedPathsToDelete.Num()!=1 ){
        ShowMessageDialog(TEXT("Delete Unused asset support one folder path"));
        return ret;
    }
    
    TArray<FString> assets = UEditorAssetLibrary::ListAssets(selectedPathsToDelete[0]);
    for (const auto &assetPath: assets){
        if (assetPath.Contains("Developers") or assetPath.Contains("Collections"))
            continue;
        if (not UEditorAssetLibrary::DoesAssetExist (assetPath)) 
            continue;
        const auto assetData = UEditorAssetLibrary::FindAssetData(assetPath);
        ret.Add(MakeShared<FAssetData>(assetData));
    }
    return ret;
}


bool FSuperManagerModule::removeSingleAsset(FAssetData ad){
    return UEditorAssetLibrary::DeleteAsset(ad.GetSoftObjectPath().ToString());   // ForceDelete
}

bool FSuperManagerModule::removeMultiAssets(TArray<FAssetData> ads)
{
    if (ObjectTools::DeleteAssets(ads) > 0 ) return true;
    return false;
}

void FSuperManagerModule::SyncCBToClickedAssetAssetForAssetList(const FString& assetPathToSync){
    TArray<FString> assetsPathToSync;
    assetsPathToSync.Add(assetPathToSync);
    UEditorAssetLibrary::SyncBrowserToObjects(assetsPathToSync);
}

void FSuperManagerModule::filterUnusedAssets(const TArray<AssetDataPtr>& inAssets, TArray<AssetDataPtr>& outAssets)
{
    outAssets.Empty();
    for (const auto &asset: inAssets){
        auto refs = UEditorAssetLibrary::FindPackageReferencersForAsset(asset->GetSoftObjectPath().ToString());
        if (refs.IsEmpty()) 
            outAssets.Add(asset);
            
    }
}


#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FSuperManagerModule, SuperManager)

View Code

2: Actor操作  WBP + C++

v3

v2

#pragma once

#include "CoreMinimal.h"
#include "EditorUtilityWidget.h"
#include "QuickActorActionsWidget.generated.h"

UENUM(BlueprintType)
enum class E_DuplicateType: uint8
{
    X UMETA(DisplayName = "X offset"),
    Y UMETA(DisplayName = "Y offset"),
    Z UMETA(DisplayName = "Z offset"),
};

USTRUCT(BlueprintType)
struct FRandomActorRotation
{
    GENERATED_BODY();
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category= "Actor Batch Transform")
    bool bXRandom{false};
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category= "Actor Batch Transform")
    bool bYRandom{false};
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category= "Actor Batch Transform")
    bool bZRandom{true};
    
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category= "Actor Batch Transform", meta=(EditCondition= "bXRandom"))
    FVector2f XMinMax{-45,45};
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category= "Actor Batch Transform", meta=(EditCondition= "bYRandom"))
    FVector2f YMinMax{-45,45};
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category= "Actor Batch Transform", meta=(EditCondition= "bZRandom"))
    FVector2f ZMinMax{-45, 45};

    
};


UCLASS()
class SUPERMANAGER_API UQuickActorActionsWidget : public UEditorUtilityWidget
{
    GENERATED_BODY()
    
public:
    UFUNCTION(BlueprintCallable)
    void SelectActorsWithSameName();

    UFUNCTION(BlueprintCallable)
    void DuplicateActors();
    
    UFUNCTION(BlueprintCallable)
    void RandomRotationActors();
    
    UFUNCTION(BlueprintCallable)
    void ResetRotationActors();
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category= "Actor Batch Duplication")
    E_DuplicateType DuplicateType = E_DuplicateType::X;
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category= "Actor Batch Duplication")
    int32 CopyNum = 4;
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category= "Actor Batch Duplication")
    float OffsetDist = 300;
    
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category= "RandomRotationTransform")
    FRandomActorRotation RandomRotation;
    
private:
    UPROPERTY()
    class UEditorActorSubsystem *EditorActorSubsystem;
    void GetEditorActorSubsystem();
};

cpp:

// Fill out your copyright notice in the Description page of Project Settings.


#include "ActorActions/QuickActorActionsWidget.h"

#include "DebugHeader.hpp"
#include "Subsystems/EditorActorSubsystem.h"

void UQuickActorActionsWidget::GetEditorActorSubsystem()
{
    if (not EditorActorSubsystem)
        EditorActorSubsystem = GEditor->GetEditorSubsystem<UEditorActorSubsystem>();
}

void UQuickActorActionsWidget::SelectActorsWithSameName(){
    Print(FColor::Black,"start select same name actors");
    GetEditorActorSubsystem();
    auto actors = EditorActorSubsystem->GetSelectedLevelActors();
    if (actors.IsEmpty()){
        ShowNotifyInfo("No Selection, Please Select one actor");
        return;
    }
    if (actors.Num() > 1){
        ShowNotifyInfo("Please Select one actor");
        return;
    }
    FString selectActorLabel = actors[0]->GetActorLabel();
    FString nameToSearch = selectActorLabel.LeftChop(3);// 去除尾
    Print(FColor::Green,nameToSearch);
    
    for (auto *actor: EditorActorSubsystem->GetAllLevelActors())
    {
        if (not actor->GetActorLabel().Contains(nameToSearch)) continue;
        EditorActorSubsystem->SetActorSelectionState(actor, true);
    }
    //EditorActorSubsystem->SetSelectedLevelActors(ActorsToSelect);
}

void UQuickActorActionsWidget::DuplicateActors(){
    auto calOffsetLocation = [this](int incrementNum)
    {
        FVector offsetLocation{0,0,0};
        switch (DuplicateType)
        {
        case E_DuplicateType::X:
            offsetLocation.X = OffsetDist * (incrementNum+1);
            break;
        case E_DuplicateType::Y:
            offsetLocation.Y = OffsetDist * (incrementNum+1);
            break;
        case E_DuplicateType::Z:
            offsetLocation.Z = OffsetDist * (incrementNum+1);
            break;
        default:
            break;
        }    
        return offsetLocation;
    };
    
    for (auto *actor: EditorActorSubsystem->GetSelectedLevelActors()){
        for (int i=0;i<CopyNum;i++){
            auto *copyedActor = EditorActorSubsystem->DuplicateActor(actor);
            copyedActor->AddActorLocalOffset(calOffsetLocation(i));
        }
    }
}

void UQuickActorActionsWidget::RandomRotationActors()
{
    for (auto *actor: EditorActorSubsystem->GetSelectedLevelActors()){
        
        if (RandomRotation.bXRandom) // Roll
        {
            auto v = FMath::RandRange(RandomRotation.XMinMax.X, RandomRotation.XMinMax.Y );
            actor->AddActorLocalRotation(FRotator{0,v,v});
        }
        if (RandomRotation.bYRandom) // Pitch
        {
            auto v = FMath::RandRange(RandomRotation.YMinMax.X, RandomRotation.YMinMax.Y );
            actor->AddActorLocalRotation(FRotator{v,v,0});
        }
        if (RandomRotation.bZRandom) // YAW
        {
            auto v = FMath::RandRange(RandomRotation.ZMinMax.X, RandomRotation.ZMinMax.Y );
            actor->AddActorLocalRotation(FRotator{0,v,0});
        }
    }
}

void UQuickActorActionsWidget::ResetRotationActors(){
    for (auto *actor: EditorActorSubsystem->GetSelectedLevelActors()){
        actor->SetActorRotation(FRotator{0,0,0});
    }
}

3: 子Menu创建:扩展ActorAsset 对着视窗一个Actor鼠标右键:

image

#pragma once

struct BuildActorActions
{
    void Init();
    void ShutDown();
private:
    void InitLevelEditorExtension();
    void AddMenuEntry(FMenuBuilder& MainMenu);
    void buildSubMenu(FMenuBuilder& subMenu);
    void onLockActorSelectionButtonClicked();
    void onUnlockActorSelectionButtonClicked();
    // Selection Event
    void InitCustomSelectionEvent(); 
};

cpp:

#include "ActorActions/BuildActorActions.h"

#include "DebugHeader.hpp"
#include "LevelEditor.h"
#include "Selection.h"

void BuildActorActions::Init()
{
    //GEngine
    InitCustomSelectionEvent();
    InitLevelEditorExtension();
}


void BuildActorActions::ShutDown()
{
    
}


void BuildActorActions::InitLevelEditorExtension()
{
    auto &levelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>(TEXT("LevelEditor"));
    TArray<FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors> &levelMenuExtenders = levelEditorModule.GetAllLevelViewportContextMenuExtenders();
    levelMenuExtenders.Add(FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors::CreateLambda([this](const TSharedRef<FUICommandList> guiCommandList, const TArray<AActor*> SelectedActors)
    {
        // Ret: TSharedRef<FExtender>
        TSharedRef<FExtender> extender = MakeShareable(new FExtender());
        if (SelectedActors.IsEmpty()) return extender;
        
        extender->AddMenuExtension(FName{"ActorOptions"}, 
            EExtensionHook::Before,
            guiCommandList,FMenuExtensionDelegate::CreateRaw(this,&BuildActorActions::AddMenuEntry) );
        return extender;
    }));
}

void BuildActorActions::AddMenuEntry(FMenuBuilder& MainMenu)
{
    /*
     * NSLOCTEXT( Namespace, Key, DefaultText )
        * Namespace(命名空间):用于组织文本的逻辑分组,避免不同模块间的 Key 冲突(通常使用模块名或功能名)。
        Key(键):该文本的唯一标识符,在同一命名空间下必须唯一。
        DefaultText(默认文本):开发时使用的源语言文本(例如英语),在翻译文件中会以此为基础生成其他语言的版本。
     * 
     */
    
    MainMenu.AddSubMenu(NSLOCTEXT("BuildActorActions", "Actor Action Extensions", "Actor Action Extensions"),
                            NSLOCTEXT("BuildActorActions", "SubMenu1Tooltip", "First sub action"),
                            FNewMenuDelegate::CreateRaw(this, &BuildActorActions::buildSubMenu) );
}

void BuildActorActions::buildSubMenu(FMenuBuilder& subMenu)
{
    subMenu.AddMenuEntry(NSLOCTEXT("BuildActorActions", "Lock Actor Selection", "Lock Actor Selection"),
                                    NSLOCTEXT("BuildActorActions", "Command1Tooltip", "Execute Command A"),
                                    FSlateIcon(), FExecuteAction::CreateRaw(this, &BuildActorActions::onLockActorSelectionButtonClicked)
                                    );
    subMenu.AddMenuEntry(NSLOCTEXT("BuildActorActions", "Unlock Actor Selection", "Unlock Actor Selection"),
                                    NSLOCTEXT("BuildActorActions", "Unlock Actor Selection", "Execute Command A"),
                                    FSlateIcon(), FExecuteAction::CreateRaw(this, &BuildActorActions::onUnlockActorSelectionButtonClicked)
                                    );
    
    
}

void BuildActorActions::onLockActorSelectionButtonClicked(){
    Print(FColor::Green, "Locked");
}
void BuildActorActions::onUnlockActorSelectionButtonClicked(){
    Print(FColor::Green, "UnLocked");
}

void BuildActorActions::InitCustomSelectionEvent()
{
    USelection *UserSelection = GEditor->GetSelectedActors();
    UserSelection->SelectObjectEvent.AddLambda([](UObject* selectObj){
        Print(FColor::Green, selectObj->GetName());
    });
}

4: 修改曲线Visualizer

v3

v3

先把build改下PostEngineInit

{
    "FileVersion": 3,
    "Version": 1,
    "VersionName": "1.0",
    "FriendlyName": "SuperManager",
    "Description": "",
    "Category": "Other",
    "CreatedBy": "liuyangping",
    "CreatedByURL": "",
    "DocsURL": "",
    "MarketplaceURL": "",
    "SupportURL": "",
    "CanContainContent": true,
    "IsBetaVersion": false,
    "IsExperimentalVersion": false,
    "Installed": false,
    "Modules": [
        {
            "Name": "SuperManager",
            "Type": "Editor",
            "LoadingPhase": "PostEngineInit"
        }
    ]
}

image

CooperSpline:

#pragma once

#include "CoreMinimal.h"
#include "Components/SplineComponent.h"
#include "CooperSpline.generated.h"


UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class SUPERMANAGER_API UCooperSpline : public USplineComponent
{
    GENERATED_BODY()

public:
    // Sets default values for this component's properties
    UCooperSpline();

protected:
    // Called when the game starts
    virtual void BeginPlay() override;

public:
    // Called every frame
    virtual void TickComponent(float DeltaTime, ELevelTick TickType,
                               FActorComponentTickFunction* ThisTickFunction) override;
};

View Code

CooperSplineVisualizer:

#include "CoreMinimal.h"
#include "Editor/ComponentVisualizers/Public/SplineComponentVisualizer.h"

class CooperSplineVisualizer: public FSplineComponentVisualizer
{
public:
    void DrawVisualization(const UActorComponent* Component, 
        const FSceneView* View, 
        FPrimitiveDrawInterface* PDI) override;
};
#include "CooperGeomtry/CooperSplineVisualizer.h"
#include "CooperGeomtry/CooperSpline.h"

void CooperSplineVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI)
{
    // 1. 必须先调用父类的绘制,否则原本的路径线会消失
    FSplineComponentVisualizer::DrawVisualization(Component, View, PDI);

    // 2. 转换成你的自定义类
    const UCooperSpline* MySpline = Cast<UCooperSpline>(Component);
    if (!MySpline) return;

    // 3. 打印日志确认是否运行到这里
    //UE_LOG(LogTemp, Warning, TEXT("CooperSpline Visualizer is Drawing! Points: %d"), MySpline->GetNumberOfSplinePoints());

    for (int32 i = 0; i < MySpline->GetNumberOfSplinePoints(); i++)
    {
        FVector p = MySpline->GetLocationAtSplinePoint(i, ESplineCoordinateSpace::World);
        
        // 4. 将半径改大到 100.f,颜色改成最扎眼的红色,防止太细看不见
        DrawWireSphere(PDI, p, FColor::Red, 10.0f, 10, SDPG_Foreground);
    }
}