#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& 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 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("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& 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& 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(GetTransientPackage(), UDismembermentGraphAsset::StaticClass(), TEXT("TempDismembermentGraphAsset")); // Create new editor instance TSharedRef 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 FFLESHEditor::SpawnTab_Viewport(const FSpawnTabArgs& Args) { return SNew(SDockTab) .Label(LOCTEXT("ViewportTitle", "Viewport")) [ CreateViewportWidget() ]; } TSharedRef FFLESHEditor::SpawnTab_Details(const FSpawnTabArgs& Args) { return SNew(SDockTab) .Label(LOCTEXT("DetailsTitle", "Details")) [ CreateDetailsWidget() ]; } TSharedRef FFLESHEditor::SpawnTab_AssetBrowser(const FSpawnTabArgs& Args) { return SNew(SDockTab) .Label(LOCTEXT("AssetBrowserTitle", "Asset Browser")) [ CreateAssetBrowserWidget() ]; } TSharedRef 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 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 FFLESHEditor::SpawnTab_Toolbar(const FSpawnTabArgs& Args) { return SNew(SDockTab) .Label(LOCTEXT("ToolbarTitle", "Toolbar")) [ CreateToolbarWidget() ]; } TSharedRef FFLESHEditor::CreateViewportWidget() { // Create viewport client ViewportClient = MakeShared(this); // Create viewport widget ViewportWidget = SNew(SViewport) .IsEnabled(FSlateApplication::Get().GetNormalExecutionAttribute()) .EnableGammaCorrection(false); // Create scene viewport Viewport = MakeShared(ViewportClient.Get(), ViewportWidget); // Set viewport in client ViewportClient->Viewport = Viewport.Get(); // Associate scene viewport with widget ViewportWidget->SetViewportInterface(StaticCastSharedRef(Viewport.ToSharedRef())); // Create viewport toolbar TSharedRef 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 FFLESHEditor::CreateDetailsWidget() { // Create details panel FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked("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>) .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 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 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 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 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 FFLESHEditor::OnGenerateBoneRow(TSharedPtr Item, const TSharedRef& OwnerTable) { // Create a row for the bone item return SNew(STableRow>, 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 Item, TArray>& OutChildren) { OutChildren = Item->Children; } void FFLESHEditor::OnBoneSelectionChanged(TSharedPtr 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()) { SkeletalMesh = Cast(EditingObject); } else { // Try to find skeletal mesh property in the editing object for (TFieldIterator PropIt(EditingObject->GetClass()); PropIt; ++PropIt) { FProperty* Property = *PropIt; if (Property->IsA()) { FObjectProperty* ObjectProperty = CastField(Property); if (ObjectProperty->PropertyClass->IsChildOf(USkeletalMesh::StaticClass())) { UObject* PropValue = ObjectProperty->GetObjectPropertyValue_InContainer(EditingObject); if (PropValue && PropValue->IsA()) { SkeletalMesh = Cast(PropValue); break; } } } } } } if (!SkeletalMesh) { UE_LOG(LogTemp, Warning, TEXT("No valid skeletal mesh found, using default bone structure")); TSharedPtr 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> 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 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* 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 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 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 HeartNode = FVisceraNodeFactory::CreateSoftBodyNode(FName("Heart"), TEXT("Heart")); RootNode->AddChild(HeartNode); // Add tetrahedron for heart TArray 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 StomachNode = FVisceraNodeFactory::CreateSoftBodyNode(FName("Stomach"), TEXT("Stomach")); RootNode->AddChild(StomachNode); // Add line chain for stomach TArray 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 IntestinesNode = FVisceraNodeFactory::CreateSoftBodyNode(FName("Intestines"), TEXT("Intestines")); RootNode->AddChild(IntestinesNode); // Add line chain for intestines TArray 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 FFLESHEditor::OnGenerateNodeRow(TSharedPtr InItem, const TSharedRef& OwnerTable) { // Create the row return SNew(STableRow>, 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 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 InItem, TArray>& OutChildren) { OutChildren = InItem->Children; } void FFLESHEditor::OnNodeSelectionChanged(TSharedPtr 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(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 FFLESHEditor::OnGetNodeContextMenu(TSharedPtr 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 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 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& Child : ParentItem->Children) { if (Child->DisplayName == UniqueName) { bNameExists = true; UniqueName = FString::Printf(TEXT("%s_%d"), *BaseName, Counter++); break; } } } // Create the new node TSharedPtr 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