From c91def4a7731c6a45c93db377cd8e98bb91b556f Mon Sep 17 00:00:00 2001 From: miku-666 <74728189+NessieHax@users.noreply.github.com> Date: Sun, 1 Sep 2024 17:25:13 +0200 Subject: [PATCH] Add ModelEditor --- .../Forms/Editor/ModelEditor.Designer.cs | 110 +++++++++++++++ PCK-Studio/Forms/Editor/ModelEditor.cs | 128 ++++++++++++++++++ PCK-Studio/Forms/Editor/ModelEditor.resx | 123 +++++++++++++++++ PCK-Studio/Internal/GameModelImporter.cs | 42 ++++-- PCK-Studio/MainForm.Designer.cs | 2 +- PCK-Studio/MainForm.cs | 51 ++----- PCK-Studio/PckStudio.csproj | 9 ++ Vendor/OMI-Lib | 2 +- 8 files changed, 413 insertions(+), 54 deletions(-) create mode 100644 PCK-Studio/Forms/Editor/ModelEditor.Designer.cs create mode 100644 PCK-Studio/Forms/Editor/ModelEditor.cs create mode 100644 PCK-Studio/Forms/Editor/ModelEditor.resx diff --git a/PCK-Studio/Forms/Editor/ModelEditor.Designer.cs b/PCK-Studio/Forms/Editor/ModelEditor.Designer.cs new file mode 100644 index 00000000..94d4d029 --- /dev/null +++ b/PCK-Studio/Forms/Editor/ModelEditor.Designer.cs @@ -0,0 +1,110 @@ +namespace PckStudio.Forms.Editor +{ + partial class ModelEditor + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.modelTreeView = new System.Windows.Forms.TreeView(); + this.modelContextMenu = new MetroFramework.Controls.MetroContextMenu(this.components); + this.exportToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.editToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.removeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.modelContextMenu.SuspendLayout(); + this.SuspendLayout(); + // + // modelTreeView + // + this.modelTreeView.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(20)))), ((int)(((byte)(20)))), ((int)(((byte)(20))))); + this.modelTreeView.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.modelTreeView.ContextMenuStrip = this.modelContextMenu; + this.modelTreeView.Dock = System.Windows.Forms.DockStyle.Fill; + this.modelTreeView.ForeColor = System.Drawing.SystemColors.Window; + this.modelTreeView.Location = new System.Drawing.Point(20, 60); + this.modelTreeView.Name = "modelTreeView"; + this.modelTreeView.PathSeparator = "."; + this.modelTreeView.Size = new System.Drawing.Size(335, 395); + this.modelTreeView.TabIndex = 0; + this.modelTreeView.BeforeSelect += new System.Windows.Forms.TreeViewCancelEventHandler(this.modelTreeView_BeforeSelect); + // + // modelContextMenu + // + this.modelContextMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.exportToolStripMenuItem, + this.editToolStripMenuItem, + this.removeToolStripMenuItem}); + this.modelContextMenu.Name = "modelContextMenu"; + this.modelContextMenu.Size = new System.Drawing.Size(118, 70); + // + // exportToolStripMenuItem + // + this.exportToolStripMenuItem.Name = "exportToolStripMenuItem"; + this.exportToolStripMenuItem.Size = new System.Drawing.Size(180, 22); + this.exportToolStripMenuItem.Text = "Export"; + this.exportToolStripMenuItem.Click += new System.EventHandler(this.exportToolStripMenuItem_Click); + // + // editToolStripMenuItem + // + this.editToolStripMenuItem.Name = "editToolStripMenuItem"; + this.editToolStripMenuItem.Size = new System.Drawing.Size(180, 22); + this.editToolStripMenuItem.Text = "Edit"; + this.editToolStripMenuItem.Visible = false; + // + // removeToolStripMenuItem + // + this.removeToolStripMenuItem.Name = "removeToolStripMenuItem"; + this.removeToolStripMenuItem.Size = new System.Drawing.Size(180, 22); + this.removeToolStripMenuItem.Text = "Remove"; + this.removeToolStripMenuItem.Visible = false; + // + // ModelEditor + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(375, 475); + this.Controls.Add(this.modelTreeView); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.MinimumSize = new System.Drawing.Size(375, 475); + this.Name = "ModelEditor"; + this.Style = MetroFramework.MetroColorStyle.Silver; + this.Text = "ModelEditor"; + this.Theme = MetroFramework.MetroThemeStyle.Dark; + this.modelContextMenu.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.TreeView modelTreeView; + private MetroFramework.Controls.MetroContextMenu modelContextMenu; + private System.Windows.Forms.ToolStripMenuItem exportToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem editToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem removeToolStripMenuItem; + } +} \ No newline at end of file diff --git a/PCK-Studio/Forms/Editor/ModelEditor.cs b/PCK-Studio/Forms/Editor/ModelEditor.cs new file mode 100644 index 00000000..ba2a4064 --- /dev/null +++ b/PCK-Studio/Forms/Editor/ModelEditor.cs @@ -0,0 +1,128 @@ +using System; +using System.IO; +using System.Data; +using System.Linq; +using System.Drawing; +using System.Diagnostics; +using System.Windows.Forms; +using System.Collections.Generic; + +using OMI.Formats.Model; +using MetroFramework.Forms; + +using PckStudio.Internal; + +namespace PckStudio.Forms.Editor +{ + public partial class ModelEditor : MetroForm + { + private readonly ModelContainer _models; + private readonly TryGetTextureDelegate _tryGetTexture; + + + public delegate bool TryGetTextureDelegate(string path, out Image img); + + public ModelEditor(ModelContainer models, TryGetTextureDelegate tryGetTexture) + { + InitializeComponent(); + _models = models; + _tryGetTexture = tryGetTexture; + } + + private class ModelNode : TreeNode + { + private Model _model; + public Model Model => _model; + + public ModelNode(Model model) + : base(model.Name) + { + _model = model; + Nodes.AddRange(GetModelNodes(_model.GetParts()).ToArray()); + } + private static IEnumerable GetModelNodes(IEnumerable parts) + { + return parts.Select(part => new ModelPartNode(part)); + } + } + + private class ModelPartNode : TreeNode + { + private ModelPart _part; + + public ModelPart Part => _part; + + public ModelPartNode(ModelPart part) + : base(part.Name) + { + _part = part; + Nodes.AddRange(GetModelPartNodeChildren(part.GetBoxes()).ToArray()); + } + private static IEnumerable GetModelPartNodeChildren(IEnumerable boxes) + { + return boxes.Select(box => new ModelBoxNode(box)); + } + } + + private class ModelBoxNode : TreeNode + { + private ModelBox _modelBox; + public ModelBoxNode(ModelBox modelBox) + : base($"Box: pos:{modelBox.Position} size:{modelBox.Size}") + { + _modelBox = modelBox; + } + } + + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + modelTreeView.Nodes.AddRange(_models.Select(model => new ModelNode(model)).ToArray()); + } + + private void exportToolStripMenuItem_Click(object sender, EventArgs e) + { + if (modelTreeView.SelectedNode is ModelNode modelNode) + { + Model model = modelNode.Model; + Debug.Write(model.Name + "; "); + Debug.WriteLine(model.TextureSize); + + GameModelImporter.Default.ExportSettings.CreateModelOutline = + MessageBox.Show( + $"Do you wish to have all model parts contained in a group called '{model.Name}'?", + "Group model parts", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes; + + using SaveFileDialog openFileDialog = new SaveFileDialog(); + openFileDialog.FileName = model.Name; + openFileDialog.Filter = GameModelImporter.Default.SupportedModelFileFormatsFilter; + + IEnumerable GetModelTextures(string modelName) + { + if (!GameModelImporter.ModelMetaData.ContainsKey(modelName) || GameModelImporter.ModelMetaData[modelName]?.TextureLocations?.Length <= 0) + yield break; + foreach (var textureLocation in GameModelImporter.ModelMetaData[modelName].TextureLocations) + { + if (_tryGetTexture(textureLocation, out Image img)) + yield return new NamedTexture(Path.GetFileName(textureLocation), img); + } + yield break; + } + + if (openFileDialog.ShowDialog(this) == DialogResult.OK) + { + IEnumerable textures = GetModelTextures(model.Name); + var modelInfo = new GameModelInfo(model, textures); + GameModelImporter.Default.Export(openFileDialog.FileName, modelInfo); + } + } + } + + private void modelTreeView_BeforeSelect(object sender, TreeViewCancelEventArgs e) + { + exportToolStripMenuItem.Visible = e.Node is ModelNode; + editToolStripMenuItem.Visible = e.Node is ModelBoxNode; + removeToolStripMenuItem.Visible = e.Node is ModelPartNode || e.Node is ModelBoxNode; + } + } +} diff --git a/PCK-Studio/Forms/Editor/ModelEditor.resx b/PCK-Studio/Forms/Editor/ModelEditor.resx new file mode 100644 index 00000000..6874eb71 --- /dev/null +++ b/PCK-Studio/Forms/Editor/ModelEditor.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/PCK-Studio/Internal/GameModelImporter.cs b/PCK-Studio/Internal/GameModelImporter.cs index d88464f3..89430db5 100644 --- a/PCK-Studio/Internal/GameModelImporter.cs +++ b/PCK-Studio/Internal/GameModelImporter.cs @@ -15,8 +15,10 @@ * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. **/ +using System; using System.IO; using System.Linq; +using System.Drawing; using System.Numerics; using System.Diagnostics; using System.Collections.Generic; @@ -24,6 +26,7 @@ using System.Collections.ObjectModel; using Newtonsoft.Json; using Newtonsoft.Json.Linq; + using OMI.Formats.Model; using PckStudio.External.Format; @@ -36,8 +39,12 @@ namespace PckStudio.Internal { public static GameModelImporter Default { get; } = new GameModelImporter(); + public class ModelExportSettings + { public bool CreateModelOutline { get; set; } = true; + } + public ModelExportSettings ExportSettings { get; } = new ModelExportSettings(); internal static ReadOnlyDictionary ModelMetaData { get; } = JsonConvert.DeserializeObject>(Resources.modelMetaData); private GameModelImporter() @@ -46,14 +53,16 @@ namespace PckStudio.Internal InternalAddProvider(new FileDialogFilter("Block bench model(*.bbmodel)", "*.bbmodel"), null, ExportBlockBenchModel); } + Vector3 bbModelTransformAxis = new Vector3(1, 1, 0); + Vector3 _heightOffset = Vector3.Zero; + private void ExportBlockBenchModel(string filepath, GameModelInfo modelInfo) { BlockBenchModel blockBenchModel = BlockBenchModel.Create(BlockBenchFormatInfos.BedrockEntity, modelInfo.Model.Name, modelInfo.Model.TextureSize, modelInfo.Textures.Select(nt => (Texture)nt)); + blockBenchModel.ModelIdentifier = modelInfo.Model.Name; Dictionary outliners = new Dictionary(5); - List elements = new List(modelInfo.Model.Parts.Count); - - Vector3 transformAxis = new Vector3(1, 1, 0); + List elements = new List(modelInfo.Model.PartCount); if (!ModelMetaData.TryGetValue(modelInfo.Model.Name, out JsonModelMetaData modelMetaData)) { @@ -61,20 +70,23 @@ namespace PckStudio.Internal return; } - foreach (ModelPart part in modelInfo.Model.Parts.Values) + _heightOffset = Vector3.UnitY * 24f; + + foreach (ModelPart part in modelInfo.Model.GetParts()) { var outline = new Outline(part.Name); Vector3 partTranslation = part.Translation; - outline.Origin = TransformSpace(partTranslation, Vector3.Zero, transformAxis); - outline.Origin += Vector3.UnitY * 24f; + outline.Origin = TransformSpace(partTranslation, Vector3.Zero, bbModelTransformAxis); + outline.Origin += _heightOffset; - Vector3 rotation = part.Rotation + part.AdditionalRotation; - outline.Rotation = rotation * TransformSpace(Vector3.One, Vector3.Zero, transformAxis); + Vector3 rotation = part.Rotation; + outline.Rotation = rotation * TransformSpace(Vector3.One, Vector3.Zero, bbModelTransformAxis); - foreach (ModelBox box in part.Boxes) + foreach (ModelBox box in part.GetBoxes()) { Element element = CreateElement(part.Name, box, partTranslation); + element.Rotation = rotation * TransformSpace(Vector3.One, Vector3.Zero, bbModelTransformAxis); element.Origin = outline.Origin; elements.Add(element); outline.Children.Add(element.Uuid); @@ -86,7 +98,7 @@ namespace PckStudio.Internal blockBenchModel.Elements = elements.ToArray(); IEnumerable outlines = outliners.Values.Where(value => modelMetaData.RootParts.Count == 0 || modelMetaData.RootParts.ContainsKey(value.Name)); - if (CreateModelOutline) + if (ExportSettings.CreateModelOutline) outlines = new Outline[1] { new Outline(modelInfo.Model.Name) { Children = JArray.FromObject(outlines) } @@ -112,7 +124,7 @@ namespace PckStudio.Internal { if (child.Type == JTokenType.String && outliners.TryGetValue(child.ToString(), out Outline childOutline)) { - childOutline.Rotation += -partentOutline.Rotation; + childOutline.Rotation -= partentOutline.Rotation; partentOutline.Children.Add(JToken.FromObject(childOutline)); } if (child.Type == JTokenType.Object) @@ -127,7 +139,7 @@ namespace PckStudio.Internal continue; } childOutline = outliners[key]; - childOutline.Rotation += -partentOutline.Rotation; + childOutline.Rotation -= partentOutline.Rotation; partentOutline.Children.Add(JToken.FromObject(childOutline)); } } @@ -135,12 +147,12 @@ namespace PckStudio.Internal } } - private static Element CreateElement(string name, ModelBox box, Vector3 origin) + private Element CreateElement(string name, ModelBox box, Vector3 origin) { Vector3 pos = box.Position; Vector3 size = box.Size; - Vector3 transformPos = TransformSpace(pos + origin, size, new Vector3(1, 1, 0)) + 24f * Vector3.UnitY; - return Element.CreateCube(name, box.Uv, transformPos, size, box.Scale, box.Mirror); + Vector3 transformPos = TransformSpace(pos + origin, size, bbModelTransformAxis) + _heightOffset; + return Element.CreateCube(name, box.Uv, transformPos, size, box.Inflate, box.Mirror); } } } diff --git a/PCK-Studio/MainForm.Designer.cs b/PCK-Studio/MainForm.Designer.cs index 8c9e1cd4..1a655e2c 100644 --- a/PCK-Studio/MainForm.Designer.cs +++ b/PCK-Studio/MainForm.Designer.cs @@ -90,11 +90,11 @@ this.mashUpPackToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.openToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.recentlyOpenToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.closeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.packSettingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.fullBoxSupportToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.saveToolStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem(); this.saveToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.closeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.editToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.quickChangeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); diff --git a/PCK-Studio/MainForm.cs b/PCK-Studio/MainForm.cs index 097eb41d..afd114f0 100644 --- a/PCK-Studio/MainForm.cs +++ b/PCK-Studio/MainForm.cs @@ -594,53 +594,30 @@ namespace PckStudio } } - public void HandleModelsFile(PckAsset file) + public void HandleModelsFile(PckAsset asset) { - ModelContainer modelContainer = file.GetData(new ModelFileReader()); + ModelContainer modelContainer = asset.GetData(new ModelFileReader()); if (modelContainer.ModelCount == 0) { MessageBox.Show("No models found.", "Empty Model file", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } - using ItemSelectionPopUp itemSelection = new ItemSelectionPopUp(modelContainer.GetModelNames().ToArray()); - if (itemSelection.ShowDialog() == DialogResult.OK && modelContainer.ContainsModel(itemSelection.SelectedItem)) + ModelEditor.TryGetTextureDelegate tryGetTexture = + (string path, out Image img) => { - Model model = modelContainer.GetModelByName(itemSelection.SelectedItem); - Debug.WriteLine(model.Name + "; "); - Debug.WriteLine(model.TextureSize + "; "); + bool found = currentPCK.TryGetAsset(path + ".png", PckAssetType.TextureFile, out PckAsset asset) || + currentPCK.TryGetAsset(path + ".tga", PckAssetType.TextureFile, out asset); + img = found ? asset.GetTexture() : default; + return found; + }; - GameModelImporter.Default.CreateModelOutline = - MessageBox.Show( - $"Do you wish to have all model parts contained in a group called '{model.Name}'?", - "Group model parts", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes; - - using SaveFileDialog openFileDialog = new SaveFileDialog(); - openFileDialog.FileName = model.Name; - openFileDialog.Filter = GameModelImporter.Default.SupportedModelFileFormatsFilter; - - IEnumerable GetModelTextures(string modelName) + var editor = new ModelEditor(modelContainer, tryGetTexture); + if (editor.ShowDialog() == DialogResult.OK) { - if (!GameModelImporter.ModelMetaData.ContainsKey(modelName) || GameModelImporter.ModelMetaData[modelName]?.TextureLocations?.Length <= 0) - return Enumerable.Empty(); - - return GameModelImporter.ModelMetaData[modelName].TextureLocations - .Where(texturePath => currentPCK.Contains(texturePath + ".png", PckAssetType.TextureFile) || currentPCK.Contains(texturePath + ".tga", PckAssetType.TextureFile)) - .Select(texturePath => - { - PckAsset modelTextureAsset = currentPCK.GetAsset(texturePath + ".png", PckAssetType.TextureFile) ?? currentPCK.GetAsset(texturePath + ".tga", PckAssetType.TextureFile); - return new NamedTexture(Path.GetFileName(texturePath), modelTextureAsset.GetTexture()); - }); + return; } - - if (openFileDialog.ShowDialog() == DialogResult.OK) - { - IEnumerable textures = GetModelTextures(model.Name); - var modelInfo = new GameModelInfo(model, textures); - GameModelImporter.Default.Export(openFileDialog.FileName, modelInfo); } - } - } public void HandleBehavioursFile(PckAsset asset) { @@ -2216,7 +2193,7 @@ namespace PckStudio { if (treeViewMain.SelectedNode.TryGetTagData(out PckAsset asset)) { - IEnumerable props = asset.SerializeProperties(seperater:" "); + IEnumerable props = asset.SerializeProperties(seperater: " "); using (var input = new MultiTextPrompt(props)) { if (input.ShowDialog(this) == DialogResult.OK) @@ -2305,7 +2282,7 @@ namespace PckStudio ButtonText = "OK" }; - if(dialog.ShowDialog(this) == DialogResult.OK) + if (dialog.ShowDialog(this) == DialogResult.OK) { BinkaConverter.ToBinka( fileDialog.FileNames, diff --git a/PCK-Studio/PckStudio.csproj b/PCK-Studio/PckStudio.csproj index 3b7dff26..fd0e175f 100644 --- a/PCK-Studio/PckStudio.csproj +++ b/PCK-Studio/PckStudio.csproj @@ -152,6 +152,12 @@ ContributorsForm.cs + + Form + + + ModelEditor.cs + @@ -478,6 +484,9 @@ ContributorsForm.cs + + ModelEditor.cs + CemuPanel.cs diff --git a/Vendor/OMI-Lib b/Vendor/OMI-Lib index 8da46067..d9faa0b7 160000 --- a/Vendor/OMI-Lib +++ b/Vendor/OMI-Lib @@ -1 +1 @@ -Subproject commit 8da46067972139bb68334c80da3914893b5aeca7 +Subproject commit d9faa0b7a09d1da9bfa291d917958e6666a18257