diff --git a/.gitmodules b/.gitmodules index 94049366..a660a84b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,7 @@ path = Penumbra.String url = git@github.com:Ottermandias/Penumbra.String.git branch = main +[submodule "Xande"] + path = Xande + url = git@github.com:XIVDev/Xande + branch = model-export diff --git a/Penumbra.sln b/Penumbra.sln index bccc56d8..5c0e8839 100644 --- a/Penumbra.sln +++ b/Penumbra.sln @@ -19,6 +19,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.Api", "Penumbra.Ap EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.String", "Penumbra.String\Penumbra.String.csproj", "{5549BAFD-6357-4B1A-800C-75AC36E5B76D}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xande", "Xande\Xande\Xande.csproj", "{87A71FEB-B5C5-4A95-A94A-1D7DF8CD5ECC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -45,6 +47,10 @@ Global {5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Debug|Any CPU.Build.0 = Debug|Any CPU {5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Release|Any CPU.ActiveCfg = Release|Any CPU {5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Release|Any CPU.Build.0 = Release|Any CPU + {87A71FEB-B5C5-4A95-A94A-1D7DF8CD5ECC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {87A71FEB-B5C5-4A95-A94A-1D7DF8CD5ECC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {87A71FEB-B5C5-4A95-A94A-1D7DF8CD5ECC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {87A71FEB-B5C5-4A95-A94A-1D7DF8CD5ECC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs index 6265e3fd..ef385f63 100644 --- a/Penumbra/Penumbra.cs +++ b/Penumbra/Penumbra.cs @@ -1,10 +1,8 @@ using System; using System.IO; using System.Linq; -using System.Reflection; using System.Text; using System.Threading.Tasks; -using Dalamud.Interface.Windowing; using Dalamud.Plugin; using ImGuiNET; using Lumina.Excel.GeneratedSheets; @@ -12,16 +10,13 @@ using Microsoft.Extensions.DependencyInjection; using OtterGui; using OtterGui.Classes; using OtterGui.Log; -using OtterGui.Widgets; using Penumbra.Api; using Penumbra.Api.Enums; -using Penumbra.Interop; using Penumbra.UI; using Penumbra.Util; using Penumbra.Collections; using Penumbra.GameData; using Penumbra.GameData.Actors; -using Penumbra.GameData.Data; using Penumbra.Interop.ResourceLoading; using Penumbra.Interop.PathResolving; using CharacterUtility = Penumbra.Interop.Services.CharacterUtility; @@ -32,6 +27,9 @@ using Penumbra.Interop.Services; using Penumbra.Mods.Manager; using Penumbra.Collections.Manager; using Penumbra.Mods; +using Penumbra.String.Classes; +using Xande; +using Xande.Havok; namespace Penumbra; @@ -61,6 +59,8 @@ public class Penumbra : IDalamudPlugin public static IObjectIdentifier Identifier { get; private set; } = null!; public static IGamePathParser GamePathParser { get; private set; } = null!; public static StainService StainService { get; private set; } = null!; + public static ModelConverter ModelConverter { get; private set; } = null!; + public static SklbResolver SklbResolver { get; private set; } = null!; // TODO public static ValidityChecker ValidityChecker { get; private set; } = null!; @@ -114,6 +114,13 @@ public class Penumbra : IDalamudPlugin PathResolver = _tmp.Services.GetRequiredService(); } + var lumina = new LuminaManager(DalamudServices.SGameData.GameData) + { + FileResolver = p + => Utf8GamePath.FromString(p, out var path, true) ? CollectionManager.Active.Current.ResolvePath(path)?.ToString() : null, + }; + ModelConverter = new ModelConverter(lumina, new HavokConverter()); + SklbResolver = new SklbResolver(); SetupInterface(); SetupApi(); @@ -121,7 +128,7 @@ public class Penumbra : IDalamudPlugin Log.Information( $"Penumbra Version {ValidityChecker.Version}, Commit #{ValidityChecker.CommitHash} successfully Loaded from {pluginInterface.SourceRepository}."); OtterTex.NativeDll.Initialize(pluginInterface.AssemblyLocation.DirectoryName); - Log.Information($"Loading native OtterTex assembly from {OtterTex.NativeDll.Directory}."); + Log.Information($"Loading native OtterTex assembly from {OtterTex.NativeDll.Directory}."); if (CharacterUtility.Ready) ResidentResources.Reload(); diff --git a/Penumbra/Penumbra.csproj b/Penumbra/Penumbra.csproj index cddc5812..ad01c5bc 100644 --- a/Penumbra/Penumbra.csproj +++ b/Penumbra/Penumbra.csproj @@ -78,6 +78,7 @@ + diff --git a/Penumbra/UI/AdvancedWindow/ResourceTreeViewer.cs b/Penumbra/UI/AdvancedWindow/ResourceTreeViewer.cs index 4d8c77a7..9121ab88 100644 --- a/Penumbra/UI/AdvancedWindow/ResourceTreeViewer.cs +++ b/Penumbra/UI/AdvancedWindow/ResourceTreeViewer.cs @@ -1,11 +1,16 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Numerics; using System.Threading.Tasks; using Dalamud.Interface; +using Dalamud.Utility; using ImGuiNET; using OtterGui.Raii; using OtterGui; +using Penumbra.GameData.Data; +using Penumbra.GameData.Enums; using Penumbra.Interop.ResourceTree; using Penumbra.UI.Classes; @@ -14,7 +19,7 @@ namespace Penumbra.UI.AdvancedWindow; public class ResourceTreeViewer { private readonly Configuration _config; - private readonly ResourceTreeFactory _treeFactory; + private readonly ResourceTreeFactory _treeFactory; private readonly int _actionCapacity; private readonly Action _onRefresh; private readonly Action _drawActions; @@ -67,7 +72,28 @@ public class ResourceTreeViewer using var id = ImRaii.PushId(index); - ImGui.Text($"Collection: {tree.CollectionName}"); + if (ImGui.Button("Export Character") && !_config.ExportDirectory.IsNullOrEmpty()) + { + try + { + var outputPath = Path.Combine(_config.ExportDirectory, $"{tree.Name}_{DateTime.UtcNow.Ticks}"); + var models = tree.Nodes.Where(n => n.Type is ResourceType.Mdl && !n.GamePath.Path.StartsWith("chara/weapon/"u8)).Select(n => n.GamePath.ToString()).ToArray(); + ushort? deform = tree.RaceCode is GenderRace.Unknown ? null : (ushort)tree.RaceCode; + var skeletons = Penumbra.SklbResolver.ResolveAll(models); + if (tree.RaceCode is not GenderRace.Unknown) + skeletons = skeletons.Prepend( + $"chara/human/c{tree.RaceCode.ToRaceCode()}/skeleton/base/b0001/skl_c{tree.RaceCode.ToRaceCode()}b0001.sklb") + .ToArray(); + Directory.CreateDirectory(outputPath); + Penumbra.ModelConverter.ExportModel(outputPath, models, skeletons, deform); + } + catch (Exception ex) + { + Penumbra.Log.Error($"Error exporting character:\n{ex}"); + } + + } + ImGui.TextUnformatted($"Collection: {tree.CollectionName}"); using var table = ImRaii.Table("##ResourceTree", _actionCapacity > 0 ? 4 : 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); diff --git a/Penumbra/packages.lock.json b/Penumbra/packages.lock.json index 26a67367..8c375f3c 100644 --- a/Penumbra/packages.lock.json +++ b/Penumbra/packages.lock.json @@ -46,6 +46,32 @@ "resolved": "5.0.0", "contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==" }, + "Microsoft.Win32.SystemEvents": { + "type": "Transitive", + "resolved": "8.0.0-preview.2.23128.3", + "contentHash": "2ugSY6UuqJfYhvVUaf4ueuUiMjIyN9dU03wpbKfRDsn13XvZ91yOlw7NLhYvB1Xc8gCAZpND20Tgbe3uBVytKA==" + }, + "SharpGLTF.Core": { + "type": "Transitive", + "resolved": "1.0.0-alpha0028", + "contentHash": "ALHHo0St08I77sZGEf/eFOIpSFF8aP/nbWNIvh27omQz/ds9MJADU4aF4MyOrHaW4Ir4bwZ1xF3QLwKyIH3Oow==" + }, + "SharpGLTF.Toolkit": { + "type": "Transitive", + "resolved": "1.0.0-alpha0028", + "contentHash": "CNw7Cc0vPtbeqag4Jv+/x7Dgc5Vfcz6PmE5TiVzfsyLho4uEve2suQO42KSJa85q8g9MeWU/cMVy0ND/9gz+Qw==", + "dependencies": { + "SharpGLTF.Core": "1.0.0-alpha0028" + } + }, + "System.Drawing.Common": { + "type": "Transitive", + "resolved": "8.0.0-preview.2.23128.3", + "contentHash": "kTEwdz83KrBKPVWEcrF4OGqLeJ6aK7jsPWBp/hX7ngT8BCvWLKAaBd5HZ4cmux0t7k5Lka/B/DqmKC8jc8TrOQ==", + "dependencies": { + "Microsoft.Win32.SystemEvents": "8.0.0-preview.2.23128.3" + } + }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", "resolved": "5.0.0", @@ -87,6 +113,14 @@ }, "penumbra.string": { "type": "Project" + }, + "xande": { + "type": "Project", + "dependencies": { + "SharpGLTF.Core": "[1.0.0-alpha0028, )", + "SharpGLTF.Toolkit": "[1.0.0-alpha0028, )", + "System.Drawing.Common": "[8.0.0-preview.2.23128.3, )" + } } } } diff --git a/Xande b/Xande new file mode 160000 index 00000000..6457d3ca --- /dev/null +++ b/Xande @@ -0,0 +1 @@ +Subproject commit 6457d3ca1b8c47f7e065539dfc47234b0f356a2a