mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-15 05:04:15 +01:00
Merge branch 'master' into mdl-name-io
This commit is contained in:
commit
a0e9e2ead3
8 changed files with 70 additions and 20 deletions
|
|
@ -4,7 +4,7 @@ using SharpGLTF.Schema2;
|
||||||
|
|
||||||
namespace Penumbra.Import.Models.Import;
|
namespace Penumbra.Import.Models.Import;
|
||||||
|
|
||||||
public partial class ModelImporter(ModelRoot _model)
|
public partial class ModelImporter(ModelRoot model)
|
||||||
{
|
{
|
||||||
public static MdlFile Import(ModelRoot model)
|
public static MdlFile Import(ModelRoot model)
|
||||||
{
|
{
|
||||||
|
|
@ -105,7 +105,7 @@ public partial class ModelImporter(ModelRoot _model)
|
||||||
|
|
||||||
/// <summary> Returns an iterator over sorted, grouped mesh nodes. </summary>
|
/// <summary> Returns an iterator over sorted, grouped mesh nodes. </summary>
|
||||||
private IEnumerable<IEnumerable<Node>> GroupedMeshNodes()
|
private IEnumerable<IEnumerable<Node>> GroupedMeshNodes()
|
||||||
=> _model.LogicalNodes
|
=> model.LogicalNodes
|
||||||
.Where(node => node.Mesh != null)
|
.Where(node => node.Mesh != null)
|
||||||
.Select(node =>
|
.Select(node =>
|
||||||
{
|
{
|
||||||
|
|
@ -184,6 +184,14 @@ public partial class ModelImporter(ModelRoot _model)
|
||||||
|
|
||||||
_shapeValues.AddRange(meshShapeKey.ShapeValues);
|
_shapeValues.AddRange(meshShapeKey.ShapeValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The number of shape values in a model is bounded by the count
|
||||||
|
// value, which is stored as a u16.
|
||||||
|
// While technically there are similar bounds on other shape struct
|
||||||
|
// arrays, values is practically guaranteed to be the highest of the
|
||||||
|
// group, so a failure on any of them will be a failure on it.
|
||||||
|
if (_shapeValues.Count > ushort.MaxValue)
|
||||||
|
throw new Exception($"Importing this file would require more than the maximum of {ushort.MaxValue} shape values.\nTry removing or applying shape keys that do not need to be changed at runtime in-game.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private ushort GetMaterialIndex(string materialName)
|
private ushort GetMaterialIndex(string materialName)
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,12 @@ public sealed class ModelManager(IFramework framework, ActiveCollections collect
|
||||||
public Task<MdlFile?> ImportGltf(string inputPath)
|
public Task<MdlFile?> ImportGltf(string inputPath)
|
||||||
{
|
{
|
||||||
var action = new ImportGltfAction(inputPath);
|
var action = new ImportGltfAction(inputPath);
|
||||||
return Enqueue(action).ContinueWith(_ => action.Out);
|
return Enqueue(action).ContinueWith(task =>
|
||||||
|
{
|
||||||
|
if (task.IsFaulted && task.Exception != 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>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
|
using Penumbra.Collections;
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.Interop.PathResolving;
|
using Penumbra.Interop.PathResolving;
|
||||||
|
|
@ -29,6 +30,7 @@ public sealed unsafe class ChangeCustomize : FastHook<ChangeCustomize.Delegate>
|
||||||
using var decal2 = _metaState.ResolveDecal(_metaState.CustomizeChangeCollection, false);
|
using var decal2 = _metaState.ResolveDecal(_metaState.CustomizeChangeCollection, false);
|
||||||
var ret = Task.Result.Original.Invoke(human, data, skipEquipment);
|
var ret = Task.Result.Original.Invoke(human, data, skipEquipment);
|
||||||
Penumbra.Log.Excessive($"[Change Customize] Invoked on {(nint)human:X} with {(nint)data:X}, {skipEquipment} -> {ret}.");
|
Penumbra.Log.Excessive($"[Change Customize] Invoked on {(nint)human:X} with {(nint)data:X}, {skipEquipment} -> {ret}.");
|
||||||
|
_metaState.CustomizeChangeCollection = ResolveData.Invalid;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,9 @@ public partial class ModEditWindow
|
||||||
public List<Utf8GamePath>? GamePaths { get; private set; }
|
public List<Utf8GamePath>? GamePaths { get; private set; }
|
||||||
public int GamePathIndex;
|
public int GamePathIndex;
|
||||||
|
|
||||||
private bool _dirty;
|
private bool _dirty;
|
||||||
public bool PendingIo { get; private set; }
|
public bool PendingIo { get; private set; }
|
||||||
public string? IoException { get; private set; }
|
public List<Exception> IoExceptions { get; private set; } = [];
|
||||||
|
|
||||||
public MdlTab(ModEditWindow edit, byte[] bytes, string path)
|
public MdlTab(ModEditWindow edit, byte[] bytes, string path)
|
||||||
{
|
{
|
||||||
|
|
@ -87,7 +87,7 @@ public partial class ModEditWindow
|
||||||
|
|
||||||
task.ContinueWith(t =>
|
task.ContinueWith(t =>
|
||||||
{
|
{
|
||||||
IoException = t.Exception?.ToString();
|
RecordIoExceptions(t.Exception);
|
||||||
GamePaths = t.Result;
|
GamePaths = t.Result;
|
||||||
PendingIo = false;
|
PendingIo = false;
|
||||||
});
|
});
|
||||||
|
|
@ -123,7 +123,7 @@ public partial class ModEditWindow
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
IoException = exception.ToString();
|
RecordIoExceptions(exception);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -131,7 +131,7 @@ public partial class ModEditWindow
|
||||||
_edit._models.ExportToGltf(Mdl, skeletons, outputPath)
|
_edit._models.ExportToGltf(Mdl, skeletons, outputPath)
|
||||||
.ContinueWith(task =>
|
.ContinueWith(task =>
|
||||||
{
|
{
|
||||||
IoException = task.Exception?.ToString();
|
RecordIoExceptions(task.Exception);
|
||||||
PendingIo = false;
|
PendingIo = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -144,7 +144,7 @@ public partial class ModEditWindow
|
||||||
_edit._models.ImportGltf(inputPath)
|
_edit._models.ImportGltf(inputPath)
|
||||||
.ContinueWith(task =>
|
.ContinueWith(task =>
|
||||||
{
|
{
|
||||||
IoException = task.Exception?.ToString();
|
RecordIoExceptions(task.Exception);
|
||||||
if (task is { IsCompletedSuccessfully: true, Result: not null })
|
if (task is { IsCompletedSuccessfully: true, Result: not null })
|
||||||
FinalizeImport(task.Result);
|
FinalizeImport(task.Result);
|
||||||
PendingIo = false;
|
PendingIo = false;
|
||||||
|
|
@ -177,6 +177,15 @@ public partial class ModEditWindow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RecordIoExceptions(Exception? exception)
|
||||||
|
{
|
||||||
|
IoExceptions = exception switch {
|
||||||
|
null => [],
|
||||||
|
AggregateException ae => ae.Flatten().InnerExceptions.ToList(),
|
||||||
|
Exception other => [other],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary> Read a .sklb from the active collection or game. </summary>
|
/// <summary> Read a .sklb from the active collection or game. </summary>
|
||||||
/// <param name="sklbPath"> Game path to the .sklb to load. </param>
|
/// <param name="sklbPath"> Game path to the .sklb to load. </param>
|
||||||
private SklbFile ReadSklb(string sklbPath)
|
private SklbFile ReadSklb(string sklbPath)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using Penumbra.GameData;
|
||||||
using Penumbra.GameData.Files;
|
using Penumbra.GameData.Files;
|
||||||
using Penumbra.Import.Models;
|
using Penumbra.Import.Models;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
|
using Penumbra.UI.Classes;
|
||||||
|
|
||||||
namespace Penumbra.UI.AdvancedWindow;
|
namespace Penumbra.UI.AdvancedWindow;
|
||||||
|
|
||||||
|
|
@ -61,8 +62,7 @@ public partial class ModEditWindow
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
DrawExport(tab, childSize, disabled);
|
DrawExport(tab, childSize, disabled);
|
||||||
|
|
||||||
if (tab.IoException != null)
|
DrawIoExceptions(tab);
|
||||||
ImGuiUtil.TextWrapped(tab.IoException);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawImport(MdlTab tab, Vector2 size, bool _1)
|
private void DrawImport(MdlTab tab, Vector2 size, bool _1)
|
||||||
|
|
@ -100,10 +100,10 @@ public partial class ModEditWindow
|
||||||
|
|
||||||
if (tab.GamePaths == null)
|
if (tab.GamePaths == null)
|
||||||
{
|
{
|
||||||
if (tab.IoException == null)
|
if (tab.IoExceptions.Count == 0)
|
||||||
ImGui.TextUnformatted("Resolving model game paths.");
|
ImGui.TextUnformatted("Resolving model game paths.");
|
||||||
else
|
else
|
||||||
ImGuiUtil.TextWrapped(tab.IoException);
|
ImGui.TextUnformatted("Failed to resolve model game paths.");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -127,6 +127,30 @@ public partial class ModEditWindow
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawIoExceptions(MdlTab tab)
|
||||||
|
{
|
||||||
|
if (tab.IoExceptions.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var size = new Vector2(ImGui.GetContentRegionAvail().X, 0);
|
||||||
|
using var frame = ImRaii.FramedGroup("Exceptions", size, headerPreIcon: FontAwesomeIcon.TimesCircle, borderColor: Colors.RegexWarningBorder);
|
||||||
|
|
||||||
|
var spaceAvail = ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X - 100;
|
||||||
|
foreach (var exception in tab.IoExceptions)
|
||||||
|
{
|
||||||
|
var message = $"{exception.GetType().Name}: {exception.Message}";
|
||||||
|
var textSize = ImGui.CalcTextSize(message).X;
|
||||||
|
if (textSize > spaceAvail)
|
||||||
|
message = message.Substring(0, (int)Math.Floor(message.Length * (spaceAvail / textSize))) + "...";
|
||||||
|
|
||||||
|
using (var exceptionNode = ImRaii.TreeNode(message))
|
||||||
|
{
|
||||||
|
if (exceptionNode)
|
||||||
|
ImGuiUtil.TextWrapped(exception.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawGamePathCombo(MdlTab tab)
|
private void DrawGamePathCombo(MdlTab tab)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -108,11 +108,14 @@ public partial class ModEditWindow
|
||||||
MipMapInput();
|
MipMapInput();
|
||||||
|
|
||||||
var canSaveInPlace = Path.IsPathRooted(_left.Path) && _left.Type is TextureType.Tex or TextureType.Dds or TextureType.Png;
|
var canSaveInPlace = Path.IsPathRooted(_left.Path) && _left.Type is TextureType.Tex or TextureType.Dds or TextureType.Png;
|
||||||
|
var isActive = _config.DeleteModModifier.IsActive();
|
||||||
|
var tt = isActive
|
||||||
|
? "This saves the texture in place. This is not revertible."
|
||||||
|
: $"This saves the texture in place. This is not revertible. Hold {_config.DeleteModModifier} to save.";
|
||||||
|
|
||||||
var buttonSize2 = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X) / 2, 0);
|
var buttonSize2 = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X) / 2, 0);
|
||||||
if (ImGuiUtil.DrawDisabledButton("Save in place", buttonSize2,
|
if (ImGuiUtil.DrawDisabledButton("Save in place", buttonSize2,
|
||||||
"This saves the texture in place. This is not revertible.",
|
tt, !isActive || !canSaveInPlace || _center.IsLeftCopy && _currentSaveAs == (int)CombinedTexture.TextureSaveType.AsIs))
|
||||||
!canSaveInPlace || _center.IsLeftCopy && _currentSaveAs == (int)CombinedTexture.TextureSaveType.AsIs))
|
|
||||||
{
|
{
|
||||||
_center.SaveAs(_left.Type, _textures, _left.Path, (CombinedTexture.TextureSaveType)_currentSaveAs, _addMipMaps);
|
_center.SaveAs(_left.Type, _textures, _left.Path, (CombinedTexture.TextureSaveType)_currentSaveAs, _addMipMaps);
|
||||||
AddReloadTask(_left.Path, false);
|
AddReloadTask(_left.Path, false);
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ public enum ColorId
|
||||||
public static class Colors
|
public static class Colors
|
||||||
{
|
{
|
||||||
// These are written as 0xAABBGGRR.
|
// These are written as 0xAABBGGRR.
|
||||||
|
|
||||||
public const uint PressEnterWarningBg = 0xFF202080;
|
public const uint PressEnterWarningBg = 0xFF202080;
|
||||||
public const uint RegexWarningBorder = 0xFF0000B0;
|
public const uint RegexWarningBorder = 0xFF0000B0;
|
||||||
public const uint MetaInfoText = 0xAAFFFFFF;
|
public const uint MetaInfoText = 0xAAFFFFFF;
|
||||||
|
|
|
||||||
|
|
@ -209,7 +209,7 @@ public class ModPanelSettingsTab : ITab
|
||||||
{
|
{
|
||||||
using var id = ImRaii.PushId(groupIdx);
|
using var id = ImRaii.PushId(groupIdx);
|
||||||
var selectedOption = _empty ? (int)group.DefaultSettings : (int)_settings.Settings[groupIdx];
|
var selectedOption = _empty ? (int)group.DefaultSettings : (int)_settings.Settings[groupIdx];
|
||||||
var minWidth = Widget.BeginFramedGroup(group.Name, group.Description);
|
var minWidth = Widget.BeginFramedGroup(group.Name, description:group.Description);
|
||||||
|
|
||||||
void DrawOptions()
|
void DrawOptions()
|
||||||
{
|
{
|
||||||
|
|
@ -288,7 +288,7 @@ public class ModPanelSettingsTab : ITab
|
||||||
{
|
{
|
||||||
using var id = ImRaii.PushId(groupIdx);
|
using var id = ImRaii.PushId(groupIdx);
|
||||||
var flags = _empty ? group.DefaultSettings : _settings.Settings[groupIdx];
|
var flags = _empty ? group.DefaultSettings : _settings.Settings[groupIdx];
|
||||||
var minWidth = Widget.BeginFramedGroup(group.Name, group.Description);
|
var minWidth = Widget.BeginFramedGroup(group.Name, description: group.Description);
|
||||||
|
|
||||||
void DrawOptions()
|
void DrawOptions()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue