diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/UnityPath.cs b/Assets/UniGLTF/Runtime/UniGLTF/IO/UnityPath.cs index ff74aeedf..5e3d312c6 100644 --- a/Assets/UniGLTF/Runtime/UniGLTF/IO/UnityPath.cs +++ b/Assets/UniGLTF/Runtime/UniGLTF/IO/UnityPath.cs @@ -343,8 +343,8 @@ namespace UniGLTF } public static UnityPath FromAsset(UnityEngine.Object asset) - { - var assetPath = AssetDatabase.GetAssetPath(asset); + { + var assetPath = AssetDatabase.GetAssetPath(asset); if (string.IsNullOrEmpty(assetPath)) { throw new System.ArgumentNullException(); diff --git a/Assets/VRM10/Editor/MigrationMenu.cs b/Assets/VRM10/Editor/MigrationMenu.cs index 39c62b578..d62cb76d8 100644 --- a/Assets/VRM10/Editor/MigrationMenu.cs +++ b/Assets/VRM10/Editor/MigrationMenu.cs @@ -6,14 +6,18 @@ namespace UniVRM10 { public static class MigrationMenu { - static string s_lastPath = Application.dataPath; + static VRMShaders.PathObject s_lastPath = VRMShaders.PathObject.UnityAssets; const string CONTEXT_MENU = "Assets/Migration: Vrm1"; [MenuItem(CONTEXT_MENU, true)] static bool Enable() { - var path = UniGLTF.UnityPath.FromAsset(Selection.activeObject); + if (Selection.activeObject == null) + { + return false; + } + var path = VRMShaders.PathObject.FromAsset(Selection.activeObject); var isVrm = path.Extension.ToLower() == ".vrm"; return isVrm; } @@ -21,27 +25,30 @@ namespace UniVRM10 [MenuItem(CONTEXT_MENU, false)] static void Exec() { - var path = UniGLTF.UnityPath.FromAsset(Selection.activeObject); - var isVrm = path.Extension.ToLower() == ".vrm"; + var path = VRMShaders.PathObject.FromAsset(Selection.activeObject); - var vrm1Bytes = MigrationVrm.Migrate(File.ReadAllBytes(path.FullPath)); + // migrate + var vrm1Bytes = MigrationVrm.Migrate(path.ReadAllBytes()); - var dst = EditorUtility.SaveFilePanel( - "Save vrm1 file", - s_lastPath, - $"{path.FileNameWithoutExtension}_vrm1", - "vrm"); - if (string.IsNullOrEmpty(dst)) + if (!s_lastPath.TrySaveDialog("Save vrm1 file", $"{path.Stem}_vrm1", out VRMShaders.PathObject dst)) { return; } - s_lastPath = Path.GetDirectoryName(dst); + s_lastPath = dst.Parent; // write result - File.WriteAllBytes(dst, vrm1Bytes); + dst.WriteAllBytes(vrm1Bytes); - // immediately import for GUI update - UniGLTF.UnityPath.FromFullpath(dst).ImportAsset(); + if (dst.IsUnderAsset) + { + // immediately import for GUI update + Debug.Log($"import: {dst}"); + dst.ImportAsset(); + } + else + { + Debug.Log($"write: {dst}"); + } } } } diff --git a/Assets/VRMShaders/GLTF/IO/Editor/PathObject.cs b/Assets/VRMShaders/GLTF/IO/Editor/PathObject.cs new file mode 100644 index 000000000..aef30c3b1 --- /dev/null +++ b/Assets/VRMShaders/GLTF/IO/Editor/PathObject.cs @@ -0,0 +1,183 @@ +using System; +using System.IO; +using UnityEditor; +using UnityEngine; + +namespace VRMShaders +{ + /// + /// UniGLTF.UnityPath (Assets の ひとつ上がルート) をすべてのパスが扱えるように拡張するのが趣旨。 + /// readonly struct で Immutable であるという主張。 + /// + public readonly struct PathObject + { + /// + /// * Delemeter は / を保証 + /// * .. を解決済み + /// * フルパス + /// * 末尾に / を付けない + /// + public string FullPath { get; } + + public string Extension => Path.GetExtension(FullPath); + + public string Stem => Path.GetFileNameWithoutExtension(FullPath); + + public PathObject Parent => FromFullPath(Path.GetDirectoryName(FullPath)); + + public bool IsUnderAsset => IsDescendantOf(UnityAssets); + + /// + /// AssetDatabase の引き数になるパスを想定。 + /// Assets のひとつ上を 基準とする相対パス。 + /// Application.dataPath は Assets を得る。 + /// + /// + public string UnityPath + { + get + { + var root = UnityRoot; + if (!IsDescendantOf(UnityRoot)) + { + throw new ArgumentException($"{FullPath} is not under UnityPath"); + } + return FullPath.Substring(root.FullPath.Length + 1); + } + } + + static PathObject? _root; + public static PathObject UnityRoot + { + get + { + if (!_root.HasValue) + { + _root = FromFullPath(Path.GetDirectoryName(Application.dataPath)); + } + return _root.Value; + } + } + + public static PathObject UnityAssets => UnityRoot.Child("Assets/"); + + PathObject(string src) + { + if (string.IsNullOrEmpty(src)) + { + throw new ArgumentNullException(); + } + src = Path.GetFullPath(src).Replace('\\', '/'); + if (src.Length > 1 && src[src.Length - 1] == '/') + { + // drop last / + src = src.Substring(0, src.Length - 1); + } + if (src[0] == '/') + { + FullPath = src; + } + else + { + if (src.Length >= 3 && src[1] == ':' && src[2] == '/') + { + FullPath = src; + } + else + { + throw new ArgumentException($"{src} is not fullpath"); + } + } + } + + public override string ToString() + { + try + { + var unityPath = UnityPath; + return $""; + } + catch (ArgumentException) + { + return $""; + } + } + + /// start with "X:/" on Windows else "/" + /// + public static PathObject FromFullPath(string src) + { + return new PathObject(src); + } + + /// AssetDatabase が使うパス + /// + public static PathObject FromUnityPath(string src) + { + return UnityRoot.Child(src); + } + + public static PathObject FromAsset(UnityEngine.Object src) + { + if (src == null) + { + throw new ArgumentNullException(); + } + var assetPath = AssetDatabase.GetAssetPath(src); + if (string.IsNullOrEmpty(assetPath)) + { + throw new ArgumentException($"{src} is not asset"); + } + return FromUnityPath(assetPath); + } + + public PathObject Child(string child) + { + return FromFullPath(Path.Combine(FullPath, child)); + } + + public bool IsDescendantOf(PathObject ascendant) + { + if (!FullPath.StartsWith(ascendant.FullPath)) + { + return false; + } + if (FullPath.Length <= ascendant.FullPath.Length || FullPath[ascendant.FullPath.Length] != '/') + { + return false; + } + return true; + } + + public byte[] ReadAllBytes() + { + return File.ReadAllBytes(FullPath); + } + + public void WriteAllBytes(byte[] data) + { + File.WriteAllBytes(FullPath, data); + } + + public void ImportAsset() + { + AssetDatabase.ImportAsset(UnityPath); + } + + public bool TrySaveDialog(string title, string name, out PathObject dst) + { + var path = EditorUtility.SaveFilePanel( + title, + FullPath, + name, + "vrm"); + if (string.IsNullOrEmpty(path)) + { + dst = default; + return false; + } + dst = PathObject.FromFullPath(path); + return true; + } + } +} diff --git a/Assets/VRMShaders/GLTF/IO/Editor/PathObject.cs.meta b/Assets/VRMShaders/GLTF/IO/Editor/PathObject.cs.meta new file mode 100644 index 000000000..0c5fa088f --- /dev/null +++ b/Assets/VRMShaders/GLTF/IO/Editor/PathObject.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d16607c9cf9d05e46b5beec82002d4ef +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRMShaders/GLTF/IO/Tests/PathObjectTests.cs b/Assets/VRMShaders/GLTF/IO/Tests/PathObjectTests.cs new file mode 100644 index 000000000..04028497f --- /dev/null +++ b/Assets/VRMShaders/GLTF/IO/Tests/PathObjectTests.cs @@ -0,0 +1,28 @@ +using System.Linq; +using NUnit.Framework; +using UnityEditor; +using UnityEngine; + +namespace VRMShaders +{ + public sealed class PathObjectTests + { + [Test] + public void Test() + { + var dataPath = PathObject.FromFullPath(Application.dataPath); + + Assert.AreEqual("Assets", dataPath.Stem); + + // UnityRoot + Assert.True(dataPath.IsDescendantOf(PathObject.UnityRoot)); + // UnityRoot/Assets + Assert.False(dataPath.IsDescendantOf(PathObject.UnityAssets)); + Assert.AreEqual(dataPath, PathObject.UnityAssets); + + Assert.AreEqual(PathObject.UnityRoot.Child("Assets"), PathObject.UnityAssets); + Assert.AreEqual(PathObject.UnityAssets.Parent, PathObject.UnityRoot); + Assert.AreEqual("Assets", PathObject.UnityAssets.UnityPath); + } + } +} diff --git a/Assets/VRMShaders/GLTF/IO/Tests/PathObjectTests.cs.meta b/Assets/VRMShaders/GLTF/IO/Tests/PathObjectTests.cs.meta new file mode 100644 index 000000000..23b001bcf --- /dev/null +++ b/Assets/VRMShaders/GLTF/IO/Tests/PathObjectTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 45383f6fca5a57246b70f3919b582b7d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRMShaders/VRM/IO/Runtime/VRM.meta b/Assets/VRMShaders/VRM/IO/Runtime/VRM.meta deleted file mode 100644 index 6d0ee19da..000000000 --- a/Assets/VRMShaders/VRM/IO/Runtime/VRM.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: eb31f565eeca6164694b06ccfe3bc251 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: