Просмотр исходного кода

Add pose saving

Poses can be saved to a different directory from the root pose folder
giving the user simple pose grouping.
habeebweeb 4 лет назад
Родитель
Сommit
90ca2e6cd8

+ 6 - 2
COM3D2.MeidoPhotoStudio.Plugin/Config/MeidoPhotoStudio/Translations/en/translation.ui.json

@@ -53,10 +53,14 @@
         "normal2": "Normal 2",
         "ero2": "Ero 2"
     },
-    "poseSave": {
+    "posePane": {
+        "categoryHeader": "Category",
+        "nameHeader": "Name",
         "saveToggle": "Save Pose",
         "saveButton": "Add",
-        "deleteButton": "D"
+        "deleteButton": "D",
+        "baseTab": "Base",
+        "customTab": "Custom"
     },
     "freeLook": {
         "freeLookToggle": "F-Look",

+ 74 - 5
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Constants.cs

@@ -13,6 +13,11 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
     internal class Constants
     {
         private static bool beginHandItemInit;
+        public const string customPoseDirectory = "Custom Poses";
+        public const string sceneDirectory = "Scenes";
+        public const string kankyoDirectory = "Environments";
+        public const string configDirectory = "MeidoPhotoStudio";
+        public const string translationDirectory = "Translations";
         public static readonly string customPosePath;
         public static readonly string scenesPath;
         public static readonly string kankyoPath;
@@ -50,6 +55,7 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
         public static bool HandItemsInitialized { get; private set; } = false;
         public static bool MenuFilesInitialized { get; private set; } = false;
         public static event EventHandler<MenuFilesEventArgs> MenuFilesChange;
+        public static event EventHandler<CustomPoseEventArgs> customPoseChange;
         public enum DoguCategory
         {
             Other, Mob, Desk, HandItem, BGSmall
@@ -68,10 +74,10 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
         {
             string modsPath = Path.Combine(BepInEx.Paths.GameRootPath, @"Mod\MeidoPhotoStudio");
 
-            customPosePath = Path.Combine(modsPath, "Custom Poses");
-            scenesPath = Path.Combine(modsPath, "Scenes");
-            kankyoPath = Path.Combine(modsPath, "Environments");
-            configPath = Path.Combine(BepInEx.Paths.ConfigPath, "MeidoPhotoStudio");
+            customPosePath = Path.Combine(modsPath, customPoseDirectory);
+            scenesPath = Path.Combine(modsPath, sceneDirectory);
+            kankyoPath = Path.Combine(modsPath, kankyoDirectory);
+            configPath = Path.Combine(BepInEx.Paths.ConfigPath, configDirectory);
 
             foreach (string directory in new[] { customPosePath, scenesPath, kankyoPath, configPath })
             {
@@ -88,6 +94,55 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             InitializeMyRoomProps();
         }
 
+        public static void AddPose(byte[] anmBinary, string filename, string directory)
+        {
+            // TODO: Consider writing a file system monitor
+
+            filename = Utility.SanitizePathPortion(filename);
+            directory = Utility.SanitizePathPortion(directory);
+            if (string.IsNullOrEmpty(filename)) filename = "custom_pose";
+            if (directory.Equals(Constants.customPoseDirectory, StringComparison.InvariantCultureIgnoreCase))
+            {
+                directory = String.Empty;
+            }
+            directory = Path.Combine(Constants.customPosePath, directory);
+
+            if (!Directory.Exists(directory)) Directory.CreateDirectory(directory);
+
+            string fullPath = Path.Combine(directory, filename);
+
+            if (File.Exists($"{fullPath}.anm")) fullPath += $"_{DateTime.Now:yyyyMMddHHmmss}";
+
+            fullPath += ".anm";
+
+            if (!fullPath.StartsWith(Constants.customPosePath))
+            {
+                Utility.Logger.LogError($"Could not save pose! Path is invalid: '{fullPath}'");
+                return;
+            }
+
+            File.WriteAllBytes(fullPath, anmBinary);
+
+            FileInfo fileInfo = new FileInfo(fullPath);
+
+            string category = fileInfo.Directory.Name;
+            string poseGroup = Constants.CustomPoseGroupList.Find(
+                group => string.Equals(category, group, StringComparison.InvariantCultureIgnoreCase)
+            );
+
+            if (string.IsNullOrEmpty(poseGroup))
+            {
+                Constants.CustomPoseGroupList.Add(category);
+                Constants.CustomPoseDict[category] = new List<string>();
+            }
+            else category = poseGroup;
+
+            Constants.CustomPoseDict[category].Add(fullPath);
+            Constants.CustomPoseDict[category].Sort();
+
+            customPoseChange?.Invoke(null, new CustomPoseEventArgs(fullPath, category));
+        }
+
         public static void InitializePoses()
         {
             // Load Poses
@@ -156,11 +211,14 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
                 if (poseList.Count > 0)
                 {
                     string poseGroupName = new DirectoryInfo(directory).Name;
-                    CustomPoseGroupList.Add(poseGroupName);
+                    if (poseGroupName != customPoseDirectory) CustomPoseGroupList.Add(poseGroupName);
                     CustomPoseDict[poseGroupName] = poseList;
                 }
             };
 
+            CustomPoseGroupList.Add(customPoseDirectory);
+            CustomPoseDict[customPoseDirectory] = new List<string>();
+
             GetPoses(customPosePath);
 
             foreach (string directory in Directory.GetDirectories(customPosePath))
@@ -664,4 +722,15 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
         public static MenuFilesEventArgs MenuFiles => new MenuFilesEventArgs(EventType.MenuFiles);
         public MenuFilesEventArgs(EventType type) => this.Type = type;
     }
+
+    public class CustomPoseEventArgs : EventArgs
+    {
+        public string Category { get; }
+        public string Path { get; }
+        public CustomPoseEventArgs(string path, string category)
+        {
+            this.Path = path;
+            this.Category = category;
+        }
+    }
 }

+ 15 - 1
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/GUI/Panes/MainWindowPanes/PoseWindowPane.cs

@@ -1,4 +1,7 @@
 using System;
+using System.IO;
+using System.Collections.Generic;
+using System.Linq;
 using UnityEngine;
 
 namespace COM3D2.MeidoPhotoStudio.Plugin
@@ -7,17 +10,22 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
     {
         private MeidoManager meidoManager;
         private MaidPoseSelectorPane maidPosePane;
+        private SavePosePane savePosePane;
         private MaidSwitcherPane maidSwitcherPane;
         private MaidFaceLookPane maidFaceLookPane;
         private MaidDressingPane maidDressingPane;
         private MaidIKPane maidIKPane;
         private Toggle freeLookToggle;
+        private Toggle savePoseToggle;
+
+        private bool savePoseMode = false;
 
         public PoseWindowPane(MeidoManager meidoManager, MaidSwitcherPane maidSwitcherPane)
         {
             this.meidoManager = meidoManager;
             this.maidSwitcherPane = maidSwitcherPane;
 
+            this.savePosePane = new SavePosePane(meidoManager);
             this.maidPosePane = new MaidPoseSelectorPane(meidoManager);
             this.maidFaceLookPane = new MaidFaceLookPane(meidoManager);
             this.maidFaceLookPane.Enabled = false;
@@ -28,11 +36,15 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
 
             this.freeLookToggle = new Toggle(Translation.Get("freeLook", "freeLookToggle"), false);
             this.freeLookToggle.ControlEvent += (s, a) => SetMaidFreeLook();
+
+            this.savePoseToggle = new Toggle(Translation.Get("posePane", "saveToggle"));
+            this.savePoseToggle.ControlEvent += (s, a) => savePoseMode = !savePoseMode;
         }
 
         protected override void ReloadTranslation()
         {
             this.freeLookToggle.Label = Translation.Get("freeLook", "freeLookToggle");
+            this.savePoseToggle.Label = Translation.Get("posePane", "saveToggle");
         }
 
         public override void Draw()
@@ -45,9 +57,11 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             GUILayout.BeginHorizontal();
             GUI.enabled = this.meidoManager.HasActiveMeido;
             freeLookToggle.Draw();
+            savePoseToggle.Draw();
             GUILayout.EndHorizontal();
 
-            maidFaceLookPane.Draw();
+            if (savePoseMode) savePosePane.Draw();
+            else maidFaceLookPane.Draw();
 
             maidDressingPane.Draw();
 

+ 35 - 3
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/GUI/Panes/PoseWindowPanes/MaidPoseSelectorPane.cs

@@ -1,3 +1,4 @@
+using System;
 using System.IO;
 using System.Collections.Generic;
 using System.Linq;
@@ -16,6 +17,7 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
         private Dropdown poseDropdown;
         private SelectionGrid poseModeGrid;
         private bool customPoseMode = false;
+        private bool poseListEnabled = true;
         private Dictionary<string, List<string>> CurrentPoseDict
         {
             get => customPoseMode ? Constants.CustomPoseDict : Constants.PoseDict;
@@ -30,12 +32,14 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
         private string SelectedPose => CurrentPoseList[SelectedPoseIndex];
         private PoseInfo CurrentPoseInfo => new PoseInfo(SelectedPoseGroup, SelectedPose, customPoseMode);
         private string previousPoseGroup;
+        private static readonly string[] tabTranslations = new[] { "baseTab", "customTab" };
 
         public MaidPoseSelectorPane(MeidoManager meidoManager)
         {
+            Constants.customPoseChange += SavePoseEnd;
             this.meidoManager = meidoManager;
 
-            this.poseModeGrid = new SelectionGrid(new[] { "Base", "Custom" });
+            this.poseModeGrid = new SelectionGrid(Translation.GetArray("posePane", tabTranslations));
             this.poseModeGrid.ControlEvent += (s, a) => SetPoseMode();
 
             this.poseGroupDropdown = new Dropdown(Translation.GetArray("poseGroupDropdown", Constants.PoseGroupList));
@@ -61,6 +65,7 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
 
         protected override void ReloadTranslation()
         {
+            this.poseModeGrid.SetItems(Translation.GetArray("posePane", tabTranslations));
             if (!customPoseMode)
             {
                 this.poseGroupDropdown.SetDropdownItems(
@@ -96,6 +101,7 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             GUILayout.EndHorizontal();
 
             GUILayout.BeginHorizontal();
+            GUI.enabled = GUI.enabled && poseListEnabled;
             this.poseLeftButton.Draw(arrowLayoutOptions);
             this.poseDropdown.Draw(dropdownLayoutOptions);
             this.poseRightButton.Draw(arrowLayoutOptions);
@@ -137,6 +143,20 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             this.updating = false;
         }
 
+        private void SavePoseEnd(object sender, CustomPoseEventArgs args)
+        {
+            this.updating = true;
+            this.poseModeGrid.SelectedItemIndex = 1;
+            this.poseGroupDropdown.SetDropdownItems(
+                CurrentPoseGroupList.ToArray(), CurrentPoseGroupList.IndexOf(args.Category)
+            );
+            this.updating = false;
+
+            this.poseDropdown.SetDropdownItems(
+                UIPoseList(CurrentPoseList), CurrentPoseDict[args.Category].IndexOf(args.Path)
+            );
+        }
+
         private void SetPoseMode()
         {
             customPoseMode = poseModeGrid.SelectedItemIndex == 1;
@@ -146,6 +166,7 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             string[] list = customPoseMode
                 ? CurrentPoseGroupList.ToArray()
                 : Translation.GetArray("poseGroupDropdown", CurrentPoseGroupList);
+
             this.poseGroupDropdown.SetDropdownItems(list, 0);
         }
 
@@ -158,13 +179,24 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             else
             {
                 previousPoseGroup = SelectedPoseGroup;
-                this.poseDropdown.SetDropdownItems(UIPoseList(CurrentPoseList), 0);
+                List<string> poseList = CurrentPoseList;
+
+                poseListEnabled = true;
+                if (poseList.Count == 0)
+                {
+                    poseListEnabled = false;
+                    this.poseDropdown.SetDropdownItems(new[] { "No Poses" }, 0);
+                }
+                else
+                {
+                    this.poseDropdown.SetDropdownItems(UIPoseList(CurrentPoseList), 0);
+                }
             }
         }
 
         private void ChangePose()
         {
-            if (updating) return;
+            if (!poseListEnabled || updating) return;
             meidoManager.ActiveMeido.SetPose(CurrentPoseInfo);
         }
 

+ 64 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/GUI/Panes/PoseWindowPanes/SavePosePane.cs

@@ -0,0 +1,64 @@
+using System;
+using UnityEngine;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    internal class SavePosePane : BasePane
+    {
+        private MeidoManager meidoManager;
+        private Button savePoseButton;
+        private Button deletePoseButton;
+        private TextField poseNameTextField;
+        private ComboBox categoryComboBox;
+        private string categoryHeader;
+        private string nameHeader;
+
+        public SavePosePane(MeidoManager meidoManager)
+        {
+            Constants.customPoseChange += (s, a) =>
+            {
+                this.categoryComboBox.SetDropdownItems(Constants.CustomPoseGroupList.ToArray());
+            };
+
+            this.meidoManager = meidoManager;
+
+            this.categoryHeader = Translation.Get("posePane", "categoryHeader");
+            this.nameHeader = Translation.Get("posePane", "nameHeader");
+
+            this.savePoseButton = new Button(Translation.Get("posePane", "saveButton"));
+            this.savePoseButton.ControlEvent += OnSavePose;
+
+            this.deletePoseButton = new Button(Translation.Get("posePane", "deleteButton"));
+            this.categoryComboBox = new ComboBox(Constants.CustomPoseGroupList.ToArray());
+            this.poseNameTextField = new TextField();
+            this.poseNameTextField.ControlEvent += OnSavePose;
+        }
+
+        protected override void ReloadTranslation()
+        {
+            this.categoryHeader = Translation.Get("posePane", "categoryHeader");
+            this.nameHeader = Translation.Get("posePane", "nameHeader");
+            this.savePoseButton.Label = Translation.Get("posePane", "saveButton");
+            this.deletePoseButton.Label = Translation.Get("posePane", "deleteButton");
+        }
+
+        public override void Draw()
+        {
+            MiscGUI.Header(categoryHeader);
+            this.categoryComboBox.Draw();
+
+            MiscGUI.Header(nameHeader);
+            GUILayout.BeginHorizontal();
+            this.poseNameTextField.Draw();
+            this.savePoseButton.Draw(GUILayout.ExpandWidth(false));
+            GUILayout.EndHorizontal();
+        }
+
+        private void OnSavePose(object sender, EventArgs args)
+        {
+            byte[] anmBinary = this.meidoManager.ActiveMeido.SerializePose();
+            Constants.AddPose(anmBinary, this.poseNameTextField.Value, this.categoryComboBox.Value);
+            this.poseNameTextField.Value = String.Empty;
+        }
+    }
+}

+ 13 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Meido/Meido.cs

@@ -101,6 +101,19 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             }
         }
 
+        public byte[] SerializePose()
+        {
+            CacheBoneDataArray cache = this.Maid.gameObject.GetComponent<CacheBoneDataArray>();
+
+            if (cache == null)
+            {
+                cache = this.Maid.gameObject.AddComponent<CacheBoneDataArray>();
+                cache.CreateCache(this.Maid.body0.GetBone("Bip01"));
+            }
+
+            return cache.GetAnmBinary(true, true);
+        }
+
         public Maid Load(int slot, int activeSlot)
         {
             isLoading = true;

+ 1 - 1
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Translation.cs

@@ -21,7 +21,7 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
                 StringComparer.InvariantCultureIgnoreCase
             );
 
-            string rootTranslationPath = Path.Combine(Constants.configPath, "Translations");
+            string rootTranslationPath = Path.Combine(Constants.configPath, Constants.translationDirectory);
 
             string currentTranslationPath = Path.Combine(rootTranslationPath, CurrentLanguage);
 

+ 8 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Utility.cs

@@ -111,5 +111,13 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             float largest = Mathf.Max(scale.x, Mathf.Max(scale.y, scale.z));
             go.transform.localScale = Vector3.one * (float)Math.Round(largest, 3);
         }
+
+        public static string SanitizePathPortion(string path)
+        {
+            char[] invalid = Path.GetInvalidFileNameChars();
+            path = path.Trim();
+            path = string.Join("_", path.Split(invalid)).Replace(".", "").Trim('_');
+            return path;
+        }
     }
 }