diff --git a/PCK-Studio/Forms/Editor/CustomSkinEditor.cs b/PCK-Studio/Forms/Editor/CustomSkinEditor.cs index 5bd53016..0ef709a7 100644 --- a/PCK-Studio/Forms/Editor/CustomSkinEditor.cs +++ b/PCK-Studio/Forms/Editor/CustomSkinEditor.cs @@ -181,20 +181,20 @@ namespace PckStudio.Forms.Editor { SaveFileDialog saveFileDialog = new SaveFileDialog(); saveFileDialog.Title = "Save Model File"; - saveFileDialog.Filter = ModelImporter.SupportedModelFileFormatsFilter; + saveFileDialog.Filter = SkinModelImporter.SupportedModelFileFormatsFilter; saveFileDialog.FileName = _skin.MetaData.Name.TrimEnd(new char[] { '\n', '\r' }).Replace(' ', '_'); if (saveFileDialog.ShowDialog() == DialogResult.OK) - ModelImporter.Export(saveFileDialog.FileName, _skin.Model); + SkinModelImporter.Export(saveFileDialog.FileName, _skin.Model); } private void importSkinButton_Click(object sender, EventArgs e) { OpenFileDialog openFileDialog = new OpenFileDialog(); openFileDialog.Title = "Select Model File"; - openFileDialog.Filter = ModelImporter.SupportedModelFileFormatsFilter; + openFileDialog.Filter = SkinModelImporter.SupportedModelFileFormatsFilter; if (MessageBox.Show("Import custom model project file? Your current work will be lost!", "", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1) == DialogResult.Yes && openFileDialog.ShowDialog() == DialogResult.OK) { - SkinModelInfo modelInfo = ModelImporter.Import(openFileDialog.FileName); + SkinModelInfo modelInfo = SkinModelImporter.Import(openFileDialog.FileName); if (modelInfo is not null) { _skin.Model = modelInfo; diff --git a/PCK-Studio/Interfaces/IModelImportProvider.cs b/PCK-Studio/Interfaces/IModelImportProvider.cs new file mode 100644 index 00000000..befe3375 --- /dev/null +++ b/PCK-Studio/Interfaces/IModelImportProvider.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using PckStudio.Internal.Skin; +using PckStudio.Internal; + +namespace PckStudio.Interfaces +{ + internal interface IModelImportProvider where T : class + { + public string Name { get; } + + public FileDialogFilter DialogFilter { get; } + + public T Import(string filename); + public T Import(Stream stream); + + public void Export(string filename, T model); + public void Export(ref Stream stream, T model); + } +} diff --git a/PCK-Studio/Interfaces/ISkinModelImportProvider.cs b/PCK-Studio/Interfaces/ISkinModelImportProvider.cs new file mode 100644 index 00000000..1434e0b3 --- /dev/null +++ b/PCK-Studio/Interfaces/ISkinModelImportProvider.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using PckStudio.Internal; +using PckStudio.Internal.Skin; + +namespace PckStudio.Interfaces +{ + internal interface ISkinModelImportProvider : IModelImportProvider + { + } +} diff --git a/PCK-Studio/Internal/App/ApplicationScope.cs b/PCK-Studio/Internal/App/ApplicationScope.cs index df66124b..e0d1878b 100644 --- a/PCK-Studio/Internal/App/ApplicationScope.cs +++ b/PCK-Studio/Internal/App/ApplicationScope.cs @@ -46,6 +46,7 @@ namespace PckStudio.Internal.App _ = Tiles.MoonPhaseImageList; _ = Tiles.PaintingImageList; SettingsManager.Initialize(); + SkinModelImporter.Initialize(); CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; Task.Run(GetContributors); } diff --git a/PCK-Studio/Internal/FileDialogFilter.cs b/PCK-Studio/Internal/FileDialogFilter.cs index 165eb407..0d38a8e1 100644 --- a/PCK-Studio/Internal/FileDialogFilter.cs +++ b/PCK-Studio/Internal/FileDialogFilter.cs @@ -1,10 +1,14 @@ -namespace PckStudio.Internal +using System.IO; + +namespace PckStudio.Internal { internal readonly struct FileDialogFilter { public readonly string Description; public readonly string Pattern; + public string Extension => Path.GetExtension(Pattern); + public FileDialogFilter(string description, string pattern) { Description = description; diff --git a/PCK-Studio/Internal/ModelImporter.cs b/PCK-Studio/Internal/SkinModelImporter.cs similarity index 87% rename from PCK-Studio/Internal/ModelImporter.cs rename to PCK-Studio/Internal/SkinModelImporter.cs index b98bfb73..62ab3e47 100644 --- a/PCK-Studio/Internal/ModelImporter.cs +++ b/PCK-Studio/Internal/SkinModelImporter.cs @@ -37,67 +37,105 @@ using OMI.Formats.Model; using PckStudio.Internal.Json; using System.Collections.ObjectModel; using PckStudio.Properties; +using PckStudio.Interfaces; namespace PckStudio.Internal { - internal static class ModelImporter + internal static class SkinModelImporter { - internal static FileDialogFilter[] SupportedModelFormts { get; } = - [ - new ("Pck skin model(*.psm)", "*.psm"), - new ("Block bench model(*.bbmodel)", "*.bbmodel"), - new ("Bedrock (Legacy) Model(*.geo.json;*.json)", "*.geo.json;*.json"), - ]; + private static Dictionary _importProviders = new Dictionary(); - internal static string SupportedModelFileFormatsFilter { get; } = string.Join("|", SupportedModelFormts); + private sealed class SimpleSkinImportProvider : ISkinModelImportProvider + { + public string Name => nameof(SimpleSkinImportProvider); + + public FileDialogFilter DialogFilter => _dialogFilter; + + private FileDialogFilter _dialogFilter; + private Func _import; + private Action _export; + + public SimpleSkinImportProvider(FileDialogFilter dialogFilter, Func import, Action export) + { + _dialogFilter = dialogFilter; + _import = import; + _export = export; + } + + public void Export(string filename, SkinModelInfo model) + { + _ = _export ?? throw new NotImplementedException(); + _export(filename, model); + } + + public SkinModelInfo Import(string filename) + { + _ = _import ?? throw new NotImplementedException(); + return _import(filename); + } + + public void Export(ref Stream stream, SkinModelInfo model) + { + throw new NotImplementedException(); + } + + public SkinModelInfo Import(Stream stream) + { + throw new NotImplementedException(); + } + } + + internal static string SupportedModelFileFormatsFilter => string.Join("|", _importProviders.Values.Select(p => p.DialogFilter)); internal static ReadOnlyDictionary ModelTextureLocations { get; private set; } - static ModelImporter() + internal static void Initialize() { ModelTextureLocations = JsonConvert.DeserializeObject>(Resources.modelTextureLocations); + InternalAddProvider(new("Pck skin model(*.psm)", "*.psm"), ImportPsm, ExportPsm); + InternalAddProvider(new("Block bench model(*.bbmodel)", "*.bbmodel"), ImportBlockBenchModel, ExportBlockBenchModel); + InternalAddProvider(new("Bedrock (Legacy) Model(*.geo.json;*.json)", "*.geo.json;*.json"), ImportBedrockJson, ExportBedrockJson); + } + + internal static bool AddProvider(ISkinModelImportProvider provider) + { + if (_importProviders.ContainsKey(provider.DialogFilter.Extension)) + return false; + + _importProviders.Add(provider.DialogFilter.Extension, provider); + return true; + } + + private static bool InternalAddProvider(FileDialogFilter dialogFilter, Func import, Action export) + { + if (import == null || export == null) + return false; + + return AddProvider(new SimpleSkinImportProvider(dialogFilter, import, export)); } internal static SkinModelInfo Import(string fileName) { string fileExtension = Path.GetExtension(fileName); - switch (fileExtension) - { - case ".psm": - return ImportPsm(fileName); - case ".bbmodel": - return ImportBlockBenchModel(fileName); - case ".json": - return ImportBedrockJson(fileName); - default: - Trace.TraceWarning($"[{nameof(ModelImporter)}:Import] Model file format: '{fileExtension}' is not supported."); - return null; - } + if (_importProviders.ContainsKey(fileExtension) && _importProviders[fileExtension] is not null) + return _importProviders[fileExtension].Import(fileName); + + Trace.TraceWarning($"[{nameof(SkinModelImporter)}:Import] No provider found for '{fileExtension}'."); + return null; } internal static void Export(string fileName, SkinModelInfo model) { if (model is null) { - Trace.TraceError($"[{nameof(ModelImporter)}:Export] Model is null."); + Trace.TraceError($"[{nameof(SkinModelImporter)}:Export] Model is null."); return; } string fileExtension = Path.GetExtension(fileName); - switch (fileExtension) - { - case ".psm": - ExportPsm(fileName, model); - break; - case ".bbmodel": - ExportBlockBenchModel(fileName, model); - break; - case ".json": - ExportBedrockJson(fileName, model); - break; - default: - Trace.TraceWarning($"[{nameof(ModelImporter)}:Export] Model file format: '{fileExtension}' is not supported."); - return; - } + if (_importProviders.ContainsKey(fileExtension) && _importProviders[fileExtension] is not null) + _importProviders[fileExtension].Export(fileName, model); + + Trace.TraceWarning($"[{nameof(SkinModelImporter)}:Export] No provider found for '{fileExtension}'."); } internal static SkinModelInfo ImportPsm(string fileName) @@ -256,17 +294,27 @@ namespace PckStudio.Internal { BlockBenchModel blockBenchModel = CreateBlockBenchModel(Path.GetFileNameWithoutExtension(fileName), model.TextureSize, textures.Select(nt => (Texture)nt)); - List outliners = new List(5); + Dictionary outliners = new Dictionary(5); List elements = new List(model.Parts.Count); Vector3 transformAxis = new Vector3(1, 1, 0); + Outline GetOrCreateOutline(string partName) + { + if (!outliners.ContainsKey(partName)) + outliners.Add(partName, new Outline(partName)); + return outliners[partName]; + } + foreach (ModelPart part in model.Parts.Values) { + //Outline outline = GetOrCreateOutline(part.Name); + var outline = new Outline(part.Name); Vector3 partTranslation = part.Translation; - outline.Origin = TranslateToInternalPosition("", partTranslation, Vector3.Zero, transformAxis); + outline.Origin = TransformSpace(partTranslation, Vector3.Zero, transformAxis); + outline.Origin += Vector3.UnitY * 24f; Vector3 rotation = part.Rotation + part.AdditionalRotation; outline.Rotation = rotation * TransformSpace(Vector3.One, Vector3.Zero, transformAxis); @@ -278,7 +326,7 @@ namespace PckStudio.Internal elements.Add(element); outline.Children.Add(element.Uuid); } - outliners.Add(outline); + outliners.Add(part.Name, outline); } blockBenchModel.Elements = elements.ToArray(); diff --git a/PCK-Studio/MainForm.cs b/PCK-Studio/MainForm.cs index a3975652..11bc7a63 100644 --- a/PCK-Studio/MainForm.cs +++ b/PCK-Studio/MainForm.cs @@ -632,10 +632,10 @@ namespace PckStudio IEnumerable GetModelTextures(string modelName) { - if (!ModelImporter.ModelTextureLocations.ContainsKey(modelName) || ModelImporter.ModelTextureLocations[modelName]?.TextureLocations?.Length <= 0) + if (!SkinModelImporter.ModelTextureLocations.ContainsKey(modelName) || SkinModelImporter.ModelTextureLocations[modelName]?.TextureLocations?.Length <= 0) return Array.Empty(); - return ModelImporter.ModelTextureLocations[modelName].TextureLocations.Select(texturePath => + return SkinModelImporter.ModelTextureLocations[modelName].TextureLocations.Select(texturePath => { if (currentPCK.TryGetAsset(texturePath + ".png", PckAssetType.TextureFile, out PckAsset modelTextureAsset) || currentPCK.TryGetAsset(texturePath + ".tga", PckAssetType.TextureFile, out modelTextureAsset)) @@ -648,7 +648,7 @@ namespace PckStudio if (openFileDialog.ShowDialog() == DialogResult.OK) { - ModelImporter.ExportBlockBenchModel(openFileDialog.FileName, model, textures); + SkinModelImporter.ExportBlockBenchModel(openFileDialog.FileName, model, textures); } } } diff --git a/PCK-Studio/PckStudio.csproj b/PCK-Studio/PckStudio.csproj index 435e8ec6..becfc447 100644 --- a/PCK-Studio/PckStudio.csproj +++ b/PCK-Studio/PckStudio.csproj @@ -150,6 +150,8 @@ ContributorsForm.cs + + @@ -167,7 +169,7 @@ - +