diff --git a/Penumbra/Import/Models/ModelManager.cs b/Penumbra/Import/Models/ModelManager.cs index e77a94e3..afb92fc0 100644 --- a/Penumbra/Import/Models/ModelManager.cs +++ b/Penumbra/Import/Models/ModelManager.cs @@ -10,7 +10,7 @@ using SharpGLTF.Schema2; namespace Penumbra.Import.Models; -public sealed class ModelManager(IFramework framework, GamePathParser _parser) : SingleTaskQueue, IDisposable +public sealed class ModelManager(IFramework framework, GamePathParser parser) : SingleTaskQueue, IDisposable { private readonly IFramework _framework = framework; @@ -29,17 +29,17 @@ public sealed class ModelManager(IFramework framework, GamePathParser _parser) : public Task ExportToGltf(MdlFile mdl, SklbFile? sklb, string outputPath) => Enqueue(new ExportToGltfAction(this, mdl, sklb, outputPath)); - public Task ImportGltf() + public Task ImportGltf(string inputPath) { - var action = new ImportGltfAction(); - return Enqueue(action).ContinueWith(_ => action.Out!); + var action = new ImportGltfAction(inputPath); + return Enqueue(action).ContinueWith(_ => action.Out); } /// Try to find the .sklb path for a .mdl file. /// .mdl file to look up the skeleton for. public string? ResolveSklbForMdl(string mdlPath) { - var info = _parser.GetFileInfo(mdlPath); + var info = parser.GetFileInfo(mdlPath); if (info.FileType is not FileType.Model) return null; @@ -126,18 +126,13 @@ public sealed class ModelManager(IFramework framework, GamePathParser _parser) : } } - private partial class ImportGltfAction : IAction + private partial class ImportGltfAction(string inputPath) : IAction { public MdlFile? Out; - public ImportGltfAction() - { - // - } - public void Execute(CancellationToken cancel) { - var model = ModelRoot.Load("C:\\Users\\ackwell\\blender\\gltf-tests\\c0201e6180_top.gltf"); + var model = ModelRoot.Load(inputPath); Out = ModelImporter.Import(model); } diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs index 90a6645a..06196610 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs @@ -18,8 +18,9 @@ public partial class ModEditWindow public List? GamePaths { get; private set; } public int GamePathIndex; - public bool PendingIo { get; private set; } - public string? IoException { get; private set; } + private bool _dirty; + public bool PendingIo { get; private set; } + public string? IoException { get; private set; } public MdlTab(ModEditWindow edit, byte[] bytes, string path, IMod? mod) { @@ -46,6 +47,16 @@ public partial class ModEditWindow public byte[] Write() => Mdl.Write(); + public bool Dirty + { + get + { + var dirty = _dirty; + _dirty = false; + return dirty; + } + } + /// Find the list of game paths that may correspond to this model. /// Resolved path to a .mdl. /// Mod within which the .mdl is resolved. @@ -77,14 +88,28 @@ public partial class ModEditWindow }); } - public void Import() + /// Import a model from an interchange format. + /// Disk path to load model data from. + public void Import(string inputPath) { - // TODO: this needs to be fleshed out a bunch. - _edit._models.ImportGltf().ContinueWith(v => Initialize(v.Result ?? Mdl)); + PendingIo = true; + _edit._models.ImportGltf(inputPath) + .ContinueWith(task => + { + IoException = task.Exception?.ToString(); + PendingIo = false; + + if (task.IsCompletedSuccessfully && task.Result != null) + { + Initialize(task.Result); + _dirty = true; + } + }); } /// Export model to an interchange format. /// Disk path to save the resulting file to. + /// Game path to consider as the canonical .mdl path during export, used for resolution of other files. public void Export(string outputPath, Utf8GamePath mdlPath) { SklbFile? sklb = null; diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs index 5703c882..b3598b9d 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs @@ -35,15 +35,9 @@ public partial class ModEditWindow ); } - DrawExport(tab, disabled); + DrawImportExport(tab, disabled); - var ret = false; - - if (ImGui.Button("import test")) - { - tab.Import(); - ret |= true; - } + var ret = tab.Dirty; ret |= DrawModelMaterialDetails(tab, disabled); @@ -56,11 +50,41 @@ public partial class ModEditWindow return !disabled && ret; } - private void DrawExport(MdlTab tab, bool disabled) + private void DrawImportExport(MdlTab tab, bool disabled) { - if (!ImGui.CollapsingHeader("Export")) + if (!ImGui.CollapsingHeader("Import / Export")) return; + var windowWidth = ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X; + var childWidth = (windowWidth - ImGui.GetStyle().ItemSpacing.X * 3) / 2; + var childSize = new Vector2(childWidth, 0); + + DrawImport(tab, childSize, disabled); + ImGui.SameLine(); + DrawExport(tab, childSize, disabled); + + if (tab.IoException != null) + ImGuiUtil.TextWrapped(tab.IoException); + } + + private void DrawImport(MdlTab tab, Vector2 size, bool disabled) + { + using var frame = ImRaii.FramedGroup("Import", size); + + if (ImGuiUtil.DrawDisabledButton("Import from glTF", Vector2.Zero, "Imports a glTF file, overriding the content of this mdl.", tab.PendingIo)) + { + _fileDialog.OpenFilePicker("Load model from glTF.", "glTF{.gltf,.glb}", (success, paths) => + { + if (success && paths.Count > 0) + tab.Import(paths[0]); + }, 1, _mod!.ModPath.FullName, false); + } + } + + private void DrawExport(MdlTab tab, Vector2 size, bool disabled) + { + using var frame = ImRaii.FramedGroup("Export", size); + if (tab.GamePaths == null) { if (tab.IoException == null) @@ -89,9 +113,6 @@ public partial class ModEditWindow _mod!.ModPath.FullName, false ); - - if (tab.IoException != null) - ImGuiUtil.TextWrapped(tab.IoException); } private void DrawGamePathCombo(MdlTab tab) @@ -116,7 +137,7 @@ public partial class ModEditWindow const string label = "Game Path"; var preview = tab.GamePaths![tab.GamePathIndex].ToString(); var labelWidth = ImGui.CalcTextSize(label).X + ImGui.GetStyle().ItemInnerSpacing.X; - var buttonWidth = ImGui.GetContentRegionAvail().X - labelWidth; + var buttonWidth = ImGui.GetContentRegionAvail().X - labelWidth - ImGui.GetStyle().ItemSpacing.X; if (tab.GamePaths!.Count == 1) { using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f));