// Copyright 2016 Mookie. All Rights Reserved. #include "EBJsonImportExportTool.h" #include "Widgets/Layout/SBorder.h" #include "Widgets/Layout/SBox.h" #include "Widgets/Layout/SScrollBox.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/Input/SButton.h" #include "Widgets/Layout/SUniformGridPanel.h" #include "Framework/Application/SlateApplication.h" #include "DesktopPlatformModule.h" #include "Json.h" #include "Engine/Engine.h" #include "AssetToolsModule.h" #include "ContentBrowserModule.h" #include "IContentBrowserSingleton.h" #include "Framework/Docking/TabManager.h" #include "WorkspaceMenuStructure.h" #include "WorkspaceMenuStructureModule.h" #include "ToolMenus.h" #include "HAL/IConsoleManager.h" const FName FEBJsonImportExportTool::ImportExportTabName("EBJsonImportExport"); // Console command to open the tool static FAutoConsoleCommand OpenJsonToolCommand( TEXT("EasyBallistics.OpenJsonTool"), TEXT("Opens the EasyBallistics JSON Import/Export tool"), FConsoleCommandDelegate::CreateStatic(&FEBJsonImportExportTool::OpenImportExportWindow) ); void SEBJsonImportExportTool::Construct(const FArguments& InArgs) { ChildSlot [ SNew(SBorder) .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) [ SNew(SScrollBox) + SScrollBox::Slot() [ SNew(SBox) .Padding(10.0f) [ SNew(SVerticalBox) // Title + SVerticalBox::Slot() .AutoHeight() .Padding(0, 0, 0, 20) [ SNew(STextBlock) .Text(FText::FromString("EasyBallistics JSON Import/Export Tool")) .Font(FCoreStyle::GetDefaultFontStyle("Bold", 16)) .Justification(ETextJustify::Center) ] // Bullet Properties Section + SVerticalBox::Slot() .AutoHeight() .Padding(0, 0, 0, 15) [ SNew(SBorder) .BorderImage(FAppStyle::GetBrush("DetailsView.CategoryTop")) .Padding(10) [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() [ SNew(STextBlock) .Text(FText::FromString("Bullet Properties")) .Font(FCoreStyle::GetDefaultFontStyle("Bold", 14)) ] + SVerticalBox::Slot() .AutoHeight() .Padding(0, 10, 0, 0) [ SNew(SUniformGridPanel) .SlotPadding(5) + SUniformGridPanel::Slot(0, 0) [ SNew(SButton) .Text(FText::FromString("Export Bullet Properties")) .OnClicked(this, &SEBJsonImportExportTool::OnExportBulletPropertiesClicked) ] + SUniformGridPanel::Slot(1, 0) [ SNew(SButton) .Text(FText::FromString("Import Bullet Properties")) .OnClicked(this, &SEBJsonImportExportTool::OnImportBulletPropertiesClicked) ] ] ] ] // Material Response Section + SVerticalBox::Slot() .AutoHeight() .Padding(0, 0, 0, 15) [ SNew(SBorder) .BorderImage(FAppStyle::GetBrush("DetailsView.CategoryTop")) .Padding(10) [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() [ SNew(STextBlock) .Text(FText::FromString("Material Response Maps")) .Font(FCoreStyle::GetDefaultFontStyle("Bold", 14)) ] + SVerticalBox::Slot() .AutoHeight() .Padding(0, 10, 0, 0) [ SNew(SUniformGridPanel) .SlotPadding(5) + SUniformGridPanel::Slot(0, 0) [ SNew(SButton) .Text(FText::FromString("Export Material Response")) .OnClicked(this, &SEBJsonImportExportTool::OnExportMaterialResponseClicked) ] + SUniformGridPanel::Slot(1, 0) [ SNew(SButton) .Text(FText::FromString("Import Material Response")) .OnClicked(this, &SEBJsonImportExportTool::OnImportMaterialResponseClicked) ] ] ] ] // Status Section + SVerticalBox::Slot() .AutoHeight() .Padding(0, 20, 0, 0) [ SNew(SBorder) .BorderImage(FAppStyle::GetBrush("DetailsView.CategoryBottom")) .Padding(10) [ SAssignNew(StatusText, STextBlock) .Text(FText::FromString("Ready")) .AutoWrapText(true) ] ] ] ] ] ]; } FReply SEBJsonImportExportTool::OnExportBulletPropertiesClicked() { FString Filename; if (SaveFileDialog("Export Bullet Properties", "", "JSON Files (*.json)|*.json", Filename)) { // For demo purposes, create default bullet properties FMathematicalBulletProperties DefaultProperties; TSharedPtr JsonObject = BulletPropertiesToJson(DefaultProperties); FString OutputString; TSharedRef> Writer = TJsonWriterFactory<>::Create(&OutputString); FJsonSerializer::Serialize(JsonObject.ToSharedRef(), Writer); if (FFileHelper::SaveStringToFile(OutputString, *Filename)) { UpdateStatus(FString::Printf(TEXT("Successfully exported bullet properties to: %s"), *Filename)); } else { UpdateStatus(TEXT("Failed to save bullet properties file.")); } } return FReply::Handled(); } FReply SEBJsonImportExportTool::OnImportBulletPropertiesClicked() { FString Filename; if (OpenFileDialog("Import Bullet Properties", "", "JSON Files (*.json)|*.json", Filename)) { FString FileContent; if (FFileHelper::LoadFileToString(FileContent, *Filename)) { TSharedPtr JsonObject; TSharedRef> Reader = TJsonReaderFactory<>::Create(FileContent); if (FJsonSerializer::Deserialize(Reader, JsonObject) && JsonObject.IsValid()) { FMathematicalBulletProperties ImportedProperties; if (JsonToBulletProperties(JsonObject, ImportedProperties)) { UpdateStatus(FString::Printf(TEXT("Successfully imported bullet properties from: %s"), *Filename)); // Here you would typically create a new asset or update an existing one } else { UpdateStatus(TEXT("Failed to parse bullet properties from JSON.")); } } else { UpdateStatus(TEXT("Failed to parse JSON file.")); } } else { UpdateStatus(TEXT("Failed to load file.")); } } return FReply::Handled(); } FReply SEBJsonImportExportTool::OnExportMaterialResponseClicked() { FString Filename; if (SaveFileDialog("Export Material Response", "", "JSON Files (*.json)|*.json", Filename)) { // For demo purposes, create default material response FEBMaterialResponseMapEntry DefaultEntry; TSharedPtr JsonObject = MaterialResponseToJson(DefaultEntry); FString OutputString; TSharedRef> Writer = TJsonWriterFactory<>::Create(&OutputString); FJsonSerializer::Serialize(JsonObject.ToSharedRef(), Writer); if (FFileHelper::SaveStringToFile(OutputString, *Filename)) { UpdateStatus(FString::Printf(TEXT("Successfully exported material response to: %s"), *Filename)); } else { UpdateStatus(TEXT("Failed to save material response file.")); } } return FReply::Handled(); } FReply SEBJsonImportExportTool::OnImportMaterialResponseClicked() { FString Filename; if (OpenFileDialog("Import Material Response", "", "JSON Files (*.json)|*.json", Filename)) { FString FileContent; if (FFileHelper::LoadFileToString(FileContent, *Filename)) { TSharedPtr JsonObject; TSharedRef> Reader = TJsonReaderFactory<>::Create(FileContent); if (FJsonSerializer::Deserialize(Reader, JsonObject) && JsonObject.IsValid()) { FEBMaterialResponseMapEntry ImportedEntry; if (JsonToMaterialResponse(JsonObject, ImportedEntry)) { UpdateStatus(FString::Printf(TEXT("Successfully imported material response from: %s"), *Filename)); // Here you would typically create a new asset or update an existing one } else { UpdateStatus(TEXT("Failed to parse material response from JSON.")); } } else { UpdateStatus(TEXT("Failed to parse JSON file.")); } } else { UpdateStatus(TEXT("Failed to load file.")); } } return FReply::Handled(); } bool SEBJsonImportExportTool::OpenFileDialog(const FString& DialogTitle, const FString& DefaultPath, const FString& FileTypes, FString& OutFilename) { IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); if (DesktopPlatform) { TArray OutFilenames; bool bOpened = DesktopPlatform->OpenFileDialog( FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr), DialogTitle, DefaultPath, TEXT(""), FileTypes, EFileDialogFlags::None, OutFilenames ); if (bOpened && OutFilenames.Num() > 0) { OutFilename = OutFilenames[0]; return true; } } return false; } bool SEBJsonImportExportTool::SaveFileDialog(const FString& DialogTitle, const FString& DefaultPath, const FString& FileTypes, FString& OutFilename) { IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); if (DesktopPlatform) { TArray OutFilenames; bool bOpened = DesktopPlatform->SaveFileDialog( FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr), DialogTitle, DefaultPath, TEXT(""), FileTypes, EFileDialogFlags::None, OutFilenames ); if (bOpened && OutFilenames.Num() > 0) { OutFilename = OutFilenames[0]; return true; } } return false; } TSharedPtr SEBJsonImportExportTool::BulletPropertiesToJson(const FMathematicalBulletProperties& Properties) { TSharedPtr JsonObject = MakeShareable(new FJsonObject); // Basic Properties JsonObject->SetNumberField("GrainWeight", Properties.GrainWeight); JsonObject->SetNumberField("DiameterInches", Properties.DiameterInches); JsonObject->SetNumberField("LengthInches", Properties.LengthInches); JsonObject->SetNumberField("BulletType", static_cast(Properties.BulletType)); JsonObject->SetNumberField("BulletMaterial", static_cast(Properties.BulletMaterial)); // Ballistics JsonObject->SetNumberField("BallisticCoefficientG1", Properties.BallisticCoefficientG1); JsonObject->SetNumberField("BallisticCoefficientG7", Properties.BallisticCoefficientG7); JsonObject->SetBoolField("UseG7Model", Properties.UseG7Model); JsonObject->SetNumberField("SectionalDensity", Properties.SectionalDensity); // Penetration JsonObject->SetNumberField("BulletHardness", Properties.BulletHardness); JsonObject->SetNumberField("PenetrationEnergyThreshold", Properties.PenetrationEnergyThreshold); JsonObject->SetNumberField("ExpansionVelocityThreshold", Properties.ExpansionVelocityThreshold); JsonObject->SetNumberField("MaxExpansionMultiplier", Properties.MaxExpansionMultiplier); return JsonObject; } bool SEBJsonImportExportTool::JsonToBulletProperties(const TSharedPtr& JsonObject, FMathematicalBulletProperties& OutProperties) { if (!JsonObject.IsValid()) { return false; } // Basic Properties OutProperties.GrainWeight = JsonObject->GetNumberField(TEXT("GrainWeight")); OutProperties.DiameterInches = JsonObject->GetNumberField(TEXT("DiameterInches")); OutProperties.LengthInches = JsonObject->GetNumberField(TEXT("LengthInches")); OutProperties.BulletType = static_cast(JsonObject->GetIntegerField(TEXT("BulletType"))); OutProperties.BulletMaterial = static_cast(JsonObject->GetIntegerField(TEXT("BulletMaterial"))); // Ballistics OutProperties.BallisticCoefficientG1 = JsonObject->GetNumberField(TEXT("BallisticCoefficientG1")); OutProperties.BallisticCoefficientG7 = JsonObject->GetNumberField(TEXT("BallisticCoefficientG7")); OutProperties.UseG7Model = JsonObject->GetBoolField(TEXT("UseG7Model")); OutProperties.SectionalDensity = JsonObject->GetNumberField(TEXT("SectionalDensity")); // Penetration OutProperties.BulletHardness = JsonObject->GetNumberField(TEXT("BulletHardness")); OutProperties.PenetrationEnergyThreshold = JsonObject->GetNumberField(TEXT("PenetrationEnergyThreshold")); OutProperties.ExpansionVelocityThreshold = JsonObject->GetNumberField(TEXT("ExpansionVelocityThreshold")); OutProperties.MaxExpansionMultiplier = JsonObject->GetNumberField(TEXT("MaxExpansionMultiplier")); return true; } TSharedPtr SEBJsonImportExportTool::MaterialResponseToJson(const FEBMaterialResponseMapEntry& Entry) { TSharedPtr JsonObject = MakeShareable(new FJsonObject); // Artistic Properties JsonObject->SetNumberField("PenTraceType", static_cast(Entry.PenTraceType)); JsonObject->SetBoolField("NeverPenetrate", Entry.NeverPenetrate); JsonObject->SetNumberField("PenetrationDepthMultiplier", Entry.PenetrationDepthMultiplier); JsonObject->SetNumberField("PenetrationNormalization", Entry.PenetrationNormalization); JsonObject->SetNumberField("PenetrationNormalizationGrazing", Entry.PenetrationNormalizationGrazing); JsonObject->SetNumberField("PenetrationEntryAngleSpread", Entry.PenetrationEntryAngleSpread); JsonObject->SetNumberField("PenetrationExitAngleSpread", Entry.PenetrationExitAngleSpread); JsonObject->SetBoolField("NeverRicochet", Entry.NeverRicochet); JsonObject->SetNumberField("RicochetProbabilityMultiplier", Entry.RicochetProbabilityMultiplier); JsonObject->SetNumberField("RicochetRestitution", Entry.RicochetRestitution); JsonObject->SetNumberField("RicochetRestitutionInfluence", Entry.RicochetRestitutionInfluence); JsonObject->SetNumberField("RicochetFriction", Entry.RicochetFriction); JsonObject->SetNumberField("RicochetFrictionInfluence", Entry.RicochetFrictionInfluence); JsonObject->SetNumberField("RicochetSpread", Entry.RicochetSpread); // Spalling Properties JsonObject->SetBoolField("EnableSpalling", Entry.EnableSpalling); JsonObject->SetNumberField("SpallVelocityThreshold", Entry.SpallVelocityThreshold); JsonObject->SetNumberField("SpallFragmentCount", Entry.SpallFragmentCount); JsonObject->SetNumberField("SpallSpreadAngle", Entry.SpallSpreadAngle); JsonObject->SetNumberField("SpallVelocityMultiplier", Entry.SpallVelocityMultiplier); JsonObject->SetNumberField("SpallMassMultiplier", Entry.SpallMassMultiplier); // Mathematical Properties JsonObject->SetBoolField("UseMathematicalProperties", Entry.UseMathematicalProperties); if (Entry.UseMathematicalProperties) { TSharedPtr MathProps = MaterialPropertiesToJson(Entry.MathematicalProperties); JsonObject->SetObjectField("MathematicalProperties", MathProps); } return JsonObject; } bool SEBJsonImportExportTool::JsonToMaterialResponse(const TSharedPtr& JsonObject, FEBMaterialResponseMapEntry& OutEntry) { if (!JsonObject.IsValid()) { return false; } // Artistic Properties OutEntry.PenTraceType = static_cast(JsonObject->GetIntegerField(TEXT("PenTraceType"))); OutEntry.NeverPenetrate = JsonObject->GetBoolField(TEXT("NeverPenetrate")); OutEntry.PenetrationDepthMultiplier = JsonObject->GetNumberField(TEXT("PenetrationDepthMultiplier")); OutEntry.PenetrationNormalization = JsonObject->GetNumberField(TEXT("PenetrationNormalization")); OutEntry.PenetrationNormalizationGrazing = JsonObject->GetNumberField(TEXT("PenetrationNormalizationGrazing")); OutEntry.PenetrationEntryAngleSpread = JsonObject->GetNumberField(TEXT("PenetrationEntryAngleSpread")); OutEntry.PenetrationExitAngleSpread = JsonObject->GetNumberField(TEXT("PenetrationExitAngleSpread")); OutEntry.NeverRicochet = JsonObject->GetBoolField(TEXT("NeverRicochet")); OutEntry.RicochetProbabilityMultiplier = JsonObject->GetNumberField(TEXT("RicochetProbabilityMultiplier")); OutEntry.RicochetRestitution = JsonObject->GetNumberField(TEXT("RicochetRestitution")); OutEntry.RicochetRestitutionInfluence = JsonObject->GetNumberField(TEXT("RicochetRestitutionInfluence")); OutEntry.RicochetFriction = JsonObject->GetNumberField(TEXT("RicochetFriction")); OutEntry.RicochetFrictionInfluence = JsonObject->GetNumberField(TEXT("RicochetFrictionInfluence")); OutEntry.RicochetSpread = JsonObject->GetNumberField(TEXT("RicochetSpread")); // Spalling Properties OutEntry.EnableSpalling = JsonObject->GetBoolField(TEXT("EnableSpalling")); OutEntry.SpallVelocityThreshold = JsonObject->GetNumberField(TEXT("SpallVelocityThreshold")); OutEntry.SpallFragmentCount = JsonObject->GetIntegerField(TEXT("SpallFragmentCount")); OutEntry.SpallSpreadAngle = JsonObject->GetNumberField(TEXT("SpallSpreadAngle")); OutEntry.SpallVelocityMultiplier = JsonObject->GetNumberField(TEXT("SpallVelocityMultiplier")); OutEntry.SpallMassMultiplier = JsonObject->GetNumberField(TEXT("SpallMassMultiplier")); // Mathematical Properties OutEntry.UseMathematicalProperties = JsonObject->GetBoolField(TEXT("UseMathematicalProperties")); if (OutEntry.UseMathematicalProperties) { const TSharedPtr* MathPropsPtr; if (JsonObject->TryGetObjectField(TEXT("MathematicalProperties"), MathPropsPtr)) { JsonToMaterialProperties(*MathPropsPtr, OutEntry.MathematicalProperties); } } return true; } TSharedPtr SEBJsonImportExportTool::MaterialPropertiesToJson(const FMathematicalMaterialProperties& Properties) { TSharedPtr JsonObject = MakeShareable(new FJsonObject); // Material Properties JsonObject->SetNumberField("DensityGPerCm3", Properties.DensityGPerCm3); JsonObject->SetNumberField("MaterialHardness", Properties.MaterialHardness); JsonObject->SetNumberField("TensileStrengthMPa", Properties.TensileStrengthMPa); JsonObject->SetNumberField("YieldStrengthMPa", Properties.YieldStrengthMPa); JsonObject->SetNumberField("ElasticModulusGPa", Properties.ElasticModulusGPa); // Ballistic Properties JsonObject->SetNumberField("BallisticLimitVelocity", Properties.BallisticLimitVelocity); JsonObject->SetNumberField("PerforationCoefficient", Properties.PerforationCoefficient); JsonObject->SetNumberField("EnergyAbsorptionCoefficient", Properties.EnergyAbsorptionCoefficient); // Spalling Properties JsonObject->SetBoolField("EnableMathematicalSpalling", Properties.EnableMathematicalSpalling); JsonObject->SetNumberField("SpallStrengthMPa", Properties.SpallStrengthMPa); JsonObject->SetNumberField("CriticalStressFactor", Properties.CriticalStressFactor); JsonObject->SetNumberField("FragmentVelocityEfficiency", Properties.FragmentVelocityEfficiency); JsonObject->SetNumberField("AverageFragmentMassRatio", Properties.AverageFragmentMassRatio); JsonObject->SetNumberField("FragmentSizeExponent", Properties.FragmentSizeExponent); JsonObject->SetNumberField("MaxFragmentDensity", Properties.MaxFragmentDensity); return JsonObject; } bool SEBJsonImportExportTool::JsonToMaterialProperties(const TSharedPtr& JsonObject, FMathematicalMaterialProperties& OutProperties) { if (!JsonObject.IsValid()) { return false; } // Material Properties OutProperties.DensityGPerCm3 = JsonObject->GetNumberField(TEXT("DensityGPerCm3")); OutProperties.MaterialHardness = JsonObject->GetNumberField(TEXT("MaterialHardness")); OutProperties.TensileStrengthMPa = JsonObject->GetNumberField(TEXT("TensileStrengthMPa")); OutProperties.YieldStrengthMPa = JsonObject->GetNumberField(TEXT("YieldStrengthMPa")); OutProperties.ElasticModulusGPa = JsonObject->GetNumberField(TEXT("ElasticModulusGPa")); // Ballistic Properties OutProperties.BallisticLimitVelocity = JsonObject->GetNumberField(TEXT("BallisticLimitVelocity")); OutProperties.PerforationCoefficient = JsonObject->GetNumberField(TEXT("PerforationCoefficient")); OutProperties.EnergyAbsorptionCoefficient = JsonObject->GetNumberField(TEXT("EnergyAbsorptionCoefficient")); // Spalling Properties OutProperties.EnableMathematicalSpalling = JsonObject->GetBoolField(TEXT("EnableMathematicalSpalling")); OutProperties.SpallStrengthMPa = JsonObject->GetNumberField(TEXT("SpallStrengthMPa")); OutProperties.CriticalStressFactor = JsonObject->GetNumberField(TEXT("CriticalStressFactor")); OutProperties.FragmentVelocityEfficiency = JsonObject->GetNumberField(TEXT("FragmentVelocityEfficiency")); OutProperties.AverageFragmentMassRatio = JsonObject->GetNumberField(TEXT("AverageFragmentMassRatio")); OutProperties.FragmentSizeExponent = JsonObject->GetNumberField(TEXT("FragmentSizeExponent")); OutProperties.MaxFragmentDensity = JsonObject->GetNumberField(TEXT("MaxFragmentDensity")); return true; } void SEBJsonImportExportTool::UpdateStatus(const FString& Message) { if (StatusText.IsValid()) { StatusText->SetText(FText::FromString(Message)); } } // FEBJsonImportExportTool implementation void FEBJsonImportExportTool::RegisterMenus() { // Register the tab spawner FGlobalTabmanager::Get()->RegisterNomadTabSpawner(ImportExportTabName, FOnSpawnTab::CreateStatic(&FEBJsonImportExportTool::OnSpawnImportExportTab)) .SetDisplayName(NSLOCTEXT("EasyBallisticsEditor", "ImportExportTabTitle", "Ballistics JSON Import/Export")) .SetMenuType(ETabSpawnerMenuType::Hidden); // Add to main menu bar under Tools UToolMenus* ToolMenus = UToolMenus::Get(); { UToolMenu* MainMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.MainMenu.Tools"); { FToolMenuSection& Section = MainMenu->FindOrAddSection("Programming"); Section.AddMenuEntry( "OpenEBJsonImportExport", NSLOCTEXT("EasyBallisticsEditor", "OpenEBJsonImportExport", "Ballistics JSON Import/Export"), NSLOCTEXT("EasyBallisticsEditor", "OpenEBJsonImportExportTooltip", "Open the Ballistics JSON Import/Export tool"), FSlateIcon(), FUIAction(FExecuteAction::CreateStatic(&FEBJsonImportExportTool::OpenImportExportWindow)) ); } } } void FEBJsonImportExportTool::OpenImportExportWindow() { // Ensure the tab spawner is registered if (!FGlobalTabmanager::Get()->HasTabSpawner(ImportExportTabName)) { FGlobalTabmanager::Get()->RegisterNomadTabSpawner(ImportExportTabName, FOnSpawnTab::CreateStatic(&FEBJsonImportExportTool::OnSpawnImportExportTab)) .SetDisplayName(NSLOCTEXT("EasyBallisticsEditor", "ImportExportTabTitle", "Ballistics JSON Import/Export")) .SetMenuType(ETabSpawnerMenuType::Hidden); } FGlobalTabmanager::Get()->TryInvokeTab(ImportExportTabName); } void FEBJsonImportExportTool::UnregisterMenus() { if (FGlobalTabmanager::Get()->HasTabSpawner(ImportExportTabName)) { FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(ImportExportTabName); } } TSharedRef FEBJsonImportExportTool::OnSpawnImportExportTab(const FSpawnTabArgs& SpawnTabArgs) { return SNew(SDockTab) .TabRole(ETabRole::NomadTab) [ SNew(SEBJsonImportExportTool) ]; }