Files
mesh-project-duplicate-remover/Source/OptimizerEditor/Private/SOptimizerPanel.cpp
Bonchellon a95b299680 Mesh Optimizer: sibling StaticMesh duplicate remover (UE 5.7)
Editor plugin that detects geometrically-identical sibling StaticMeshes across a
level, rebases each placement onto one canonical mesh with a corrected transform
(W' = D * W, verified by exact vertex matching), and can collapse groups into HISM.
Native Slate tool panel + BlueprintCallable UOptimizerSubsystem.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 18:26:45 +03:00

212 lines
6.3 KiB
C++

// Copyright IHY.
#include "SOptimizerPanel.h"
#include "OptimizerSubsystem.h"
#include "Editor.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Layout/SScrollBox.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Input/SCheckBox.h"
#include "Widgets/Text/STextBlock.h"
#include "Styling/AppStyle.h"
#include "Styling/CoreStyle.h"
#define LOCTEXT_NAMESPACE "OptimizerPanel"
void SOptimizerPanel::Construct(const FArguments& InArgs)
{
const FSlateFontInfo TitleFont = FCoreStyle::GetDefaultFontStyle("Bold", 15);
const FSlateFontInfo MonoFont = FCoreStyle::GetDefaultFontStyle("Mono", 9);
ChildSlot
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(FMargin(14.f))
[
SNew(SVerticalBox)
// Title
+ SVerticalBox::Slot().AutoHeight().Padding(0, 0, 0, 2)
[
SNew(STextBlock)
.Text(LOCTEXT("Title", "Mesh Optimizer"))
.Font(TitleFont)
]
// Subtitle
+ SVerticalBox::Slot().AutoHeight().Padding(0, 0, 0, 12)
[
SNew(STextBlock)
.Text(LOCTEXT("Subtitle", "Find sibling StaticMeshes, unify onto one canonical mesh, then build HISM."))
.ColorAndOpacity(FSlateColor::UseSubduedForeground())
.AutoWrapText(true)
]
// Detection toggles
+ SVerticalBox::Slot().AutoHeight().Padding(0, 0, 0, 10)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot().AutoWidth()[ MakeLabeledCheck(ChkMirror, LOCTEXT("Mirror", "Mirrored"), false) ]
+ SHorizontalBox::Slot().AutoWidth()[ MakeLabeledCheck(ChkScale, LOCTEXT("Scale", "Scaled"), false) ]
+ SHorizontalBox::Slot().AutoWidth()[ MakeLabeledCheck(ChkMergeMat, LOCTEXT("MergeMat", "Merge materials"), false) ]
]
// Action buttons
+ SVerticalBox::Slot().AutoHeight().Padding(0, 0, 0, 8)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot().AutoWidth().Padding(0, 0, 8, 0)
[ MakeButton(LOCTEXT("Scan", "Scan"), FOnClicked::CreateSP(this, &SOptimizerPanel::OnScan)) ]
+ SHorizontalBox::Slot().AutoWidth().Padding(0, 0, 8, 0)
[ MakeButton(LOCTEXT("Unify", "Unify"), FOnClicked::CreateSP(this, &SOptimizerPanel::OnUnify)) ]
+ SHorizontalBox::Slot().AutoWidth()
[ MakeButton(LOCTEXT("HISM", "Build HISM"), FOnClicked::CreateSP(this, &SOptimizerPanel::OnBuildHISM)) ]
]
// Destroy-originals toggle
+ SVerticalBox::Slot().AutoHeight().Padding(0, 0, 0, 12)
[
MakeLabeledCheck(ChkDestroy, LOCTEXT("Destroy", "Destroy originals when building HISM"), true)
]
// Results area
+ SVerticalBox::Slot().FillHeight(1.f)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.DarkGroupBorder"))
.Padding(FMargin(8.f))
[
SNew(SScrollBox)
+ SScrollBox::Slot()
[
SAssignNew(ResultsBox, STextBlock)
.Text(LOCTEXT("Idle", "Press Scan to analyze the level."))
.Font(MonoFont)
.AutoWrapText(true)
]
]
]
]
];
}
TSharedRef<SWidget> SOptimizerPanel::MakeLabeledCheck(TSharedPtr<SCheckBox>& Out, const FText& Label, bool bDefault)
{
return SNew(SHorizontalBox)
+ SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center)
[
SAssignNew(Out, SCheckBox)
.IsChecked(bDefault ? ECheckBoxState::Checked : ECheckBoxState::Unchecked)
]
+ SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center).Padding(5, 0, 16, 0)
[
SNew(STextBlock).Text(Label)
];
}
TSharedRef<SWidget> SOptimizerPanel::MakeButton(const FText& Label, FOnClicked OnClicked)
{
return SNew(SButton)
.Text(Label)
.OnClicked(OnClicked)
.ContentPadding(FMargin(18.f, 6.f))
.HAlign(HAlign_Center);
}
bool SOptimizerPanel::IsChecked(const TSharedPtr<SCheckBox>& Chk) const
{
return Chk.IsValid() && Chk->IsChecked();
}
void SOptimizerPanel::SetReport(const FString& Text)
{
if (ResultsBox.IsValid())
{
ResultsBox->SetText(FText::FromString(Text));
}
}
FReply SOptimizerPanel::OnScan()
{
UOptimizerSubsystem* SS = GEditor ? GEditor->GetEditorSubsystem<UOptimizerSubsystem>() : nullptr;
if (!SS)
{
SetReport(TEXT("Subsystem unavailable."));
return FReply::Handled();
}
FOptimizerScanSettings Settings;
Settings.bAllowMirrored = IsChecked(ChkMirror);
Settings.bAllowUniformScale = IsChecked(ChkScale);
Settings.bMergeAcrossMaterials = IsChecked(ChkMergeMat);
const FOptimizerScanResult R = SS->ScanLevel(Settings);
FString Report = R.Summary + TEXT("\n");
Report += FString::Printf(TEXT("actors=%d components=%d unique meshes=%d\n\n"),
R.ActorsScanned, R.ComponentsScanned, R.UniqueMeshes);
if (R.Groups.Num() == 0)
{
Report += TEXT("No sibling groups found.");
}
for (const FOptimizerGroupView& G : R.Groups)
{
FString Flags;
if (G.bHasMirrored) { Flags += TEXT(" [mirror]"); }
if (G.bHasScaled) { Flags += TEXT(" [scale]"); }
if (G.bMaterialMismatch) { Flags += TEXT(" [mat!=]"); }
if (G.ShearRejectedCount > 0) { Flags += FString::Printf(TEXT(" [shear x%d]"), G.ShearRejectedCount); }
Report += FString::Printf(TEXT("- %s (members=%d, placements=%d, fix=%d, maxDev=%.4f cm)%s\n %s\n"),
*G.CanonicalMeshName, G.MemberMeshNames.Num(), G.InstanceCount, G.TransformFixCount,
G.MaxDeviation, *Flags, *FString::Join(G.MemberMeshNames, TEXT(", ")));
}
SetReport(Report);
return FReply::Handled();
}
FReply SOptimizerPanel::OnUnify()
{
UOptimizerSubsystem* SS = GEditor ? GEditor->GetEditorSubsystem<UOptimizerSubsystem>() : nullptr;
if (!SS)
{
SetReport(TEXT("Subsystem unavailable."));
return FReply::Handled();
}
if (!SS->HasPlan())
{
SetReport(TEXT("Run Scan first."));
return FReply::Handled();
}
const int32 N = SS->ApplyUnify();
SetReport(FString::Printf(TEXT("Unify done: %d placement(s) reassigned to canonical mesh + corrected transform.\nUndo with Ctrl+Z. Re-scan before Build HISM."), N));
return FReply::Handled();
}
FReply SOptimizerPanel::OnBuildHISM()
{
UOptimizerSubsystem* SS = GEditor ? GEditor->GetEditorSubsystem<UOptimizerSubsystem>() : nullptr;
if (!SS)
{
SetReport(TEXT("Subsystem unavailable."));
return FReply::Handled();
}
if (!SS->HasPlan())
{
SetReport(TEXT("Run Scan first."));
return FReply::Handled();
}
const bool bDestroy = IsChecked(ChkDestroy);
const int32 N = SS->BuildHISM(bDestroy);
SetReport(FString::Printf(TEXT("Build HISM done: %d HISM actor(s) created%s.\nUndo with Ctrl+Z."),
N, bDestroy ? TEXT(" (originals destroyed)") : TEXT("")));
return FReply::Handled();
}
#undef LOCTEXT_NAMESPACE