diff --git a/Penumbra/Import/Models/Export/MaterialExporter.cs b/Penumbra/Import/Models/Export/MaterialExporter.cs
index 2a49e77f..61609bb5 100644
--- a/Penumbra/Import/Models/Export/MaterialExporter.cs
+++ b/Penumbra/Import/Models/Export/MaterialExporter.cs
@@ -23,7 +23,7 @@ public class MaterialExporter
}
/// Build a glTF material from a hydrated XIV model, with the provided name.
- public static MaterialBuilder Export(Material material, string name)
+ public static MaterialBuilder Export(Material material, string name, IoNotifier notifier)
{
Penumbra.Log.Debug($"Exporting material \"{name}\".");
return material.Mtrl.ShaderPackage.Name switch
@@ -34,7 +34,7 @@ public class MaterialExporter
"hair.shpk" => BuildHair(material, name),
"iris.shpk" => BuildIris(material, name),
"skin.shpk" => BuildSkin(material, name),
- _ => BuildFallback(material, name),
+ _ => BuildFallback(material, name, notifier),
};
}
@@ -335,9 +335,9 @@ public class MaterialExporter
/// Build a material from a source with unknown semantics.
/// Will make a loose effort to fetch common / simple textures.
- 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)
.WithMetallicRoughnessShader()
diff --git a/Penumbra/Import/Models/Export/MeshExporter.cs b/Penumbra/Import/Models/Export/MeshExporter.cs
index da6b4df4..71e8f082 100644
--- a/Penumbra/Import/Models/Export/MeshExporter.cs
+++ b/Penumbra/Import/Models/Export/MeshExporter.cs
@@ -38,14 +38,16 @@ public class MeshExporter
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);
}
private const byte MaximumMeshBufferStreams = 3;
+ private readonly IoNotifier _notifier;
+
private readonly MdlFile _mdl;
private readonly byte _lod;
private readonly ushort _meshIndex;
@@ -61,8 +63,9 @@ public class MeshExporter
private readonly Type _materialType;
private readonly Type _skinningType;
- private MeshExporter(MdlFile mdl, byte lod, ushort meshIndex, MaterialBuilder[] materials, IReadOnlyDictionary? boneNameMap)
+ private MeshExporter(MdlFile mdl, byte lod, ushort meshIndex, MaterialBuilder[] materials, IReadOnlyDictionary? boneNameMap, IoNotifier notifier)
{
+ _notifier = notifier;
_mdl = mdl;
_lod = lod;
_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 (_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(
$"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];
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);
}
@@ -271,7 +274,7 @@ public class MeshExporter
}
/// Read a vertex attribute of the specified type from a vertex buffer stream.
- private static object ReadVertexAttribute(MdlFile.VertexType type, BinaryReader reader)
+ private object ReadVertexAttribute(MdlFile.VertexType type, BinaryReader reader)
{
return type switch
{
@@ -284,15 +287,15 @@ public class MeshExporter
MdlFile.VertexType.Half4 => new Vector4((float)reader.ReadHalf(), (float)reader.ReadHalf(), (float)reader.ReadHalf(),
(float)reader.ReadHalf()),
- _ => throw new ArgumentOutOfRangeException(),
+ var other => throw _notifier.Exception($"Unhandled vertex type {other}"),
};
}
/// Get the vertex geometry type for this mesh's vertex usages.
- private static Type GetGeometryType(IReadOnlyDictionary usages)
+ private Type GetGeometryType(IReadOnlyDictionary usages)
{
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))
return typeof(VertexPosition);
@@ -330,11 +333,11 @@ public class MeshExporter
);
}
- throw new Exception($"Unknown geometry type {_geometryType}.");
+ throw _notifier.Exception($"Unknown geometry type {_geometryType}.");
}
/// Get the vertex material type for this mesh's vertex usages.
- private static Type GetMaterialType(IReadOnlyDictionary usages)
+ private Type GetMaterialType(IReadOnlyDictionary usages)
{
var uvCount = 0;
if (usages.TryGetValue(MdlFile.VertexUsage.UV, out var type))
@@ -343,7 +346,7 @@ public class MeshExporter
MdlFile.VertexType.Half2 => 1,
MdlFile.VertexType.Half4 => 2,
MdlFile.VertexType.Single4 => 2,
- _ => throw new Exception($"Unexpected UV vertex type {type}."),
+ _ => throw _notifier.Exception($"Unexpected UV vertex type {type}."),
};
var materialUsages = (
@@ -403,7 +406,7 @@ public class MeshExporter
);
}
- throw new Exception($"Unknown material type {_skinningType}");
+ throw _notifier.Exception($"Unknown material type {_skinningType}");
}
/// Get the vertex skinning type for this mesh's vertex usages.
@@ -424,7 +427,7 @@ public class MeshExporter
if (_skinningType == typeof(VertexJoints4))
{
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 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.
var xivBoneIndex = indices[bindingIndex];
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]);
})
@@ -443,7 +446,7 @@ public class MeshExporter
return new VertexJoints4(bindings);
}
- throw new Exception($"Unknown skinning type {_skinningType}");
+ throw _notifier.Exception($"Unknown skinning type {_skinningType}");
}
/// Convert a vertex attribute value to a Vector2. Supported inputs are Vector2, Vector3, and Vector4.
diff --git a/Penumbra/Import/Models/Export/ModelExporter.cs b/Penumbra/Import/Models/Export/ModelExporter.cs
index da24fbb0..550aaf11 100644
--- a/Penumbra/Import/Models/Export/ModelExporter.cs
+++ b/Penumbra/Import/Models/Export/ModelExporter.cs
@@ -23,16 +23,16 @@ public class ModelExporter
}
/// Export a model in preparation for usage in a glTF file. If provided, skeleton will be used to skin the resulting meshes where appropriate.
- public static Model Export(MdlFile mdl, IEnumerable? xivSkeleton, Dictionary rawMaterials)
+ public static Model Export(MdlFile mdl, IEnumerable? xivSkeleton, Dictionary rawMaterials, IoNotifier notifier)
{
var gltfSkeleton = xivSkeleton != null ? ConvertSkeleton(xivSkeleton) : null;
- var materials = ConvertMaterials(mdl, rawMaterials);
- var meshes = ConvertMeshes(mdl, materials, gltfSkeleton);
+ var materials = ConvertMaterials(mdl, rawMaterials, notifier);
+ var meshes = ConvertMeshes(mdl, materials, gltfSkeleton, notifier);
return new Model(meshes, gltfSkeleton);
}
/// Convert a .mdl to a mesh (group) per LoD.
- private static List ConvertMeshes(MdlFile mdl, MaterialBuilder[] materials, GltfSkeleton? skeleton)
+ private static List ConvertMeshes(MdlFile mdl, MaterialBuilder[] materials, GltfSkeleton? skeleton, IoNotifier notifier)
{
var meshes = new List();
@@ -43,7 +43,8 @@ public class ModelExporter
// TODO: consider other types of mesh?
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);
}
}
@@ -52,11 +53,11 @@ public class ModelExporter
}
/// Build materials for each of the material slots in the .mdl.
- private static MaterialBuilder[] ConvertMaterials(MdlFile mdl, Dictionary rawMaterials)
+ private static MaterialBuilder[] ConvertMaterials(MdlFile mdl, Dictionary rawMaterials, IoNotifier notifier)
=> mdl.Materials
// 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.
- .Select(name => MaterialExporter.Export(rawMaterials[name], name))
+ .Select(name => MaterialExporter.Export(rawMaterials[name], name, notifier.WithContext($"Material {name}")))
.ToArray();
/// Convert XIV skeleton data into a glTF-compatible node tree, with mappings.
diff --git a/Penumbra/Import/Models/ModelManager.cs b/Penumbra/Import/Models/ModelManager.cs
index 7f1171f3..ffcb5bbe 100644
--- a/Penumbra/Import/Models/ModelManager.cs
+++ b/Penumbra/Import/Models/ModelManager.cs
@@ -37,20 +37,17 @@ public sealed class ModelManager(IFramework framework, ActiveCollections collect
_tasks.Clear();
}
- public Task ExportToGltf(MdlFile mdl, IEnumerable sklbPaths, Func read, string outputPath)
- => Enqueue(new ExportToGltfAction(this, mdl, sklbPaths, read, outputPath));
+ public Task ExportToGltf(MdlFile mdl, IEnumerable sklbPaths, Func read, string outputPath)
+ => EnqueueWithResult(
+ new ExportToGltfAction(this, mdl, sklbPaths, read, outputPath),
+ action => action.Notifier
+ );
public Task ImportGltf(string inputPath)
- {
- var action = new ImportGltfAction(inputPath);
- return Enqueue(action).ContinueWith(task =>
- {
- if (task is { IsFaulted: true, Exception: not null })
- throw task.Exception;
-
- return action.Out;
- });
- }
+ => EnqueueWithResult(
+ new ImportGltfAction(inputPath),
+ action => action.Out
+ );
/// Try to find the .sklb paths for a .mdl file.
/// .mdl file to look up the skeletons for.
@@ -168,6 +165,16 @@ public sealed class ModelManager(IFramework framework, ActiveCollections collect
return task;
}
+ private Task EnqueueWithResult(TAction action, Func 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(
ModelManager manager,
MdlFile mdl,
@@ -176,6 +183,8 @@ public sealed class ModelManager(IFramework framework, ActiveCollections collect
string outputPath)
: IAction
{
+ public IoNotifier Notifier = new IoNotifier();
+
public void Execute(CancellationToken cancel)
{
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...");
- var model = ModelExporter.Export(mdl, xivSkeletons, materials);
+ var model = ModelExporter.Export(mdl, xivSkeletons, materials, Notifier);
Penumbra.Log.Debug("[GLTF Export] Building scene...");
var scene = new SceneBuilder();
diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs
index 15c6cb21..17b46626 100644
--- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs
+++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs
@@ -134,6 +134,8 @@ public partial class ModEditWindow
.ContinueWith(task =>
{
RecordIoExceptions(task.Exception);
+ if (task is { IsCompletedSuccessfully: true, Result: not null })
+ IoWarnings = task.Result.GetWarnings().ToList();
PendingIo = false;
});
}