This commit is contained in:
Valentin 2026-06-20 19:20:08 +00:00 committed by GitHub
commit 23333cbdfa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
63 changed files with 3614 additions and 1245 deletions

@ -1 +1 @@
Subproject commit 024b005c4d15e8082ecebfb202700d59bb6113c0
Subproject commit 8d145bb84efee7dfc1efe9ed2b2d9e53d8b463c3

View File

@ -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;
@ -110,25 +112,39 @@ public partial class App
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Logs"));
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data"));
const string template = "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] {Enriched}: {Message:lj}{NewLine}{Exception}";
#if DEBUG
var filePath = Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Debug-Log-{DateTime.Now:yyyy-MM-dd}.log");
#else
var filePath = Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Log-{DateTime.Now:yyyy-MM-dd}.log");
#endif
const string template1 = "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] {Enriched}: {Message:lj}{NewLine}{Exception}";
const string template2 = "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{ClassName}] {ObjectPath}: {Message:lj}{NewLine}{Exception}";
Log.Logger = new LoggerConfiguration()
#if DEBUG
.Enrich.With<SourceEnricher>()
.MinimumLevel.Verbose()
.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"))
#else
.Enrich.With<CallerEnricher>()
.WriteTo.File(outputTemplate: template,
path: Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Log-{DateTime.Now:yyyy-MM-dd}.log"))
#endif
.WriteTo.Logger(lc => lc
.Filter.ByExcluding(IsConversionLibrary)
.WriteTo.Console(outputTemplate: template1, theme: AnsiConsoleTheme.Literate)
.WriteTo.File(outputTemplate: template1, path: filePath))
.WriteTo.Logger(lc => lc
.Filter.ByIncludingOnly(IsConversionLibrary)
.WriteTo.Console(outputTemplate: template2, theme: AnsiConsoleTheme.Literate)
.WriteTo.File(outputTemplate: template2, path: filePath))
.MinimumLevel.Override("CUE4Parse_Conversion", LogEventLevel.Verbose).WriteTo.Sink(ImGuiSink.Instance)
.CreateLogger();
Log.Information("Version {Version} ({CommitId})", Constants.APP_VERSION, Constants.APP_COMMIT_ID);
Log.Information("{OS}", GetOperatingSystemProductName());
Log.Information("{RuntimeVer}", RuntimeInformation.FrameworkDescription);
Log.Information("Culture {SysLang}", CultureInfo.CurrentCulture);
static bool IsConversionLibrary(LogEvent e) =>
e.Properties.TryGetValue("SourceContext", out var sc) &&
sc.ToString().Contains("CUE4Parse_Conversion");
}
private void AppExit(object sender, ExitEventArgs e)

View File

@ -56,8 +56,13 @@ public class BaseIcon : UCreator
Preview = Utils.GetBitmap(otherPreview);
else if (Object.TryGetValue(out UMaterialInstanceConstant materialInstancePreview, "EventCalloutImage"))
Preview = Utils.GetBitmap(materialInstancePreview);
else if (Object.TryGetValue(out FStructFallback brush, "IconBrush") && brush.TryGetValue(out UTexture2D res, "ResourceObject"))
else if (Object.TryGetValue(out FStructFallback brush, "IconBrush", "BuildingSymbolNormal") && brush.TryGetValue(out UTexture2D res, "ResourceObject"))
Preview = Utils.GetBitmap(res);
else if (Object.TryGetValue(out FStructFallback mission, "MissionIcons", "PopupWidgetData"))
{
if (mission.TryGetValue(out FStructFallback brushsize, "Brush_XL", "Brush_L", "Brush_M", "Brush_S", "Brush_XS", "Brush_XXS", "AvailableIcon", "UnavailableIcon") && brushsize.TryGetValue(out UTexture2D res2, "ResourceObject"))
Preview = Utils.GetBitmap(res2);
}
}
// text

View File

@ -51,6 +51,11 @@ public class CreatorPackage : IDisposable
case "CosmeticShoesItemDefinition":
case "CosmeticCompanionItemDefinition":
case "CosmeticCompanionReactFXItemDefinition":
case "MagpieEntitlementRewardDefinition":
case "FortDeferredItemGrantDefinition":
case "BattleLabDeviceItemDefinition":
case "PiggybackDanceItemDefinition":
case "MyTownBuildingDefinitionData":
case "AthenaPickaxeItemDefinition":
case "AthenaGadgetItemDefinition":
case "AthenaGliderItemDefinition":
@ -64,6 +69,7 @@ public class CreatorPackage : IDisposable
case "FortTokenType":
case "FortAbilityKit":
case "FortWorkerType":
case "FortMissionInfo":
case "RewardGraphToken":
case "JunoKnowledgeBundle":
case "FortBannerTokenType":

View File

@ -109,6 +109,7 @@ public enum EBulkType
Audio = 1 << 5,
Code = 1 << 6,
Raw = 1 << 7,
Worlds = 1 << 8,
}
public enum EAssetCategory : uint

View 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;
}
}

View File

@ -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>

View File

@ -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 E000F8FF (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

View File

@ -19,12 +19,12 @@ public class ViewModel : INotifyPropertyChanged, INotifyDataErrorInfo, IDataErro
if (string.IsNullOrEmpty(propertyName))
return Error;
return _validationErrors.ContainsKey(propertyName) ? string.Join(Environment.NewLine, _validationErrors[propertyName]) : string.Empty;
return _validationErrors.TryGetValue(propertyName, out IList<string> validationError) ? string.Join(Environment.NewLine, validationError) : string.Empty;
}
}
[JsonIgnore] public string Error => string.Join(Environment.NewLine, GetAllErrors());
[JsonIgnore] public bool HasErrors => _validationErrors.Any();
[JsonIgnore] public virtual bool HasErrors => _validationErrors.Count != 0;
public IEnumerable GetErrors(string propertyName)
{
@ -49,8 +49,7 @@ public class ViewModel : INotifyPropertyChanged, INotifyDataErrorInfo, IDataErro
public void ClearValidationErrors(string propertyName)
{
if (_validationErrors.ContainsKey(propertyName))
_validationErrors.Remove(propertyName);
_validationErrors.Remove(propertyName);
}
public event PropertyChangedEventHandler PropertyChanged;
@ -72,4 +71,4 @@ public class ViewModel : INotifyPropertyChanged, INotifyDataErrorInfo, IDataErro
RaisePropertyChanged(propertyName);
return true;
}
}
}

View File

@ -39,7 +39,7 @@ public static class Helper
else
{
var w = GetOpenedWindow<T>(windowName);
if (windowName == "Search For Packages") w.WindowState = WindowState.Normal;
if (w.WindowState == WindowState.Minimized) w.WindowState = WindowState.Normal;
w.Focus();
}
}

View File

@ -2,6 +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:viewModels="clr-namespace:FModel.ViewModels"
xmlns:controls="clr-namespace:FModel.Views.Resources.Controls"
xmlns:inputs="clr-namespace:FModel.Views.Resources.Controls.Inputs"
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
@ -24,6 +25,28 @@
</TaskbarItemInfo.ProgressState>
</TaskbarItemInfo>
</Window.TaskbarItemInfo>
<Window.Resources>
<Canvas x:Key="ExportSessionIcon" x:Shared="False" Width="24" Height="24">
<Path Stroke="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}"
StrokeThickness="2" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round"
Data="M12 21l-8 -4.5v-9l8 -4.5l8 4.5v4.5" />
<Path Stroke="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}"
StrokeThickness="2" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round"
Data="M12 12l8 -4.5" />
<Path Stroke="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}"
StrokeThickness="2" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round"
Data="M12 12v9" />
<Path Stroke="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}"
StrokeThickness="2" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round"
Data="M12 12l-8 -4.5" />
<Path Stroke="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}"
StrokeThickness="2" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round"
Data="M15 18h7" />
<Path Stroke="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}"
StrokeThickness="2" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round"
Data="M19 15l3 3l-3 3" />
</Canvas>
</Window.Resources>
<adonisControls:AdonisWindow.Style>
<Style TargetType="adonisControls:AdonisWindow" BasedOn="{StaticResource {x:Type adonisControls:AdonisWindow}}" >
<Setter Property="Title" Value="{Binding DataContext.InitialWindowTitle, RelativeSource={RelativeSource Self}}" />
@ -134,6 +157,13 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Export Session" Command="{Binding MenuCommand}" CommandParameter="Views_ExportSession">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<StaticResource ResourceKey="ExportSessionIcon" />
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Audio Player" Command="{Binding MenuCommand}" CommandParameter="Views_AudioPlayer">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
@ -211,7 +241,7 @@
<TextBlock Grid.Column="0" Text="Preview New Explorer System" VerticalAlignment="Center" />
<CheckBox Grid.Column="1" Margin="5 2 5 0" Unchecked="FeaturePreviewOnUnchecked" KeyboardNavigation.TabNavigation="None" KeyboardNavigation.ControlTabNavigation="None"
IsChecked="{Binding FeaturePreviewNewAssetExplorer, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
IsChecked="{Binding FeaturePreviewNewAssetExplorer, Source={x:Static settings:UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}"/>
</Grid>
</Grid>
@ -253,7 +283,7 @@
Command="{Binding LoadingModes.LoadCommand}" IsEnabled="{Binding Status.IsReady}"
CommandParameter="{Binding SelectedItems, ElementName=DirectoryFilesListBox}" />
</Grid>
<Grid DockPanel.Dock="Top">
<Grid DockPanel.Dock="Top" Grid.IsSharedSizeScope="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
@ -279,7 +309,7 @@
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding SelectedItem.MountPoint, ElementName=DirectoryFilesListBox, FallbackValue='/', Converter={x:Static converters:TrimRightToLeftConverter.Instance}, ConverterParameter=275}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="Mount Point" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding SelectedItem.FileCount, ElementName=DirectoryFilesListBox, FallbackValue='0', StringFormat={}{0} Files}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding SelectedItem.FileCount, ElementName=DirectoryFilesListBox, FallbackValue='0', StringFormat={}{0:N0} Files}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="File Count" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="{Binding SelectedItem.IsEncrypted, ElementName=DirectoryFilesListBox, FallbackValue='False'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="2" Grid.Column="1" Text="Is Encrypted" VerticalAlignment="Center" HorizontalAlignment="Right" />
@ -371,9 +401,9 @@
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding SelectedItem.AssetsList.Assets.Count, ElementName=AssetsFolderName, FallbackValue=0}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding SelectedItem.AssetsList.Assets.Count, ElementName=AssetsFolderName, FallbackValue=0, StringFormat={}{0:N0}}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="Packages Count" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding SelectedItem.FoldersView.Count, ElementName=AssetsFolderName, FallbackValue=0}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding SelectedItem.FoldersView.Count, ElementName=AssetsFolderName, FallbackValue=0, StringFormat={}{0:N0}}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="Folders Count" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="{Binding SelectedItem.Archive, ElementName=AssetsFolderName, FallbackValue='None'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="2" Grid.Column="1" Text="Included In Archive" VerticalAlignment="Center" HorizontalAlignment="Right" />
@ -588,7 +618,9 @@
Command="{Binding MenuCommand}" CommandParameter="ToolBox_Open_Output_Directory" Focusable="False">
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource FolderIcon}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M12 6h-6a2 2 0 0 0 -2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-6" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M11 13l9 -9" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M15 4h5v5" />
</Canvas>
</Viewbox>
</Button>
@ -698,23 +730,82 @@
</TextBlock>
</StatusBarItem>
<StatusBarItem Margin="0 0 5 0" HorizontalAlignment="Right">
<StatusBarItem Margin="0 0 5 0" Padding="0"
HorizontalAlignment="Right" VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
<StackPanel Orientation="Horizontal">
<StatusBarItem Margin="0 0 10 0" HorizontalContentAlignment="Stretch">
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource StatusBarIcon}" />
</Canvas>
</Viewbox>
<StatusBarItem Margin="0 0 10 0" Padding="0" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
<Button x:Name="ExportSessionButton"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Center"
Command="{Binding MenuCommand}" CommandParameter="Views_ExportSession"
ToolTip="There are items waiting in the export queue, click to open the export session and process them!"
Background="{DynamicResource {x:Static adonisUi:Brushes.AlertBrush}}"
Style="{StaticResource StatusBarButton}"
Visibility="{Binding Session.TotalQueued, Source={x:Static viewModels:ExportSessionViewModel.Instance}, Converter={x:Static converters:IntGreaterThanZeroToVisibilityConverter.Instance}}">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<Viewbox Width="16" Height="16">
<StaticResource ResourceKey="ExportSessionIcon" />
</Viewbox>
<TextBlock Margin="10 0 0 0" Text="{Binding Session.TotalQueued, Source={x:Static viewModels:ExportSessionViewModel.Instance}, Mode=OneWay, StringFormat=Queued Items: {0}}" />
</StackPanel>
</Button>
</StatusBarItem>
<StatusBarItem Margin="10 0 0 0">
<TextBlock Text="{Binding LastUpdateCheck, Source={x:Static local:Settings.UserSettings.Default}, Converter={x:Static converters:RelativeDateTimeConverter.Instance}, StringFormat=Last Refresh: {0}}" />
<StatusBarItem Margin="0">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource StatusBarIcon}" />
</Canvas>
</Viewbox>
<TextBlock Margin="10 0 0 0" Text="{Binding LastUpdateCheck, Source={x:Static settings:UserSettings.Default}, Converter={x:Static converters:RelativeDateTimeConverter.Instance}, StringFormat=Last Refresh: {0}}" />
</StackPanel>
</StatusBarItem>
</StackPanel>
</StatusBarItem>
</StatusBar>
<controls:DropOverlay Grid.RowSpan="99"
Panel.ZIndex="1000" />
<controls:DropOverlay Grid.Row="0" Grid.RowSpan="99" Panel.ZIndex="1000" />
<Popup Grid.Row="0" Grid.RowSpan="99" Panel.ZIndex="999"
PlacementTarget="{Binding ElementName=ExportSessionButton}" Placement="Custom"
CustomPopupPlacementCallback="OnQueueToastCustomPopupPlacement"
IsOpen="{Binding ShowQueueToast, Source={x:Static viewModels:ExportSessionViewModel.Instance}}"
AllowsTransparency="True" IsHitTestVisible="False">
<Border CornerRadius="5" Padding="10"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer2BackgroundBrush}}"
BorderBrush="{DynamicResource {x:Static adonisUi:Brushes.Layer1BorderBrush}}"
BorderThickness="1">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<Viewbox Width="28" Height="28" VerticalAlignment="Center" Margin="0 0 10 0">
<Canvas Width="24" Height="24">
<Path Stroke="{DynamicResource {x:Static adonisUi:Brushes.AccentBrush}}"
StrokeThickness="2" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round"
Data="M8 9h8" />
<Path Stroke="{DynamicResource {x:Static adonisUi:Brushes.AccentBrush}}"
StrokeThickness="2" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round"
Data="M8 13h6" />
<Path Stroke="{DynamicResource {x:Static adonisUi:Brushes.AccentBrush}}"
StrokeThickness="2" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round"
Data="M15 18h-2l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v5.5" />
<Path Stroke="{DynamicResource {x:Static adonisUi:Brushes.AccentBrush}}"
StrokeThickness="2" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round"
Data="M19 16v3" />
<Path Stroke="{DynamicResource {x:Static adonisUi:Brushes.AccentBrush}}"
StrokeThickness="2" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round"
Data="M19 22v.01" />
</Canvas>
</Viewbox>
<StackPanel VerticalAlignment="Center">
<TextBlock Text="Export Session" FontWeight="SemiBold"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" />
<TextBlock Text="{Binding ToolTip, ElementName=ExportSessionButton}"
MaxWidth="250" TextWrapping="Wrap" FontSize="11"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
</StackPanel>
</StackPanel>
</Border>
</Popup>
</Grid>
</adonisControls:AdonisWindow>

View File

@ -376,4 +376,15 @@ public partial class MainWindow
childFolder.IsExpanded = true;
childFolder.IsSelected = true;
}
private CustomPopupPlacement[] OnQueueToastCustomPopupPlacement(Size popupSize, Size targetSize, Point offset)
{
return
[
new CustomPopupPlacement(
new Point((targetSize.Width - popupSize.Width) / 2, -popupSize.Height - 10),
PopupPrimaryAxis.Horizontal
)
];
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -4,14 +4,10 @@ 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.Lua.unluac;
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.Extensions.Themes;
using FModel.Framework;
using FModel.ViewModels;
@ -58,26 +54,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,
Default.TextureQuality,
Default.SaveHdrTexturesAsHdr,
Default.MaterialExportFormat,
Default.SaveEmbeddedMaterials,
Default.SaveMorphTargets,
Default.SocketExportFormat,
Default.CompressionFormat
);
}
private bool _showChangelog = true;
public bool ShowChangelog
@ -402,6 +395,13 @@ namespace FModel.Settings
set => SetProperty(ref _addAudio, value);
}
private Hotkey _removeAudio = new(Key.X);
public Hotkey RemoveAudio
{
get => _removeAudio;
set => SetProperty(ref _removeAudio, value);
}
private Hotkey _playPauseAudio = new(Key.K);
public Hotkey PlayPauseAudio
{
@ -430,15 +430,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);
@ -451,6 +458,13 @@ namespace FModel.Settings
set => SetProperty(ref _textureExportFormat, value);
}
private int _textureQuality = 100;
public int TextureQuality
{
get => _textureQuality;
set => SetProperty(ref _textureQuality, value);
}
private ESocketFormat _socketExportFormat = ESocketFormat.Bone;
public ESocketFormat SocketExportFormat
{
@ -465,13 +479,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
{

View File

@ -239,6 +239,20 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
});
}
public void Unload()
{
Application.Current.Dispatcher.Invoke(() =>
{
_waveSource = null;
PlayedFile = new AudioFile(-1, "No audio file");
Spectrum = null;
RaiseSourceEvent(ESourceEventType.Clearing);
ClearSoundOut();
});
}
public void AddToPlaylist(byte[] data, string filePath)
{
Application.Current.Dispatcher.Invoke(() =>
@ -270,11 +284,30 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
if (_audioFiles.Count < 1) return;
Application.Current.Dispatcher.Invoke(() =>
{
var removedPlaying = false;
if (PlayedFile.Id == SelectedAudioFile.Id)
{
removedPlaying = true;
Stop();
}
_audioFiles.RemoveAt(SelectedAudioFile.Id);
for (var i = 0; i < _audioFiles.Count; i++)
{
_audioFiles[i].Id = i;
}
if (_audioFiles.Count < 1)
{
Unload();
return;
}
SelectedAudioFile = SelectedAudioFile.Id >= _audioFiles.Count ? _audioFiles.Last() : _audioFiles[SelectedAudioFile.Id];
if (!removedPlaying) return;
Load();
Play();
});
}
@ -526,6 +559,11 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
_soundOut.Volume = UserSettings.Default.AudioPlayerVolume / 100;
}
private void ClearSoundOut()
{
_soundOut = null;
}
private IEnumerable<MMDevice> EnumerateDevices()
{
using var deviceEnumerator = new MMDeviceEnumerator();

View File

@ -44,7 +44,6 @@ using CUE4Parse.UE4.Assets.Exports.StaticMesh;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Assets.Exports.Verse;
using CUE4Parse.UE4.Assets.Exports.Wwise;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.BinaryConfig;
using CUE4Parse.UE4.CriWare;
using CUE4Parse.UE4.CriWare.Readers;
@ -62,7 +61,7 @@ using CUE4Parse.UE4.Shaders;
using CUE4Parse.UE4.Versions;
using CUE4Parse.UE4.Wwise;
using CUE4Parse.Utils;
using CUE4Parse_Conversion;
using CUE4Parse_Conversion.Exporters;
using CUE4Parse_Conversion.Sounds;
using CUE4Parse.MappingsProvider.Jmap;
using CUE4Parse.MappingsProvider.Usmap;
@ -213,6 +212,7 @@ public class CUE4ParseViewModel : ViewModel
Provider.ReadScriptData = UserSettings.Default.ReadScriptData;
Provider.ReadShaderMaps = UserSettings.Default.ReadShaderMaps;
Provider.ReadNaniteData = true;
PropertyUtil.SearchPropertyInTemplate = true; // search template properties when looking for a prop via GetOrDefault and cie
GameDirectory = new GameDirectoryViewModel();
AssetsFolder = new AssetsFolderViewModel();
@ -617,7 +617,7 @@ public class CUE4ParseViewModel : ViewModel
Parallel.ForEach(folder.AssetsList.Assets, entry =>
{
cancellationToken.ThrowIfCancellationRequested();
ExportData(entry.Asset, false);
ExportData(entry.Asset);
});
foreach (var f in folder.Folders) ExportFolder(cancellationToken, f);
@ -626,27 +626,6 @@ public class CUE4ParseViewModel : ViewModel
public void ExtractFolder(CancellationToken cancellationToken, TreeItem folder, EBulkType bulk)
=> BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, bulk));
public void ExtractFolder(CancellationToken cancellationToken, TreeItem folder)
=> BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs));
public void SaveFolder(CancellationToken cancellationToken, TreeItem folder)
=> BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Properties | EBulkType.Auto));
public void TextureFolder(CancellationToken cancellationToken, TreeItem folder)
=> BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Textures | EBulkType.Auto));
public void ModelFolder(CancellationToken cancellationToken, TreeItem folder)
=> BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Meshes | EBulkType.Auto));
public void AnimationFolder(CancellationToken cancellationToken, TreeItem folder)
=> BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Animations | EBulkType.Auto));
public void AudioFolder(CancellationToken cancellationToken, TreeItem folder)
=> BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Audio | EBulkType.Auto));
public void CodeFolder(CancellationToken cancellationToken, TreeItem folder)
=> BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Code | EBulkType.Auto));
public void Extract(CancellationToken cancellationToken, GameFile entry, bool addNewTab = false, EBulkType bulk = EBulkType.None)
{
ApplicationService.ApplicationView.IsAssetsExplorerVisible = false;
@ -1520,10 +1499,11 @@ public class CUE4ParseViewModel : ViewModel
case UStaticMesh when HasFlag(bulk, EBulkType.Meshes):
case USkeletalMesh when HasFlag(bulk, EBulkType.Meshes):
case USkeleton when UserSettings.Default.SaveSkeletonAsMesh && HasFlag(bulk, EBulkType.Meshes):
// case UMaterialInstance when HasFlag(bulk, EBulkType.Materials): // read the fucking json
case UAnimSequenceBase when HasFlag(bulk, EBulkType.Animations):
// case UMaterialInterface when HasFlag(bulk, EBulkType.Materials):
case UAnimationAsset when HasFlag(bulk, EBulkType.Animations):
case UWorld when HasFlag(bulk, EBulkType.Worlds):
{
SaveExport(pointer.Object.Value, updateUi);
SaveExport(pointer.Object.Value);
return true;
}
default:
@ -1680,64 +1660,27 @@ public class CUE4ParseViewModel : ViewModel
});
}
private void SaveExport(UObject export, bool updateUi = true)
private void SaveExport(UObject export)
{
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))
try
{
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);
});
}
ExportSessionViewModel.Instance.Session.Add(export);
}
else
catch (Exception e)
{
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));
Log.Error(e, "Could not add to export session");
}
}
private readonly object _rawData = new ();
public void ExportData(GameFile entry, bool updateUi = true)
public void ExportData(GameFile entry)
{
if (Provider.TrySavePackage(entry, out var assets))
try
{
string path = UserSettings.Default.RawDataDirectory;
Parallel.ForEach(assets, kvp =>
{
lock (_rawData)
{
path = Path.Combine(UserSettings.Default.RawDataDirectory, UserSettings.Default.KeepDirectoryStructure ? kvp.Key : kvp.Key.SubstringAfterLast('/')).Replace('\\', '/');
Directory.CreateDirectory(path.SubstringBeforeLast('/'));
File.WriteAllBytes(path, kvp.Value);
}
});
Interlocked.Increment(ref ExportedCount);
Log.Information("{FileName} successfully exported", entry.Name);
if (updateUi)
{
FLogger.Append(ELog.Information, () =>
{
FLogger.Text("Successfully exported ", Constants.WHITE);
FLogger.Link(entry.Name, path, true);
});
}
ExportSessionViewModel.Instance.Session.Add(new RawDataExporter(entry, Provider));
}
else
catch (Exception e)
{
Interlocked.Increment(ref FailedExportCount);
Log.Error("{FileName} could not be exported", entry.Name);
if (updateUi)
FLogger.Append(ELog.Error, () => FLogger.Text($"Could not export '{entry.Name}'", Constants.WHITE, true));
Log.Error(e, "Could not add to export session");
}
}

View File

@ -40,6 +40,9 @@ public class MenuCommand : ViewModelCommand<ApplicationViewModel>
case "Views_3dViewer":
contextViewModel.CUE4Parse.SnooperViewer.Run();
break;
case "Views_ExportSession":
Helper.OpenWindow<AdonisWindow>("Export Session", () => new ExportSessionWindow().Show());
break;
case "Views_AudioPlayer":
Helper.OpenWindow<AdonisWindow>("Audio Player", () => new AudioPlayer().Show());
break;

View File

@ -67,6 +67,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
"Save_Properties" => (EAction.Export, EShowAssetType.None, EBulkType.Properties),
"Save_Textures" => (EAction.Export, EShowAssetType.None, EBulkType.Textures),
"Save_Models" => (EAction.Export, EShowAssetType.None, EBulkType.Meshes),
"Save_Worlds" => (EAction.Export, EShowAssetType.None, EBulkType.Worlds),
"Save_Animations" => (EAction.Export, EShowAssetType.None, EBulkType.Animations),
"Save_Audio" => (EAction.Export, EShowAssetType.None, EBulkType.Audio),
"Save_Code" => (EAction.Export, EShowAssetType.None, EBulkType.Code),
@ -108,6 +109,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
EBulkType.Properties => (UserSettings.Default.PropertiesDirectory, "json files"),
EBulkType.Textures => (UserSettings.Default.TextureDirectory, "textures"),
EBulkType.Meshes => (UserSettings.Default.ModelDirectory, "models"),
EBulkType.Worlds => (UserSettings.Default.ModelDirectory, "worlds"),
EBulkType.Animations => (UserSettings.Default.ModelDirectory, "animations"),
EBulkType.Audio => (UserSettings.Default.AudioDirectory, "audio files"),
EBulkType.Code => (UserSettings.Default.CodeDirectory, "code files"),
@ -126,16 +128,17 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
foreach (var folder in folders)
{
cancellationToken.ThrowIfCancellationRequested();
var queuedBefore = ExportSessionViewModel.Instance.Session.TotalQueued;
folderAction(folder);
var path = Path.Combine(dirType, UserSettings.Default.KeepDirectoryStructure ? folder.PathAtThisPoint : folder.PathAtThisPoint.SubstringAfterLast('/')).Replace('\\', '/');
LogExport(contextViewModel, folder.PathAtThisPoint, path, dirType, filetype);
LogExport(contextViewModel, folder.PathAtThisPoint, path, dirType, filetype, queuedBefore);
}
Action<GameFile, EBulkType, bool> fileAction = bulktype switch
Action<GameFile, EBulkType> fileAction = bulktype switch
{
EBulkType.Raw => (entry, _, update) => contextViewModel.CUE4Parse.ExportData(entry, !update),
_ => (entry, bulk, update) => contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, bulk),
EBulkType.Raw => (entry, _) => contextViewModel.CUE4Parse.ExportData(entry),
_ => (entry, bulk) => contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, bulk),
};
foreach (var group in assetsGroups)
@ -144,24 +147,26 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
var list = group.ToArray();
var update = list.Length > 1;
var bulk = bulktype | (update ? EBulkType.Auto : EBulkType.None);
var queuedBefore = ExportSessionViewModel.Instance.Session.TotalQueued;
foreach (var entry in list)
{
Thread.Yield();
cancellationToken.ThrowIfCancellationRequested();
fileAction(entry, bulk, update);
fileAction(entry, bulk);
}
if (update)
{
var path = Path.Combine(dirType, UserSettings.Default.KeepDirectoryStructure ? directory : directory.SubstringAfterLast('/')).Replace('\\', '/');
LogExport(contextViewModel, directory, path, dirType, filetype);
LogExport(contextViewModel, directory, path, dirType, filetype, queuedBefore);
}
}
});
}
private void LogExport(ApplicationViewModel contextViewModel, string directory, string path, string basePath, string fileType)
private void LogExport(ApplicationViewModel contextViewModel, string directory, string path, string basePath, string fileType, int queuedBefore = 0)
{
var queuedDelta = ExportSessionViewModel.Instance.Session.TotalQueued - queuedBefore;
if (contextViewModel.CUE4Parse.ExportedCount > 0)
{
FLogger.Append(ELog.Information, () =>
@ -170,6 +175,13 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
FLogger.Link(directory, Path.Exists(path) ? path : basePath, true);
});
}
else if (queuedDelta > 0)
{
FLogger.Append(ELog.Information, () =>
{
FLogger.Text($"Queued {queuedDelta} {fileType} for export from {directory}", Constants.WHITE, true);
});
}
else if (contextViewModel.CUE4Parse.FailedExportCount == 0)
{
// Not an error because folder simply might not contain type of asset user is trying to save

View File

@ -31,9 +31,15 @@ public class TabCommand : ViewModelCommand<TabItem>
case "Close_Other_Tabs":
_applicationView.CUE4Parse.TabControl.RemoveOtherTabs(tabViewModel);
break;
case "Assets_Show_Metadata":
_applicationView.CUE4Parse.ShowMetadata(tabViewModel.Entry);
break;
case "Find_References":
_applicationView.CUE4Parse.FindReferences(tabViewModel.Entry);
break;
case "Assets_Decompile":
_applicationView.CUE4Parse.Decompile(tabViewModel.Entry);
break;
case "Save_Data":
await _threadWorkerView.Begin(_ => _applicationView.CUE4Parse.ExportData(tabViewModel.Entry));
break;
@ -55,6 +61,12 @@ public class TabCommand : ViewModelCommand<TabItem>
_applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Meshes);
});
break;
case "Save_Worlds":
await _threadWorkerView.Begin(cancellationToken =>
{
_applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Worlds);
});
break;
case "Save_Animations":
await _threadWorkerView.Begin(cancellationToken =>
{
@ -77,9 +89,21 @@ public class TabCommand : ViewModelCommand<TabItem>
}.Show();
});
break;
case "Copy_Asset_Path":
case "File_Path":
Clipboard.SetText(tabViewModel.Entry.Path);
break;
case "File_Name":
Clipboard.SetText(tabViewModel.Entry.Name);
break;
case "Directory_Path":
Clipboard.SetText(tabViewModel.Entry.Directory);
break;
case "File_Path_No_Extension":
Clipboard.SetText(tabViewModel.Entry.PathWithoutExtension);
break;
case "File_Name_No_Extension":
Clipboard.SetText(tabViewModel.Entry.NameWithoutExtension);
break;
}
}
}

View File

@ -0,0 +1,202 @@
using System;
using System.Collections.Generic;
using System.Windows.Threading;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse_Conversion.Options;
using CUE4Parse_Conversion.Writers.UEFormat.Enums;
using FModel.Framework;
using FModel.Settings;
namespace FModel.ViewModels;
public class ExportOptionsViewModel : ViewModel
{
public bool OverrideOptions
{
get;
set => SetProperty(ref field, value);
}
private DispatcherTimer? _feedbackTimer;
public string? FeedbackMessage
{
get;
private set
{
if (!SetProperty(ref field, value)) return;
RaisePropertyChanged(nameof(HasFeedback));
_feedbackTimer?.Stop();
if (string.IsNullOrWhiteSpace(value)) return;
if (_feedbackTimer == null)
{
_feedbackTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2) };
#pragma warning disable CA2011
_feedbackTimer.Tick += (_, _) => FeedbackMessage = null;
#pragma warning restore CA2011
}
_feedbackTimer.Start();
}
}
public bool HasFeedback => FeedbackMessage != null;
public string OutputDirectory
{
get;
set => SetProperty(ref field, value);
}
public IEnumerable<EMeshFormat> MeshFormats { get; } = Enum.GetValues<EMeshFormat>();
public EMeshFormat SelectedMeshFormat
{
get;
set
{
if (!SetProperty(ref field, value)) return;
RaisePropertyChanged(nameof(SocketSettingsEnabled));
RaisePropertyChanged(nameof(CompressionSettingsEnabled));
}
}
public IEnumerable<ENaniteMeshFormat> NaniteMeshFormats { get; } = Enum.GetValues<ENaniteMeshFormat>();
public ENaniteMeshFormat SelectedNaniteMeshFormat
{
get;
set
{
if (!SetProperty(ref field, value)) return;
RaisePropertyChanged(nameof(ShowNaniteWarning));
}
}
public bool ShowNaniteWarning => SelectedNaniteMeshFormat != ENaniteMeshFormat.NoNanite;
public IEnumerable<EMeshQuality> MeshQualities { get; } = Enum.GetValues<EMeshQuality>();
public EMeshQuality SelectedMeshQuality
{
get;
set => SetProperty(ref field, value);
}
public IEnumerable<ESocketFormat> SocketFormats { get; } = Enum.GetValues<ESocketFormat>();
public ESocketFormat SelectedSocketFormat
{
get;
set => SetProperty(ref field, value);
}
public bool SocketSettingsEnabled => SelectedMeshFormat == EMeshFormat.ActorX;
public IEnumerable<EFileCompressionFormat> CompressionFormats { get; } = Enum.GetValues<EFileCompressionFormat>();
public EFileCompressionFormat SelectedCompressionFormat
{
get;
set => SetProperty(ref field, value);
}
public bool CompressionSettingsEnabled => SelectedMeshFormat == EMeshFormat.UEFormat;
public IEnumerable<EMaterialDepth> MaterialDepths { get; } = Enum.GetValues<EMaterialDepth>();
public EMaterialDepth SelectedMaterialDepth
{
get;
set => SetProperty(ref field, value);
}
public bool ExportMaterials
{
get;
set => SetProperty(ref field, value);
}
public IEnumerable<ETexturePlatform> TexturePlatforms { get; } = Enum.GetValues<ETexturePlatform>();
public ETexturePlatform SelectedTexturePlatform
{
get;
set => SetProperty(ref field, value);
}
public IEnumerable<ETextureFormat> TextureFormats { get; } = Enum.GetValues<ETextureFormat>();
public ETextureFormat SelectedTextureFormat
{
get;
set => SetProperty(ref field, value);
}
public bool ExportHdrTexturesAsHdr
{
get;
set => SetProperty(ref field, value);
}
public int TextureQuality
{
get;
set => SetProperty(ref field, value);
}
public bool ExportMorphTargets
{
get;
set => SetProperty(ref field, value);
}
public ExportOptionsViewModel()
{
ResetToUserDefaults();
}
public void ResetToUserDefaults()
{
OutputDirectory = UserSettings.Default.ModelDirectory;
SelectedMeshFormat = UserSettings.Default.MeshExportFormat;
SelectedNaniteMeshFormat = UserSettings.Default.NaniteMeshExportFormat;
SelectedMeshQuality = UserSettings.Default.MeshQuality;
SelectedSocketFormat = UserSettings.Default.SocketExportFormat;
SelectedCompressionFormat = UserSettings.Default.CompressionFormat;
SelectedMaterialDepth = UserSettings.Default.MaterialExportFormat;
ExportMaterials = UserSettings.Default.SaveEmbeddedMaterials;
SelectedTexturePlatform = UserSettings.Default.CurrentDir.TexturePlatform;
SelectedTextureFormat = UserSettings.Default.TextureExportFormat;
ExportHdrTexturesAsHdr = UserSettings.Default.SaveHdrTexturesAsHdr;
ExportMorphTargets = UserSettings.Default.SaveMorphTargets;
TextureQuality = UserSettings.Default.TextureQuality;
OverrideOptions = false;
FeedbackMessage = "Reset to defaults";
}
public void SaveAsUserDefaults()
{
UserSettings.Default.ModelDirectory = OutputDirectory;
UserSettings.Default.MeshExportFormat = SelectedMeshFormat;
UserSettings.Default.NaniteMeshExportFormat = SelectedNaniteMeshFormat;
UserSettings.Default.MeshQuality = SelectedMeshQuality;
UserSettings.Default.SocketExportFormat = SelectedSocketFormat;
UserSettings.Default.CompressionFormat = SelectedCompressionFormat;
UserSettings.Default.MaterialExportFormat = SelectedMaterialDepth;
UserSettings.Default.SaveEmbeddedMaterials = ExportMaterials;
UserSettings.Default.CurrentDir.TexturePlatform = SelectedTexturePlatform;
UserSettings.Default.TextureExportFormat = SelectedTextureFormat;
UserSettings.Default.SaveHdrTexturesAsHdr = ExportHdrTexturesAsHdr;
UserSettings.Default.SaveMorphTargets = ExportMorphTargets;
UserSettings.Default.TextureQuality = TextureQuality;
UserSettings.Save();
OverrideOptions = false;
FeedbackMessage = "Saved as default";
}
public ExportOptions BuildOptions() => new(
SelectedMeshFormat,
SelectedNaniteMeshFormat,
SelectedMeshQuality,
SelectedTexturePlatform,
SelectedTextureFormat,
TextureQuality,
ExportHdrTexturesAsHdr,
SelectedMaterialDepth,
ExportMaterials,
ExportMorphTargets,
SelectedSocketFormat,
SelectedCompressionFormat
);
}

View File

@ -0,0 +1,395 @@
using System;
using System.Collections.Concurrent;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using CUE4Parse_Conversion;
using CUE4Parse_Conversion.Options;
using CUE4Parse.Utils;
using FModel.Extensions;
using FModel.Framework;
using FModel.Settings;
using FModel.Views.Snooper;
using Serilog.Events;
namespace FModel.ViewModels;
public class ExportSessionViewModel : ViewModel
{
public static ExportSessionViewModel Instance { get; } = new();
private DispatcherTimer? _toastTimer;
public bool ShowQueueToast
{
get;
set
{
if (!SetProperty(ref field, value)) return;
if (!value)
{
_toastTimer?.Stop();
return;
}
if (_toastTimer == null)
{
_toastTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(5) };
_toastTimer.Tick += (_, _) =>
{
field = false;
RaisePropertyChanged(nameof(ShowQueueToast));
_toastTimer.Stop();
};
}
_toastTimer.Stop();
_toastTimer.Start();
}
}
private ExportSession? _session;
public ExportSession Session
{
get
{
if (_session != null) return _session;
_session = new ExportSession();
_session.PropertyChanged += OnSessionPropertyChanged;
return _session;
}
}
public ExportOptionsViewModel Options { get; } = new();
public bool IsRunning
{
get;
private set
{
if (!SetProperty(ref field, value)) return;
RaisePropertyChanged(nameof(CanExport));
}
}
public bool IsFinished
{
get;
private set => SetProperty(ref field, value);
}
public bool CanExport => !IsRunning && Session.TotalQueued > 0;
public int CompletedCount
{
get;
private set => SetProperty(ref field, value);
}
public int SucceededCount
{
get;
private set => SetProperty(ref field, value);
}
public int FailedCount
{
get;
private set => SetProperty(ref field, value);
}
public string? CurrentItemName
{
get;
private set => SetProperty(ref field, value);
}
public TimeSpan ElapsedTime
{
get;
private set => SetProperty(ref field, value);
}
public TimeSpan? EtaTime
{
get;
private set => SetProperty(ref field, value);
}
public bool IsCanceled
{
get;
private set => SetProperty(ref field, value);
}
public double ProgressValue
{
get;
private set => SetProperty(ref field, value);
}
public ObservableCollection<ClassGroupViewModel> ClassGroups { get; } = [];
private CancellationTokenSource? _cts;
private readonly Stopwatch _stopwatch = new();
private readonly ConcurrentQueue<LogEvent> _pendingLogs = new();
private DispatcherTimer? _uiTimer;
private ExportSessionViewModel()
{
ImGuiSink.Instance.OnExporterLogEvent += OnLogEvent;
}
private void OnLogEvent(LogEvent log)
{
_pendingLogs.Enqueue(log);
Application.Current?.Dispatcher.InvokeAsync(DrainLogs);
}
private int _previousCount;
private void OnSessionPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != nameof(ExportSession.TotalQueued)) return;
var count = _session?.TotalQueued ?? 0;
Application.Current?.Dispatcher.InvokeAsync(() =>
{
if (count > 0 && _previousCount == 0)
{
ClearExportHistory();
}
ShowQueueToast = count switch
{
> 0 when _previousCount == 0 => true,
0 => false,
_ => ShowQueueToast
};
_previousCount = count;
RaisePropertyChanged(nameof(CanExport));
});
}
public async Task ExportAsync()
{
if (IsRunning || Session.TotalQueued == 0) return;
IsRunning = true;
IsFinished = false;
IsCanceled = false;
CompletedCount = 0;
SucceededCount = 0;
FailedCount = 0;
_stopwatch.Restart();
_cts = new CancellationTokenSource();
StartUiTimer();
string exportDirectory;
ExportOptions exportOptions;
if (Options.OverrideOptions)
{
exportDirectory = Options.OutputDirectory;
exportOptions = Options.BuildOptions();
}
else
{
exportDirectory = UserSettings.Default.ModelDirectory;
exportOptions = UserSettings.GetExportOptions();
}
var progress = new Progress<ExportProgress>(p =>
{
Application.Current?.Dispatcher.InvokeAsync(() =>
{
CompletedCount = p.Completed;
CurrentItemName = p.LastResult?.ObjectPath;
if (p.LastResult != null)
{
if (p.LastResult.Success) SucceededCount++;
else FailedCount++;
}
ProgressValue = p.Total > 0 ? (double)p.Completed / p.Total : 0;
});
});
try
{
await Session.RunAsync(exportDirectory, exportOptions, progress, _cts.Token).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
Application.Current?.Dispatcher.InvokeAsync(() => IsCanceled = true);
}
finally
{
_stopwatch.Stop();
Application.Current?.Dispatcher.InvokeAsync(() =>
{
StopUiTimer();
IsRunning = false;
IsFinished = !IsCanceled;
UpdateElapsedAndEta();
});
}
}
public void CancelExport()
{
_cts?.Cancel();
}
public void ClearQueue()
{
_session?.Clear();
ClearExportHistory();
}
private void StartUiTimer()
{
_uiTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) };
_uiTimer.Tick += (_, _) => UpdateElapsedAndEta();
_uiTimer.Start();
}
private void StopUiTimer()
{
_uiTimer?.Stop();
_uiTimer = null;
}
private void UpdateElapsedAndEta()
{
ElapsedTime = _stopwatch.Elapsed;
var remaining = Session.TotalQueued;
if (IsRunning && remaining > 0 && CompletedCount > 1 && ElapsedTime.TotalSeconds > 0)
{
var rate = CompletedCount / ElapsedTime.TotalSeconds;
if (rate > 0)
{
EtaTime = TimeSpan.FromSeconds(remaining / rate);
return;
}
}
EtaTime = null;
}
private void ClearExportHistory()
{
CompletedCount = 0;
SucceededCount = 0;
FailedCount = 0;
ProgressValue = 0;
ElapsedTime = TimeSpan.Zero;
EtaTime = null;
CurrentItemName = null;
IsFinished = false;
IsCanceled = false;
ClassGroups.Clear();
}
private void DrainLogs()
{
while (_pendingLogs.TryDequeue(out var log))
{
var className = log.GetContext("ClassName");
var objectPath = log.GetContext("ObjectPath");
var filePath = log.GetContext("FilePath");
var cg = FindOrCreateClass(className);
var og = FindOrCreateObject(cg, objectPath);
if (log.Level >= LogEventLevel.Error)
{
og.ErrorCount++;
cg.ErrorCount++;
}
if (og.FirstFilePath == null && !string.IsNullOrEmpty(filePath))
og.FirstFilePath = filePath;
og.Entries.Add(new LogEntryViewModel(log));
}
}
private ClassGroupViewModel FindOrCreateClass(string name)
{
var cg = ClassGroups.FirstOrDefault(c => c.Name == name);
if (cg != null) return cg;
cg = new ClassGroupViewModel(name);
ClassGroups.Add(cg);
return cg;
}
private static ObjectGroupViewModel FindOrCreateObject(ClassGroupViewModel cg, string path)
{
var og = cg.Objects.FirstOrDefault(o => o.Path == path);
if (og != null) return og;
og = new ObjectGroupViewModel(path);
cg.Objects.Add(og);
return og;
}
}
public class ClassGroupViewModel(string name) : ViewModel
{
public string Name { get; } = name;
public ObservableCollection<ObjectGroupViewModel> Objects { get; } = [];
public bool IsExpanded
{
get;
set => SetProperty(ref field, value);
}
public int ErrorCount
{
get;
set
{
SetProperty(ref field, value);
RaisePropertyChanged(nameof(HasErrors));
}
}
public override bool HasErrors => ErrorCount > 0;
}
public class ObjectGroupViewModel(string path) : ViewModel
{
public string Path { get; } = path;
public string Name { get; } = path.SubstringAfterLast('.');
public ObservableCollection<LogEntryViewModel> Entries { get; } = [];
public bool IsExpanded
{
get;
set => SetProperty(ref field, value);
}
public int ErrorCount
{
get;
set
{
SetProperty(ref field, value);
RaisePropertyChanged(nameof(HasErrors));
}
}
public override bool HasErrors => ErrorCount > 0;
public string? FirstFilePath
{
get;
set
{
SetProperty(ref field, value);
RaisePropertyChanged(nameof(HasFilePath));
}
}
public bool HasFilePath => FirstFilePath != null;
}
public class LogEntryViewModel(LogEvent log)
{
public LogEventLevel Level { get; } = log.Level;
public DateTimeOffset Timestamp { get; } = log.Timestamp;
public string Message { get; } = log.Exception switch
{
NullReferenceException or ArgumentException => log.RenderMessage(),
_ => log.Exception?.Message ?? log.RenderMessage()
};
public Exception? Exception { get; } = log.Exception;
}

View File

@ -170,7 +170,7 @@ public class GameFileViewModel(GameFile asset) : ViewModel
if (Asset.Extension is "umap")
{
AssetCategory = EAssetCategory.World;
AssetActions = EBulkType.Meshes | EBulkType.Textures | EBulkType.Audio | EBulkType.Code;
AssetActions = EBulkType.Worlds | EBulkType.Textures | EBulkType.Audio | EBulkType.Code;
ResolvedAssetType = "World";
Resolved |= EResolveCompute.Preview;
return Task.CompletedTask;

View File

@ -134,11 +134,27 @@ public class GameSelectorViewModel : ViewModel
}
}
var crashReportClientExe = Path.Combine(projectDir, "..", "Engine", "Binaries", "Win64", "CrashReportClient.exe");
if (File.Exists(crashReportClientExe) && TryGetUeVersionFromExe(crashReportClientExe, out ueVersion))
var projectEngineBinariesDir = Path.Combine(projectDir, "..", "Engine", "Binaries", "Win64");
if (Directory.Exists(projectEngineBinariesDir))
{
Log.Information("Detected UE version {UeVersion} from \"{Exe}\"", ueVersion, crashReportClientExe);
return true;
var crashReportClientExe = Path.Combine(projectEngineBinariesDir, "CrashReportClient.exe");
if (File.Exists(crashReportClientExe) && TryGetUeVersionFromExe(crashReportClientExe, out ueVersion))
{
Log.Information("Detected UE version {UeVersion} from \"{Exe}\"", ueVersion, crashReportClientExe);
return true;
}
if (Directory.GetFiles(projectEngineBinariesDir, "*-Win64-Shipping.exe") is { Length: > 0 } shipping)
{
foreach (var exe in shipping)
{
if (TryGetUeVersionFromExe(exe, out ueVersion))
{
Log.Information("Detected UE version {UeVersion} from \"{Exe}\"", ueVersion, exe);
return true;
}
}
}
}
ueVersion = EGame.GAME_UE4_LATEST;

View File

@ -2,20 +2,16 @@ 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 FModel.Extensions;
using CUE4Parse_Conversion.Options;
using CUE4Parse_Conversion.Writers.UEFormat.Enums;
using CUE4Parse.UE4.Assets.Exports.Material;
using FModel.Extensions.Themes;
using FModel.Framework;
using FModel.Services;
using FModel.Settings;
using ICSharpCode.AvalonEdit.Highlighting;
namespace FModel.ViewModels;
@ -30,13 +26,6 @@ public class SettingsViewModel : ViewModel
set => SetProperty(ref _useCustomOutputFolders, value);
}
private ETexturePlatform _selectedUePlatform;
public ETexturePlatform SelectedUePlatform
{
get => _selectedUePlatform;
set => SetProperty(ref _selectedUePlatform, value);
}
private EGame _selectedUeGame;
public EGame SelectedUeGame
{
@ -114,60 +103,6 @@ public class SettingsViewModel : ViewModel
set => SetProperty(ref _selectedCosmeticStyle, value);
}
private EMeshFormat _selectedMeshExportFormat;
public EMeshFormat SelectedMeshExportFormat
{
get => _selectedMeshExportFormat;
set
{
SetProperty(ref _selectedMeshExportFormat, value);
RaisePropertyChanged(nameof(SocketSettingsEnabled));
RaisePropertyChanged(nameof(CompressionSettingsEnabled));
}
}
private ESocketFormat _selectedSocketExportFormat;
public ESocketFormat SelectedSocketExportFormat
{
get => _selectedSocketExportFormat;
set => SetProperty(ref _selectedSocketExportFormat, value);
}
private EFileCompressionFormat _selectedCompressionFormat;
public EFileCompressionFormat SelectedCompressionFormat
{
get => _selectedCompressionFormat;
set => SetProperty(ref _selectedCompressionFormat, value);
}
private ELodFormat _selectedLodExportFormat;
public ELodFormat SelectedLodExportFormat
{
get => _selectedLodExportFormat;
set => SetProperty(ref _selectedLodExportFormat, value);
}
private ENaniteMeshFormat _selectedNaniteMeshExportFormat;
public ENaniteMeshFormat SelectedNaniteMeshExportFormat
{
get => _selectedNaniteMeshExportFormat;
set => SetProperty(ref _selectedNaniteMeshExportFormat, value);
}
private EMaterialFormat _selectedMaterialExportFormat;
public EMaterialFormat SelectedMaterialExportFormat
{
get => _selectedMaterialExportFormat;
set => SetProperty(ref _selectedMaterialExportFormat, value);
}
private ETextureFormat _selectedTextureExportFormat;
public ETextureFormat SelectedTextureExportFormat
{
get => _selectedTextureExportFormat;
set => SetProperty(ref _selectedTextureExportFormat, value);
}
private EJsonHighlightTheme _selectedJsonHighlightTheme;
public EJsonHighlightTheme SelectedJsonHighlightTheme
{
@ -189,8 +124,7 @@ public class SettingsViewModel : ViewModel
set => SetProperty(ref _unluacOpcodeMap, value);
}
public bool SocketSettingsEnabled => SelectedMeshExportFormat == EMeshFormat.ActorX;
public bool CompressionSettingsEnabled => SelectedMeshExportFormat == EMeshFormat.UEFormat;
public ExportOptionsViewModel Options { get; } = new();
public ReadOnlyObservableCollection<EGame> UeGames { get; private set; }
public ReadOnlyObservableCollection<ELanguage> AssetLanguages { get; private set; }
@ -198,14 +132,6 @@ public class SettingsViewModel : ViewModel
public ReadOnlyObservableCollection<EDiscordRpc> DiscordRpcs { get; private set; }
public ReadOnlyObservableCollection<ECompressedAudio> CompressedAudios { get; private set; }
public ReadOnlyObservableCollection<EIconStyle> CosmeticStyles { get; private set; }
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<ENaniteMeshFormat> NaniteMeshExportFormats { get; private set; }
public ReadOnlyObservableCollection<EMaterialFormat> MaterialExportFormats { get; private set; }
public ReadOnlyObservableCollection<ETextureFormat> TextureExportFormats { get; private set; }
public ReadOnlyObservableCollection<ETexturePlatform> Platforms { get; private set; }
public ReadOnlyObservableCollection<EJsonHighlightTheme> JsonHighlightThemes { get; private set; }
private string _outputSnapshot;
@ -216,7 +142,6 @@ public class SettingsViewModel : ViewModel
private string _codeSnapshot;
private string _modelSnapshot;
private string _gameSnapshot;
private ETexturePlatform _uePlatformSnapshot;
private EGame _ueGameSnapshot;
private IList<FCustomVersion> _customVersionsSnapshot;
private IDictionary<string, bool> _optionsSnapshot;
@ -224,13 +149,6 @@ public class SettingsViewModel : ViewModel
private ELanguage _assetLanguageSnapshot;
private ECompressedAudio _compressedAudioSnapshot;
private EIconStyle _cosmeticStyleSnapshot;
private EMeshFormat _meshExportFormatSnapshot;
private ESocketFormat _socketExportFormatSnapshot;
private EFileCompressionFormat _compressionFormatSnapshot;
private ELodFormat _lodExportFormatSnapshot;
private ENaniteMeshFormat _naniteMeshExportFormatSnapshot;
private EMaterialFormat _materialExportFormatSnapshot;
private ETextureFormat _textureExportFormatSnapshot;
private EJsonHighlightTheme _jsonHighlightThemeSnapshot;
private bool _mappingsUpdate = false;
@ -250,7 +168,6 @@ public class SettingsViewModel : ViewModel
_codeSnapshot = UserSettings.Default.CodeDirectory;
_modelSnapshot = UserSettings.Default.ModelDirectory;
_gameSnapshot = UserSettings.Default.GameDirectory;
_uePlatformSnapshot = UserSettings.Default.CurrentDir.TexturePlatform;
_ueGameSnapshot = UserSettings.Default.CurrentDir.UeVersion;
_customVersionsSnapshot = UserSettings.Default.CurrentDir.Versioning.CustomVersions;
_optionsSnapshot = UserSettings.Default.CurrentDir.Versioning.Options;
@ -269,16 +186,8 @@ public class SettingsViewModel : ViewModel
_assetLanguageSnapshot = UserSettings.Default.AssetLanguage;
_compressedAudioSnapshot = UserSettings.Default.CompressedAudioMode;
_cosmeticStyleSnapshot = UserSettings.Default.CosmeticStyle;
_meshExportFormatSnapshot = UserSettings.Default.MeshExportFormat;
_socketExportFormatSnapshot = UserSettings.Default.SocketExportFormat;
_compressionFormatSnapshot = UserSettings.Default.CompressionFormat;
_lodExportFormatSnapshot = UserSettings.Default.LodExportFormat;
_naniteMeshExportFormatSnapshot = UserSettings.Default.NaniteMeshExportFormat;
_materialExportFormatSnapshot = UserSettings.Default.MaterialExportFormat;
_textureExportFormatSnapshot = UserSettings.Default.TextureExportFormat;
_jsonHighlightThemeSnapshot = UserSettings.Default.JsonHighlightTheme;
SelectedUePlatform = _uePlatformSnapshot;
SelectedUeGame = _ueGameSnapshot;
SelectedCustomVersions = _customVersionsSnapshot;
SelectedOptions = _optionsSnapshot;
@ -286,13 +195,6 @@ public class SettingsViewModel : ViewModel
SelectedAssetLanguage = _assetLanguageSnapshot;
SelectedCompressedAudio = _compressedAudioSnapshot;
SelectedCosmeticStyle = _cosmeticStyleSnapshot;
SelectedMeshExportFormat = _meshExportFormatSnapshot;
SelectedSocketExportFormat = _socketExportFormatSnapshot;
SelectedCompressionFormat = _selectedCompressionFormat;
SelectedLodExportFormat = _lodExportFormatSnapshot;
SelectedNaniteMeshExportFormat = _naniteMeshExportFormatSnapshot;
SelectedMaterialExportFormat = _materialExportFormatSnapshot;
SelectedTextureExportFormat = _textureExportFormatSnapshot;
CriwareDecryptionKey = _criwareDecryptionKey;
UnluacOpcodeMap = _unluacOpcodeMap;
SelectedJsonHighlightTheme = _jsonHighlightThemeSnapshot;
@ -305,14 +207,6 @@ public class SettingsViewModel : ViewModel
DiscordRpcs = new ReadOnlyObservableCollection<EDiscordRpc>(new ObservableCollection<EDiscordRpc>(EnumerateDiscordRpcs()));
CompressedAudios = new ReadOnlyObservableCollection<ECompressedAudio>(new ObservableCollection<ECompressedAudio>(EnumerateCompressedAudios()));
CosmeticStyles = new ReadOnlyObservableCollection<EIconStyle>(new ObservableCollection<EIconStyle>(EnumerateCosmeticStyles()));
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()));
NaniteMeshExportFormats = new ReadOnlyObservableCollection<ENaniteMeshFormat>(new ObservableCollection<ENaniteMeshFormat>(EnumerateNaniteMeshExportFormat()));
MaterialExportFormats = new ReadOnlyObservableCollection<EMaterialFormat>(new ObservableCollection<EMaterialFormat>(EnumerateMaterialExportFormat()));
TextureExportFormats = new ReadOnlyObservableCollection<ETextureFormat>(new ObservableCollection<ETextureFormat>(EnumerateTextureExportFormat()));
Platforms = new ReadOnlyObservableCollection<ETexturePlatform>(new ObservableCollection<ETexturePlatform>(EnumerateUePlatforms()));
JsonHighlightThemes = new ReadOnlyObservableCollection<EJsonHighlightTheme>(new ObservableCollection<EJsonHighlightTheme>(EnumerateJsonHighlightThemes()));
}
@ -327,13 +221,12 @@ public class SettingsViewModel : ViewModel
whatShouldIDo.Add(SettingsOut.ReloadMappings);
if (_ueGameSnapshot != SelectedUeGame || _customVersionsSnapshot != SelectedCustomVersions ||
_uePlatformSnapshot != SelectedUePlatform || _optionsSnapshot != SelectedOptions || // combobox
_optionsSnapshot != SelectedOptions || // combobox
_mapStructTypesSnapshot != SelectedMapStructTypes ||
_gameSnapshot != UserSettings.Default.GameDirectory) // textbox
restart = true;
UserSettings.Default.CurrentDir.UeVersion = SelectedUeGame;
UserSettings.Default.CurrentDir.TexturePlatform = SelectedUePlatform;
UserSettings.Default.CurrentDir.Versioning.CustomVersions = SelectedCustomVersions;
UserSettings.Default.CurrentDir.Versioning.Options = SelectedOptions;
UserSettings.Default.CurrentDir.Versioning.MapStructTypes = SelectedMapStructTypes;
@ -343,17 +236,12 @@ public class SettingsViewModel : ViewModel
UserSettings.Default.AssetLanguage = SelectedAssetLanguage;
UserSettings.Default.CompressedAudioMode = SelectedCompressedAudio;
UserSettings.Default.CosmeticStyle = SelectedCosmeticStyle;
UserSettings.Default.MeshExportFormat = SelectedMeshExportFormat;
UserSettings.Default.SocketExportFormat = SelectedSocketExportFormat;
UserSettings.Default.CompressionFormat = SelectedCompressionFormat;
UserSettings.Default.LodExportFormat = SelectedLodExportFormat;
UserSettings.Default.NaniteMeshExportFormat = SelectedNaniteMeshExportFormat;
UserSettings.Default.MaterialExportFormat = SelectedMaterialExportFormat;
UserSettings.Default.TextureExportFormat = SelectedTextureExportFormat;
UserSettings.Default.AesReload = SelectedAesReload;
UserSettings.Default.DiscordRpc = SelectedDiscordRpc;
UserSettings.Default.JsonHighlightTheme = SelectedJsonHighlightTheme;
Options.SaveAsUserDefaults();
if (SelectedDiscordRpc == EDiscordRpc.Never)
_discordHandler.Shutdown();
@ -370,13 +258,5 @@ public class SettingsViewModel : ViewModel
private IEnumerable<EDiscordRpc> EnumerateDiscordRpcs() => Enum.GetValues<EDiscordRpc>();
private IEnumerable<ECompressedAudio> EnumerateCompressedAudios() => Enum.GetValues<ECompressedAudio>();
private IEnumerable<EIconStyle> EnumerateCosmeticStyles() => Enum.GetValues<EIconStyle>();
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<ENaniteMeshFormat> EnumerateNaniteMeshExportFormat() => Enum.GetValues<ENaniteMeshFormat>();
private IEnumerable<EMaterialFormat> EnumerateMaterialExportFormat() => Enum.GetValues<EMaterialFormat>();
private IEnumerable<ETextureFormat> EnumerateTextureExportFormat() => Enum.GetValues<ETextureFormat>();
private IEnumerable<ETexturePlatform> EnumerateUePlatforms() => Enum.GetValues<ETexturePlatform>();
private IEnumerable<EJsonHighlightTheme> EnumerateJsonHighlightThemes() => Enum.GetValues<EJsonHighlightTheme>();
}

View File

@ -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;

View File

@ -63,7 +63,7 @@ public partial class UpdateViewModel : ViewModel
var coAuthorMap = new Dictionary<GitHubCommit, HashSet<string>>();
foreach (var commit in Commits)
{
if (!commit.Commit.Message.Contains("Co-authored-by"))
if (!commit.Commit.Message.Contains("Co-authored-by", StringComparison.OrdinalIgnoreCase))
continue;
var regex = GetCoAuthorRegex();

View File

@ -75,6 +75,8 @@ public partial class AudioPlayer
_applicationView.AudioPlayer.Previous();
else if (UserSettings.Default.NextAudio.IsTriggered(e.Key))
_applicationView.AudioPlayer.Next();
else if (UserSettings.Default.RemoveAudio.IsTriggered(e.Key))
_applicationView.AudioPlayer.Remove();
}
private void OnAudioFileMouseDoubleClick(object sender, MouseButtonEventArgs e)

View File

@ -0,0 +1,535 @@
<adonisControls:AdonisWindow x:Class="FModel.Views.ExportSessionWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" d:DesignWidth="512" d:DesignHeight="512"
d:DataContext="{d:DesignInstance Type=vm:ExportSessionViewModel, IsDesignTimeCreatable=False}"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer0BackgroundBrush}}"
xmlns:vm="clr-namespace:FModel.ViewModels"
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI"
xmlns:adonisExtensions="clr-namespace:AdonisUI.Extensions;assembly=AdonisUI"
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
xmlns:controls="clr-namespace:FModel.Views.Resources.Controls"
xmlns:serilog="clr-namespace:Serilog.Events;assembly=Serilog"
xmlns:c4pMeshes="clr-namespace:CUE4Parse_Conversion.Options;assembly=CUE4Parse-Conversion"
DataContext="{x:Static vm:ExportSessionViewModel.Instance}"
WindowStartupLocation="CenterScreen" ResizeMode="NoResize" IconVisibility="Collapsed"
Height="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenHeight}, Converter={converters:RatioConverter}, ConverterParameter='0.75'}"
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.40'}">
<adonisControls:AdonisWindow.Style>
<Style TargetType="adonisControls:AdonisWindow" BasedOn="{StaticResource {x:Type adonisControls:AdonisWindow}}">
<Setter Property="Title" Value="Export Session" />
</Style>
</adonisControls:AdonisWindow.Style>
<adonisControls:AdonisWindow.Resources>
<Style x:Key="StretchTreeViewItemStyle" TargetType="TreeViewItem" BasedOn="{StaticResource TreeViewItemStyle}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TreeViewItem">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Border x:Name="Border"
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding adonisExtensions:CornerRadiusExtension.CornerRadius}" />
<Border x:Name="SpotlightLayer"
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
Background="{TemplateBinding adonisExtensions:CursorSpotlightExtension.BackgroundBrush}"
BorderBrush="{TemplateBinding adonisExtensions:CursorSpotlightExtension.BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding adonisExtensions:CornerRadiusExtension.CornerRadius}"
adonisExtensions:CursorSpotlightExtension.MouseEventSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=TreeViewItem}}"
SnapsToDevicePixels="False" />
<ToggleButton x:Name="Expander"
Grid.Row="0" Grid.Column="0"
Width="16" Height="16"
Margin="8,0,8,0"
Focusable="False" ClickMode="Press"
Foreground="{TemplateBinding Foreground}"
IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
RenderTransformOrigin="0.5,0.5">
<ToggleButton.Template>
<ControlTemplate TargetType="ToggleButton">
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
<Canvas Width="24" Height="24" Background="Transparent">
<Path Fill="{TemplateBinding Foreground}"
Data="{StaticResource ArrowIcon}" />
</Canvas>
</Viewbox>
</ControlTemplate>
</ToggleButton.Template>
<ToggleButton.RenderTransform>
<RotateTransform x:Name="ExpanderRotateTransform" Angle="-90" />
</ToggleButton.RenderTransform>
</ToggleButton>
<ContentPresenter x:Name="PART_Header"
Grid.Row="0" Grid.Column="1"
ContentSource="Header"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="0,4,8,4" />
<ItemsPresenter x:Name="ItemsHost"
Grid.Row="1" Grid.Column="1" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="False">
<Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed" />
</Trigger>
<Trigger Property="IsExpanded" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="ExpanderRotateTransform"
Storyboard.TargetProperty="Angle"
Duration="0:0:0.2" To="0" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="ExpanderRotateTransform"
Storyboard.TargetProperty="Angle"
Duration="0:0:0.2" From="0" />
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
<Trigger Property="HasItems" Value="False">
<Setter TargetName="Expander" Property="Visibility" Value="Hidden" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="LogEntryTemplate" DataType="{x:Type vm:LogEntryViewModel}">
<Grid Margin="0 1" ToolTip="{Binding Exception}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Viewbox Grid.Column="0" Width="16" Height="16" Margin="0 0 5 0" VerticalAlignment="Center">
<Canvas Width="24" Height="24">
<Path x:Name="LevelIcon"
Fill="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}"
Data="{StaticResource BugIcon}" />
</Canvas>
</Viewbox>
<TextBlock Grid.Column="1" Text="{Binding Timestamp, StringFormat='[{0:HH:mm:ss.fff}]'}"
FontSize="11" Margin="0 0 5 0" VerticalAlignment="Center"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
<TextBlock Grid.Column="2"
Text="{Binding Message}" FontSize="11" VerticalAlignment="Center"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" />
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Level}" Value="{x:Static serilog:LogEventLevel.Verbose}">
<Setter TargetName="LevelIcon" Property="Data" Value="{StaticResource NoteIcon}" />
</DataTrigger>
<DataTrigger Binding="{Binding Level}" Value="{x:Static serilog:LogEventLevel.Debug}">
<Setter TargetName="LevelIcon" Property="Data" Value="{StaticResource BugIcon}" />
</DataTrigger>
<DataTrigger Binding="{Binding Level}" Value="{x:Static serilog:LogEventLevel.Information}">
<Setter TargetName="LevelIcon" Property="Fill" Value="#42A5F5" />
<Setter TargetName="LevelIcon" Property="Data" Value="{StaticResource InfoIcon}" />
</DataTrigger>
<DataTrigger Binding="{Binding Level}" Value="{x:Static serilog:LogEventLevel.Warning}">
<Setter TargetName="LevelIcon" Property="Fill" Value="#FFA726" />
<Setter TargetName="LevelIcon" Property="Data" Value="{StaticResource WarningIcon}" />
</DataTrigger>
<DataTrigger Binding="{Binding Level}" Value="{x:Static serilog:LogEventLevel.Error}">
<Setter TargetName="LevelIcon" Property="Fill" Value="#EF5350" />
<Setter TargetName="LevelIcon" Property="Data" Value="{StaticResource CloseIcon}" />
</DataTrigger>
<DataTrigger Binding="{Binding Level}" Value="{x:Static serilog:LogEventLevel.Fatal}">
<Setter TargetName="LevelIcon" Property="Fill" Value="#EF5350" />
<Setter TargetName="LevelIcon" Property="Data" Value="{StaticResource CloseIcon}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<DataTemplate x:Key="ObjectGroupHeaderTemplate" DataType="{x:Type vm:ObjectGroupViewModel}">
<Grid VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Viewbox Grid.Column="0" Width="16" Height="16" Margin="0 0 5 0" VerticalAlignment="Center"
Visibility="{Binding HasErrors, Converter={StaticResource BoolToVisibilityConverter}}">
<Canvas Width="24" Height="24">
<Path Fill="#EF5350" Data="{StaticResource CloseIcon}" />
</Canvas>
</Viewbox>
<TextBlock Grid.Column="1" Text="{Binding Name}" VerticalAlignment="Center"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
ToolTip="{Binding Path}" />
<Button Grid.Column="3" ToolTip="Open in Explorer" Click="OnOpenInExplorerClick"
Tag="{Binding FirstFilePath}"
Visibility="{Binding HasFilePath, Converter={StaticResource BoolToVisibilityConverter}}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Padding="0" Margin="0">
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M12 6h-6a2 2 0 0 0 -2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-6" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M11 13l9 -9" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M15 4h5v5" />
</Canvas>
</Viewbox>
</Button>
</Grid>
</DataTemplate>
<DataTemplate x:Key="ClassGroupHeaderTemplate" DataType="{x:Type vm:ClassGroupViewModel}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" FontWeight="SemiBold" Text="{Binding Name}" VerticalAlignment="Center" />
<Grid Grid.Column="1" Margin="5 0 0 0"
Visibility="{Binding HasErrors, Converter={StaticResource BoolToVisibilityConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Viewbox Grid.Column="0" Width="16" Height="16" VerticalAlignment="Center" Margin="0 0 5 0">
<Canvas Width="24" Height="24">
<Path Fill="#EF5350" Data="{StaticResource CloseIcon}" />
</Canvas>
</Viewbox>
<TextBlock Grid.Column="1" Text="{Binding ErrorCount}" Foreground="#EF5350" VerticalAlignment="Center" />
</Grid>
<TextBlock Grid.Column="2" VerticalAlignment="Center" Margin="5 0 0 0"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}"
Text="{Binding Objects.Count, Mode=OneWay, StringFormat='({0})'}"
ToolTip="Assets sharing the same name across different packages are grouped together in this view. Exports are not affected."/>
</Grid>
</DataTemplate>
</adonisControls:AdonisWindow.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DockPanel Grid.Row="0" Margin="10">
<Grid DockPanel.Dock="Right" VerticalAlignment="Center" HorizontalAlignment="Right">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
Text="{Binding ElapsedTime, Mode=OneWay, Converter={x:Static converters:TimeSpanConverter.Instance}, ConverterParameter=elapsed, FallbackValue=00:00.00}"
FontSize="16" FontWeight="SemiBold" HorizontalAlignment="Right"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" />
<TextBlock Grid.Row="1" Grid.Column="1" FontSize="10" HorizontalAlignment="Right">
<Run Text="ETA " Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
<Run Foreground="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Text="{Binding EtaTime, Mode=OneWay, Converter={x:Static converters:TimeSpanConverter.Instance}, FallbackValue=--:--}" />
</TextBlock>
<TextBlock Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"
FontSize="10" HorizontalAlignment="Right"
TextTrimming="CharacterEllipsis" MaxWidth="220">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding CurrentItemName, Mode=OneWay, FallbackValue=/}" />
<Setter Property="Foreground" Value="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsCanceled}" Value="True">
<Setter Property="Text" Value="Export canceled" />
<Setter Property="Foreground" Value="#EF5350" />
</DataTrigger>
<DataTrigger Binding="{Binding IsFinished}" Value="True">
<Setter Property="Text" Value="Export finished" />
<Setter Property="Foreground" Value="#66BB6A" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
<Grid DockPanel.Dock="Left" VerticalAlignment="Center" HorizontalAlignment="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0"
Text="{Binding Session.TotalQueued, Mode=OneWay, FallbackValue=0}"
FontSize="20" FontWeight="SemiBold"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="in queue" FontSize="10"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
<Rectangle Grid.Row="0" Grid.RowSpan="2" Grid.Column="1" Width="1" Margin="16 2"
Fill="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
<TextBlock Grid.Row="0" Grid.Column="2"
Text="{Binding CompletedCount, Mode=OneWay, FallbackValue=0}"
FontSize="20" FontWeight="SemiBold"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" />
<TextBlock Grid.Row="1" Grid.Column="2" Text="completed" FontSize="10"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
<Rectangle Grid.Row="0" Grid.RowSpan="2" Grid.Column="3" Width="1" Margin="16 2"
Fill="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
<TextBlock Grid.Row="0" Grid.Column="4"
Text="{Binding SucceededCount, Mode=OneWay, FallbackValue=0}"
FontSize="20" FontWeight="SemiBold" Foreground="#66BB6A" />
<TextBlock Grid.Row="1" Grid.Column="4" Text="succeeded" FontSize="10"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
<TextBlock Grid.Row="0" Grid.Column="5" Margin="5 0 0 0"
Text="{Binding FailedCount, Mode=OneWay, FallbackValue=0}"
FontSize="20" FontWeight="SemiBold" Foreground="#EF5350" />
<TextBlock Grid.Row="1" Grid.Column="5" Text="failed" FontSize="10" Margin="5 0 0 0"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
</Grid>
</DockPanel>
<ProgressBar Grid.Row="1" Height="5" Margin="0 5 0 5" Maximum="1" Value="{Binding ProgressValue, Mode=OneWay, FallbackValue=0.5}" />
<TabControl Grid.Row="2" adonisExtensions:LayerExtension.Layer="2"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer0BackgroundBrush}}">
<TabItem Header="Export Logs">
<Grid>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="13"
Text="Start an export to see results here." FontWeight="SemiBold"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsRunning}" Value="False" />
<Condition Binding="{Binding ClassGroups.Count}" Value="0" />
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Visible" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TreeView ItemsSource="{Binding ClassGroups}"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling"
VirtualizingPanel.ScrollUnit="Item"
ScrollViewer.CanContentScroll="True">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem" BasedOn="{StaticResource StretchTreeViewItemStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=TreeView}, Path=Items.Count}" Value="1">
<Setter Property="IsExpanded" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type vm:ClassGroupViewModel}" ItemsSource="{Binding Objects}"
ItemContainerStyle="{StaticResource StretchTreeViewItemStyle}">
<ContentPresenter Content="{Binding}"
ContentTemplate="{StaticResource ClassGroupHeaderTemplate}"
HorizontalAlignment="Stretch" />
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type vm:ObjectGroupViewModel}" ItemsSource="{Binding Entries}"
ItemContainerStyle="{StaticResource StretchTreeViewItemStyle}">
<ContentPresenter Content="{Binding}"
ContentTemplate="{StaticResource ObjectGroupHeaderTemplate}"
HorizontalAlignment="Stretch" />
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate DataType="{x:Type vm:LogEntryViewModel}">
<ContentPresenter Content="{Binding}" ContentTemplate="{StaticResource LogEntryTemplate}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</TabItem>
<TabItem Header="Export Options" adonisExtensions:LayerExtension.Layer="2">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<CheckBox Grid.Column="0"
IsChecked="{Binding Options.OverrideOptions, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}"
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}" />
<TextBlock Grid.Column="1" VerticalAlignment="Center" FontSize="11" Margin="5 2 0 0"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}"
Text="Override default export options for this session"
Visibility="{Binding Options.HasFeedback, Converter={x:Static converters:InvertBoolToVisibilityConverter.Instance}}" />
<TextBlock Grid.Column="1" VerticalAlignment="Center" FontSize="11" Margin="5 2 0 0"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.AccentBrush}}"
FontWeight="SemiBold" Text="{Binding Options.FeedbackMessage}"
Visibility="{Binding Options.HasFeedback, Converter={StaticResource BoolToVisibilityConverter}}" />
<Button Grid.Column="2" Click="OnMakeDefaultOptions" ToolTip="Save current options as your default settings"
Padding="4" BorderThickness="1" Margin="5 0 0 0"
IsEnabled="{Binding Options.OverrideOptions}" Focusable="False"
Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}">
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" />
</Canvas>
</Viewbox>
</Button>
<Button Grid.Column="3" Click="OnResetOptions" ToolTip="Reset all options to default settings"
Padding="4" BorderThickness="1" Margin="5 0 0 0"
IsEnabled="{Binding Options.OverrideOptions}" Focusable="False"
Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}">
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4" />
</Canvas>
</Viewbox>
</Button>
</Grid>
<Separator Grid.Row="1" Style="{StaticResource CustomSeparator}" Margin="0 5 0 10" />
<controls:ExportOptionsControl Grid.Row="2"
DataContext="{Binding Options}"
IsEnabled="{Binding OverrideOptions}" />
</Grid>
</ScrollViewer>
</TabItem>
</TabControl>
<Border Grid.Row="3"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer1BackgroundBrush}}"
adonisExtensions:LayerExtension.IncreaseLayer="True">
<Grid Margin="30, 12, 6, 12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" MinWidth="78" Margin="0 0 12 0" IsDefault="False" IsCancel="True"
Visibility="{Binding IsRunning, Converter={StaticResource BoolToVisibilityConverter}}"
HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="Cancel" Click="OnCancelClick"
Style="{DynamicResource {x:Static adonisUi:Styles.AccentButton}}">
<Button.Resources>
<SolidColorBrush x:Key="{x:Static adonisUi:Brushes.AccentBrush}" Color="#E53935" />
<SolidColorBrush x:Key="{x:Static adonisUi:Brushes.AccentHighlightBrush}" Color="#EF5350" />
<SolidColorBrush x:Key="{x:Static adonisUi:Brushes.AccentIntenseHighlightBrush}" Color="#EF5350" />
<SolidColorBrush x:Key="{x:Static adonisUi:Brushes.AccentInteractionBrush}" Color="#C62828" />
<SolidColorBrush x:Key="{x:Static adonisUi:Brushes.AccentInteractionBorderBrush}" Color="#B71C1C" />
</Button.Resources>
</Button>
<Button Grid.Column="2" MinWidth="78" Margin="0 0 12 0" IsDefault="False" IsCancel="False"
IsEnabled="{Binding CanExport}"
HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="Clear Queue" Click="OnClearQueueClick">
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsRunning}" Value="True">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding IsFinished}" Value="True">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<Button Grid.Column="3" MinWidth="78" Margin="0 0 12 0" IsDefault="False" IsCancel="False"
HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="Export"
IsEnabled="{Binding CanExport}"
Visibility="{Binding IsFinished, Converter={x:Static converters:InvertBoolToVisibilityConverter.Instance}}"
Click="OnExportClick" />
<Button Grid.Column="3" MinWidth="78" Margin="0 0 12 0" IsDefault="False" IsCancel="False"
HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="OK"
Visibility="{Binding IsFinished, Converter={StaticResource BoolToVisibilityConverter}}"
Click="OnOkClick" />
</Grid>
</Border>
</Grid>
</adonisControls:AdonisWindow>

View File

@ -0,0 +1,67 @@
using System.Diagnostics;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using FModel.ViewModels;
namespace FModel.Views;
public partial class ExportSessionWindow
{
public ExportSessionWindow()
{
InitializeComponent();
}
private async void OnExportClick(object sender, RoutedEventArgs e)
{
if (sender is Button { DataContext: ExportSessionViewModel { CanExport: true } viewModel })
await viewModel.ExportAsync();
}
private void OnOkClick(object sender, RoutedEventArgs e)
{
Close();
}
private void OnCancelClick(object sender, RoutedEventArgs e)
{
if (sender is Button { DataContext: ExportSessionViewModel viewModel })
viewModel.CancelExport();
}
private void OnClearQueueClick(object sender, RoutedEventArgs e)
{
if (sender is Button { DataContext: ExportSessionViewModel viewModel })
viewModel.ClearQueue();
}
private void OnMakeDefaultOptions(object sender, RoutedEventArgs e)
{
if (sender is Button { DataContext: ExportSessionViewModel viewModel })
viewModel.Options.SaveAsUserDefaults();
}
private void OnResetOptions(object sender, RoutedEventArgs e)
{
if (sender is Button { DataContext: ExportSessionViewModel viewModel })
viewModel.Options.ResetToUserDefaults();
}
private void OnOpenInExplorerClick(object sender, RoutedEventArgs e)
{
if (sender is not Button { Tag: string path }) return;
try
{
if (File.Exists(path) || Directory.Exists(path))
{
Process.Start("explorer.exe", $"/select,\"{path}\"");
}
}
catch
{
//
}
}
}

View File

@ -4,7 +4,8 @@ namespace FModel.Views.Resources.Controls.Aup;
public enum ESourceEventType
{
Loading
Loading,
Clearing
}
public class SourceEventArgs : EventArgs
@ -15,4 +16,4 @@ public class SourceEventArgs : EventArgs
{
Event = e;
}
}
}

View File

@ -116,7 +116,7 @@ public sealed class Timeclock : UserControl
private void OnSourceEvent(object sender, SourceEventArgs e)
{
if (Source == null) return;
if (e.Event != ESourceEventType.Loading || Source == null) return;
Label = Source.PlayedFile.FileName;
Dispatcher.BeginInvoke((Action) CalculateTime);
}

View File

@ -100,7 +100,7 @@ public sealed class Timeline : UserControl
private void OnSourceEvent(object sender, SourceEventArgs e)
{
if (Source == null) return;
if (e.Event != ESourceEventType.Loading || Source == null) return;
Dispatcher.BeginInvoke((Action) UpdateTimeline);
}
@ -347,4 +347,4 @@ public sealed class Timeline : UserControl
}
}
}
}
}

View File

@ -110,146 +110,189 @@
</MenuItem.Style>
</MenuItem>
<Separator />
<MenuItem Command="{Binding RightClickMenuCommand}">
<MenuItem.Header>
<TextBlock
Text="{Binding PlacementTarget.SelectedItem.Asset.Extension,
FallbackValue='Export Raw Data',
StringFormat='Export Raw Data (.{0})',
RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</MenuItem.Header>
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Data" />
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem Header="Export">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExportIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Properties (.json)" Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Properties" />
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Texture" Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Textures" />
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.IsEnabled>
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}">
<Binding.Converter>
<converters:AnyItemMeetsConditionConverter>
<converters:AnyItemMeetsConditionConverter.Conditions>
<converters:ItemActionCondition Action="Textures" />
</converters:AnyItemMeetsConditionConverter.Conditions>
</converters:AnyItemMeetsConditionConverter>
</Binding.Converter>
</Binding>
</MenuItem.IsEnabled>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource TextureIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Model" Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Models" />
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.IsEnabled>
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}">
<Binding.Converter>
<converters:AnyItemMeetsConditionConverter>
<converters:AnyItemMeetsConditionConverter.Conditions>
<converters:ItemActionCondition Action="Meshes" />
</converters:AnyItemMeetsConditionConverter.Conditions>
</converters:AnyItemMeetsConditionConverter>
</Binding.Converter>
</Binding>
</MenuItem.IsEnabled>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ModelIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Animation" Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Animations" />
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.IsEnabled>
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}">
<Binding.Converter>
<converters:AnyItemMeetsConditionConverter>
<converters:AnyItemMeetsConditionConverter.Conditions>
<converters:ItemActionCondition Action="Animations" />
</converters:AnyItemMeetsConditionConverter.Conditions>
</converters:AnyItemMeetsConditionConverter>
</Binding.Converter>
</Binding>
</MenuItem.IsEnabled>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AnimationIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Audio" Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Audio" />
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.IsEnabled>
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}">
<Binding.Converter>
<converters:AnyItemMeetsConditionConverter>
<converters:AnyItemMeetsConditionConverter.Conditions>
<converters:ItemActionCondition Action="Audio" />
</converters:AnyItemMeetsConditionConverter.Conditions>
</converters:AnyItemMeetsConditionConverter>
</Binding.Converter>
</Binding>
</MenuItem.IsEnabled>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AudioIcon}" />
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M12 2l.117 .007a1 1 0 0 1 .876 .876l.007 .117v4l.005 .15a2 2 0 0 0 1.838 1.844l.157 .006h4l.117 .007a1 1 0 0 1 .876 .876l.007 .117v9a3 3 0 0 1 -2.824 2.995l-.176 .005h-10a3 3 0 0 1 -2.995 -2.824l-.005 -.176v-14a3 3 0 0 1 2.824 -2.995l.176 -.005zm1 10l-5 5v2h2l5 -5a1.414 1.414 0 0 0 -2 -2" />
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M19 7h-4l-.001 -4.001z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
<MenuItem Command="{Binding RightClickMenuCommand}">
<MenuItem.Header>
<TextBlock
Text="{Binding PlacementTarget.SelectedItem.Asset.Extension,
FallbackValue='Raw Data',
StringFormat='Raw Data (.{0})',
RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</MenuItem.Header>
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Data" />
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExportIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Properties (.json)" Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Properties" />
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Texture" Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Textures" />
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.IsEnabled>
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}">
<Binding.Converter>
<converters:AnyItemMeetsConditionConverter>
<converters:AnyItemMeetsConditionConverter.Conditions>
<converters:ItemActionCondition Action="Textures" />
</converters:AnyItemMeetsConditionConverter.Conditions>
</converters:AnyItemMeetsConditionConverter>
</Binding.Converter>
</Binding>
</MenuItem.IsEnabled>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource TextureIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Model" Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Models" />
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.IsEnabled>
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}">
<Binding.Converter>
<converters:AnyItemMeetsConditionConverter>
<converters:AnyItemMeetsConditionConverter.Conditions>
<converters:ItemActionCondition Action="Meshes" />
</converters:AnyItemMeetsConditionConverter.Conditions>
</converters:AnyItemMeetsConditionConverter>
</Binding.Converter>
</Binding>
</MenuItem.IsEnabled>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ModelIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="World" Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Worlds" />
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.IsEnabled>
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}">
<Binding.Converter>
<converters:AnyItemMeetsConditionConverter>
<converters:AnyItemMeetsConditionConverter.Conditions>
<converters:ItemActionCondition Action="Worlds" />
</converters:AnyItemMeetsConditionConverter.Conditions>
</converters:AnyItemMeetsConditionConverter>
</Binding.Converter>
</Binding>
</MenuItem.IsEnabled>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World1}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World2}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World3}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World4}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World5}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Animation" Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Animations" />
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.IsEnabled>
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}">
<Binding.Converter>
<converters:AnyItemMeetsConditionConverter>
<converters:AnyItemMeetsConditionConverter.Conditions>
<converters:ItemActionCondition Action="Animations" />
</converters:AnyItemMeetsConditionConverter.Conditions>
</converters:AnyItemMeetsConditionConverter>
</Binding.Converter>
</Binding>
</MenuItem.IsEnabled>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AnimationIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Audio" Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Audio" />
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.IsEnabled>
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}">
<Binding.Converter>
<converters:AnyItemMeetsConditionConverter>
<converters:AnyItemMeetsConditionConverter.Conditions>
<converters:ItemActionCondition Action="Audio" />
</converters:AnyItemMeetsConditionConverter.Conditions>
</converters:AnyItemMeetsConditionConverter>
</Binding.Converter>
</Binding>
</MenuItem.IsEnabled>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AudioIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<Separator />
<MenuItem Header="Copy">

View File

@ -5,145 +5,183 @@
x:Class="FModel.Views.Resources.Controls.ContextMenus.FolderContextMenuDictionary">
<ContextMenu x:Key="FolderContextMenu" x:Shared="False"
Opened="FolderContextMenu_OnOpened">
<MenuItem Header="Export Folder's Packages Raw Data (.uasset)"
Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Data" />
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem Header="Export Folder">
<MenuItem.Icon>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Data="{StaticResource ExportIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Folder's Packages Properties (.json)"
Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Properties" />
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Data="{StaticResource SaveIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Folder's Packages Textures"
Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Textures" />
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Data="{StaticResource TextureIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Folder's Decompiled Blueprints"
Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Code" />
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Data="{StaticResource CodeIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Folder's Packages Models"
Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Models" />
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Data="{StaticResource ModelIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Folder's Packages Animations"
Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Animations" />
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Data="{StaticResource AnimationIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Folder's Packages Audio"
Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Audio" />
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Data="{StaticResource AudioIcon}" />
Data="M2 6c0 -.796 .316 -1.558 .879 -2.121c.563 -.563 1.325 -.879 2.121 -.879h4l.099 .005c.229 .023 .444 .124 .608 .288l2.707 2.707h6.586c.796 0 1.558 .316 2.121 .879c.319 .319 .559 .703 .707 1.121l-14.523 0c-.407 0 -.805 .125 -1.14 .356c-.292 .203 -.525 .48 -.674 .801l-.058 .141l-1.379 3.676c-.194 .517 .068 1.093 .585 1.287c.517 .194 1.094 -.068 1.288 -.585l1.134 -3.027c.146 -.39 .519 -.649 .937 -.649h13.002l.217 .012c.216 .024 .426 .082 .624 .173c.054 .025 .107 .053 .159 .083c.199 .115 .377 .263 .525 .439c.188 .222 .325 .482 .403 .762c.077 .28 .092 .573 .045 .859c-.001 .008 -.003 .016 -.005 .024l-.995 5.21c-.131 .686 -.497 1.304 -1.036 1.749c-.47 .389 -1.046 .624 -1.65 .677l-.261 .012h-14.026c-.796 0 -1.558 -.316 -2.121 -.879c-.563 -.563 -.879 -1.325 -.879 -2.121v-11z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
<MenuItem Header="Raw Data (.uasset)"
Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Data" />
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Data="{StaticResource ExportIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Properties (.json)"
Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Properties" />
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Data="{StaticResource SaveIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Textures"
Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Textures" />
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Data="{StaticResource TextureIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Decompiled Blueprints"
Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Code" />
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Data="{StaticResource CodeIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Models"
Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Models" />
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Data="{StaticResource ModelIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Worlds"
Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Worlds" />
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Canvas Width="24" Height="24">
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World1}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World2}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World3}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World4}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World5}" />
</Canvas>
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Animations"
Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Animations" />
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Data="{StaticResource AnimationIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Audio"
Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Audio" />
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Data="{StaticResource AudioIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<Separator />
<MenuItem Header="Favorite Directory" Click="OnFavoriteDirectoryClick"

View File

@ -0,0 +1,247 @@
<UserControl x:Class="FModel.Views.Resources.Controls.ExportOptionsControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" d:DesignWidth="512"
d:DataContext="{d:DesignInstance Type=vm:ExportOptionsViewModel, IsDesignTimeCreatable=False}"
xmlns:vm="clr-namespace:FModel.ViewModels"
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
xmlns:c4pMeshes="clr-namespace:CUE4Parse_Conversion.Options;assembly=CUE4Parse-Conversion">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Output Directory" VerticalAlignment="Center" />
<TextBox Grid.Row="0" Grid.Column="2" IsReadOnly="True"
Text="{Binding OutputDirectory, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button Grid.Row="0" Grid.Column="4" Content="..." MinWidth="28" Click="OnBrowseOutputDirectory" />
<Separator Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="5" Style="{StaticResource CustomSeparator}" Tag="MESH" Margin="0 10 0 10" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Mesh Format" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 5"
ItemsSource="{Binding MeshFormats}"
SelectedItem="{Binding SelectedMeshFormat, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ContentControl Grid.Row="3" Grid.Column="2" Grid.ColumnSpan="3" FontSize="10" Margin="0 0 0 5">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="Visibility" Value="Collapsed" />
<EventSetter Event="Hyperlink.Click" Handler="OnHyperlinkClick" />
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedMeshFormat}" Value="{x:Static c4pMeshes:EMeshFormat.UEFormat}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock>
<Run Text="Importer (Blender/UE): " />
<Hyperlink NavigateUri="https://github.com/h4lfheart/UEFormat">github.com/h4lfheart/UEFormat</Hyperlink>
</TextBlock>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding SelectedMeshFormat}" Value="{x:Static c4pMeshes:EMeshFormat.ActorX}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock>
<Run Text="Importers (Blender): " />
<Hyperlink NavigateUri="https://github.com/DarklightGames/io_scene_psk_psa">github.com/DarklightGames/io_scene_psk_psa</Hyperlink>
</TextBlock>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
<TextBlock Grid.Row="4" Grid.Column="0" Text="Mesh Quality" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="4" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 5"
ItemsSource="{Binding MeshQualities}"
SelectedItem="{Binding SelectedMeshQuality, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="5" Grid.Column="0" Text="Socket Format" VerticalAlignment="Center" Margin="0 0 0 5"
IsEnabled="{Binding SocketSettingsEnabled}" />
<ComboBox Grid.Row="5" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 5"
ItemsSource="{Binding SocketFormats}"
SelectedItem="{Binding SelectedSocketFormat, Mode=TwoWay}"
IsEnabled="{Binding SocketSettingsEnabled}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="6" Grid.Column="0" Text="Compression Format" VerticalAlignment="Center" Margin="0 0 0 5"
IsEnabled="{Binding CompressionSettingsEnabled}" />
<ComboBox Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 5"
ItemsSource="{Binding CompressionFormats}"
SelectedItem="{Binding SelectedCompressionFormat, Mode=TwoWay}"
IsEnabled="{Binding CompressionSettingsEnabled}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="7" Grid.Column="0" Text="Nanite Format" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="7" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 5"
ItemsSource="{Binding NaniteMeshFormats}"
SelectedItem="{Binding SelectedNaniteMeshFormat, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Border Grid.Row="8" Grid.Column="0" Grid.ColumnSpan="5" Margin="0 0 0 5" CornerRadius="1"
Background="#1AFFA726" BorderBrush="#FFA726" BorderThickness="1" Padding="10"
Visibility="{Binding ShowNaniteWarning, Converter={StaticResource BoolToVisibilityConverter}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Viewbox Grid.Column="0" Height="{Binding Height, RelativeSource={RelativeSource AncestorType=Border}}"
Margin="0 0 10 0" VerticalAlignment="Center">
<Canvas Width="24" Height="24">
<Path Fill="#FFA726" Data="{StaticResource WarningIcon}" />
</Canvas>
</Viewbox>
<TextBlock Grid.Column="1" TextWrapping="Wrap" Foreground="#FFA726">
<Run FontWeight="Bold" Text="Nanite meshes are slower to export" />
<LineBreak />
<Run Text="It is not recommended for world exports, as this stacks up for every mesh in the level." />
</TextBlock>
<Viewbox Grid.Column="2" Height="{Binding Height, RelativeSource={RelativeSource AncestorType=Border}}"
Margin="10 0 0 0" VerticalAlignment="Center">
<Canvas Width="24" Height="24">
<Path Fill="#FFA726" Data="{StaticResource WarningIcon}" />
</Canvas>
</Viewbox>
</Grid>
</Border>
<Separator Grid.Row="9" Grid.Column="0" Grid.ColumnSpan="5" Style="{StaticResource CustomSeparator}" Tag="TEXTURE" Margin="0 10 0 10" />
<TextBlock Grid.Row="10" Grid.Column="0" Text="Texture Format" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="10" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 5"
ItemsSource="{Binding TextureFormats}"
SelectedItem="{Binding SelectedTextureFormat, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="11" Grid.Column="0" Text="Texture Platform" VerticalAlignment="Center" Margin="0 0 0 10" />
<ComboBox Grid.Row="11" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 10"
ItemsSource="{Binding TexturePlatforms}"
SelectedItem="{Binding SelectedTexturePlatform, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="12" Grid.Column="0" Text="Texture Quality" VerticalAlignment="Center" Margin="0 0 0 12" />
<Slider Grid.Row="12" Grid.Column="2" Grid.ColumnSpan="3" Minimum="1" Maximum="100" TickPlacement="None"
AutoToolTipPlacement="BottomRight" IsMoveToPointEnabled="True" IsSnapToTickEnabled="True" Margin="0 0 0 12"
Value="{Binding TextureQuality, Mode=TwoWay}" />
<TextBlock Grid.Row="13" Grid.Column="0" Text="Save HDR as .hdr" VerticalAlignment="Center" />
<CheckBox Grid.Row="13" Grid.Column="2" Grid.ColumnSpan="3"
IsChecked="{Binding ExportHdrTexturesAsHdr, Mode=TwoWay}"
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" />
<Separator Grid.Row="14" Grid.Column="0" Grid.ColumnSpan="5" Style="{StaticResource CustomSeparator}" Tag="MATERIAL &amp; MORPH" Margin="0 10 0 10" />
<TextBlock Grid.Row="15" Grid.Column="0" Text="Material Depth" VerticalAlignment="Center" Margin="0 0 0 12" />
<ComboBox Grid.Row="15" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 12"
ItemsSource="{Binding MaterialDepths}"
SelectedItem="{Binding SelectedMaterialDepth, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="16" Grid.Column="0" Text="Auto-Export Attached Materials" VerticalAlignment="Center" Margin="0 0 0 15"
ToolTip="When exporting a mesh, also find and export its attached materials." />
<Grid Grid.Row="16" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 15">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<CheckBox Grid.Column="0"
IsChecked="{Binding ExportMaterials, Mode=TwoWay}"
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" />
<TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="5 0 0 0" FontSize="11"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}"
Text="For geometry-only exports, it can be significantly faster to disable this option." />
</Grid>
<TextBlock Grid.Row="17" Grid.Column="0" Text="Export Morph Targets" VerticalAlignment="Center" Margin="0 0 0 15" />
<CheckBox Grid.Row="17" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 15"
IsChecked="{Binding ExportMorphTargets, Mode=TwoWay}"
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" />
</Grid>
</UserControl>

View File

@ -0,0 +1,32 @@
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using FModel.ViewModels;
using Ookii.Dialogs.Wpf;
namespace FModel.Views.Resources.Controls;
public partial class ExportOptionsControl
{
public ExportOptionsControl()
{
InitializeComponent();
}
private void OnBrowseOutputDirectory(object sender, RoutedEventArgs e)
{
if (sender is Button { DataContext: ExportOptionsViewModel viewModel })
{
var folderBrowser = new VistaFolderBrowserDialog { ShowNewFolderButton = false };
if (folderBrowser.ShowDialog() == true)
viewModel.OutputDirectory = folderBrowser.SelectedPath;
}
}
private void OnHyperlinkClick(object sender, RoutedEventArgs e)
{
if (e.OriginalSource is Hyperlink hyperlink)
Process.Start(new ProcessStartInfo(hyperlink.NavigateUri.AbsoluteUri) { UseShellExecute = true });
}
}

View File

@ -28,6 +28,14 @@ public enum ELog
None
}
/// <summary>
/// this whole thing needs a refactor
/// TODO: source of truth for logs should be Serilog
/// a custom sink to reroute some of these logs to the UI
/// the ui should be an ObservableCollection of things
/// the things displayed in ListBox (virtualized) should use a custom template
/// see how logs work in ExportSessionViewModel
/// </summary>
public class FLogger : ITextFormatter
{
public static CustomRichTextBox Logger;
@ -107,14 +115,12 @@ public class FLogger : ITextFormatter
try
{
Logger.Document.ContentEnd.InsertTextInRun(message);
if (newLine) Logger.Document.ContentEnd.InsertLineBreak();
Logger.Selection.Select(Logger.Document.ContentStart.GetPositionAtOffset(_previous), Logger.Document.ContentEnd);
Logger.Selection.ApplyPropertyValue(TextElement.ForegroundProperty, _brushConverter.ConvertFromString(color));
}
finally
{
Finally();
Finally(newLine);
}
}
@ -122,7 +128,7 @@ public class FLogger : ITextFormatter
{
try
{
new Hyperlink(new Run(newLine ? $"{message}{Environment.NewLine}" : message), Logger.Document.ContentEnd)
new Hyperlink(new Run(message), Logger.Document.ContentEnd)
{
NavigateUri = new Uri(url),
OverridesDefaultStyle = true,
@ -163,12 +169,21 @@ public class FLogger : ITextFormatter
}
finally
{
Finally();
Finally(newLine);
}
}
private static void Finally()
private const int MaxBlocks = 250; // in the meantime
private static void Finally(bool newLine)
{
if (newLine) Logger.Document.Blocks.Add(new Paragraph { Margin = new Thickness(0) });
while (Logger.Document.Blocks.Count > MaxBlocks)
{
Logger.Document.Blocks.Remove(Logger.Document.Blocks.FirstBlock);
}
Logger.ScrollToEnd();
_previous = Math.Abs(Logger.Document.ContentEnd.GetOffsetToPosition(Logger.Document.ContentStart)) - 2;
}

View File

@ -0,0 +1,17 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace FModel.Views.Resources.Converters;
public class IntGreaterThanZeroToVisibilityConverter : IValueConverter
{
public static readonly IntGreaterThanZeroToVisibilityConverter Instance = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> value is int n && n > 0 ? Visibility.Visible : Visibility.Collapsed;
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> throw new NotSupportedException();
}

View File

@ -0,0 +1,21 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace FModel.Views.Resources.Converters;
public class TimeSpanConverter : IValueConverter
{
public static readonly TimeSpanConverter Instance = new();
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is not TimeSpan ts) return "--:--";
return parameter is "elapsed"
? $"{(int)ts.TotalMinutes:D2}:{ts.Seconds:D2}.{ts.Milliseconds / 10:D2}"
: $"{(int)ts.TotalMinutes:D2}:{ts.Seconds:D2}";
}
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) =>
throw new NotImplementedException();
}

View File

@ -11,7 +11,8 @@
<Geometry x:Key="ExpanderIcon">M16,17.01V11c0-0.55-0.45-1-1-1s-1,0.45-1,1v6.01h-1.79c-0.45,0-0.67,0.54-0.35,0.85l2.79,2.78c0.2,0.19,0.51,0.19,0.71,0 l2.79-2.78c0.32-0.31,0.09-0.85-0.35-0.85H16z M8.65,3.35L5.86,6.14c-0.32,0.31-0.1,0.85,0.35,0.85H8V13c0,0.55,0.45,1,1,1 s1-0.45,1-1V6.99h1.79c0.45,0,0.67-0.54,0.35-0.85L9.35,3.35C9.16,3.16,8.84,3.16,8.65,3.35z</Geometry>
<Geometry x:Key="FolderIcon">M20,6h-8l-1.41-1.41C10.21,4.21,9.7,4,9.17,4H4C2.9,4,2,4.9,2,6v12c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V8 C22,6.9,21.1,6,20,6z M15.98,15.74l-1.07-0.82l-1.07,0.82c-0.39,0.29-0.92-0.08-0.78-0.55l0.42-1.36l-1.2-0.95 C11.91,12.6,12.12,12,12.59,12H14l0.43-1.34c0.15-0.46,0.8-0.46,0.95,0L15.82,12h1.41c0.47,0,0.68,0.6,0.31,0.89l-1.2,0.95 l0.42,1.36C16.91,15.66,16.37,16.04,15.98,15.74z</Geometry>
<Geometry x:Key="ExportIcon">M19.41,7.41l-4.83-4.83C14.21,2.21,13.7,2,13.17,2H6C4.9,2,4.01,2.9,4.01,4L4,20c0,1.1,0.89,2,1.99,2H18c1.1,0,2-0.9,2-2 V8.83C20,8.3,19.79,7.79,19.41,7.41z M14.8,15H13v3c0,0.55-0.45,1-1,1s-1-0.45-1-1v-3H9.21c-0.45,0-0.67-0.54-0.35-0.85l2.8-2.79 c0.2-0.19,0.51-0.19,0.71,0l2.79,2.79C15.46,14.46,15.24,15,14.8,15z M14,9c-0.55,0-1-0.45-1-1V3.5L18.5,9H14z</Geometry>
<Geometry x:Key="SaveIcon">M16.17,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V7.83c0-0.53-0.21-1.04-0.59-1.41l-2.83-2.83 C17.21,3.21,16.7,3,16.17,3z M12,18c-1.66,0-3-1.34-3-3s1.34-3,3-3s3,1.34,3,3S13.66,18,12,18z M14,10H7c-0.55,0-1-0.45-1-1V7 c0-0.55,0.45-1,1-1h7c0.55,0,1,0.45,1,1v2C15,9.55,14.55,10,14,10z</Geometry>
<Geometry x:Key="MagnifierIcon">M14 3.072a8 8 0 0 1 2.617 11.424l4.944 4.943a1.5 1.5 0 0 1 -2.008 2.225l-.114 -.103l-4.943 -4.944a8 8 0 0 1 -12.49 -6.332l-.006 -.285l.005 -.285a8 8 0 0 1 11.995 -6.643z</Geometry>
<Geometry x:Key="SaveIcon">M16 3a1 1 0 0 1 .707 .293l4 4a1 1 0 0 1 .293 .707v10a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3v-12a3 3 0 0 1 3 -3h1v4a1 1 0 0 0 .883 .993l.117 .007h6a1 1 0 0 0 1 -1v-4zm-4 8a2.995 2.995 0 0 0 -2.995 2.898a1 1 0 0 0 -.005 .102a3 3 0 1 0 3 -3m1 -8v3h-4v-3z</Geometry>
<Geometry x:Key="TextureIcon">M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M6.6,16.2l2-2.67 c0.2-0.27,0.6-0.27,0.8,0L11.25,16l2.6-3.47c0.2-0.27,0.6-0.27,0.8,0l2.75,3.67c0.25,0.33,0.01,0.8-0.4,0.8H7 C6.59,17,6.35,16.53,6.6,16.2z</Geometry>
<Geometry x:Key="ModelIcon">M12 6q-.825 0-1.412-.588Q10 4.825 10 4t.588-1.413Q11.175 2 12 2t1.413.587Q14 3.175 14 4q0 .825-.587 1.412Q12.825 6 12 6ZM9 22V9H3V7h18v2h-6v13h-2v-6h-2v6Z</Geometry>
<Geometry x:Key="AnimationIcon">M9 22q-1.45 0-2.725-.55Q5 20.9 4.05 19.95q-.95-.95-1.5-2.225Q2 16.45 2 15q0-2.025 1.05-3.7Q4.1 9.625 5.8 8.75q.5-.975 1.238-1.713Q7.775 6.3 8.75 5.8q.825-1.7 2.525-2.75T15 2q1.45 0 2.725.55Q19 3.1 19.95 4.05q.95.95 1.5 2.225Q22 7.55 22 9q0 2.125-1.05 3.75t-2.75 2.5q-.5.975-1.238 1.712-.737.738-1.712 1.238-.875 1.7-2.55 2.75Q11.025 22 9 22Zm0-2q.825 0 1.588-.25Q11.35 19.5 12 19q-1.45 0-2.725-.55Q8 17.9 7.05 16.95q-.95-.95-1.5-2.225Q5 13.45 5 12q-.5.65-.75 1.412Q4 14.175 4 15q0 1.05.4 1.95.4.9 1.075 1.575.675.675 1.575 1.075.9.4 1.95.4Zm3-3q.825 0 1.613-.25.787-.25 1.437-.75-1.475 0-2.75-.562-1.275-.563-2.225-1.513-.95-.95-1.513-2.225Q8 10.425 8 8.95q-.5.65-.75 1.437Q7 11.175 7 12q0 1.05.388 1.95.387.9 1.087 1.575.675.7 1.575 1.088.9.387 1.95.387Zm3-3q.45 0 .863-.075.412-.075.837-.225.55-1.5.163-2.888-.388-1.387-1.338-2.337-.95-.95-2.337-1.338Q11.8 6.75 10.3 7.3q-.15.425-.225.837Q10 8.55 10 9q0 1.05.387 1.95.388.9 1.088 1.575.675.7 1.575 1.088.9.387 1.95.387Zm4-1.95q.5-.65.75-1.438Q20 9.825 20 9q0-1.05-.387-1.95-.388-.9-1.088-1.575-.675-.7-1.575-1.088Q16.05 4 15 4q-.875 0-1.637.25-.763.25-1.413.75 1.475 0 2.75.562 1.275.563 2.225 1.513.95.95 1.513 2.225.562 1.275.562 2.75Z</Geometry>
@ -21,7 +22,7 @@
<Geometry x:Key="ExtractIcon">M20.29,10.29l-3.59-3.59C16.08,6.08,15,6.52,15,7.41V10H8c-2.76,0-5,2.24-5,5v3c0,0.55,0.45,1,1,1h0c0.55,0,1-0.45,1-1v-3 c0-1.65,1.35-3,3-3h7v2.59c0,0.89,1.08,1.34,1.71,0.71l3.59-3.59C20.68,11.32,20.68,10.68,20.29,10.29z</Geometry>
<Geometry x:Key="GoToIcon">M9.5,5.5c1.1,0,2-0.9,2-2s-0.9-2-2-2s-2,0.9-2,2S8.4,5.5,9.5,5.5z M5.75,8.9L3.23,21.81C3.11,22.43,3.58,23,4.21,23H4.3 c0.47,0,0.88-0.33,0.98-0.79L6.85,15L9,17v5c0,0.55,0.45,1,1,1h0c0.55,0,1-0.45,1-1v-6.14c0-0.27-0.11-0.52-0.29-0.71L8.95,13.4 l0.6-3c1.07,1.32,2.58,2.23,4.31,2.51c0.6,0.1,1.14-0.39,1.14-1v0c0-0.49-0.36-0.9-0.84-0.98c-1.49-0.25-2.75-1.15-3.51-2.38 L9.7,6.95C9.35,6.35,8.7,6,8,6C7.75,6,7.5,6.05,7.25,6.15l-4.63,1.9C2.25,8.2,2,8.57,2,8.97V12c0,0.55,0.45,1,1,1h0 c0.55,0,1-0.45,1-1V9.65L5.75,8.9 M21,2h-7c-0.55,0-1,0.45-1,1v5c0,0.55,0.45,1,1,1h2.75v13.25c0,0.41,0.34,0.75,0.75,0.75 s0.75-0.34,0.75-0.75V9H21c0.55,0,1-0.45,1-1V3C22,2.45,21.55,2,21,2z M20.15,5.85l-1.28,1.29c-0.31,0.32-0.85,0.09-0.85-0.35V6.25 h-2.76c-0.41,0-0.75-0.34-0.75-0.75s0.34-0.75,0.75-0.75h2.76V4.21c0-0.45,0.54-0.67,0.85-0.35l1.28,1.29 C20.34,5.34,20.34,5.66,20.15,5.85z</Geometry>
<Geometry x:Key="DiscordIcon">M22,24L16.75,19L17.38,21H4.5A2.5,2.5 0 0,1 2,18.5V3.5A2.5,2.5 0 0,1 4.5,1H19.5A2.5,2.5 0 0,1 22,3.5V24M12,6.8C9.32,6.8 7.44,7.95 7.44,7.95C8.47,7.03 10.27,6.5 10.27,6.5L10.1,6.33C8.41,6.36 6.88,7.53 6.88,7.53C5.16,11.12 5.27,14.22 5.27,14.22C6.67,16.03 8.75,15.9 8.75,15.9L9.46,15C8.21,14.73 7.42,13.62 7.42,13.62C7.42,13.62 9.3,14.9 12,14.9C14.7,14.9 16.58,13.62 16.58,13.62C16.58,13.62 15.79,14.73 14.54,15L15.25,15.9C15.25,15.9 17.33,16.03 18.73,14.22C18.73,14.22 18.84,11.12 17.12,7.53C17.12,7.53 15.59,6.36 13.9,6.33L13.73,6.5C13.73,6.5 15.53,7.03 16.56,7.95C16.56,7.95 14.68,6.8 12,6.8M9.93,10.59C10.58,10.59 11.11,11.16 11.1,11.86C11.1,12.55 10.58,13.13 9.93,13.13C9.29,13.13 8.77,12.55 8.77,11.86C8.77,11.16 9.28,10.59 9.93,10.59M14.1,10.59C14.75,10.59 15.27,11.16 15.27,11.86C15.27,12.55 14.75,13.13 14.1,13.13C13.46,13.13 12.94,12.55 12.94,11.86C12.94,11.16 13.45,10.59 14.1,10.59Z</Geometry>
<Geometry x:Key="BugIcon">M14,12H10V10H14M14,16H10V14H14M20,8H17.19C16.74,7.22 16.12,6.55 15.37,6.04L17,4.41L15.59,3L13.42,5.17C12.96,5.06 12.5,5 12,5C11.5,5 11.04,5.06 10.59,5.17L8.41,3L7,4.41L8.62,6.04C7.88,6.55 7.26,7.22 6.81,8H4V10H6.09C6.04,10.33 6,10.66 6,11V12H4V14H6V15C6,15.34 6.04,15.67 6.09,16H4V18H6.81C7.85,19.79 9.78,21 12,21C14.22,21 16.15,19.79 17.19,18H20V16H17.91C17.96,15.67 18,15.34 18,15V14H20V12H18V11C18,10.66 17.96,10.33 17.91,10H20V8Z</Geometry>
<Geometry x:Key="BugIcon">M12 4a4 4 0 0 1 3.995 3.8l.005 .2a1 1 0 0 1 .428 .096l3.033 -1.938a1 1 0 1 1 1.078 1.684l-3.015 1.931a7.17 7.17 0 0 1 .476 2.227h3a1 1 0 0 1 0 2h-3v1a6.01 6.01 0 0 1 -.195 1.525l2.708 1.616a1 1 0 1 1 -1.026 1.718l-2.514 -1.501a6.002 6.002 0 0 1 -3.973 2.56v-5.918a1 1 0 0 0 -2 0v5.917a6.002 6.002 0 0 1 -3.973 -2.56l-2.514 1.503a1 1 0 1 1 -1.026 -1.718l2.708 -1.616a6.01 6.01 0 0 1 -.195 -1.526v-1h-3a1 1 0 0 1 0 -2h3.001v-.055a7 7 0 0 1 .474 -2.173l-3.014 -1.93a1 1 0 1 1 1.078 -1.684l3.032 1.939l.024 -.012l.068 -.027l.019 -.005l.016 -.006l.032 -.008l.04 -.013l.034 -.007l.034 -.004l.045 -.008l.015 -.001l.015 -.002l.087 -.004a4 4 0 0 1 4 -4zm0 2a2 2 0 0 0 -2 2h4a2 2 0 0 0 -2 -2z</Geometry>
<Geometry x:Key="GiftIcon">M22 10.92L19.26 9.33C21.9 7.08 19.25 2.88 16.08 4.31L15.21 4.68L15.1 3.72C15 2.64 14.44 1.87 13.7 1.42C12.06 .467 9.56 1.12 9.16 3.5L6.41 1.92C5.45 1.36 4.23 1.69 3.68 2.65L2.68 4.38C2.4 4.86 2.57 5.47 3.05 5.75L10.84 10.25L12.34 7.65L14.07 8.65L12.57 11.25L20.36 15.75C20.84 16 21.46 15.86 21.73 15.38L22.73 13.65C23.28 12.69 22.96 11.47 22 10.92M12.37 5C11.5 5.25 10.8 4.32 11.24 3.55C11.5 3.07 12.13 2.91 12.61 3.18C13.38 3.63 13.23 4.79 12.37 5M17.56 8C16.7 8.25 16 7.32 16.44 6.55C16.71 6.07 17.33 5.91 17.8 6.18C18.57 6.63 18.42 7.79 17.56 8M20.87 16.88C21.28 16.88 21.67 16.74 22 16.5V20C22 21.11 21.11 22 20 22H4C2.9 22 2 21.11 2 20V11H10.15L11 11.5V20H13V12.65L19.87 16.61C20.17 16.79 20.5 16.88 20.87 16.88Z</Geometry>
<Geometry x:Key="NoteIcon">M4 10.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-6c-.83 0-1.5.67-1.5 1.5S3.17 7.5 4 7.5 5.5 6.83 5.5 6 4.83 4.5 4 4.5zm0 12c-.83 0-1.5.68-1.5 1.5s.68 1.5 1.5 1.5 1.5-.68 1.5-1.5-.67-1.5-1.5-1.5zM8 19h12c.55 0 1-.45 1-1s-.45-1-1-1H8c-.55 0-1 .45-1 1s.45 1 1 1zm0-6h12c.55 0 1-.45 1-1s-.45-1-1-1H8c-.55 0-1 .45-1 1s.45 1 1 1zM7 6c0 .55.45 1 1 1h12c.55 0 1-.45 1-1s-.45-1-1-1H8c-.55 0-1 .45-1 1z</Geometry>
<Geometry x:Key="InfoIcon">M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 15c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1s1 .45 1 1v4c0 .55-.45 1-1 1zm1-8h-2V7h2v2z</Geometry>
@ -39,6 +40,7 @@
<Geometry x:Key="WholeWordIcon">M18 10v3H6v-3c0-.55-.45-1-1-1s-1 .45-1 1v4c0 .55.45 1 1 1h14c.55 0 1-.45 1-1v-4c0-.55-.45-1-1-1s-1 .45-1 1z</Geometry>
<Geometry x:Key="CloseIcon">M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm4.3 14.3c-.39.39-1.02.39-1.41 0L12 13.41 9.11 16.3c-.39.39-1.02.39-1.41 0-.39-.39-.39-1.02 0-1.41L10.59 12 7.7 9.11c-.39-.39-.39-1.02 0-1.41.39-.39 1.02-.39 1.41 0L12 10.59l2.89-2.89c.39-.39 1.02-.39 1.41 0 .39.39.39 1.02 0 1.41L13.41 12l2.89 2.89c.38.38.38 1.02 0 1.41z</Geometry>
<Geometry x:Key="PlayIcon">M8 6.82v10.36c0 .79.87 1.27 1.54.84l8.14-5.18c.62-.39.62-1.29 0-1.69L9.54 5.98C8.87 5.55 8 6.03 8 6.82z</Geometry>
<Geometry x:Key="ResetIcon">M12 5V2L8 6l4 4V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z</Geometry>
<Geometry x:Key="PauseIcon">M8 19c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2s-2 .9-2 2v10c0 1.1.9 2 2 2zm6-12v10c0 1.1.9 2 2 2s2-.9 2-2V7c0-1.1-.9-2-2-2s-2 .9-2 2z</Geometry>
<Geometry x:Key="StopIcon">M8 6h8c1.1 0 2 .9 2 2v8c0 1.1-.9 2-2 2H8c-1.1 0-2-.9-2-2V8c0-1.1.9-2 2-2z</Geometry>
<Geometry x:Key="SkipNextIcon">M7.58 16.89l5.77-4.07c.56-.4.56-1.24 0-1.63L7.58 7.11C6.91 6.65 6 7.12 6 7.93v8.14c0 .81.91 1.28 1.58.82zM16 7v10c0 .55.45 1 1 1s1-.45 1-1V7c0-.55-.45-1-1-1s-1 .45-1 1z</Geometry>
@ -50,6 +52,7 @@
<Geometry x:Key="AnchorIcon">M13,9V7.82C14.16,7.4,15,6.3,15,5c0-1.65-1.35-3-3-3S9,3.35,9,5c0,1.3,0.84,2.4,2,2.82V9H9c-0.55,0-1,0.45-1,1v0 c0,0.55,0.45,1,1,1h2v8.92c-2.22-0.33-4.59-1.68-5.55-3.37l1.14-1.14c0.22-0.22,0.19-0.57-0.05-0.75L3.8,12.6 C3.47,12.35,3,12.59,3,13v2c0,3.88,4.92,7,9,7s9-3.12,9-7v-2c0-0.41-0.47-0.65-0.8-0.4l-2.74,2.05c-0.24,0.18-0.27,0.54-0.05,0.75 l1.14,1.14c-0.96,1.69-3.33,3.04-5.55,3.37V11h2c0.55,0,1-0.45,1-1v0c0-0.55-0.45-1-1-1H13z M12,4c0.55,0,1,0.45,1,1s-0.45,1-1,1 s-1-0.45-1-1S11.45,4,12,4z</Geometry>
<Geometry x:Key="FoldIcon">M8.12 19.3c.39.39 1.02.39 1.41 0L12 16.83l2.47 2.47c.39.39 1.02.39 1.41 0 .39-.39.39-1.02 0-1.41l-3.17-3.17c-.39-.39-1.02-.39-1.41 0l-3.17 3.17c-.4.38-.4 1.02-.01 1.41zm7.76-14.6c-.39-.39-1.02-.39-1.41 0L12 7.17 9.53 4.7c-.39-.39-1.02-.39-1.41 0-.39.39-.39 1.03 0 1.42l3.17 3.17c.39.39 1.02.39 1.41 0l3.17-3.17c.4-.39.4-1.03.01-1.42z</Geometry>
<Geometry x:Key="UnfoldIcon">M12 5.83l2.46 2.46c.39.39 1.02.39 1.41 0 .39-.39.39-1.02 0-1.41L12.7 3.7c-.39-.39-1.02-.39-1.41 0L8.12 6.88c-.39.39-.39 1.02 0 1.41.39.39 1.02.39 1.41 0L12 5.83zm0 12.34l-2.46-2.46c-.39-.39-1.02-.39-1.41 0-.39.39-.39 1.02 0 1.41l3.17 3.18c.39.39 1.02.39 1.41 0l3.17-3.17c.39-.39.39-1.02 0-1.41-.39-.39-1.02-.39-1.41 0L12 18.17z</Geometry>
<Geometry x:Key="WarningIcon">M12 1.67c.955 0 1.845 .467 2.39 1.247l.105 .16l8.114 13.548a2.914 2.914 0 0 1 -2.307 4.363l-.195 .008h-16.225a2.914 2.914 0 0 1 -2.582 -4.2l.099 -.185l8.11 -13.538a2.914 2.914 0 0 1 2.491 -1.403zm.01 13.33l-.127 .007a1 1 0 0 0 0 1.986l.117 .007l.127 -.007a1 1 0 0 0 0 -1.986l-.117 -.007zm-.01 -7a1 1 0 0 0 -.993 .883l-.007 .117v4l.007 .117a1 1 0 0 0 1.986 0l.007 -.117v-4l-.007 -.117a1 1 0 0 0 -.993 -.883z</Geometry>
<Geometry x:Key="LocateMeIcon">M11.71,17.99C8.53,17.84,6,15.22,6,12c0-3.31,2.69-6,6-6c3.22,0,5.84,2.53,5.99,5.71l-2.1-0.63C15.48,9.31,13.89,8,12,8 c-2.21,0-4,1.79-4,4c0,1.89,1.31,3.48,3.08,3.89L11.71,17.99z M22,12c0,0.3-0.01,0.6-0.04,0.9l-1.97-0.59C20,12.21,20,12.1,20,12 c0-4.42-3.58-8-8-8s-8,3.58-8,8s3.58,8,8,8c0.1,0,0.21,0,0.31-0.01l0.59,1.97C12.6,21.99,12.3,22,12,22C6.48,22,2,17.52,2,12 C2,6.48,6.48,2,12,2S22,6.48,22,12z M18.23,16.26l2.27-0.76c0.46-0.15,0.45-0.81-0.01-0.95l-7.6-2.28 c-0.38-0.11-0.74,0.24-0.62,0.62l2.28,7.6c0.14,0.47,0.8,0.48,0.95,0.01l0.76-2.27l3.91,3.91c0.2,0.2,0.51,0.2,0.71,0l1.27-1.27 c0.2-0.2,0.2-0.51,0-0.71L18.23,16.26z</Geometry>
<Geometry x:Key="MeshIcon">M1.8 6q-.525 0-.887-.35Q.55 5.3.55 4.8V4q0-1.425 1.012-2.438Q2.575.55 4 .55h.8q.5 0 .85.362.35.363.35.888 0 .5-.35.85T4.8 3H4q-.425 0-.712.287Q3 3.575 3 4v.8q0 .5-.35.85T1.8 6ZM4 23.45q-1.425 0-2.438-1.012Q.55 21.425.55 20v-.8q0-.5.363-.85.362-.35.887-.35.5 0 .85.35t.35.85v.8q0 .425.288.712Q3.575 21 4 21h.8q.5 0 .85.35t.35.85q0 .525-.35.887-.35.363-.85.363Zm15.2 0q-.5 0-.85-.363-.35-.362-.35-.887 0-.5.35-.85t.85-.35h.8q.425 0 .712-.288Q21 20.425 21 20v-.8q0-.5.35-.85t.85-.35q.525 0 .888.35.362.35.362.85v.8q0 1.425-1.012 2.438Q21.425 23.45 20 23.45ZM22.2 6q-.5 0-.85-.35T21 4.8V4q0-.425-.288-.713Q20.425 3 20 3h-.8q-.5 0-.85-.35T18 1.8q0-.525.35-.888.35-.362.85-.362h.8q1.425 0 2.438 1.012Q23.45 2.575 23.45 4v.8q0 .5-.362.85-.363.35-.888.35ZM12 17.35l1-.575v-4.1l3.55-2.075V9.425l-1-.575L12 10.925 8.45 8.85l-1 .575V10.6L11 12.675v4.1Zm-1.325 2.325-4.55-2.65q-.625-.35-.975-.963-.35-.612-.35-1.337V9.45q0-.725.35-1.337.35-.613.975-.963l4.55-2.65Q11.3 4.15 12 4.15t1.325.35l4.55 2.65q.625.35.975.963.35.612.35 1.337v5.275q0 .725-.35 1.337-.35.613-.975.963l-4.55 2.65q-.625.35-1.325.35t-1.325-.35Z</Geometry>
<Geometry x:Key="ArchiveIcon">M3.5 1.75v11.5c0 .09.048.173.126.217a.75.75 0 0 1-.752 1.298A1.748 1.748 0 0 1 2 13.25V1.75C2 .784 2.784 0 3.75 0h5.586c.464 0 .909.185 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v8.586A1.75 1.75 0 0 1 12.25 15h-.5a.75.75 0 0 1 0-1.5h.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177L9.513 1.573a.25.25 0 0 0-.177-.073H7.25a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1 0-1.5h-3a.25.25 0 0 0-.25.25Zm3.75 8.75h.5c.966 0 1.75.784 1.75 1.75v3a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1-.75-.75v-3c0-.966.784-1.75 1.75-1.75ZM6 5.25a.75.75 0 0 1 .75-.75h.5a.75.75 0 0 1 0 1.5h-.5A.75.75 0 0 1 6 5.25Zm.75 2.25h.5a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1 0-1.5ZM8 6.75A.75.75 0 0 1 8.75 6h.5a.75.75 0 0 1 0 1.5h-.5A.75.75 0 0 1 8 6.75ZM8.75 3h.5a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1 0-1.5ZM8 9.75A.75.75 0 0 1 8.75 9h.5a.75.75 0 0 1 0 1.5h-.5A.75.75 0 0 1 8 9.75Zm-1 2.5v2.25h1v-2.25a.25.25 0 0 0-.25-.25h-.5a.25.25 0 0 0-.25.25Z</Geometry>
@ -102,8 +105,13 @@
<Geometry x:Key="SQLIcon">M231.822 132.778c1.2-5.152 6.352-8.357 11.505-7.158 5.153 1.2 8.357 6.352 7.158 11.505l-20.773 88.972c-1.199 5.153-6.352 8.358-11.504 7.158-5.153-1.199-8.358-6.352-7.158-11.505l20.772-88.972zM21.123 259.247h16.714V52.395C37.837 23.601 61.438 0 90.232 0h230.505a7.998 7.998 0 016.39 3.183l96.876 104.809a7.937 7.937 0 012.118 5.411h.041v145.844h16.714c11.618 0 21.124 9.525 21.124 21.124v163.863c0 11.599-9.526 21.124-21.124 21.124h-17.058c-3.001 25.877-25.473 46.505-52.051 46.505H90.232c-26.72 0-49.082-20.526-52.055-46.505H21.123C9.526 465.358 0 455.853 0 444.234V280.371c0-11.619 9.506-21.124 21.123-21.124zm32.731 0h356.292V136.525h-32.219v-.033h-.124c-19.111-.302-34.068-5.373-44.736-14.486-11.073-9.458-17.282-22.969-18.52-39.763l-.075-1.098V16.016H90.232c-20.032 0-36.378 16.346-36.378 36.379v206.852zm355.795 206.111H54.344c2.862 17.157 17.981 30.488 35.888 30.488h283.535c17.803 0 32.993-13.394 35.882-30.488zM330.467 30.272V81.11c.913 12.412 5.265 22.192 12.97 28.775 7.822 6.682 19.34 10.418 34.49 10.676v-.03h32.219v-4.052l-79.679-86.207zM199.303 204.095c3.972 3.477 4.377 9.521.9 13.493-3.478 3.973-9.521 4.377-13.494.9l-36.354-31.854c-3.972-3.477-4.377-9.521-.9-13.493.295-.336.606-.644.933-.927l36.321-31.826c3.973-3.478 10.016-3.073 13.494.899 3.477 3.973 3.072 10.016-.9 13.494l-28.14 24.656 28.14 24.658zm77.99 14.393c-3.972 3.477-10.016 3.073-13.493-.9-3.478-3.972-3.073-10.016.899-13.493l28.14-24.658-28.14-24.656c-3.972-3.478-4.377-9.521-.899-13.494 3.477-3.972 9.521-4.377 13.493-.899l36.318 31.826c.328.283.639.591.933.927 3.477 3.972 3.073 10.016-.899 13.493l-36.352 31.854zM120.405 400.89l3.805-19.455c8.333 2.083 15.827 3.123 22.484 3.123 6.66 0 12.024-.27 16.101-.815v-9.755l-12.228-1.086c-11.051-.999-18.635-3.648-22.759-7.951-4.121-4.302-6.181-10.665-6.181-19.089 0-11.592 2.514-19.565 7.54-23.911 5.029-4.35 13.565-6.523 25.612-6.523 12.047 0 22.915 1.134 32.61 3.398l-3.398 18.978c-8.423-1.358-15.171-2.038-20.244-2.038s-9.375.226-12.908.68v9.416l9.783.952c11.865 1.177 20.063 4.008 24.591 8.492 4.531 4.484 6.795 10.71 6.795 18.682 0 5.706-.77 10.528-2.311 14.47-1.541 3.94-3.373 6.927-5.502 8.966-2.129 2.038-5.141 3.601-9.037 4.687-3.892 1.087-7.312 1.745-10.255 1.97-2.944.228-6.864.341-11.755.341-11.775 0-22.688-1.177-32.743-3.532zm128.036-1.631l-11.548 3.125c-15.219 0-25.414-3.926-30.707-11.684-2.674-3.923-4.621-8.266-5.708-13.111-1.087-4.844-1.628-10.712-1.628-17.595 0-15.491 2.899-26.789 8.695-33.9 5.797-7.109 16.212-10.666 31.249-10.666 15.036 0 25.498 3.579 31.386 10.734 5.887 7.156 8.83 18.432 8.83 33.832 0 11.502-2.399 20.786-7.199 27.851l9.782 5.708-7.067 15.623-26.085-4.754v-5.163zm-20.382-18.072h11.279c3.714 0 6.407-.429 8.083-1.29 1.675-.861 2.514-2.83 2.514-5.909v-35.325h-11.414c-3.62 0-6.272.429-7.947 1.29-1.675.861-2.515 2.834-2.515 5.909v35.325zm115.536 21.197h-54.349v-84.917h27.175v63.179h27.174v21.738z</Geometry>
<Geometry x:Key="PythonIcon">M19.14,7.5A2.86,2.86 0 0,1 22,10.36V14.14A2.86,2.86 0 0,1 19.14,17H12C12,17.39 12.32,17.96 12.71,17.96H17V19.64A2.86,2.86 0 0,1 14.14,22.5H9.86A2.86,2.86 0 0,1 7,19.64V15.89C7,14.31 8.28,13.04 9.86,13.04H15.11C16.69,13.04 17.96,11.76 17.96,10.18V7.5H19.14M14.86,19.29C14.46,19.29 14.14,19.59 14.14,20.18C14.14,20.77 14.46,20.89 14.86,20.89A0.71,0.71 0 0,0 15.57,20.18C15.57,19.59 15.25,19.29 14.86,19.29M4.86,17.5C3.28,17.5 2,16.22 2,14.64V10.86C2,9.28 3.28,8 4.86,8H12C12,7.61 11.68,7.04 11.29,7.04H7V5.36C7,3.78 8.28,2.5 9.86,2.5H14.14C15.72,2.5 17,3.78 17,5.36V9.11C17,10.69 15.72,11.96 14.14,11.96H8.89C7.31,11.96 6.04,13.24 6.04,14.82V17.5H4.86M9.14,5.71C9.54,5.71 9.86,5.41 9.86,4.82C9.86,4.23 9.54,4.11 9.14,4.11C8.75,4.11 8.43,4.23 8.43,4.82C8.43,5.41 8.75,5.71 9.14,5.71Z</Geometry>
<Geometry x:Key="CSharpIcon">M7878 15350 c-162 -15 -335 -63 -471 -131 -146 -74 -5492 -3082 -5567 -3133 -41 -28 -120 -96 -175 -151 -189 -189 -307 -411 -362 -682 -17 -84 -18 -245 -18 -3253 0 -3008 1 -3169 18 -3253 50 -249 159 -466 323 -645 64 -71 188 -173 269 -224 102 -64 5469 -3075 5535 -3106 367 -170 814 -166 1165 10 75 38 5370 3012 5482 3079 326 196 543 505 620 886 17 84 18 245 18 3253 0 3008 -1 3169 -18 3253 -55 271 -173 493 -362 682 -55 55 -134 123 -175 151 -75 51 -5421 3059 -5568 3133 -203 102 -479 152 -714 131z m591 -3534 c526 -70 1018 -235 1456 -489 298 -172 642 -446 866 -689 117 -126 259 -298 259 -312 -1 -6 -301 -184 -667 -396 -366 -212 -680 -393 -697 -403 l-31 -18 -85 82 c-583 568 -1404 778 -2184 559 -501 -141 -952 -471 -1245 -910 -249 -373 -374 -786 -374 -1240 0 -372 82 -709 250 -1030 337 -644 947 -1075 1678 -1185 133 -21 439 -23 570 -6 499 66 958 288 1304 631 l84 82 32 -18 c892 -514 1364 -791 1365 -800 1 -32 -300 -365 -462 -511 -583 -528 -1275 -856 -2043 -967 -200 -29 -382 -39 -638 -33 -277 6 -411 20 -672 72 -1512 301 -2719 1521 -3009 3040 -52 276 -61 378 -61 725 0 355 9 453 66 747 255 1307 1201 2410 2461 2867 312 113 669 190 1018 220 118 10 641 -2 759 -18z m3371 -2536 l0 -320 320 0 320 0 0 320 0 320 320 0 320 0 0 -320 0 -320 320 0 320 0 0 -320 0 -320 -320 0 -320 0 0 -320 0 -320 320 0 320 0 0 -320 0 -320 -320 0 -320 0 0 -320 0 -320 -320 0 -320 0 0 320 0 320 -320 0 -320 0 0 -320 0 -320 -320 0 -320 0 0 320 0 320 -320 0 -320 0 0 320 0 320 320 0 320 0 0 320 0 320 -320 0 -320 0 0 320 0 320 320 0 320 0 0 320 0 320 320 0 320 0 0 -320z M11840 8000 l0 -320 320 0 320 0 0 320 0 320 -320 0 -320 0 0 -320z</Geometry>
<Geometry x:Key="World1">M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0</Geometry>
<Geometry x:Key="World2">M3.6 9h16.8</Geometry>
<Geometry x:Key="World3">M3.6 15h16.8</Geometry>
<Geometry x:Key="World4">M11.5 3a17 17 0 0 0 0 18</Geometry>
<Geometry x:Key="World5">M12.5 3a17 17 0 0 1 0 18</Geometry>
<Geometry x:Key="ThemeIcon">M17.5,12A1.5,1.5 0 0,1 16,10.5A1.5,1.5 0 0,1 17.5,9A1.5,1.5 0 0,1 19,10.5A1.5,1.5 0 0,1 17.5,12M14.5,8A1.5,1.5 0 0,1 13,6.5A1.5,1.5 0 0,1 14.5,5A1.5,1.5 0 0,1 16,6.5A1.5,1.5 0 0,1 14.5,8M9.5,8A1.5,1.5 0 0,1 8,6.5A1.5,1.5 0 0,1 9.5,5A1.5,1.5 0 0,1 11,6.5A1.5,1.5 0 0,1 9.5,8M6.5,12A1.5,1.5 0 0,1 5,10.5A1.5,1.5 0 0,1 6.5,9A1.5,1.5 0 0,1 8,10.5A1.5,1.5 0 0,1 6.5,12M12,3A9,9 0 0,0 3,12A9,9 0 0,0 12,21A1.5,1.5 0 0,0 13.5,19.5C13.5,19.11 13.35,18.76 13.11,18.5C12.88,18.23 12.73,17.88 12.73,17.5A1.5,1.5 0 0,1 14.23,16H16A5,5 0 0,0 21,11C21,6.58 16.97,3 12,3Z</Geometry>
<!-- For specific games-->
<Geometry x:Key="BorderlandsIcon">M13,9V3.5L18.5,9M6,2C4.89,2 4,2.89 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2H6 ZM12,11A3,3 0 1,0 12,17A3,3 0 0,0 12,11 ZM12,12.5L14,16H13L12,14.5L11,16H10L12,12.5Z</Geometry>
<Geometry x:Key="AionIcon">M609 180.1c-19.4 2.3-38.2 7.8-56.5 16.8-53.8 26.5-89.2 74.9-98.7 135.1-1.1 7-1.3 133.1-1.3 739 0 693.9.1 731 1.8 741.4 12.8 79.4 70.7 137.8 149.2 150.6 11.8 2 20.6 2 563.5 2s551.7 0 563.5-2c57.2-9.3 104-43 130.8-94.1 9.1-17.2 14.6-33.9 18.4-55.4 1.6-9.4 1.8-40.6 2-554l.3-544-268.2-268.2L1345.5 179l-364.5.1c-200.5.1-367.9.5-372 1m893 378.4L1747.5 804H1256V558.5c0-135 .1-245.5.3-245.5.1 0 110.7 110.5 245.7 245.5m-792.1 532c24.5 1.3 47 2.9 49.8 3.4 7.1 1.3 12.8 5.5 22.5 16.4 6.4 7.3 9.2 11.5 13.6 20.7 9.2 19.4 21.4 47.2 44.4 101.5 27.5 65.1 39.7 92.3 55.5 124 6.9 13.7 16.1 32.9 20.5 42.5 8.2 17.9 15.4 31.9 18.3 35.5 1.5 1.8 1.5-7.2.9-109-.6-108.7-.7-111.1-2.7-116.5-3.1-8.3-10-14.5-24.7-22-10.1-5.1-11.9-6.4-10.9-7.6 1.6-2 5.8-1.8 21.3 1.1 19.3 3.6 39.1 4.5 97.7 4.5 42.1 0 51.9.3 53.4 1.4 1.8 1.3 1.8 1.4-.1 1.9-1 .3-3.9.8-6.4 1.2s-8.5 1.7-13.5 3c-4.9 1.3-12.1 2.6-16 3-7.9.8-14.3 3.2-18 6.9l-2.5 2.5v123.7c0 121.8 0 123.8 2 125.9 1.4 1.5 2.1 4.1 2.6 9.2.8 8.1 2.2 10.1 12.6 17.2 9.3 6.5 9.3 6.5-26.7 7.3-17.6.4-45.3.7-61.5.5-29.2-.2-29.5-.2-29.8-2.3-.2-1.7.7-2.5 5.5-4.5 3.2-1.3 7.5-4.1 9.8-6.4 4.5-4.5 8.3-11 7.2-12.2-1.3-1.2-72.8-1.6-90.2-.5-13.5.9-16.2.8-17.5-.4-1.9-1.9-.9-2.7 8-6.4 9.8-4.1 11-5.2 11-11 0-3.9-3.7-13.2-22.3-56.1l-22.3-51.4-4.5-.6c-13.1-2-52.1-3.1-69.3-2-14.4.9-38.5 3.6-39.3 4.4-.1.1-5.6 13.5-12.3 29.7s-16 38.7-20.7 50c-7.4 17.9-8.5 21.1-8.1 25.1.6 5.6 3.9 10.3 10.2 14.3 4.8 3 5.8 5.1 2.9 5.8-1 .2-10.6 0-21.3-.5-21.7-.9-83.9-.2-108.4 1.3-22.1 1.4-22.5.3-1.7-5 17.9-4.6 33.3-9.6 42.4-13.7l7.7-3.6 6.9-13.1c3.8-7.2 14.2-27.5 23.1-45.2 17.9-35.6 19-38.9 15.5-47.9-1-2.7-1.4-5.6-1.1-7.1.4-1.5 8.1-10.1 17.5-19.8 9.2-9.4 17.6-18.6 18.5-20.4s6.4-15.7 12.2-30.8c5.7-15.1 17.9-46.7 27.1-70.2l16.6-42.7-9.3-18.2c-5.1-10-10.7-19.8-12.5-21.6-6-6.1-16.6-9.9-32-11.4-7.8-.8-8-.9-8.3-3.6s-.3-2.7 5.9-2.7c3.5 0 26.3 1.1 50.8 2.5m999.3 20.4c3 5.2 4.8 10.9 9.7 31 8.8 35.5 11.8 55.6 14.2 93.1 1.5 24.1.6 87-1.5 108-1.8 17.4-4.4 37.5-5.3 40.5-.3 1.3-.2 1.7.5 1 1.2-1.2 11.9-33.8 16.2-49.5 1.8-6.3 3.7-12.4 4.2-13.4 3.4-6.4 2.2 14.4-2 34.9-1.3 6.3-1.5 9.6-.9 10.7 2.1 3.3-9.4 55.8-18.3 83.3-5.5 17.2-15.6 42.7-18.1 45.9-1.1 1.5-2.6 2.3-3.4 1.9-.8-.3-5.7-6.9-10.9-14.7-17.4-26.2-33.3-45.2-60.7-72.1-16.4-16.1-29.1-27.5-45.6-41-4-3.2-7.5-6.5-7.8-7.3-1.2-3.2 5.5.3 19.9 10.4 8.1 5.8 15 10.3 15.2 10.2.4-.5-17.3-14.6-37.1-29.4-43.5-32.7-81.4-58.4-86.1-58.4-1.3 0-1.4 9.9-.8 86.7.4 61.2 1 87.5 1.8 89.3 1.4 3.1 7.5 7.2 14 9.5 6.9 2.4 7.6 3.2 4.6 5.5-2.4 1.9-5 2-74.2 2-45.3 0-71.8-.4-71.8-1 0-.9 3.2-1.9 12-3.9 9.2-2.1 22.3-6.2 26.9-8.6 7.6-3.8 8.9-8.7 10.1-38 1.3-29.8 2.5-203 1.5-217.8-.7-11.3-.9-12-3.8-15.3-1.6-1.9-4.3-4.2-6-5-5.1-2.7-24.7-6.3-42.9-7.9-9.7-.8-18.4-1.8-19.2-2.1-1.4-.5-2.2-2-1.3-2.6.3-.2 73.8-1.9 90.7-2.1 12.6-.2 13.9-.4 19.5-3 4.8-2.3 8.6-3.1 19.5-4.2 18.5-1.9 34.2-2.3 34.7-.9.3 1-2.3 4.6-10.9 14.7l-3.3 3.9-.3 20.2-.3 20.2 5.7 8c17.5 24.5 49.6 55.6 93.9 91 20 16.1 58.7 45.6 59.1 45.1.7-.7 6.5-38.1 8.5-55.7 3-25 3.3-66.8.6-84-2.5-16.2-9.6-43-11.4-43-.2 0-8.1 7.2-17.5 16.1-9.3 8.8-17.2 15.8-17.5 15.5s2.7-4.5 6.8-9.3c13-15.6 35.1-41.7 37.2-44.1 1.1-1.2 1.8-2.2 1.4-2.2-.3 0-16.9 17-36.8 37.7-37.3 38.8-41.5 43-42.6 41.9-.8-.8-4.1 3.1 54-63.3 28.4-32.6 51.7-59.6 51.7-59.9s-3.2 2.3-7 5.7c-14.3 12.9-15.9 13.8-7.5 4.4 16.1-18.1 32.8-35.6 33.9-35.2.6.2 2.8 3.2 4.8 6.6m-578.4 94.3c-.3.7-5 4.4-10.5 8.3-13.5 9.4-17.7 12.9-31.6 26.4-8.1 8-13.5 14.2-17.6 20.5-6.8 10.7-11.5 18.6-10.9 18.6.3 0 2-2.1 3.8-4.8 5.1-7.1 19.5-21.6 28.5-28.7 24.4-19.1 53.7-31.3 89-37.2 13.7-2.2 53.7-2.5 68.5-.5 45.5 6.4 82.4 22.6 109.1 48 26 24.7 40.4 54.7 45 93.7 1.7 14.2.6 42.3-2.1 54.9-8.5 39.6-25.4 68.8-53.5 92.6-15.5 13-29.1 20.9-45.7 26.5-18.4 6.1-35.4 7.7-56 5.3l-10.3-1.3 10.5-.6c11.4-.8 27.2-3.2 31.1-4.9 2.3-.9 2.2-1-.6-.5-15.1 2.4-38.9 3.2-48.9 1.5-11.5-1.9-10.1-4 6.3-9.9 6.2-2.3 15.4-6.2 20.4-8.8 56.5-29.2 84.7-97.9 69.1-168.3-11.5-51.5-43.4-85-89.3-93.6-11.4-2.1-32.9-2.2-44.2 0-40.6 7.7-70 34.9-84.5 78.1-5.8 17.5-7.7 31.8-7.1 54 .6 21.3 2.2 32.3 7.3 49.2 11.8 39 34.4 64.6 78.4 88.7 4.7 2.6 9.6 5.8 11 7.1 2.3 2.4 2.4 2.6.7 4.2-1.6 1.6-3.8 1.8-17 1.7-45.3-.2-82.1-18.2-112.6-55-4.2-5.1-9.6-12.4-12.1-16.2-2.5-3.9-4.9-7.1-5.4-7.1-.5-.1-.6 3.7-.3 8.5l.5 8.5-2.4-2.2c-3-2.8-10.5-17.2-14.3-27.7-7.2-19.5-12.1-46.9-12.1-67.8 0-30.8 5.7-56.3 18.5-81.9 16.2-32.6 43.1-58.4 79-75.7 10.3-4.9 13-5.7 12.3-3.6 M731 1231.2c-6.2 15.5-15.9 40.1-21.6 54.5-5.7 14.5-10.4 26.7-10.4 27.2 0 1.5 7.9 2 39 2.7 26.5.5 51.1-.3 52.6-1.8.6-.6-46.1-108.6-47.5-110.1-.5-.5-5.9 11.9-12.1 27.5</Geometry>

View File

@ -98,33 +98,16 @@
</Setter>
</Style>
<Style x:Key="DirectoryFilesListBox"
TargetType="ListBox"
BasedOn="{StaticResource {x:Type ListBox}}">
<Setter Property="ItemsSource"
Value="{Binding CUE4Parse.GameDirectory.DirectoryFilesView, IsAsync=True}" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility"
Value="Disabled" />
<Setter Property="adonisExtensions:ScrollViewerExtension.VerticalScrollBarExpansionMode"
Value="NeverExpand" />
<Setter Property="adonisExtensions:ScrollViewerExtension.VerticalScrollBarPlacement"
Value="Docked" />
<Style x:Key="DirectoryFilesListBox" TargetType="ListBox" BasedOn="{StaticResource {x:Type ListBox}}">
<Setter Property="ItemsSource" Value="{Binding CUE4Parse.GameDirectory.DirectoryFilesView, IsAsync=True}" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />
<Setter Property="adonisExtensions:ScrollViewerExtension.VerticalScrollBarExpansionMode" Value="NeverExpand" />
<Setter Property="adonisExtensions:ScrollViewerExtension.VerticalScrollBarPlacement" Value="Docked" />
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}"
BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="HorizontalContentAlignment"
Value="Stretch" />
<Setter Property="IsEnabled"
Value="{Binding IsEnabled}" />
<Setter Property="Margin"
Value="0 1" />
<Setter Property="Padding"
Value="7 5" />
<Setter Property="BorderBrush"
Value="Transparent" />
<Setter Property="BorderThickness"
Value="1" />
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="IsEnabled" Value="{Binding IsEnabled}" />
</Style>
</Setter.Value>
</Setter>
@ -135,97 +118,64 @@
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="95" />
<ColumnDefinition Width="85" />
<ColumnDefinition Width="Auto" SharedSizeGroup="FileCountCol" />
<ColumnDefinition Width="Auto" SharedSizeGroup="LengthCol" />
</Grid.ColumnDefinitions>
<Image x:Name="ListImage"
Source="/FModel;component/Resources/archive.png"
Width="16"
Height="16"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0 0 4 0"
<Image x:Name="ListImage" Source="/FModel;component/Resources/archive.png"
Width="16" Height="16" HorizontalAlignment="Center" Margin="0 0 4 0"
RenderOptions.BitmapScalingMode="HighQuality" />
<TextBlock Grid.Column="1"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Text="{Binding Name}"
TextTrimming="CharacterEllipsis" />
<TextBlock Grid.Column="1" HorizontalAlignment="Left" Text="{Binding Name}" TextTrimming="CharacterEllipsis" />
<TextBlock Grid.Column="2"
Margin="12 0 8 0"
VerticalAlignment="Center"
Margin="10 0 0 0"
HorizontalAlignment="Right"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Opacity="0.75"
VerticalAlignment="Bottom"
FontSize="11"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}"
Text="{Binding FileCount, StringFormat={}{0:N0} files}" />
<TextBlock x:Name="LengthText"
Grid.Column="3"
Margin="8 0 0 0"
VerticalAlignment="Center"
HorizontalAlignment="Right"
Text="{Binding Length, Converter={x:Static converters:SizeToStringConverter.Instance}}" />
Grid.Column="3" HorizontalAlignment="Right" Text="{Binding Length, Converter={x:Static converters:SizeToStringConverter.Instance}}"
Margin="10 0 0 0" />
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsEnabled}"
Value="True">
<Setter TargetName="ListImage"
Property="Source"
Value="/FModel;component/Resources/archive_enabled.png" />
<DataTrigger Binding="{Binding IsEnabled}" Value="True">
<Setter TargetName="ListImage" Property="Source" Value="/FModel;component/Resources/archive_enabled.png" />
</DataTrigger>
<DataTrigger Binding="{Binding IsEnabled}"
Value="False">
<Setter TargetName="ListImage"
Property="Source"
Value="/FModel;component/Resources/archive_disabled.png" />
<DataTrigger Binding="{Binding IsEnabled}" Value="False">
<Setter TargetName="ListImage" Property="Source" Value="/FModel;component/Resources/archive_disabled.png" />
</DataTrigger>
<DataTrigger Binding="{Binding IsLooseFilesContainer}"
Value="True">
<Setter TargetName="LengthText"
Property="Visibility"
Value="Collapsed" />
<Setter TargetName="ListImage"
Property="Source"
Value="/FModel;component/Resources/asset.png" />
<DataTrigger Binding="{Binding IsLooseFilesContainer}" Value="True">
<Setter TargetName="LengthText" Property="Visibility" Value="Collapsed" />
<Setter TargetName="ListImage" Property="Source" Value="/FModel;component/Resources/asset.png" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Items.Count, RelativeSource={RelativeSource Self}, FallbackValue=0}"
Value="0">
<DataTrigger Binding="{Binding Items.Count, RelativeSource={RelativeSource Self}, FallbackValue=0}" Value="0">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<TextBlock Text="No archives found in the specified directory"
FontWeight="SemiBold"
TextAlignment="Center"
<TextBlock Text="No archives found in the specified directory" FontWeight="SemiBold" TextAlignment="Center"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ErrorBrush}}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}"
Value="{x:Static local:ELoadingMode.Multiple}">
<Setter Property="SelectionMode"
Value="Extended" />
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}" Value="{x:Static local:ELoadingMode.Multiple}">
<Setter Property="SelectionMode" Value="Extended" />
</DataTrigger>
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}"
Value="{x:Static local:ELoadingMode.All}">
<Setter Property="SelectionMode"
Value="Extended" />
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}" Value="{x:Static local:ELoadingMode.All}">
<Setter Property="SelectionMode" Value="Extended" />
</DataTrigger>
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}"
Value="{x:Static local:ELoadingMode.AllButNew}">
<Setter Property="SelectionMode"
Value="Extended" />
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}" Value="{x:Static local:ELoadingMode.AllButNew}">
<Setter Property="SelectionMode" Value="Extended" />
</DataTrigger>
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}"
Value="{x:Static local:ELoadingMode.AllButModified}">
<Setter Property="SelectionMode"
Value="Extended" />
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}" Value="{x:Static local:ELoadingMode.AllButModified}">
<Setter Property="SelectionMode" Value="Extended" />
</DataTrigger>
</Style.Triggers>
</Style>
@ -973,6 +923,26 @@
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="Show Metadata" Command="{Binding TabCommand}" CommandParameter="Assets_Show_Metadata">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource InfoIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
<MenuItem.IsEnabled>
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}">
<Binding.Converter>
<converters:AnyItemMeetsConditionConverter>
<converters:AnyItemMeetsConditionConverter.Conditions>
<converters:ItemIsUePackageCondition />
</converters:AnyItemMeetsConditionConverter.Conditions>
</converters:AnyItemMeetsConditionConverter>
</Binding.Converter>
</Binding>
</MenuItem.IsEnabled>
</MenuItem>
<MenuItem Header="Find References" Command="{Binding TabCommand}" CommandParameter="Find_References">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
@ -994,63 +964,118 @@
</Binding>
</MenuItem.IsEnabled>
</MenuItem>
<MenuItem Header="Decompile Blueprint" Command="{Binding TabCommand}" CommandParameter="Assets_Decompile">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource CppIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
<MenuItem.Style>
<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource {x:Type MenuItem}}">
<Style.Triggers>
<DataTrigger Binding="{Binding ShowDecompileOption, Source={x:Static settings:UserSettings.Default}}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
<MenuItem.IsEnabled>
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}">
<Binding.Converter>
<converters:AnyItemMeetsConditionConverter>
<converters:AnyItemMeetsConditionConverter.Conditions>
<converters:ItemActionCondition Action="Code" />
</converters:AnyItemMeetsConditionConverter.Conditions>
</converters:AnyItemMeetsConditionConverter>
</Binding.Converter>
</Binding>
</MenuItem.IsEnabled>
</MenuItem>
<Separator />
<MenuItem Command="{Binding TabCommand}" CommandParameter="Save_Data">
<MenuItem.Header>
<TextBlock Text="{Binding Entry.Extension, FallbackValue='Export Raw Data', StringFormat='Export Raw Data (.{0})'}" />
</MenuItem.Header>
<MenuItem Header="Export">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExportIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Properties (.json)" Command="{Binding TabCommand}" CommandParameter="Save_Properties">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Texture" Command="{Binding TabCommand}" CommandParameter="Save_Textures">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource TextureIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Model" Command="{Binding TabCommand}" CommandParameter="Save_Models">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ModelIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Animation" Command="{Binding TabCommand}" CommandParameter="Save_Animations">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AnimationIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Audio" Command="{Binding TabCommand}" CommandParameter="Save_Audio">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AudioIcon}" />
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M12 2l.117 .007a1 1 0 0 1 .876 .876l.007 .117v4l.005 .15a2 2 0 0 0 1.838 1.844l.157 .006h4l.117 .007a1 1 0 0 1 .876 .876l.007 .117v9a3 3 0 0 1 -2.824 2.995l-.176 .005h-10a3 3 0 0 1 -2.995 -2.824l-.005 -.176v-14a3 3 0 0 1 2.824 -2.995l.176 -.005zm1 10l-5 5v2h2l5 -5a1.414 1.414 0 0 0 -2 -2" />
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M19 7h-4l-.001 -4.001z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
<MenuItem Command="{Binding TabCommand}" CommandParameter="Save_Data">
<MenuItem.Header>
<TextBlock Text="{Binding Entry.Extension, FallbackValue='Raw Data', StringFormat='Raw Data (.{0})'}" />
</MenuItem.Header>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExportIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Properties (.json)" Command="{Binding TabCommand}" CommandParameter="Save_Properties">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Texture" Command="{Binding TabCommand}" CommandParameter="Save_Textures">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource TextureIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Model" Command="{Binding TabCommand}" CommandParameter="Save_Models">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ModelIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="World" Command="{Binding TabCommand}" CommandParameter="Save_Worlds">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World1}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World2}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World3}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World4}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World5}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Animation" Command="{Binding TabCommand}" CommandParameter="Save_Animations">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AnimationIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Audio" Command="{Binding TabCommand}" CommandParameter="Save_Audio">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AudioIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<Separator />
<MenuItem Header="Open Properties" Command="{Binding TabCommand}" CommandParameter="Open_Properties">
@ -1073,7 +1098,7 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Copy Package Path" Command="{Binding TabCommand}" CommandParameter="Copy_Asset_Path">
<MenuItem Header="Copy">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
@ -1081,6 +1106,11 @@
</Canvas>
</Viewbox>
</MenuItem.Icon>
<MenuItem Header="Package Path" Command="{Binding TabCommand}" CommandParameter="File_Path" />
<MenuItem Header="Package Name" Command="{Binding TabCommand}" CommandParameter="File_Name" />
<MenuItem Header="Directory Path" Command="{Binding TabCommand}" CommandParameter="Directory_Path" />
<MenuItem Header="Package Path w/o Extension" Command="{Binding TabCommand}" CommandParameter="File_Path_No_Extension" />
<MenuItem Header="Package Name w/o Extension" Command="{Binding TabCommand}" CommandParameter="File_Name_No_Extension" />
</MenuItem>
</ContextMenu>
</Setter.Value>
@ -1419,7 +1449,7 @@
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
Style="{StaticResource CustomExpanderDownHeaderStyle}"/>
Style="{DynamicResource CustomExpanderDownHeaderStyle}"/>
<Border x:Name="ExpandSiteContainerWrapper"
DockPanel.Dock="Bottom"
@ -1881,6 +1911,20 @@
</Style.Triggers>
</Style>
<Style x:Key="StatusBarButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Foreground" Value="{Binding Foreground, RelativeSource={RelativeSource AncestorType=StatusBar}}"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="BorderThickness" Value="0"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="adonisExtensions:CursorSpotlightExtension.BackgroundBrush" Value="#33FFFFFF"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="PlayPauseToolbarButton" TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Content">
<Setter.Value>

View File

@ -161,6 +161,21 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Show Metadata" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Show_Metadata" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource InfoIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Find References" Click="OnFindRefs">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
@ -209,99 +224,131 @@
</MenuItem.IsEnabled>
</MenuItem>
<Separator />
<MenuItem Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.Header>
<TextBlock Text="{Binding DataContext.SelectedItem.Extension, FallbackValue='Export Raw Data',
StringFormat='Export Raw Data (.{0})', RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</MenuItem.Header>
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Data" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem Header="Export">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExportIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Properties (.json)" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Properties" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Texture" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Textures" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource TextureIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Model" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Models" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ModelIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Animation" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Animations" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AnimationIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Audio" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Audio" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AudioIcon}" />
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M12 2l.117 .007a1 1 0 0 1 .876 .876l.007 .117v4l.005 .15a2 2 0 0 0 1.838 1.844l.157 .006h4l.117 .007a1 1 0 0 1 .876 .876l.007 .117v9a3 3 0 0 1 -2.824 2.995l-.176 .005h-10a3 3 0 0 1 -2.995 -2.824l-.005 -.176v-14a3 3 0 0 1 2.824 -2.995l.176 -.005zm1 10l-5 5v2h2l5 -5a1.414 1.414 0 0 0 -2 -2" />
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M19 7h-4l-.001 -4.001z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
<MenuItem Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.Header>
<TextBlock Text="{Binding DataContext.SelectedItem.Extension, FallbackValue='Raw Data',
StringFormat='Raw Data (.{0})', RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</MenuItem.Header>
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Data" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExportIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Properties (.json)" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Properties" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Texture" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Textures" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource TextureIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Model" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Models" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ModelIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="World" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Worlds" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World1}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World2}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World3}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World4}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World5}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Animation" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Animations" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AnimationIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Audio" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Audio" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AudioIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<Separator />
<MenuItem Header="Copy">
@ -529,6 +576,21 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Show Metadata" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Show_Metadata" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource InfoIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Find References" Click="OnFindRefs">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
@ -554,99 +616,131 @@
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.Header>
<TextBlock Text="{Binding DataContext.SelectedItem.Extension, FallbackValue='Export Raw Data',
StringFormat='Export Raw Data (.{0})', RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</MenuItem.Header>
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Data" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem Header="Export">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExportIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Properties (.json)" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Properties" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Texture" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Textures" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource TextureIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Model" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Models" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ModelIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Animation" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Animations" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AnimationIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Audio" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Audio" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AudioIcon}" />
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M12 2l.117 .007a1 1 0 0 1 .876 .876l.007 .117v4l.005 .15a2 2 0 0 0 1.838 1.844l.157 .006h4l.117 .007a1 1 0 0 1 .876 .876l.007 .117v9a3 3 0 0 1 -2.824 2.995l-.176 .005h-10a3 3 0 0 1 -2.995 -2.824l-.005 -.176v-14a3 3 0 0 1 2.824 -2.995l.176 -.005zm1 10l-5 5v2h2l5 -5a1.414 1.414 0 0 0 -2 -2" />
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M19 7h-4l-.001 -4.001z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
<MenuItem Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.Header>
<TextBlock Text="{Binding DataContext.SelectedItem.Extension, FallbackValue='Raw Data',
StringFormat='Raw Data (.{0})', RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</MenuItem.Header>
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Data" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExportIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Properties (.json)" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Properties" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Texture" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Textures" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource TextureIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Model" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Models" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ModelIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="World" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Worlds" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World1}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World2}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World3}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World4}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World5}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Animation" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Animations" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AnimationIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Audio" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Audio" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AudioIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<Separator />
<MenuItem Header="Copy">

View File

@ -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"
@ -343,7 +343,12 @@
</Grid>
</Grid>
</DataTemplate>
<DataTemplate x:Key="ModelsTemplate">
<DataTemplate x:Key="ExportTemplate">
<controls:ExportOptionsControl
adonisExtensions:LayerExtension.Layer="2"
DataContext="{Binding DataContext.SettingsView.Options, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" />
</DataTemplate>
<DataTemplate x:Key="PreviewTemplate">
<Grid adonisExtensions:LayerExtension.Layer="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
@ -353,20 +358,6 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
@ -376,176 +367,40 @@
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Model Export Directory *" VerticalAlignment="Center" Margin="0 0 0 5"
ToolTip="This will be the directory where Meshes, Materials and Animations will be exported" />
<TextBox Grid.Row="0" Grid.Column="2" IsReadOnly="True" Text="{Binding ModelDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0 0 0 5" />
<Button Grid.Row="0" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseModels" Margin="0 0 0 5" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Mesh Format" VerticalAlignment="Center" Margin="0 0 0 5"/>
<ComboBox Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.MeshExportFormats}" SelectedItem="{Binding SettingsView.SelectedMeshExportFormat, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ContentControl Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="3" FontSize="10" Margin="0 0 0 5">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="Visibility" Value="Collapsed" />
<EventSetter Event="Hyperlink.Click" Handler="OnHyperlinkClick" />
<Style.Triggers>
<DataTrigger Binding="{Binding DataContext.SettingsView.SelectedMeshExportFormat, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Value="{x:Static c4pMeshes:EMeshFormat.UEFormat}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock>
<Run Text="Importer (Blender/UE): " />
<Hyperlink NavigateUri="https://github.com/h4lfheart/UEFormat">
github.com/h4lfheart/UEFormat
</Hyperlink>
</TextBlock>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding DataContext.SettingsView.SelectedMeshExportFormat, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Value="{x:Static c4pMeshes:EMeshFormat.ActorX}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock>
<Run Text="Importers (Blender): " />
<Hyperlink NavigateUri="https://github.com/DarklightGames/io_scene_psk_psa">
github.com/DarklightGames/io_scene_psk_psa
</Hyperlink>
</TextBlock>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
<TextBlock Grid.Row="3" Grid.Column="0" Text="Socket Format (ActorX)" VerticalAlignment="Center" Margin="0 0 0 5" IsEnabled="{Binding SettingsView.SocketSettingsEnabled}"/>
<ComboBox Grid.Row="3" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.SocketExportFormats}" SelectedItem="{Binding SettingsView.SelectedSocketExportFormat, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5" IsEnabled="{Binding SettingsView.SocketSettingsEnabled}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="4" Grid.Column="0" Text="Compression Format (UEFormat)" VerticalAlignment="Center" Margin="0 0 0 5" IsEnabled="{Binding SettingsView.CompressionSettingsEnabled}"/>
<ComboBox Grid.Row="4" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.CompressionFormats}" SelectedItem="{Binding SettingsView.SelectedCompressionFormat, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5" IsEnabled="{Binding SettingsView.CompressionSettingsEnabled}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="5" Grid.Column="0" Text="Level Of Detail 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>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Separator Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="5" Style="{StaticResource CustomSeparator}" />
<TextBlock Grid.Row="7" Grid.Column="0" Text="Preview Max Texture Size" VerticalAlignment="Center" Margin="0 0 0 5" />
<Slider Grid.Row="7" Grid.Column="2" Grid.ColumnSpan="3" TickPlacement="None" Minimum="4" Maximum="4096" Ticks="4,8,16,32,64,128,256,512,1024,2048,4096"
<TextBlock Grid.Row="0" Grid.Column="0" Text="Preview Max Texture Size" VerticalAlignment="Center" Margin="0 0 0 5" />
<Slider Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="3" TickPlacement="None" Minimum="4" Maximum="4096" Ticks="4,8,16,32,64,128,256,512,1024,2048,4096"
AutoToolTipPlacement="BottomRight" IsMoveToPointEnabled="True" IsSnapToTickEnabled="True" Margin="0 5 0 5"
Value="{Binding PreviewMaxTextureSize, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"/>
<TextBlock Grid.Row="8" Grid.Column="0" Text="Preview Static Meshes" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="8" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
<TextBlock Grid.Row="1" Grid.Column="0" Text="Preview Static Meshes" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding PreviewStaticMeshes, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
<TextBlock Grid.Row="9" Grid.Column="0" Text="Preview Skeletal Meshes" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="9" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
<TextBlock Grid.Row="2" Grid.Column="0" Text="Preview Skeletal Meshes" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding PreviewSkeletalMeshes, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
<TextBlock Grid.Row="10" Grid.Column="0" Text="Preview Animations" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="10" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
<TextBlock Grid.Row="3" Grid.Column="0" Text="Preview Animations" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="3" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding PreviewAnimations, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
<TextBlock Grid.Row="11" Grid.Column="0" Text="Preview Materials" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="11" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
<TextBlock Grid.Row="4" Grid.Column="0" Text="Preview Materials" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="4" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding PreviewMaterials, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
<TextBlock Grid.Row="12" Grid.Column="0" Text="Preview Levels (.umap)" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="12" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
<TextBlock Grid.Row="5" Grid.Column="0" Text="Preview Worlds (.umap)" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="5" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding PreviewWorlds, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
<TextBlock Grid.Row="13" Grid.Column="0" Text="Save Materials Embedded within Meshes" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="13" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding SaveEmbeddedMaterials, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
<TextBlock Grid.Row="14" Grid.Column="0" Text="Save Morph Targets in Meshes" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="14" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding SaveMorphTargets, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
<TextBlock Grid.Row="15" Grid.Column="0" Text="Handle Skeletons as Empty Meshes" VerticalAlignment="Center" />
<CheckBox Grid.Row="15" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
<TextBlock Grid.Row="6" Grid.Column="0" Text="Handle Skeletons as Empty Meshes" VerticalAlignment="Center" />
<CheckBox Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding SaveSkeletonAsMesh, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 0"/>
<Separator Grid.Row="16" Grid.Column="0" Grid.ColumnSpan="5" Style="{StaticResource CustomSeparator}" />
<TextBlock Grid.Row="17" Grid.Column="0" Text="Nanite Format" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="17" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.NaniteMeshExportFormats}" SelectedItem="{Binding SettingsView.SelectedNaniteMeshExportFormat, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="18" Grid.Column="0" Text="Material Format" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="18" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.MaterialExportFormats}" SelectedItem="{Binding SettingsView.SelectedMaterialExportFormat, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="19" Grid.Column="0" Text="Texture Format" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="19" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.TextureExportFormats}" SelectedItem="{Binding SettingsView.SelectedTextureExportFormat, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="20" Grid.Column="0" Text="Save HDR Textures as Radiance .hdr" VerticalAlignment="Center" />
<CheckBox Grid.Row="20" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding SaveHdrTexturesAsHdr, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="KeybindingsTemplate">
@ -564,6 +419,7 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
@ -610,6 +466,9 @@
<TextBlock Grid.Row="12" Grid.Column="0" Text="Next Audio" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="12" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding NextAudio, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<TextBlock Grid.Row="13" Grid.Column="0" Text="Remove Selected Audio" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="13" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding RemoveAudio, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
</Grid>
</DataTemplate>
<DataTemplate x:Key="UnluacTemplate">
@ -707,8 +566,6 @@
<DataTemplate x:Key="ThemesTemplate">
<Grid adonisExtensions:LayerExtension.Layer="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="10" />
<RowDefinition Height="Auto" />
<RowDefinition Height="10" />
<RowDefinition Height="*" />
@ -718,11 +575,11 @@
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0"
Grid.Column="0"
Text="JSON Highlight Theme"
VerticalAlignment="Center"
Margin="0 0 0 5" />
VerticalAlignment="Center" />
<ComboBox Grid.Row="0"
Grid.Column="2"
ItemsSource="{Binding SettingsView.JsonHighlightThemes}"
@ -737,12 +594,8 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Separator Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="3"
Style="{StaticResource CustomSeparator}"
Tag="PREVIEW" />
<Border Grid.Row="4"
<Border Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="3"
Padding="1"
@ -812,7 +665,20 @@
</Style>
</TreeViewItem.Style>
</TreeViewItem>
<TreeViewItem Tag="ModelsTemplate">
<TreeViewItem Tag="ExportTemplate">
<TreeViewItem.Header>
<StackPanel Orientation="Horizontal">
<Viewbox Width="16" Height="16" HorizontalAlignment="Center" Margin="-20 4 7.5 4">
<Canvas Width="24" Height="24">
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="M14 3v4a1 1 0 0 0 1 1h4" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="M11.5 21h-4.5a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v5m-5 6h7m-3 -3l3 3l-3 3" />
</Canvas>
</Viewbox>
<TextBlock Text="Export" HorizontalAlignment="Left" VerticalAlignment="Center" />
</StackPanel>
</TreeViewItem.Header>
</TreeViewItem>
<TreeViewItem Tag="PreviewTemplate">
<TreeViewItem.Header>
<StackPanel Orientation="Horizontal">
<Viewbox Width="16" Height="16" HorizontalAlignment="Center" Margin="-20 4 7.5 4">
@ -824,7 +690,7 @@
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="M19,14.87V9.13c0-0.72-0.38-1.38-1-1.73l-5-2.88c-0.31-0.18-0.65-0.27-1-0.27s-0.69,0.09-1,0.27L6,7.39 C5.38,7.75,5,8.41,5,9.13v5.74c0,0.72,0.38,1.38,1,1.73l5,2.88c0.31,0.18,0.65,0.27,1,0.27s0.69-0.09,1-0.27l5-2.88 C18.62,16.25,19,15.59,19,14.87z M11,17.17l-4-2.3v-4.63l4,2.33V17.17z M12,10.84L8.04,8.53L12,6.25l3.96,2.28L12,10.84z M17,14.87l-4,2.3v-4.6l4-2.33V14.87z" />
</Canvas>
</Viewbox>
<TextBlock Text="Models" HorizontalAlignment="Left" VerticalAlignment="Center" />
<TextBlock Text="Preview" HorizontalAlignment="Left" VerticalAlignment="Center" />
</StackPanel>
</TreeViewItem.Header>
</TreeViewItem>

View File

@ -67,8 +67,10 @@ public partial class SettingsView
_applicationView.CUE4Parse.Provider.ReadScriptData = UserSettings.Default.ReadScriptData;
_applicationView.CUE4Parse.Provider.ReadShaderMaps = UserSettings.Default.ReadShaderMaps;
_applicationView.CUE4Parse.Provider.Versions.Platform = UserSettings.Default.CurrentDir.TexturePlatform;
UserSettings.Save();
ExportSessionViewModel.Instance.Options.ResetToUserDefaults();
}
private void OnBrowseOutput(object sender, RoutedEventArgs e)

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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();

View File

@ -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);

View File

@ -0,0 +1,664 @@
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 CUE4Parse.Utils;
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();
foreach (var node in nodes) node.AddToExportSession(session);
_exportResults = await session.RunAsync(exportDirectory, options, _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 objectPath = log.GetContext("ObjectPath");
var filePath = log.GetContext("FilePath");
var cg = FindOrCreateClass(className);
var og = FindOrCreateObject(cg, objectPath);
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 path)
{
foreach (var og in cg.Objects)
if (og.Path == path) return og;
var n = new ObjectGroup(path);
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 path)
{
public string Path { get; } = path;
public string Name { get; } = path.SubstringAfterLast('.');
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);
}
}
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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];

View File

@ -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>();
@ -100,11 +99,11 @@ public class SkeletalModel : UModel
foreach (var morph in export.MorphTargets)
{
if (!morph.TryLoad(out UMorphTarget morphTarget) || morphTarget.MorphLODModels.Length <= skeletalMesh.LODs[LodLevel].LODIndex ||
morphTarget.MorphLODModels[skeletalMesh.LODs[LodLevel].LODIndex].Vertices.Length < 1)
if (!morph.TryLoad(out UMorphTarget morphTarget) || morphTarget.MorphLODModels.Length <= skeletalMesh.LODs[LodLevel].SourceLodIndex ||
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));
}
}

View File

@ -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)];

View File

@ -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;

View File

@ -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()

View File

@ -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;

View File

@ -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;

View File

@ -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))
{

View File

@ -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) =>
{