mirror of
https://github.com/4sval/FModel.git
synced 2026-06-21 23:40:12 -05:00
yoinked snooper's imgui export modal
This commit is contained in:
parent
32a12da5d0
commit
05693db228
|
|
@ -1 +1 @@
|
|||
Subproject commit d807df729bda91706598ef2f4531bd9799dfb8ef
|
||||
Subproject commit 4c3332989787d0b57325d160137e9fa61d82a139
|
||||
|
|
@ -11,7 +11,9 @@ using CUE4Parse;
|
|||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
using FModel.Views.Snooper;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog.Events;
|
||||
using Serilog.Sinks.SystemConsole.Themes;
|
||||
using MessageBox = AdonisUI.Controls.MessageBox;
|
||||
using MessageBoxImage = AdonisUI.Controls.MessageBoxImage;
|
||||
|
|
@ -118,10 +120,12 @@ public partial class App
|
|||
.WriteTo.Console(outputTemplate: template, theme: AnsiConsoleTheme.Literate)
|
||||
.WriteTo.File(outputTemplate: template,
|
||||
path: Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Debug-Log-{DateTime.Now:yyyy-MM-dd}.log"))
|
||||
.MinimumLevel.Override("CUE4Parse_Conversion", LogEventLevel.Verbose).WriteTo.Sink(ImGuiSink.Instance)
|
||||
#else
|
||||
.Enrich.With<CallerEnricher>()
|
||||
.WriteTo.File(outputTemplate: template,
|
||||
path: Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Log-{DateTime.Now:yyyy-MM-dd}.log"))
|
||||
.MinimumLevel.Override("CUE4Parse_Conversion", LogEventLevel.Verbose).WriteTo.Sink(ImGuiSink.Instance)
|
||||
#endif
|
||||
.CreateLogger();
|
||||
|
||||
|
|
|
|||
11
FModel/Extensions/LogEventExtensions.cs
Normal file
11
FModel/Extensions/LogEventExtensions.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
using Serilog.Events;
|
||||
|
||||
namespace FModel.Extensions;
|
||||
|
||||
public static class LogEventExtensions
|
||||
{
|
||||
public static string GetContext(this LogEvent log, string propertyName)
|
||||
{
|
||||
return log.Properties.TryGetValue(propertyName, out var value) ? value.ToString().Trim('"') : string.Empty;
|
||||
}
|
||||
}
|
||||
|
|
@ -121,6 +121,9 @@
|
|||
<None Remove="Resources\bone.frag" />
|
||||
<None Remove="Resources\bone.vert" />
|
||||
<None Remove="Resources\collision.vert" />
|
||||
<None Remove="Resources\fa-brands-400.otf" />
|
||||
<None Remove="Resources\fa-solid-900.otf" />
|
||||
<None Remove="Resources\fa-regular-400.otf" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -149,6 +152,9 @@
|
|||
<EmbeddedResource Include="Resources\bone.frag" />
|
||||
<EmbeddedResource Include="Resources\bone.vert" />
|
||||
<EmbeddedResource Include="Resources\collision.vert" />
|
||||
<EmbeddedResource Include="Resources\fa-brands-400.otf" />
|
||||
<EmbeddedResource Include="Resources\fa-solid-900.otf" />
|
||||
<EmbeddedResource Include="Resources\fa-regular-400.otf" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -66,18 +66,69 @@ public class ImGuiController : IDisposable
|
|||
var iniFileNamePtr = Marshal.StringToCoTaskMemUTF8(Path.Combine(UserSettings.Default.OutputDirectory, ".data", "imgui.ini"));
|
||||
io.NativePtr->IniFilename = (byte*)iniFileNamePtr;
|
||||
}
|
||||
|
||||
// If not found, Fallback to default ImGui Font
|
||||
var normalPath = @"C:\Windows\Fonts\segoeui.ttf";
|
||||
var boldPath = @"C:\Windows\Fonts\segoeuib.ttf";
|
||||
var semiBoldPath = @"C:\Windows\Fonts\seguisb.ttf";
|
||||
|
||||
if (File.Exists(normalPath))
|
||||
FontNormal = io.Fonts.AddFontFromFileTTF(normalPath, 16 * DpiScale);
|
||||
if (File.Exists(boldPath))
|
||||
FontBold = io.Fonts.AddFontFromFileTTF(boldPath, 16 * DpiScale);
|
||||
if (File.Exists(semiBoldPath))
|
||||
FontSemiBold = io.Fonts.AddFontFromFileTTF(semiBoldPath, 16 * DpiScale);
|
||||
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
|
||||
var assemblyName = assembly.GetName().Name;
|
||||
byte[] LoadFont(string name)
|
||||
{
|
||||
using var stream = assembly.GetManifestResourceStream($"{assemblyName}.Resources.{name}")
|
||||
?? throw new FileNotFoundException($"Embedded font '{name}' not found.");
|
||||
using var ms = new MemoryStream();
|
||||
stream.CopyTo(ms);
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
var faSolid = LoadFont("fa-solid-900.otf");
|
||||
var faRegular = LoadFont("fa-regular-400.otf");
|
||||
var faBrands = LoadFont("fa-brands-400.otf");
|
||||
|
||||
unsafe
|
||||
{
|
||||
// FA5 icons live in E000–F8FF (brands/regular/solid)
|
||||
var iconRanges = stackalloc ushort[] { 0xe000, 0xf8ff, 0 };
|
||||
|
||||
var cfg = ImGuiNative.ImFontConfig_ImFontConfig();
|
||||
cfg->MergeMode = 1;
|
||||
cfg->PixelSnapH = 1;
|
||||
cfg->GlyphMinAdvanceX = 16f;
|
||||
// FontDataOwnedByAtlas = 0 because we manage the GCHandle lifetime ourselves
|
||||
cfg->FontDataOwnedByAtlas = 0;
|
||||
|
||||
void MergeFontAwesome(byte[] solid, byte[] regular, byte[] brands)
|
||||
{
|
||||
fixed (byte* pSolid = solid)
|
||||
fixed (byte* pRegular = regular)
|
||||
fixed (byte* pBrands = brands)
|
||||
{
|
||||
io.Fonts.AddFontFromMemoryTTF((IntPtr)pSolid, solid.Length, 14, (IntPtr)cfg, (IntPtr)iconRanges);
|
||||
io.Fonts.AddFontFromMemoryTTF((IntPtr)pRegular, regular.Length, 14, (IntPtr)cfg, (IntPtr)iconRanges);
|
||||
io.Fonts.AddFontFromMemoryTTF((IntPtr)pBrands, brands.Length, 14, (IntPtr)cfg, (IntPtr)iconRanges);
|
||||
}
|
||||
}
|
||||
|
||||
// If not found, Fallback to default ImGui Font
|
||||
var normalPath = @"C:\Windows\Fonts\segoeui.ttf";
|
||||
var boldPath = @"C:\Windows\Fonts\segoeuib.ttf";
|
||||
var semiBoldPath = @"C:\Windows\Fonts\seguisb.ttf";
|
||||
|
||||
if (File.Exists(normalPath))
|
||||
{
|
||||
FontNormal = io.Fonts.AddFontFromFileTTF(normalPath, 16 * DpiScale);
|
||||
MergeFontAwesome(faSolid, faRegular, faBrands);
|
||||
}
|
||||
if (File.Exists(boldPath))
|
||||
{
|
||||
FontBold = io.Fonts.AddFontFromFileTTF(boldPath, 16 * DpiScale);
|
||||
MergeFontAwesome(faSolid, faRegular, faBrands);
|
||||
}
|
||||
if (File.Exists(semiBoldPath))
|
||||
{
|
||||
FontSemiBold = io.Fonts.AddFontFromFileTTF(semiBoldPath, 16 * DpiScale);
|
||||
MergeFontAwesome(faSolid, faRegular, faBrands);
|
||||
}
|
||||
|
||||
ImGuiNative.ImFontConfig_destroy(cfg);
|
||||
}
|
||||
|
||||
io.Fonts.AddFontDefault();
|
||||
io.Fonts.Build(); // Build font atlas
|
||||
|
|
|
|||
BIN
FModel/Resources/fa-brands-400.otf
Normal file
BIN
FModel/Resources/fa-brands-400.otf
Normal file
Binary file not shown.
BIN
FModel/Resources/fa-regular-400.otf
Normal file
BIN
FModel/Resources/fa-regular-400.otf
Normal file
Binary file not shown.
BIN
FModel/Resources/fa-solid-900.otf
Normal file
BIN
FModel/Resources/fa-solid-900.otf
Normal file
Binary file not shown.
|
|
@ -4,13 +4,9 @@ using System.IO;
|
|||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using CUE4Parse.UE4.Assets.Exports.Nanite;
|
||||
using CUE4Parse.UE4.Versions;
|
||||
using CUE4Parse_Conversion;
|
||||
using CUE4Parse_Conversion.Animations;
|
||||
using CUE4Parse_Conversion.Meshes;
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
using CUE4Parse_Conversion.UEFormat.Enums;
|
||||
using CUE4Parse_Conversion.Options;
|
||||
using CUE4Parse_Conversion.Writers.UEFormat.Enums;
|
||||
using CUE4Parse.UE4.Lua.unluac;
|
||||
using FModel.Framework;
|
||||
using FModel.ViewModels;
|
||||
|
|
@ -57,26 +53,23 @@ namespace FModel.Settings
|
|||
return endpoint.Overwrite || endpoint.IsValid;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public ExporterOptions ExportOptions => new()
|
||||
public static ExportOptions GetExportOptions()
|
||||
{
|
||||
LodFormat = Default.LodExportFormat,
|
||||
MeshFormat = Default.MeshExportFormat,
|
||||
NaniteMeshFormat = Default.NaniteMeshExportFormat,
|
||||
AnimFormat = Default.MeshExportFormat switch
|
||||
{
|
||||
EMeshFormat.UEFormat => EAnimFormat.UEFormat,
|
||||
_ => EAnimFormat.ActorX
|
||||
},
|
||||
MaterialFormat = Default.MaterialExportFormat,
|
||||
TextureFormat = Default.TextureExportFormat,
|
||||
SocketFormat = Default.SocketExportFormat,
|
||||
CompressionFormat = Default.CompressionFormat,
|
||||
Platform = Default.CurrentDir.TexturePlatform,
|
||||
ExportMorphTargets = Default.SaveMorphTargets,
|
||||
ExportMaterials = Default.SaveEmbeddedMaterials,
|
||||
ExportHdrTexturesAsHdr = Default.SaveHdrTexturesAsHdr
|
||||
};
|
||||
return new ExportOptions(
|
||||
Default.MeshExportFormat,
|
||||
Default.NaniteMeshExportFormat,
|
||||
Default.MeshQuality,
|
||||
Default.CurrentDir.TexturePlatform,
|
||||
Default.TextureExportFormat,
|
||||
100,
|
||||
Default.SaveHdrTexturesAsHdr,
|
||||
Default.MaterialExportFormat,
|
||||
Default.SaveEmbeddedMaterials,
|
||||
Default.SaveMorphTargets,
|
||||
Default.SocketExportFormat,
|
||||
Default.CompressionFormat
|
||||
);
|
||||
}
|
||||
|
||||
private bool _showChangelog = true;
|
||||
public bool ShowChangelog
|
||||
|
|
@ -422,15 +415,22 @@ namespace FModel.Settings
|
|||
set => SetProperty(ref _meshExportFormat, value);
|
||||
}
|
||||
|
||||
private ENaniteMeshFormat _naniteMeshExportFormat = ENaniteMeshFormat.OnlyNaniteLOD;
|
||||
private ENaniteMeshFormat _naniteMeshExportFormat = ENaniteMeshFormat.NaniteOnly;
|
||||
public ENaniteMeshFormat NaniteMeshExportFormat
|
||||
{
|
||||
get => _naniteMeshExportFormat;
|
||||
set => SetProperty(ref _naniteMeshExportFormat, value);
|
||||
}
|
||||
|
||||
private EMaterialFormat _materialExportFormat = EMaterialFormat.FirstLayer;
|
||||
public EMaterialFormat MaterialExportFormat
|
||||
private EMeshQuality _meshQuality = EMeshQuality.Highest;
|
||||
public EMeshQuality MeshQuality
|
||||
{
|
||||
get => _meshQuality;
|
||||
set => SetProperty(ref _meshQuality, value);
|
||||
}
|
||||
|
||||
private EMaterialDepth _materialExportFormat = EMaterialDepth.TopLayerOnly;
|
||||
public EMaterialDepth MaterialExportFormat
|
||||
{
|
||||
get => _materialExportFormat;
|
||||
set => SetProperty(ref _materialExportFormat, value);
|
||||
|
|
@ -457,13 +457,6 @@ namespace FModel.Settings
|
|||
set => SetProperty(ref _compressionFormat, value);
|
||||
}
|
||||
|
||||
private ELodFormat _lodExportFormat = ELodFormat.FirstLod;
|
||||
public ELodFormat LodExportFormat
|
||||
{
|
||||
get => _lodExportFormat;
|
||||
set => SetProperty(ref _lodExportFormat, value);
|
||||
}
|
||||
|
||||
private bool _showSkybox = true;
|
||||
public bool ShowSkybox
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1678,32 +1678,34 @@ public class CUE4ParseViewModel : ViewModel
|
|||
|
||||
private void SaveExport(UObject export, bool updateUi = true)
|
||||
{
|
||||
var toSave = new Exporter(export, UserSettings.Default.ExportOptions);
|
||||
var toSaveDirectory = new DirectoryInfo(UserSettings.Default.ModelDirectory);
|
||||
if (toSave.TryWriteToDir(toSaveDirectory, out var label, out var savedFilePath))
|
||||
{
|
||||
Interlocked.Increment(ref ExportedCount);
|
||||
Log.Information("Successfully saved {FilePath}", savedFilePath);
|
||||
if (updateUi)
|
||||
{
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
FLogger.Text("Successfully saved ", Constants.WHITE);
|
||||
FLogger.Link(label, savedFilePath, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Interlocked.Increment(ref FailedExportCount);
|
||||
Log.Error("{FileName} could not be saved", export.Name);
|
||||
FLogger.Append(ELog.Error, () => FLogger.Text($"Could not save '{export.Name}'", Constants.WHITE, true));
|
||||
}
|
||||
// TODO: export session
|
||||
// var toSave = new Exporter(export, UserSettings.Default.ExportOptions);
|
||||
// var toSaveDirectory = new DirectoryInfo(UserSettings.Default.ModelDirectory);
|
||||
// if (toSave.TryWriteToDir(toSaveDirectory, out var label, out var savedFilePath))
|
||||
// {
|
||||
// Interlocked.Increment(ref ExportedCount);
|
||||
// Log.Information("Successfully saved {FilePath}", savedFilePath);
|
||||
// if (updateUi)
|
||||
// {
|
||||
// FLogger.Append(ELog.Information, () =>
|
||||
// {
|
||||
// FLogger.Text("Successfully saved ", Constants.WHITE);
|
||||
// FLogger.Link(label, savedFilePath, true);
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// Interlocked.Increment(ref FailedExportCount);
|
||||
// Log.Error("{FileName} could not be saved", export.Name);
|
||||
// FLogger.Append(ELog.Error, () => FLogger.Text($"Could not save '{export.Name}'", Constants.WHITE, true));
|
||||
// }
|
||||
}
|
||||
|
||||
private readonly object _rawData = new ();
|
||||
public void ExportData(GameFile entry, bool updateUi = true)
|
||||
{
|
||||
// TODO: export session
|
||||
if (Provider.TrySavePackage(entry, out var assets))
|
||||
{
|
||||
string path = UserSettings.Default.RawDataDirectory;
|
||||
|
|
|
|||
|
|
@ -2,14 +2,12 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using CUE4Parse.UE4.Assets.Exports.Nanite;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Objects.Core.Serialization;
|
||||
using CUE4Parse.UE4.Versions;
|
||||
using CUE4Parse_Conversion.Meshes;
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
using CUE4Parse_Conversion.UEFormat.Enums;
|
||||
using CUE4Parse_Conversion.Options;
|
||||
using CUE4Parse_Conversion.Writers.UEFormat.Enums;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
|
|
@ -137,8 +135,8 @@ public class SettingsViewModel : ViewModel
|
|||
set => SetProperty(ref _selectedCompressionFormat, value);
|
||||
}
|
||||
|
||||
private ELodFormat _selectedLodExportFormat;
|
||||
public ELodFormat SelectedLodExportFormat
|
||||
private EMeshQuality _selectedLodExportFormat;
|
||||
public EMeshQuality SelectedLodExportFormat
|
||||
{
|
||||
get => _selectedLodExportFormat;
|
||||
set => SetProperty(ref _selectedLodExportFormat, value);
|
||||
|
|
@ -151,8 +149,8 @@ public class SettingsViewModel : ViewModel
|
|||
set => SetProperty(ref _selectedNaniteMeshExportFormat, value);
|
||||
}
|
||||
|
||||
private EMaterialFormat _selectedMaterialExportFormat;
|
||||
public EMaterialFormat SelectedMaterialExportFormat
|
||||
private EMaterialDepth _selectedMaterialExportFormat;
|
||||
public EMaterialDepth SelectedMaterialExportFormat
|
||||
{
|
||||
get => _selectedMaterialExportFormat;
|
||||
set => SetProperty(ref _selectedMaterialExportFormat, value);
|
||||
|
|
@ -191,9 +189,9 @@ public class SettingsViewModel : ViewModel
|
|||
public ReadOnlyObservableCollection<EMeshFormat> MeshExportFormats { get; private set; }
|
||||
public ReadOnlyObservableCollection<ESocketFormat> SocketExportFormats { get; private set; }
|
||||
public ReadOnlyObservableCollection<EFileCompressionFormat> CompressionFormats { get; private set; }
|
||||
public ReadOnlyObservableCollection<ELodFormat> LodExportFormats { get; private set; }
|
||||
public ReadOnlyObservableCollection<EMeshQuality> LodExportFormats { get; private set; }
|
||||
public ReadOnlyObservableCollection<ENaniteMeshFormat> NaniteMeshExportFormats { get; private set; }
|
||||
public ReadOnlyObservableCollection<EMaterialFormat> MaterialExportFormats { get; private set; }
|
||||
public ReadOnlyObservableCollection<EMaterialDepth> MaterialExportFormats { get; private set; }
|
||||
public ReadOnlyObservableCollection<ETextureFormat> TextureExportFormats { get; private set; }
|
||||
public ReadOnlyObservableCollection<ETexturePlatform> Platforms { get; private set; }
|
||||
|
||||
|
|
@ -216,9 +214,9 @@ public class SettingsViewModel : ViewModel
|
|||
private EMeshFormat _meshExportFormatSnapshot;
|
||||
private ESocketFormat _socketExportFormatSnapshot;
|
||||
private EFileCompressionFormat _compressionFormatSnapshot;
|
||||
private ELodFormat _lodExportFormatSnapshot;
|
||||
private EMeshQuality _lodExportFormatSnapshot;
|
||||
private ENaniteMeshFormat _naniteMeshExportFormatSnapshot;
|
||||
private EMaterialFormat _materialExportFormatSnapshot;
|
||||
private EMaterialDepth _materialExportFormatSnapshot;
|
||||
private ETextureFormat _textureExportFormatSnapshot;
|
||||
|
||||
private bool _mappingsUpdate = false;
|
||||
|
|
@ -260,7 +258,7 @@ public class SettingsViewModel : ViewModel
|
|||
_meshExportFormatSnapshot = UserSettings.Default.MeshExportFormat;
|
||||
_socketExportFormatSnapshot = UserSettings.Default.SocketExportFormat;
|
||||
_compressionFormatSnapshot = UserSettings.Default.CompressionFormat;
|
||||
_lodExportFormatSnapshot = UserSettings.Default.LodExportFormat;
|
||||
_lodExportFormatSnapshot = UserSettings.Default.MeshQuality;
|
||||
_naniteMeshExportFormatSnapshot = UserSettings.Default.NaniteMeshExportFormat;
|
||||
_materialExportFormatSnapshot = UserSettings.Default.MaterialExportFormat;
|
||||
_textureExportFormatSnapshot = UserSettings.Default.TextureExportFormat;
|
||||
|
|
@ -294,9 +292,9 @@ public class SettingsViewModel : ViewModel
|
|||
MeshExportFormats = new ReadOnlyObservableCollection<EMeshFormat>(new ObservableCollection<EMeshFormat>(EnumerateMeshExportFormat()));
|
||||
SocketExportFormats = new ReadOnlyObservableCollection<ESocketFormat>(new ObservableCollection<ESocketFormat>(EnumerateSocketExportFormat()));
|
||||
CompressionFormats = new ReadOnlyObservableCollection<EFileCompressionFormat>(new ObservableCollection<EFileCompressionFormat>(EnumerateCompressionFormat()));
|
||||
LodExportFormats = new ReadOnlyObservableCollection<ELodFormat>(new ObservableCollection<ELodFormat>(EnumerateLodExportFormat()));
|
||||
LodExportFormats = new ReadOnlyObservableCollection<EMeshQuality>(new ObservableCollection<EMeshQuality>(EnumerateLodExportFormat()));
|
||||
NaniteMeshExportFormats = new ReadOnlyObservableCollection<ENaniteMeshFormat>(new ObservableCollection<ENaniteMeshFormat>(EnumerateNaniteMeshExportFormat()));
|
||||
MaterialExportFormats = new ReadOnlyObservableCollection<EMaterialFormat>(new ObservableCollection<EMaterialFormat>(EnumerateMaterialExportFormat()));
|
||||
MaterialExportFormats = new ReadOnlyObservableCollection<EMaterialDepth>(new ObservableCollection<EMaterialDepth>(EnumerateMaterialExportFormat()));
|
||||
TextureExportFormats = new ReadOnlyObservableCollection<ETextureFormat>(new ObservableCollection<ETextureFormat>(EnumerateTextureExportFormat()));
|
||||
Platforms = new ReadOnlyObservableCollection<ETexturePlatform>(new ObservableCollection<ETexturePlatform>(EnumerateUePlatforms()));
|
||||
}
|
||||
|
|
@ -331,7 +329,7 @@ public class SettingsViewModel : ViewModel
|
|||
UserSettings.Default.MeshExportFormat = SelectedMeshExportFormat;
|
||||
UserSettings.Default.SocketExportFormat = SelectedSocketExportFormat;
|
||||
UserSettings.Default.CompressionFormat = SelectedCompressionFormat;
|
||||
UserSettings.Default.LodExportFormat = SelectedLodExportFormat;
|
||||
UserSettings.Default.MeshQuality = SelectedLodExportFormat;
|
||||
UserSettings.Default.NaniteMeshExportFormat = SelectedNaniteMeshExportFormat;
|
||||
UserSettings.Default.MaterialExportFormat = SelectedMaterialExportFormat;
|
||||
UserSettings.Default.TextureExportFormat = SelectedTextureExportFormat;
|
||||
|
|
@ -357,9 +355,9 @@ public class SettingsViewModel : ViewModel
|
|||
private IEnumerable<EMeshFormat> EnumerateMeshExportFormat() => Enum.GetValues<EMeshFormat>();
|
||||
private IEnumerable<ESocketFormat> EnumerateSocketExportFormat() => Enum.GetValues<ESocketFormat>();
|
||||
private IEnumerable<EFileCompressionFormat> EnumerateCompressionFormat() => Enum.GetValues<EFileCompressionFormat>();
|
||||
private IEnumerable<ELodFormat> EnumerateLodExportFormat() => Enum.GetValues<ELodFormat>();
|
||||
private IEnumerable<EMeshQuality> EnumerateLodExportFormat() => Enum.GetValues<EMeshQuality>();
|
||||
private IEnumerable<ENaniteMeshFormat> EnumerateNaniteMeshExportFormat() => Enum.GetValues<ENaniteMeshFormat>();
|
||||
private IEnumerable<EMaterialFormat> EnumerateMaterialExportFormat() => Enum.GetValues<EMaterialFormat>();
|
||||
private IEnumerable<EMaterialDepth> EnumerateMaterialExportFormat() => Enum.GetValues<EMaterialDepth>();
|
||||
private IEnumerable<ETextureFormat> EnumerateTextureExportFormat() => Enum.GetValues<ETextureFormat>();
|
||||
private IEnumerable<ETexturePlatform> EnumerateUePlatforms() => Enum.GetValues<ETexturePlatform>();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System.Linq;
|
|||
using System.Threading;
|
||||
using System.Windows;
|
||||
using System.Windows.Media.Imaging;
|
||||
using CUE4Parse_Conversion.Options;
|
||||
using CUE4Parse.FileProvider.Objects;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.Utils;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:FModel"
|
||||
xmlns:c4pMeshes="clr-namespace:CUE4Parse_Conversion.Meshes;assembly=CUE4Parse-Conversion"
|
||||
xmlns:c4pMeshes="clr-namespace:CUE4Parse_Conversion.Options;assembly=CUE4Parse-Conversion"
|
||||
xmlns:controls="clr-namespace:FModel.Views.Resources.Controls"
|
||||
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
|
||||
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
|
||||
|
|
@ -452,7 +452,7 @@
|
|||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock Grid.Row="5" Grid.Column="0" Text="Level Of Detail Format" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<TextBlock Grid.Row="5" Grid.Column="0" Text="Mesh Quality Format" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<ComboBox Grid.Row="5" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.LodExportFormats}" SelectedItem="{Binding SettingsView.SelectedLodExportFormat, Mode=TwoWay}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
|
||||
<ComboBox.ItemTemplate>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using CUE4Parse_Conversion;
|
||||
using CUE4Parse_Conversion.Animations.PSA;
|
||||
using CUE4Parse_Conversion.Writers.ActorX.Structs.Animations;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using FModel.Settings;
|
||||
|
|
@ -12,7 +11,7 @@ using ImGuiNET;
|
|||
|
||||
namespace FModel.Views.Snooper.Animations;
|
||||
|
||||
public class Animation : IDisposable
|
||||
public class Animation : IExportableThing, IDisposable
|
||||
{
|
||||
private readonly UObject _export;
|
||||
|
||||
|
|
@ -82,7 +81,7 @@ public class Animation : IDisposable
|
|||
AttachedModels.Clear();
|
||||
}
|
||||
|
||||
public void ImGuiAnimation(Snooper s, Save saver, ImDrawListPtr drawList, ImFontPtr fontPtr, Vector2 timelineP0, Vector2 treeP0, Vector2 timeStep, Vector2 timeRatio, float y, float t, int i)
|
||||
public void ImGuiAnimation(Snooper s, ImDrawListPtr drawList, ImFontPtr fontPtr, Vector2 timelineP0, Vector2 treeP0, Vector2 timeStep, Vector2 timeRatio, float y, float t, int i)
|
||||
{
|
||||
var name = $"{Name}##{i}";
|
||||
var p1 = new Vector2(timelineP0.X + StartTime * timeRatio.X + t, y + t);
|
||||
|
|
@ -96,7 +95,7 @@ public class Animation : IDisposable
|
|||
{
|
||||
s.Renderer.Options.SelectAnimation(i);
|
||||
}
|
||||
Popup(s, saver, i);
|
||||
Popup(s, i);
|
||||
|
||||
drawList.AddRectFilled(p1, p2, IsSelected ? 0xFF48B048 : 0xFF175F17, 5.0f, ImDrawFlags.RoundCornersTop);
|
||||
for (int j = 0; j < Sequences.Length; j++)
|
||||
|
|
@ -109,10 +108,10 @@ public class Animation : IDisposable
|
|||
{
|
||||
s.Renderer.Options.SelectAnimation(i);
|
||||
}
|
||||
Popup(s, saver, i);
|
||||
Popup(s, i);
|
||||
}
|
||||
|
||||
private void Popup(Snooper s, Save saver, int i)
|
||||
private void Popup(Snooper s, int i)
|
||||
{
|
||||
SnimGui.Popup(() =>
|
||||
{
|
||||
|
|
@ -134,11 +133,16 @@ public class Animation : IDisposable
|
|||
if (ImGui.MenuItem("Save"))
|
||||
{
|
||||
s.WindowShouldFreeze(true);
|
||||
saver.Value = new Exporter(_export, UserSettings.Default.ExportOptions).TryWriteToDir(new DirectoryInfo(UserSettings.Default.ModelDirectory), out saver.Label, out saver.Path);
|
||||
ExportModal.Instance.Export([this], UserSettings.Default.ModelDirectory, UserSettings.GetExportOptions());
|
||||
s.WindowShouldFreeze(false);
|
||||
}
|
||||
ImGui.Separator();
|
||||
if (ImGui.MenuItem("Copy Path to Clipboard")) ImGui.SetClipboardText(Path);
|
||||
});
|
||||
}
|
||||
|
||||
public void AddToExportSession(ExportSession session)
|
||||
{
|
||||
session.Add(_export);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
using System.Numerics;
|
||||
using CUE4Parse_Conversion.Animations.PSA;
|
||||
using CUE4Parse_Conversion.Writers.ActorX.Structs.Animations;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace FModel.Views.Snooper.Animations;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using CUE4Parse_Conversion.Animations.PSA;
|
||||
using CUE4Parse_Conversion.Writers.ActorX.Structs.Animations;
|
||||
using CUE4Parse.UE4.Assets.Exports.Animation;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using FModel.Views.Snooper.Buffers;
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ public class TimeTracker : IDisposable
|
|||
}
|
||||
|
||||
private readonly string[] _icons = { "tl_forward", "tl_pause", "tl_rewind" };
|
||||
public void ImGuiTimeline(Snooper s, Save saver, Dictionary<string, Texture> icons, List<Animation> animations, Vector2 outliner, ImFontPtr fontPtr)
|
||||
public void ImGuiTimeline(Snooper s, Dictionary<string, Texture> icons, List<Animation> animations, Vector2 outliner, ImFontPtr fontPtr)
|
||||
{
|
||||
var dpiScale = ImGui.GetWindowDpiScale();
|
||||
var thickness = 2.0f * dpiScale;
|
||||
|
|
@ -151,7 +151,7 @@ public class TimeTracker : IDisposable
|
|||
for (int i = 0; i < animations.Count; i++)
|
||||
{
|
||||
var y = timelineP0.Y + timeBarHeight + timeStep.Y * i;
|
||||
animations[i].ImGuiAnimation(s, saver, drawList, fontPtr, timelineP0, treeP0, timeStep, timeRatio, y, thickness, i);
|
||||
animations[i].ImGuiAnimation(s, drawList, fontPtr, timelineP0, treeP0, timeStep, timeRatio, y, thickness, i);
|
||||
DrawSeparator(drawList, timelineP0, y + timeStep.Y, animations[i].EndTime * timeRatio.X, timeHeight, timeBarHeight, ETrackerType.End);
|
||||
}
|
||||
ImGui.PopStyleVar();
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ public class PickingTexture : IDisposable
|
|||
Bind(0);
|
||||
}
|
||||
|
||||
public void Render(Matrix4x4 viewMatrix, Matrix4x4 projMatrix, IDictionary<FGuid, UModel> models)
|
||||
public void Render(Matrix4x4 viewMatrix, Matrix4x4 projMatrix, IDictionary<FGuid, IRenderableModel> models)
|
||||
{
|
||||
Bind();
|
||||
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
||||
|
|
|
|||
662
FModel/Views/Snooper/ExportModal.cs
Normal file
662
FModel/Views/Snooper/ExportModal.cs
Normal file
|
|
@ -0,0 +1,662 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CUE4Parse_Conversion;
|
||||
using CUE4Parse_Conversion.Options;
|
||||
using FModel.Extensions;
|
||||
using FModel.Views.Snooper.Models;
|
||||
using ImGuiNET;
|
||||
using Serilog;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace FModel.Views.Snooper;
|
||||
|
||||
public sealed class ExportModal
|
||||
{
|
||||
public static ExportModal Instance { get; } = new();
|
||||
|
||||
private const string Title = "Export Progress";
|
||||
private const string IconXMark = "\uf057";
|
||||
private const string IconFolder = "\uf08e";
|
||||
|
||||
private readonly Vector4[] _pieColors =
|
||||
[
|
||||
new(0.22f, 0.52f, 0.90f, 1f),
|
||||
new(0.28f, 0.78f, 0.44f, 1f),
|
||||
new(0.90f, 0.62f, 0.22f, 1f),
|
||||
new(0.75f, 0.32f, 0.75f, 1f),
|
||||
new(0.32f, 0.75f, 0.85f, 1f),
|
||||
new(0.85f, 0.32f, 0.45f, 1f),
|
||||
new(0.90f, 0.90f, 0.22f, 1f),
|
||||
new(0.55f, 0.75f, 0.32f, 1f),
|
||||
];
|
||||
|
||||
private static readonly Vector4 _redColor = new(1f, 0.4f, 0.4f, 1f);
|
||||
private static readonly Vector4 _orangeColor = new(1f, 0.5f, 0f, 1f);
|
||||
private static readonly Vector4 _yellowColor = new(1f, 1f, 0.4f, 1f);
|
||||
private static readonly Vector4 _greenColor = new(0.4f, 1f, 0.4f, 1f);
|
||||
|
||||
private bool _openPopup;
|
||||
private bool _modalOpen;
|
||||
private bool _inProgress;
|
||||
private CancellationTokenSource? _cts;
|
||||
private IReadOnlyList<ExportResult>? _exportResults;
|
||||
|
||||
private ExportProgress _currentProgress;
|
||||
private readonly IProgress<ExportProgress> _progress;
|
||||
private readonly Stopwatch _stopwatch = new();
|
||||
private readonly ConcurrentQueue<LogEvent> _pendingLogs = new();
|
||||
private readonly List<ClassGroup> _classGroups = [];
|
||||
|
||||
private const int MaxGraphSamples = 4096;
|
||||
private const float GraphSampleIntervalSec = 0.25f;
|
||||
private readonly List<float> _graphSamples = [];
|
||||
private float _graphNextSampleAt;
|
||||
private int _graphLastCompleted;
|
||||
|
||||
private ExportModal()
|
||||
{
|
||||
ImGuiSink.Instance.OnExporterLogEvent += _pendingLogs.Enqueue;
|
||||
_progress = new Progress<ExportProgress>(p => _currentProgress = p);
|
||||
}
|
||||
|
||||
public void Export(IEnumerable<IExportableThing> nodes, string exportDirectory, ExportOptions options)
|
||||
{
|
||||
Reset();
|
||||
_openPopup = true;
|
||||
_inProgress = true;
|
||||
_cts = new CancellationTokenSource();
|
||||
_stopwatch.Restart();
|
||||
|
||||
var token = _cts.Token;
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var session = new ExportSession(exportDirectory, options);
|
||||
foreach (var node in nodes) node.AddToExportSession(session);
|
||||
_exportResults = await session.RunAsync(_progress, token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Log.Error("Export cancelled by user");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Export failed");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_stopwatch.Stop();
|
||||
_inProgress = false;
|
||||
}
|
||||
}, token);
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
if (_openPopup)
|
||||
{
|
||||
ImGui.OpenPopup(Title);
|
||||
_modalOpen = true;
|
||||
_openPopup = false;
|
||||
}
|
||||
|
||||
if (!_modalOpen) return;
|
||||
|
||||
var viewport = ImGui.GetMainViewport();
|
||||
ImGui.SetNextWindowSize(viewport.WorkSize * 0.75f, ImGuiCond.Always);
|
||||
ImGui.SetNextWindowPos(viewport.GetCenter(), ImGuiCond.Always, new Vector2(0.5f, 0.5f));
|
||||
|
||||
var open = true;
|
||||
if (ImGui.BeginPopupModal(Title, ref open, ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize))
|
||||
{
|
||||
if (ImGui.BeginChild("##ModalInfoBody", Vector2.Zero, ImGuiChildFlags.FrameStyle))
|
||||
{
|
||||
DrawProgressBar();
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.SeparatorText("Throughput");
|
||||
DrawThroughputGraph();
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.SeparatorText("Export Log");
|
||||
DrawExportLog();
|
||||
}
|
||||
ImGui.EndChild();
|
||||
ImGui.EndPopup();
|
||||
}
|
||||
|
||||
if (!open)
|
||||
{
|
||||
_modalOpen = false;
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
_pendingLogs.Clear();
|
||||
_classGroups.Clear();
|
||||
_exportResults = null;
|
||||
_currentProgress = new ExportProgress(0, 0);
|
||||
_cts?.Cancel();
|
||||
_cts?.Dispose();
|
||||
_cts = null;
|
||||
|
||||
_graphSamples.Clear();
|
||||
_graphNextSampleAt = 0f;
|
||||
_graphLastCompleted = 0;
|
||||
}
|
||||
|
||||
private void DrawProgressBar()
|
||||
{
|
||||
var e = _stopwatch.Elapsed;
|
||||
ImGui.TextDisabled("\uf2f2");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted($"{e.Minutes:D2}:{e.Seconds:D2}.{e.Milliseconds / 10:D2}");
|
||||
|
||||
if (_inProgress && _currentProgress is { Total: > 0, Completed: > 1 })
|
||||
{
|
||||
var rate = _currentProgress.Completed / e.TotalSeconds;
|
||||
if (rate > 0)
|
||||
{
|
||||
var remaining = (_currentProgress.Total - _currentProgress.Completed) / rate;
|
||||
var eta = TimeSpan.FromSeconds(remaining);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextDisabled("\uf017");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted($"ETA {eta.Minutes:D2}:{eta.Seconds:D2}");
|
||||
}
|
||||
}
|
||||
else if (!_inProgress && _exportResults is { Count: > 0 })
|
||||
{
|
||||
ImGui.SameLine();
|
||||
ImGui.TextColored(_greenColor, "\uf058");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted($"{_exportResults?.Count(r => r.Success) ?? 0} succeeded");
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.TextColored(_redColor, IconXMark);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted($"{_exportResults?.Count(r => !r.Success) ?? 0} failed");
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
var barColor = _classGroups.Any(cg => cg.ErrorCount > 0) ? new Vector4(0.75f, 0.32f, 0.32f, 1f) : _inProgress ? new Vector4(0.22f, 0.52f, 0.90f, 1f) : new Vector4(0.28f, 0.78f, 0.44f, 1f);
|
||||
var label = _inProgress && _currentProgress.Total > 0 ? _currentProgress.DisplayText : _inProgress ? "Preparing..." : "Done";
|
||||
|
||||
var barPos = ImGui.GetCursorScreenPos();
|
||||
var barSize = new Vector2(ImGui.GetContentRegionAvail().X, ImGui.GetFrameHeight());
|
||||
var hovered = ImGui.IsMouseHoveringRect(barPos, barPos + barSize);
|
||||
if (hovered && _inProgress)
|
||||
{
|
||||
barColor = new Vector4(0.75f, 0.32f, 0.32f, 1f);
|
||||
label = "\uf05e Cancel";
|
||||
}
|
||||
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 4f);
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.FrameBorderSize, 1f);
|
||||
ImGui.PushStyleColor(ImGuiCol.PlotHistogram, barColor);
|
||||
ImGui.ProgressBar(_currentProgress.Percentage, barSize, label);
|
||||
ImGui.PopStyleColor();
|
||||
ImGui.PopStyleVar(2);
|
||||
|
||||
if (_inProgress)
|
||||
{
|
||||
ImGui.SetCursorScreenPos(barPos);
|
||||
if (ImGui.InvisibleButton("##BarAction", barSize))
|
||||
{
|
||||
_cts?.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawThroughputGraph()
|
||||
{
|
||||
var elapsedSec = (float)_stopwatch.Elapsed.TotalSeconds;
|
||||
if (_inProgress && _currentProgress.Completed > 0 && elapsedSec >= _graphNextSampleAt && _graphSamples.Count < MaxGraphSamples)
|
||||
{
|
||||
var completed = _currentProgress.Completed;
|
||||
var rate = (completed - _graphLastCompleted) / GraphSampleIntervalSec;
|
||||
_graphSamples.Add(MathF.Max(rate, 0f));
|
||||
_graphLastCompleted = completed;
|
||||
_graphNextSampleAt = elapsedSec + GraphSampleIntervalSec;
|
||||
}
|
||||
|
||||
var graphPos = ImGui.GetCursorScreenPos();
|
||||
var graphW = ImGui.GetContentRegionAvail().X;
|
||||
var graphH = ImGui.GetFrameHeight() * 3f;
|
||||
var graphSize = new Vector2(graphW, graphH);
|
||||
ImGui.InvisibleButton("##ThroughputGraph", graphSize);
|
||||
var dl = ImGui.GetWindowDrawList();
|
||||
|
||||
dl.AddRectFilled(graphPos, graphPos + graphSize, 0xFF_14_14_14);
|
||||
|
||||
var count = _graphSamples.Count;
|
||||
switch (count)
|
||||
{
|
||||
case 0 when !_inProgress:
|
||||
{
|
||||
dl.AddRect(graphPos, graphPos + graphSize, 0xFF_2A_2A_2A);
|
||||
return;
|
||||
}
|
||||
case < 2:
|
||||
{
|
||||
if (_inProgress)
|
||||
{
|
||||
const string msg = "Collecting data...";
|
||||
var msgSz = ImGui.CalcTextSize(msg);
|
||||
dl.AddText(graphPos + (graphSize - msgSz) * 0.5f, 0x44_FF_FF_FF, msg);
|
||||
}
|
||||
dl.AddRect(graphPos, graphPos + graphSize, 0xFF_2A_2A_2A);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Y scale
|
||||
var maxVal = 0f;
|
||||
for (var i = 0; i < count; i++) maxVal = MathF.Max(maxVal, _graphSamples[i]);
|
||||
if (maxVal < 0.01f) maxVal = 1f;
|
||||
maxVal *= 1.15f; // top headroom
|
||||
|
||||
dl.PushClipRect(graphPos, graphPos + graphSize, true);
|
||||
|
||||
// Horizontal grid lines at 25 / 50 / 75 %
|
||||
for (var g = 1; g < 4; g++)
|
||||
{
|
||||
var gy = graphPos.Y + graphH * g / 4f;
|
||||
dl.AddLine(new Vector2(graphPos.X, gy), new Vector2(graphPos.X + graphW, gy), 0x18_FF_FF_FF, 0.5f);
|
||||
}
|
||||
|
||||
var lineColor = ImGui.GetColorU32(new Vector4(0.22f, 0.52f, 0.90f, 1f));
|
||||
var fillColor = ImGui.GetColorU32(new Vector4(0.22f, 0.52f, 0.90f, 0.15f));
|
||||
|
||||
// xStep shrinks as samples accumulate → graph zooms out naturally.
|
||||
// Oldest sample is always at x=0, newest at x=graphW.
|
||||
var xStep = graphW / (count - 1f);
|
||||
|
||||
// Filled area (series of convex quads)
|
||||
for (var i = 0; i < count - 1; i++)
|
||||
{
|
||||
var t0 = Math.Clamp(_graphSamples[i] / maxVal, 0f, 1f);
|
||||
var t1 = Math.Clamp(_graphSamples[i + 1] / maxVal, 0f, 1f);
|
||||
var x0 = graphPos.X + xStep * i;
|
||||
var x1 = graphPos.X + xStep * (i + 1);
|
||||
var y0 = graphPos.Y + graphH - graphH * t0;
|
||||
var y1 = graphPos.Y + graphH - graphH * t1;
|
||||
var bot = graphPos.Y + graphH;
|
||||
dl.AddQuadFilled(new Vector2(x0, y0), new Vector2(x1, y1), new Vector2(x1, bot), new Vector2(x0, bot), fillColor);
|
||||
}
|
||||
|
||||
// Line
|
||||
for (var i = 0; i < count - 1; i++)
|
||||
{
|
||||
var t0 = Math.Clamp(_graphSamples[i] / maxVal, 0f, 1f);
|
||||
var t1 = Math.Clamp(_graphSamples[i + 1] / maxVal, 0f, 1f);
|
||||
var x0 = graphPos.X + xStep * i;
|
||||
var x1 = graphPos.X + xStep * (i + 1);
|
||||
var y0 = graphPos.Y + graphH - graphH * t0;
|
||||
var y1 = graphPos.Y + graphH - graphH * t1;
|
||||
dl.AddLine(new Vector2(x0, y0), new Vector2(x1, y1), lineColor, 1.5f);
|
||||
}
|
||||
|
||||
// Dot on the newest sample (always at the right edge)
|
||||
var newestT = Math.Clamp(_graphSamples[count - 1] / maxVal, 0f, 1f);
|
||||
var newestY = graphPos.Y + graphH - graphH * newestT;
|
||||
dl.AddCircleFilled(new Vector2(graphPos.X + graphW, newestY), 3f, lineColor);
|
||||
|
||||
var rateStr = $"{_graphSamples[count - 1]:F1} items/s";
|
||||
dl.AddText(new Vector2(graphPos.X + 4, graphPos.Y + 3), 0xCC_FF_FF_FF, rateStr);
|
||||
|
||||
dl.PopClipRect();
|
||||
|
||||
dl.AddRect(graphPos, graphPos + graphSize, 0xFF_2A_2A_2A);
|
||||
}
|
||||
|
||||
private void DrawExportLog()
|
||||
{
|
||||
var avail = ImGui.GetContentRegionAvail();
|
||||
var rowH = ImGui.GetTextLineHeightWithSpacing();
|
||||
var canvasSize = 15 * rowH + ImGui.GetFrameHeightWithSpacing();
|
||||
var treeW = avail.X - canvasSize - ImGui.GetStyle().ItemSpacing.X;
|
||||
|
||||
DrainPendingLogs();
|
||||
|
||||
if (ImGui.BeginChild("##ExportLogTree", avail with { X = treeW }, ImGuiChildFlags.FrameStyle))
|
||||
{
|
||||
if (_classGroups.Count == 0)
|
||||
{
|
||||
ImGui.TextDisabled(_inProgress ? "Waiting for export data..." : "No export log.");
|
||||
}
|
||||
else for (var i = 0; i < _classGroups.Count; i++)
|
||||
{
|
||||
DrawClassGroup(i, _classGroups[i]);
|
||||
}
|
||||
}
|
||||
ImGui.EndChild();
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.BeginChild("##RightPanel", new Vector2(canvasSize, -1), ImGuiChildFlags.FrameStyle))
|
||||
{
|
||||
DrawPieCanvas();
|
||||
|
||||
if (_classGroups.Count == 0)
|
||||
{
|
||||
ImGui.TextDisabled(_inProgress ? "Waiting for data..." : "No export data.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var total = _classGroups.Sum(cg => cg.Objects.Count);
|
||||
for (var i = 0; i < _classGroups.Count; i++)
|
||||
{
|
||||
var cg = _classGroups[i];
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, _pieColors[i % _pieColors.Length]);
|
||||
ImGui.TextUnformatted("\uf111");
|
||||
ImGui.PopStyleColor();
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(cg.Name);
|
||||
ImGui.SameLine();
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled));
|
||||
ImGui.TextUnformatted($"({(float) cg.Objects.Count / total * 100f:F1}%)");
|
||||
ImGui.PopStyleColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndChild();
|
||||
}
|
||||
|
||||
private void DrawPieCanvas()
|
||||
{
|
||||
const int segments = 64;
|
||||
|
||||
var canvasPos = ImGui.GetCursorScreenPos();
|
||||
var size = ImGui.GetContentRegionAvail().X;
|
||||
var canvasVec = new Vector2(size);
|
||||
ImGui.InvisibleButton("##PieCanvas", canvasVec);
|
||||
var isHovered = ImGui.IsItemHovered();
|
||||
var mousePos = ImGui.GetMousePos();
|
||||
var dl = ImGui.GetWindowDrawList();
|
||||
|
||||
dl.AddRectFilled(canvasPos, canvasPos + canvasVec, 0xFF_14_14_14);
|
||||
dl.AddRect(canvasPos, canvasPos + canvasVec, 0xFF_32_32_32);
|
||||
|
||||
var total = _classGroups.Sum(cg => cg.Objects.Count);
|
||||
var padding = ImGui.GetFrameHeight() * 0.5f;
|
||||
var radius = size * 0.5f - padding;
|
||||
var center = canvasPos + new Vector2(size * 0.5f);
|
||||
|
||||
if (total == 0 || radius <= 0)
|
||||
{
|
||||
dl.AddCircleFilled(center, MathF.Max(radius, 1f), 0xFF_1F_1F_1F, segments);
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine hovered slice by angle
|
||||
var hoveredSlice = -1;
|
||||
if (isHovered)
|
||||
{
|
||||
var dx = mousePos.X - center.X;
|
||||
var dy = mousePos.Y - center.Y;
|
||||
if (dx * dx + dy * dy <= radius * radius)
|
||||
{
|
||||
var angle = MathF.Atan2(dy, dx);
|
||||
while (angle < -MathF.PI / 2f) angle += MathF.PI * 2f;
|
||||
var cur = -MathF.PI / 2f;
|
||||
for (var i = 0; i < _classGroups.Count; i++)
|
||||
{
|
||||
var sweep = (float)_classGroups[i].Objects.Count / total * MathF.PI * 2f;
|
||||
if (angle >= cur && angle < cur + sweep) { hoveredSlice = i; break; }
|
||||
cur += sweep;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float startAngle = -MathF.PI / 2f;
|
||||
for (var i = 0; i < _classGroups.Count; i++)
|
||||
{
|
||||
var cg = _classGroups[i];
|
||||
var ratio = (float) cg.Objects.Count / total;
|
||||
var sliceAngle = ratio * MathF.PI * 2f;
|
||||
var col = ImGui.GetColorU32(_pieColors[i % _pieColors.Length]);
|
||||
var r = i == hoveredSlice ? radius + padding * 0.25f : radius;
|
||||
dl.PathLineTo(center);
|
||||
dl.PathArcTo(center, r, startAngle, startAngle + sliceAngle);
|
||||
dl.PathFillConvex(col);
|
||||
|
||||
var midAngle = startAngle + sliceAngle * 0.5f;
|
||||
var labelPos = center + new Vector2(MathF.Cos(midAngle), MathF.Sin(midAngle)) * (radius * 0.62f);
|
||||
var pctStr = $"{ratio * 100f:F0}%";
|
||||
dl.AddText(labelPos - ImGui.CalcTextSize(pctStr) * 0.5f, 0xFF_FF_FF_FF, pctStr);
|
||||
|
||||
startAngle += sliceAngle;
|
||||
}
|
||||
|
||||
dl.AddCircle(center, radius, 0xAA_00_00_00, segments, 1.5f);
|
||||
|
||||
if (hoveredSlice >= 0)
|
||||
{
|
||||
ImGui.BeginTooltip();
|
||||
|
||||
var cg = _classGroups[hoveredSlice];
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, _pieColors[hoveredSlice % _pieColors.Length]);
|
||||
ImGui.TextUnformatted("\uf111");
|
||||
ImGui.PopStyleColor();
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(cg.Name);
|
||||
|
||||
ImGui.EndTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawClassGroup(int index, ClassGroup cg)
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.Header, new Vector4(0.20f, 0.20f, 0.20f, 1.00f));
|
||||
ImGui.PushStyleColor(ImGuiCol.HeaderHovered, new Vector4(0.69f, 0.69f, 1.00f, 0.20f));
|
||||
ImGui.PushStyleColor(ImGuiCol.HeaderActive, new Vector4(0.69f, 0.69f, 1.00f, 0.20f));
|
||||
var open = ImGui.CollapsingHeader($"{cg.Name} ({cg.Objects.Count})##class_{cg.Name}");
|
||||
ImGui.PopStyleColor(3);
|
||||
var headerMin = ImGui.GetItemRectMin();
|
||||
var headerMax = ImGui.GetItemRectMax();
|
||||
|
||||
var labelW = MathF.Floor(ImGui.GetStyle().ItemSpacing.X * 0.5f);
|
||||
var col = ImGui.GetColorU32(_pieColors[index % _pieColors.Length]);
|
||||
ImGui.GetWindowDrawList().AddRectFilled(headerMin, headerMax with { X = headerMin.X + labelW }, col);
|
||||
|
||||
if (cg.ErrorCount > 0 && ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetTooltip($"{cg.ErrorCount} error{(cg.ErrorCount > 1 ? "s" : "")} in this class");
|
||||
}
|
||||
|
||||
if (!open) return;
|
||||
foreach (var og in cg.Objects)
|
||||
{
|
||||
DrawObjectGroup(cg.Name, og);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawObjectGroup(string className, ObjectGroup og)
|
||||
{
|
||||
var rightEdge = ImGui.GetCursorPosX() + ImGui.GetContentRegionAvail().X;
|
||||
|
||||
var hasErr = og.ErrorCount > 0;
|
||||
if (hasErr) ImGui.PushStyleColor(ImGuiCol.Text, _redColor);
|
||||
var flags = ImGuiTreeNodeFlags.AllowOverlap | ImGuiTreeNodeFlags.SpanFullWidth | ImGuiTreeNodeFlags.FramePadding;
|
||||
var open = ImGui.TreeNodeEx($"{og.Name}##obj_{className}_{og.Name}", flags);
|
||||
if (hasErr) ImGui.PopStyleColor();
|
||||
|
||||
if (og.Entries.FirstOrDefault(e => !string.IsNullOrEmpty(e.FilePath)) is { } first)
|
||||
{
|
||||
var style = ImGui.GetStyle();
|
||||
var btnW = ImGui.CalcTextSize(IconFolder).X + style.FramePadding.X * 2;
|
||||
ImGui.SameLine(rightEdge - btnW);
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, style.ItemSpacing with { X = 0 });
|
||||
ImGui.PushStyleColor(ImGuiCol.Button, Vector4.Zero);
|
||||
if (ImGui.Button($"{IconFolder}##obj_{className}_{og.Name}"))
|
||||
{
|
||||
OpenInExplorer(first.FilePath!);
|
||||
}
|
||||
if (ImGui.IsItemHovered()) ImGui.SetTooltip("Open In Explorer");
|
||||
ImGui.PopStyleColor();
|
||||
ImGui.PopStyleVar();
|
||||
}
|
||||
|
||||
if (!open) return;
|
||||
foreach (var entry in og.Entries)
|
||||
{
|
||||
DrawLogEntry(entry);
|
||||
}
|
||||
ImGui.TreePop();
|
||||
}
|
||||
|
||||
private void DrawLogEntry(LogEntry entry)
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, entry.Color);
|
||||
ImGui.TextUnformatted(entry.Icon);
|
||||
ImGui.PopStyleColor();
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(entry.Message);
|
||||
if (ImGui.IsItemHovered() && entry.Exception != null)
|
||||
{
|
||||
DrawExceptionTooltip(entry.Exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawExceptionTooltip(Exception ex)
|
||||
{
|
||||
ImGui.BeginTooltip();
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, _redColor);
|
||||
ImGui.TextUnformatted(ex.GetType().ToString());
|
||||
ImGui.PopStyleColor();
|
||||
ImGui.SameLine(0, 0);
|
||||
ImGui.TextDisabled(":");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(ex.Message);
|
||||
if (!string.IsNullOrEmpty(ex.StackTrace))
|
||||
{
|
||||
ImGui.SetWindowFontScale(0.85f);
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled));
|
||||
ImGui.TextUnformatted(ex.StackTrace);
|
||||
ImGui.PopStyleColor();
|
||||
ImGui.SetWindowFontScale(1.0f);
|
||||
}
|
||||
ImGui.EndTooltip();
|
||||
}
|
||||
|
||||
private void OpenInExplorer(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(path) || Directory.Exists(path)) Process.Start("explorer.exe", $"/select, \"{path}\"");
|
||||
else Log.Warning("File or directory does not exist: {Path}", path);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed to open in explorer: {Path}", path);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrainPendingLogs()
|
||||
{
|
||||
while (_pendingLogs.TryDequeue(out var log))
|
||||
{
|
||||
var className = log.GetContext("ClassName");
|
||||
var objectName = log.GetContext("ObjectName");
|
||||
var filePath = log.GetContext("FilePath");
|
||||
|
||||
var cg = FindOrCreateClass(className);
|
||||
var og = FindOrCreateObject(cg, objectName);
|
||||
var entry = new LogEntry(log, filePath);
|
||||
if (entry.Icon == IconXMark)
|
||||
{
|
||||
og.ErrorCount++;
|
||||
cg.ErrorCount++;
|
||||
}
|
||||
og.Entries.Add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
private ClassGroup FindOrCreateClass(string name)
|
||||
{
|
||||
foreach (var cg in _classGroups)
|
||||
if (cg.Name == name) return cg;
|
||||
|
||||
var n = new ClassGroup(name);
|
||||
_classGroups.Add(n);
|
||||
return n;
|
||||
}
|
||||
|
||||
private static ObjectGroup FindOrCreateObject(ClassGroup cg, string name)
|
||||
{
|
||||
foreach (var og in cg.Objects)
|
||||
if (og.Name == name) return og;
|
||||
|
||||
var n = new ObjectGroup(name);
|
||||
cg.Objects.Add(n);
|
||||
return n;
|
||||
}
|
||||
|
||||
private sealed class LogEntry(LogEvent log, string? filePath)
|
||||
{
|
||||
public string Icon { get; } = log.Level switch
|
||||
{
|
||||
LogEventLevel.Error or LogEventLevel.Fatal => IconXMark,
|
||||
LogEventLevel.Warning => "\uf071",
|
||||
LogEventLevel.Information => "\uf05a",
|
||||
LogEventLevel.Debug => "\uf188",
|
||||
_ => "\uf5dc"
|
||||
};
|
||||
public Vector4 Color { get; } = log.Level switch
|
||||
{
|
||||
LogEventLevel.Error or LogEventLevel.Fatal => _redColor,
|
||||
LogEventLevel.Warning => _yellowColor,
|
||||
_ => new Vector4(0.5f, 0.5f, 0.5f, 1f)
|
||||
};
|
||||
public string Message { get; } = $"[{log.Timestamp:HH:mm:ss.fff}] {log.RenderMessage()}";
|
||||
public string? FilePath { get; } = filePath;
|
||||
public Exception? Exception { get; } = log.Exception;
|
||||
}
|
||||
|
||||
private sealed class ObjectGroup(string name)
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public List<LogEntry> Entries { get; } = [];
|
||||
public int ErrorCount { get; set; }
|
||||
}
|
||||
|
||||
private sealed class ClassGroup(string name)
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public List<ObjectGroup> Objects { get; } = [];
|
||||
public int ErrorCount { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
public class ImGuiSink : ILogEventSink
|
||||
{
|
||||
public static ImGuiSink Instance { get; } = new();
|
||||
|
||||
private ImGuiSink()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public event Action<LogEvent>? OnExporterLogEvent;
|
||||
|
||||
public void Emit(LogEvent logEvent)
|
||||
{
|
||||
if (logEvent.Properties.TryGetValue("ExporterV2", out var state) && state is ScalarValue { Value: true })
|
||||
{
|
||||
OnExporterLogEvent?.Invoke(logEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ public class Attachment
|
|||
_attachedFor = new List<string>();
|
||||
}
|
||||
|
||||
public void Attach(UModel attachedTo, Transform transform, Socket socket, SocketAttachementInfo info)
|
||||
public void Attach(IRenderableModel attachedTo, Transform transform, Socket socket, SocketAttachementInfo info)
|
||||
{
|
||||
socket.AttachedModels.Add(info);
|
||||
|
||||
|
|
@ -38,13 +38,13 @@ public class Attachment
|
|||
transform.Scale = FVector.OneVector;
|
||||
}
|
||||
|
||||
public void Detach(UModel attachedTo, Transform transform, Socket socket, SocketAttachementInfo info)
|
||||
public void Detach(IRenderableModel attachedTo, Transform transform, Socket socket, SocketAttachementInfo info)
|
||||
{
|
||||
socket.AttachedModels.Remove(info);
|
||||
SafeDetach(attachedTo, transform);
|
||||
}
|
||||
|
||||
public void SafeDetach(UModel attachedTo, Transform transform)
|
||||
public void SafeDetach(IRenderableModel attachedTo, Transform transform)
|
||||
{
|
||||
_attachedTo = string.Empty;
|
||||
attachedTo.Attachments.RemoveAttachment(_modelName);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using CUE4Parse_Conversion;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using FModel.Views.Snooper.Buffers;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
|
||||
namespace FModel.Views.Snooper.Models;
|
||||
|
||||
public interface IRenderableModel : IDisposable
|
||||
public interface IRenderableModel : IExportableThing, IDisposable
|
||||
{
|
||||
protected int Handle { get; set; }
|
||||
protected BufferObject<uint> Ebo { get; set; }
|
||||
|
|
@ -24,15 +26,36 @@ public interface IRenderableModel : IDisposable
|
|||
public List<Transform> Transforms { get; }
|
||||
public Attachment Attachments { get; }
|
||||
|
||||
public FBox Box { get; protected init; }
|
||||
public List<Socket> Sockets { get; }
|
||||
public List<Collision> Collisions { get; }
|
||||
public Material[] Materials { get; protected init; }
|
||||
public bool IsTwoSided { get; internal set; }
|
||||
public bool IsProp { get; internal set; }
|
||||
|
||||
public bool HasSockets { get; }
|
||||
public bool HasCollisions { get; }
|
||||
public int TransformsCount { get; }
|
||||
|
||||
public bool IsSetup { get; set; }
|
||||
public bool IsVisible { get; set; }
|
||||
public bool IsSelected { get; set; }
|
||||
public bool ShowWireframe { get; set; }
|
||||
public bool ShowCollisions { get; set; }
|
||||
public int SelectedInstance { get; set; }
|
||||
|
||||
public void Setup(Options options);
|
||||
public void SetupInstances();
|
||||
public void Render(Shader shader, Texture checker = null, bool outline = false);
|
||||
public void RenderCollision(Shader shader);
|
||||
public void PickingRender(Shader shader);
|
||||
public void Update(Options options);
|
||||
public void AddInstance(Transform transform);
|
||||
|
||||
public Transform GetTransform();
|
||||
}
|
||||
|
||||
public interface IExportableThing
|
||||
{
|
||||
public void AddToExportSession(ExportSession session);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ public class Morph : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
public Morph(float[] vertices, Dictionary<uint, int> dict, UMorphTarget morphTarget, int index = 0)
|
||||
public Morph(float[] vertices, Dictionary<uint, int> dict, UMorphTarget morphTarget, uint index = 0)
|
||||
{
|
||||
Name = morphTarget.Name;
|
||||
Vertices = new float[vertices.Length];
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using CUE4Parse_Conversion.Meshes.PSK;
|
||||
using CUE4Parse_Conversion.Dto;
|
||||
using CUE4Parse.UE4.Assets.Exports.Animation;
|
||||
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
|
|
@ -14,7 +13,7 @@ using OpenTK.Graphics.OpenGL4;
|
|||
|
||||
namespace FModel.Views.Snooper.Models;
|
||||
|
||||
public class SkeletalModel : UModel
|
||||
public class SkeletalModel : UModel<SkinnedMeshVertex>
|
||||
{
|
||||
private BufferObject<float> _morphVbo;
|
||||
|
||||
|
|
@ -25,10 +24,10 @@ public class SkeletalModel : UModel
|
|||
|
||||
public float MorphTime;
|
||||
|
||||
public SkeletalModel(USkeletalMesh export, CSkeletalMesh skeletalMesh, Transform transform = null)
|
||||
: base(export, skeletalMesh.LODs[LodLevel], export.Materials, skeletalMesh.LODs[LodLevel].Verts, skeletalMesh.LODs.Count, transform)
|
||||
public SkeletalModel(USkeletalMesh export, SkeletalMeshDto skeletalMesh, Transform transform = null)
|
||||
: base(export, skeletalMesh.LODs[LodLevel], export.Materials, skeletalMesh.LODs[LodLevel].Vertices, skeletalMesh.LODs.Count, transform)
|
||||
{
|
||||
Box = skeletalMesh.BoundingBox * Constants.SCALE_DOWN_RATIO;
|
||||
Box = skeletalMesh.Bounds * Constants.SCALE_DOWN_RATIO;
|
||||
Skeleton = new Skeleton(export.ReferenceSkeleton);
|
||||
|
||||
var sockets = new List<FPackageIndex>();
|
||||
|
|
@ -101,10 +100,10 @@ public class SkeletalModel : UModel
|
|||
foreach (var morph in export.MorphTargets)
|
||||
{
|
||||
if (!morph.TryLoad(out UMorphTarget morphTarget) || morphTarget.MorphLODModels.Length < 1 ||
|
||||
morphTarget.MorphLODModels[skeletalMesh.LODs[LodLevel].LODIndex].Vertices.Length < 1)
|
||||
morphTarget.MorphLODModels[skeletalMesh.LODs[LodLevel].SourceLodIndex].Vertices.Length < 1)
|
||||
continue;
|
||||
|
||||
Morphs.Add(new Morph(cachedVertices, vertexLookup, morphTarget, skeletalMesh.LODs[LodLevel].LODIndex));
|
||||
Morphs.Add(new Morph(cachedVertices, vertexLookup, morphTarget, skeletalMesh.LODs[LodLevel].SourceLodIndex));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using CUE4Parse_Conversion.Meshes.PSK;
|
||||
using CUE4Parse_Conversion.Dto;
|
||||
using CUE4Parse.UE4.Assets.Exports.Component.SplineMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using FModel.Views.Snooper.Buffers;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
|
|
@ -74,7 +71,7 @@ public class SplineModel : StaticModel
|
|||
private readonly List<GpuParams> _splineParams;
|
||||
private BufferObject<GpuParams> _ssbo;
|
||||
|
||||
public SplineModel(UStaticMesh export, CStaticMesh staticMesh, USplineMeshComponent splineMesh, Transform transform = null) : base(export, staticMesh, transform)
|
||||
public SplineModel(UStaticMesh export, StaticMeshDto staticMesh, USplineMeshComponent splineMesh, Transform transform = null) : base(export, staticMesh, transform)
|
||||
{
|
||||
_splineParams = [new GpuParams(splineMesh)];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using CUE4Parse_Conversion.Meshes.PSK;
|
||||
using CUE4Parse_Conversion.Dto;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
|
|
@ -11,24 +11,24 @@ using OpenTK.Graphics.OpenGL4;
|
|||
|
||||
namespace FModel.Views.Snooper.Models;
|
||||
|
||||
public class StaticModel : UModel
|
||||
public class StaticModel : UModel<MeshVertex>
|
||||
{
|
||||
public StaticModel(UMaterialInterface unrealMaterial, CStaticMesh staticMesh) : base(unrealMaterial)
|
||||
public StaticModel(UMaterialInterface unrealMaterial, StaticMeshDto staticMesh) : base(unrealMaterial)
|
||||
{
|
||||
var lod = staticMesh.LODs[LodLevel];
|
||||
|
||||
Indices = new uint[lod.Indices.Value.Length];
|
||||
Indices = new uint[lod.Indices.Length];
|
||||
for (int i = 0; i < Indices.Length; i++)
|
||||
{
|
||||
Indices[i] = (uint) lod.Indices.Value[i];
|
||||
Indices[i] = lod.Indices[i];
|
||||
}
|
||||
|
||||
Vertices = new float[lod.NumVerts * VertexSize];
|
||||
for (int i = 0; i < lod.Verts.Length; i++)
|
||||
Vertices = new float[lod.Vertices.Length * VertexSize];
|
||||
for (int i = 0; i < lod.Vertices.Length; i++)
|
||||
{
|
||||
var count = 0;
|
||||
var baseIndex = i * VertexSize;
|
||||
var vert = lod.Verts[i];
|
||||
var vert = lod.Vertices[i];
|
||||
Vertices[baseIndex + count++] = i;
|
||||
Vertices[baseIndex + count++] = vert.Position.X * Constants.SCALE_DOWN_RATIO;
|
||||
Vertices[baseIndex + count++] = vert.Position.Z * Constants.SCALE_DOWN_RATIO;
|
||||
|
|
@ -39,8 +39,8 @@ public class StaticModel : UModel
|
|||
Vertices[baseIndex + count++] = vert.Tangent.X;
|
||||
Vertices[baseIndex + count++] = vert.Tangent.Z;
|
||||
Vertices[baseIndex + count++] = vert.Tangent.Y;
|
||||
Vertices[baseIndex + count++] = vert.UV.U;
|
||||
Vertices[baseIndex + count++] = vert.UV.V;
|
||||
Vertices[baseIndex + count++] = vert.Uv.U;
|
||||
Vertices[baseIndex + count++] = vert.Uv.V;
|
||||
Vertices[baseIndex + count++] = .5f;
|
||||
}
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ public class StaticModel : UModel
|
|||
|
||||
AddInstance(Transform.Identity);
|
||||
|
||||
Box = staticMesh.BoundingBox * 1.5f * Constants.SCALE_DOWN_RATIO;
|
||||
Box = staticMesh.Bounds * 1.5f * Constants.SCALE_DOWN_RATIO;
|
||||
}
|
||||
|
||||
public StaticModel(UPaperSprite paperSprite, UTexture2D texture) : base(paperSprite)
|
||||
|
|
@ -108,8 +108,8 @@ public class StaticModel : UModel
|
|||
Box = new FBox(-backward, backward) * Constants.SCALE_DOWN_RATIO;
|
||||
}
|
||||
|
||||
public StaticModel(UStaticMesh export, CStaticMesh staticMesh, Transform transform = null)
|
||||
: base(export, staticMesh.LODs[LodLevel], export.Materials, staticMesh.LODs[LodLevel].Verts, staticMesh.LODs.Count, transform)
|
||||
public StaticModel(UStaticMesh export, StaticMeshDto staticMesh, Transform transform = null)
|
||||
: base(export, staticMesh.LODs[LodLevel], export.Materials, staticMesh.LODs[LodLevel].Vertices, staticMesh.LODs.Count, transform)
|
||||
{
|
||||
if (export.BodySetup.TryLoad(out UBodySetup bodySetup) && bodySetup.AggGeom != null)
|
||||
{
|
||||
|
|
@ -135,7 +135,7 @@ public class StaticModel : UModel
|
|||
}
|
||||
}
|
||||
|
||||
Box = staticMesh.BoundingBox * Constants.SCALE_DOWN_RATIO;
|
||||
Box = staticMesh.Bounds * Constants.SCALE_DOWN_RATIO;
|
||||
for (int i = 0; i < export.Sockets.Length; i++)
|
||||
{
|
||||
if (export.Sockets[i].Load<UStaticMeshSocket>() is not { } socket) continue;
|
||||
|
|
|
|||
|
|
@ -1,16 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using CUE4Parse_Conversion;
|
||||
using CUE4Parse_Conversion.Meshes.PSK;
|
||||
using CUE4Parse.UE4.Assets;
|
||||
using CUE4Parse_Conversion.Dto;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.Utils;
|
||||
using FModel.Settings;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using FModel.Views.Snooper.Buffers;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
|
|
@ -24,7 +21,7 @@ public class VertexAttribute
|
|||
public bool Enabled;
|
||||
}
|
||||
|
||||
public abstract class UModel : IRenderableModel
|
||||
public abstract class UModel<TVertex> : IRenderableModel where TVertex : struct, IMeshVertex
|
||||
{
|
||||
protected const int LodLevel = 0;
|
||||
|
||||
|
|
@ -58,12 +55,12 @@ public abstract class UModel : IRenderableModel
|
|||
public List<Transform> Transforms { get; }
|
||||
public Attachment Attachments { get; }
|
||||
|
||||
public FBox Box;
|
||||
public readonly List<Socket> Sockets;
|
||||
public readonly List<Collision> Collisions;
|
||||
public Material[] Materials;
|
||||
public bool IsTwoSided;
|
||||
public bool IsProp;
|
||||
public FBox Box { get; init; }
|
||||
public List<Socket> Sockets { get; }
|
||||
public List<Collision> Collisions { get; }
|
||||
public Material[] Materials { get; init; }
|
||||
public bool IsTwoSided { get; set; }
|
||||
public bool IsProp { get; set; }
|
||||
|
||||
public int VertexSize => _vertexAttributes.Where(x => x.Enabled).Sum(x => x.Size);
|
||||
public bool HasVertexColors => _vertexAttributes[(int) EAttribute.Colors].Enabled;
|
||||
|
|
@ -76,7 +73,7 @@ public abstract class UModel : IRenderableModel
|
|||
public bool IsSelected { get; set; }
|
||||
public bool ShowWireframe { get; set; }
|
||||
public bool ShowCollisions { get; set; }
|
||||
public int SelectedInstance;
|
||||
public int SelectedInstance { get; set; }
|
||||
|
||||
protected UModel()
|
||||
{
|
||||
|
|
@ -110,16 +107,16 @@ public abstract class UModel : IRenderableModel
|
|||
_vertexAttributes[(int) EAttribute.Layer].Enabled = true;
|
||||
}
|
||||
|
||||
protected UModel(UObject export, CBaseMeshLod lod, IReadOnlyList<ResolvedObject> materials, IReadOnlyList<CMeshVertex> vertices, int numLods, Transform transform = null) : this(export)
|
||||
protected UModel(UObject export, MeshLodDto<TVertex> lod, IReadOnlyList<FPackageIndex> materials, IReadOnlyList<TVertex> vertices, int numLods, Transform transform = null) : this(export)
|
||||
{
|
||||
var hasCustomUvs = lod.ExtraUV.IsValueCreated;
|
||||
UvCount = hasCustomUvs ? Math.Max(lod.NumTexCoords, numLods) : lod.NumTexCoords;
|
||||
var hasCustomUvs = lod.ExtraUvs.Length > 0;
|
||||
UvCount = hasCustomUvs ? Math.Max(lod.ExtraUvs.Length, numLods) : lod.ExtraUvs.Length + 1;
|
||||
IsTwoSided = lod.IsTwoSided;
|
||||
|
||||
Indices = new uint[lod.Indices.Value.Length];
|
||||
Indices = new uint[lod.Indices.Length];
|
||||
for (int i = 0; i < Indices.Length; i++)
|
||||
{
|
||||
Indices[i] = (uint) lod.Indices.Value[i];
|
||||
Indices[i] = lod.Indices[i];
|
||||
}
|
||||
|
||||
Materials = new Material[materials.Count];
|
||||
|
|
@ -129,11 +126,11 @@ public abstract class UModel : IRenderableModel
|
|||
Materials[m] = new Material(unrealMaterial); else Materials[m] = new Material();
|
||||
}
|
||||
|
||||
_vertexAttributes[(int) EAttribute.Colors].Enabled = lod.VertexColors is { Length: > 0};
|
||||
_vertexAttributes[(int) EAttribute.Colors].Enabled = lod.VertexColors is { Length: > 0 };
|
||||
_vertexAttributes[(int) EAttribute.BonesId].Enabled =
|
||||
_vertexAttributes[(int) EAttribute.BonesWeight].Enabled = vertices is CSkelMeshVertex[];
|
||||
_vertexAttributes[(int) EAttribute.BonesWeight].Enabled = vertices is SkinnedMeshVertex[];
|
||||
|
||||
Vertices = new float[lod.NumVerts * VertexSize];
|
||||
Vertices = new float[vertices.Count * VertexSize];
|
||||
for (int i = 0; i < vertices.Count; i++)
|
||||
{
|
||||
var count = 0;
|
||||
|
|
@ -149,18 +146,18 @@ public abstract class UModel : IRenderableModel
|
|||
Vertices[baseIndex + count++] = vert.Tangent.X;
|
||||
Vertices[baseIndex + count++] = vert.Tangent.Z;
|
||||
Vertices[baseIndex + count++] = vert.Tangent.Y;
|
||||
Vertices[baseIndex + count++] = vert.UV.U;
|
||||
Vertices[baseIndex + count++] = vert.UV.V;
|
||||
Vertices[baseIndex + count++] = hasCustomUvs ? lod.ExtraUV.Value[0][i].U - 1 : .5f;
|
||||
Vertices[baseIndex + count++] = vert.Uv.U;
|
||||
Vertices[baseIndex + count++] = vert.Uv.V;
|
||||
Vertices[baseIndex + count++] = hasCustomUvs ? lod.ExtraUvs[0][i].U - 1 : .5f;
|
||||
|
||||
if (HasVertexColors)
|
||||
{
|
||||
Vertices[baseIndex + count++] = lod.VertexColors[i].ToPackedARGB();
|
||||
Vertices[baseIndex + count++] = lod.VertexColors![0].Colors[i].ToPackedARGB();
|
||||
}
|
||||
|
||||
if (vert is CSkelMeshVertex skelVert)
|
||||
if (vert is SkinnedMeshVertex skelVert)
|
||||
{
|
||||
int max = skelVert.Influences.Count;
|
||||
int max = skelVert.Influences.Length;
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
var boneID = j < max ? skelVert.Influences[j].Bone : (ushort) 0;
|
||||
|
|
@ -172,10 +169,10 @@ public abstract class UModel : IRenderableModel
|
|||
}
|
||||
}
|
||||
|
||||
Sections = new Section[lod.Sections.Value.Length];
|
||||
Sections = new Section[lod.Sections.Length];
|
||||
for (var s = 0; s < Sections.Length; s++)
|
||||
{
|
||||
var section = lod.Sections.Value[s];
|
||||
var section = lod.Sections[s];
|
||||
Sections[s] = new Section(section.MaterialIndex, section.NumFaces * 3, section.FirstIndex);
|
||||
if (section.IsValid) Sections[s].SetupMaterial(Materials[section.MaterialIndex]);
|
||||
}
|
||||
|
|
@ -392,10 +389,9 @@ public abstract class UModel : IRenderableModel
|
|||
return socket.Transform.LocalMatrix * socketRelation;
|
||||
}
|
||||
|
||||
public bool Save(out string label, out string savedFilePath)
|
||||
public void AddToExportSession(ExportSession session)
|
||||
{
|
||||
var toSave = new Exporter(_export, UserSettings.Default.ExportOptions);
|
||||
return toSave.TryWriteToDir(new DirectoryInfo(UserSettings.Default.ModelDirectory), out label, out savedFilePath);
|
||||
session.Add(_export);
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
|
|
@ -20,7 +21,7 @@ public class Options
|
|||
public int SelectedMorph { get; private set; }
|
||||
public int SelectedAnimation{ get; private set; }
|
||||
|
||||
public readonly Dictionary<FGuid, UModel> Models;
|
||||
public readonly Dictionary<FGuid, IRenderableModel> Models;
|
||||
public readonly Dictionary<FGuid, Texture> Textures;
|
||||
public readonly List<Light> Lights;
|
||||
|
||||
|
|
@ -33,7 +34,7 @@ public class Options
|
|||
|
||||
public Options()
|
||||
{
|
||||
Models = new Dictionary<FGuid, UModel>();
|
||||
Models = new Dictionary<FGuid, IRenderableModel>();
|
||||
Textures = new Dictionary<FGuid, Texture>();
|
||||
Lights = new List<Light>();
|
||||
|
||||
|
|
@ -110,30 +111,30 @@ public class Options
|
|||
|
||||
public void RemoveModel(FGuid guid)
|
||||
{
|
||||
if (!TryGetModel(guid, out var m) || m is not UModel model)
|
||||
if (!TryGetModel(guid, out var m) || m is null)
|
||||
return;
|
||||
|
||||
DetachAndRemoveModels(model, true);
|
||||
model.Dispose();
|
||||
DetachAndRemoveModels(m, true);
|
||||
m.Dispose();
|
||||
Models.Remove(guid);
|
||||
}
|
||||
|
||||
private void DetachAndRemoveModels(UModel model, bool detach)
|
||||
private void DetachAndRemoveModels(IRenderableModel model, bool detach)
|
||||
{
|
||||
foreach (var socket in model.Sockets.ToList())
|
||||
{
|
||||
foreach (var info in socket.AttachedModels)
|
||||
{
|
||||
if (!TryGetModel(info.Guid, out var m) || m is not UModel attachedModel)
|
||||
if (!TryGetModel(info.Guid, out var m) || m is null)
|
||||
continue;
|
||||
|
||||
var t = attachedModel.GetTransform();
|
||||
if (attachedModel.IsProp)
|
||||
var t = m.GetTransform();
|
||||
if (m.IsProp)
|
||||
{
|
||||
attachedModel.Attachments.SafeDetach(model, t);
|
||||
m.Attachments.SafeDetach(model, t);
|
||||
RemoveModel(info.Guid);
|
||||
}
|
||||
else if (detach) attachedModel.Attachments.SafeDetach(model, t);
|
||||
else if (detach) m.Attachments.SafeDetach(model, t);
|
||||
}
|
||||
|
||||
if (socket.IsVirtual)
|
||||
|
|
@ -209,8 +210,8 @@ public class Options
|
|||
return texture != null;
|
||||
}
|
||||
|
||||
public bool TryGetModel(out UModel model) => Models.TryGetValue(SelectedModel, out model);
|
||||
public bool TryGetModel(FGuid guid, out UModel model) => Models.TryGetValue(guid, out model);
|
||||
public bool TryGetModel([MaybeNullWhen(false)] out IRenderableModel model) => Models.TryGetValue(SelectedModel, out model);
|
||||
public bool TryGetModel(FGuid guid, [MaybeNullWhen(false)] out IRenderableModel model) => Models.TryGetValue(guid, out model);
|
||||
|
||||
public bool TryGetSection(out Section section) => TryGetSection(SelectedModel, out section);
|
||||
public bool TryGetSection(FGuid guid, out Section section)
|
||||
|
|
@ -223,7 +224,7 @@ public class Options
|
|||
section = null;
|
||||
return false;
|
||||
}
|
||||
public bool TryGetSection(UModel model, out Section section)
|
||||
public bool TryGetSection(IRenderableModel model, out Section section)
|
||||
{
|
||||
if (SelectedSection >= 0 && SelectedSection < model.Sections.Length)
|
||||
section = model.Sections[SelectedSection]; else section = null;
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ using System.Threading;
|
|||
using System.Windows;
|
||||
using CUE4Parse_Conversion.Animations;
|
||||
using CUE4Parse_Conversion.Meshes;
|
||||
using CUE4Parse_Conversion.Options;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Animation;
|
||||
using CUE4Parse.UE4.Assets.Exports.Component.SplineMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.Component.StaticMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.GeometryCollection;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using CUE4Parse.UE4.Assets.Exports.Nanite;
|
||||
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
|
|
@ -29,7 +29,6 @@ using FModel.Views.Snooper.Lights;
|
|||
using FModel.Views.Snooper.Models;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
using OpenTK.Windowing.GraphicsLibraryFramework;
|
||||
using UModel = FModel.Views.Snooper.Models.UModel;
|
||||
|
||||
namespace FModel.Views.Snooper;
|
||||
|
||||
|
|
@ -175,7 +174,7 @@ public class Renderer : IDisposable
|
|||
t.Scale = offset.Scale3D;
|
||||
}
|
||||
|
||||
UModel addedModel = null;
|
||||
IRenderableModel addedModel = null;
|
||||
switch (export)
|
||||
{
|
||||
case UStaticMesh st:
|
||||
|
|
@ -344,7 +343,7 @@ public class Renderer : IDisposable
|
|||
wnd.WindowShouldClose(true, true);
|
||||
}
|
||||
|
||||
private void LoadStaticMesh(UStaticMesh original, ENaniteMeshFormat naniteFormat = ENaniteMeshFormat.OnlyNormalLODs)
|
||||
private void LoadStaticMesh(UStaticMesh original, ENaniteMeshFormat naniteFormat = ENaniteMeshFormat.NoNanite)
|
||||
{
|
||||
var guid = original.LightingGuid;
|
||||
if (Options.TryGetModel(guid, out var model))
|
||||
|
|
@ -713,7 +712,7 @@ public class Renderer : IDisposable
|
|||
continue;
|
||||
|
||||
var parameters = new CMaterialParams2();
|
||||
unrealMaterial.GetParams(parameters, EMaterialFormat.FirstLayer);
|
||||
unrealMaterial.GetParams(parameters, EMaterialDepth.TopLayerOnly);
|
||||
|
||||
if (!parameters.TryGetLinearColor(out var color, "Color"))
|
||||
color = FLinearColor.Gray;
|
||||
|
|
@ -726,7 +725,7 @@ public class Renderer : IDisposable
|
|||
if (!material.TryLoad(out UMaterialInterface unrealMaterial)) continue;
|
||||
|
||||
var parameters = new CMaterialParams2();
|
||||
unrealMaterial.GetParams(parameters, EMaterialFormat.FirstLayer);
|
||||
unrealMaterial.GetParams(parameters, EMaterialDepth.TopLayerOnly);
|
||||
|
||||
if (!byte.TryParse(material.Name.SubstringAfterLast("_"), out var indexAsByte))
|
||||
indexAsByte = byte.MaxValue;
|
||||
|
|
|
|||
|
|
@ -317,7 +317,7 @@ public class Material : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
public bool ImGuiTextures(Dictionary<string, Texture> icons, UModel model)
|
||||
public bool ImGuiTextures(Dictionary<string, Texture> icons, IRenderableModel model)
|
||||
{
|
||||
if (ImGui.BeginTable("material_textures", 2))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using FModel.Framework;
|
||||
using ImGuiNET;
|
||||
|
|
@ -38,30 +37,10 @@ public class Swap
|
|||
}
|
||||
}
|
||||
|
||||
public class Save
|
||||
{
|
||||
public bool Value;
|
||||
public string Label;
|
||||
public string Path;
|
||||
|
||||
public Save()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Value = false;
|
||||
Label = string.Empty;
|
||||
Path = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public class SnimGui
|
||||
{
|
||||
public readonly ImGuiController Controller;
|
||||
private readonly Swap _swapper = new ();
|
||||
private readonly Save _saver = new ();
|
||||
private readonly string _renderer;
|
||||
private readonly string _version;
|
||||
private readonly float _tableWidth;
|
||||
|
|
@ -96,7 +75,7 @@ public class SnimGui
|
|||
|
||||
SectionWindow("Material Inspector", s.Renderer, DrawMaterialInspector, false);
|
||||
AnimationWindow("Timeline", s.Renderer, (icons, tracker, animations) =>
|
||||
tracker.ImGuiTimeline(s, _saver, icons, animations, _outlinerSize, Controller.FontSemiBold));
|
||||
tracker.ImGuiTimeline(s, icons, animations, _outlinerSize, Controller.FontSemiBold));
|
||||
|
||||
Window("World", () => DrawWorld(s), false);
|
||||
|
||||
|
|
@ -144,36 +123,13 @@ public class SnimGui
|
|||
}
|
||||
});
|
||||
|
||||
Modal("Saved", _saver.Value, () =>
|
||||
{
|
||||
ImGui.TextWrapped($"Successfully saved {_saver.Label}");
|
||||
ImGui.Separator();
|
||||
|
||||
var size = new Vector2(120, 0);
|
||||
if (ImGui.Button("OK", size))
|
||||
{
|
||||
_saver.Reset();
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ImGui.SetItemDefaultFocus();
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("Show In Explorer", size))
|
||||
{
|
||||
Process.Start("explorer.exe", $"/select, \"{_saver.Path.Replace('/', '\\')}\"");
|
||||
|
||||
_saver.Reset();
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
});
|
||||
ExportModal.Instance.Draw();
|
||||
}
|
||||
|
||||
private void DrawWorld(Snooper s)
|
||||
{
|
||||
if (ImGui.BeginTable("world_details", 2, ImGuiTableFlags.SizingStretchProp))
|
||||
{
|
||||
var b = false;
|
||||
var length = s.Renderer.Options.Models.Count;
|
||||
|
||||
NoFramePaddingOnY(() =>
|
||||
|
|
@ -184,31 +140,7 @@ public class SnimGui
|
|||
|
||||
if (ImGui.SmallButton("Save All"))
|
||||
{
|
||||
foreach (var model in s.Renderer.Options.Models.Values)
|
||||
{
|
||||
b |= model.Save(out _, out _);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Modal("Saved", b, () =>
|
||||
{
|
||||
ImGui.TextWrapped($"Successfully saved {length} models");
|
||||
ImGui.Separator();
|
||||
|
||||
var size = new Vector2(120, 0);
|
||||
if (ImGui.Button("OK", size))
|
||||
{
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ImGui.SetItemDefaultFocus();
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("Show In Explorer", size))
|
||||
{
|
||||
Process.Start("explorer.exe", $"/select, \"{UserSettings.Default.ModelDirectory.Replace('/', '\\')}\"");
|
||||
ImGui.CloseCurrentPopup();
|
||||
ExportModal.Instance.Export(s.Renderer.Options.Models.Values, UserSettings.Default.ModelDirectory, UserSettings.GetExportOptions());
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -412,7 +344,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
if (ImGui.MenuItem("Save"))
|
||||
{
|
||||
s.WindowShouldFreeze(true);
|
||||
_saver.Value = model.Save(out _saver.Label, out _saver.Path);
|
||||
ExportModal.Instance.Export([model], UserSettings.Default.ModelDirectory, UserSettings.GetExportOptions());
|
||||
s.WindowShouldFreeze(false);
|
||||
}
|
||||
if (ImGui.MenuItem("Animate", model is SkeletalModel))
|
||||
|
|
@ -591,7 +523,11 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
{
|
||||
ImGui.PushID(0); ImGui.BeginDisabled(model.TransformsCount < 2);
|
||||
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
|
||||
ImGui.SliderInt("", ref model.SelectedInstance, 0, model.TransformsCount - 1, "Instance %i", ImGuiSliderFlags.AlwaysClamp);
|
||||
var instance = model.SelectedInstance;
|
||||
if (ImGui.SliderInt("", ref instance, 0, model.TransformsCount - 1, "Instance %i", ImGuiSliderFlags.AlwaysClamp))
|
||||
{
|
||||
model.SelectedInstance = instance;
|
||||
}
|
||||
ImGui.EndDisabled(); ImGui.PopID();
|
||||
|
||||
if (ImGui.BeginTable("guizmo_controls", 2, ImGuiTableFlags.SizingStretchProp))
|
||||
|
|
@ -669,7 +605,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
ImGui.PopStyleVar();
|
||||
}
|
||||
|
||||
private void DrawMaterialInspector(Dictionary<string, Texture> icons, UModel model, Section section)
|
||||
private void DrawMaterialInspector(Dictionary<string, Texture> icons, IRenderableModel model, Section section)
|
||||
{
|
||||
var material = model.Materials[section.MaterialIndex];
|
||||
|
||||
|
|
@ -906,7 +842,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
ImGui.End();
|
||||
}
|
||||
|
||||
private void MeshWindow(string name, Renderer renderer, Action<Dictionary<string, Texture>, UModel> content, bool styled = true)
|
||||
private void MeshWindow(string name, Renderer renderer, Action<Dictionary<string, Texture>, IRenderableModel> content, bool styled = true)
|
||||
{
|
||||
Window(name, () =>
|
||||
{
|
||||
|
|
@ -915,7 +851,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
}, styled);
|
||||
}
|
||||
|
||||
private void SectionWindow(string name, Renderer renderer, Action<Dictionary<string, Texture>, UModel, Section> content, bool styled = true)
|
||||
private void SectionWindow(string name, Renderer renderer, Action<Dictionary<string, Texture>, IRenderableModel, Section> content, bool styled = true)
|
||||
{
|
||||
MeshWindow(name, renderer, (icons, model) =>
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user