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;