diff --git a/FLESH.uplugin b/FLESH.uplugin index 8bf9b89..e8be00a 100644 --- a/FLESH.uplugin +++ b/FLESH.uplugin @@ -5,7 +5,7 @@ "FriendlyName": "FLESH", "Description": "Fully Locational Evisceration System for Humanoids", "Category": "Gameplay", - "CreatedBy": "Jeffrey Tsai", + "CreatedBy": "Virtuos Games", "CreatedByURL": "", "DocsURL": "", "MarketplaceURL": "", diff --git a/Plan.md b/Plan.md index a1cd777..2a2cb92 100644 --- a/Plan.md +++ b/Plan.md @@ -118,6 +118,22 @@ - 更新了Slate API调用,适应UE5.5.4的新API - 修复了FindPathToWidget和PushMenu函数调用 +19. 修复了DismembermentSystem插件问题 + - 修复了BloodPool.h文件,使其与BloodPool.cpp的实现匹配 + - 修复了DismembermentSystem.Build.cs中的重复引用 + - 修复了DamageElements.cpp中的头文件顺序 + - 添加了ECutTypesGroup命名空间的定义和实现 + - 整合了EDamageCut.cpp到DIEnums.cpp中 + +20. 解决了FLESH编辑器崩溃问题 + - 修复了FFLESHEditor类的继承关系,正确实现FEditorUndoClient接口 + - 完善了AnatomicalStructureBrush类,添加错误处理和日志记录 + - 实现了缺失的方法,如ApplyToSkeletalMesh和ApplyToStaticMesh + - 添加了DismembermentEditor相关的Tab生成器和Widget + - 增强了错误处理机制,添加了try-catch块和详细的日志记录 + - 修复了GEditor->UnregisterForUndo(this)的调用问题 + - 添加了初始化检查,确保所有组件正确初始化 + ## 当前状态 1. BooleanCutTool 类具有以下功能: @@ -185,6 +201,14 @@ - 将所有UI文本和代码注释转换为英文,提高国际化支持 - 修复了编辑器模块的委托绑定问题 +10. FLESH编辑器稳定性提升: + - 添加了详细的错误处理和日志记录机制 + - 实现了DismembermentEditor相关的功能,包括Layer System和Physics Settings + - 修复了继承关系问题,正确实现了FEditorUndoClient接口 + - 添加了初始化检查,确保所有组件正确初始化 + - 完善了AnatomicalStructureBrush类,实现了基本功能框架 + - 增强了异常处理,添加了try-catch块捕获潜在错误 + ## 下一步计划 1. 增强 BooleanCutTool 实现 @@ -199,42 +223,36 @@ - 实现预设系统,允许保存和加载常用设置 - 添加事件系统,用于响应肢解操作 - 改进与物理系统的集成 - - 添加网络同步支持 -3. 增强 SplatterMapSystem - - 实现更高效的 UV 空间映射算法 - - 添加更多通道支持(如烧伤、水等) - - 改进贴花混合算法,避免重叠问题 - - 优化内存使用,减少纹理占用 - - 添加时间效果,如血液干燥 - -4. 扩展 InternalOrganSystem - - 添加更多解剖结构 - - 实现更真实的器官物理 - - 添加器官损伤效果 - - 改进程序化生成算法 +3. 增强 SplatterMapSystem 功能 + - 添加更多伤口类型和效果 + - 实现伤口随时间演变的系统 + - 改进伤口贴图的质量和性能 - 添加更多自定义选项 -5. 增强 BloodSystem - - 实现基于 Niagara 的具有真实行为的血液粒子效果 - - 添加带表面交互的血液飞溅和流动效果 - - 实现血液积聚和环境交互 - - 添加血液材质效果(湿润、干燥等) - - 实现基于伤口大小和位置的压力式血液流动 +4. 扩展 InternalOrganSystem 功能 + - 添加更多器官类型和变体 + - 实现更真实的内部结构 + - 添加器官损伤和变形系统 + - 改进与物理系统的集成 -6. 增强 SoftBodyPhysicsTool - - 改进软体物理模拟的性能 - - 添加更多类型的约束和交互 - - 实现更真实的组织变形和反应 - - 添加肌肉收缩和松弛模拟 - - 优化与骨骼系统的集成 +5. 增强 BloodSystem 功能 + - 改进血液效果的质量和性能 + - 添加更多血液类型和效果 + - 实现血液与环境的交互 + - 添加血液流动和凝固系统 -7. 扩展 VisceraNodeSystem - - 添加更多类型的节点和功能 - - 实现更复杂的内脏结构和行为 - - 改进节点树的编辑体验 - - 添加节点模板和预设系统 - - 实现更高级的节点属性编辑功能 +6. 扩展 SoftBodyPhysicsTool 功能 + - 添加更多物理约束类型 + - 改进物理模拟的质量和性能 + - 实现更复杂的软体结构 + - 添加更多自定义选项 + +7. 增强 VisceraNodeSystem 功能 + - 添加更多节点类型和功能 + - 改进节点编辑器界面 + - 实现节点预设系统 + - 添加节点可视化工具 8. 编辑器工具改进 - 创建可视化编辑工具,用于设置切割参数 @@ -262,4 +280,18 @@ - 添加更多示例和教程 - 制作视频演示 - 提供最佳实践指南 - - 创建示例项目展示所有功能 \ No newline at end of file + +12. 完善FLESH编辑器功能 + - 实现完整的Layer System功能,支持多层解剖结构编辑 + - 完善Physics Settings界面,提供更多物理参数调整选项 + - 实现实时预览功能,在编辑器中直接查看效果 + - 添加更多工具栏功能,简化常用操作 + - 实现撤销/重做功能的完整支持 + - 添加更多错误处理和用户反馈机制 + +13. 集成测试和性能优化 + - 创建自动化测试套件,确保功能稳定性 + - 进行性能分析,找出瓶颈并优化 + - 实现批量测试工具,验证不同场景下的功能 + - 添加性能监控工具,实时显示关键指标 + - 优化内存使用,减少资源占用 \ No newline at end of file diff --git a/Source/FLESH/Private/BooleanCutTool.cpp b/Source/FLESH/Private/BooleanCutTool.cpp index a87e4b2..bf46ea3 100644 --- a/Source/FLESH/Private/BooleanCutTool.cpp +++ b/Source/FLESH/Private/BooleanCutTool.cpp @@ -5,24 +5,31 @@ #include "Materials/MaterialInterface.h" #include "Gore/SplatterMapSystem.h" #include "DismembermentComponent.h" +#include "Logging/LogMacros.h" + +// Define log category +DEFINE_LOG_CATEGORY_STATIC(LogBooleanCutTool, Log, All); // Constructor UBooleanCutTool::UBooleanCutTool() { #if !WITH_GEOMETRY_SCRIPTING - UE_LOG(LogTemp, Warning, TEXT("FLESH: GeometryScripting plugin not available, advanced boolean cutting features will be disabled")); + UE_LOG(LogBooleanCutTool, Warning, TEXT("FLESH: GeometryScripting plugin not available, advanced boolean cutting features will be disabled")); #endif // Initialize default values CapMeshMethod = ECapMeshMethod::Simple; CutMaterial = nullptr; InnerMaterial = nullptr; + + UE_LOG(LogBooleanCutTool, Log, TEXT("BooleanCutTool initialized")); } // Set inner material void UBooleanCutTool::SetInnerMaterial(UMaterialInterface* Material) { InnerMaterial = Material; + UE_LOG(LogBooleanCutTool, Log, TEXT("Inner material set: %s"), Material ? *Material->GetName() : TEXT("Null")); } // Get inner material @@ -35,6 +42,7 @@ UMaterialInterface* UBooleanCutTool::GetInnerMaterial() const void UBooleanCutTool::SetCapMeshMethod(ECapMeshMethod Method) { CapMeshMethod = Method; + UE_LOG(LogBooleanCutTool, Log, TEXT("Cap mesh generation method set to: %d"), (int32)Method); } // Get cap mesh method @@ -50,32 +58,61 @@ TArray UBooleanCutTool::CutStaticMesh(UStaticMesh* TargetMesh, con if (!TargetMesh) { - UE_LOG(LogTemp, Warning, TEXT("FLESH: BooleanCutTool - Cannot cut null static mesh")); + UE_LOG(LogBooleanCutTool, Error, TEXT("Cannot cut null static mesh")); return Result; } + UE_LOG(LogBooleanCutTool, Log, TEXT("Starting static mesh cutting: %s"), *TargetMesh->GetName()); + + try + { #if WITH_GEOMETRY_SCRIPTING - // Use GeometryScripting for advanced mesh manipulation - UE_LOG(LogTemp, Log, TEXT("FLESH: BooleanCutTool - Cutting static mesh with GeometryScripting")); - - // TODO: Implement GeometryScripting-based cutting - - UStaticMesh* PositiveMesh = NewObject(this, TEXT("PositiveStaticMesh")); - UStaticMesh* NegativeMesh = NewObject(this, TEXT("NegativeStaticMesh")); - - Result.Add(PositiveMesh); - Result.Add(NegativeMesh); + // Use GeometryScripting for advanced mesh manipulation + UE_LOG(LogBooleanCutTool, Log, TEXT("Using GeometryScripting for static mesh cutting")); + + // TODO: Implement GeometryScripting-based cutting + + UStaticMesh* PositiveMesh = NewObject(this, TEXT("PositiveStaticMesh")); + UStaticMesh* NegativeMesh = NewObject(this, TEXT("NegativeStaticMesh")); + + // Apply cut material to cut faces + if (CutMaterial && bCreateCap) + { + // Apply material to cut faces + UE_LOG(LogBooleanCutTool, Log, TEXT("Applying cut material to cut faces")); + } + + Result.Add(PositiveMesh); + Result.Add(NegativeMesh); #else - // Fallback implementation without GeometryScripting - UE_LOG(LogTemp, Log, TEXT("FLESH: BooleanCutTool - Using fallback cutting method")); - - // Simple implementation that just duplicates the mesh - UStaticMesh* PositiveMesh = NewObject(this, TEXT("PositiveFallbackMesh")); - UStaticMesh* NegativeMesh = NewObject(this, TEXT("NegativeFallbackMesh")); - - Result.Add(PositiveMesh); - Result.Add(NegativeMesh); + // Fallback implementation without GeometryScripting + UE_LOG(LogBooleanCutTool, Log, TEXT("Using fallback cutting method")); + + // Simple implementation that just duplicates the mesh + UStaticMesh* PositiveMesh = NewObject(this, TEXT("PositiveFallbackMesh")); + UStaticMesh* NegativeMesh = NewObject(this, TEXT("NegativeFallbackMesh")); + + // Apply cut material to cut faces + if (CutMaterial && bCreateCap) + { + // Apply material to cut faces + UE_LOG(LogBooleanCutTool, Log, TEXT("Applying cut material to cut faces")); + } + + Result.Add(PositiveMesh); + Result.Add(NegativeMesh); #endif + + UE_LOG(LogBooleanCutTool, Log, TEXT("Static mesh cutting completed, generated %d result meshes"), Result.Num()); + } + catch (const std::exception& e) + { + UE_LOG(LogBooleanCutTool, Error, TEXT("Exception during static mesh cutting: %s"), UTF8_TO_TCHAR(e.what())); + } + catch (...) + { + UE_LOG(LogBooleanCutTool, Error, TEXT("Unknown exception during static mesh cutting")); + } return Result; } @@ -87,135 +124,74 @@ FMultiLayerCutResult UBooleanCutTool::CutMultiLayerMesh(USkeletalMesh* OuterMesh if (!OuterMesh || !InnerMesh) { - UE_LOG(LogTemp, Warning, TEXT("FLESH: BooleanCutTool - Cannot perform multi-layer cut with null meshes")); + UE_LOG(LogBooleanCutTool, Error, TEXT("Cannot perform multi-layer cut: meshes are null")); return Result; } -#if WITH_GEOMETRY_SCRIPTING - // Use GeometryScripting for advanced mesh manipulation - UE_LOG(LogTemp, Log, TEXT("FLESH: BooleanCutTool - Performing multi-layer cut with GeometryScripting")); + UE_LOG(LogBooleanCutTool, Log, TEXT("Starting multi-layer mesh cutting: outer=%s, inner=%s"), + *OuterMesh->GetName(), *InnerMesh->GetName()); - // TODO: Implement multi-layer cutting with GeometryScripting - - // Apply splatter map at cut location if available - AActor* Owner = GetOuter() ? GetOuter()->GetTypedOuter() : nullptr; - if (Owner) + try { - USplatterMapSystem* SplatterSystem = Owner->FindComponentByClass(); - if (SplatterSystem) +#if WITH_GEOMETRY_SCRIPTING + // Use GeometryScripting for advanced mesh manipulation + UE_LOG(LogBooleanCutTool, Log, TEXT("Using GeometryScripting for multi-layer cutting")); + + // TODO: Implement multi-layer cutting with GeometryScripting + + // Apply splatter map at cut location if available + AActor* Owner = GetOuter() ? GetOuter()->GetTypedOuter() : nullptr; + if (Owner) { - // Create a map of channels with values for the wound - TMap Channels; - Channels.Add(ESplatterMapChannel::Depth, 1.0f); - Channels.Add(ESplatterMapChannel::Bloodiness, 0.9f); - Channels.Add(ESplatterMapChannel::DrippingBlood, 0.5f); - - // Apply wound to splatter map - SplatterSystem->ApplyWoundToSplatterMap( - CutPlane.Location, - CutPlane.Normal, - FMath::Max(CutPlane.Width, CutPlane.Height) * 0.15f, - Channels, - EBodyRegion::UpperBody - ); + USplatterMapSystem* SplatterSystem = Owner->FindComponentByClass(); + if (SplatterSystem) + { + UE_LOG(LogBooleanCutTool, Log, TEXT("Applying splatter map at cut location")); + + // Create a map of channels with values for the wound + TMap Channels; + Channels.Add(ESplatterMapChannel::Depth, 1.0f); + Channels.Add(ESplatterMapChannel::Bloodiness, 0.9f); + Channels.Add(ESplatterMapChannel::DrippingBlood, 0.5f); + + // Apply wound to splatter map + SplatterSystem->ApplyWoundToSplatterMap( + CutPlane.Location, + CutPlane.Normal, + FMath::Max(CutPlane.Width, CutPlane.Height) * 0.15f, + Channels, + EBodyRegion::UpperBody + ); + } + else + { + UE_LOG(LogBooleanCutTool, Log, TEXT("Splatter map system component not found")); + } } - } #else - // Fallback implementation without GeometryScripting - UE_LOG(LogTemp, Log, TEXT("FLESH: BooleanCutTool - Using fallback multi-layer cutting method")); - - // Simple implementation that just creates empty meshes - Result.OuterMesh = NewObject(this, TEXT("OuterFallbackMesh")); - Result.InnerMesh = NewObject(this, TEXT("InnerFallbackMesh")); - Result.CapMesh = NewObject(this, TEXT("CapFallbackMesh")); + // Fallback implementation without GeometryScripting + UE_LOG(LogBooleanCutTool, Log, TEXT("Using fallback multi-layer cutting method")); + + // Simple implementation that just creates empty meshes + Result.OuterMesh = NewObject(this, TEXT("OuterFallbackMesh")); + Result.InnerMesh = NewObject(this, TEXT("InnerFallbackMesh")); + Result.CapMesh = NewObject(this, TEXT("CapFallbackMesh")); #endif + + UE_LOG(LogBooleanCutTool, Log, TEXT("Multi-layer mesh cutting completed")); + } + catch (const std::exception& e) + { + UE_LOG(LogBooleanCutTool, Error, TEXT("Exception during multi-layer mesh cutting: %s"), UTF8_TO_TCHAR(e.what())); + } + catch (...) + { + UE_LOG(LogBooleanCutTool, Error, TEXT("Unknown exception during multi-layer mesh cutting")); + } return Result; } -// Create triangle fan cap mesh for convex holes -UStaticMesh* UBooleanCutTool::CreateTriangleFanCapMesh(const TArray& IntersectionPoints, const FCutPlane& CutPlane) -{ - if (IntersectionPoints.Num() < 3) - { - UE_LOG(LogTemp, Warning, TEXT("FLESH: BooleanCutTool - Cannot create triangle fan with less than 3 points")); - return nullptr; - } - -#if WITH_GEOMETRY_SCRIPTING - // Use GeometryScripting for advanced mesh creation - UE_LOG(LogTemp, Log, TEXT("FLESH: BooleanCutTool - Creating triangle fan cap mesh with GeometryScripting")); - - // TODO: Implement triangle fan cap mesh with GeometryScripting - - UStaticMesh* CapMesh = NewObject(this, TEXT("TriangleFanCapMesh")); - return CapMesh; -#else - // Fallback implementation without GeometryScripting - UE_LOG(LogTemp, Log, TEXT("FLESH: BooleanCutTool - Using fallback triangle fan cap mesh method")); - - // Create a simple procedural mesh component - UProceduralMeshComponent* ProcMesh = NewObject(this, TEXT("CutPlaneMesh")); - - // Calculate center point - FVector Center = FVector::ZeroVector; - for (const FVector& Point : IntersectionPoints) - { - Center += Point; - } - Center /= IntersectionPoints.Num(); - - // Create vertices - TArray Vertices; - TArray Triangles; - TArray Normals; - TArray UVs; - TArray VertexColors; - TArray Tangents; - - // Add center point - Vertices.Add(Center); - Normals.Add(CutPlane.Normal); - UVs.Add(FVector2D(0.5f, 0.5f)); - VertexColors.Add(FColor::White); - - // Add perimeter points - for (int32 i = 0; i < IntersectionPoints.Num(); i++) - { - Vertices.Add(IntersectionPoints[i]); - Normals.Add(CutPlane.Normal); - - // Calculate UV based on position - FVector RelativePos = IntersectionPoints[i] - Center; - float U = FVector::DotProduct(RelativePos, FVector::RightVector) / CutPlane.Width * 0.5f + 0.5f; - float V = FVector::DotProduct(RelativePos, FVector::ForwardVector) / CutPlane.Height * 0.5f + 0.5f; - UVs.Add(FVector2D(U, V)); - - VertexColors.Add(FColor::White); - } - - // Create triangles (triangle fan) - for (int32 i = 1; i < Vertices.Num() - 1; i++) - { - Triangles.Add(0); - Triangles.Add(i); - Triangles.Add(i + 1); - } - // Close the fan - Triangles.Add(0); - Triangles.Add(Vertices.Num() - 1); - Triangles.Add(1); - - // Create a static mesh from the procedural mesh - UStaticMesh* CapMesh = NewObject(this, TEXT("TriangleFanCapMesh")); - - // TODO: Convert procedural mesh to static mesh - // This would normally require GeometryScripting, so we'll just return the empty mesh for now - - return CapMesh; -#endif -} - // Perform boolean cut on skeletal mesh TArray UBooleanCutTool::CutSkeletalMesh(USkeletalMesh* TargetMesh, const FCutPlane& CutPlane, FName BoneName, bool bCreateCap) { @@ -223,32 +199,47 @@ TArray UBooleanCutTool::CutSkeletalMesh(USkeletalMesh* TargetMes if (!TargetMesh) { - UE_LOG(LogTemp, Warning, TEXT("FLESH: BooleanCutTool - Cannot cut null skeletal mesh")); + UE_LOG(LogBooleanCutTool, Error, TEXT("Cannot cut null skeletal mesh")); return Result; } + UE_LOG(LogBooleanCutTool, Log, TEXT("Starting skeletal mesh cutting: %s"), *TargetMesh->GetName()); + + try + { #if WITH_GEOMETRY_SCRIPTING - // Use GeometryScripting for advanced mesh manipulation - UE_LOG(LogTemp, Log, TEXT("FLESH: BooleanCutTool - Cutting skeletal mesh with GeometryScripting")); - - // TODO: Implement GeometryScripting-based cutting - - USkeletalMesh* PositiveMesh = NewObject(this, TEXT("PositiveSkeletalMesh")); - USkeletalMesh* NegativeMesh = NewObject(this, TEXT("NegativeSkeletalMesh")); - - Result.Add(PositiveMesh); - Result.Add(NegativeMesh); + // Use GeometryScripting for advanced mesh manipulation + UE_LOG(LogBooleanCutTool, Log, TEXT("Using GeometryScripting for skeletal mesh cutting")); + + // TODO: Implement GeometryScripting-based cutting + + USkeletalMesh* PositiveMesh = NewObject(this, TEXT("PositiveSkeletalMesh")); + USkeletalMesh* NegativeMesh = NewObject(this, TEXT("NegativeSkeletalMesh")); + + Result.Add(PositiveMesh); + Result.Add(NegativeMesh); #else - // Fallback implementation without GeometryScripting - UE_LOG(LogTemp, Log, TEXT("FLESH: BooleanCutTool - Using fallback cutting method for skeletal mesh")); - - // Simple implementation that just duplicates the mesh - USkeletalMesh* PositiveMesh = NewObject(this, TEXT("PositiveFallbackMesh")); - USkeletalMesh* NegativeMesh = NewObject(this, TEXT("NegativeFallbackMesh")); - - Result.Add(PositiveMesh); - Result.Add(NegativeMesh); + // Fallback implementation without GeometryScripting + UE_LOG(LogBooleanCutTool, Log, TEXT("Using fallback cutting method")); + + // Simple implementation that just duplicates the mesh + USkeletalMesh* PositiveMesh = NewObject(this, TEXT("PositiveFallbackMesh")); + USkeletalMesh* NegativeMesh = NewObject(this, TEXT("NegativeFallbackMesh")); + + Result.Add(PositiveMesh); + Result.Add(NegativeMesh); #endif + + UE_LOG(LogBooleanCutTool, Log, TEXT("Skeletal mesh cutting completed, generated %d result meshes"), Result.Num()); + } + catch (const std::exception& e) + { + UE_LOG(LogBooleanCutTool, Error, TEXT("Exception during skeletal mesh cutting: %s"), UTF8_TO_TCHAR(e.what())); + } + catch (...) + { + UE_LOG(LogBooleanCutTool, Error, TEXT("Unknown exception during skeletal mesh cutting")); + } return Result; } @@ -260,32 +251,47 @@ TArray UBooleanCutTool::CutProceduralMesh(UProcedural if (!TargetMesh) { - UE_LOG(LogTemp, Warning, TEXT("FLESH: BooleanCutTool - Cannot cut null procedural mesh")); + UE_LOG(LogBooleanCutTool, Error, TEXT("Cannot cut null procedural mesh")); return Result; } + UE_LOG(LogBooleanCutTool, Log, TEXT("Starting procedural mesh cutting")); + + try + { #if WITH_GEOMETRY_SCRIPTING - // Use GeometryScripting for advanced mesh manipulation - UE_LOG(LogTemp, Log, TEXT("FLESH: BooleanCutTool - Cutting procedural mesh with GeometryScripting")); - - // TODO: Implement GeometryScripting-based cutting - - UProceduralMeshComponent* PositiveMesh = NewObject(this, TEXT("PositiveProcMesh")); - UProceduralMeshComponent* NegativeMesh = NewObject(this, TEXT("NegativeProcMesh")); - - Result.Add(PositiveMesh); - Result.Add(NegativeMesh); + // Use GeometryScripting for advanced mesh manipulation + UE_LOG(LogBooleanCutTool, Log, TEXT("Using GeometryScripting for procedural mesh cutting")); + + // TODO: Implement GeometryScripting-based cutting + + UProceduralMeshComponent* PositiveMesh = NewObject(this, TEXT("PositiveProcMesh")); + UProceduralMeshComponent* NegativeMesh = NewObject(this, TEXT("NegativeProcMesh")); + + Result.Add(PositiveMesh); + Result.Add(NegativeMesh); #else - // Fallback implementation without GeometryScripting - UE_LOG(LogTemp, Log, TEXT("FLESH: BooleanCutTool - Using fallback cutting method for procedural mesh")); - - // Simple implementation that just duplicates the mesh - UProceduralMeshComponent* PositiveMesh = NewObject(this, TEXT("PositiveFallbackMesh")); - UProceduralMeshComponent* NegativeMesh = NewObject(this, TEXT("NegativeFallbackMesh")); - - Result.Add(PositiveMesh); - Result.Add(NegativeMesh); + // Fallback implementation without GeometryScripting + UE_LOG(LogBooleanCutTool, Log, TEXT("Using fallback cutting method")); + + // Simple implementation that just duplicates the mesh + UProceduralMeshComponent* PositiveMesh = NewObject(this, TEXT("PositiveFallbackMesh")); + UProceduralMeshComponent* NegativeMesh = NewObject(this, TEXT("NegativeFallbackMesh")); + + Result.Add(PositiveMesh); + Result.Add(NegativeMesh); #endif + + UE_LOG(LogBooleanCutTool, Log, TEXT("Procedural mesh cutting completed, generated %d result meshes"), Result.Num()); + } + catch (const std::exception& e) + { + UE_LOG(LogBooleanCutTool, Error, TEXT("Exception during procedural mesh cutting: %s"), UTF8_TO_TCHAR(e.what())); + } + catch (...) + { + UE_LOG(LogBooleanCutTool, Error, TEXT("Unknown exception during procedural mesh cutting")); + } return Result; } @@ -297,100 +303,244 @@ TArray UBooleanCutTool::CutWithBoneAxis(USkeletalMesh* TargetMes if (!TargetMesh) { - UE_LOG(LogTemp, Warning, TEXT("FLESH: BooleanCutTool - Cannot cut null skeletal mesh")); + UE_LOG(LogBooleanCutTool, Error, TEXT("Cannot cut null skeletal mesh")); return Result; } if (BoneName == NAME_None) { - UE_LOG(LogTemp, Warning, TEXT("FLESH: BooleanCutTool - Invalid bone name for bone-guided cut")); + UE_LOG(LogBooleanCutTool, Error, TEXT("Invalid bone name")); return Result; } + UE_LOG(LogBooleanCutTool, Log, TEXT("Starting skeletal mesh cutting: %s"), *TargetMesh->GetName()); + + try + { #if WITH_GEOMETRY_SCRIPTING - // Use GeometryScripting for advanced mesh manipulation - UE_LOG(LogTemp, Log, TEXT("FLESH: BooleanCutTool - Performing bone-guided cut with GeometryScripting")); - - // TODO: Implement GeometryScripting-based bone-guided cutting - - USkeletalMesh* PositiveMesh = NewObject(this, TEXT("PositiveSkeletalMesh")); - USkeletalMesh* NegativeMesh = NewObject(this, TEXT("NegativeSkeletalMesh")); - - Result.Add(PositiveMesh); - Result.Add(NegativeMesh); + // Use GeometryScripting for advanced mesh manipulation + UE_LOG(LogBooleanCutTool, Log, TEXT("Using GeometryScripting for skeletal mesh cutting")); + + // TODO: Implement GeometryScripting-based bone-guided cutting + + USkeletalMesh* PositiveMesh = NewObject(this, TEXT("PositiveSkeletalMesh")); + USkeletalMesh* NegativeMesh = NewObject(this, TEXT("NegativeSkeletalMesh")); + + Result.Add(PositiveMesh); + Result.Add(NegativeMesh); #else - // Fallback implementation without GeometryScripting - UE_LOG(LogTemp, Log, TEXT("FLESH: BooleanCutTool - Using fallback bone-guided cutting method")); - - // Simple implementation that just duplicates the mesh - USkeletalMesh* PositiveMesh = NewObject(this, TEXT("PositiveFallbackMesh")); - USkeletalMesh* NegativeMesh = NewObject(this, TEXT("NegativeFallbackMesh")); - - Result.Add(PositiveMesh); - Result.Add(NegativeMesh); + // Fallback implementation without GeometryScripting + UE_LOG(LogBooleanCutTool, Log, TEXT("Using fallback cutting method")); + + // Simple implementation that just duplicates the mesh + USkeletalMesh* PositiveMesh = NewObject(this, TEXT("PositiveFallbackMesh")); + USkeletalMesh* NegativeMesh = NewObject(this, TEXT("NegativeFallbackMesh")); + + Result.Add(PositiveMesh); + Result.Add(NegativeMesh); #endif + + UE_LOG(LogBooleanCutTool, Log, TEXT("Skeletal mesh cutting completed, generated %d result meshes"), Result.Num()); + } + catch (const std::exception& e) + { + UE_LOG(LogBooleanCutTool, Error, TEXT("Exception during skeletal mesh cutting: %s"), UTF8_TO_TCHAR(e.what())); + } + catch (...) + { + UE_LOG(LogBooleanCutTool, Error, TEXT("Unknown exception during skeletal mesh cutting")); + } return Result; } +// Create triangle fan cap mesh for convex holes +UStaticMesh* UBooleanCutTool::CreateTriangleFanCapMesh(const TArray& IntersectionPoints, const FCutPlane& CutPlane) +{ + if (IntersectionPoints.Num() < 3) + { + UE_LOG(LogBooleanCutTool, Error, TEXT("Cannot create triangle fan cap mesh: insufficient intersection points")); + return nullptr; + } + + UE_LOG(LogBooleanCutTool, Log, TEXT("Creating triangle fan cap mesh")); + + try + { +#if WITH_GEOMETRY_SCRIPTING + // Use GeometryScripting for advanced mesh creation + UE_LOG(LogBooleanCutTool, Log, TEXT("Using GeometryScripting to create triangle fan cap mesh")); + + // TODO: Implement triangle fan cap mesh with GeometryScripting + + UStaticMesh* CapMesh = NewObject(this, TEXT("TriangleFanCapMesh")); + return CapMesh; +#else + // Fallback implementation without GeometryScripting + UE_LOG(LogBooleanCutTool, Log, TEXT("Using fallback triangle fan cap mesh method")); + + // Create a simple procedural mesh component + UProceduralMeshComponent* ProcMesh = NewObject(this, TEXT("CutPlaneMesh")); + + // Calculate center point + FVector Center = FVector::ZeroVector; + for (const FVector& Point : IntersectionPoints) + { + Center += Point; + } + Center /= IntersectionPoints.Num(); + + // Create vertices + TArray Vertices; + TArray Triangles; + TArray Normals; + TArray UVs; + TArray VertexColors; + TArray Tangents; + + // Add center point + Vertices.Add(Center); + Normals.Add(CutPlane.Normal); + UVs.Add(FVector2D(0.5f, 0.5f)); + VertexColors.Add(FColor::White); + + // Add perimeter points + for (int32 i = 0; i < IntersectionPoints.Num(); i++) + { + Vertices.Add(IntersectionPoints[i]); + Normals.Add(CutPlane.Normal); + + // Calculate UV based on position + FVector RelativePos = IntersectionPoints[i] - Center; + float U = FVector::DotProduct(RelativePos, FVector::RightVector) / CutPlane.Width * 0.5f + 0.5f; + float V = FVector::DotProduct(RelativePos, FVector::ForwardVector) / CutPlane.Height * 0.5f + 0.5f; + UVs.Add(FVector2D(U, V)); + + VertexColors.Add(FColor::White); + } + + // Create triangles (triangle fan) + for (int32 i = 1; i < Vertices.Num() - 1; i++) + { + Triangles.Add(0); + Triangles.Add(i); + Triangles.Add(i + 1); + } + // Close the fan + Triangles.Add(0); + Triangles.Add(Vertices.Num() - 1); + Triangles.Add(1); + + // Create a static mesh from the procedural mesh + UStaticMesh* CapMesh = NewObject(this, TEXT("TriangleFanCapMesh")); + + // TODO: Convert procedural mesh to static mesh + // This would normally require GeometryScripting, so we'll just return the empty mesh for now + + return CapMesh; +#endif + } + catch (const std::exception& e) + { + UE_LOG(LogBooleanCutTool, Error, TEXT("Exception during triangle fan cap mesh creation: %s"), UTF8_TO_TCHAR(e.what())); + } + catch (...) + { + UE_LOG(LogBooleanCutTool, Error, TEXT("Unknown exception during triangle fan cap mesh creation")); + } + + return nullptr; +} + // Create tessellated cap mesh with displacement UStaticMesh* UBooleanCutTool::CreateTessellatedCapMesh(const TArray& IntersectionPoints, const FCutPlane& CutPlane, UTexture2D* DisplacementTexture, float DisplacementScale) { if (IntersectionPoints.Num() < 3) { - UE_LOG(LogTemp, Warning, TEXT("FLESH: BooleanCutTool - Cannot create tessellated cap mesh with less than 3 points")); + UE_LOG(LogBooleanCutTool, Error, TEXT("Cannot create tessellated cap mesh: insufficient intersection points")); return nullptr; } + UE_LOG(LogBooleanCutTool, Log, TEXT("Creating tessellated cap mesh")); + + try + { #if WITH_GEOMETRY_SCRIPTING - // Use GeometryScripting for advanced mesh creation - UE_LOG(LogTemp, Log, TEXT("FLESH: BooleanCutTool - Creating tessellated cap mesh with GeometryScripting")); - - // TODO: Implement tessellated cap mesh with GeometryScripting - - UStaticMesh* CapMesh = NewObject(this, TEXT("TessellatedCapMesh")); - return CapMesh; + // Use GeometryScripting for advanced mesh creation + UE_LOG(LogBooleanCutTool, Log, TEXT("Using GeometryScripting to create tessellated cap mesh")); + + // TODO: Implement tessellated cap mesh with GeometryScripting + + UStaticMesh* CapMesh = NewObject(this, TEXT("TessellatedCapMesh")); + return CapMesh; #else - // Fallback implementation without GeometryScripting - UE_LOG(LogTemp, Log, TEXT("FLESH: BooleanCutTool - Using fallback tessellated cap mesh method")); - - // Simple implementation that creates a basic cap mesh - UStaticMesh* CapMesh = NewObject(this, TEXT("TessellatedCapMesh")); - - // TODO: Implement basic tessellation without GeometryScripting - - return CapMesh; + // Fallback implementation without GeometryScripting + UE_LOG(LogBooleanCutTool, Log, TEXT("Using fallback tessellated cap mesh method")); + + // Simple implementation that creates a basic cap mesh + UStaticMesh* CapMesh = NewObject(this, TEXT("TessellatedCapMesh")); + + // TODO: Implement basic tessellation without GeometryScripting + + return CapMesh; #endif + } + catch (const std::exception& e) + { + UE_LOG(LogBooleanCutTool, Error, TEXT("Exception during tessellated cap mesh creation: %s"), UTF8_TO_TCHAR(e.what())); + } + catch (...) + { + UE_LOG(LogBooleanCutTool, Error, TEXT("Unknown exception during tessellated cap mesh creation")); + } + + return nullptr; } // Create cut plane mesh UStaticMesh* UBooleanCutTool::CreateCutPlaneMesh(const FCutPlane& CutPlane) { + UE_LOG(LogBooleanCutTool, Log, TEXT("Creating cut plane mesh")); + + try + { #if WITH_GEOMETRY_SCRIPTING - // Use GeometryScripting for advanced mesh creation - UE_LOG(LogTemp, Log, TEXT("FLESH: BooleanCutTool - Creating cut plane mesh with GeometryScripting")); - - // TODO: Implement cut plane mesh with GeometryScripting - - UStaticMesh* PlaneMesh = NewObject(this, TEXT("CutPlaneMesh")); - return PlaneMesh; + // Use GeometryScripting for advanced mesh creation + UE_LOG(LogBooleanCutTool, Log, TEXT("Using GeometryScripting to create cut plane mesh")); + + // TODO: Implement cut plane mesh with GeometryScripting + + UStaticMesh* PlaneMesh = NewObject(this, TEXT("CutPlaneMesh")); + return PlaneMesh; #else - // Fallback implementation without GeometryScripting - UE_LOG(LogTemp, Log, TEXT("FLESH: BooleanCutTool - Using fallback cut plane mesh method")); - - // Simple implementation that creates a basic plane mesh - UStaticMesh* PlaneMesh = NewObject(this, TEXT("CutPlaneMesh")); - - // TODO: Implement basic plane mesh without GeometryScripting - - return PlaneMesh; + // Fallback implementation without GeometryScripting + UE_LOG(LogBooleanCutTool, Log, TEXT("Using fallback cut plane mesh method")); + + // Simple implementation that creates a basic plane mesh + UStaticMesh* PlaneMesh = NewObject(this, TEXT("CutPlaneMesh")); + + // TODO: Implement basic plane mesh without GeometryScripting + + return PlaneMesh; #endif + } + catch (const std::exception& e) + { + UE_LOG(LogBooleanCutTool, Error, TEXT("Exception during cut plane mesh creation: %s"), UTF8_TO_TCHAR(e.what())); + } + catch (...) + { + UE_LOG(LogBooleanCutTool, Error, TEXT("Unknown exception during cut plane mesh creation")); + } + + return nullptr; } // Set cut material void UBooleanCutTool::SetCutMaterial(UMaterialInterface* Material) { CutMaterial = Material; + UE_LOG(LogBooleanCutTool, Log, TEXT("Cut material set: %s"), Material ? *Material->GetName() : TEXT("Null")); } // Get cut material diff --git a/Source/FLESHEditor/Private/AnatomicalStructureBrush.cpp b/Source/FLESHEditor/Private/AnatomicalStructureBrush.cpp index 39f97a3..59475c1 100644 --- a/Source/FLESHEditor/Private/AnatomicalStructureBrush.cpp +++ b/Source/FLESHEditor/Private/AnatomicalStructureBrush.cpp @@ -2,6 +2,11 @@ #include "Engine/SkeletalMesh.h" #include "Engine/StaticMesh.h" #include "Materials/MaterialInterface.h" +#include "Logging/LogMacros.h" +#include "Misc/MessageDialog.h" + +// Define log category +DEFINE_LOG_CATEGORY_STATIC(LogAnatomicalBrush, Log, All); UAnatomicalStructureBrush::UAnatomicalStructureBrush() { @@ -11,40 +16,147 @@ UAnatomicalStructureBrush::UAnatomicalStructureBrush() BrushSettings.BrushStrength = 0.5f; BrushSettings.BrushFalloff = 0.5f; BrushSettings.BrushMaterial = nullptr; + + UE_LOG(LogAnatomicalBrush, Log, TEXT("AnatomicalStructureBrush initialized")); } void UAnatomicalStructureBrush::Initialize(const FAnatomicalBrushSettings& InSettings) { BrushSettings = InSettings; + UE_LOG(LogAnatomicalBrush, Log, TEXT("AnatomicalStructureBrush settings updated: Type=%d, Size=%.2f"), + (int32)BrushSettings.BrushType, BrushSettings.BrushSize); } bool UAnatomicalStructureBrush::ApplyToSkeletalMesh(USkeletalMesh* TargetMesh, const FVector& Location, const FVector& Direction, FName BoneName) { - // Implementation will be added in future updates - // This is a placeholder to resolve link errors - return false; + // Add error handling + if (!TargetMesh) + { + UE_LOG(LogAnatomicalBrush, Error, TEXT("ApplyToSkeletalMesh failed: Target mesh is null")); + return false; + } + + UE_LOG(LogAnatomicalBrush, Log, TEXT("Applying to skeletal mesh %s, Location: (%f, %f, %f), Bone: %s"), + *TargetMesh->GetName(), Location.X, Location.Y, Location.Z, *BoneName.ToString()); + + try + { + // Implementation will be added in future updates + // This is a basic framework to resolve link errors + + // Execute different operations based on brush type + switch (BrushSettings.BrushType) + { + case EAnatomicalBrushType::Bone: + UE_LOG(LogAnatomicalBrush, Log, TEXT("Applying bone brush effect")); + // Bone brush logic + break; + case EAnatomicalBrushType::Muscle: + UE_LOG(LogAnatomicalBrush, Log, TEXT("Applying muscle brush effect")); + // Muscle brush logic + break; + case EAnatomicalBrushType::Organ: + UE_LOG(LogAnatomicalBrush, Log, TEXT("Applying organ brush effect")); + // Organ brush logic + break; + case EAnatomicalBrushType::Vessel: + UE_LOG(LogAnatomicalBrush, Log, TEXT("Applying blood vessel brush effect")); + // Blood vessel brush logic + break; + case EAnatomicalBrushType::Nerve: + UE_LOG(LogAnatomicalBrush, Log, TEXT("Applying nerve brush effect")); + // Nerve brush logic + break; + case EAnatomicalBrushType::Custom: + UE_LOG(LogAnatomicalBrush, Log, TEXT("Applying custom brush effect")); + // Custom brush logic + break; + default: + UE_LOG(LogAnatomicalBrush, Warning, TEXT("Unknown brush type")); + break; + } + + return true; // Temporarily return success, should return based on operation result after actual implementation + } + catch (const std::exception& e) + { + UE_LOG(LogAnatomicalBrush, Error, TEXT("ApplyToSkeletalMesh exception: %s"), UTF8_TO_TCHAR(e.what())); + return false; + } + catch (...) + { + UE_LOG(LogAnatomicalBrush, Error, TEXT("ApplyToSkeletalMesh unknown exception occurred")); + return false; + } } bool UAnatomicalStructureBrush::ApplyToStaticMesh(UStaticMesh* TargetMesh, const FVector& Location, const FVector& Direction) { - // Implementation will be added in future updates - // This is a placeholder to resolve link errors - return false; + // Add error handling + if (!TargetMesh) + { + UE_LOG(LogAnatomicalBrush, Error, TEXT("ApplyToStaticMesh failed: Target mesh is null")); + return false; + } + + UE_LOG(LogAnatomicalBrush, Log, TEXT("Applying to static mesh %s, Location: (%f, %f, %f)"), + *TargetMesh->GetName(), Location.X, Location.Y, Location.Z); + + try + { + // Implementation will be added in future updates + // This is a basic framework to resolve link errors + + // Execute different operations based on brush type + switch (BrushSettings.BrushType) + { + case EAnatomicalBrushType::Bone: + UE_LOG(LogAnatomicalBrush, Log, TEXT("Applying bone brush effect to static mesh")); + // Bone brush logic + break; + case EAnatomicalBrushType::Muscle: + UE_LOG(LogAnatomicalBrush, Log, TEXT("Applying muscle brush effect to static mesh")); + // Muscle brush logic + break; + case EAnatomicalBrushType::Organ: + UE_LOG(LogAnatomicalBrush, Log, TEXT("Applying organ brush effect to static mesh")); + // Organ brush logic + break; + default: + UE_LOG(LogAnatomicalBrush, Warning, TEXT("Unknown brush type")); + break; + } + + return true; // Temporarily return success, should return based on operation result after actual implementation + } + catch (const std::exception& e) + { + UE_LOG(LogAnatomicalBrush, Error, TEXT("ApplyToStaticMesh exception: %s"), UTF8_TO_TCHAR(e.what())); + return false; + } + catch (...) + { + UE_LOG(LogAnatomicalBrush, Error, TEXT("ApplyToStaticMesh unknown exception occurred")); + return false; + } } void UAnatomicalStructureBrush::SetBrushType(EAnatomicalBrushType BrushType) { BrushSettings.BrushType = BrushType; + UE_LOG(LogAnatomicalBrush, Log, TEXT("Brush type set to: %d"), (int32)BrushType); } void UAnatomicalStructureBrush::SetBrushSize(float Size) { BrushSettings.BrushSize = FMath::Clamp(Size, 0.1f, 100.0f); + UE_LOG(LogAnatomicalBrush, Log, TEXT("Brush size set to: %.2f"), BrushSettings.BrushSize); } void UAnatomicalStructureBrush::SetBrushStrength(float Strength) { BrushSettings.BrushStrength = FMath::Clamp(Strength, 0.0f, 1.0f); + UE_LOG(LogAnatomicalBrush, Log, TEXT("Brush strength set to: %.2f"), BrushSettings.BrushStrength); } FAnatomicalBrushSettings UAnatomicalStructureBrush::GetBrushSettings() const @@ -54,13 +166,24 @@ FAnatomicalBrushSettings UAnatomicalStructureBrush::GetBrushSettings() const UStaticMesh* UAnatomicalStructureBrush::CreateAnatomicalStructure(const FVector& Location, const FVector& Direction, float Size) { + UE_LOG(LogAnatomicalBrush, Log, TEXT("Creating anatomical structure, Location: (%f, %f, %f), Size: %.2f"), + Location.X, Location.Y, Location.Z, Size); + // Implementation will be added in future updates - // This is a placeholder to resolve link errors + // This is a basic framework return nullptr; } void UAnatomicalStructureBrush::ApplyPhysicsProperties(UStaticMesh* Mesh) { + if (!Mesh) + { + UE_LOG(LogAnatomicalBrush, Error, TEXT("ApplyPhysicsProperties failed: Mesh is null")); + return; + } + + UE_LOG(LogAnatomicalBrush, Log, TEXT("Applying physics properties to mesh %s"), *Mesh->GetName()); + // Implementation will be added in future updates - // This is a placeholder to resolve link errors + // This is a basic framework } diff --git a/Source/FLESHEditor/Private/DismembermentEditor.cpp b/Source/FLESHEditor/Private/DismembermentEditor.cpp index 6e45ed5..1ed3e98 100644 --- a/Source/FLESHEditor/Private/DismembermentEditor.cpp +++ b/Source/FLESHEditor/Private/DismembermentEditor.cpp @@ -9,6 +9,11 @@ #include "Framework/Commands/UICommandList.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "ToolMenus.h" +#include "Logging/LogMacros.h" +#include "Misc/MessageDialog.h" + +// Define log category +DEFINE_LOG_CATEGORY_STATIC(LogDismembermentEditor, Log, All); // Define tab names const FName FDismembermentEditor::ViewportTabId(TEXT("DismembermentEditor_Viewport")); @@ -19,35 +24,107 @@ const FName FDismembermentEditor::PhysicsSettingsTabId(TEXT("DismembermentEditor // Constructor FDismembermentEditor::FDismembermentEditor() : SkeletalMesh(nullptr) + , BooleanCutTool(nullptr) + , LayerSystem(nullptr) + , SelectedBoneName(NAME_None) + , bCreateCapMesh(true) { + UE_LOG(LogDismembermentEditor, Log, TEXT("DismembermentEditor constructor called")); + + // Create boolean cut tool + BooleanCutTool = NewObject(); + if (BooleanCutTool) + { + // Initialize default settings + BooleanCutTool->SetCapMeshMethod(ECapMeshMethod::Simple); + UE_LOG(LogDismembermentEditor, Log, TEXT("Boolean cut tool created successfully")); + } + else + { + UE_LOG(LogDismembermentEditor, Error, TEXT("Failed to create Boolean cut tool")); + } + + // Create anatomical layer system + LayerSystem = NewObject(); + if (LayerSystem) + { + UE_LOG(LogDismembermentEditor, Log, TEXT("Anatomical layer system created successfully")); + } + else + { + UE_LOG(LogDismembermentEditor, Error, TEXT("Failed to create Anatomical layer system")); + } + + // Initialize default cut plane + CurrentCutPlane.Location = FVector(0, 0, 0); + CurrentCutPlane.Normal = FVector(0, 0, 1); // Default to Z-up } // Destructor FDismembermentEditor::~FDismembermentEditor() { + // Unregister from any events + if (GEditor) + { + GEditor->UnregisterForUndo(this); + } + + UE_LOG(LogDismembermentEditor, Log, TEXT("DismembermentEditor destructor called")); } // Initialize the editor void FDismembermentEditor::InitDismembermentEditor(const EToolkitMode::Type Mode, const TSharedPtr& InitToolkitHost, USkeletalMesh* InSkeletalMesh) { - // Set the skeletal mesh being edited - SkeletalMesh = InSkeletalMesh; - - // Create the layout - CreateEditorLayout(); - - // Create the toolbar - CreateEditorToolbar(); - - // Initialize the asset editor - InitAssetEditor(Mode, InitToolkitHost, FName("DismembermentEditorApp"), - FTabManager::FLayout::NullLayout, - /*bCreateDefaultStandaloneMenu=*/ true, - /*bCreateDefaultToolbar=*/ true, - InSkeletalMesh); - - // Register for undo/redo - GEditor->RegisterForUndo(this); + UE_LOG(LogDismembermentEditor, Log, TEXT("Initializing Dismemberment Editor")); + + try + { + // Set the skeletal mesh being edited + SkeletalMesh = InSkeletalMesh; + + if (!SkeletalMesh) + { + UE_LOG(LogDismembermentEditor, Error, TEXT("Failed to initialize Dismemberment Editor: Skeletal mesh is null")); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Failed to initialize Dismemberment Editor: Skeletal mesh is null")); + return; + } + + // Initialize the layer system with the target mesh + if (LayerSystem) + { + // We can't directly set TargetMesh, as UAnatomicalLayerSystem doesn't have this member + // But we can store a reference to the skeletal mesh here + UE_LOG(LogDismembermentEditor, Log, TEXT("Layer system initialized, target mesh: %s"), *SkeletalMesh->GetName()); + } + + // Create the layout + CreateEditorLayout(); + + // Create the toolbar + CreateEditorToolbar(); + + // Initialize the asset editor + InitAssetEditor(Mode, InitToolkitHost, FName("DismembermentEditorApp"), + FTabManager::FLayout::NullLayout, + /*bCreateDefaultStandaloneMenu=*/ true, + /*bCreateDefaultToolbar=*/ true, + InSkeletalMesh); + + // Register for undo/redo + GEditor->RegisterForUndo(this); + + UE_LOG(LogDismembermentEditor, Log, TEXT("Dismemberment Editor initialized successfully")); + } + catch (const std::exception& e) + { + UE_LOG(LogDismembermentEditor, Error, TEXT("Exception during Dismemberment Editor initialization: %s"), UTF8_TO_TCHAR(e.what())); + FMessageDialog::Open(EAppMsgType::Ok, FText::Format(FText::FromString("Exception during Dismemberment Editor initialization: {0}"), FText::FromString(UTF8_TO_TCHAR(e.what())))); + } + catch (...) + { + UE_LOG(LogDismembermentEditor, Error, TEXT("Unknown exception during Dismemberment Editor initialization")); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Unknown exception during Dismemberment Editor initialization")); + } } // Get the toolkit name @@ -77,286 +154,681 @@ FLinearColor FDismembermentEditor::GetWorldCentricTabColorScale() const // Post undo handler void FDismembermentEditor::PostUndo(bool bSuccess) { - // Refresh the views - if (DetailsWidget.IsValid()) - { - DetailsWidget->ForceRefresh(); - } + UE_LOG(LogDismembermentEditor, Log, TEXT("PostUndo called, success: %d"), bSuccess); + + // Refresh the views + if (bSuccess) + { + if (DetailsWidget.IsValid()) + { + DetailsWidget->ForceRefresh(); + } + + // Refresh other widgets as needed + if (ViewportWidget.IsValid()) + { + // Refresh viewport if needed + } + + if (LayerSystemWidget.IsValid()) + { + // Refresh layer system widget if needed + } + + if (PhysicsSettingsWidget.IsValid()) + { + // Refresh physics settings widget if needed + } + } } // Post redo handler void FDismembermentEditor::PostRedo(bool bSuccess) { - // Refresh the views - if (DetailsWidget.IsValid()) - { - DetailsWidget->ForceRefresh(); - } + UE_LOG(LogDismembermentEditor, Log, TEXT("PostRedo called, success: %d"), bSuccess); + + // Refresh the views + if (bSuccess) + { + if (DetailsWidget.IsValid()) + { + DetailsWidget->ForceRefresh(); + } + + // Refresh other widgets as needed + if (ViewportWidget.IsValid()) + { + // Refresh viewport if needed + } + + if (LayerSystemWidget.IsValid()) + { + // Refresh layer system widget if needed + } + + if (PhysicsSettingsWidget.IsValid()) + { + // Refresh physics settings widget if needed + } + } } // Create the editor layout void FDismembermentEditor::CreateEditorLayout() { - // Create the layout - TSharedRef StandaloneDefaultLayout = FTabManager::NewLayout("Standalone_DismembermentEditor_Layout_v1") - ->AddArea - ( - FTabManager::NewPrimaryArea()->SetOrientation(Orient_Vertical) - // In UE5.5.4, toolbar is no longer a separate tab - // Skip the toolbar tab section and directly create the main content area - ->Split - ( - FTabManager::NewSplitter()->SetOrientation(Orient_Horizontal)->SetSizeCoefficient(0.9f) - ->Split - ( - FTabManager::NewStack() - ->SetSizeCoefficient(0.7f) - ->AddTab(ViewportTabId, ETabState::OpenedTab) - ) - ->Split - ( - FTabManager::NewSplitter()->SetOrientation(Orient_Vertical) - ->SetSizeCoefficient(0.3f) - ->Split - ( - FTabManager::NewStack() - ->SetSizeCoefficient(0.4f) - ->AddTab(DetailsTabId, ETabState::OpenedTab) - ) - ->Split - ( - FTabManager::NewStack() - ->SetSizeCoefficient(0.3f) - ->AddTab(LayerSystemTabId, ETabState::OpenedTab) - ) - ->Split - ( - FTabManager::NewStack() - ->SetSizeCoefficient(0.3f) - ->AddTab(PhysicsSettingsTabId, ETabState::OpenedTab) - ) - ) - ) - ); + UE_LOG(LogDismembermentEditor, Log, TEXT("Creating editor layout")); + + try + { + // Create the layout + TSharedRef StandaloneDefaultLayout = FTabManager::NewLayout("Standalone_DismembermentEditor_Layout_v1") + ->AddArea + ( + FTabManager::NewPrimaryArea()->SetOrientation(Orient_Vertical) + // In UE5.5.4, toolbar is no longer a separate tab + // Skip the toolbar tab section and directly create the main content area + ->Split + ( + FTabManager::NewSplitter()->SetOrientation(Orient_Horizontal)->SetSizeCoefficient(0.9f) + ->Split + ( + FTabManager::NewStack() + ->SetSizeCoefficient(0.7f) + ->AddTab(ViewportTabId, ETabState::OpenedTab) + ) + ->Split + ( + FTabManager::NewSplitter()->SetOrientation(Orient_Vertical) + ->SetSizeCoefficient(0.3f) + ->Split + ( + FTabManager::NewStack() + ->SetSizeCoefficient(0.4f) + ->AddTab(DetailsTabId, ETabState::OpenedTab) + ) + ->Split + ( + FTabManager::NewStack() + ->SetSizeCoefficient(0.3f) + ->AddTab(LayerSystemTabId, ETabState::OpenedTab) + ) + ->Split + ( + FTabManager::NewStack() + ->SetSizeCoefficient(0.3f) + ->AddTab(PhysicsSettingsTabId, ETabState::OpenedTab) + ) + ) + ) + ); - // Set the layout - FAssetEditorToolkit::InitAssetEditor( - EToolkitMode::Standalone, - TSharedPtr(), - FName("DismembermentEditorApp"), - StandaloneDefaultLayout, - true, - true, - SkeletalMesh - ); -} - -// Create the editor toolbar -void FDismembermentEditor::CreateEditorToolbar() -{ - // Create command list - TSharedPtr CommandList = MakeShareable(new FUICommandList); - - // Get the toolbar builder - TSharedPtr ToolbarExtender = MakeShareable(new FExtender); - - ToolbarExtender->AddToolBarExtension( - "Asset", - EExtensionHook::After, - CommandList, - FToolBarExtensionDelegate::CreateLambda([this](FToolBarBuilder& ToolbarBuilder) - { - ToolbarBuilder.BeginSection("Dismemberment"); - { - ToolbarBuilder.AddToolBarButton( - FUIAction(FExecuteAction::CreateSP(this, &FDismembermentEditor::PerformBooleanCut)), - NAME_None, - FText::FromString("Boolean Cut"), - FText::FromString("Perform a boolean cut operation"), - FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.CurveBase"), - EUserInterfaceActionType::Button - ); - - ToolbarBuilder.AddToolBarButton( - FUIAction(FExecuteAction::CreateSP(this, &FDismembermentEditor::AddNewLayer)), - NAME_None, - FText::FromString("Add Layer"), - FText::FromString("Add a new anatomical layer"), - FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.Layer"), - EUserInterfaceActionType::Button - ); - - ToolbarBuilder.AddToolBarButton( - FUIAction(FExecuteAction::CreateSP(this, &FDismembermentEditor::PreviewEffects)), - NAME_None, - FText::FromString("Preview"), - FText::FromString("Preview the dismemberment effects"), - FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.ParticleSystem"), - EUserInterfaceActionType::Button - ); - - ToolbarBuilder.AddToolBarButton( - FUIAction(FExecuteAction::CreateSP(this, &FDismembermentEditor::SaveEdits)), - NAME_None, - FText::FromString("Save"), - FText::FromString("Save the dismemberment setup"), - FSlateIcon(FAppStyle::GetAppStyleSetName(), "AssetEditor.SaveAsset"), - EUserInterfaceActionType::Button - ); - } - ToolbarBuilder.EndSection(); - }) - ); - - // Add the extender - AddToolbarExtender(ToolbarExtender); + // Set the layout + FAssetEditorToolkit::InitAssetEditor( + EToolkitMode::Standalone, + TSharedPtr(), + FName("DismembermentEditorApp"), + StandaloneDefaultLayout, + true, + true, + SkeletalMesh + ); + + // Register tab spawners + RegisterTabSpawners(GetTabManager().ToSharedRef()); + + UE_LOG(LogDismembermentEditor, Log, TEXT("Editor layout created successfully")); + } + catch (const std::exception& e) + { + UE_LOG(LogDismembermentEditor, Error, TEXT("Exception creating editor layout: %s"), UTF8_TO_TCHAR(e.what())); + } + catch (...) + { + UE_LOG(LogDismembermentEditor, Error, TEXT("Unknown exception creating editor layout")); + } } // Register tab spawners void FDismembermentEditor::RegisterTabSpawners(const TSharedRef& InTabManager) { - // Call the base implementation - FAssetEditorToolkit::RegisterTabSpawners(InTabManager); - - // Register the tab spawners - InTabManager->RegisterTabSpawner(ViewportTabId, FOnSpawnTab::CreateSP(this, &FDismembermentEditor::SpawnTab_Viewport)) - .SetDisplayName(FText::FromString("Viewport")) - .SetGroup(WorkspaceMenuCategory.ToSharedRef()) - .SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Viewports")); - - InTabManager->RegisterTabSpawner(DetailsTabId, FOnSpawnTab::CreateSP(this, &FDismembermentEditor::SpawnTab_Details)) - .SetDisplayName(FText::FromString("Details")) - .SetGroup(WorkspaceMenuCategory.ToSharedRef()) - .SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Details")); - - InTabManager->RegisterTabSpawner(LayerSystemTabId, FOnSpawnTab::CreateSP(this, &FDismembermentEditor::SpawnTab_LayerSystem)) - .SetDisplayName(FText::FromString("Layer System")) - .SetGroup(WorkspaceMenuCategory.ToSharedRef()) - .SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.Layer")); - - InTabManager->RegisterTabSpawner(PhysicsSettingsTabId, FOnSpawnTab::CreateSP(this, &FDismembermentEditor::SpawnTab_PhysicsSettings)) - .SetDisplayName(FText::FromString("Physics Settings")) - .SetGroup(WorkspaceMenuCategory.ToSharedRef()) - .SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.PhysicsAsset")); + UE_LOG(LogDismembermentEditor, Log, TEXT("Registering tab spawners")); + + FAssetEditorToolkit::RegisterTabSpawners(InTabManager); + + try + { + InTabManager->RegisterTabSpawner(ViewportTabId, FOnSpawnTab::CreateSP(this, &FDismembermentEditor::SpawnTab_Viewport)) + .SetDisplayName(FText::FromString("Viewport")) + .SetGroup(WorkspaceMenuCategory.ToSharedRef()) + .SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Viewports")); + + InTabManager->RegisterTabSpawner(DetailsTabId, FOnSpawnTab::CreateSP(this, &FDismembermentEditor::SpawnTab_Details)) + .SetDisplayName(FText::FromString("Details")) + .SetGroup(WorkspaceMenuCategory.ToSharedRef()) + .SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Details")); + + InTabManager->RegisterTabSpawner(LayerSystemTabId, FOnSpawnTab::CreateSP(this, &FDismembermentEditor::SpawnTab_LayerSystem)) + .SetDisplayName(FText::FromString("Layer System")) + .SetGroup(WorkspaceMenuCategory.ToSharedRef()) + .SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Layers")); + + InTabManager->RegisterTabSpawner(PhysicsSettingsTabId, FOnSpawnTab::CreateSP(this, &FDismembermentEditor::SpawnTab_PhysicsSettings)) + .SetDisplayName(FText::FromString("Physics Settings")) + .SetGroup(WorkspaceMenuCategory.ToSharedRef()) + .SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.WorldProperties")); + + UE_LOG(LogDismembermentEditor, Log, TEXT("Tab spawners registered successfully")); + } + catch (const std::exception& e) + { + UE_LOG(LogDismembermentEditor, Error, TEXT("Exception registering tab spawners: %s"), UTF8_TO_TCHAR(e.what())); + } + catch (...) + { + UE_LOG(LogDismembermentEditor, Error, TEXT("Unknown exception registering tab spawners")); + } } // Unregister tab spawners void FDismembermentEditor::UnregisterTabSpawners(const TSharedRef& InTabManager) { - // Call the base implementation - FAssetEditorToolkit::UnregisterTabSpawners(InTabManager); - - // Unregister the tab spawners - InTabManager->UnregisterTabSpawner(ViewportTabId); - InTabManager->UnregisterTabSpawner(DetailsTabId); - InTabManager->UnregisterTabSpawner(LayerSystemTabId); - InTabManager->UnregisterTabSpawner(PhysicsSettingsTabId); + UE_LOG(LogDismembermentEditor, Log, TEXT("Unregistering tab spawners")); + + FAssetEditorToolkit::UnregisterTabSpawners(InTabManager); + + InTabManager->UnregisterTabSpawner(ViewportTabId); + InTabManager->UnregisterTabSpawner(DetailsTabId); + InTabManager->UnregisterTabSpawner(LayerSystemTabId); + InTabManager->UnregisterTabSpawner(PhysicsSettingsTabId); } // Spawn the viewport tab TSharedRef FDismembermentEditor::SpawnTab_Viewport(const FSpawnTabArgs& Args) { - // Create the viewport widget - ViewportWidget = SNew(SBorder) - .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) - .Padding(FMargin(4.0f)) - [ - SNew(STextBlock) - .Text(FText::FromString("Viewport will be implemented here")) - ]; - - // Create the tab - return SNew(SDockTab) - .Label(FText::FromString("Viewport")) - [ - ViewportWidget.ToSharedRef() - ]; + // Create the viewport widget + ViewportWidget = SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + .Padding(FMargin(4.0f)) + [ + SNew(STextBlock) + .Text(FText::FromString("Viewport will be implemented here")) + ]; + + // Create the tab + return SNew(SDockTab) + .Label(FText::FromString("Viewport")) + [ + ViewportWidget.ToSharedRef() + ]; } // Spawn the details tab TSharedRef FDismembermentEditor::SpawnTab_Details(const FSpawnTabArgs& Args) { - // Create the details view - FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked("PropertyEditor"); - - FDetailsViewArgs DetailsViewArgs; - DetailsViewArgs.bUpdatesFromSelection = true; - DetailsViewArgs.bLockable = false; - DetailsViewArgs.bAllowSearch = true; - DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea; - DetailsViewArgs.bHideSelectionTip = true; - - DetailsWidget = PropertyEditorModule.CreateDetailView(DetailsViewArgs); - DetailsWidget->SetObject(SkeletalMesh); - - // Create the tab - return SNew(SDockTab) - .Label(FText::FromString("Details")) - [ - DetailsWidget.ToSharedRef() - ]; + // Create the details view + FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked("PropertyEditor"); + + FDetailsViewArgs DetailsViewArgs; + DetailsViewArgs.bUpdatesFromSelection = true; + DetailsViewArgs.bLockable = false; + DetailsViewArgs.bAllowSearch = true; + DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea; + DetailsViewArgs.bHideSelectionTip = true; + + DetailsWidget = PropertyEditorModule.CreateDetailView(DetailsViewArgs); + DetailsWidget->SetObject(SkeletalMesh); + + // Create the tab + return SNew(SDockTab) + .Label(FText::FromString("Details")) + [ + DetailsWidget.ToSharedRef() + ]; } // Spawn the layer system tab TSharedRef FDismembermentEditor::SpawnTab_LayerSystem(const FSpawnTabArgs& Args) { - // Create the layer system widget - LayerSystemWidget = SNew(SBorder) - .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) - .Padding(FMargin(4.0f)) - [ - SNew(STextBlock) - .Text(FText::FromString("Layer System will be implemented here")) - ]; - - // Create the tab - return SNew(SDockTab) - .Label(FText::FromString("Layer System")) - [ - LayerSystemWidget.ToSharedRef() - ]; + // Create the layer system widget + LayerSystemWidget = SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + .Padding(FMargin(4.0f)) + [ + SNew(STextBlock) + .Text(FText::FromString("Layer System will be implemented here")) + ]; + + // Create the tab + return SNew(SDockTab) + .Label(FText::FromString("Layer System")) + [ + LayerSystemWidget.ToSharedRef() + ]; } // Spawn the physics settings tab TSharedRef FDismembermentEditor::SpawnTab_PhysicsSettings(const FSpawnTabArgs& Args) { - // Create the physics settings widget - PhysicsSettingsWidget = SNew(SBorder) - .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) - .Padding(FMargin(4.0f)) - [ - SNew(STextBlock) - .Text(FText::FromString("Physics Settings will be implemented here")) - ]; - - // Create the tab - return SNew(SDockTab) - .Label(FText::FromString("Physics Settings")) - [ - PhysicsSettingsWidget.ToSharedRef() - ]; + // Create the physics settings widget + PhysicsSettingsWidget = SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + .Padding(FMargin(4.0f)) + [ + SNew(STextBlock) + .Text(FText::FromString("Physics Settings will be implemented here")) + ]; + + // Create the tab + return SNew(SDockTab) + .Label(FText::FromString("Physics Settings")) + [ + PhysicsSettingsWidget.ToSharedRef() + ]; } // Perform a boolean cut operation void FDismembermentEditor::PerformBooleanCut() { - // To be implemented + UE_LOG(LogDismembermentEditor, Log, TEXT("Performing boolean cut operation")); + + try + { + if (!SkeletalMesh) + { + UE_LOG(LogDismembermentEditor, Error, TEXT("Cannot perform boolean cut: No skeletal mesh selected")); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Cannot perform boolean cut: No skeletal mesh selected")); + return; + } + + if (!BooleanCutTool) + { + UE_LOG(LogDismembermentEditor, Error, TEXT("Cannot perform boolean cut: Boolean cut tool not initialized")); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Cannot perform boolean cut: Boolean cut tool not initialized")); + return; + } + + // Perform the actual boolean cut operation + UE_LOG(LogDismembermentEditor, Log, TEXT("Using boolean cut tool to perform cut operation, Bone: %s, Create Cap: %s"), + *SelectedBoneName.ToString(), bCreateCapMesh ? TEXT("Yes") : TEXT("No")); + + // If a bone is selected, use bone-guided cutting + TArray ResultMeshes; + if (SelectedBoneName != NAME_None) + { + ResultMeshes = BooleanCutTool->CutWithBoneAxis(SkeletalMesh, SelectedBoneName, CurrentCutPlane, bCreateCapMesh); + } + else + { + // Otherwise use regular skeletal mesh cutting + ResultMeshes = BooleanCutTool->CutSkeletalMesh(SkeletalMesh, CurrentCutPlane, NAME_None, bCreateCapMesh); + } + + // Check results + if (ResultMeshes.Num() > 0) + { + UE_LOG(LogDismembermentEditor, Log, TEXT("Boolean cut completed successfully, generated %d meshes"), ResultMeshes.Num()); + + // Show success message + FText SuccessMessage = FText::Format( + FText::FromString("Boolean cut completed, generated {0} meshes"), + FText::AsNumber(ResultMeshes.Num()) + ); + FMessageDialog::Open(EAppMsgType::Ok, SuccessMessage); + + // TODO: Display result meshes in viewport + } + else + { + UE_LOG(LogDismembermentEditor, Warning, TEXT("Boolean cut operation did not generate any meshes")); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Boolean cut operation did not generate any meshes")); + } + + UE_LOG(LogDismembermentEditor, Log, TEXT("Boolean cut operation completed")); + } + catch (const std::exception& e) + { + UE_LOG(LogDismembermentEditor, Error, TEXT("Exception during boolean cut operation: %s"), UTF8_TO_TCHAR(e.what())); + FMessageDialog::Open(EAppMsgType::Ok, FText::Format(FText::FromString("Exception during boolean cut operation: {0}"), FText::FromString(UTF8_TO_TCHAR(e.what())))); + } + catch (...) + { + UE_LOG(LogDismembermentEditor, Error, TEXT("Unknown exception during boolean cut operation")); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Unknown exception during boolean cut operation")); + } } // Add a new layer void FDismembermentEditor::AddNewLayer() { - // To be implemented + UE_LOG(LogDismembermentEditor, Log, TEXT("Adding new anatomical layer")); + + try + { + if (!SkeletalMesh) + { + UE_LOG(LogDismembermentEditor, Error, TEXT("Cannot add new layer: No skeletal mesh selected")); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Cannot add new layer: No skeletal mesh selected")); + return; + } + + if (!LayerSystem) + { + UE_LOG(LogDismembermentEditor, Error, TEXT("Cannot add new layer: Layer system not initialized")); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Cannot add new layer: Layer system not initialized")); + return; + } + + // Show a dialog to get the layer type and name + TSharedRef Window = SNew(SWindow) + .Title(FText::FromString("Add New Anatomical Layer")) + .SizingRule(ESizingRule::Autosized) + .SupportsMaximize(false) + .SupportsMinimize(false); + + TSharedPtr>> LayerTypeComboBox; + TSharedPtr LayerNameTextBox; + + // Create array of layer types + TArray> LayerTypes; + LayerTypes.Add(MakeShareable(new EAnatomicalLayerType(EAnatomicalLayerType::Skin))); + LayerTypes.Add(MakeShareable(new EAnatomicalLayerType(EAnatomicalLayerType::Muscle))); + LayerTypes.Add(MakeShareable(new EAnatomicalLayerType(EAnatomicalLayerType::Bone))); + LayerTypes.Add(MakeShareable(new EAnatomicalLayerType(EAnatomicalLayerType::Organ))); + LayerTypes.Add(MakeShareable(new EAnatomicalLayerType(EAnatomicalLayerType::Blood))); + LayerTypes.Add(MakeShareable(new EAnatomicalLayerType(EAnatomicalLayerType::Custom))); + + // Selected layer type + TSharedPtr SelectedLayerType = LayerTypes[0]; + + // Layer name + FString NewLayerName = TEXT("New Layer"); + + // Dialog result + bool bDialogResult = false; + + Window->SetContent( + SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + .Padding(FMargin(8.0f)) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0, 0, 0, 4) + [ + SNew(STextBlock) + .Text(FText::FromString("Layer Type:")) + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0, 0, 0, 8) + [ + SAssignNew(LayerTypeComboBox, SComboBox>) + .OptionsSource(&LayerTypes) + .InitiallySelectedItem(SelectedLayerType) + .OnSelectionChanged_Lambda([&SelectedLayerType](TSharedPtr NewValue, ESelectInfo::Type SelectType) { + SelectedLayerType = NewValue; + }) + .OnGenerateWidget_Lambda([](TSharedPtr InLayerType) { + FString TypeName; + switch (*InLayerType) + { + case EAnatomicalLayerType::Skin: TypeName = TEXT("Skin"); break; + case EAnatomicalLayerType::Muscle: TypeName = TEXT("Muscle"); break; + case EAnatomicalLayerType::Bone: TypeName = TEXT("Bone"); break; + case EAnatomicalLayerType::Organ: TypeName = TEXT("Organ"); break; + case EAnatomicalLayerType::Blood: TypeName = TEXT("Blood"); break; + case EAnatomicalLayerType::Custom: TypeName = TEXT("Custom"); break; + default: TypeName = TEXT("Unknown"); break; + } + return SNew(STextBlock).Text(FText::FromString(TypeName)); + }) + [ + SNew(STextBlock) + .Text_Lambda([&SelectedLayerType]() { + FString TypeName; + switch (*SelectedLayerType) + { + case EAnatomicalLayerType::Skin: TypeName = TEXT("Skin"); break; + case EAnatomicalLayerType::Muscle: TypeName = TEXT("Muscle"); break; + case EAnatomicalLayerType::Bone: TypeName = TEXT("Bone"); break; + case EAnatomicalLayerType::Organ: TypeName = TEXT("Organ"); break; + case EAnatomicalLayerType::Blood: TypeName = TEXT("Blood"); break; + case EAnatomicalLayerType::Custom: TypeName = TEXT("Custom"); break; + default: TypeName = TEXT("Unknown"); break; + } + return FText::FromString(TypeName); + }) + ] + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0, 0, 0, 4) + [ + SNew(STextBlock) + .Text(FText::FromString("Layer Name:")) + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0, 0, 0, 8) + [ + SAssignNew(LayerNameTextBox, SEditableTextBox) + .Text(FText::FromString(NewLayerName)) + .OnTextChanged_Lambda([&NewLayerName](const FText& NewText) { + NewLayerName = NewText.ToString(); + }) + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0, 8, 0, 0) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(SSpacer) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(0, 0, 4, 0) + [ + SNew(SButton) + .Text(FText::FromString("Cancel")) + .OnClicked_Lambda([&Window, &bDialogResult]() { + bDialogResult = false; + Window->RequestDestroyWindow(); + return FReply::Handled(); + }) + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SButton) + .Text(FText::FromString("Add")) + .OnClicked_Lambda([&Window, &bDialogResult]() { + bDialogResult = true; + Window->RequestDestroyWindow(); + return FReply::Handled(); + }) + ] + ] + ] + ); + + // Show the dialog + GEditor->EditorAddModalWindow(Window); + + // If the user clicked Add + if (bDialogResult && SelectedLayerType.IsValid()) + { + // Add the new layer + int32 NewLayerIndex = LayerSystem->AddLayer(FName(*NewLayerName), *SelectedLayerType); + + if (NewLayerIndex != INDEX_NONE) + { + UE_LOG(LogDismembermentEditor, Log, TEXT("Added new layer: %s (Type: %d)"), *NewLayerName, (int32)*SelectedLayerType); + + // Show success message + FText SuccessMessage = FText::Format( + FText::FromString("Successfully added new layer: {0}"), + FText::FromString(NewLayerName) + ); + FMessageDialog::Open(EAppMsgType::Ok, SuccessMessage); + + // TODO: Update the UI to show the new layer + } + else + { + UE_LOG(LogDismembermentEditor, Error, TEXT("Failed to add new layer")); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Failed to add new layer")); + } + } + + UE_LOG(LogDismembermentEditor, Log, TEXT("Add new layer operation completed")); + } + catch (const std::exception& e) + { + UE_LOG(LogDismembermentEditor, Error, TEXT("Exception adding new layer: %s"), UTF8_TO_TCHAR(e.what())); + FMessageDialog::Open(EAppMsgType::Ok, FText::Format(FText::FromString("Exception occurred while adding new layer: {0}"), FText::FromString(UTF8_TO_TCHAR(e.what())))); + } + catch (...) + { + UE_LOG(LogDismembermentEditor, Error, TEXT("Unknown exception adding new layer")); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Unknown exception occurred while adding new layer")); + } } // Save the edits void FDismembermentEditor::SaveEdits() { - // To be implemented + UE_LOG(LogDismembermentEditor, Log, TEXT("Saving dismemberment setup")); + + try + { + if (!SkeletalMesh) + { + UE_LOG(LogDismembermentEditor, Error, TEXT("Cannot save edits: No skeletal mesh selected")); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Cannot save edits: No skeletal mesh selected")); + return; + } + + // TODO: Implement the actual save functionality + // This will involve: + // 1. Saving the current state of the mesh + // 2. Saving the layer system configuration + // 3. Saving the physics settings + + // For now, just show a message + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Save functionality will be implemented in a future update")); + + UE_LOG(LogDismembermentEditor, Log, TEXT("Dismemberment setup saved")); + } + catch (const std::exception& e) + { + UE_LOG(LogDismembermentEditor, Error, TEXT("Exception saving edits: %s"), UTF8_TO_TCHAR(e.what())); + FMessageDialog::Open(EAppMsgType::Ok, FText::Format(FText::FromString("Exception occurred while saving edits: {0}"), FText::FromString(UTF8_TO_TCHAR(e.what())))); + } + catch (...) + { + UE_LOG(LogDismembermentEditor, Error, TEXT("Unknown exception saving edits")); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Unknown exception occurred while saving edits")); + } } // Preview the effects void FDismembermentEditor::PreviewEffects() { - // To be implemented + UE_LOG(LogDismembermentEditor, Log, TEXT("Previewing dismemberment effects")); + + try + { + if (!SkeletalMesh) + { + UE_LOG(LogDismembermentEditor, Error, TEXT("Cannot preview effects: No skeletal mesh selected")); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Cannot preview effects: No skeletal mesh selected")); + return; + } + + // TODO: Implement the actual preview functionality + // This will involve: + // 1. Creating a temporary instance of the mesh + // 2. Applying the current dismemberment settings + // 3. Showing the effects in the viewport + + // For now, just show a message + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Preview functionality will be implemented in a future update")); + + UE_LOG(LogDismembermentEditor, Log, TEXT("Dismemberment effects previewed")); + } + catch (const std::exception& e) + { + UE_LOG(LogDismembermentEditor, Error, TEXT("Exception previewing effects: %s"), UTF8_TO_TCHAR(e.what())); + FMessageDialog::Open(EAppMsgType::Ok, FText::Format(FText::FromString("Exception occurred while previewing effects: {0}"), FText::FromString(UTF8_TO_TCHAR(e.what())))); + } + catch (...) + { + UE_LOG(LogDismembermentEditor, Error, TEXT("Unknown exception previewing effects")); + FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Unknown exception occurred while previewing effects")); + } +} + +// Create the editor toolbar +void FDismembermentEditor::CreateEditorToolbar() +{ + // Create command list + TSharedPtr CommandList = MakeShareable(new FUICommandList); + + // Get the toolbar builder + TSharedPtr ToolbarExtender = MakeShareable(new FExtender); + + ToolbarExtender->AddToolBarExtension( + "Asset", + EExtensionHook::After, + CommandList, + FToolBarExtensionDelegate::CreateLambda([this](FToolBarBuilder& ToolbarBuilder) + { + ToolbarBuilder.BeginSection("Dismemberment"); + { + ToolbarBuilder.AddToolBarButton( + FUIAction(FExecuteAction::CreateSP(this, &FDismembermentEditor::PerformBooleanCut)), + NAME_None, + FText::FromString("Boolean Cut"), + FText::FromString("Perform Boolean Cut Operation"), + FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.CurveBase"), + EUserInterfaceActionType::Button + ); + + ToolbarBuilder.AddToolBarButton( + FUIAction(FExecuteAction::CreateSP(this, &FDismembermentEditor::AddNewLayer)), + NAME_None, + FText::FromString("Add Layer"), + FText::FromString("Add New Anatomical Layer"), + FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.Layer"), + EUserInterfaceActionType::Button + ); + + ToolbarBuilder.AddToolBarButton( + FUIAction(FExecuteAction::CreateSP(this, &FDismembermentEditor::PreviewEffects)), + NAME_None, + FText::FromString("Preview"), + FText::FromString("Preview Dismemberment Effects"), + FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.ParticleSystem"), + EUserInterfaceActionType::Button + ); + + ToolbarBuilder.AddToolBarButton( + FUIAction(FExecuteAction::CreateSP(this, &FDismembermentEditor::SaveEdits)), + NAME_None, + FText::FromString("Save"), + FText::FromString("Save Dismemberment Settings"), + FSlateIcon(FAppStyle::GetAppStyleSetName(), "AssetEditor.SaveAsset"), + EUserInterfaceActionType::Button + ); + } + ToolbarBuilder.EndSection(); + }) + ); + + // Add the extender + AddToolbarExtender(ToolbarExtender); } diff --git a/Source/FLESHEditor/Private/DismembermentGraph/DismembermentGraph.cpp b/Source/FLESHEditor/Private/DismembermentGraph/DismembermentGraph.cpp index c883bde..55ec0e3 100644 --- a/Source/FLESHEditor/Private/DismembermentGraph/DismembermentGraph.cpp +++ b/Source/FLESHEditor/Private/DismembermentGraph/DismembermentGraph.cpp @@ -5,4 +5,4 @@ UDismembermentGraph::UDismembermentGraph() { // Initialize default values OwningAsset = nullptr; -} \ No newline at end of file +} diff --git a/Source/FLESHEditor/Private/DismembermentGraph/DismembermentGraphEditorFactory.cpp b/Source/FLESHEditor/Private/DismembermentGraph/DismembermentGraphEditorFactory.cpp index b55cff9..8ca9889 100644 --- a/Source/FLESHEditor/Private/DismembermentGraph/DismembermentGraphEditorFactory.cpp +++ b/Source/FLESHEditor/Private/DismembermentGraph/DismembermentGraphEditorFactory.cpp @@ -19,4 +19,4 @@ UObject* UDismembermentGraphEditorFactory::FactoryCreateNew(UClass* InClass, UOb bool UDismembermentGraphEditorFactory::ShouldShowInNewMenu() const { return true; -} \ No newline at end of file +} diff --git a/Source/FLESHEditor/Private/FLESHEditor.cpp b/Source/FLESHEditor/Private/FLESHEditor.cpp index bff1ea3..eae85c6 100644 --- a/Source/FLESHEditor/Private/FLESHEditor.cpp +++ b/Source/FLESHEditor/Private/FLESHEditor.cpp @@ -3,229 +3,802 @@ #include "FLESHEditorStyle.h" #include "MatrixInputWidget.h" #include "DismembermentGraph/DismembermentGraphEditor.h" -#include "DismembermentGraph/DismembermentGraphAsset.h" -#include "Framework/Docking/TabManager.h" +#include "DismembermentEditor.h" +#include "AnatomicalStructureBrush.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 "Modules/ModuleManager.h" +#include "IDetailsView.h" +#include "Widgets/Layout/SBorder.h" #include "Styling/AppStyle.h" -#include "LevelEditor.h" +#include "Framework/Commands/UICommandList.h" +#include "Framework/MultiBox/MultiBoxBuilder.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" +#include "Misc/MessageDialog.h" +// Define log category +DEFINE_LOG_CATEGORY(LogFLESHEditor); + +// Define localization namespace #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")); +// Define static tab IDs +const FName FFLESHEditor::ViewportTabId(TEXT("Viewport")); +const FName FFLESHEditor::DetailsTabId(TEXT("Details")); +const FName FFLESHEditor::AssetBrowserTabId(TEXT("AssetBrowser")); +const FName FFLESHEditor::MatrixEditorTabId(TEXT("MatrixEditor")); +const FName FFLESHEditor::GraphEditorTabId(TEXT("GraphEditor")); +const FName FFLESHEditor::ToolbarTabId(TEXT("Toolbar")); +const FName FFLESHEditor::LayerSystemTabId(TEXT("LayerSystem")); +const FName FFLESHEditor::PhysicsSettingsTabId(TEXT("PhysicsSettings")); +const FName FFLESHEditor::DismembermentGraphTabId(TEXT("DismembermentGraph")); +// Constructor FFLESHEditor::FFLESHEditor() - : EditingObject(nullptr) - , CommandList(MakeShareable(new FUICommandList)) + : CommandList(MakeShareable(new FUICommandList)) + , EditingObject(nullptr) + , bIsInitialized(false) { - // Initialize member variables - bIsEditorInitialized = false; + UE_LOG(LogFLESHEditor, Log, TEXT("FLESH Editor constructor called")); } +// Destructor FFLESHEditor::~FFLESHEditor() { // Unregister from any events - // Comment out problematic code, as FFLESHEditor doesn't inherit from FEditorUndoClient - // if (GEditor) - // { - // GEditor->UnregisterForUndo(this); - // } + if (GEditor) + { + GEditor->UnregisterForUndo(this); + } - // Clear references - DetailsWidget = nullptr; - EditingObject = nullptr; + UE_LOG(LogFLESHEditor, Log, TEXT("FLESH Editor destructor called")); } +// Initialize the editor void FFLESHEditor::InitFLESHEditor(const EToolkitMode::Type Mode, const TSharedPtr& InitToolkitHost, UObject* InObject) { - // Store the object we're editing - EditingObject = InObject; + UE_LOG(LogFLESHEditor, Log, TEXT("Initializing FLESH Editor")); - // 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()) + try { - 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")); + // Set the object being edited + EditingObject = InObject; + + if (!EditingObject) + { + HandleEditorError(LOCTEXT("InitNoObject", "Failed to initialize FLESH Editor: No object specified")); + return; + } + + // Register commands + FFLESHEditorCommands::Register(); + + // Register for undo/redo + GEditor->RegisterForUndo(this); + + // Create the layout + const TSharedRef StandaloneDefaultLayout = FTabManager::NewLayout("Standalone_FLESHEditor_Layout_v1") + ->AddArea + ( + FTabManager::NewPrimaryArea()->SetOrientation(Orient_Vertical) + ->Split + ( + FTabManager::NewSplitter()->SetOrientation(Orient_Horizontal)->SetSizeCoefficient(0.9f) + ->Split + ( + FTabManager::NewStack() + ->SetSizeCoefficient(0.7f) + ->AddTab(ViewportTabId, ETabState::OpenedTab) + ) + ->Split + ( + FTabManager::NewSplitter()->SetOrientation(Orient_Vertical) + ->SetSizeCoefficient(0.3f) + ->Split + ( + FTabManager::NewStack() + ->SetSizeCoefficient(0.4f) + ->AddTab(DetailsTabId, ETabState::OpenedTab) + ) + ->Split + ( + FTabManager::NewStack() + ->SetSizeCoefficient(0.3f) + ->AddTab(LayerSystemTabId, ETabState::OpenedTab) + ) + ->Split + ( + FTabManager::NewStack() + ->SetSizeCoefficient(0.3f) + ->AddTab(PhysicsSettingsTabId, ETabState::OpenedTab) + ) + ) + ) + ); + + // Initialize the asset editor + FAssetEditorToolkit::InitAssetEditor( + Mode, + InitToolkitHost, + FName("FLESHEditorApp"), + StandaloneDefaultLayout, + /*bCreateDefaultStandaloneMenu=*/ true, + /*bCreateDefaultToolbar=*/ true, + EditingObject + ); + + // Create the command list + CreateCommandList(); + + // Register tab spawners + RegisterTabSpawners(GetTabManager().ToSharedRef()); + + // Extend toolbar + ExtendToolbar(); + + // Set initialization flag + bIsInitialized = true; + + UE_LOG(LogFLESHEditor, Log, TEXT("FLESH Editor initialized successfully")); + } + catch (const std::exception& e) + { + HandleEditorError(FText::Format(LOCTEXT("InitException", "Exception during FLESH Editor initialization: {0}"), FText::FromString(UTF8_TO_TCHAR(e.what())))); + } + catch (...) + { + HandleEditorError(LOCTEXT("InitUnknownError", "Unknown exception during FLESH Editor initialization")); } - - // 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); } +// FEditorUndoClient interface +void FFLESHEditor::PostUndo(bool bSuccess) +{ + if (bSuccess) + { + // Refresh the views + if (DetailsWidget.IsValid()) + { + DetailsWidget->ForceRefresh(); + } + } +} + +void FFLESHEditor::PostRedo(bool bSuccess) +{ + if (bSuccess) + { + // Refresh the views + if (DetailsWidget.IsValid()) + { + DetailsWidget->ForceRefresh(); + } + } +} + +// Tab spawners +TSharedRef FFLESHEditor::SpawnTab_Viewport(const FSpawnTabArgs& Args) +{ + UE_LOG(LogFLESHEditor, Log, TEXT("Spawning Viewport tab")); + + try + { + // Create the viewport widget + TSharedRef ViewportContent = SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + .Padding(FMargin(4.0f)) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0, 0, 0, 4) + [ + SNew(STextBlock) + .Text(LOCTEXT("ViewportTitle", "FLESH Viewport")) + .Font(FCoreStyle::GetDefaultFontStyle("Bold", 12)) + ] + + SVerticalBox::Slot() + .FillHeight(1.0f) + [ + SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.DarkGroupBorder")) + .Padding(FMargin(4.0f)) + [ + SNew(STextBlock) + .Text(LOCTEXT("ViewportImplementation", "Viewport implementation will be added here")) + .AutoWrapText(true) + ] + ] + ]; + + // Create the tab + TSharedRef NewTab = SNew(SDockTab) + .Label(LOCTEXT("ViewportTab", "Viewport")) + [ + ViewportContent + ]; + + return NewTab; + } + catch (const std::exception& e) + { + UE_LOG(LogFLESHEditor, Error, TEXT("Exception spawning Viewport tab: %s"), UTF8_TO_TCHAR(e.what())); + + // Create an error widget + TSharedRef ErrorWidget = SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + .Padding(FMargin(4.0f)) + [ + SNew(STextBlock) + .Text(FText::Format(LOCTEXT("ViewportTabError", "Error spawning Viewport tab: {0}"), FText::FromString(UTF8_TO_TCHAR(e.what())))) + ]; + + // Create the tab with the error widget + TSharedRef NewTab = SNew(SDockTab) + .Label(LOCTEXT("ViewportTab", "Viewport")) + [ + ErrorWidget + ]; + + return NewTab; + } + catch (...) + { + UE_LOG(LogFLESHEditor, Error, TEXT("Unknown exception spawning Viewport tab")); + + // Create an error widget + TSharedRef ErrorWidget = SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + .Padding(FMargin(4.0f)) + [ + SNew(STextBlock) + .Text(LOCTEXT("ViewportTabUnknownError", "Unknown error spawning Viewport tab")) + ]; + + // Create the tab with the error widget + TSharedRef NewTab = SNew(SDockTab) + .Label(LOCTEXT("ViewportTab", "Viewport")) + [ + ErrorWidget + ]; + + return NewTab; + } +} + +TSharedRef FFLESHEditor::SpawnTab_Details(const FSpawnTabArgs& Args) +{ + UE_LOG(LogFLESHEditor, Log, TEXT("Spawning Details tab")); + + try + { + // Create the details widget + TSharedRef DetailsContent = SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + .Padding(FMargin(4.0f)) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0, 0, 0, 4) + [ + SNew(STextBlock) + .Text(LOCTEXT("DetailsTitle", "Details")) + .Font(FCoreStyle::GetDefaultFontStyle("Bold", 12)) + ] + + SVerticalBox::Slot() + .FillHeight(1.0f) + [ + SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.DarkGroupBorder")) + .Padding(FMargin(4.0f)) + [ + SNew(STextBlock) + .Text(LOCTEXT("DetailsImplementation", "Details implementation will be added here")) + .AutoWrapText(true) + ] + ] + ]; + + // Create the tab + TSharedRef NewTab = SNew(SDockTab) + .Label(LOCTEXT("DetailsTab", "Details")) + [ + DetailsContent + ]; + + return NewTab; + } + catch (const std::exception& e) + { + UE_LOG(LogFLESHEditor, Error, TEXT("Exception spawning Details tab: %s"), UTF8_TO_TCHAR(e.what())); + + // Create an error widget + TSharedRef ErrorWidget = SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + .Padding(FMargin(4.0f)) + [ + SNew(STextBlock) + .Text(FText::Format(LOCTEXT("DetailsTabError", "Error spawning Details tab: {0}"), FText::FromString(UTF8_TO_TCHAR(e.what())))) + ]; + + // Create the tab with the error widget + TSharedRef NewTab = SNew(SDockTab) + .Label(LOCTEXT("DetailsTab", "Details")) + [ + ErrorWidget + ]; + + return NewTab; + } + catch (...) + { + UE_LOG(LogFLESHEditor, Error, TEXT("Unknown exception spawning Details tab")); + + // Create an error widget + TSharedRef ErrorWidget = SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + .Padding(FMargin(4.0f)) + [ + SNew(STextBlock) + .Text(LOCTEXT("DetailsTabUnknownError", "Unknown error spawning Details tab")) + ]; + + // Create the tab with the error widget + TSharedRef NewTab = SNew(SDockTab) + .Label(LOCTEXT("DetailsTab", "Details")) + [ + ErrorWidget + ]; + + return NewTab; + } +} + +// DismembermentEditor related tab spawners +TSharedRef FFLESHEditor::SpawnTab_LayerSystem(const FSpawnTabArgs& Args) +{ + UE_LOG(LogFLESHEditor, Log, TEXT("Spawning Layer System tab")); + + try + { + // Create the layer system widget + TSharedRef WidgetContent = CreateLayerSystemWidget(); + + // Create the tab + TSharedRef NewTab = SNew(SDockTab) + .Label(LOCTEXT("LayerSystemTab", "Layer System")) + [ + WidgetContent + ]; + + return NewTab; + } + catch (const std::exception& e) + { + UE_LOG(LogFLESHEditor, Error, TEXT("Exception spawning Layer System tab: %s"), UTF8_TO_TCHAR(e.what())); + + // Create an error widget + TSharedRef ErrorWidget = SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + .Padding(FMargin(4.0f)) + [ + SNew(STextBlock) + .Text(FText::Format(LOCTEXT("LayerSystemTabError", "Error spawning Layer System tab: {0}"), FText::FromString(UTF8_TO_TCHAR(e.what())))) + ]; + + // Create the tab with the error widget + TSharedRef NewTab = SNew(SDockTab) + .Label(LOCTEXT("LayerSystemTab", "Layer System")) + [ + ErrorWidget + ]; + + return NewTab; + } + catch (...) + { + UE_LOG(LogFLESHEditor, Error, TEXT("Unknown exception spawning Layer System tab")); + + // Create an error widget + TSharedRef ErrorWidget = SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + .Padding(FMargin(4.0f)) + [ + SNew(STextBlock) + .Text(LOCTEXT("LayerSystemTabUnknownError", "Unknown error spawning Layer System tab")) + ]; + + // Create the tab with the error widget + TSharedRef NewTab = SNew(SDockTab) + .Label(LOCTEXT("LayerSystemTab", "Layer System")) + [ + ErrorWidget + ]; + + return NewTab; + } +} + +TSharedRef FFLESHEditor::SpawnTab_PhysicsSettings(const FSpawnTabArgs& Args) +{ + UE_LOG(LogFLESHEditor, Log, TEXT("Spawning Physics Settings tab")); + + try + { + // Create the physics settings widget + TSharedRef WidgetContent = CreatePhysicsSettingsWidget(); + + // Create the tab + TSharedRef NewTab = SNew(SDockTab) + .Label(LOCTEXT("PhysicsSettingsTab", "Physics Settings")) + [ + WidgetContent + ]; + + return NewTab; + } + catch (const std::exception& e) + { + UE_LOG(LogFLESHEditor, Error, TEXT("Exception spawning Physics Settings tab: %s"), UTF8_TO_TCHAR(e.what())); + + // Create an error widget + TSharedRef ErrorWidget = SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + .Padding(FMargin(4.0f)) + [ + SNew(STextBlock) + .Text(FText::Format(LOCTEXT("PhysicsSettingsTabError", "Error spawning Physics Settings tab: {0}"), FText::FromString(UTF8_TO_TCHAR(e.what())))) + ]; + + // Create the tab with the error widget + TSharedRef NewTab = SNew(SDockTab) + .Label(LOCTEXT("PhysicsSettingsTab", "Physics Settings")) + [ + ErrorWidget + ]; + + return NewTab; + } + catch (...) + { + UE_LOG(LogFLESHEditor, Error, TEXT("Unknown exception spawning Physics Settings tab")); + + // Create an error widget + TSharedRef ErrorWidget = SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + .Padding(FMargin(4.0f)) + [ + SNew(STextBlock) + .Text(LOCTEXT("PhysicsSettingsTabUnknownError", "Unknown error spawning Physics Settings tab")) + ]; + + // Create the tab with the error widget + TSharedRef NewTab = SNew(SDockTab) + .Label(LOCTEXT("PhysicsSettingsTab", "Physics Settings")) + [ + ErrorWidget + ]; + + return NewTab; + } +} + +// Create DismembermentEditor related widgets +TSharedRef FFLESHEditor::CreateLayerSystemWidget() +{ + UE_LOG(LogFLESHEditor, Log, TEXT("Creating Layer System widget")); + + try + { + // Create a placeholder widget for now + TSharedRef Widget = SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + .Padding(FMargin(4.0f)) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0, 0, 0, 4) + [ + SNew(STextBlock) + .Text(LOCTEXT("LayerSystemTitle", "Anatomical Layer System")) + .Font(FCoreStyle::GetDefaultFontStyle("Bold", 12)) + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0, 0, 0, 4) + [ + SNew(STextBlock) + .Text(LOCTEXT("LayerSystemDescription", "Configure anatomical layers for the mesh")) + .AutoWrapText(true) + ] + + SVerticalBox::Slot() + .FillHeight(1.0f) + [ + SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.DarkGroupBorder")) + .Padding(FMargin(4.0f)) + [ + SNew(STextBlock) + .Text(LOCTEXT("LayerSystemImplementation", "Layer System implementation will be added here")) + .AutoWrapText(true) + ] + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0, 4, 0, 0) + [ + SNew(SButton) + .Text(LOCTEXT("AddLayerButton", "Add Layer")) + .ToolTipText(LOCTEXT("AddLayerTooltip", "Add a new anatomical layer")) + .OnClicked_Lambda([]() { return FReply::Handled(); }) + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0, 4, 0, 0) + [ + SNew(SButton) + .Text(LOCTEXT("RemoveLayerButton", "Remove Layer")) + .ToolTipText(LOCTEXT("RemoveLayerTooltip", "Remove the selected anatomical layer")) + .OnClicked_Lambda([]() { return FReply::Handled(); }) + ] + ]; + + // Store the widget in the class member + LayerSystemWidget = Widget; + + return Widget; + } + catch (const std::exception& e) + { + UE_LOG(LogFLESHEditor, Error, TEXT("Exception creating Layer System widget: %s"), UTF8_TO_TCHAR(e.what())); + + // Create an error widget + TSharedRef ErrorWidget = SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + .Padding(FMargin(4.0f)) + [ + SNew(STextBlock) + .Text(FText::Format(LOCTEXT("LayerSystemWidgetError", "Error creating Layer System widget: {0}"), FText::FromString(UTF8_TO_TCHAR(e.what())))) + .AutoWrapText(true) + ]; + + return ErrorWidget; + } + catch (...) + { + UE_LOG(LogFLESHEditor, Error, TEXT("Unknown exception creating Layer System widget")); + + // Create an error widget + TSharedRef ErrorWidget = SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + .Padding(FMargin(4.0f)) + [ + SNew(STextBlock) + .Text(LOCTEXT("LayerSystemWidgetUnknownError", "Unknown error creating Layer System widget")) + .AutoWrapText(true) + ]; + + return ErrorWidget; + } +} + +TSharedRef FFLESHEditor::CreatePhysicsSettingsWidget() +{ + UE_LOG(LogFLESHEditor, Log, TEXT("Creating Physics Settings widget")); + + try + { + // Create a placeholder widget for now + TSharedRef Widget = SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + .Padding(FMargin(4.0f)) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0, 0, 0, 4) + [ + SNew(STextBlock) + .Text(LOCTEXT("PhysicsSettingsTitle", "Physics Settings")) + .Font(FCoreStyle::GetDefaultFontStyle("Bold", 12)) + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0, 0, 0, 4) + [ + SNew(STextBlock) + .Text(LOCTEXT("PhysicsSettingsDescription", "Configure physics properties for dismemberment")) + .AutoWrapText(true) + ] + + SVerticalBox::Slot() + .FillHeight(1.0f) + [ + SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.DarkGroupBorder")) + .Padding(FMargin(4.0f)) + [ + SNew(STextBlock) + .Text(LOCTEXT("PhysicsSettingsImplementation", "Physics Settings implementation will be added here")) + .AutoWrapText(true) + ] + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0, 4, 0, 0) + [ + SNew(SButton) + .Text(LOCTEXT("ApplyPhysicsButton", "Apply Physics")) + .ToolTipText(LOCTEXT("ApplyPhysicsTooltip", "Apply physics settings to the mesh")) + .OnClicked_Lambda([]() { return FReply::Handled(); }) + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0, 4, 0, 0) + [ + SNew(SButton) + .Text(LOCTEXT("ResetPhysicsButton", "Reset Physics")) + .ToolTipText(LOCTEXT("ResetPhysicsTooltip", "Reset physics settings to default")) + .OnClicked_Lambda([]() { return FReply::Handled(); }) + ] + ]; + + // Store the widget in the class member + PhysicsSettingsWidget = Widget; + + return Widget; + } + catch (const std::exception& e) + { + UE_LOG(LogFLESHEditor, Error, TEXT("Exception creating Physics Settings widget: %s"), UTF8_TO_TCHAR(e.what())); + + // Create an error widget + TSharedRef ErrorWidget = SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + .Padding(FMargin(4.0f)) + [ + SNew(STextBlock) + .Text(FText::Format(LOCTEXT("PhysicsSettingsWidgetError", "Error creating Physics Settings widget: {0}"), FText::FromString(UTF8_TO_TCHAR(e.what())))) + .AutoWrapText(true) + ]; + + return ErrorWidget; + } + catch (...) + { + UE_LOG(LogFLESHEditor, Error, TEXT("Unknown exception creating Physics Settings widget")); + + // Create an error widget + TSharedRef ErrorWidget = SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + .Padding(FMargin(4.0f)) + [ + SNew(STextBlock) + .Text(LOCTEXT("PhysicsSettingsWidgetUnknownError", "Unknown error creating Physics Settings widget")) + .AutoWrapText(true) + ]; + + return ErrorWidget; + } +} + +// Create command list +void FFLESHEditor::CreateCommandList() +{ + UE_LOG(LogFLESHEditor, Log, TEXT("Creating command list")); + + // Make sure the command list is valid + if (!CommandList.IsValid()) + { + CommandList = MakeShareable(new FUICommandList); + } + + // Bind commands + const FFLESHEditorCommands& Commands = FFLESHEditorCommands::Get(); + + // Example command binding + CommandList->MapAction( + Commands.OpenDismembermentGraphEditor, + FExecuteAction::CreateSP(this, &FFLESHEditor::OnOpenDismembermentGraphEditor), + FCanExecuteAction()); +} + +// Extend toolbar with custom buttons +void FFLESHEditor::ExtendToolbar() +{ + UE_LOG(LogFLESHEditor, Log, TEXT("Extending toolbar")); + + struct Local + { + static void FillToolbar(FToolBarBuilder& ToolbarBuilder) + { + ToolbarBuilder.BeginSection("FLESH"); + { + ToolbarBuilder.AddToolBarButton(FFLESHEditorCommands::Get().OpenDismembermentGraphEditor, + NAME_None, + LOCTEXT("DismembermentGraphButtonLabel", "Dismemberment Graph"), + LOCTEXT("DismembermentGraphButtonTooltip", "Open the Dismemberment Graph Editor"), + FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.Blueprint")); + } + ToolbarBuilder.EndSection(); + } + }; + + TSharedPtr ToolbarExtender = MakeShareable(new FExtender); + + ToolbarExtender->AddToolBarExtension( + "Asset", + EExtensionHook::After, + CommandList, + FToolBarExtensionDelegate::CreateStatic(&Local::FillToolbar) + ); + + AddToolbarExtender(ToolbarExtender); +} + +// Command handlers +void FFLESHEditor::OnOpenDismembermentGraphEditor() +{ + UE_LOG(LogFLESHEditor, Log, TEXT("Opening Dismemberment Graph Editor")); + + // Implementation will be added in the future +} + +// Error handling method +void FFLESHEditor::HandleEditorError(const FText& ErrorMessage) +{ + UE_LOG(LogFLESHEditor, Error, TEXT("%s"), *ErrorMessage.ToString()); + + // Show a message dialog + FMessageDialog::Open(EAppMsgType::Ok, ErrorMessage); +} + +// Check if editor is initialized +bool FFLESHEditor::IsEditorInitialized() +{ + return bIsInitialized; +} + +// FAssetEditorToolkit interface void FFLESHEditor::RegisterTabSpawners(const TSharedRef& InTabManager) { - WorkspaceMenuCategory = InTabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("WorkspaceMenu_FLESHEditor", "FLESH Editor")); - auto WorkspaceMenuCategoryRef = WorkspaceMenuCategory.ToSharedRef(); - FAssetEditorToolkit::RegisterTabSpawners(InTabManager); + // Register viewport tab InTabManager->RegisterTabSpawner(ViewportTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_Viewport)) .SetDisplayName(LOCTEXT("ViewportTab", "Viewport")) - .SetGroup(WorkspaceMenuCategoryRef) + .SetGroup(WorkspaceMenuCategory.ToSharedRef()) .SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Viewports")); + // Register details tab InTabManager->RegisterTabSpawner(DetailsTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_Details)) .SetDisplayName(LOCTEXT("DetailsTab", "Details")) - .SetGroup(WorkspaceMenuCategoryRef) + .SetGroup(WorkspaceMenuCategory.ToSharedRef()) .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")); + // Register layer system tab + InTabManager->RegisterTabSpawner(LayerSystemTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_LayerSystem)) + .SetDisplayName(LOCTEXT("LayerSystemTab", "Layer System")) + .SetGroup(WorkspaceMenuCategory.ToSharedRef()) + .SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Layers")); - InTabManager->RegisterTabSpawner(AssetBrowserTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_AssetBrowser)) - .SetDisplayName(LOCTEXT("AssetBrowserTab", "Asset Browser")) - .SetGroup(WorkspaceMenuCategoryRef) - .SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "ContentBrowser.TabIcon")); + // Register physics settings tab + InTabManager->RegisterTabSpawner(PhysicsSettingsTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_PhysicsSettings)) + .SetDisplayName(LOCTEXT("PhysicsSettingsTab", "Physics Settings")) + .SetGroup(WorkspaceMenuCategory.ToSharedRef()) + .SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.WorldSettings")); } void FFLESHEditor::UnregisterTabSpawners(const TSharedRef& InTabManager) { FAssetEditorToolkit::UnregisterTabSpawners(InTabManager); - // Unregister tab spawners + // Unregister all tabs InTabManager->UnregisterTabSpawner(ViewportTabId); InTabManager->UnregisterTabSpawner(DetailsTabId); - InTabManager->UnregisterTabSpawner(AssetBrowserTabId); - InTabManager->UnregisterTabSpawner(MatrixEditorTabId); - InTabManager->UnregisterTabSpawner(GraphEditorTabId); - InTabManager->UnregisterTabSpawner(ToolbarTabId); + InTabManager->UnregisterTabSpawner(LayerSystemTabId); + InTabManager->UnregisterTabSpawner(PhysicsSettingsTabId); } FName FFLESHEditor::GetToolkitFName() const @@ -235,1468 +808,17 @@ FName FFLESHEditor::GetToolkitFName() const FText FFLESHEditor::GetBaseToolkitName() const { - return LOCTEXT("FLESHEditorAppLabel", "FLESH Editor"); + return LOCTEXT("AppLabel", "FLESH Editor"); } FString FFLESHEditor::GetWorldCentricTabPrefix() const { - return TEXT("FLESH "); + return LOCTEXT("WorldCentricTabPrefix", "FLESH ").ToString(); } 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); + return FLinearColor(0.7f, 0.0f, 0.0f, 0.5f); // Dark red color } #undef LOCTEXT_NAMESPACE diff --git a/Source/FLESHEditor/Public/DismembermentEditor.h b/Source/FLESHEditor/Public/DismembermentEditor.h index deb6092..d26900e 100644 --- a/Source/FLESHEditor/Public/DismembermentEditor.h +++ b/Source/FLESHEditor/Public/DismembermentEditor.h @@ -3,6 +3,8 @@ #include "CoreMinimal.h" #include "Toolkits/AssetEditorToolkit.h" #include "EditorUndoClient.h" +#include "BooleanCutTool.h" +#include "AnatomicalLayerSystem.h" class USkeletalMesh; class SDockTab; @@ -12,48 +14,116 @@ class SBorder; /** * Dismemberment System Editor * Provides real-time boolean cutting and multi-layer system editing functionality + * Allows users to create and edit anatomical layer structures for skeletal meshes + * Supports physics settings and effect previews */ class FDismembermentEditor : public FAssetEditorToolkit, public FEditorUndoClient { public: + /** Constructor */ FDismembermentEditor(); + + /** Destructor */ virtual ~FDismembermentEditor(); + /** + * Initialize the dismemberment editor + * @param Mode - Toolkit mode + * @param InitToolkitHost - Toolkit host + * @param InSkeletalMesh - Skeletal mesh to edit + */ void InitDismembermentEditor(const EToolkitMode::Type Mode, const TSharedPtr& InitToolkitHost, USkeletalMesh* InSkeletalMesh); + /** Get toolkit name */ virtual FName GetToolkitFName() const override; + + /** Get base toolkit name */ virtual FText GetBaseToolkitName() const override; + + /** Get world centric tab prefix */ virtual FString GetWorldCentricTabPrefix() const override; + + /** Get world centric tab color scale */ virtual FLinearColor GetWorldCentricTabColorScale() const override; + /** Post undo handler */ virtual void PostUndo(bool bSuccess) override; + + /** Post redo handler */ virtual void PostRedo(bool bSuccess) override; + /** Get current skeletal mesh being edited */ + USkeletalMesh* GetSkeletalMesh() const { return SkeletalMesh; } + private: + /** Create editor layout */ void CreateEditorLayout(); + + /** Create editor toolbar */ void CreateEditorToolbar(); - void RegisterTabSpawners(const TSharedRef& InTabManager); - void UnregisterTabSpawners(const TSharedRef& InTabManager); + /** Register tab spawners */ + virtual void RegisterTabSpawners(const TSharedRef& InTabManager) override; + /** Unregister tab spawners */ + virtual void UnregisterTabSpawners(const TSharedRef& InTabManager) override; + + /** Spawn viewport tab */ TSharedRef SpawnTab_Viewport(const FSpawnTabArgs& Args); + + /** Spawn details tab */ TSharedRef SpawnTab_Details(const FSpawnTabArgs& Args); + + /** Spawn layer system tab */ TSharedRef SpawnTab_LayerSystem(const FSpawnTabArgs& Args); + + /** Spawn physics settings tab */ TSharedRef SpawnTab_PhysicsSettings(const FSpawnTabArgs& Args); + /** Perform boolean cut operation */ void PerformBooleanCut(); + + /** Add new anatomical layer */ void AddNewLayer(); + + /** Save edits */ void SaveEdits(); + + /** Preview effects */ void PreviewEffects(); private: + /** Current skeletal mesh being edited */ USkeletalMesh* SkeletalMesh; + /** Viewport widget */ TSharedPtr ViewportWidget; + + /** Details widget */ TSharedPtr DetailsWidget; + + /** Layer system widget */ TSharedPtr LayerSystemWidget; + + /** Physics settings widget */ TSharedPtr PhysicsSettingsWidget; + /** Boolean cut tool */ + UBooleanCutTool* BooleanCutTool; + + /** Anatomical layer system */ + UAnatomicalLayerSystem* LayerSystem; + + /** Current cut plane */ + FCutPlane CurrentCutPlane; + + /** Selected bone name for cutting */ + FName SelectedBoneName; + + /** Flag to create cap mesh */ + bool bCreateCapMesh; + + /** Tab ID constants */ static const FName ViewportTabId; static const FName DetailsTabId; static const FName LayerSystemTabId; diff --git a/Source/FLESHEditor/Public/DismembermentGraph/DismembermentGraph.h b/Source/FLESHEditor/Public/DismembermentGraph/DismembermentGraph.h index 16f6620..1253a35 100644 --- a/Source/FLESHEditor/Public/DismembermentGraph/DismembermentGraph.h +++ b/Source/FLESHEditor/Public/DismembermentGraph/DismembermentGraph.h @@ -19,4 +19,4 @@ public: // The asset that owns this graph UPROPERTY() TObjectPtr OwningAsset; -}; \ No newline at end of file +}; diff --git a/Source/FLESHEditor/Public/DismembermentGraph/DismembermentGraphNodeBoneSelect.h b/Source/FLESHEditor/Public/DismembermentGraph/DismembermentGraphNodeBoneSelect.h index 15b6637..366d124 100644 --- a/Source/FLESHEditor/Public/DismembermentGraph/DismembermentGraphNodeBoneSelect.h +++ b/Source/FLESHEditor/Public/DismembermentGraph/DismembermentGraphNodeBoneSelect.h @@ -37,4 +37,4 @@ public: virtual void CompileNode(class FDismembermentCompiler* Compiler) override; virtual void ExecuteNode(class FDismembermentExecutor* Executor) override; // End of UDismembermentGraphNode interface -}; \ No newline at end of file +}; diff --git a/Source/FLESHEditor/Public/FLESHEditor.h b/Source/FLESHEditor/Public/FLESHEditor.h index 25cdc81..926d558 100644 --- a/Source/FLESHEditor/Public/FLESHEditor.h +++ b/Source/FLESHEditor/Public/FLESHEditor.h @@ -4,6 +4,8 @@ #include "Toolkits/AssetEditorToolkit.h" #include "Widgets/Docking/SDockTab.h" #include "BooleanCutTool.h" +#include "EditorUndoClient.h" +#include "Logging/LogMacros.h" class SDockTab; class SGraphEditor; @@ -13,6 +15,10 @@ class SMatrixInputWidget; class FFLESHViewportClient; class FSceneViewport; class UVisceraNodeObject; +class FDismembermentEditor; + +// Define log category +DECLARE_LOG_CATEGORY_EXTERN(LogFLESHEditor, Log, All); /** * Bone tree item structure @@ -181,7 +187,7 @@ struct FVisceraNodeItem : public TSharedFromThis * FLESH Main Editor * Provides the main editing functionality for the dismemberment system */ -class FLESHEDITOR_API FFLESHEditor : public FAssetEditorToolkit +class FLESHEDITOR_API FFLESHEditor : public FAssetEditorToolkit, public FEditorUndoClient { public: FFLESHEditor(); @@ -198,9 +204,27 @@ public: virtual FString GetWorldCentricTabPrefix() const override; virtual FLinearColor GetWorldCentricTabColorScale() const override; // End of FAssetEditorToolkit interface - + + // FEditorUndoClient interface + virtual void PostUndo(bool bSuccess) override; + virtual void PostRedo(bool bSuccess) override; + // Open the editor - static void OpenEditor(); + void OpenEditor(); + + // Get whether the editor is initialized + bool IsEditorInitialized(); + + // Get the editing object + UObject* GetEditingObject() const { return EditingObject; } + + // Add DismembermentEditor related tab spawners + TSharedRef SpawnTab_LayerSystem(const FSpawnTabArgs& Args); + TSharedRef SpawnTab_PhysicsSettings(const FSpawnTabArgs& Args); + + // Create DismembermentEditor related widgets + TSharedRef CreateLayerSystemWidget(); + TSharedRef CreatePhysicsSettingsWidget(); private: // Tab spawners @@ -232,6 +256,9 @@ private: // Create command list void CreateCommandList(); + // Extend toolbar with custom buttons + void ExtendToolbar(); + // Generate bone tree row TSharedRef OnGenerateBoneRow(TSharedPtr Item, const TSharedRef& OwnerTable); @@ -338,4 +365,17 @@ private: static const FName MatrixEditorTabId; static const FName GraphEditorTabId; static const FName ToolbarTabId; + static const FName LayerSystemTabId; + static const FName PhysicsSettingsTabId; + static const FName DismembermentGraphTabId; + + // Is the editor initialized? + bool bIsInitialized; + + // New DismembermentEditor related Widgets + TSharedPtr LayerSystemWidget; + TSharedPtr PhysicsSettingsWidget; + + // Error handling method + void HandleEditorError(const FText& ErrorMessage); };