835 lines
32 KiB
C++
835 lines
32 KiB
C++
#include "DismembermentEditor.h"
|
|
#include "Widgets/Docking/SDockTab.h"
|
|
#include "PropertyEditorModule.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "IDetailsView.h"
|
|
#include "Widgets/Layout/SBorder.h"
|
|
#include "Styling/AppStyle.h"
|
|
#include "Engine/SkeletalMesh.h"
|
|
#include "Framework/Commands/UICommandList.h"
|
|
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
|
#include "ToolMenus.h"
|
|
#include "Logging/LogMacros.h"
|
|
#include "Misc/MessageDialog.h"
|
|
|
|
// Define log category
|
|
DEFINE_LOG_CATEGORY_STATIC(LogDismembermentEditor, Log, All);
|
|
|
|
// Define tab names
|
|
const FName FDismembermentEditor::ViewportTabId(TEXT("DismembermentEditor_Viewport"));
|
|
const FName FDismembermentEditor::DetailsTabId(TEXT("DismembermentEditor_Details"));
|
|
const FName FDismembermentEditor::LayerSystemTabId(TEXT("DismembermentEditor_LayerSystem"));
|
|
const FName FDismembermentEditor::PhysicsSettingsTabId(TEXT("DismembermentEditor_PhysicsSettings"));
|
|
|
|
// Constructor
|
|
FDismembermentEditor::FDismembermentEditor()
|
|
: SkeletalMesh(nullptr)
|
|
, BooleanCutTool(nullptr)
|
|
, LayerSystem(nullptr)
|
|
, SelectedBoneName(NAME_None)
|
|
, bCreateCapMesh(true)
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Log, TEXT("DismembermentEditor constructor called"));
|
|
|
|
// Create boolean cut tool
|
|
BooleanCutTool = NewObject<UBooleanCutTool>();
|
|
if (BooleanCutTool)
|
|
{
|
|
// Initialize default settings
|
|
BooleanCutTool->SetCapMeshMethod(ECapMeshMethod::Simple);
|
|
UE_LOG(LogDismembermentEditor, Log, TEXT("Boolean cut tool created successfully"));
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Error, TEXT("Failed to create Boolean cut tool"));
|
|
}
|
|
|
|
// Create anatomical layer system
|
|
LayerSystem = NewObject<UAnatomicalLayerSystem>();
|
|
if (LayerSystem)
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Log, TEXT("Anatomical layer system created successfully"));
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Error, TEXT("Failed to create Anatomical layer system"));
|
|
}
|
|
|
|
// Initialize default cut plane
|
|
CurrentCutPlane.Location = FVector(0, 0, 0);
|
|
CurrentCutPlane.Normal = FVector(0, 0, 1); // Default to Z-up
|
|
}
|
|
|
|
// Destructor
|
|
FDismembermentEditor::~FDismembermentEditor()
|
|
{
|
|
// Unregister from any events
|
|
if (GEditor)
|
|
{
|
|
GEditor->UnregisterForUndo(this);
|
|
}
|
|
|
|
UE_LOG(LogDismembermentEditor, Log, TEXT("DismembermentEditor destructor called"));
|
|
}
|
|
|
|
// Initialize the editor
|
|
void FDismembermentEditor::InitDismembermentEditor(const EToolkitMode::Type Mode, const TSharedPtr<IToolkitHost>& InitToolkitHost, USkeletalMesh* InSkeletalMesh)
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Log, TEXT("Initializing Dismemberment Editor"));
|
|
|
|
try
|
|
{
|
|
// Set the skeletal mesh being edited
|
|
SkeletalMesh = InSkeletalMesh;
|
|
|
|
if (!SkeletalMesh)
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Error, TEXT("Failed to initialize Dismemberment Editor: Skeletal mesh is null"));
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Failed to initialize Dismemberment Editor: Skeletal mesh is null"));
|
|
return;
|
|
}
|
|
|
|
// Initialize the layer system with the target mesh
|
|
if (LayerSystem)
|
|
{
|
|
// We can't directly set TargetMesh, as UAnatomicalLayerSystem doesn't have this member
|
|
// But we can store a reference to the skeletal mesh here
|
|
UE_LOG(LogDismembermentEditor, Log, TEXT("Layer system initialized, target mesh: %s"), *SkeletalMesh->GetName());
|
|
}
|
|
|
|
// Create the layout
|
|
CreateEditorLayout();
|
|
|
|
// Create the toolbar
|
|
CreateEditorToolbar();
|
|
|
|
// Initialize the asset editor
|
|
InitAssetEditor(Mode, InitToolkitHost, FName("DismembermentEditorApp"),
|
|
FTabManager::FLayout::NullLayout,
|
|
/*bCreateDefaultStandaloneMenu=*/ true,
|
|
/*bCreateDefaultToolbar=*/ true,
|
|
InSkeletalMesh);
|
|
|
|
// Register for undo/redo
|
|
GEditor->RegisterForUndo(this);
|
|
|
|
UE_LOG(LogDismembermentEditor, Log, TEXT("Dismemberment Editor initialized successfully"));
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Error, TEXT("Exception during Dismemberment Editor initialization: %s"), UTF8_TO_TCHAR(e.what()));
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::Format(FText::FromString("Exception during Dismemberment Editor initialization: {0}"), FText::FromString(UTF8_TO_TCHAR(e.what()))));
|
|
}
|
|
catch (...)
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Error, TEXT("Unknown exception during Dismemberment Editor initialization"));
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Unknown exception during Dismemberment Editor initialization"));
|
|
}
|
|
}
|
|
|
|
// Get the toolkit name
|
|
FName FDismembermentEditor::GetToolkitFName() const
|
|
{
|
|
return FName("DismembermentEditor");
|
|
}
|
|
|
|
// Get the base toolkit name
|
|
FText FDismembermentEditor::GetBaseToolkitName() const
|
|
{
|
|
return FText::FromString("Dismemberment Editor");
|
|
}
|
|
|
|
// Get the world centric tab prefix
|
|
FString FDismembermentEditor::GetWorldCentricTabPrefix() const
|
|
{
|
|
return "DismembermentEditor";
|
|
}
|
|
|
|
// Get the world centric tab color scale
|
|
FLinearColor FDismembermentEditor::GetWorldCentricTabColorScale() const
|
|
{
|
|
return FLinearColor(0.3f, 0.2f, 0.5f, 0.5f);
|
|
}
|
|
|
|
// Post undo handler
|
|
void FDismembermentEditor::PostUndo(bool bSuccess)
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Log, TEXT("PostUndo called, success: %d"), bSuccess);
|
|
|
|
// Refresh the views
|
|
if (bSuccess)
|
|
{
|
|
if (DetailsWidget.IsValid())
|
|
{
|
|
DetailsWidget->ForceRefresh();
|
|
}
|
|
|
|
// Refresh other widgets as needed
|
|
if (ViewportWidget.IsValid())
|
|
{
|
|
// Refresh viewport if needed
|
|
}
|
|
|
|
if (LayerSystemWidget.IsValid())
|
|
{
|
|
// Refresh layer system widget if needed
|
|
}
|
|
|
|
if (PhysicsSettingsWidget.IsValid())
|
|
{
|
|
// Refresh physics settings widget if needed
|
|
}
|
|
}
|
|
}
|
|
|
|
// Post redo handler
|
|
void FDismembermentEditor::PostRedo(bool bSuccess)
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Log, TEXT("PostRedo called, success: %d"), bSuccess);
|
|
|
|
// Refresh the views
|
|
if (bSuccess)
|
|
{
|
|
if (DetailsWidget.IsValid())
|
|
{
|
|
DetailsWidget->ForceRefresh();
|
|
}
|
|
|
|
// Refresh other widgets as needed
|
|
if (ViewportWidget.IsValid())
|
|
{
|
|
// Refresh viewport if needed
|
|
}
|
|
|
|
if (LayerSystemWidget.IsValid())
|
|
{
|
|
// Refresh layer system widget if needed
|
|
}
|
|
|
|
if (PhysicsSettingsWidget.IsValid())
|
|
{
|
|
// Refresh physics settings widget if needed
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create the editor layout
|
|
void FDismembermentEditor::CreateEditorLayout()
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Log, TEXT("Creating editor layout"));
|
|
|
|
try
|
|
{
|
|
// Create the layout
|
|
TSharedRef<FTabManager::FLayout> StandaloneDefaultLayout = FTabManager::NewLayout("Standalone_DismembermentEditor_Layout_v1")
|
|
->AddArea
|
|
(
|
|
FTabManager::NewPrimaryArea()->SetOrientation(Orient_Vertical)
|
|
// In UE5.5.4, toolbar is no longer a separate tab
|
|
// Skip the toolbar tab section and directly create the main content area
|
|
->Split
|
|
(
|
|
FTabManager::NewSplitter()->SetOrientation(Orient_Horizontal)->SetSizeCoefficient(0.9f)
|
|
->Split
|
|
(
|
|
FTabManager::NewStack()
|
|
->SetSizeCoefficient(0.7f)
|
|
->AddTab(ViewportTabId, ETabState::OpenedTab)
|
|
)
|
|
->Split
|
|
(
|
|
FTabManager::NewSplitter()->SetOrientation(Orient_Vertical)
|
|
->SetSizeCoefficient(0.3f)
|
|
->Split
|
|
(
|
|
FTabManager::NewStack()
|
|
->SetSizeCoefficient(0.4f)
|
|
->AddTab(DetailsTabId, ETabState::OpenedTab)
|
|
)
|
|
->Split
|
|
(
|
|
FTabManager::NewStack()
|
|
->SetSizeCoefficient(0.3f)
|
|
->AddTab(LayerSystemTabId, ETabState::OpenedTab)
|
|
)
|
|
->Split
|
|
(
|
|
FTabManager::NewStack()
|
|
->SetSizeCoefficient(0.3f)
|
|
->AddTab(PhysicsSettingsTabId, ETabState::OpenedTab)
|
|
)
|
|
)
|
|
)
|
|
);
|
|
|
|
// Set the layout
|
|
FAssetEditorToolkit::InitAssetEditor(
|
|
EToolkitMode::Standalone,
|
|
TSharedPtr<IToolkitHost>(),
|
|
FName("DismembermentEditorApp"),
|
|
StandaloneDefaultLayout,
|
|
true,
|
|
true,
|
|
SkeletalMesh
|
|
);
|
|
|
|
// Register tab spawners
|
|
RegisterTabSpawners(GetTabManager().ToSharedRef());
|
|
|
|
UE_LOG(LogDismembermentEditor, Log, TEXT("Editor layout created successfully"));
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Error, TEXT("Exception creating editor layout: %s"), UTF8_TO_TCHAR(e.what()));
|
|
}
|
|
catch (...)
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Error, TEXT("Unknown exception creating editor layout"));
|
|
}
|
|
}
|
|
|
|
// Register tab spawners
|
|
void FDismembermentEditor::RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Log, TEXT("Registering tab spawners"));
|
|
|
|
FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
|
|
|
|
try
|
|
{
|
|
InTabManager->RegisterTabSpawner(ViewportTabId, FOnSpawnTab::CreateSP(this, &FDismembermentEditor::SpawnTab_Viewport))
|
|
.SetDisplayName(FText::FromString("Viewport"))
|
|
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
|
|
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Viewports"));
|
|
|
|
InTabManager->RegisterTabSpawner(DetailsTabId, FOnSpawnTab::CreateSP(this, &FDismembermentEditor::SpawnTab_Details))
|
|
.SetDisplayName(FText::FromString("Details"))
|
|
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
|
|
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Details"));
|
|
|
|
InTabManager->RegisterTabSpawner(LayerSystemTabId, FOnSpawnTab::CreateSP(this, &FDismembermentEditor::SpawnTab_LayerSystem))
|
|
.SetDisplayName(FText::FromString("Layer System"))
|
|
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
|
|
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Layers"));
|
|
|
|
InTabManager->RegisterTabSpawner(PhysicsSettingsTabId, FOnSpawnTab::CreateSP(this, &FDismembermentEditor::SpawnTab_PhysicsSettings))
|
|
.SetDisplayName(FText::FromString("Physics Settings"))
|
|
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
|
|
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.WorldProperties"));
|
|
|
|
UE_LOG(LogDismembermentEditor, Log, TEXT("Tab spawners registered successfully"));
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Error, TEXT("Exception registering tab spawners: %s"), UTF8_TO_TCHAR(e.what()));
|
|
}
|
|
catch (...)
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Error, TEXT("Unknown exception registering tab spawners"));
|
|
}
|
|
}
|
|
|
|
// Unregister tab spawners
|
|
void FDismembermentEditor::UnregisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Log, TEXT("Unregistering tab spawners"));
|
|
|
|
FAssetEditorToolkit::UnregisterTabSpawners(InTabManager);
|
|
|
|
InTabManager->UnregisterTabSpawner(ViewportTabId);
|
|
InTabManager->UnregisterTabSpawner(DetailsTabId);
|
|
InTabManager->UnregisterTabSpawner(LayerSystemTabId);
|
|
InTabManager->UnregisterTabSpawner(PhysicsSettingsTabId);
|
|
}
|
|
|
|
// Spawn the viewport tab
|
|
TSharedRef<SDockTab> FDismembermentEditor::SpawnTab_Viewport(const FSpawnTabArgs& Args)
|
|
{
|
|
// Create the viewport widget
|
|
ViewportWidget = SNew(SBorder)
|
|
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
|
|
.Padding(FMargin(4.0f))
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(FText::FromString("Viewport will be implemented here"))
|
|
];
|
|
|
|
// Create the tab
|
|
return SNew(SDockTab)
|
|
.Label(FText::FromString("Viewport"))
|
|
[
|
|
ViewportWidget.ToSharedRef()
|
|
];
|
|
}
|
|
|
|
// Spawn the details tab
|
|
TSharedRef<SDockTab> FDismembermentEditor::SpawnTab_Details(const FSpawnTabArgs& Args)
|
|
{
|
|
// Create the details view
|
|
FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
|
|
|
FDetailsViewArgs DetailsViewArgs;
|
|
DetailsViewArgs.bUpdatesFromSelection = true;
|
|
DetailsViewArgs.bLockable = false;
|
|
DetailsViewArgs.bAllowSearch = true;
|
|
DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea;
|
|
DetailsViewArgs.bHideSelectionTip = true;
|
|
|
|
DetailsWidget = PropertyEditorModule.CreateDetailView(DetailsViewArgs);
|
|
DetailsWidget->SetObject(SkeletalMesh);
|
|
|
|
// Create the tab
|
|
return SNew(SDockTab)
|
|
.Label(FText::FromString("Details"))
|
|
[
|
|
DetailsWidget.ToSharedRef()
|
|
];
|
|
}
|
|
|
|
// Spawn the layer system tab
|
|
TSharedRef<SDockTab> FDismembermentEditor::SpawnTab_LayerSystem(const FSpawnTabArgs& Args)
|
|
{
|
|
// Create the layer system widget
|
|
LayerSystemWidget = SNew(SBorder)
|
|
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
|
|
.Padding(FMargin(4.0f))
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(FText::FromString("Layer System will be implemented here"))
|
|
];
|
|
|
|
// Create the tab
|
|
return SNew(SDockTab)
|
|
.Label(FText::FromString("Layer System"))
|
|
[
|
|
LayerSystemWidget.ToSharedRef()
|
|
];
|
|
}
|
|
|
|
// Spawn the physics settings tab
|
|
TSharedRef<SDockTab> FDismembermentEditor::SpawnTab_PhysicsSettings(const FSpawnTabArgs& Args)
|
|
{
|
|
// Create the physics settings widget
|
|
PhysicsSettingsWidget = SNew(SBorder)
|
|
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
|
|
.Padding(FMargin(4.0f))
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(FText::FromString("Physics Settings will be implemented here"))
|
|
];
|
|
|
|
// Create the tab
|
|
return SNew(SDockTab)
|
|
.Label(FText::FromString("Physics Settings"))
|
|
[
|
|
PhysicsSettingsWidget.ToSharedRef()
|
|
];
|
|
}
|
|
|
|
// Perform a boolean cut operation
|
|
void FDismembermentEditor::PerformBooleanCut()
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Log, TEXT("Performing boolean cut operation"));
|
|
|
|
try
|
|
{
|
|
if (!SkeletalMesh)
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Error, TEXT("Cannot perform boolean cut: No skeletal mesh selected"));
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Cannot perform boolean cut: No skeletal mesh selected"));
|
|
return;
|
|
}
|
|
|
|
if (!BooleanCutTool)
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Error, TEXT("Cannot perform boolean cut: Boolean cut tool not initialized"));
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Cannot perform boolean cut: Boolean cut tool not initialized"));
|
|
return;
|
|
}
|
|
|
|
// Perform the actual boolean cut operation
|
|
UE_LOG(LogDismembermentEditor, Log, TEXT("Using boolean cut tool to perform cut operation, Bone: %s, Create Cap: %s"),
|
|
*SelectedBoneName.ToString(), bCreateCapMesh ? TEXT("Yes") : TEXT("No"));
|
|
|
|
// If a bone is selected, use bone-guided cutting
|
|
TArray<USkeletalMesh*> ResultMeshes;
|
|
if (SelectedBoneName != NAME_None)
|
|
{
|
|
ResultMeshes = BooleanCutTool->CutWithBoneAxis(SkeletalMesh, SelectedBoneName, CurrentCutPlane, bCreateCapMesh);
|
|
}
|
|
else
|
|
{
|
|
// Otherwise use regular skeletal mesh cutting
|
|
ResultMeshes = BooleanCutTool->CutSkeletalMesh(SkeletalMesh, CurrentCutPlane, NAME_None, bCreateCapMesh);
|
|
}
|
|
|
|
// Check results
|
|
if (ResultMeshes.Num() > 0)
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Log, TEXT("Boolean cut completed successfully, generated %d meshes"), ResultMeshes.Num());
|
|
|
|
// Show success message
|
|
FText SuccessMessage = FText::Format(
|
|
FText::FromString("Boolean cut completed, generated {0} meshes"),
|
|
FText::AsNumber(ResultMeshes.Num())
|
|
);
|
|
FMessageDialog::Open(EAppMsgType::Ok, SuccessMessage);
|
|
|
|
// TODO: Display result meshes in viewport
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Warning, TEXT("Boolean cut operation did not generate any meshes"));
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Boolean cut operation did not generate any meshes"));
|
|
}
|
|
|
|
UE_LOG(LogDismembermentEditor, Log, TEXT("Boolean cut operation completed"));
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Error, TEXT("Exception during boolean cut operation: %s"), UTF8_TO_TCHAR(e.what()));
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::Format(FText::FromString("Exception during boolean cut operation: {0}"), FText::FromString(UTF8_TO_TCHAR(e.what()))));
|
|
}
|
|
catch (...)
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Error, TEXT("Unknown exception during boolean cut operation"));
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Unknown exception during boolean cut operation"));
|
|
}
|
|
}
|
|
|
|
// Add a new layer
|
|
void FDismembermentEditor::AddNewLayer()
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Log, TEXT("Adding new anatomical layer"));
|
|
|
|
try
|
|
{
|
|
if (!SkeletalMesh)
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Error, TEXT("Cannot add new layer: No skeletal mesh selected"));
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Cannot add new layer: No skeletal mesh selected"));
|
|
return;
|
|
}
|
|
|
|
if (!LayerSystem)
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Error, TEXT("Cannot add new layer: Layer system not initialized"));
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Cannot add new layer: Layer system not initialized"));
|
|
return;
|
|
}
|
|
|
|
// Show a dialog to get the layer type and name
|
|
TSharedRef<SWindow> Window = SNew(SWindow)
|
|
.Title(FText::FromString("Add New Anatomical Layer"))
|
|
.SizingRule(ESizingRule::Autosized)
|
|
.SupportsMaximize(false)
|
|
.SupportsMinimize(false);
|
|
|
|
TSharedPtr<SComboBox<TSharedPtr<EAnatomicalLayerType>>> LayerTypeComboBox;
|
|
TSharedPtr<SEditableTextBox> LayerNameTextBox;
|
|
|
|
// Create array of layer types
|
|
TArray<TSharedPtr<EAnatomicalLayerType>> LayerTypes;
|
|
LayerTypes.Add(MakeShareable(new EAnatomicalLayerType(EAnatomicalLayerType::Skin)));
|
|
LayerTypes.Add(MakeShareable(new EAnatomicalLayerType(EAnatomicalLayerType::Muscle)));
|
|
LayerTypes.Add(MakeShareable(new EAnatomicalLayerType(EAnatomicalLayerType::Bone)));
|
|
LayerTypes.Add(MakeShareable(new EAnatomicalLayerType(EAnatomicalLayerType::Organ)));
|
|
LayerTypes.Add(MakeShareable(new EAnatomicalLayerType(EAnatomicalLayerType::Blood)));
|
|
LayerTypes.Add(MakeShareable(new EAnatomicalLayerType(EAnatomicalLayerType::Custom)));
|
|
|
|
// Selected layer type
|
|
TSharedPtr<EAnatomicalLayerType> SelectedLayerType = LayerTypes[0];
|
|
|
|
// Layer name
|
|
FString NewLayerName = TEXT("New Layer");
|
|
|
|
// Dialog result
|
|
bool bDialogResult = false;
|
|
|
|
Window->SetContent(
|
|
SNew(SBorder)
|
|
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
|
|
.Padding(FMargin(8.0f))
|
|
[
|
|
SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(0, 0, 0, 4)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(FText::FromString("Layer Type:"))
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(0, 0, 0, 8)
|
|
[
|
|
SAssignNew(LayerTypeComboBox, SComboBox<TSharedPtr<EAnatomicalLayerType>>)
|
|
.OptionsSource(&LayerTypes)
|
|
.InitiallySelectedItem(SelectedLayerType)
|
|
.OnSelectionChanged_Lambda([&SelectedLayerType](TSharedPtr<EAnatomicalLayerType> NewValue, ESelectInfo::Type SelectType) {
|
|
SelectedLayerType = NewValue;
|
|
})
|
|
.OnGenerateWidget_Lambda([](TSharedPtr<EAnatomicalLayerType> InLayerType) {
|
|
FString TypeName;
|
|
switch (*InLayerType)
|
|
{
|
|
case EAnatomicalLayerType::Skin: TypeName = TEXT("Skin"); break;
|
|
case EAnatomicalLayerType::Muscle: TypeName = TEXT("Muscle"); break;
|
|
case EAnatomicalLayerType::Bone: TypeName = TEXT("Bone"); break;
|
|
case EAnatomicalLayerType::Organ: TypeName = TEXT("Organ"); break;
|
|
case EAnatomicalLayerType::Blood: TypeName = TEXT("Blood"); break;
|
|
case EAnatomicalLayerType::Custom: TypeName = TEXT("Custom"); break;
|
|
default: TypeName = TEXT("Unknown"); break;
|
|
}
|
|
return SNew(STextBlock).Text(FText::FromString(TypeName));
|
|
})
|
|
[
|
|
SNew(STextBlock)
|
|
.Text_Lambda([&SelectedLayerType]() {
|
|
FString TypeName;
|
|
switch (*SelectedLayerType)
|
|
{
|
|
case EAnatomicalLayerType::Skin: TypeName = TEXT("Skin"); break;
|
|
case EAnatomicalLayerType::Muscle: TypeName = TEXT("Muscle"); break;
|
|
case EAnatomicalLayerType::Bone: TypeName = TEXT("Bone"); break;
|
|
case EAnatomicalLayerType::Organ: TypeName = TEXT("Organ"); break;
|
|
case EAnatomicalLayerType::Blood: TypeName = TEXT("Blood"); break;
|
|
case EAnatomicalLayerType::Custom: TypeName = TEXT("Custom"); break;
|
|
default: TypeName = TEXT("Unknown"); break;
|
|
}
|
|
return FText::FromString(TypeName);
|
|
})
|
|
]
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(0, 0, 0, 4)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(FText::FromString("Layer Name:"))
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(0, 0, 0, 8)
|
|
[
|
|
SAssignNew(LayerNameTextBox, SEditableTextBox)
|
|
.Text(FText::FromString(NewLayerName))
|
|
.OnTextChanged_Lambda([&NewLayerName](const FText& NewText) {
|
|
NewLayerName = NewText.ToString();
|
|
})
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(0, 8, 0, 0)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.FillWidth(1.0f)
|
|
[
|
|
SNew(SSpacer)
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(0, 0, 4, 0)
|
|
[
|
|
SNew(SButton)
|
|
.Text(FText::FromString("Cancel"))
|
|
.OnClicked_Lambda([&Window, &bDialogResult]() {
|
|
bDialogResult = false;
|
|
Window->RequestDestroyWindow();
|
|
return FReply::Handled();
|
|
})
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SButton)
|
|
.Text(FText::FromString("Add"))
|
|
.OnClicked_Lambda([&Window, &bDialogResult]() {
|
|
bDialogResult = true;
|
|
Window->RequestDestroyWindow();
|
|
return FReply::Handled();
|
|
})
|
|
]
|
|
]
|
|
]
|
|
);
|
|
|
|
// Show the dialog
|
|
GEditor->EditorAddModalWindow(Window);
|
|
|
|
// If the user clicked Add
|
|
if (bDialogResult && SelectedLayerType.IsValid())
|
|
{
|
|
// Add the new layer
|
|
int32 NewLayerIndex = LayerSystem->AddLayer(FName(*NewLayerName), *SelectedLayerType);
|
|
|
|
if (NewLayerIndex != INDEX_NONE)
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Log, TEXT("Added new layer: %s (Type: %d)"), *NewLayerName, (int32)*SelectedLayerType);
|
|
|
|
// Show success message
|
|
FText SuccessMessage = FText::Format(
|
|
FText::FromString("Successfully added new layer: {0}"),
|
|
FText::FromString(NewLayerName)
|
|
);
|
|
FMessageDialog::Open(EAppMsgType::Ok, SuccessMessage);
|
|
|
|
// TODO: Update the UI to show the new layer
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Error, TEXT("Failed to add new layer"));
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Failed to add new layer"));
|
|
}
|
|
}
|
|
|
|
UE_LOG(LogDismembermentEditor, Log, TEXT("Add new layer operation completed"));
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Error, TEXT("Exception adding new layer: %s"), UTF8_TO_TCHAR(e.what()));
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::Format(FText::FromString("Exception occurred while adding new layer: {0}"), FText::FromString(UTF8_TO_TCHAR(e.what()))));
|
|
}
|
|
catch (...)
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Error, TEXT("Unknown exception adding new layer"));
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Unknown exception occurred while adding new layer"));
|
|
}
|
|
}
|
|
|
|
// Save the edits
|
|
void FDismembermentEditor::SaveEdits()
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Log, TEXT("Saving dismemberment setup"));
|
|
|
|
try
|
|
{
|
|
if (!SkeletalMesh)
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Error, TEXT("Cannot save edits: No skeletal mesh selected"));
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Cannot save edits: No skeletal mesh selected"));
|
|
return;
|
|
}
|
|
|
|
// TODO: Implement the actual save functionality
|
|
// This will involve:
|
|
// 1. Saving the current state of the mesh
|
|
// 2. Saving the layer system configuration
|
|
// 3. Saving the physics settings
|
|
|
|
// For now, just show a message
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Save functionality will be implemented in a future update"));
|
|
|
|
UE_LOG(LogDismembermentEditor, Log, TEXT("Dismemberment setup saved"));
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Error, TEXT("Exception saving edits: %s"), UTF8_TO_TCHAR(e.what()));
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::Format(FText::FromString("Exception occurred while saving edits: {0}"), FText::FromString(UTF8_TO_TCHAR(e.what()))));
|
|
}
|
|
catch (...)
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Error, TEXT("Unknown exception saving edits"));
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Unknown exception occurred while saving edits"));
|
|
}
|
|
}
|
|
|
|
// Preview the effects
|
|
void FDismembermentEditor::PreviewEffects()
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Log, TEXT("Previewing dismemberment effects"));
|
|
|
|
try
|
|
{
|
|
if (!SkeletalMesh)
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Error, TEXT("Cannot preview effects: No skeletal mesh selected"));
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Cannot preview effects: No skeletal mesh selected"));
|
|
return;
|
|
}
|
|
|
|
// TODO: Implement the actual preview functionality
|
|
// This will involve:
|
|
// 1. Creating a temporary instance of the mesh
|
|
// 2. Applying the current dismemberment settings
|
|
// 3. Showing the effects in the viewport
|
|
|
|
// For now, just show a message
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Preview functionality will be implemented in a future update"));
|
|
|
|
UE_LOG(LogDismembermentEditor, Log, TEXT("Dismemberment effects previewed"));
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Error, TEXT("Exception previewing effects: %s"), UTF8_TO_TCHAR(e.what()));
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::Format(FText::FromString("Exception occurred while previewing effects: {0}"), FText::FromString(UTF8_TO_TCHAR(e.what()))));
|
|
}
|
|
catch (...)
|
|
{
|
|
UE_LOG(LogDismembermentEditor, Error, TEXT("Unknown exception previewing effects"));
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Unknown exception occurred while previewing effects"));
|
|
}
|
|
}
|
|
|
|
// Create the editor toolbar
|
|
void FDismembermentEditor::CreateEditorToolbar()
|
|
{
|
|
// Create command list
|
|
TSharedPtr<FUICommandList> CommandList = MakeShareable(new FUICommandList);
|
|
|
|
// Get the toolbar builder
|
|
TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender);
|
|
|
|
ToolbarExtender->AddToolBarExtension(
|
|
"Asset",
|
|
EExtensionHook::After,
|
|
CommandList,
|
|
FToolBarExtensionDelegate::CreateLambda([this](FToolBarBuilder& ToolbarBuilder)
|
|
{
|
|
ToolbarBuilder.BeginSection("Dismemberment");
|
|
{
|
|
ToolbarBuilder.AddToolBarButton(
|
|
FUIAction(FExecuteAction::CreateSP(this, &FDismembermentEditor::PerformBooleanCut)),
|
|
NAME_None,
|
|
FText::FromString("Boolean Cut"),
|
|
FText::FromString("Perform Boolean Cut Operation"),
|
|
FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.CurveBase"),
|
|
EUserInterfaceActionType::Button
|
|
);
|
|
|
|
ToolbarBuilder.AddToolBarButton(
|
|
FUIAction(FExecuteAction::CreateSP(this, &FDismembermentEditor::AddNewLayer)),
|
|
NAME_None,
|
|
FText::FromString("Add Layer"),
|
|
FText::FromString("Add New Anatomical Layer"),
|
|
FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.Layer"),
|
|
EUserInterfaceActionType::Button
|
|
);
|
|
|
|
ToolbarBuilder.AddToolBarButton(
|
|
FUIAction(FExecuteAction::CreateSP(this, &FDismembermentEditor::PreviewEffects)),
|
|
NAME_None,
|
|
FText::FromString("Preview"),
|
|
FText::FromString("Preview Dismemberment Effects"),
|
|
FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.ParticleSystem"),
|
|
EUserInterfaceActionType::Button
|
|
);
|
|
|
|
ToolbarBuilder.AddToolBarButton(
|
|
FUIAction(FExecuteAction::CreateSP(this, &FDismembermentEditor::SaveEdits)),
|
|
NAME_None,
|
|
FText::FromString("Save"),
|
|
FText::FromString("Save Dismemberment Settings"),
|
|
FSlateIcon(FAppStyle::GetAppStyleSetName(), "AssetEditor.SaveAsset"),
|
|
EUserInterfaceActionType::Button
|
|
);
|
|
}
|
|
ToolbarBuilder.EndSection();
|
|
})
|
|
);
|
|
|
|
// Add the extender
|
|
AddToolbarExtender(ToolbarExtender);
|
|
}
|