Wire up notifications through export

This commit is contained in:
ackwell 2024-01-19 02:11:50 +11:00
parent 5c15a3a4ff
commit 6f3be39cb9
5 changed files with 55 additions and 40 deletions

View file

@ -23,7 +23,7 @@ public class MaterialExporter
} }
/// <summary> Build a glTF material from a hydrated XIV model, with the provided name. </summary> /// <summary> Build a glTF material from a hydrated XIV model, with the provided name. </summary>
public static MaterialBuilder Export(Material material, string name) public static MaterialBuilder Export(Material material, string name, IoNotifier notifier)
{ {
Penumbra.Log.Debug($"Exporting material \"{name}\"."); Penumbra.Log.Debug($"Exporting material \"{name}\".");
return material.Mtrl.ShaderPackage.Name switch return material.Mtrl.ShaderPackage.Name switch
@ -34,7 +34,7 @@ public class MaterialExporter
"hair.shpk" => BuildHair(material, name), "hair.shpk" => BuildHair(material, name),
"iris.shpk" => BuildIris(material, name), "iris.shpk" => BuildIris(material, name),
"skin.shpk" => BuildSkin(material, name), "skin.shpk" => BuildSkin(material, name),
_ => BuildFallback(material, name), _ => BuildFallback(material, name, notifier),
}; };
} }
@ -335,9 +335,9 @@ public class MaterialExporter
/// <summary> Build a material from a source with unknown semantics. </summary> /// <summary> Build a material from a source with unknown semantics. </summary>
/// <remarks> Will make a loose effort to fetch common / simple textures. </remarks> /// <remarks> Will make a loose effort to fetch common / simple textures. </remarks>
private static MaterialBuilder BuildFallback(Material material, string name) private static MaterialBuilder BuildFallback(Material material, string name, IoNotifier notifier)
{ {
Penumbra.Log.Warning($"Unhandled shader package: {material.Mtrl.ShaderPackage.Name}"); notifier.Warning($"Unhandled shader package: {material.Mtrl.ShaderPackage.Name}");
var materialBuilder = BuildSharedBase(material, name) var materialBuilder = BuildSharedBase(material, name)
.WithMetallicRoughnessShader() .WithMetallicRoughnessShader()

View file

@ -38,14 +38,16 @@ public class MeshExporter
public string[] Attributes; public string[] Attributes;
} }
public static Mesh Export(MdlFile mdl, byte lod, ushort meshIndex, MaterialBuilder[] materials, GltfSkeleton? skeleton) public static Mesh Export(MdlFile mdl, byte lod, ushort meshIndex, MaterialBuilder[] materials, GltfSkeleton? skeleton, IoNotifier notifier)
{ {
var self = new MeshExporter(mdl, lod, meshIndex, materials, skeleton?.Names); var self = new MeshExporter(mdl, lod, meshIndex, materials, skeleton?.Names, notifier);
return new Mesh(self.BuildMeshes(), skeleton?.Joints); return new Mesh(self.BuildMeshes(), skeleton?.Joints);
} }
private const byte MaximumMeshBufferStreams = 3; private const byte MaximumMeshBufferStreams = 3;
private readonly IoNotifier _notifier;
private readonly MdlFile _mdl; private readonly MdlFile _mdl;
private readonly byte _lod; private readonly byte _lod;
private readonly ushort _meshIndex; private readonly ushort _meshIndex;
@ -61,8 +63,9 @@ public class MeshExporter
private readonly Type _materialType; private readonly Type _materialType;
private readonly Type _skinningType; private readonly Type _skinningType;
private MeshExporter(MdlFile mdl, byte lod, ushort meshIndex, MaterialBuilder[] materials, IReadOnlyDictionary<string, int>? boneNameMap) private MeshExporter(MdlFile mdl, byte lod, ushort meshIndex, MaterialBuilder[] materials, IReadOnlyDictionary<string, int>? boneNameMap, IoNotifier notifier)
{ {
_notifier = notifier;
_mdl = mdl; _mdl = mdl;
_lod = lod; _lod = lod;
_meshIndex = meshIndex; _meshIndex = meshIndex;
@ -84,7 +87,7 @@ public class MeshExporter
// If there's skinning usages but no bone mapping, there's probably something wrong with the data. // If there's skinning usages but no bone mapping, there's probably something wrong with the data.
if (_skinningType != typeof(VertexEmpty) && _boneIndexMap == null) if (_skinningType != typeof(VertexEmpty) && _boneIndexMap == null)
Penumbra.Log.Warning($"Mesh {meshIndex} has skinned vertex usages but no bone information was provided."); _notifier.Warning($"Skinned vertex usages but no bone information was provided.");
Penumbra.Log.Debug( Penumbra.Log.Debug(
$"Mesh {meshIndex} using vertex types geometry: {_geometryType.Name}, material: {_materialType.Name}, skinning: {_skinningType.Name}"); $"Mesh {meshIndex} using vertex types geometry: {_geometryType.Name}, material: {_materialType.Name}, skinning: {_skinningType.Name}");
@ -105,7 +108,7 @@ public class MeshExporter
{ {
var boneName = _mdl.Bones[xivBoneIndex]; var boneName = _mdl.Bones[xivBoneIndex];
if (!boneNameMap.TryGetValue(boneName, out var gltfBoneIndex)) if (!boneNameMap.TryGetValue(boneName, out var gltfBoneIndex))
throw new Exception($"Armature does not contain bone \"{boneName}\" requested by mesh {_meshIndex}."); throw _notifier.Exception($"Armature does not contain bone \"{boneName}\". Ensure all dependencies are enabled in the current collection, and EST entries (if required) are configured.");
indexMap.Add((ushort)tableIndex, gltfBoneIndex); indexMap.Add((ushort)tableIndex, gltfBoneIndex);
} }
@ -271,7 +274,7 @@ public class MeshExporter
} }
/// <summary> Read a vertex attribute of the specified type from a vertex buffer stream. </summary> /// <summary> Read a vertex attribute of the specified type from a vertex buffer stream. </summary>
private static object ReadVertexAttribute(MdlFile.VertexType type, BinaryReader reader) private object ReadVertexAttribute(MdlFile.VertexType type, BinaryReader reader)
{ {
return type switch return type switch
{ {
@ -284,15 +287,15 @@ public class MeshExporter
MdlFile.VertexType.Half4 => new Vector4((float)reader.ReadHalf(), (float)reader.ReadHalf(), (float)reader.ReadHalf(), MdlFile.VertexType.Half4 => new Vector4((float)reader.ReadHalf(), (float)reader.ReadHalf(), (float)reader.ReadHalf(),
(float)reader.ReadHalf()), (float)reader.ReadHalf()),
_ => throw new ArgumentOutOfRangeException(), var other => throw _notifier.Exception<ArgumentOutOfRangeException>($"Unhandled vertex type {other}"),
}; };
} }
/// <summary> Get the vertex geometry type for this mesh's vertex usages. </summary> /// <summary> Get the vertex geometry type for this mesh's vertex usages. </summary>
private static Type GetGeometryType(IReadOnlyDictionary<MdlFile.VertexUsage, MdlFile.VertexType> usages) private Type GetGeometryType(IReadOnlyDictionary<MdlFile.VertexUsage, MdlFile.VertexType> usages)
{ {
if (!usages.ContainsKey(MdlFile.VertexUsage.Position)) if (!usages.ContainsKey(MdlFile.VertexUsage.Position))
throw new Exception("Mesh does not contain position vertex elements."); throw _notifier.Exception("Mesh does not contain position vertex elements.");
if (!usages.ContainsKey(MdlFile.VertexUsage.Normal)) if (!usages.ContainsKey(MdlFile.VertexUsage.Normal))
return typeof(VertexPosition); return typeof(VertexPosition);
@ -330,11 +333,11 @@ public class MeshExporter
); );
} }
throw new Exception($"Unknown geometry type {_geometryType}."); throw _notifier.Exception($"Unknown geometry type {_geometryType}.");
} }
/// <summary> Get the vertex material type for this mesh's vertex usages. </summary> /// <summary> Get the vertex material type for this mesh's vertex usages. </summary>
private static Type GetMaterialType(IReadOnlyDictionary<MdlFile.VertexUsage, MdlFile.VertexType> usages) private Type GetMaterialType(IReadOnlyDictionary<MdlFile.VertexUsage, MdlFile.VertexType> usages)
{ {
var uvCount = 0; var uvCount = 0;
if (usages.TryGetValue(MdlFile.VertexUsage.UV, out var type)) if (usages.TryGetValue(MdlFile.VertexUsage.UV, out var type))
@ -343,7 +346,7 @@ public class MeshExporter
MdlFile.VertexType.Half2 => 1, MdlFile.VertexType.Half2 => 1,
MdlFile.VertexType.Half4 => 2, MdlFile.VertexType.Half4 => 2,
MdlFile.VertexType.Single4 => 2, MdlFile.VertexType.Single4 => 2,
_ => throw new Exception($"Unexpected UV vertex type {type}."), _ => throw _notifier.Exception($"Unexpected UV vertex type {type}."),
}; };
var materialUsages = ( var materialUsages = (
@ -403,7 +406,7 @@ public class MeshExporter
); );
} }
throw new Exception($"Unknown material type {_skinningType}"); throw _notifier.Exception($"Unknown material type {_skinningType}");
} }
/// <summary> Get the vertex skinning type for this mesh's vertex usages. </summary> /// <summary> Get the vertex skinning type for this mesh's vertex usages. </summary>
@ -424,7 +427,7 @@ public class MeshExporter
if (_skinningType == typeof(VertexJoints4)) if (_skinningType == typeof(VertexJoints4))
{ {
if (_boneIndexMap == null) if (_boneIndexMap == null)
throw new Exception("Tried to build skinned vertex but no bone mappings are available."); throw _notifier.Exception("Tried to build skinned vertex but no bone mappings are available.");
var indices = ToByteArray(attributes[MdlFile.VertexUsage.BlendIndices]); var indices = ToByteArray(attributes[MdlFile.VertexUsage.BlendIndices]);
var weights = ToVector4(attributes[MdlFile.VertexUsage.BlendWeights]); var weights = ToVector4(attributes[MdlFile.VertexUsage.BlendWeights]);
@ -435,7 +438,7 @@ public class MeshExporter
// NOTE: I've not seen any files that throw this error that aren't completely broken. // NOTE: I've not seen any files that throw this error that aren't completely broken.
var xivBoneIndex = indices[bindingIndex]; var xivBoneIndex = indices[bindingIndex];
if (!_boneIndexMap.TryGetValue(xivBoneIndex, out var jointIndex)) if (!_boneIndexMap.TryGetValue(xivBoneIndex, out var jointIndex))
throw new Exception($"Vertex contains weight for unknown bone index {xivBoneIndex}."); throw _notifier.Exception($"Vertex contains weight for unknown bone index {xivBoneIndex}.");
return (jointIndex, weights[bindingIndex]); return (jointIndex, weights[bindingIndex]);
}) })
@ -443,7 +446,7 @@ public class MeshExporter
return new VertexJoints4(bindings); return new VertexJoints4(bindings);
} }
throw new Exception($"Unknown skinning type {_skinningType}"); throw _notifier.Exception($"Unknown skinning type {_skinningType}");
} }
/// <summary> Convert a vertex attribute value to a Vector2. Supported inputs are Vector2, Vector3, and Vector4. </summary> /// <summary> Convert a vertex attribute value to a Vector2. Supported inputs are Vector2, Vector3, and Vector4. </summary>

View file

@ -23,16 +23,16 @@ public class ModelExporter
} }
/// <summary> Export a model in preparation for usage in a glTF file. If provided, skeleton will be used to skin the resulting meshes where appropriate. </summary> /// <summary> Export a model in preparation for usage in a glTF file. If provided, skeleton will be used to skin the resulting meshes where appropriate. </summary>
public static Model Export(MdlFile mdl, IEnumerable<XivSkeleton>? xivSkeleton, Dictionary<string, MaterialExporter.Material> rawMaterials) public static Model Export(MdlFile mdl, IEnumerable<XivSkeleton>? xivSkeleton, Dictionary<string, MaterialExporter.Material> rawMaterials, IoNotifier notifier)
{ {
var gltfSkeleton = xivSkeleton != null ? ConvertSkeleton(xivSkeleton) : null; var gltfSkeleton = xivSkeleton != null ? ConvertSkeleton(xivSkeleton) : null;
var materials = ConvertMaterials(mdl, rawMaterials); var materials = ConvertMaterials(mdl, rawMaterials, notifier);
var meshes = ConvertMeshes(mdl, materials, gltfSkeleton); var meshes = ConvertMeshes(mdl, materials, gltfSkeleton, notifier);
return new Model(meshes, gltfSkeleton); return new Model(meshes, gltfSkeleton);
} }
/// <summary> Convert a .mdl to a mesh (group) per LoD. </summary> /// <summary> Convert a .mdl to a mesh (group) per LoD. </summary>
private static List<MeshExporter.Mesh> ConvertMeshes(MdlFile mdl, MaterialBuilder[] materials, GltfSkeleton? skeleton) private static List<MeshExporter.Mesh> ConvertMeshes(MdlFile mdl, MaterialBuilder[] materials, GltfSkeleton? skeleton, IoNotifier notifier)
{ {
var meshes = new List<MeshExporter.Mesh>(); var meshes = new List<MeshExporter.Mesh>();
@ -43,7 +43,8 @@ public class ModelExporter
// TODO: consider other types of mesh? // TODO: consider other types of mesh?
for (ushort meshOffset = 0; meshOffset < lod.MeshCount; meshOffset++) for (ushort meshOffset = 0; meshOffset < lod.MeshCount; meshOffset++)
{ {
var mesh = MeshExporter.Export(mdl, lodIndex, (ushort)(lod.MeshIndex + meshOffset), materials, skeleton); var meshIndex = (ushort)(lod.MeshIndex + meshOffset);
var mesh = MeshExporter.Export(mdl, lodIndex, meshIndex, materials, skeleton, notifier.WithContext($"Mesh {meshIndex}"));
meshes.Add(mesh); meshes.Add(mesh);
} }
} }
@ -52,11 +53,11 @@ public class ModelExporter
} }
/// <summary> Build materials for each of the material slots in the .mdl. </summary> /// <summary> Build materials for each of the material slots in the .mdl. </summary>
private static MaterialBuilder[] ConvertMaterials(MdlFile mdl, Dictionary<string, MaterialExporter.Material> rawMaterials) private static MaterialBuilder[] ConvertMaterials(MdlFile mdl, Dictionary<string, MaterialExporter.Material> rawMaterials, IoNotifier notifier)
=> mdl.Materials => mdl.Materials
// TODO: material generation should be fallible, which means this lookup should be a tryget, with a fallback. // TODO: material generation should be fallible, which means this lookup should be a tryget, with a fallback.
// fallback can likely be a static on the material exporter. // fallback can likely be a static on the material exporter.
.Select(name => MaterialExporter.Export(rawMaterials[name], name)) .Select(name => MaterialExporter.Export(rawMaterials[name], name, notifier.WithContext($"Material {name}")))
.ToArray(); .ToArray();
/// <summary> Convert XIV skeleton data into a glTF-compatible node tree, with mappings. </summary> /// <summary> Convert XIV skeleton data into a glTF-compatible node tree, with mappings. </summary>

View file

@ -37,20 +37,17 @@ public sealed class ModelManager(IFramework framework, ActiveCollections collect
_tasks.Clear(); _tasks.Clear();
} }
public Task ExportToGltf(MdlFile mdl, IEnumerable<string> sklbPaths, Func<string, byte[]> read, string outputPath) public Task<IoNotifier> ExportToGltf(MdlFile mdl, IEnumerable<string> sklbPaths, Func<string, byte[]> read, string outputPath)
=> Enqueue(new ExportToGltfAction(this, mdl, sklbPaths, read, outputPath)); => EnqueueWithResult(
new ExportToGltfAction(this, mdl, sklbPaths, read, outputPath),
action => action.Notifier
);
public Task<MdlFile?> ImportGltf(string inputPath) public Task<MdlFile?> ImportGltf(string inputPath)
{ => EnqueueWithResult(
var action = new ImportGltfAction(inputPath); new ImportGltfAction(inputPath),
return Enqueue(action).ContinueWith(task => action => action.Out
{ );
if (task is { IsFaulted: true, Exception: not null })
throw task.Exception;
return action.Out;
});
}
/// <summary> Try to find the .sklb paths for a .mdl file. </summary> /// <summary> Try to find the .sklb paths for a .mdl file. </summary>
/// <param name="mdlPath"> .mdl file to look up the skeletons for. </param> /// <param name="mdlPath"> .mdl file to look up the skeletons for. </param>
@ -168,6 +165,16 @@ public sealed class ModelManager(IFramework framework, ActiveCollections collect
return task; return task;
} }
private Task<TOut> EnqueueWithResult<TAction, TOut>(TAction action, Func<TAction, TOut> process)
where TAction : IAction
=> Enqueue(action).ContinueWith(task =>
{
if (task is { IsFaulted: true, Exception: not null })
throw task.Exception;
return process(action);
});
private class ExportToGltfAction( private class ExportToGltfAction(
ModelManager manager, ModelManager manager,
MdlFile mdl, MdlFile mdl,
@ -176,6 +183,8 @@ public sealed class ModelManager(IFramework framework, ActiveCollections collect
string outputPath) string outputPath)
: IAction : IAction
{ {
public IoNotifier Notifier = new IoNotifier();
public void Execute(CancellationToken cancel) public void Execute(CancellationToken cancel)
{ {
Penumbra.Log.Debug($"[GLTF Export] Exporting model to {outputPath}..."); Penumbra.Log.Debug($"[GLTF Export] Exporting model to {outputPath}...");
@ -190,7 +199,7 @@ public sealed class ModelManager(IFramework framework, ActiveCollections collect
); );
Penumbra.Log.Debug("[GLTF Export] Converting model..."); Penumbra.Log.Debug("[GLTF Export] Converting model...");
var model = ModelExporter.Export(mdl, xivSkeletons, materials); var model = ModelExporter.Export(mdl, xivSkeletons, materials, Notifier);
Penumbra.Log.Debug("[GLTF Export] Building scene..."); Penumbra.Log.Debug("[GLTF Export] Building scene...");
var scene = new SceneBuilder(); var scene = new SceneBuilder();

View file

@ -134,6 +134,8 @@ public partial class ModEditWindow
.ContinueWith(task => .ContinueWith(task =>
{ {
RecordIoExceptions(task.Exception); RecordIoExceptions(task.Exception);
if (task is { IsCompletedSuccessfully: true, Result: not null })
IoWarnings = task.Result.GetWarnings().ToList();
PendingIo = false; PendingIo = false;
}); });
} }