save mesh

This commit is contained in:
4sval 2022-12-03 02:02:29 +01:00
parent 88fa0dfd82
commit 7a9556e957
8 changed files with 147 additions and 41 deletions

@ -1 +1 @@
Subproject commit f71be1eb998ee38e94a1a77994fb21e0338905c1
Subproject commit 5987bbdb4e094c8c08e7b3cf05fc6ad6728299fb

View File

@ -873,11 +873,11 @@ public class CUE4ParseViewModel : ViewModel
};
var toSave = new Exporter(export, exportOptions);
var toSaveDirectory = new DirectoryInfo(UserSettings.Default.ModelDirectory);
if (toSave.TryWriteToDir(toSaveDirectory, out var savedFileName))
if (toSave.TryWriteToDir(toSaveDirectory, out var label, out var savedFilePath))
{
Log.Information("Successfully saved {FileName}", savedFileName);
Log.Information("Successfully saved {FilePath}", savedFilePath);
FLogger.AppendInformation();
FLogger.AppendText($"Successfully saved {savedFileName}", Constants.WHITE, true);
FLogger.AppendText($"Successfully saved {label}", Constants.WHITE, true);
}
else
{

View File

@ -4,7 +4,7 @@ namespace FModel.Views.Snooper;
public class Cube : Model
{
public Cube(UMaterialInterface unrealMaterial) : base(unrealMaterial.Name, unrealMaterial.ExportType)
public Cube(UMaterialInterface unrealMaterial) : base(unrealMaterial)
{
Indices = new uint[]
{

View File

@ -259,6 +259,15 @@ public class Material : IDisposable
}
}
public void ImGuiColors(Dictionary<string, FLinearColor> colors)
{
foreach ((string key, FLinearColor value) in colors.Reverse())
{
ImGui.ColorButton(key, new Vector4(value.R, value.G, value.B, value.A), ImGuiColorEditFlags.None, new Vector2(16));
ImGui.SameLine();ImGui.Text(key);SnimGui.TooltipCopy(key);
}
}
public void ImGuiTextures(Dictionary<string, Texture> icons, Model model)
{
if (ImGui.BeginTable("material_textures", 2))
@ -286,7 +295,9 @@ public class Material : IDisposable
ImGui.EndTable();
}
ImGui.Image(GetSelectedTexture() ?? icons["noimage"].GetPointer(), new Vector2(ImGui.GetContentRegionAvail().X), Vector2.Zero, Vector2.One, Vector4.One, new Vector4(1.0f, 1.0f, 1.0f, 0.25f));
ImGui.Image(GetSelectedTexture() ?? icons["noimage"].GetPointer(),
new Vector2(ImGui.GetContentRegionAvail().X - ImGui.GetScrollX()),
Vector2.Zero, Vector2.One, Vector4.One, new Vector4(1.0f, 1.0f, 1.0f, 0.25f));
ImGui.Spacing();
}

View File

@ -1,12 +1,20 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;
using CUE4Parse_Conversion;
using CUE4Parse.UE4.Assets.Exports.Animation;
using CUE4Parse.UE4.Objects.UObject;
using CUE4Parse_Conversion.Meshes.PSK;
using CUE4Parse.UE4.Assets;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
using FModel.Extensions;
using FModel.Services;
using FModel.Settings;
using OpenTK.Graphics.OpenGL4;
namespace FModel.Views.Snooper;
@ -21,10 +29,12 @@ public class Model : IDisposable
private BufferObject<Matrix4x4> _matrixVbo;
private VertexArrayObject<float, uint> _vao;
private readonly UObject _export;
private readonly int _vertexSize = 13; // VertexIndex + Position + Normal + Tangent + UV + TextureLayer
private readonly uint[] _facesIndex = { 1, 0, 2 };
private const int _faceSize = 3; // just so we don't have to do .Length
public readonly string Path;
public readonly string Name;
public readonly string Type;
public readonly bool HasVertexColors;
@ -50,20 +60,22 @@ public class Model : IDisposable
public int SelectedInstance;
public float MorphTime;
protected Model(string name, string type)
protected Model(UObject export)
{
Name = name;
Type = type;
_export = export;
Path = _export.GetPathName();
Name = Path.SubstringAfterLast('/').SubstringBefore('.');
Type = export.ExportType;
NumTexCoords = 1;
Transforms = new List<Transform>();
}
public Model(string name, string type, ResolvedObject[] materials, CStaticMesh staticMesh) : this(name, type, materials, staticMesh, Transform.Identity) {}
public Model(string name, string type, ResolvedObject[] materials, CStaticMesh staticMesh, Transform transform) : this(name, type, materials, null, staticMesh.LODs[0], staticMesh.LODs[0].Verts, transform) {}
public Model(string name, string type, ResolvedObject[] materials, FPackageIndex skeleton, CSkeletalMesh skeletalMesh) : this(name, type, materials, skeleton, skeletalMesh, Transform.Identity) {}
public Model(string name, string type, ResolvedObject[] materials, FPackageIndex skeleton, CSkeletalMesh skeletalMesh, Transform transform) : this(name, type, materials, skeleton, skeletalMesh.LODs[0], skeletalMesh.LODs[0].Verts, transform) {}
public Model(string name, string type, ResolvedObject[] materials, FPackageIndex skeleton, FPackageIndex[] morphTargets, CSkeletalMesh skeletalMesh) : this(name, type, materials, skeleton, skeletalMesh)
public Model(UStaticMesh export, CStaticMesh staticMesh) : this(export, staticMesh, Transform.Identity) {}
public Model(UStaticMesh export, CStaticMesh staticMesh, Transform transform) : this(export, export.Materials, null, staticMesh.LODs[0], staticMesh.LODs[0].Verts, transform) {}
private Model(USkeletalMesh export, CSkeletalMesh skeletalMesh, Transform transform) : this(export, export.Materials, export.Skeleton, skeletalMesh.LODs[0], skeletalMesh.LODs[0].Verts, transform) {}
public Model(USkeletalMesh export, CSkeletalMesh skeletalMesh) : this(export, skeletalMesh, Transform.Identity)
{
var morphTargets = export.MorphTargets;
if (morphTargets is not { Length: > 0 })
return;
@ -74,12 +86,12 @@ public class Model : IDisposable
for (var i = 0; i < Morphs.Length; i++)
{
Morphs[i] = new Morph(Vertices, _vertexSize, morphTargets[i].Load<UMorphTarget>());
Services.ApplicationService.ApplicationView.Status.UpdateStatusLabel($"{Morphs[i].Name} ... {i}/{length}");
ApplicationService.ApplicationView.Status.UpdateStatusLabel($"{Morphs[i].Name} ... {i}/{length}");
}
Services.ApplicationService.ApplicationView.Status.UpdateStatusLabel("");
ApplicationService.ApplicationView.Status.UpdateStatusLabel("");
}
private Model(string name, string type, ResolvedObject[] materials, FPackageIndex skeleton, CBaseMeshLod lod, CMeshVertex[] vertices, Transform transform = null) : this(name, type)
private Model(UObject export, ResolvedObject[] materials, FPackageIndex skeleton, CBaseMeshLod lod, CMeshVertex[] vertices, Transform transform = null) : this(export)
{
NumTexCoords = lod.NumTexCoords;
@ -305,6 +317,20 @@ public class Model : IDisposable
GL.Disable(EnableCap.StencilTest);
}
public bool TrySave(out string label, out string savedFilePath)
{
var exportOptions = new ExporterOptions
{
TextureFormat = UserSettings.Default.TextureExportFormat,
LodFormat = UserSettings.Default.LodExportFormat,
MeshFormat = UserSettings.Default.MeshExportFormat,
Platform = UserSettings.Default.OverridedPlatform,
ExportMorphTargets = UserSettings.Default.SaveMorphTargets
};
var toSave = new Exporter(_export, exportOptions);
return toSave.TryWriteToDir(new DirectoryInfo(UserSettings.Default.ModelDirectory), out label, out savedFilePath);
}
public void Dispose()
{
_ebo.Dispose();

View File

@ -157,7 +157,7 @@ public class Renderer : IDisposable
if (!original.TryConvert(out var mesh))
return null;
Options.Models[guid] = new Model(original.Name, original.ExportType, original.Materials, mesh);
Options.Models[guid] = new Model(original, mesh);
Options.SelectModel(guid);
return SetupCamera(mesh.BoundingBox *= Constants.SCALE_DOWN_RATIO);
}
@ -167,7 +167,7 @@ public class Renderer : IDisposable
var guid = Guid.NewGuid();
if (Options.Models.ContainsKey(guid) || !original.TryConvert(out var mesh)) return null;
Options.Models[guid] = new Model(original.Name, original.ExportType, original.Materials, original.Skeleton, original.MorphTargets, mesh);
Options.Models[guid] = new Model(original, mesh);
Options.SelectModel(guid);
return SetupCamera(mesh.BoundingBox *= Constants.SCALE_DOWN_RATIO);
}
@ -231,7 +231,7 @@ public class Renderer : IDisposable
private void WorldMesh(UObject actor, Transform transform)
{
if (!actor.TryGetValue(out FPackageIndex staticMeshComponent, "StaticMeshComponent", "Mesh", "LightMesh") ||
if (!actor.TryGetValue(out FPackageIndex staticMeshComponent, "StaticMeshComponent", "StaticMesh", "Mesh", "LightMesh") ||
staticMeshComponent.Load() is not { } staticMeshComp) return;
if (!staticMeshComp.TryGetValue(out FPackageIndex staticMesh, "StaticMesh") && actor.Class is UBlueprintGeneratedClass)
@ -257,7 +257,7 @@ public class Renderer : IDisposable
}
else if (m.TryConvert(out var mesh))
{
model = new Model(m.Name, m.ExportType, m.Materials, mesh, t);
model = new Model(m, mesh, t);
if (actor.TryGetAllValues(out FPackageIndex[] textureData, "TextureData"))
{
for (int j = 0; j < textureData.Length; j++)

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using CUE4Parse.UE4.Objects.Core.Misc;
using FModel.Framework;
using ImGuiNET;
@ -8,17 +9,42 @@ using System.Numerics;
namespace FModel.Views.Snooper;
public class Swap
{
public bool Value;
public bool IsAware;
public void Reset()
{
Value = false;
}
}
public class Save
{
public bool Value;
public string Label;
public string Path;
public void Reset()
{
Value = false;
Label = string.Empty;
Path = string.Empty;
}
}
public class SnimGui
{
public readonly ImGuiController Controller;
private bool _viewportFocus;
private bool _swapAwareness;
private readonly Swap _swapper = new ();
private readonly Save _saver = new ();
private readonly Vector4 _accentColor = new (0.125f, 0.42f, 0.831f, 1.0f);
private readonly Vector4 _alertColor = new (0.831f, 0.573f, 0.125f, 1.0f);
private readonly Vector4 _errorColor = new (0.761f, 0.169f, 0.169f, 1.0f);
private const ImGuiCond _firstUse = ImGuiCond.FirstUseEver; // switch to FirstUseEver once the docking branch will not be useful anymore...
private const uint _dockspaceId = 1337;
public SnimGui(int width, int height)
@ -132,7 +158,7 @@ public class SnimGui
const string text = "Press ESC to Exit...";
ImGui.SetCursorPosX(ImGui.GetWindowViewport().WorkSize.X - ImGui.CalcTextSize(text).X - 5);
ImGui.TextColored(new Vector4(0.36f, 0.42f, 0.47f, 1.00f), text); // ImGuiCol.TextDisabled
ImGui.TextColored(new Vector4(0.36f, 0.42f, 0.47f, 1.00f), text);
ImGui.EndMainMenuBar();
}
@ -176,9 +202,12 @@ public class SnimGui
ImGui.Separator();
if (ImGui.Selectable("Save"))
{
s.WindowShouldFreeze(true);
_saver.Value = model.TrySave(out _saver.Label, out _saver.Path);
s.WindowShouldFreeze(false);
}
ImGui.BeginDisabled(!model.HasSkeleton);
ImGui.BeginDisabled(true);
// ImGui.BeginDisabled(!model.HasSkeleton);
if (ImGui.Selectable("Animate"))
{
s.Renderer.Options.AnimateMesh(true);
@ -200,6 +229,30 @@ public class SnimGui
i++;
}
Popup("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();
}
});
ImGui.EndTable();
}
});
@ -260,7 +313,6 @@ public class SnimGui
ImGui.TableSetupColumn("Material");
ImGui.TableHeadersRow();
var swap = false;
for (var i = 0; i < model.Sections.Length; i++)
{
var section = model.Sections[i];
@ -286,12 +338,12 @@ public class SnimGui
if (ImGui.MenuItem("Show", null, section.Show)) section.Show = !section.Show;
if (ImGui.Selectable("Swap"))
{
if (_swapAwareness)
if (_swapper.IsAware)
{
s.Renderer.Options.SwapMaterial(true);
s.WindowShouldClose(true, false);
}
else swap = true;
else _swapper.Value = true;
}
ImGui.Separator();
if (ImGui.Selectable("Copy Name to Clipboard")) ImGui.SetClipboardText(material.Name);
@ -301,23 +353,21 @@ public class SnimGui
}
ImGui.EndTable();
var p_open = true;
if (swap) ImGui.OpenPopup("Swap?");
ImGui.SetNextWindowPos(ImGui.GetMainViewport().GetCenter(), ImGuiCond.Appearing, new Vector2(.5f));
if (ImGui.BeginPopupModal("Swap?", ref p_open, ImGuiWindowFlags.AlwaysAutoResize))
Popup("Swap?", _swapper.Value, () =>
{
ImGui.TextWrapped("You're about to swap a material.\nThe window will close for you to extract a material!\n\n");
ImGui.Separator();
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero);
ImGui.Checkbox("Got it! Don't show me again", ref _swapAwareness);
ImGui.Checkbox("Got it! Don't show me again", ref _swapper.IsAware);
ImGui.PopStyleVar();
var size = new Vector2(120, 0);
if (ImGui.Button("OK", size))
{
ImGui.CloseCurrentPopup();
_swapper.Reset();
s.Renderer.Options.SwapMaterial(true);
ImGui.CloseCurrentPopup();
s.WindowShouldClose(true, false);
}
@ -326,14 +376,14 @@ public class SnimGui
if (ImGui.Button("Cancel", size))
{
_swapper.Reset();
ImGui.CloseCurrentPopup();
}
ImGui.EndPopup();
}
});
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Transform"))
{
const int width = 100;
@ -397,6 +447,7 @@ public class SnimGui
model.UpdateMatrix(model.SelectedInstance);
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Morph Targets"))
{
if (model.HasMorphTargets)
@ -426,7 +477,7 @@ public class SnimGui
ImGui.Text($"Time: {model.MorphTime:P}%");
}
}
else ImGui.TextColored(_errorColor, "mesh has no morph targets");
else CenteredTextColored(_errorColor, "Selected Mesh Has No Morph Targets");
ImGui.EndTabItem();
}
}
@ -473,7 +524,7 @@ public class SnimGui
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.TreeNode("Colors"))
{
material.ImGuiDictionaries("colors", material.Parameters.Colors, true);
material.ImGuiColors(material.Parameters.Colors);
ImGui.TreePop();
}
if (ImGui.TreeNode("Referenced Textures"))
@ -535,6 +586,18 @@ public class SnimGui
ImGui.PopStyleVar();
}
private void Popup(string title, bool condition, Action content)
{
var pOpen = true;
if (condition) ImGui.OpenPopup(title);
ImGui.SetNextWindowPos(ImGui.GetMainViewport().GetCenter(), ImGuiCond.Appearing, new Vector2(.5f));
if (ImGui.BeginPopupModal(title, ref pOpen, ImGuiWindowFlags.AlwaysAutoResize))
{
content();
ImGui.EndPopup();
}
}
private void Window(string name, Action content, bool styled = true)
{
if (ImGui.Begin(name, ImGuiWindowFlags.NoScrollbar))
@ -682,7 +745,7 @@ public class SnimGui
style.Colors[(int) ImGuiCol.TabHovered] = new Vector4(0.35f, 0.35f, 0.41f, 0.80f);
style.Colors[(int) ImGuiCol.TabActive] = new Vector4(0.23f, 0.24f, 0.29f, 1.00f);
style.Colors[(int) ImGuiCol.TabUnfocused] = new Vector4(0.15f, 0.15f, 0.15f, 1.00f);
style.Colors[(int) ImGuiCol.TabUnfocusedActive] = new Vector4(0.15f, 0.15f, 0.15f, 1.00f);
style.Colors[(int) ImGuiCol.TabUnfocusedActive] = new Vector4(0.23f, 0.24f, 0.29f, 1.00f);
style.Colors[(int) ImGuiCol.DockingPreview] = new Vector4(0.26f, 0.59f, 0.98f, 0.70f);
style.Colors[(int) ImGuiCol.DockingEmptyBg] = new Vector4(0.20f, 0.20f, 0.20f, 1.00f);
style.Colors[(int) ImGuiCol.PlotLines] = new Vector4(0.61f, 0.61f, 0.61f, 1.00f);

View File

@ -59,6 +59,12 @@ public class Snooper : GameWindow
IsVisible = !value;
}
public unsafe void WindowShouldFreeze(bool value)
{
GLFW.SetWindowShouldClose(WindowPtr, value); // start / stop game loop
IsVisible = true;
}
public override void Run()
{
Application.Current.Dispatcher.Invoke(delegate