diff --git a/Penumbra/Import/Models/Export/Config.cs b/Penumbra/Import/Models/Export/Config.cs index 58329a1d..4ea5163c 100644 --- a/Penumbra/Import/Models/Export/Config.cs +++ b/Penumbra/Import/Models/Export/Config.cs @@ -3,4 +3,5 @@ namespace Penumbra.Import.Models.Export; public struct ExportConfig { public bool GenerateMissingBones; + public bool LenientMode; } diff --git a/Penumbra/Import/Models/Export/MeshExporter.cs b/Penumbra/Import/Models/Export/MeshExporter.cs index 2e41f65a..9660b59c 100644 --- a/Penumbra/Import/Models/Export/MeshExporter.cs +++ b/Penumbra/Import/Models/Export/MeshExporter.cs @@ -445,7 +445,7 @@ public class MeshExporter return new VertexTexture2ColorFfxiv( new Vector2(uv.X, uv.Y), new Vector2(uv.Z, uv.W), - ToVector4(GetFirstSafe(attributes, MdlFile.VertexUsage.Color)) + ToVector4(_config.LenientMode ? GetFirstUnsafe(attributes, MdlFile.VertexUsage.Color) : GetFirstSafe(attributes, MdlFile.VertexUsage.Color)) ); } if (_materialType == typeof(VertexTexture3)) @@ -468,7 +468,7 @@ public class MeshExporter new Vector2(uv0.X, uv0.Y), new Vector2(uv0.Z, uv0.W), new Vector2(uv1.X, uv1.Y), - ToVector4(GetFirstSafe(attributes, MdlFile.VertexUsage.Color)) + ToVector4(_config.LenientMode ? GetFirstUnsafe(attributes, MdlFile.VertexUsage.Color) : GetFirstSafe(attributes, MdlFile.VertexUsage.Color)) ); } @@ -537,6 +537,23 @@ public class MeshExporter return list[0]; } + + /// Check that the list has length 1 for any case where this is expected and return the one entry. Otherwise, report a warning. + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private T GetFirstUnsafe(IReadOnlyDictionary> attributes, MdlFile.VertexUsage usage) + { + var list = attributes[usage]; + switch (list.Count) + { + case > 1: + _notifier.Warning($"Multiple usage indices encountered for {usage}.", true); + break; + case 0: + throw _notifier.Exception($"No usage indices encountered for {usage}"); + } + + return list[0]; + } /// Convert a vertex attribute value to a Vector2. Supported inputs are Vector2, Vector3, and Vector4. private static Vector2 ToVector2(object data) diff --git a/Penumbra/Import/Models/IoNotifier.cs b/Penumbra/Import/Models/IoNotifier.cs index 56ef7103..d7d2df2a 100644 --- a/Penumbra/Import/Models/IoNotifier.cs +++ b/Penumbra/Import/Models/IoNotifier.cs @@ -12,8 +12,8 @@ public record class IoNotifier => this with { _context = $"{_context}{context}: "}; /// Send a warning with any current context to notification channels. - public void Warning(string content) - => SendMessage(content, Logger.LogLevel.Warning); + public void Warning(string content, bool ignoreDuplicates = false) + => SendMessage(content, Logger.LogLevel.Warning, ignoreDuplicates); /// Get the current warnings for this notifier. /// This does not currently filter to notifications with the current notifier's context - it will return all IO notifications from all notifiers. @@ -31,9 +31,15 @@ public record class IoNotifier where TException : Exception, new() => (TException)Activator.CreateInstance(typeof(TException), $"{_context}{message}")!; - private void SendMessage(string message, Logger.LogLevel type) + private void SendMessage(string message, Logger.LogLevel type, bool ignoreDuplicates = false) { var fullText = $"{_context}{message}"; + + if (ignoreDuplicates && _messages.Contains(fullText)) + { + return; + } + Penumbra.Log.Message(type, fullText); _messages.Add(fullText); } diff --git a/Penumbra/Import/Models/ModelManager.cs b/Penumbra/Import/Models/ModelManager.cs index 6818ad64..c331ee32 100644 --- a/Penumbra/Import/Models/ModelManager.cs +++ b/Penumbra/Import/Models/ModelManager.cs @@ -16,6 +16,7 @@ using Penumbra.Meta; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; using SharpGLTF.Scenes; +using SharpGLTF.Validation; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; @@ -216,7 +217,10 @@ public sealed class ModelManager(IFramework framework, MetaFileManager metaFileM Penumbra.Log.Debug("[GLTF Export] Saving..."); var gltfModel = scene.ToGltf2(); - gltfModel.Save(outputPath); + gltfModel.Save(outputPath, new Schema2.WriteSettings() + { + Validation = config.LenientMode ? ValidationMode.TryFix : ValidationMode.Strict, + }); Penumbra.Log.Debug("[GLTF Export] Done."); } diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs index cc592296..55edbc70 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs @@ -168,6 +168,14 @@ public partial class ModEditWindow + "It is primarily intended to allow exporting models weighted to bones that do not exist.\n" + "Before enabling, ensure dependencies are enabled in the current collection, and EST metadata is correctly configured."); + ImGui.SameLine(200 * UiHelpers.Scale + ImGui.GetStyle().ItemSpacing.X + ImGui.GetStyle().WindowPadding.X); + + ImGui.Checkbox("##tryFixValidation", ref tab.ExportConfig.LenientMode); + ImGui.SameLine(); + ImGuiUtil.LabeledHelpMarker("Lenient mode", + "Try fixing potential errors during model validation and ignore superfluous color information.\n" + + "This may result in a broken model, or one missing information, so use with care!"); + var gamePath = tab.GamePathIndex >= 0 && tab.GamePathIndex < tab.GamePaths.Count ? tab.GamePaths[tab.GamePathIndex] : _customGamePath;