mirror of
https://github.com/4sval/FModel.git
synced 2026-04-05 00:26:17 -05:00
save mesh
This commit is contained in:
parent
88fa0dfd82
commit
7a9556e957
|
|
@ -1 +1 @@
|
|||
Subproject commit f71be1eb998ee38e94a1a77994fb21e0338905c1
|
||||
Subproject commit 5987bbdb4e094c8c08e7b3cf05fc6ad6728299fb
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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[]
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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++)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user