Add lenient model export mode

This commit is contained in:
Ridan Vandenbergh 2025-08-02 01:15:49 +02:00
parent 6689e326ee
commit 1166c9e297
No known key found for this signature in database
5 changed files with 42 additions and 6 deletions

View file

@ -3,4 +3,5 @@ namespace Penumbra.Import.Models.Export;
public struct ExportConfig
{
public bool GenerateMissingBones;
public bool LenientMode;
}

View file

@ -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];
}
/// <summary> Check that the list has length 1 for any case where this is expected and return the one entry. Otherwise, report a warning. </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private T GetFirstUnsafe<T>(IReadOnlyDictionary<MdlFile.VertexUsage, List<T>> 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];
}
/// <summary> Convert a vertex attribute value to a Vector2. Supported inputs are Vector2, Vector3, and Vector4. </summary>
private static Vector2 ToVector2(object data)

View file

@ -12,8 +12,8 @@ public record class IoNotifier
=> this with { _context = $"{_context}{context}: "};
/// <summary> Send a warning with any current context to notification channels. </summary>
public void Warning(string content)
=> SendMessage(content, Logger.LogLevel.Warning);
public void Warning(string content, bool ignoreDuplicates = false)
=> SendMessage(content, Logger.LogLevel.Warning, ignoreDuplicates);
/// <summary> Get the current warnings for this notifier. </summary>
/// <remarks> This does not currently filter to notifications with the current notifier's context - it will return all IO notifications from all notifiers. </remarks>
@ -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);
}

View file

@ -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.");
}

View file

@ -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;