1703 lines
61 KiB
C++
1703 lines
61 KiB
C++
#include "FLESHEditor.h"
|
|
#include "FLESHEditorCommands.h"
|
|
#include "FLESHEditorStyle.h"
|
|
#include "MatrixInputWidget.h"
|
|
#include "DismembermentGraph/DismembermentGraphEditor.h"
|
|
#include "DismembermentGraph/DismembermentGraphAsset.h"
|
|
#include "Framework/Docking/TabManager.h"
|
|
#include "Widgets/Docking/SDockTab.h"
|
|
#include "Widgets/Layout/SBox.h"
|
|
#include "Widgets/Layout/SBorder.h"
|
|
#include "Widgets/Input/SButton.h"
|
|
#include "Widgets/Text/STextBlock.h"
|
|
#include "Widgets/Input/SSearchBox.h"
|
|
#include "Widgets/Layout/SUniformGridPanel.h"
|
|
#include "Widgets/Views/STreeView.h"
|
|
#include "Widgets/Views/STableViewBase.h"
|
|
#include "Widgets/Views/STableRow.h"
|
|
#include "Widgets/Input/SCheckBox.h"
|
|
#include "PropertyEditorModule.h"
|
|
#include "Styling/AppStyle.h"
|
|
#include "LevelEditor.h"
|
|
#include "ToolMenus.h"
|
|
#include "Widgets/Input/SSearchBox.h"
|
|
#include "Widgets/Layout/SUniformGridPanel.h"
|
|
#include "FLESHViewportClient.h"
|
|
#include "VisceraNodeObject.h"
|
|
#include "BooleanCutTool.h"
|
|
#include "VisceraNodeFactory.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "FLESHEditor"
|
|
|
|
const FName FFLESHEditor::ViewportTabId(TEXT("FLESHEditor_Viewport"));
|
|
const FName FFLESHEditor::DetailsTabId(TEXT("FLESHEditor_Details"));
|
|
const FName FFLESHEditor::AssetBrowserTabId(TEXT("FLESHEditor_AssetBrowser"));
|
|
const FName FFLESHEditor::MatrixEditorTabId(TEXT("FLESHEditor_MatrixEditor"));
|
|
const FName FFLESHEditor::GraphEditorTabId(TEXT("FLESHEditor_GraphEditor"));
|
|
const FName FFLESHEditor::ToolbarTabId(TEXT("FLESHEditor_Toolbar"));
|
|
|
|
FFLESHEditor::FFLESHEditor()
|
|
: EditingObject(nullptr)
|
|
, CommandList(MakeShareable(new FUICommandList))
|
|
{
|
|
// Initialize member variables
|
|
bIsEditorInitialized = false;
|
|
}
|
|
|
|
FFLESHEditor::~FFLESHEditor()
|
|
{
|
|
// Unregister from any events
|
|
// Comment out problematic code, as FFLESHEditor doesn't inherit from FEditorUndoClient
|
|
// if (GEditor)
|
|
// {
|
|
// GEditor->UnregisterForUndo(this);
|
|
// }
|
|
|
|
// Clear references
|
|
DetailsWidget = nullptr;
|
|
EditingObject = nullptr;
|
|
}
|
|
|
|
void FFLESHEditor::InitFLESHEditor(const EToolkitMode::Type Mode, const TSharedPtr<IToolkitHost>& InitToolkitHost, UObject* InObject)
|
|
{
|
|
// Store the object we're editing
|
|
EditingObject = InObject;
|
|
|
|
// Create command list
|
|
this->CreateCommandList();
|
|
|
|
// Build bone hierarchy
|
|
BuildBoneHierarchy();
|
|
|
|
// Create tab layout
|
|
const TSharedRef<FTabManager::FLayout> StandaloneDefaultLayout = FTabManager::NewLayout("FLESHEditorLayout_v3.0")
|
|
->AddArea
|
|
(
|
|
FTabManager::NewPrimaryArea()->SetOrientation(Orient_Vertical)
|
|
->Split
|
|
(
|
|
FTabManager::NewStack()
|
|
->SetSizeCoefficient(0.1f)
|
|
->AddTab(ToolbarTabId, ETabState::OpenedTab)
|
|
->SetHideTabWell(true)
|
|
)
|
|
->Split
|
|
(
|
|
FTabManager::NewSplitter()->SetOrientation(Orient_Horizontal)
|
|
->SetSizeCoefficient(0.9f)
|
|
->Split
|
|
(
|
|
// Left side outline view panel - includes bone hierarchy
|
|
FTabManager::NewStack()
|
|
->SetSizeCoefficient(0.2f)
|
|
->AddTab(DetailsTabId, ETabState::OpenedTab)
|
|
->SetForegroundTab(DetailsTabId)
|
|
)
|
|
->Split
|
|
(
|
|
// Middle main view area
|
|
FTabManager::NewStack()
|
|
->SetSizeCoefficient(0.6f)
|
|
->AddTab(ViewportTabId, ETabState::OpenedTab)
|
|
->SetForegroundTab(ViewportTabId)
|
|
)
|
|
->Split
|
|
(
|
|
// Right side property panel
|
|
FTabManager::NewSplitter()->SetOrientation(Orient_Vertical)
|
|
->SetSizeCoefficient(0.2f)
|
|
->Split
|
|
(
|
|
// Matrix editor
|
|
FTabManager::NewStack()
|
|
->SetSizeCoefficient(0.5f)
|
|
->AddTab(MatrixEditorTabId, ETabState::OpenedTab)
|
|
)
|
|
->Split
|
|
(
|
|
// Graph editor
|
|
FTabManager::NewStack()
|
|
->SetSizeCoefficient(0.5f)
|
|
->AddTab(GraphEditorTabId, ETabState::OpenedTab)
|
|
)
|
|
)
|
|
)
|
|
);
|
|
|
|
// Create a standalone toolkit
|
|
FAssetEditorToolkit::InitAssetEditor(
|
|
Mode,
|
|
InitToolkitHost,
|
|
FName("FLESHEditorApp"),
|
|
StandaloneDefaultLayout,
|
|
true, // bCreateDefaultStandaloneMenu
|
|
true, // bCreateDefaultToolbar
|
|
InObject);
|
|
|
|
// Register tab spawners
|
|
TabManager = GetTabManager();
|
|
if (TabManager.IsValid())
|
|
{
|
|
TabManager->RegisterTabSpawner(ViewportTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_Viewport))
|
|
.SetDisplayName(LOCTEXT("ViewportTab", "Viewport"))
|
|
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
|
|
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Viewports"));
|
|
|
|
TabManager->RegisterTabSpawner(DetailsTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_Details))
|
|
.SetDisplayName(LOCTEXT("DetailsTab", "Details"))
|
|
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
|
|
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Details"));
|
|
|
|
TabManager->RegisterTabSpawner(AssetBrowserTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_AssetBrowser))
|
|
.SetDisplayName(LOCTEXT("AssetBrowserTab", "Asset Browser"))
|
|
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
|
|
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "ContentBrowser.TabIcon"));
|
|
|
|
TabManager->RegisterTabSpawner(MatrixEditorTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_MatrixEditor))
|
|
.SetDisplayName(LOCTEXT("MatrixEditorTab", "Matrix Editor"))
|
|
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
|
|
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "PropertyEditor.Grid.TabIcon"));
|
|
|
|
TabManager->RegisterTabSpawner(GraphEditorTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_GraphEditor))
|
|
.SetDisplayName(LOCTEXT("GraphEditorTab", "Graph Editor"))
|
|
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
|
|
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "GraphEditor.EventGraph_16x"));
|
|
|
|
TabManager->RegisterTabSpawner(ToolbarTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_Toolbar))
|
|
.SetDisplayName(LOCTEXT("ToolbarTab", "Toolbar"))
|
|
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
|
|
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Toolbar"));
|
|
}
|
|
|
|
// Initialize the editor using the base class method, so it won't crash even if an empty object is passed in.
|
|
FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
|
FDetailsViewArgs DetailsViewArgs;
|
|
DetailsViewArgs.bUpdatesFromSelection = true;
|
|
DetailsViewArgs.bLockable = false;
|
|
DetailsViewArgs.bAllowSearch = true;
|
|
DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea;
|
|
DetailsViewArgs.bHideSelectionTip = true;
|
|
DetailsViewArgs.NotifyHook = nullptr;
|
|
DetailsViewArgs.bSearchInitialKeyFocus = false;
|
|
DetailsViewArgs.ViewIdentifier = NAME_None;
|
|
DetailsViewArgs.DefaultsOnlyVisibility = EEditDefaultsOnlyNodeVisibility::Automatic;
|
|
DetailsViewArgs.bShowOptions = true;
|
|
DetailsViewArgs.bAllowMultipleTopLevelObjects = true;
|
|
DetailsWidget = PropertyEditorModule.CreateDetailView(DetailsViewArgs);
|
|
DetailsWidget->SetObject(EditingObject);
|
|
}
|
|
|
|
void FFLESHEditor::RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
|
|
{
|
|
WorkspaceMenuCategory = InTabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("WorkspaceMenu_FLESHEditor", "FLESH Editor"));
|
|
auto WorkspaceMenuCategoryRef = WorkspaceMenuCategory.ToSharedRef();
|
|
|
|
FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
|
|
|
|
InTabManager->RegisterTabSpawner(ViewportTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_Viewport))
|
|
.SetDisplayName(LOCTEXT("ViewportTab", "Viewport"))
|
|
.SetGroup(WorkspaceMenuCategoryRef)
|
|
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Viewports"));
|
|
|
|
InTabManager->RegisterTabSpawner(DetailsTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_Details))
|
|
.SetDisplayName(LOCTEXT("DetailsTab", "Details"))
|
|
.SetGroup(WorkspaceMenuCategoryRef)
|
|
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Details"));
|
|
|
|
InTabManager->RegisterTabSpawner(GraphEditorTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_GraphEditor))
|
|
.SetDisplayName(LOCTEXT("GraphEditorTab", "Graph Editor"))
|
|
.SetGroup(WorkspaceMenuCategoryRef)
|
|
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "GraphEditor.EventGraph_16x"));
|
|
|
|
InTabManager->RegisterTabSpawner(AssetBrowserTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_AssetBrowser))
|
|
.SetDisplayName(LOCTEXT("AssetBrowserTab", "Asset Browser"))
|
|
.SetGroup(WorkspaceMenuCategoryRef)
|
|
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "ContentBrowser.TabIcon"));
|
|
}
|
|
|
|
void FFLESHEditor::UnregisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
|
|
{
|
|
FAssetEditorToolkit::UnregisterTabSpawners(InTabManager);
|
|
|
|
// Unregister tab spawners
|
|
InTabManager->UnregisterTabSpawner(ViewportTabId);
|
|
InTabManager->UnregisterTabSpawner(DetailsTabId);
|
|
InTabManager->UnregisterTabSpawner(AssetBrowserTabId);
|
|
InTabManager->UnregisterTabSpawner(MatrixEditorTabId);
|
|
InTabManager->UnregisterTabSpawner(GraphEditorTabId);
|
|
InTabManager->UnregisterTabSpawner(ToolbarTabId);
|
|
}
|
|
|
|
FName FFLESHEditor::GetToolkitFName() const
|
|
{
|
|
return FName("FLESHEditor");
|
|
}
|
|
|
|
FText FFLESHEditor::GetBaseToolkitName() const
|
|
{
|
|
return LOCTEXT("FLESHEditorAppLabel", "FLESH Editor");
|
|
}
|
|
|
|
FString FFLESHEditor::GetWorldCentricTabPrefix() const
|
|
{
|
|
return TEXT("FLESH ");
|
|
}
|
|
|
|
FLinearColor FFLESHEditor::GetWorldCentricTabColorScale() const
|
|
{
|
|
return FLinearColor(0.7f, 0.0f, 0.0f, 0.5f);
|
|
}
|
|
|
|
void FFLESHEditor::OpenEditor()
|
|
{
|
|
try
|
|
{
|
|
// Create a DismembermentGraphAsset to avoid assertion failure and provide meaningful editing
|
|
UDismembermentGraphAsset* GraphAsset = NewObject<UDismembermentGraphAsset>(GetTransientPackage(), UDismembermentGraphAsset::StaticClass(), TEXT("TempDismembermentGraphAsset"));
|
|
|
|
// Create new editor instance
|
|
TSharedRef<FFLESHEditor> NewEditor = MakeShareable(new FFLESHEditor());
|
|
NewEditor->InitFLESHEditor(EToolkitMode::Standalone, nullptr, GraphAsset);
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
UE_LOG(LogTemp, Error, TEXT("Exception in FFLESHEditor::OpenEditor: %s"), UTF8_TO_TCHAR(e.what()));
|
|
FMessageDialog::Open(EAppMsgType::Ok, FText::Format(LOCTEXT("EditorOpenError", "无法打开FLESH编辑器: {0}"), FText::FromString(UTF8_TO_TCHAR(e.what()))));
|
|
}
|
|
catch (...)
|
|
{
|
|
UE_LOG(LogTemp, Error, TEXT("Unknown exception in FFLESHEditor::OpenEditor"));
|
|
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("EditorOpenUnknownError", "打开FLESH编辑器时发生未知错误"));
|
|
}
|
|
}
|
|
|
|
TSharedRef<SDockTab> FFLESHEditor::SpawnTab_Viewport(const FSpawnTabArgs& Args)
|
|
{
|
|
return SNew(SDockTab)
|
|
.Label(LOCTEXT("ViewportTitle", "Viewport"))
|
|
[
|
|
CreateViewportWidget()
|
|
];
|
|
}
|
|
|
|
TSharedRef<SDockTab> FFLESHEditor::SpawnTab_Details(const FSpawnTabArgs& Args)
|
|
{
|
|
return SNew(SDockTab)
|
|
.Label(LOCTEXT("DetailsTitle", "Details"))
|
|
[
|
|
CreateDetailsWidget()
|
|
];
|
|
}
|
|
|
|
TSharedRef<SDockTab> FFLESHEditor::SpawnTab_AssetBrowser(const FSpawnTabArgs& Args)
|
|
{
|
|
return SNew(SDockTab)
|
|
.Label(LOCTEXT("AssetBrowserTitle", "Asset Browser"))
|
|
[
|
|
CreateAssetBrowserWidget()
|
|
];
|
|
}
|
|
|
|
TSharedRef<SDockTab> FFLESHEditor::SpawnTab_MatrixEditor(const FSpawnTabArgs& Args)
|
|
{
|
|
try
|
|
{
|
|
return SNew(SDockTab)
|
|
.Label(LOCTEXT("MatrixEditorTitle", "Matrix Editor"))
|
|
[
|
|
CreateMatrixEditorWidget()
|
|
];
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
UE_LOG(LogTemp, Error, TEXT("Exception in SpawnTab_MatrixEditor: %s"), UTF8_TO_TCHAR(e.what()));
|
|
|
|
// Return a simple error widget to avoid crashing
|
|
return SNew(SDockTab)
|
|
.Label(LOCTEXT("MatrixEditorTitle", "Matrix Editor"))
|
|
[
|
|
SNew(SBox)
|
|
.HAlign(HAlign_Center)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("MatrixEditorError", "Error loading Matrix Editor"))
|
|
.ColorAndOpacity(FLinearColor::Red)
|
|
]
|
|
];
|
|
}
|
|
catch (...)
|
|
{
|
|
UE_LOG(LogTemp, Error, TEXT("Unknown exception in SpawnTab_MatrixEditor"));
|
|
|
|
// Return a simple error widget to avoid crashing
|
|
return SNew(SDockTab)
|
|
.Label(LOCTEXT("MatrixEditorTitle", "Matrix Editor"))
|
|
[
|
|
SNew(SBox)
|
|
.HAlign(HAlign_Center)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("MatrixEditorError", "Error loading Matrix Editor"))
|
|
.ColorAndOpacity(FLinearColor::Red)
|
|
]
|
|
];
|
|
}
|
|
}
|
|
|
|
TSharedRef<SDockTab> FFLESHEditor::SpawnTab_GraphEditor(const FSpawnTabArgs& Args)
|
|
{
|
|
try
|
|
{
|
|
return SNew(SDockTab)
|
|
.Label(LOCTEXT("GraphEditorTitle", "Graph Editor"))
|
|
[
|
|
CreateGraphEditorWidget()
|
|
];
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
UE_LOG(LogTemp, Error, TEXT("Exception in SpawnTab_GraphEditor: %s"), UTF8_TO_TCHAR(e.what()));
|
|
|
|
// Return a simple error widget to avoid crashing
|
|
return SNew(SDockTab)
|
|
.Label(LOCTEXT("GraphEditorTitle", "Graph Editor"))
|
|
[
|
|
SNew(SBox)
|
|
.HAlign(HAlign_Center)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("GraphEditorError", "Error loading Graph Editor"))
|
|
.ColorAndOpacity(FLinearColor::Red)
|
|
]
|
|
];
|
|
}
|
|
catch (...)
|
|
{
|
|
UE_LOG(LogTemp, Error, TEXT("Unknown exception in SpawnTab_GraphEditor"));
|
|
|
|
// Return a simple error widget to avoid crashing
|
|
return SNew(SDockTab)
|
|
.Label(LOCTEXT("GraphEditorTitle", "Graph Editor"))
|
|
[
|
|
SNew(SBox)
|
|
.HAlign(HAlign_Center)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("GraphEditorError", "Error loading Graph Editor"))
|
|
.ColorAndOpacity(FLinearColor::Red)
|
|
]
|
|
];
|
|
}
|
|
}
|
|
|
|
TSharedRef<SDockTab> FFLESHEditor::SpawnTab_Toolbar(const FSpawnTabArgs& Args)
|
|
{
|
|
return SNew(SDockTab)
|
|
.Label(LOCTEXT("ToolbarTitle", "Toolbar"))
|
|
[
|
|
CreateToolbarWidget()
|
|
];
|
|
}
|
|
|
|
TSharedRef<SWidget> FFLESHEditor::CreateViewportWidget()
|
|
{
|
|
// Create viewport client
|
|
ViewportClient = MakeShared<FFLESHViewportClient>(this);
|
|
|
|
// Create viewport widget
|
|
ViewportWidget = SNew(SViewport)
|
|
.IsEnabled(FSlateApplication::Get().GetNormalExecutionAttribute())
|
|
.EnableGammaCorrection(false);
|
|
|
|
// Create scene viewport
|
|
Viewport = MakeShared<FSceneViewport>(ViewportClient.Get(), ViewportWidget);
|
|
|
|
// Set viewport in client
|
|
ViewportClient->Viewport = Viewport.Get();
|
|
|
|
// Associate scene viewport with widget
|
|
ViewportWidget->SetViewportInterface(StaticCastSharedRef<ISlateViewport>(Viewport.ToSharedRef()));
|
|
|
|
// Create viewport toolbar
|
|
TSharedRef<SWidget> ViewportToolbar = SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(2.0f)
|
|
[
|
|
SNew(SButton)
|
|
.ButtonStyle(FAppStyle::Get(), "FlatButton.Success")
|
|
.OnClicked(this, &FFLESHEditor::OnResetCameraClicked)
|
|
.ToolTipText(LOCTEXT("ResetCamera", "Reset camera position"))
|
|
.ContentPadding(FMargin(2.0f))
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("ResetCameraButton", "Reset Camera"))
|
|
.Font(FAppStyle::GetFontStyle("PropertyWindow.NormalFont"))
|
|
]
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(2.0f)
|
|
[
|
|
SNew(SButton)
|
|
.ButtonStyle(FAppStyle::Get(), "FlatButton.Success")
|
|
.OnClicked(this, &FFLESHEditor::OnToggleWireframeClicked)
|
|
.ToolTipText(LOCTEXT("ToggleWireframe", "Toggle wireframe mode"))
|
|
.ContentPadding(FMargin(2.0f))
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("ToggleWireframeButton", "Wireframe"))
|
|
.Font(FAppStyle::GetFontStyle("PropertyWindow.NormalFont"))
|
|
]
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(2.0f)
|
|
[
|
|
SNew(SButton)
|
|
.ButtonStyle(FAppStyle::Get(), "FlatButton.Success")
|
|
.OnClicked(this, &FFLESHEditor::OnToggleBonesClicked)
|
|
.ToolTipText(LOCTEXT("ToggleBones", "Show/Hide bones"))
|
|
.ContentPadding(FMargin(2.0f))
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("ToggleBonesButton", "Show Bones"))
|
|
.Font(FAppStyle::GetFontStyle("PropertyWindow.NormalFont"))
|
|
]
|
|
];
|
|
|
|
// Create viewport container
|
|
return SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(2.0f)
|
|
[
|
|
ViewportToolbar
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.FillHeight(1.0f)
|
|
[
|
|
ViewportWidget.ToSharedRef()
|
|
];
|
|
}
|
|
|
|
TSharedRef<SWidget> FFLESHEditor::CreateDetailsWidget()
|
|
{
|
|
// Create details panel
|
|
FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
|
FDetailsViewArgs DetailsViewArgs;
|
|
DetailsViewArgs.bUpdatesFromSelection = true;
|
|
DetailsViewArgs.bLockable = false;
|
|
DetailsViewArgs.bAllowSearch = true;
|
|
DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea;
|
|
DetailsViewArgs.bHideSelectionTip = true;
|
|
DetailsViewArgs.NotifyHook = nullptr;
|
|
DetailsViewArgs.bSearchInitialKeyFocus = false;
|
|
DetailsViewArgs.ViewIdentifier = NAME_None;
|
|
DetailsViewArgs.DefaultsOnlyVisibility = EEditDefaultsOnlyNodeVisibility::Automatic;
|
|
DetailsViewArgs.bShowOptions = true;
|
|
DetailsViewArgs.bAllowMultipleTopLevelObjects = true;
|
|
DetailsWidget = PropertyEditorModule.CreateDetailView(DetailsViewArgs);
|
|
|
|
// Create the node tree
|
|
BuildVisceraNodeTree();
|
|
|
|
// Create the outline view
|
|
return SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(0, 0, 0, 4)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("NodeTreeHeader", "Node Tree"))
|
|
.Font(FAppStyle::GetFontStyle("HeadingFont"))
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.FillHeight(1.0f)
|
|
[
|
|
SNew(SBorder)
|
|
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
|
|
.Padding(4.0f)
|
|
[
|
|
SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.FillWidth(1.0f)
|
|
[
|
|
SNew(SSearchBox)
|
|
.HintText(LOCTEXT("SearchNodes", "Search nodes..."))
|
|
.OnTextChanged_Lambda([this](const FText& InFilterText)
|
|
{
|
|
// TODO: Implement search functionality
|
|
})
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(4, 0, 0, 0)
|
|
[
|
|
SNew(SButton)
|
|
.ButtonStyle(FAppStyle::Get(), "SimpleButton")
|
|
.ContentPadding(FMargin(1, 0))
|
|
.ToolTipText(LOCTEXT("AddNewSoftBodyTooltip", "Add a new SoftBody node"))
|
|
.OnClicked_Lambda([this]() -> FReply
|
|
{
|
|
// Add a new SoftBody node
|
|
AddNewVisceraNode(TEXT("SoftBody"));
|
|
return FReply::Handled();
|
|
})
|
|
[
|
|
SNew(SImage)
|
|
.Image(FAppStyle::GetBrush("Icons.PlusCircle"))
|
|
]
|
|
]
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.FillHeight(1.0f)
|
|
.Padding(0, 4, 0, 0)
|
|
[
|
|
SAssignNew(NodeTreeView, STreeView<TSharedPtr<FVisceraNodeItem>>)
|
|
.TreeItemsSource(&NodeItems)
|
|
.OnGenerateRow(this, &FFLESHEditor::OnGenerateNodeRow)
|
|
.OnGetChildren(this, &FFLESHEditor::OnGetNodeChildren)
|
|
.OnSelectionChanged(this, &FFLESHEditor::OnNodeSelectionChanged)
|
|
.SelectionMode(ESelectionMode::Single)
|
|
]
|
|
]
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(0, 8, 0, 4)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("PropertiesHeader", "Properties"))
|
|
.Font(FAppStyle::GetFontStyle("HeadingFont"))
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.FillHeight(1.0f)
|
|
[
|
|
SNew(SBorder)
|
|
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
|
|
.Padding(4.0f)
|
|
[
|
|
DetailsWidget.ToSharedRef()
|
|
]
|
|
];
|
|
}
|
|
|
|
TSharedRef<SWidget> FFLESHEditor::CreateAssetBrowserWidget()
|
|
{
|
|
// Create asset browser
|
|
return SNew(SBorder)
|
|
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
|
|
.Padding(4.0f)
|
|
[
|
|
SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("AssetBrowserHeader", "Asset Browser"))
|
|
.Font(FAppStyle::GetFontStyle("HeadingFont"))
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.FillHeight(1.0f)
|
|
[
|
|
SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(2.0f)
|
|
[
|
|
SNew(SButton)
|
|
.Text(LOCTEXT("ImportCharacterModel", "Import Character Model"))
|
|
.OnClicked_Lambda([this]() { OnImportCharacterModel(); return FReply::Handled(); })
|
|
.ButtonStyle(FAppStyle::Get(), "FlatButton")
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(2.0f)
|
|
[
|
|
SNew(SButton)
|
|
.Text(LOCTEXT("ImportOrganModel", "Import Organ Model"))
|
|
.OnClicked_Lambda([this]() { OnImportOrganModel(); return FReply::Handled(); })
|
|
.ButtonStyle(FAppStyle::Get(), "FlatButton")
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(2.0f)
|
|
[
|
|
SNew(SButton)
|
|
.Text(LOCTEXT("ImportSkeletonModel", "Import Skeleton Model"))
|
|
.OnClicked_Lambda([this]() { OnImportSkeletonModel(); return FReply::Handled(); })
|
|
.ButtonStyle(FAppStyle::Get(), "FlatButton")
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(2.0f)
|
|
[
|
|
SNew(SButton)
|
|
.Text(LOCTEXT("ImportPhysicsAsset", "Import Physics Asset"))
|
|
.OnClicked_Lambda([this]() { OnImportPhysicsAsset(); return FReply::Handled(); })
|
|
.ButtonStyle(FAppStyle::Get(), "FlatButton")
|
|
]
|
|
]
|
|
];
|
|
}
|
|
|
|
TSharedRef<SWidget> FFLESHEditor::CreateMatrixEditorWidget()
|
|
{
|
|
// Create matrix editor widget
|
|
return SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(0, 0, 0, 4)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("MatrixEditorHeader", "切割参数"))
|
|
.Font(FAppStyle::GetFontStyle("HeadingFont"))
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew(SBorder)
|
|
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
|
|
.Padding(8.0f)
|
|
[
|
|
SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(0, 0, 0, 8)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("CuttingPlaneMatrix", "切割平面矩阵"))
|
|
.Font(FAppStyle::GetFontStyle("NormalFont"))
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SAssignNew(MatrixEditorWidget, SMatrixInputWidget)
|
|
.Rows(4)
|
|
.Columns(3)
|
|
.Matrix(FMatrix::Identity)
|
|
.OnMatrixChanged(FOnMatrixChanged::CreateLambda([this](const FMatrix& NewMatrix)
|
|
{
|
|
// 处理矩阵变化
|
|
UE_LOG(LogTemp, Display, TEXT("Matrix changed"));
|
|
}))
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(0, 8, 0, 0)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(0, 0, 4, 0)
|
|
[
|
|
SNew(SButton)
|
|
.Text(LOCTEXT("ResetMatrix", "重置"))
|
|
.ToolTipText(LOCTEXT("ResetMatrixTooltip", "重置矩阵为单位矩阵"))
|
|
.OnClicked_Lambda([this]()
|
|
{
|
|
// 重置矩阵
|
|
if (MatrixEditorWidget.IsValid())
|
|
{
|
|
MatrixEditorWidget->ResetToIdentity();
|
|
}
|
|
return FReply::Handled();
|
|
})
|
|
]
|
|
]
|
|
]
|
|
];
|
|
}
|
|
|
|
TSharedRef<SWidget> FFLESHEditor::CreateGraphEditorWidget()
|
|
{
|
|
// Create graph editor
|
|
return SNew(SBorder)
|
|
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
|
|
.Padding(4.0f)
|
|
[
|
|
SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("GraphEditorHeader", "Graph Editor"))
|
|
.Font(FAppStyle::GetFontStyle("HeadingFont"))
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.FillHeight(1.0f)
|
|
[
|
|
SNew(SBorder)
|
|
.BorderImage(FAppStyle::GetBrush("ToolPanel.DarkGroupBorder"))
|
|
.Padding(4.0f)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("GraphEditorPlaceholder", "Dismemberment system logic graph will be displayed here"))
|
|
]
|
|
]
|
|
];
|
|
}
|
|
|
|
TSharedRef<SWidget> FFLESHEditor::CreateToolbarWidget()
|
|
{
|
|
// Create toolbar widget
|
|
return SNew(SBorder)
|
|
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
|
|
.Padding(4.0f)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
// Cutting tools group
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(4.0f, 0.0f)
|
|
[
|
|
SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.HAlign(HAlign_Center)
|
|
.Padding(0.0f, 0.0f, 0.0f, 4.0f)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("CutTools", "Cutting Tools"))
|
|
.Font(FAppStyle::GetFontStyle("SmallFont"))
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew(SUniformGridPanel)
|
|
.SlotPadding(FMargin(2.0f))
|
|
.MinDesiredSlotWidth(64.0f)
|
|
.MinDesiredSlotHeight(64.0f)
|
|
|
|
// Plane cut
|
|
+ SUniformGridPanel::Slot(0, 0)
|
|
[
|
|
SNew(SButton)
|
|
.ButtonStyle(FAppStyle::Get(), "ToggleButton")
|
|
.ToolTipText(LOCTEXT("PlaneCutTooltip", "Plane cutting tool"))
|
|
.OnClicked_Lambda([this]()
|
|
{
|
|
// TODO: Activate plane cutting tool
|
|
return FReply::Handled();
|
|
})
|
|
.Content()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("PlaneCut", "Plane"))
|
|
]
|
|
]
|
|
|
|
// Curve cut
|
|
+ SUniformGridPanel::Slot(1, 0)
|
|
[
|
|
SNew(SButton)
|
|
.ButtonStyle(FAppStyle::Get(), "ToggleButton")
|
|
.ToolTipText(LOCTEXT("CurveCutTooltip", "Curve cutting tool"))
|
|
.OnClicked_Lambda([this]()
|
|
{
|
|
// TODO: Activate curve cutting tool
|
|
return FReply::Handled();
|
|
})
|
|
.Content()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("CurveCut", "Curve"))
|
|
]
|
|
]
|
|
|
|
// Freeform cut
|
|
+ SUniformGridPanel::Slot(0, 1)
|
|
[
|
|
SNew(SButton)
|
|
.ButtonStyle(FAppStyle::Get(), "ToggleButton")
|
|
.ToolTipText(LOCTEXT("FreeformCutTooltip", "Freeform cutting tool"))
|
|
.OnClicked_Lambda([this]()
|
|
{
|
|
// TODO: Activate freeform cutting tool
|
|
return FReply::Handled();
|
|
})
|
|
.Content()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("FreeformCut", "Freeform"))
|
|
]
|
|
]
|
|
|
|
// Preset cut
|
|
+ SUniformGridPanel::Slot(1, 1)
|
|
[
|
|
SNew(SButton)
|
|
.ButtonStyle(FAppStyle::Get(), "ToggleButton")
|
|
.ToolTipText(LOCTEXT("PresetCutTooltip", "Preset cutting tool"))
|
|
.OnClicked_Lambda([this]()
|
|
{
|
|
// TODO: Activate preset cutting tool
|
|
return FReply::Handled();
|
|
})
|
|
.Content()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("PresetCut", "Preset"))
|
|
]
|
|
]
|
|
]
|
|
]
|
|
|
|
// Cutting layer options
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(8.0f, 0.0f)
|
|
[
|
|
SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.HAlign(HAlign_Center)
|
|
.Padding(0.0f, 0.0f, 0.0f, 4.0f)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("CutLayers", "Cutting Layers"))
|
|
.Font(FAppStyle::GetFontStyle("SmallFont"))
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(2.0f)
|
|
[
|
|
SNew(SCheckBox)
|
|
.IsChecked(ECheckBoxState::Checked)
|
|
.Content()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("SkinLayer", "Skin"))
|
|
]
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(2.0f)
|
|
[
|
|
SNew(SCheckBox)
|
|
.IsChecked(ECheckBoxState::Checked)
|
|
.Content()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("MuscleLayer", "Muscle"))
|
|
]
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(2.0f)
|
|
[
|
|
SNew(SCheckBox)
|
|
.IsChecked(ECheckBoxState::Checked)
|
|
.Content()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("BoneLayer", "Bone"))
|
|
]
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(2.0f)
|
|
[
|
|
SNew(SCheckBox)
|
|
.IsChecked(ECheckBoxState::Checked)
|
|
.Content()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("OrganLayer", "Organ"))
|
|
]
|
|
]
|
|
]
|
|
]
|
|
|
|
// Effects options
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(8.0f, 0.0f)
|
|
[
|
|
SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.HAlign(HAlign_Center)
|
|
.Padding(0.0f, 0.0f, 0.0f, 4.0f)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("Effects", "Effects"))
|
|
.Font(FAppStyle::GetFontStyle("SmallFont"))
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(2.0f)
|
|
[
|
|
SNew(SCheckBox)
|
|
.IsChecked(ECheckBoxState::Checked)
|
|
.Content()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("BloodEffect", "Blood"))
|
|
]
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(2.0f)
|
|
[
|
|
SNew(SCheckBox)
|
|
.IsChecked(ECheckBoxState::Checked)
|
|
.Content()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("PhysicsEffect", "Physics"))
|
|
]
|
|
]
|
|
]
|
|
]
|
|
|
|
// File operations
|
|
+ SHorizontalBox::Slot()
|
|
.FillWidth(1.0f)
|
|
.HAlign(HAlign_Right)
|
|
.Padding(8.0f, 0.0f)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(2.0f)
|
|
[
|
|
SNew(SButton)
|
|
.Text(LOCTEXT("ImportModel", "Import Model"))
|
|
.OnClicked_Lambda([this]() { OnImportModel(); return FReply::Handled(); })
|
|
.ButtonStyle(FAppStyle::Get(), "FlatButton")
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(2.0f)
|
|
[
|
|
SNew(SButton)
|
|
.Text(LOCTEXT("SavePreset", "Save Preset"))
|
|
.OnClicked_Lambda([this]() { OnSavePreset(); return FReply::Handled(); })
|
|
.ButtonStyle(FAppStyle::Get(), "FlatButton")
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(2.0f)
|
|
[
|
|
SNew(SButton)
|
|
.Text(LOCTEXT("LoadPreset", "Load Preset"))
|
|
.OnClicked_Lambda([this]() { OnLoadPreset(); return FReply::Handled(); })
|
|
.ButtonStyle(FAppStyle::Get(), "FlatButton")
|
|
]
|
|
]
|
|
];
|
|
}
|
|
|
|
void FFLESHEditor::CreateCommandList()
|
|
{
|
|
// Create command list
|
|
CommandList = MakeShareable(new FUICommandList);
|
|
|
|
// Bind commands
|
|
CommandList->MapAction(
|
|
FFLESHEditorCommands::Get().OpenDismembermentGraphEditor,
|
|
FExecuteAction::CreateSP(this, &FFLESHEditor::OnOpenDismembermentGraphEditor),
|
|
FCanExecuteAction());
|
|
|
|
CommandList->MapAction(
|
|
FFLESHEditorCommands::Get().OpenAnatomicalLayerEditor,
|
|
FExecuteAction::CreateSP(this, &FFLESHEditor::OnOpenAnatomicalLayerEditor),
|
|
FCanExecuteAction());
|
|
|
|
CommandList->MapAction(
|
|
FFLESHEditorCommands::Get().OpenBooleanCutTool,
|
|
FExecuteAction::CreateSP(this, &FFLESHEditor::OnOpenBooleanCutTool),
|
|
FCanExecuteAction());
|
|
|
|
CommandList->MapAction(
|
|
FFLESHEditorCommands::Get().OpenBloodSystemEditor,
|
|
FExecuteAction::CreateSP(this, &FFLESHEditor::OnOpenBloodSystemEditor),
|
|
FCanExecuteAction());
|
|
}
|
|
|
|
void FFLESHEditor::OnOpenDismembermentGraphEditor()
|
|
{
|
|
// Open dismemberment graph editor
|
|
FGlobalTabmanager::Get()->TryInvokeTab(GraphEditorTabId);
|
|
}
|
|
|
|
void FFLESHEditor::OnOpenAnatomicalLayerEditor()
|
|
{
|
|
// Open anatomical layer editor
|
|
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("AnatomicalLayerEditorNotImplemented", "Anatomical Layer Editor is not implemented yet"));
|
|
}
|
|
|
|
void FFLESHEditor::OnOpenBooleanCutTool()
|
|
{
|
|
// Open boolean cut tool
|
|
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("BooleanCutToolNotImplemented", "Boolean Cut Tool is not implemented yet"));
|
|
}
|
|
|
|
void FFLESHEditor::OnOpenBloodSystemEditor()
|
|
{
|
|
// Open blood system editor
|
|
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("BloodSystemEditorNotImplemented", "Blood System Editor is not implemented yet"));
|
|
}
|
|
|
|
void FFLESHEditor::OnImportCharacterModel()
|
|
{
|
|
// Import character model
|
|
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("ImportCharacterModelNotImplemented", "Import Character Model feature is not implemented yet"));
|
|
}
|
|
|
|
void FFLESHEditor::OnImportOrganModel()
|
|
{
|
|
// Import organ model
|
|
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("ImportOrganModelNotImplemented", "Import Organ Model feature is not implemented yet"));
|
|
}
|
|
|
|
void FFLESHEditor::OnImportSkeletonModel()
|
|
{
|
|
// Import skeleton model
|
|
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("ImportSkeletonModelNotImplemented", "Import Skeleton Model feature is not implemented yet"));
|
|
}
|
|
|
|
void FFLESHEditor::OnImportPhysicsAsset()
|
|
{
|
|
// Import physics asset
|
|
UE_LOG(LogTemp, Display, TEXT("Import Physics Asset"));
|
|
}
|
|
|
|
void FFLESHEditor::OnTestMatrix()
|
|
{
|
|
// Test matrix
|
|
UE_LOG(LogTemp, Display, TEXT("Test Matrix"));
|
|
}
|
|
|
|
void FFLESHEditor::OnImportModel()
|
|
{
|
|
// Import model
|
|
UE_LOG(LogTemp, Display, TEXT("Import Model"));
|
|
}
|
|
|
|
void FFLESHEditor::OnSavePreset()
|
|
{
|
|
// Save preset
|
|
UE_LOG(LogTemp, Display, TEXT("Save Preset"));
|
|
}
|
|
|
|
void FFLESHEditor::OnLoadPreset()
|
|
{
|
|
// Load preset
|
|
UE_LOG(LogTemp, Display, TEXT("Load Preset"));
|
|
}
|
|
|
|
FReply FFLESHEditor::OnResetCameraClicked()
|
|
{
|
|
// Reset camera
|
|
if (ViewportClient.IsValid())
|
|
{
|
|
ViewportClient->ResetCamera();
|
|
}
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply FFLESHEditor::OnToggleWireframeClicked()
|
|
{
|
|
// Toggle wireframe
|
|
if (ViewportClient.IsValid())
|
|
{
|
|
ViewportClient->ToggleWireframe();
|
|
}
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply FFLESHEditor::OnToggleBonesClicked()
|
|
{
|
|
// Toggle bones
|
|
if (ViewportClient.IsValid())
|
|
{
|
|
ViewportClient->ToggleBones();
|
|
}
|
|
return FReply::Handled();
|
|
}
|
|
|
|
TSharedRef<ITableRow> FFLESHEditor::OnGenerateBoneRow(TSharedPtr<FBoneTreeItem> Item, const TSharedRef<STableViewBase>& OwnerTable)
|
|
{
|
|
// Create a row for the bone item
|
|
return SNew(STableRow<TSharedPtr<FBoneTreeItem>>, OwnerTable)
|
|
.Style(FAppStyle::Get(), "TableView.Row")
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.FillWidth(0.75f)
|
|
.VAlign(VAlign_Center)
|
|
.Padding(4.0f, 0.0f)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(FText::FromString(Item->DisplayName))
|
|
.Font(FAppStyle::Get().GetFontStyle("PropertyWindow.NormalFont"))
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.FillWidth(0.25f)
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Right)
|
|
.Padding(4.0f, 0.0f)
|
|
[
|
|
SNew(SButton)
|
|
.ButtonStyle(FAppStyle::Get(), "FlatButton.Primary")
|
|
.ContentPadding(FMargin(2.0f, 1.0f))
|
|
.ToolTipText(LOCTEXT("CutBoneTooltip", "Apply cut to this bone"))
|
|
.OnClicked_Lambda([this, Item]()
|
|
{
|
|
// Apply cut to this bone
|
|
SelectedBoneItem = Item;
|
|
// TODO: Implement actual cut operation
|
|
return FReply::Handled();
|
|
})
|
|
.Content()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("CutBone", "Cut"))
|
|
]
|
|
]
|
|
];
|
|
}
|
|
|
|
void FFLESHEditor::OnGetBoneChildren(TSharedPtr<FBoneTreeItem> Item, TArray<TSharedPtr<FBoneTreeItem>>& OutChildren)
|
|
{
|
|
OutChildren = Item->Children;
|
|
}
|
|
|
|
void FFLESHEditor::OnBoneSelectionChanged(TSharedPtr<FBoneTreeItem> Item, ESelectInfo::Type SelectInfo)
|
|
{
|
|
SelectedBoneItem = Item;
|
|
|
|
// If a bone is selected, update the details panel
|
|
if (SelectedBoneItem.IsValid())
|
|
{
|
|
UE_LOG(LogTemp, Display, TEXT("Selected bone: %s"), *SelectedBoneItem->DisplayName);
|
|
|
|
// Add more bone selection handling logic here
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogTemp, Display, TEXT("No bone selected"));
|
|
}
|
|
}
|
|
|
|
void FFLESHEditor::BuildBoneHierarchy()
|
|
{
|
|
// Clear existing items
|
|
BoneItems.Empty();
|
|
|
|
// Add some default cutting planes for testing
|
|
CuttingPlaneNames.Empty();
|
|
CuttingPlaneNames.Add(MakeShareable(new FName("Horizontal Plane")));
|
|
CuttingPlaneNames.Add(MakeShareable(new FName("Vertical Plane")));
|
|
CuttingPlaneNames.Add(MakeShareable(new FName("Custom Angle Plane")));
|
|
|
|
// Get skeletal mesh from editing object
|
|
USkeletalMesh* SkeletalMesh = nullptr;
|
|
|
|
if (EditingObject)
|
|
{
|
|
// Try to get skeletal mesh from the editing object
|
|
if (EditingObject->IsA<USkeletalMesh>())
|
|
{
|
|
SkeletalMesh = Cast<USkeletalMesh>(EditingObject);
|
|
}
|
|
else
|
|
{
|
|
// Try to find skeletal mesh property in the editing object
|
|
for (TFieldIterator<FProperty> PropIt(EditingObject->GetClass()); PropIt; ++PropIt)
|
|
{
|
|
FProperty* Property = *PropIt;
|
|
if (Property->IsA<FObjectProperty>())
|
|
{
|
|
FObjectProperty* ObjectProperty = CastField<FObjectProperty>(Property);
|
|
if (ObjectProperty->PropertyClass->IsChildOf(USkeletalMesh::StaticClass()))
|
|
{
|
|
UObject* PropValue = ObjectProperty->GetObjectPropertyValue_InContainer(EditingObject);
|
|
if (PropValue && PropValue->IsA<USkeletalMesh>())
|
|
{
|
|
SkeletalMesh = Cast<USkeletalMesh>(PropValue);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!SkeletalMesh)
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("No valid skeletal mesh found, using default bone structure"));
|
|
TSharedPtr<FBoneTreeItem> RootItem = MakeShareable(new FBoneTreeItem(FName(TEXT("Root")), TEXT("Root")));
|
|
BoneItems.Add(RootItem);
|
|
}
|
|
else
|
|
{
|
|
// Get reference skeleton
|
|
const FReferenceSkeleton& RefSkeleton = SkeletalMesh->GetRefSkeleton();
|
|
|
|
// Create bone items
|
|
TMap<int32, TSharedPtr<FBoneTreeItem>> BoneItemMap;
|
|
|
|
// Create root bones (bones with no parent)
|
|
for (int32 BoneIndex = 0; BoneIndex < RefSkeleton.GetNum(); ++BoneIndex)
|
|
{
|
|
const FName BoneName = RefSkeleton.GetBoneName(BoneIndex);
|
|
const FString DisplayName = BoneName.ToString();
|
|
const int32 ParentIndex = RefSkeleton.GetParentIndex(BoneIndex);
|
|
|
|
TSharedPtr<FBoneTreeItem> BoneItem = MakeShareable(new FBoneTreeItem(BoneName, DisplayName));
|
|
BoneItemMap.Add(BoneIndex, BoneItem);
|
|
|
|
if (ParentIndex == INDEX_NONE)
|
|
{
|
|
// This is a root bone
|
|
BoneItems.Add(BoneItem);
|
|
}
|
|
else
|
|
{
|
|
// Add as child to parent
|
|
TSharedPtr<FBoneTreeItem>* ParentItem = BoneItemMap.Find(ParentIndex);
|
|
if (ParentItem)
|
|
{
|
|
(*ParentItem)->AddChild(BoneItem);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Refresh the tree view
|
|
if (BoneTreeView.IsValid())
|
|
{
|
|
BoneTreeView->RequestTreeRefresh();
|
|
}
|
|
}
|
|
|
|
void FFLESHEditor::BuildVisceraNodeTree()
|
|
{
|
|
// Clear existing items
|
|
NodeItems.Empty();
|
|
SelectedNodeItem = nullptr;
|
|
|
|
// Create root node for viscera
|
|
TSharedPtr<FVisceraNodeItem> RootNode = FVisceraNodeFactory::CreateSoftBodyNode(FName("SoftBody_Viscera"), TEXT("SoftBody_Viscera"));
|
|
NodeItems.Add(RootNode);
|
|
|
|
// Add default properties for advanced cutting
|
|
RootNode->BoolProperties.Add(TEXT("EnableMultiLayerCutting"), true);
|
|
RootNode->FloatProperties.Add(TEXT("CapMethod"), (float)ECapMeshMethod::TriangleFan);
|
|
|
|
// Add lungs with anchors and plane constraints
|
|
TSharedPtr<FVisceraNodeItem> LungsNode = FVisceraNodeFactory::CreateSoftBodyNode(FName("Lungs"), TEXT("Lungs"));
|
|
RootNode->AddChild(LungsNode);
|
|
|
|
// Add anchor points for lungs
|
|
LungsNode->AddChild(FVisceraNodeFactory::CreateAnchorNode(
|
|
FName("LeftLungAnchor"),
|
|
TEXT("LeftLungAnchor"),
|
|
FVector(-10.0f, 5.0f, 0.0f),
|
|
3.0f,
|
|
0.8f));
|
|
|
|
LungsNode->AddChild(FVisceraNodeFactory::CreateAnchorNode(
|
|
FName("RightLungAnchor"),
|
|
TEXT("RightLungAnchor"),
|
|
FVector(10.0f, 5.0f, 0.0f),
|
|
3.0f,
|
|
0.8f));
|
|
|
|
// Add plane constraints for lungs
|
|
LungsNode->AddChild(FVisceraNodeFactory::CreatePlaneNode(
|
|
FName("LungTopPlane"),
|
|
TEXT("LungTopPlane"),
|
|
FVector(0.0f, 10.0f, 0.0f),
|
|
FVector(0.0f, -1.0f, 0.0f),
|
|
0.7f));
|
|
|
|
// Add heart with tetrahedron structure
|
|
TSharedPtr<FVisceraNodeItem> HeartNode = FVisceraNodeFactory::CreateSoftBodyNode(FName("Heart"), TEXT("Heart"));
|
|
RootNode->AddChild(HeartNode);
|
|
|
|
// Add tetrahedron for heart
|
|
TArray<FVector> HeartTetraPoints;
|
|
HeartTetraPoints.Add(FVector(0.0f, 0.0f, 0.0f));
|
|
HeartTetraPoints.Add(FVector(5.0f, 0.0f, 0.0f));
|
|
HeartTetraPoints.Add(FVector(0.0f, 5.0f, 0.0f));
|
|
HeartTetraPoints.Add(FVector(0.0f, 0.0f, 5.0f));
|
|
|
|
HeartNode->AddChild(FVisceraNodeFactory::CreateTetraNode(
|
|
FName("HeartTetra"),
|
|
TEXT("HeartTetra"),
|
|
HeartTetraPoints,
|
|
0.9f));
|
|
|
|
// Add anchor for heart
|
|
HeartNode->AddChild(FVisceraNodeFactory::CreateAnchorNode(
|
|
FName("HeartAnchor"),
|
|
TEXT("HeartAnchor"),
|
|
FVector(0.0f, 0.0f, 5.0f),
|
|
2.0f,
|
|
1.0f));
|
|
|
|
// Add stomach with line chain structure
|
|
TSharedPtr<FVisceraNodeItem> StomachNode = FVisceraNodeFactory::CreateSoftBodyNode(FName("Stomach"), TEXT("Stomach"));
|
|
RootNode->AddChild(StomachNode);
|
|
|
|
// Add line chain for stomach
|
|
TArray<FVector> StomachLinePoints;
|
|
StomachLinePoints.Add(FVector(-5.0f, -5.0f, 0.0f));
|
|
StomachLinePoints.Add(FVector(0.0f, -8.0f, 0.0f));
|
|
StomachLinePoints.Add(FVector(5.0f, -5.0f, 0.0f));
|
|
|
|
StomachNode->AddChild(FVisceraNodeFactory::CreateLineChainNode(
|
|
FName("StomachLineChain"),
|
|
TEXT("StomachLineChain"),
|
|
StomachLinePoints,
|
|
0.6f,
|
|
1.5f));
|
|
|
|
// Add intestines with complex line chain
|
|
TSharedPtr<FVisceraNodeItem> IntestinesNode = FVisceraNodeFactory::CreateSoftBodyNode(FName("Intestines"), TEXT("Intestines"));
|
|
RootNode->AddChild(IntestinesNode);
|
|
|
|
// Add line chain for intestines
|
|
TArray<FVector> IntestinesLinePoints;
|
|
IntestinesLinePoints.Add(FVector(-8.0f, -10.0f, 0.0f));
|
|
IntestinesLinePoints.Add(FVector(-4.0f, -12.0f, 0.0f));
|
|
IntestinesLinePoints.Add(FVector(0.0f, -15.0f, 0.0f));
|
|
IntestinesLinePoints.Add(FVector(4.0f, -12.0f, 0.0f));
|
|
IntestinesLinePoints.Add(FVector(8.0f, -10.0f, 0.0f));
|
|
|
|
IntestinesNode->AddChild(FVisceraNodeFactory::CreateLineChainNode(
|
|
FName("IntestinesLineChain"),
|
|
TEXT("IntestinesLineChain"),
|
|
IntestinesLinePoints,
|
|
0.4f,
|
|
1.2f));
|
|
|
|
// Add group collisions node
|
|
RootNode->AddChild(FVisceraNodeFactory::CreateGroupCollisionsNode(
|
|
FName("GroupCollisions"),
|
|
TEXT("GroupCollisions")));
|
|
|
|
// Add time node with simulation parameters
|
|
RootNode->AddChild(FVisceraNodeFactory::CreateTimeNode(
|
|
FName("TimeNode"),
|
|
TEXT("TimeNode"),
|
|
0.01f,
|
|
2.0f));
|
|
|
|
// Refresh the tree view
|
|
if (NodeTreeView.IsValid())
|
|
{
|
|
NodeTreeView->RequestTreeRefresh();
|
|
}
|
|
}
|
|
|
|
TSharedRef<ITableRow> FFLESHEditor::OnGenerateNodeRow(TSharedPtr<FVisceraNodeItem> InItem, const TSharedRef<STableViewBase>& OwnerTable)
|
|
{
|
|
// Create the row
|
|
return SNew(STableRow<TSharedPtr<FVisceraNodeItem>>, OwnerTable)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
.Padding(0, 0, 4, 0)
|
|
[
|
|
SNew(SImage)
|
|
.Image_Lambda([InItem]() -> const FSlateBrush*
|
|
{
|
|
if (InItem->NodeType == TEXT("Root"))
|
|
{
|
|
return FAppStyle::GetBrush("ClassIcon.Blueprint");
|
|
}
|
|
else if (InItem->NodeType == TEXT("SoftBody"))
|
|
{
|
|
return FAppStyle::GetBrush("ClassIcon.SkeletalMesh");
|
|
}
|
|
else if (InItem->NodeType == TEXT("AnchorSphere"))
|
|
{
|
|
return FAppStyle::GetBrush("ClassIcon.StaticMeshComponent");
|
|
}
|
|
else if (InItem->NodeType == TEXT("Plane"))
|
|
{
|
|
return FAppStyle::GetBrush("ClassIcon.Plane");
|
|
}
|
|
return FAppStyle::GetBrush("ClassIcon.Object");
|
|
})
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.FillWidth(1.0f)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(FText::FromString(InItem->DisplayName))
|
|
.ColorAndOpacity_Lambda([InItem]() -> FSlateColor
|
|
{
|
|
if (InItem->NodeType == TEXT("Root"))
|
|
{
|
|
return FLinearColor(0.9f, 0.9f, 0.9f);
|
|
}
|
|
else if (InItem->NodeType == TEXT("SoftBody"))
|
|
{
|
|
return FLinearColor(0.2f, 0.8f, 0.2f);
|
|
}
|
|
else if (InItem->NodeType == TEXT("AnchorSphere"))
|
|
{
|
|
return FLinearColor(0.8f, 0.2f, 0.2f);
|
|
}
|
|
else if (InItem->NodeType == TEXT("Plane"))
|
|
{
|
|
return FLinearColor(0.2f, 0.2f, 0.8f);
|
|
}
|
|
return FLinearColor::White;
|
|
})
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
.Padding(4, 0, 0, 0)
|
|
[
|
|
SNew(SButton)
|
|
.ButtonStyle(FAppStyle::Get(), "SimpleButton")
|
|
.ContentPadding(FMargin(1, 0))
|
|
.Visibility_Lambda([InItem]() -> EVisibility
|
|
{
|
|
return InItem->NodeType != TEXT("Root") ? EVisibility::Visible : EVisibility::Collapsed;
|
|
})
|
|
.OnClicked_Lambda([this, InItem]() -> FReply
|
|
{
|
|
// Show context menu
|
|
const FVector2D CursorPos = FSlateApplication::Get().GetCursorPos();
|
|
FWidgetPath WidgetPath;
|
|
|
|
// Correctly call FindPathToWidget function
|
|
FSlateApplication::Get().FindPathToWidget(
|
|
FSlateApplication::Get().GetActiveTopLevelWindow().ToSharedRef(),
|
|
WidgetPath,
|
|
EVisibility::Visible
|
|
);
|
|
|
|
TSharedRef<SWidget> MenuContent = OnGetNodeContextMenu(InItem).ToSharedRef();
|
|
FSlateApplication::Get().PushMenu(
|
|
FSlateApplication::Get().GetActiveTopLevelWindow().ToSharedRef(),
|
|
WidgetPath,
|
|
MenuContent,
|
|
CursorPos,
|
|
FPopupTransitionEffect(FPopupTransitionEffect::ContextMenu)
|
|
);
|
|
return FReply::Handled();
|
|
})
|
|
[
|
|
SNew(SImage)
|
|
.Image(FAppStyle::GetBrush("Icons.Settings"))
|
|
]
|
|
]
|
|
];
|
|
}
|
|
|
|
void FFLESHEditor::OnGetNodeChildren(TSharedPtr<FVisceraNodeItem> InItem, TArray<TSharedPtr<FVisceraNodeItem>>& OutChildren)
|
|
{
|
|
OutChildren = InItem->Children;
|
|
}
|
|
|
|
void FFLESHEditor::OnNodeSelectionChanged(TSharedPtr<FVisceraNodeItem> InItem, ESelectInfo::Type SelectInfo)
|
|
{
|
|
SelectedNodeItem = InItem;
|
|
|
|
// Update details panel with the selected node's properties
|
|
if (DetailsWidget.IsValid() && InItem.IsValid())
|
|
{
|
|
// Create a UVisceraNodeObject and initialize it from the selected node
|
|
UVisceraNodeObject* NodeObject = NewObject<UVisceraNodeObject>(GetTransientPackage(), UVisceraNodeObject::StaticClass(), TEXT("VisceraNodeObject"));
|
|
NodeObject->InitFromNodeItem(InItem);
|
|
|
|
// Set the object in the details panel
|
|
DetailsWidget->SetObject(NodeObject);
|
|
|
|
// Register for property changes
|
|
DetailsWidget->OnFinishedChangingProperties().AddLambda([this, NodeObject](const FPropertyChangedEvent& PropertyChangedEvent) {
|
|
if (SelectedNodeItem.IsValid())
|
|
{
|
|
// Apply changes back to the node item
|
|
NodeObject->ApplyToNodeItem(SelectedNodeItem);
|
|
|
|
// If this is a SoftBody node, update the simulation settings
|
|
if (SelectedNodeItem->NodeType.Equals(TEXT("SoftBody")))
|
|
{
|
|
// In a real implementation, you would update the simulation settings
|
|
// For now, we'll just log a message
|
|
UE_LOG(LogTemp, Display, TEXT("Updated SoftBody simulation settings"));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
TSharedPtr<SWidget> FFLESHEditor::OnGetNodeContextMenu(TSharedPtr<FVisceraNodeItem> InItem)
|
|
{
|
|
FMenuBuilder MenuBuilder(true, CommandList);
|
|
|
|
if (InItem->NodeType == TEXT("SoftBody"))
|
|
{
|
|
// Add options for SoftBody nodes
|
|
MenuBuilder.BeginSection("SoftBodyActions", FText::FromString("Add Node"));
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
FText::FromString("Add SoftBodyAnchorSphereNode"),
|
|
FText::FromString("Add a new anchor sphere to this soft body"),
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction::CreateLambda([this, InItem]() {
|
|
AddNewVisceraNode(TEXT("AnchorSphere"), InItem);
|
|
}))
|
|
);
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
FText::FromString("Add SoftBodyLineChainNode"),
|
|
FText::FromString("Add a new line chain to this soft body"),
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction::CreateLambda([this, InItem]() {
|
|
AddNewVisceraNode(TEXT("LineChain"), InItem);
|
|
}))
|
|
);
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
FText::FromString("Add SoftBodyTetraNode"),
|
|
FText::FromString("Add a new tetra node to this soft body"),
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction::CreateLambda([this, InItem]() {
|
|
AddNewVisceraNode(TEXT("Tetra"), InItem);
|
|
}))
|
|
);
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
FText::FromString("Add SoftBodyTimeNode"),
|
|
FText::FromString("Add a new time node to this soft body"),
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction::CreateLambda([this, InItem]() {
|
|
AddNewVisceraNode(TEXT("Time"), InItem);
|
|
}))
|
|
);
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
FText::FromString("Add SoftBodyGroupCollisionsNode"),
|
|
FText::FromString("Add a new group collisions node to this soft body"),
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction::CreateLambda([this, InItem]() {
|
|
AddNewVisceraNode(TEXT("GroupCollisions"), InItem);
|
|
}))
|
|
);
|
|
|
|
MenuBuilder.EndSection();
|
|
}
|
|
|
|
// Common actions for all nodes
|
|
MenuBuilder.BeginSection("CommonActions", FText::FromString("Actions"));
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
FText::FromString("Delete"),
|
|
FText::FromString("Delete this node"),
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction::CreateLambda([this, InItem]() {
|
|
// Remove the node from its parent
|
|
TSharedPtr<FVisceraNodeItem> ParentItem = InItem->ParentItem.Pin();
|
|
if (ParentItem.IsValid())
|
|
{
|
|
ParentItem->Children.Remove(InItem);
|
|
NodeTreeView->RequestTreeRefresh();
|
|
}
|
|
}))
|
|
);
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
FText::FromString("Rename"),
|
|
FText::FromString("Rename this node"),
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction::CreateLambda([this, InItem]() {
|
|
// In a real implementation, you would show a rename dialog
|
|
// For now, we'll just log a message
|
|
UE_LOG(LogTemp, Display, TEXT("Rename node: %s"), *InItem->DisplayName);
|
|
}))
|
|
);
|
|
|
|
MenuBuilder.EndSection();
|
|
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
|
|
void FFLESHEditor::AddNewVisceraNode(const FString& NodeType, TSharedPtr<FVisceraNodeItem> ParentItem)
|
|
{
|
|
if (!ParentItem.IsValid())
|
|
{
|
|
// If no parent is specified, use the root node
|
|
if (NodeItems.Num() > 0)
|
|
{
|
|
ParentItem = NodeItems[0];
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Generate a unique name for the new node
|
|
FString BaseName = NodeType;
|
|
FString UniqueName = BaseName;
|
|
int32 Counter = 1;
|
|
|
|
bool bNameExists = true;
|
|
while (bNameExists)
|
|
{
|
|
bNameExists = false;
|
|
|
|
// Check if the name already exists in the parent's children
|
|
for (const TSharedPtr<FVisceraNodeItem>& Child : ParentItem->Children)
|
|
{
|
|
if (Child->DisplayName == UniqueName)
|
|
{
|
|
bNameExists = true;
|
|
UniqueName = FString::Printf(TEXT("%s_%d"), *BaseName, Counter++);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create the new node
|
|
TSharedPtr<FVisceraNodeItem> NewNode = MakeShareable(new FVisceraNodeItem(
|
|
FName(*UniqueName),
|
|
UniqueName,
|
|
NodeType
|
|
));
|
|
|
|
// Add the new node to the parent
|
|
ParentItem->AddChild(NewNode);
|
|
|
|
// Refresh the tree view
|
|
NodeTreeView->RequestTreeRefresh();
|
|
|
|
// Select the new node
|
|
NodeTreeView->SetItemSelection(NewNode, true);
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|