#unreal

Implement IModelingToolsExtension for example in your editor module class.

  1. GetExtensionName
  2. GetToolSectionName
  3. GetExtensionTools

Implement a Style class and a Commands class - these are mostly boilerplate which you can copy from below. Implement the tool classes and tool builders, basic examples below.

See also

http://www.gradientspace.com/tutorials/2022/5/27/modeling-mode-extension-plugins-in-ue5 https://github.com/gradientspace/UE5ModelingModeExtensionDemo/blob/main/Plugins/SampleModelingModeExtension/Source/SampleModelingModeExtension/Private/SampleModelingModeExtensionModule.cpp

Examples

Module class

void FBillysEditorModule::StartupModule()
{
	FBillysModelingStyle::Initialize();
	FBillysModelingCommands::Register();
	IModularFeatures::Get().RegisterModularFeature(GetModularFeatureName(), this);
}
 
FText FBillysEditorModule::GetExtensionName()
{
	return INVTEXT("BillysModelingTools");
}
 
FText FBillysEditorModule::GetToolSectionName()
{
	return INVTEXT("Billys");
}
 
void FBillysEditorModule::GetExtensionTools(const FExtensionToolQueryInfo& QueryInfo, TArray<FExtensionToolDescription>& ToolsOut)
{
	FExtensionToolDescription FlipUVs;
	FlipUVs.ToolName = INVTEXT("Flip UVs");
	FlipUVs.ToolCommand = FBillysModelingCommands::Get().FlipUVsTool;
	FlipUVs.ToolBuilder = NewObject<UBillysFlipUVsToolBuilder>();
 
	ToolsOut.Add(FlipUVs);
}

Style class

Largely boilerplate that you can copypaste

See also: there is at least one engine plugin which does this. It shows how you can use the plugin content directory for assets like icons.

#pragma once
 
#include "CoreMinimal.h"
#include "Styling/ISlateStyle.h"
#include "Styling/SlateStyle.h"
 
/**
 * 
 */
class BILLYSEDITOR_API FBillysModelingStyle
{
public:
	static void Initialize();
 
	static void Shutdown();
 
	static TSharedPtr< class ISlateStyle > Get();
 
	static FName GetStyleSetName();
private:
	static FString InContent(const FString& RelativePath, const ANSICHAR* Extension);
 
	static TSharedPtr< class FSlateStyleSet > StyleSet;
};
 
// Copyright Billys Boys 2022
 
#include "BillysModelingStyle.h"
#include "Styling/SlateStyleRegistry.h"
#include "Styling/SlateTypes.h"
#include "Styling/CoreStyle.h"
#include "Styling/AppStyle.h"
#include "SlateOptMacros.h"
#include "Styling/SlateStyleMacros.h"
 
 
// This is to fix the issue that SlateStyleMacros like IMAGE_BRUSH look for RootToContentDir but StyleSet->RootToContentDir is how this style is set up
#define RootToContentDir StyleSet->RootToContentDir
 
 
TSharedPtr< FSlateStyleSet > FBillysModelingStyle::StyleSet = nullptr;
TSharedPtr< class ISlateStyle > FBillysModelingStyle::Get() { return StyleSet; }
 
FName FBillysModelingStyle::GetStyleSetName()
{
	static FName ModelingToolsStyleName(TEXT("BillysModelingStyle"));
	return ModelingToolsStyleName;
}
 
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
 
void FBillysModelingStyle::Initialize()
{
	// Const icon sizes
	const FVector2D Icon20x20(20.0f, 20.0f);
 
	// Only register once
	if (StyleSet.IsValid())
	{
		return;
	}
 
	StyleSet = MakeShareable(new FSlateStyleSet(GetStyleSetName()));
	StyleSet->SetContentRoot(FPaths::EngineContentDir() / TEXT("Editor/Slate/"));
	StyleSet->SetCoreContentRoot(FPaths::EngineContentDir() / TEXT("Slate"));
 
	{
		StyleSet->Set("BillysModelingCommands.FlipUVsTool", new IMAGE_BRUSH("Icons/icon_Blueprint_Sequence_16x", Icon20x20));
	}
 
	FSlateStyleRegistry::RegisterSlateStyle(*StyleSet.Get());
};
 
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
 
#undef IMAGE_PLUGIN_BRUSH
#undef IMAGE_BRUSH
#undef BOX_BRUSH
#undef DEFAULT_FONT
 
void FBillysModelingStyle::Shutdown()
{
	if (StyleSet.IsValid())
	{
		FSlateStyleRegistry::UnRegisterSlateStyle(*StyleSet.Get());
		ensure(StyleSet.IsUnique());
		StyleSet.Reset();
	}
}
 

Commands class

/**
 * 
 */
class BILLYSEDITOR_API FBillysModelingCommands : public TCommands<FBillysModelingCommands>
{
public:
	FBillysModelingCommands();
	
	TSharedPtr<FUICommandInfo> FlipUVsTool;
 
	/**
	 * Initialize commands
	 */
	virtual void RegisterCommands() override;
};
 
#include "BillysModelingCommands.h"
 
#include "BillysModelingStyle.h"
 
#define LOCTEXT_NAMESPACE "BillysModelingCommands"
 
FBillysModelingCommands::FBillysModelingCommands()
	: TCommands<FBillysModelingCommands>(
		"BillysModelingCommands", // Context name for fast lookup
		INVTEXT("Billys Modeling Commands"), // Localized context name for displaying
		NAME_None, // Parent
		FBillysModelingStyle::Get()->GetStyleSetName() // Icon Style Set
	)
{
}
 
void FBillysModelingCommands::RegisterCommands()
{
	UI_COMMAND(FlipUVsTool, "FlipUVs", "Flip UVs for target face", EUserInterfaceActionType::ToggleButton, FInputChord());
}
 
#undef LOCTEXT_NAMESPACE

Tool class and tool builder class

#include "CoreMinimal.h"
#include "BaseTools/BaseMeshProcessingTool.h"
#include "BillysFlipUVsTool.generated.h"
 
/**
 * 
 */
UCLASS()
class BILLYSEDITOR_API UBillysFlipUVsTool : public UBaseMeshProcessingTool
{
	GENERATED_BODY()
public:
	UBillysFlipUVsTool();
};
 
 
UCLASS()
class BILLYSEDITOR_API UBillysFlipUVsToolBuilder : public UBaseMeshProcessingToolBuilder
{
	GENERATED_BODY()
public:
	virtual UBaseMeshProcessingTool* MakeNewToolInstance(UObject* Outer) const override
	{
		return NewObject<UBillysFlipUVsTool>(Outer);
	}
};
#include "BillysFlipUVsTool.h"
 
UBillysFlipUVsTool::UBillysFlipUVsTool()
{
	UInteractiveTool::SetToolDisplayName(INVTEXT("Flip UVs"));
}