Browse Source

[unity] Cleanup: Spine `Editor/Utility/SpineEditorUtilities` class into multiple files with partial class qualifier.

Harald Csaszar 6 years ago
parent
commit
ddf5082dc4
26 changed files with 2892 additions and 1616 deletions
  1. 4 1
      CHANGELOG.md
  2. 1 1
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/AnimationReferenceAssetEditor.cs
  3. 5 5
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SkeletonDataAssetInspector.cs
  4. 1 1
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SpineAtlasAssetInspector.cs
  5. 1 1
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoneFollowerGraphicInspector.cs
  6. 1 1
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoneFollowerInspector.cs
  7. 1 1
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoundingBoxFollowerInspector.cs
  8. 1 1
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/PointFollowerInspector.cs
  9. 1 1
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicInspector.cs
  10. 2 2
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Menus.cs
  11. 1051 0
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/AssetUtility.cs
  12. 12 0
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/AssetUtility.cs.meta
  13. 339 0
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/BuildSettings.cs
  14. 12 0
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/BuildSettings.cs.meta
  15. 149 0
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/DataReloadHandler.cs
  16. 12 0
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/DataReloadHandler.cs.meta
  17. 167 0
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/Icons.cs
  18. 12 0
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/Icons.cs.meta
  19. 203 0
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/Instantiation.cs
  20. 12 0
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/Instantiation.cs.meta
  21. 313 0
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/Preferences.cs
  22. 12 0
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/Preferences.cs.meta
  23. 39 1594
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineEditorUtilities.cs
  24. 522 0
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineHandles.cs
  25. 12 0
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineHandles.cs.meta
  26. 7 7
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SkeletonBaker.cs

+ 4 - 1
CHANGELOG.md

@@ -138,7 +138,10 @@
     * When receiving namespace related errors, replace using statements of `using Spine.Unity.Modules.AttachmentTools;` with `using Spine.Unity.AttachmentTools;`. You can remove `using Spine.Unity.Modules;` statements when a `using Spine.Unity` statement is already present in the file.
     * `AttachmentTools`, `SkeletonPartsRenderer`, `SkeletonRenderSeparator`, `SkeletonRendererCustomMaterials` changed to namespace `Spine.Unity`.
     * `SkeletonGhost`, `SkeletonGhostRenderer`, `AtlasRegionAttacher`, `SkeletonGraphicMirror`, `SkeletonRagdoll`, `SkeletonRagdoll2D`, `SkeletonUtilityEyeConstraint`, `SkeletonUtilityGroundConstraint`, `SkeletonUtilityKinematicShadow` changed to namespace `Spine.Unity.Examples`.
-  
+  * Split `Editor/Utility/SpineEditorUtilities` class into multiple files with partial class qualifier.
+    * Nested classes `SpineEditorUtilities.AssetUtility` and `SpineEditorUtilities.EditorInstantiation` are now no longer nested. If you receive namespace related errors, replace any occurrance of
+      * `SpineEditorUtilities.AssetUtility` with `AssetUtility` and
+      * `SpineEditorUtilities.EditorInstantiation` with `EditorInstantiation`.
 
 * **Additions**
   * **Spine Preferences stored in Assets/Editor/SpineSettings.asset** Now Spine uses the new `SettingsProvider` API, storing settings in a SpineSettings.asset file which can be shared with team members. Your old preferences are automatically migrated to the new system.

+ 1 - 1
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/AnimationReferenceAssetEditor.cs

@@ -113,7 +113,7 @@ namespace Spine.Unity.Editor {
 				EditorGUILayout.HelpBox(string.Format("Animation named {0} was not found for this Skeleton.", animationNameProperty.stringValue), MessageType.Warning);
 			} else {
 				using (new SpineInspectorUtility.BoxScope()) {
-					if (!string.Equals(SpineEditorUtilities.AssetUtility.GetPathSafeName(animationName), ThisAnimationReferenceAsset.name, System.StringComparison.OrdinalIgnoreCase))
+					if (!string.Equals(AssetUtility.GetPathSafeName(animationName), ThisAnimationReferenceAsset.name, System.StringComparison.OrdinalIgnoreCase))
 						EditorGUILayout.HelpBox("Animation name value does not match this asset's name. Inspectors using this asset may be misleading.", MessageType.None);
 
 					EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(animationName, SpineEditorUtilities.Icons.animation));

+ 5 - 5
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SkeletonDataAssetInspector.cs

@@ -255,7 +255,7 @@ namespace Spine.Unity.Editor {
 			FieldInfo nameField = typeof(AnimationReferenceAsset).GetField("animationName", BindingFlags.NonPublic | BindingFlags.Instance);
 			FieldInfo skeletonDataAssetField = typeof(AnimationReferenceAsset).GetField("skeletonDataAsset", BindingFlags.NonPublic | BindingFlags.Instance);
 			foreach (var animation in targetSkeletonData.Animations) {
-				string assetPath = string.Format("{0}/{1}.asset", dataPath, SpineEditorUtilities.AssetUtility.GetPathSafeName(animation.Name));
+				string assetPath = string.Format("{0}/{1}.asset", dataPath, AssetUtility.GetPathSafeName(animation.Name));
 				AnimationReferenceAsset existingAsset = AssetDatabase.LoadAssetAtPath<AnimationReferenceAsset>(assetPath);
 				if (existingAsset == null) {
 					AnimationReferenceAsset newAsset = ScriptableObject.CreateInstance<AnimationReferenceAsset>();
@@ -600,7 +600,7 @@ namespace Spine.Unity.Editor {
 				warnings.Add("Missing Skeleton JSON");
 			} else {
 				var fieldValue = (TextAsset)skeletonJSON.objectReferenceValue;
-				if (!SpineEditorUtilities.AssetUtility.IsSpineData(fieldValue)) {
+				if (!AssetUtility.IsSpineData(fieldValue)) {
 					warnings.Add("Skeleton data file is not a valid Spine JSON or binary file.");
 				} else {
 					#if SPINE_TK2D
@@ -631,7 +631,7 @@ namespace Spine.Unity.Editor {
 						} else {
 							List<string> missingPaths = null;
 							if (atlasAssets.arraySize > 0) {
-								missingPaths = SpineEditorUtilities.AssetUtility.GetRequiredAtlasRegions(AssetDatabase.GetAssetPath(skeletonJSON.objectReferenceValue));
+								missingPaths = AssetUtility.GetRequiredAtlasRegions(AssetDatabase.GetAssetPath(skeletonJSON.objectReferenceValue));
 
 								foreach (var atlas in atlasList) {
 									for (int i = 0; i < missingPaths.Count; i++) {
@@ -661,7 +661,7 @@ namespace Spine.Unity.Editor {
 		}
 
 		void DoReimport () {
-			SpineEditorUtilities.AssetUtility.ImportSpineContent(new [] { AssetDatabase.GetAssetPath(skeletonJSON.objectReferenceValue) }, true);
+			AssetUtility.ImportSpineContent(new [] { AssetDatabase.GetAssetPath(skeletonJSON.objectReferenceValue) }, true);
 			preview.Clear();
 			InitializeEditor();
 			EditorUtility.SetDirty(targetSkeletonDataAsset);
@@ -831,7 +831,7 @@ namespace Spine.Unity.Editor {
 
 				if (previewGameObject == null) {
 					try {
-						previewGameObject = SpineEditorUtilities.EditorInstantiation.InstantiateSkeletonAnimation(skeletonDataAsset, skinName).gameObject;
+						previewGameObject = EditorInstantiation.InstantiateSkeletonAnimation(skeletonDataAsset, skinName).gameObject;
 
 						if (previewGameObject != null) {
 							previewGameObject.hideFlags = HideFlags.HideAndDontSave;

+ 1 - 1
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SpineAtlasAssetInspector.cs

@@ -92,7 +92,7 @@ namespace Spine.Unity.Editor {
 				string bakedDirPath = Path.Combine(atlasAssetDirPath, atlasAsset.name);
 				for (int i = 0; i < regions.Count; i++) {
 					AtlasRegion region = regions[i];
-					string bakedPrefabPath = Path.Combine(bakedDirPath, SpineEditorUtilities.AssetUtility.GetPathSafeRegionName(region) + ".prefab").Replace("\\", "/");
+					string bakedPrefabPath = Path.Combine(bakedDirPath, AssetUtility.GetPathSafeRegionName(region) + ".prefab").Replace("\\", "/");
 					GameObject prefab = (GameObject)AssetDatabase.LoadAssetAtPath(bakedPrefabPath, typeof(GameObject));
 					baked.Add(prefab != null);
 					bakedObjects.Add(prefab);

+ 1 - 1
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoneFollowerGraphicInspector.cs

@@ -47,7 +47,7 @@ namespace Spine.Unity.Editor {
 		[MenuItem ("CONTEXT/SkeletonGraphic/Add BoneFollower GameObject")]
 		static void AddBoneFollowerGameObject (MenuCommand cmd) {
 			var skeletonGraphic = cmd.context as SkeletonGraphic;
-			var go = SpineEditorUtilities.EditorInstantiation.NewGameObject("BoneFollower", typeof(RectTransform));
+			var go = EditorInstantiation.NewGameObject("BoneFollower", typeof(RectTransform));
 			var t = go.transform;
 			t.SetParent(skeletonGraphic.transform);
 			t.localPosition = Vector3.zero;

+ 1 - 1
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoneFollowerInspector.cs

@@ -45,7 +45,7 @@ namespace Spine.Unity.Editor {
 		[MenuItem ("CONTEXT/SkeletonRenderer/Add BoneFollower GameObject")]
 		static void AddBoneFollowerGameObject (MenuCommand cmd) {
 			var skeletonRenderer = cmd.context as SkeletonRenderer;
-			var go = SpineEditorUtilities.EditorInstantiation.NewGameObject("New BoneFollower");
+			var go = EditorInstantiation.NewGameObject("New BoneFollower");
 			var t = go.transform;
 			t.SetParent(skeletonRenderer.transform);
 			t.localPosition = Vector3.zero;

+ 1 - 1
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoundingBoxFollowerInspector.cs

@@ -191,7 +191,7 @@ namespace Spine.Unity.Editor {
 		#endregion
 
 		static GameObject AddBoundingBoxFollowerChild (SkeletonRenderer sr, BoundingBoxFollower original = null) {
-			var go = SpineEditorUtilities.EditorInstantiation.NewGameObject("BoundingBoxFollower");
+			var go = EditorInstantiation.NewGameObject("BoundingBoxFollower");
 			go.transform.SetParent(sr.transform, false);
 			var newFollower = go.AddComponent<BoundingBoxFollower>();
 

+ 1 - 1
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/PointFollowerInspector.cs

@@ -46,7 +46,7 @@ namespace Spine.Unity.Editor {
 		[MenuItem("CONTEXT/SkeletonRenderer/Add PointFollower GameObject")]
 		static void AddBoneFollowerGameObject (MenuCommand cmd) {
 			var skeletonRenderer = cmd.context as SkeletonRenderer;
-			var go = SpineEditorUtilities.EditorInstantiation.NewGameObject("PointFollower");
+			var go = EditorInstantiation.NewGameObject("PointFollower");
 			var t = go.transform;
 			t.SetParent(skeletonRenderer.transform);
 			t.localPosition = Vector3.zero;

+ 1 - 1
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicInspector.cs

@@ -205,7 +205,7 @@ namespace Spine.Unity.Editor {
 		}
 
 		static GameObject NewSkeletonGraphicGameObject (string gameObjectName) {
-			var go = SpineEditorUtilities.EditorInstantiation.NewGameObject(gameObjectName, typeof(RectTransform), typeof(CanvasRenderer), typeof(SkeletonGraphic));
+			var go = EditorInstantiation.NewGameObject(gameObjectName, typeof(RectTransform), typeof(CanvasRenderer), typeof(SkeletonGraphic));
 			var graphic = go.GetComponent<SkeletonGraphic>();
 			graphic.material = SkeletonGraphicInspector.DefaultSkeletonGraphicMaterial;
 			return go;

+ 2 - 2
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Menus.cs

@@ -36,12 +36,12 @@ namespace Spine.Unity.Editor {
 	public static class Menus {
 		[MenuItem("GameObject/Spine/SkeletonRenderer", false, 10)]
 		static public void CreateSkeletonRendererGameObject () {
-			SpineEditorUtilities.EditorInstantiation.InstantiateEmptySpineGameObject<SkeletonRenderer>("New SkeletonRenderer");
+			EditorInstantiation.InstantiateEmptySpineGameObject<SkeletonRenderer>("New SkeletonRenderer");
 		}
 
 		[MenuItem("GameObject/Spine/SkeletonAnimation", false, 10)]
 		static public void CreateSkeletonAnimationGameObject () {
-			SpineEditorUtilities.EditorInstantiation.InstantiateEmptySpineGameObject<SkeletonAnimation>("New SkeletonAnimation");
+			EditorInstantiation.InstantiateEmptySpineGameObject<SkeletonAnimation>("New SkeletonAnimation");
 		}
 	}
 }

+ 1051 - 0
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/AssetUtility.cs

@@ -0,0 +1,1051 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated May 1, 2019. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2019, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS
+ * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+#pragma warning disable 0219
+
+#define SPINE_SKELETONMECANIM
+
+#if UNITY_2017_2_OR_NEWER
+#define NEWPLAYMODECALLBACKS
+#endif
+
+#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
+#define NEW_PREFAB_SYSTEM
+#endif
+
+#if UNITY_2018 || UNITY_2019 || UNITY_2018_3_OR_NEWER
+#define NEWHIERARCHYWINDOWCALLBACKS
+#endif
+
+#if UNITY_2018_3_OR_NEWER
+#define NEW_PREFERENCES_SETTINGS_PROVIDER
+#endif
+
+#if UNITY_2019_1_OR_NEWER
+#define NEW_TIMELINE_AS_PACKAGE
+#endif
+
+using UnityEngine;
+using UnityEditor;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Linq;
+using System.Reflection;
+using System.Globalization;
+
+namespace Spine.Unity.Editor {
+	
+	public static class AssetUtility {
+		public const string SkeletonDataSuffix = "_SkeletonData";
+		public const string AtlasSuffix = "_Atlas";
+
+		static readonly int[][] compatibleBinaryVersions = { new[] { 3, 8, 0 } };
+		static readonly int[][] compatibleJsonVersions = { new[] { 3, 8, 0 } };
+		//static bool isFixVersionRequired = false;
+
+		/// HACK: This list keeps the asset reference temporarily during importing.
+		///
+		/// In cases of very large projects/sufficient RAM pressure, when AssetDatabase.SaveAssets is called,
+		/// Unity can mistakenly unload assets whose references are only on the stack.
+		/// This leads to MissingReferenceException and other errors.
+		public static readonly List<ScriptableObject> protectFromStackGarbageCollection = new List<ScriptableObject>();
+		public static HashSet<string> assetsImportedInWrongState = new HashSet<string>();
+
+		public static void HandleOnPostprocessAllAssets (string[] imported) {
+			// In case user used "Assets -> Reimport All", during the import process,
+			// asset database is not initialized until some point. During that period,
+			// all attempts to load any assets using API (i.e. AssetDatabase.LoadAssetAtPath)
+			// will return null, and as result, assets won't be loaded even if they actually exists,
+			// which may lead to numerous importing errors.
+			// This situation also happens if Library folder is deleted from the project, which is a pretty
+			// common case, since when using version control systems, the Library folder must be excluded.
+			//
+			// So to avoid this, in case asset database is not available, we delay loading the assets
+			// until next time.
+			//
+			// Unity *always* reimports some internal assets after the process is done, so this method
+			// is always called once again in a state when asset database is available.
+			//
+			// Checking whether AssetDatabase is initialized is done by attempting to load
+			// a known "marker" asset that should always be available. Failing to load this asset
+			// means that AssetDatabase is not initialized.
+			AssetUtility.assetsImportedInWrongState.UnionWith(imported);
+			if (AssetDatabaseAvailabilityDetector.IsAssetDatabaseAvailable()) {
+				string[] combinedAssets = AssetUtility.assetsImportedInWrongState.ToArray();
+				AssetUtility.assetsImportedInWrongState.Clear();
+				AssetUtility.ImportSpineContent(combinedAssets);
+			}
+		}
+
+#region Match SkeletonData with Atlases
+		static readonly AttachmentType[] AtlasTypes = { AttachmentType.Region, AttachmentType.Linkedmesh, AttachmentType.Mesh };
+
+		public static List<string> GetRequiredAtlasRegions (string skeletonDataPath) {
+			List<string> requiredPaths = new List<string>();
+
+			if (skeletonDataPath.Contains(".skel")) {
+				AddRequiredAtlasRegionsFromBinary(skeletonDataPath, requiredPaths);
+				return requiredPaths;
+			}
+
+			TextReader reader = null;
+			TextAsset spineJson = AssetDatabase.LoadAssetAtPath<TextAsset>(skeletonDataPath);
+			Dictionary<string, object> root = null;
+			try {
+				if (spineJson != null) {
+					reader = new StringReader(spineJson.text);
+				}
+				else {
+					// On a "Reimport All" the order of imports can be wrong, thus LoadAssetAtPath() above could return null.
+					// as a workaround, we provide a fallback reader.
+					reader = new StreamReader(skeletonDataPath);
+				}
+				root = Json.Deserialize(reader) as Dictionary<string, object>;
+			}
+			finally {
+				if (reader != null)
+					reader.Dispose();
+			}
+
+			if (root == null || !root.ContainsKey("skins"))
+				return requiredPaths;
+
+			foreach (Dictionary<string, object> skinMap in (List<object>)root["skins"]) {
+				if (!skinMap.ContainsKey("attachments"))
+					continue;
+				foreach (var slot in (Dictionary<string, object>)skinMap["attachments"]) {
+					foreach (var attachment in ((Dictionary<string, object>)slot.Value)) {
+						var data = ((Dictionary<string, object>)attachment.Value);
+
+						// Ignore non-atlas-requiring types.
+						if (data.ContainsKey("type")) {
+							AttachmentType attachmentType;
+							string typeString = (string)data["type"];
+							try {
+								attachmentType = (AttachmentType)System.Enum.Parse(typeof(AttachmentType), typeString, true);
+							} catch (System.ArgumentException e) {
+								// For more info, visit: http://esotericsoftware.com/forum/Spine-editor-and-runtime-version-management-6534
+								Debug.LogWarning(string.Format("Unidentified Attachment type: \"{0}\". Skeleton may have been exported from an incompatible Spine version.", typeString));
+								throw e;
+							}
+
+							if (!AtlasTypes.Contains(attachmentType))
+								continue;
+						}
+
+						if (data.ContainsKey("path"))
+							requiredPaths.Add((string)data["path"]);
+						else if (data.ContainsKey("name"))
+							requiredPaths.Add((string)data["name"]);
+						else
+							requiredPaths.Add(attachment.Key);
+					}
+				}
+			}
+
+			return requiredPaths;
+		}
+
+		internal static void AddRequiredAtlasRegionsFromBinary (string skeletonDataPath, List<string> requiredPaths) {
+			SkeletonBinary binary = new SkeletonBinary(new AtlasRequirementLoader(requiredPaths));
+			Stream input = null;
+			TextAsset data = AssetDatabase.LoadAssetAtPath<TextAsset>(skeletonDataPath);
+			try {
+				if (data != null) {
+					input = new MemoryStream(data.bytes);
+				}
+				else {
+					// On a "Reimport All" the order of imports can be wrong, thus LoadAssetAtPath() above could return null.
+					// as a workaround, we provide a fallback reader.
+					input = File.Open(skeletonDataPath, FileMode.Open, FileAccess.Read);
+				}
+				binary.ReadSkeletonData(input);
+			}
+			finally {
+				if (input != null)
+					input.Dispose();
+			}
+			binary = null;
+		}
+
+		internal static AtlasAssetBase GetMatchingAtlas (List<string> requiredPaths, List<AtlasAssetBase> atlasAssets) {
+			AtlasAssetBase atlasAssetMatch = null;
+
+			foreach (AtlasAssetBase a in atlasAssets) {
+				Atlas atlas = a.GetAtlas();
+				bool failed = false;
+				foreach (string regionPath in requiredPaths) {
+					if (atlas.FindRegion(regionPath) == null) {
+						failed = true;
+						break;
+					}
+				}
+
+				if (!failed) {
+					atlasAssetMatch = a;
+					break;
+				}
+			}
+
+			return atlasAssetMatch;
+		}
+
+		public class AtlasRequirementLoader : AttachmentLoader {
+			List<string> requirementList;
+
+			public AtlasRequirementLoader (List<string> requirementList) {
+				this.requirementList = requirementList;
+			}
+
+			public RegionAttachment NewRegionAttachment (Skin skin, string name, string path) {
+				requirementList.Add(path);
+				return new RegionAttachment(name);
+			}
+
+			public MeshAttachment NewMeshAttachment (Skin skin, string name, string path) {
+				requirementList.Add(path);
+				return new MeshAttachment(name);
+			}
+
+			public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name) {
+				return new BoundingBoxAttachment(name);
+			}
+
+			public PathAttachment NewPathAttachment (Skin skin, string name) {
+				return new PathAttachment(name);
+			}
+
+			public PointAttachment NewPointAttachment (Skin skin, string name) {
+				return new PointAttachment(name);
+			}
+
+			public ClippingAttachment NewClippingAttachment (Skin skin, string name) {
+				return new ClippingAttachment(name);
+			}
+		}
+#endregion
+
+		public static void ImportSpineContent (string[] imported, bool reimport = false) {
+			var atlasPaths = new List<string>();
+			var imagePaths = new List<string>();
+			var skeletonPaths = new List<string>();
+
+			foreach (string str in imported) {
+				string extension = Path.GetExtension(str).ToLower();
+				switch (extension) {
+					case ".atlas":
+						if (SpineEditorUtilities.Preferences.atlasTxtImportWarning) {
+							Debug.LogWarningFormat("`{0}` : If this file is a Spine atlas, please change its extension to `.atlas.txt`. This is to allow Unity to recognize it and avoid filename collisions. You can also set this file extension when exporting from the Spine editor.", str);
+						}
+						break;
+					case ".txt":
+						if (str.EndsWith(".atlas.txt", System.StringComparison.Ordinal))
+							atlasPaths.Add(str);
+						break;
+					case ".png":
+					case ".jpg":
+						imagePaths.Add(str);
+						break;
+					case ".json":
+						var jsonAsset = AssetDatabase.LoadAssetAtPath<TextAsset>(str);
+						if (jsonAsset != null && IsSpineData(jsonAsset))
+							skeletonPaths.Add(str);
+						break;
+					case ".bytes":
+						if (str.ToLower().EndsWith(".skel.bytes", System.StringComparison.Ordinal)) {
+							if (IsSpineData(AssetDatabase.LoadAssetAtPath<TextAsset>(str)))
+								skeletonPaths.Add(str);
+						}
+						break;
+				}
+			}
+
+			// Import atlases first.
+			var atlases = new List<AtlasAssetBase>();
+			foreach (string ap in atlasPaths) {
+				if (ap.StartsWith("Packages"))
+					continue;
+				TextAsset atlasText = AssetDatabase.LoadAssetAtPath<TextAsset>(ap);
+				AtlasAssetBase atlas = IngestSpineAtlas(atlasText);
+				atlases.Add(atlas);
+			}
+
+			// Import skeletons and match them with atlases.
+			bool abortSkeletonImport = false;
+			foreach (string skeletonPath in skeletonPaths) {
+				if (skeletonPath.StartsWith("Packages"))
+					continue;
+				if (!reimport && CheckForValidSkeletonData(skeletonPath)) {
+					ReloadSkeletonData(skeletonPath);
+					continue;
+				}
+
+				string dir = Path.GetDirectoryName(skeletonPath);
+
+#if SPINE_TK2D
+				IngestSpineProject(AssetDatabase.LoadAssetAtPath<TextAsset>(skeletonPath), null);
+#else
+				var localAtlases = FindAtlasesAtPath(dir);
+				var requiredPaths = GetRequiredAtlasRegions(skeletonPath);
+				var atlasMatch = GetMatchingAtlas(requiredPaths, localAtlases);
+				if (atlasMatch != null || requiredPaths.Count == 0) {
+					IngestSpineProject(AssetDatabase.LoadAssetAtPath<TextAsset>(skeletonPath), atlasMatch);
+				} else {
+					SkeletonImportDialog(skeletonPath, localAtlases, requiredPaths, ref abortSkeletonImport);
+				}
+
+				if (abortSkeletonImport)
+					break;
+#endif
+			}
+
+			SkeletonDataAssetInspector[] skeletonDataInspectors = Resources.FindObjectsOfTypeAll<SkeletonDataAssetInspector>();
+			foreach (var inspector in skeletonDataInspectors) {
+				inspector.UpdateSkeletonData();
+			}
+				
+			// Any post processing of images
+
+			// Under some circumstances (e.g. on first import) SkeletonGraphic objects 
+			// have their skeletonGraphic.skeletonDataAsset reference corrupted
+			// by the instance of the ScriptableObject being destroyed but still assigned.
+			// Here we restore broken skeletonGraphic.skeletonDataAsset references.
+			var skeletonGraphicObjects = Resources.FindObjectsOfTypeAll(typeof(SkeletonGraphic)) as SkeletonGraphic[];
+			foreach (var skeletonGraphic in skeletonGraphicObjects) {
+					
+				if (skeletonGraphic.skeletonDataAsset == null) {
+					var skeletonGraphicID = skeletonGraphic.GetInstanceID();
+					if (SpineEditorUtilities.DataReloadHandler.savedSkeletonDataAssetAtSKeletonGraphicID.ContainsKey(skeletonGraphicID)) {
+						string assetPath = SpineEditorUtilities.DataReloadHandler.savedSkeletonDataAssetAtSKeletonGraphicID[skeletonGraphicID];
+						skeletonGraphic.skeletonDataAsset = (SkeletonDataAsset)AssetDatabase.LoadAssetAtPath<SkeletonDataAsset>(assetPath);
+					}
+				}
+			}
+		}
+			
+		static void ReloadSkeletonData (string skeletonJSONPath) {
+			string dir = Path.GetDirectoryName(skeletonJSONPath);
+			TextAsset textAsset = AssetDatabase.LoadAssetAtPath<TextAsset>(skeletonJSONPath);
+			DirectoryInfo dirInfo = new DirectoryInfo(dir);
+			FileInfo[] files = dirInfo.GetFiles("*.asset");
+
+			foreach (var f in files) {
+				string localPath = dir + "/" + f.Name;
+				var obj = AssetDatabase.LoadAssetAtPath(localPath, typeof(Object));
+				var skeletonDataAsset = obj as SkeletonDataAsset;
+				if (skeletonDataAsset != null) {
+					if (skeletonDataAsset.skeletonJSON == textAsset) {
+						if (Selection.activeObject == skeletonDataAsset)
+							Selection.activeObject = null;
+
+						Debug.LogFormat("Changes to '{0}' detected. Clearing SkeletonDataAsset: {1}", skeletonJSONPath, localPath);
+						skeletonDataAsset.Clear();
+
+						string guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(skeletonDataAsset));
+						string lastHash = EditorPrefs.GetString(guid + "_hash");
+
+						// For some weird reason sometimes Unity loses the internal Object pointer,
+						// and as a result, all comparisons with null returns true.
+						// But the C# wrapper is still alive, so we can "restore" the object
+						// by reloading it from its Instance ID.
+						AtlasAssetBase[] skeletonDataAtlasAssets = skeletonDataAsset.atlasAssets;
+						if (skeletonDataAtlasAssets != null) {
+							for (int i = 0; i < skeletonDataAtlasAssets.Length; i++) {
+								if (!ReferenceEquals(null, skeletonDataAtlasAssets[i]) &&
+									skeletonDataAtlasAssets[i].Equals(null) &&
+									skeletonDataAtlasAssets[i].GetInstanceID() != 0
+								) {
+									skeletonDataAtlasAssets[i] = EditorUtility.InstanceIDToObject(skeletonDataAtlasAssets[i].GetInstanceID()) as AtlasAssetBase;
+								}
+							}
+						}
+
+						SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(true);
+						string currentHash = skeletonData != null ? skeletonData.Hash : null;
+
+#if SPINE_SKELETONMECANIM
+						if (currentHash == null || lastHash != currentHash)
+							SkeletonBaker.UpdateMecanimClips(skeletonDataAsset);
+#endif
+
+						// if (currentHash == null || lastHash != currentHash)
+						// Do any upkeep on synchronized assets
+
+						if (currentHash != null)
+							EditorPrefs.SetString(guid + "_hash", currentHash);
+					}
+					SpineEditorUtilities.DataReloadHandler.ReloadSceneSkeletonComponents(skeletonDataAsset);
+				}
+			}
+		}
+
+#region Import Atlases
+		static List<AtlasAssetBase> FindAtlasesAtPath (string path) {
+			List<AtlasAssetBase> arr = new List<AtlasAssetBase>();
+			DirectoryInfo dir = new DirectoryInfo(path);
+			FileInfo[] assetInfoArr = dir.GetFiles("*.asset");
+
+			int subLen = Application.dataPath.Length - 6;
+			foreach (var f in assetInfoArr) {
+				string assetRelativePath = f.FullName.Substring(subLen, f.FullName.Length - subLen).Replace("\\", "/");
+				Object obj = AssetDatabase.LoadAssetAtPath(assetRelativePath, typeof(AtlasAssetBase));
+				if (obj != null)
+					arr.Add(obj as AtlasAssetBase);
+			}
+
+			return arr;
+		}
+
+		static AtlasAssetBase IngestSpineAtlas (TextAsset atlasText) {
+			if (atlasText == null) {
+				Debug.LogWarning("Atlas source cannot be null!");
+				return null;
+			}
+
+			string primaryName = Path.GetFileNameWithoutExtension(atlasText.name).Replace(".atlas", "");
+			string assetPath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(atlasText));
+
+			string atlasPath = assetPath + "/" + primaryName + AtlasSuffix + ".asset";
+
+			SpineAtlasAsset atlasAsset = (SpineAtlasAsset)AssetDatabase.LoadAssetAtPath(atlasPath, typeof(SpineAtlasAsset));
+
+			List<Material> vestigialMaterials = new List<Material>();
+
+			if (atlasAsset == null)
+				atlasAsset = SpineAtlasAsset.CreateInstance<SpineAtlasAsset>();
+			else {
+				foreach (Material m in atlasAsset.materials)
+					vestigialMaterials.Add(m);
+			}
+
+			protectFromStackGarbageCollection.Add(atlasAsset);
+			atlasAsset.atlasFile = atlasText;
+
+			//strip CR
+			string atlasStr = atlasText.text;
+			atlasStr = atlasStr.Replace("\r", "");
+
+			string[] atlasLines = atlasStr.Split('\n');
+			List<string> pageFiles = new List<string>();
+			for (int i = 0; i < atlasLines.Length - 1; i++) {
+				if (atlasLines[i].Trim().Length == 0)
+					pageFiles.Add(atlasLines[i + 1].Trim());
+			}
+
+			var populatingMaterials = new List<Material>(pageFiles.Count);//atlasAsset.materials = new Material[pageFiles.Count];
+
+			for (int i = 0; i < pageFiles.Count; i++) {
+				string texturePath = assetPath + "/" + pageFiles[i];
+				Texture2D texture = (Texture2D)AssetDatabase.LoadAssetAtPath(texturePath, typeof(Texture2D));
+
+				if (SpineEditorUtilities.Preferences.setTextureImporterSettings) {
+					TextureImporter texImporter = (TextureImporter)TextureImporter.GetAtPath(texturePath);
+					if (texImporter == null) {
+						Debug.LogWarning(string.Format("{0} ::: Texture asset \"{1}\" not found. Skipping. Please check your atlas file for renamed files.", atlasAsset.name, texturePath));
+						continue;
+					}
+
+					texImporter.textureCompression = TextureImporterCompression.Uncompressed;
+					texImporter.alphaSource = TextureImporterAlphaSource.FromInput;
+					texImporter.mipmapEnabled = false;
+					texImporter.alphaIsTransparency = false; // Prevent the texture importer from applying bleed to the transparent parts for PMA.
+					texImporter.spriteImportMode = SpriteImportMode.None;
+					texImporter.maxTextureSize = 2048;
+
+					EditorUtility.SetDirty(texImporter);
+					AssetDatabase.ImportAsset(texturePath);
+					AssetDatabase.SaveAssets();
+				}
+
+				string pageName = Path.GetFileNameWithoutExtension(pageFiles[i]);
+
+				//because this looks silly
+				if (pageName == primaryName && pageFiles.Count == 1)
+					pageName = "Material";
+
+				string materialPath = assetPath + "/" + primaryName + "_" + pageName + ".mat";
+				Material mat = (Material)AssetDatabase.LoadAssetAtPath(materialPath, typeof(Material));
+
+				if (mat == null) {
+					mat = new Material(Shader.Find(SpineEditorUtilities.Preferences.defaultShader));
+					AssetDatabase.CreateAsset(mat, materialPath);
+				} else {
+					vestigialMaterials.Remove(mat);
+				}
+
+				mat.mainTexture = texture;
+				EditorUtility.SetDirty(mat);
+				AssetDatabase.SaveAssets();
+
+				populatingMaterials.Add(mat); //atlasAsset.materials[i] = mat;
+			}
+
+			atlasAsset.materials = populatingMaterials.ToArray();
+
+			for (int i = 0; i < vestigialMaterials.Count; i++)
+				AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(vestigialMaterials[i]));
+
+			if (AssetDatabase.GetAssetPath(atlasAsset) == "")
+				AssetDatabase.CreateAsset(atlasAsset, atlasPath);
+			else
+				atlasAsset.Clear();
+
+			EditorUtility.SetDirty(atlasAsset);
+			AssetDatabase.SaveAssets();
+
+			if (pageFiles.Count != atlasAsset.materials.Length)
+				Debug.LogWarning(string.Format("{0} :: Not all atlas pages were imported. If you rename your image files, please make sure you also edit the filenames specified in the atlas file.", atlasAsset.name));
+			else
+				Debug.Log(string.Format("{0} :: Imported with {1} material", atlasAsset.name, atlasAsset.materials.Length));
+
+			// Iterate regions and bake marked.
+			Atlas atlas = atlasAsset.GetAtlas();
+			if (atlas != null) {
+				FieldInfo field = typeof(Atlas).GetField("regions", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.NonPublic);
+				var regions = (List<AtlasRegion>)field.GetValue(atlas);
+				string atlasAssetPath = AssetDatabase.GetAssetPath(atlasAsset);
+				string atlasAssetDirPath = Path.GetDirectoryName(atlasAssetPath);
+				string bakedDirPath = Path.Combine(atlasAssetDirPath, atlasAsset.name);
+
+				bool hasBakedRegions = false;
+				for (int i = 0; i < regions.Count; i++) {
+					AtlasRegion region = regions[i];
+					string bakedPrefabPath = Path.Combine(bakedDirPath, AssetUtility.GetPathSafeName(region.name) + ".prefab").Replace("\\", "/");
+					GameObject prefab = (GameObject)AssetDatabase.LoadAssetAtPath(bakedPrefabPath, typeof(GameObject));
+					if (prefab != null) {
+						SkeletonBaker.BakeRegion(atlasAsset, region, false);
+						hasBakedRegions = true;
+					}
+				}
+
+				if (hasBakedRegions) {
+					AssetDatabase.SaveAssets();
+					AssetDatabase.Refresh();
+				}
+			}
+
+			protectFromStackGarbageCollection.Remove(atlasAsset);
+			return (AtlasAssetBase)AssetDatabase.LoadAssetAtPath(atlasPath, typeof(AtlasAssetBase));
+		}
+#endregion
+
+#region Import SkeletonData (json or binary)
+		internal static SkeletonDataAsset IngestSpineProject (TextAsset spineJson, params AtlasAssetBase[] atlasAssets) {
+			string primaryName = Path.GetFileNameWithoutExtension(spineJson.name);
+			string assetPath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(spineJson));
+			string filePath = assetPath + "/" + primaryName + SkeletonDataSuffix + ".asset";
+
+#if SPINE_TK2D
+			if (spineJson != null) {
+				SkeletonDataAsset skeletonDataAsset = (SkeletonDataAsset)AssetDatabase.LoadAssetAtPath(filePath, typeof(SkeletonDataAsset));
+				if (skeletonDataAsset == null) {
+					skeletonDataAsset = SkeletonDataAsset.CreateInstance<SkeletonDataAsset>();
+					skeletonDataAsset.skeletonJSON = spineJson;
+					skeletonDataAsset.fromAnimation = new string[0];
+					skeletonDataAsset.toAnimation = new string[0];
+					skeletonDataAsset.duration = new float[0];
+					skeletonDataAsset.defaultMix = SpineEditorUtilities.Preferences.defaultMix;
+					skeletonDataAsset.scale = SpineEditorUtilities.Preferences.defaultScale;
+
+					AssetDatabase.CreateAsset(skeletonDataAsset, filePath);
+					AssetDatabase.SaveAssets();
+				} else {
+					skeletonDataAsset.Clear();
+					skeletonDataAsset.GetSkeletonData(true);
+				}
+
+				return skeletonDataAsset;
+			} else {
+				EditorUtility.DisplayDialog("Error!", "Tried to ingest null Spine data.", "OK");
+				return null;
+			}
+
+#else
+			if (spineJson != null && atlasAssets != null) {
+				SkeletonDataAsset skeletonDataAsset = (SkeletonDataAsset)AssetDatabase.LoadAssetAtPath(filePath, typeof(SkeletonDataAsset));
+				if (skeletonDataAsset == null) {
+					skeletonDataAsset = ScriptableObject.CreateInstance<SkeletonDataAsset>();
+					{
+						skeletonDataAsset.atlasAssets = atlasAssets;
+						skeletonDataAsset.skeletonJSON = spineJson;
+						skeletonDataAsset.defaultMix = SpineEditorUtilities.Preferences.defaultMix;
+						skeletonDataAsset.scale = SpineEditorUtilities.Preferences.defaultScale;
+					}
+
+					AssetDatabase.CreateAsset(skeletonDataAsset, filePath);
+					AssetDatabase.SaveAssets();
+				} else {
+					skeletonDataAsset.atlasAssets = atlasAssets;
+					skeletonDataAsset.Clear();
+					skeletonDataAsset.GetSkeletonData(true);
+				}
+
+				return skeletonDataAsset;
+			} else {
+				EditorUtility.DisplayDialog("Error!", "Must specify both Spine JSON and AtlasAsset array", "OK");
+				return null;
+			}
+#endif
+		}
+#endregion
+
+#region Spine Skeleton Data File Validation
+		public static bool CheckForValidSkeletonData (string skeletonJSONPath) {
+			string dir = Path.GetDirectoryName(skeletonJSONPath);
+			TextAsset textAsset = AssetDatabase.LoadAssetAtPath<TextAsset>(skeletonJSONPath);
+			DirectoryInfo dirInfo = new DirectoryInfo(dir);
+			FileInfo[] files = dirInfo.GetFiles("*.asset");
+
+			foreach (var path in files) {
+				string localPath = dir + "/" + path.Name;
+				var obj = AssetDatabase.LoadAssetAtPath(localPath, typeof(Object));
+				var skeletonDataAsset = obj as SkeletonDataAsset;
+				if (skeletonDataAsset != null && skeletonDataAsset.skeletonJSON == textAsset)
+					return true;
+			}
+
+			return false;
+		}
+
+		public static bool IsSpineData (TextAsset asset) {
+			if (asset == null)
+				return false;
+
+			bool isSpineData = false;
+			string rawVersion = null;
+
+			int[][] compatibleVersions;
+			if (asset.name.Contains(".skel")) {
+				try {
+					using (var memStream = new MemoryStream(asset.bytes)) {
+						rawVersion = SkeletonBinary.GetVersionString(memStream);
+					}
+					isSpineData = !(string.IsNullOrEmpty(rawVersion));
+					compatibleVersions = compatibleBinaryVersions;
+				} catch (System.Exception e) {
+					Debug.LogErrorFormat("Failed to read '{0}'. It is likely not a binary Spine SkeletonData file.\n{1}", asset.name, e);
+					return false;
+				}
+			} else {
+				object obj = Json.Deserialize(new StringReader(asset.text));
+				if (obj == null) {
+					Debug.LogErrorFormat("'{0}' is not valid JSON.", asset.name);
+					return false;
+				}
+
+				var root = obj as Dictionary<string, object>;
+				if (root == null) {
+					Debug.LogError("Parser returned an incorrect type.");
+					return false;
+				}
+
+				isSpineData = root.ContainsKey("skeleton");
+				if (isSpineData) {
+					var skeletonInfo = (Dictionary<string, object>)root["skeleton"];
+					object jv;
+					skeletonInfo.TryGetValue("spine", out jv);
+					rawVersion = jv as string;
+				}
+
+				compatibleVersions = compatibleJsonVersions;
+			}
+
+			// Version warning
+			if (isSpineData) {
+				string primaryRuntimeVersionDebugString = compatibleVersions[0][0] + "." + compatibleVersions[0][1];
+
+				if (string.IsNullOrEmpty(rawVersion)) {
+					Debug.LogWarningFormat("Skeleton '{0}' has no version information. It may be incompatible with your runtime version: spine-unity v{1}", asset.name, primaryRuntimeVersionDebugString);
+				} else {
+					string[] versionSplit = rawVersion.Split('.');
+					bool match = false;
+					foreach (var version in compatibleVersions) {
+						bool primaryMatch = version[0] == int.Parse(versionSplit[0], CultureInfo.InvariantCulture);
+						bool secondaryMatch = version[1] == int.Parse(versionSplit[1], CultureInfo.InvariantCulture);
+
+						// if (isFixVersionRequired) secondaryMatch &= version[2] <= int.Parse(jsonVersionSplit[2], CultureInfo.InvariantCulture);
+
+						if (primaryMatch && secondaryMatch) {
+							match = true;
+							break;
+						}
+					}
+
+					if (!match)
+						Debug.LogWarningFormat("Skeleton '{0}' (exported with Spine {1}) may be incompatible with your runtime version: spine-csharp v{2}", asset.name, rawVersion, primaryRuntimeVersionDebugString);
+				}
+			}
+
+			return isSpineData;
+		}
+#endregion
+
+#region Dialogs
+		public static void SkeletonImportDialog (string skeletonPath, List<AtlasAssetBase> localAtlases, List<string> requiredPaths, ref bool abortSkeletonImport) {
+			bool resolved = false;
+			while (!resolved) {
+
+				string filename = Path.GetFileNameWithoutExtension(skeletonPath);
+				int result = EditorUtility.DisplayDialogComplex(
+					string.Format("AtlasAsset for \"{0}\"", filename),
+					string.Format("Could not automatically set the AtlasAsset for \"{0}\".\n\n (You may resolve this manually later.)", filename),
+					"Resolve atlases...", "Import without atlases", "Stop importing"
+				);
+
+				switch (result) {
+					case -1:
+						//Debug.Log("Select Atlas");
+						AtlasAssetBase selectedAtlas = BrowseAtlasDialog(Path.GetDirectoryName(skeletonPath));
+						if (selectedAtlas != null) {
+							localAtlases.Clear();
+							localAtlases.Add(selectedAtlas);
+							var atlasMatch = AssetUtility.GetMatchingAtlas(requiredPaths, localAtlases);
+							if (atlasMatch != null) {
+								resolved = true;
+								AssetUtility.IngestSpineProject(AssetDatabase.LoadAssetAtPath<TextAsset>(skeletonPath), atlasMatch);
+							}
+						}
+						break;
+					case 0: // Resolve AtlasAssets...
+						var atlasList = MultiAtlasDialog(requiredPaths, Path.GetDirectoryName(skeletonPath), Path.GetFileNameWithoutExtension(skeletonPath));
+						if (atlasList != null)
+							AssetUtility.IngestSpineProject(AssetDatabase.LoadAssetAtPath<TextAsset>(skeletonPath), atlasList.ToArray());
+
+						resolved = true;
+						break;
+					case 1: // Import without atlas
+						Debug.LogWarning("Imported with missing atlases. Skeleton will not render: " + Path.GetFileName(skeletonPath));
+						AssetUtility.IngestSpineProject(AssetDatabase.LoadAssetAtPath<TextAsset>(skeletonPath), new AtlasAssetBase[] { });
+						resolved = true;
+						break;
+					case 2: // Stop importing all
+						abortSkeletonImport = true;
+						resolved = true;
+						break;
+				}
+			}
+		}
+
+		public static List<AtlasAssetBase> MultiAtlasDialog (List<string> requiredPaths, string initialDirectory, string filename = "") {
+			List<AtlasAssetBase> atlasAssets = new List<AtlasAssetBase>();
+			bool resolved = false;
+			string lastAtlasPath = initialDirectory;
+			while (!resolved) {
+
+				// Build dialog box message.
+				var missingRegions = new List<string>(requiredPaths);
+				var dialogText = new StringBuilder();
+				{
+					dialogText.AppendLine(string.Format("SkeletonDataAsset for \"{0}\"", filename));
+					dialogText.AppendLine("has missing regions.");
+					dialogText.AppendLine();
+					dialogText.AppendLine("Current Atlases:");
+
+					if (atlasAssets.Count == 0)
+						dialogText.AppendLine("\t--none--");
+
+					for (int i = 0; i < atlasAssets.Count; i++)
+						dialogText.AppendLine("\t" + atlasAssets[i].name);
+
+					dialogText.AppendLine();
+					dialogText.AppendLine("Missing Regions:");
+
+					foreach (var atlasAsset in atlasAssets) {
+						var atlas = atlasAsset.GetAtlas();
+						for (int i = 0; i < missingRegions.Count; i++) {
+							if (atlas.FindRegion(missingRegions[i]) != null) {
+								missingRegions.RemoveAt(i);
+								i--;
+							}
+						}
+					}
+
+					int n = missingRegions.Count;
+					if (n == 0)
+						break;
+
+					const int MaxListLength = 15;
+					for (int i = 0; (i < n && i < MaxListLength); i++)
+						dialogText.AppendLine(string.Format("\t {0}", missingRegions[i]));
+
+					if (n > MaxListLength)
+						dialogText.AppendLine(string.Format("\t... {0} more...", n - MaxListLength));
+				}
+
+				// Show dialog box.
+				int result = EditorUtility.DisplayDialogComplex(
+					"SkeletonDataAsset has missing Atlas.",
+					dialogText.ToString(),
+					"Browse Atlas...", "Import anyway", "Cancel import"
+				);
+
+				switch (result) {
+					case 0: // Browse...
+						AtlasAssetBase selectedAtlasAsset = BrowseAtlasDialog(lastAtlasPath);
+						if (selectedAtlasAsset != null) {
+							if (!atlasAssets.Contains(selectedAtlasAsset)) {
+								var atlas = selectedAtlasAsset.GetAtlas();
+								bool hasValidRegion = false;
+								foreach (string str in missingRegions) {
+									if (atlas.FindRegion(str) != null) {
+										hasValidRegion = true;
+										break;
+									}
+								}
+								atlasAssets.Add(selectedAtlasAsset);
+							}
+						}
+						break;
+					case 1: // Import anyway
+						resolved = true;
+						break;
+					case 2: // Cancel
+						atlasAssets = null;
+						resolved = true;
+						break;
+				}
+			}
+
+			return atlasAssets;
+		}
+
+		public static AtlasAssetBase BrowseAtlasDialog (string dirPath) {
+			string path = EditorUtility.OpenFilePanel("Select AtlasAsset...", dirPath, "asset");
+			if (path == "")
+				return null; // Canceled or closed by user.
+
+			int subLen = Application.dataPath.Length - 6;
+			string assetRelativePath = path.Substring(subLen, path.Length - subLen).Replace("\\", "/");
+
+			var obj = AssetDatabase.LoadAssetAtPath(assetRelativePath, typeof(AtlasAssetBase));
+			if (obj == null || !(obj is AtlasAssetBase)) {
+				Debug.Log("Chosen asset was not of type AtlasAssetBase");
+				return null;
+			}
+
+			return (AtlasAssetBase)obj;
+		}
+#endregion
+
+		public static string GetPathSafeName (string name) {
+			foreach (char c in System.IO.Path.GetInvalidFileNameChars()) { // Doesn't handle more obscure file name limitations.
+				name = name.Replace(c, '_');
+			}
+			return name;
+		}
+	}
+
+	public static class EditorInstantiation {
+		public delegate Component InstantiateDelegate (SkeletonDataAsset skeletonDataAsset);
+
+		public class SkeletonComponentSpawnType {
+			public string menuLabel;
+			public InstantiateDelegate instantiateDelegate;
+			public bool isUI;
+		}
+
+		internal static readonly List<SkeletonComponentSpawnType> additionalSpawnTypes = new List<SkeletonComponentSpawnType>();
+
+		public static void TryInitializeSkeletonRendererSettings (SkeletonRenderer skeletonRenderer, Skin skin = null) {
+			const string PMAShaderQuery = "Spine/Skeleton";
+			const string TintBlackShaderQuery = "Tint Black";
+
+			if (skeletonRenderer == null) return;
+			var skeletonDataAsset = skeletonRenderer.skeletonDataAsset;
+			if (skeletonDataAsset == null) return;
+
+			bool pmaVertexColors = false;
+			bool tintBlack = false;
+			foreach (AtlasAssetBase atlasAsset in skeletonDataAsset.atlasAssets) {
+				if (!pmaVertexColors) {
+					foreach (Material m in atlasAsset.Materials) {
+						if (m.shader.name.Contains(PMAShaderQuery)) {
+							pmaVertexColors = true;
+							break;
+						}
+					}
+				}
+
+				if (!tintBlack) {
+					foreach (Material m in atlasAsset.Materials) {
+						if (m.shader.name.Contains(TintBlackShaderQuery)) {
+							tintBlack = true;
+							break;
+						}
+					}
+				}
+			}
+
+			skeletonRenderer.pmaVertexColors = pmaVertexColors;
+			skeletonRenderer.tintBlack = tintBlack;
+			skeletonRenderer.zSpacing = SpineEditorUtilities.Preferences.defaultZSpacing;
+
+			var data = skeletonDataAsset.GetSkeletonData(false);
+			bool noSkins = data.DefaultSkin == null && (data.Skins == null || data.Skins.Count == 0); // Support attachmentless/skinless SkeletonData.
+			skin = skin ?? data.DefaultSkin ?? (noSkins ? null : data.Skins.Items[0]);
+			if (skin != null && skin != data.DefaultSkin) {
+				skeletonRenderer.initialSkinName = skin.Name;
+			}
+		}
+
+		public static SkeletonAnimation InstantiateSkeletonAnimation (SkeletonDataAsset skeletonDataAsset, string skinName, bool destroyInvalid = true) {
+			var skeletonData = skeletonDataAsset.GetSkeletonData(true);
+			var skin = skeletonData != null ? skeletonData.FindSkin(skinName) : null;
+			return InstantiateSkeletonAnimation(skeletonDataAsset, skin, destroyInvalid);
+		}
+
+		public static SkeletonAnimation InstantiateSkeletonAnimation (SkeletonDataAsset skeletonDataAsset, Skin skin = null, bool destroyInvalid = true) {
+			SkeletonData data = skeletonDataAsset.GetSkeletonData(true);
+
+			if (data == null) {
+				for (int i = 0; i < skeletonDataAsset.atlasAssets.Length; i++) {
+					string reloadAtlasPath = AssetDatabase.GetAssetPath(skeletonDataAsset.atlasAssets[i]);
+					skeletonDataAsset.atlasAssets[i] = (AtlasAssetBase)AssetDatabase.LoadAssetAtPath(reloadAtlasPath, typeof(AtlasAssetBase));
+				}
+				data = skeletonDataAsset.GetSkeletonData(false);
+			}
+
+			if (data == null) {
+				Debug.LogWarning("InstantiateSkeletonAnimation tried to instantiate a skeleton from an invalid SkeletonDataAsset.");
+				return null;
+			}
+
+			string spineGameObjectName = string.Format("Spine GameObject ({0})", skeletonDataAsset.name.Replace("_SkeletonData", ""));
+			GameObject go = EditorInstantiation.NewGameObject(spineGameObjectName, typeof(MeshFilter), typeof(MeshRenderer), typeof(SkeletonAnimation));
+			SkeletonAnimation newSkeletonAnimation = go.GetComponent<SkeletonAnimation>();
+			newSkeletonAnimation.skeletonDataAsset = skeletonDataAsset;
+			TryInitializeSkeletonRendererSettings(newSkeletonAnimation, skin);
+
+			// Initialize
+			try {
+				newSkeletonAnimation.Initialize(false);
+			} catch (System.Exception e) {
+				if (destroyInvalid) {
+					Debug.LogWarning("Editor-instantiated SkeletonAnimation threw an Exception. Destroying GameObject to prevent orphaned GameObject.");
+					GameObject.DestroyImmediate(go);
+				}
+				throw e;
+			}
+
+			newSkeletonAnimation.loop = SpineEditorUtilities.Preferences.defaultInstantiateLoop;
+			newSkeletonAnimation.skeleton.Update(0);
+			newSkeletonAnimation.state.Update(0);
+			newSkeletonAnimation.state.Apply(newSkeletonAnimation.skeleton);
+			newSkeletonAnimation.skeleton.UpdateWorldTransform();
+
+			return newSkeletonAnimation;
+		}
+			
+		/// <summary>Handles creating a new GameObject in the Unity Editor. This uses the new ObjectFactory API where applicable.</summary>
+		public static GameObject NewGameObject (string name) {
+			#if NEW_PREFAB_SYSTEM
+			return ObjectFactory.CreateGameObject(name);
+			#else
+			return new GameObject(name);
+			#endif
+		}
+
+		/// <summary>Handles creating a new GameObject in the Unity Editor. This uses the new ObjectFactory API where applicable.</summary>
+		public static GameObject NewGameObject (string name, params System.Type[] components) {
+			#if NEW_PREFAB_SYSTEM
+			return ObjectFactory.CreateGameObject(name, components);
+			#else
+			return new GameObject(name, components);
+			#endif
+		}
+
+		public static void InstantiateEmptySpineGameObject<T> (string name) where T : MonoBehaviour {
+			var parentGameObject = Selection.activeObject as GameObject;
+			var parentTransform = parentGameObject == null ? null : parentGameObject.transform;
+
+			var gameObject = EditorInstantiation.NewGameObject(name, typeof(T));
+			gameObject.transform.SetParent(parentTransform, false);
+			EditorUtility.FocusProjectWindow();
+			Selection.activeObject = gameObject;
+			EditorGUIUtility.PingObject(Selection.activeObject);
+		}
+
+#region SkeletonMecanim
+#if SPINE_SKELETONMECANIM
+		public static SkeletonMecanim InstantiateSkeletonMecanim (SkeletonDataAsset skeletonDataAsset, string skinName) {
+			return InstantiateSkeletonMecanim(skeletonDataAsset, skeletonDataAsset.GetSkeletonData(true).FindSkin(skinName));
+		}
+
+		public static SkeletonMecanim InstantiateSkeletonMecanim (SkeletonDataAsset skeletonDataAsset, Skin skin = null, bool destroyInvalid = true) {
+			SkeletonData data = skeletonDataAsset.GetSkeletonData(true);
+
+			if (data == null) {
+				for (int i = 0; i < skeletonDataAsset.atlasAssets.Length; i++) {
+					string reloadAtlasPath = AssetDatabase.GetAssetPath(skeletonDataAsset.atlasAssets[i]);
+					skeletonDataAsset.atlasAssets[i] = (AtlasAssetBase)AssetDatabase.LoadAssetAtPath(reloadAtlasPath, typeof(AtlasAssetBase));
+				}
+				data = skeletonDataAsset.GetSkeletonData(false);
+			}
+
+			if (data == null) {
+				Debug.LogWarning("InstantiateSkeletonMecanim tried to instantiate a skeleton from an invalid SkeletonDataAsset.");
+				return null;
+			}
+
+			string spineGameObjectName = string.Format("Spine Mecanim GameObject ({0})", skeletonDataAsset.name.Replace("_SkeletonData", ""));
+			GameObject go = EditorInstantiation.NewGameObject(spineGameObjectName, typeof(MeshFilter), typeof(MeshRenderer), typeof(Animator), typeof(SkeletonMecanim));
+
+			if (skeletonDataAsset.controller == null) {
+				SkeletonBaker.GenerateMecanimAnimationClips(skeletonDataAsset);
+				Debug.Log(string.Format("Mecanim controller was automatically generated and assigned for {0}", skeletonDataAsset.name));
+			}
+
+			go.GetComponent<Animator>().runtimeAnimatorController = skeletonDataAsset.controller;
+
+			SkeletonMecanim newSkeletonMecanim = go.GetComponent<SkeletonMecanim>();
+			newSkeletonMecanim.skeletonDataAsset = skeletonDataAsset;
+			TryInitializeSkeletonRendererSettings(newSkeletonMecanim, skin);
+
+			// Initialize
+			try {
+				newSkeletonMecanim.Initialize(false);
+			} catch (System.Exception e) {
+				if (destroyInvalid) {
+					Debug.LogWarning("Editor-instantiated SkeletonAnimation threw an Exception. Destroying GameObject to prevent orphaned GameObject.");
+					GameObject.DestroyImmediate(go);
+				}
+				throw e;
+			}
+
+			newSkeletonMecanim.skeleton.Update(0);
+			newSkeletonMecanim.skeleton.UpdateWorldTransform();
+			newSkeletonMecanim.LateUpdate();
+
+			return newSkeletonMecanim;
+		}
+#endif
+#endregion
+	}
+}

+ 12 - 0
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/AssetUtility.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 49377c72c37e2c149b106c260a241f3c
+timeCreated: 1563311043
+licenseType: Free
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 339 - 0
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/BuildSettings.cs

@@ -0,0 +1,339 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated May 1, 2019. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2019, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS
+ * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+#pragma warning disable 0219
+
+#define SPINE_SKELETONMECANIM
+
+#if UNITY_2017_2_OR_NEWER
+#define NEWPLAYMODECALLBACKS
+#endif
+
+#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
+#define NEW_PREFAB_SYSTEM
+#endif
+
+#if UNITY_2018 || UNITY_2019 || UNITY_2018_3_OR_NEWER
+#define NEWHIERARCHYWINDOWCALLBACKS
+#endif
+
+#if UNITY_2018_3_OR_NEWER
+#define NEW_PREFERENCES_SETTINGS_PROVIDER
+#endif
+
+#if UNITY_2019_1_OR_NEWER
+#define NEW_TIMELINE_AS_PACKAGE
+#endif
+
+using UnityEngine;
+using UnityEditor;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Linq;
+using System.Reflection;
+using System.Globalization;
+
+namespace Spine.Unity.Editor {
+	public partial class SpineEditorUtilities {
+		public static class SpineTK2DEditorUtility {
+			const string SPINE_TK2D_DEFINE = "SPINE_TK2D";
+
+			internal static void EnableTK2D () {
+				SpineBuildEnvUtility.DisableSpineAsmdefFiles();
+				SpineBuildEnvUtility.EnableBuildDefine(SPINE_TK2D_DEFINE);
+			}
+
+			internal static void DisableTK2D () {
+				SpineBuildEnvUtility.EnableSpineAsmdefFiles();
+				SpineBuildEnvUtility.DisableBuildDefine(SPINE_TK2D_DEFINE);
+			}
+		}
+
+		public static class SpinePackageDependencyUtility
+		{
+			public enum RequestState {
+				NoRequestIssued = 0,
+				InProgress,
+				Success,
+				Failure
+			}
+
+			#if NEW_TIMELINE_AS_PACKAGE
+			const string SPINE_TIMELINE_PACKAGE_DOWNLOADED_DEFINE = "SPINE_TIMELINE_PACKAGE_DOWNLOADED";
+			const string TIMELINE_PACKAGE_NAME = "com.unity.timeline";
+			const string TIMELINE_ASMDEF_DEPENDENCY_STRING = "\"Unity.Timeline\"";
+			static UnityEditor.PackageManager.Requests.AddRequest timelineRequest = null;
+			
+			/// <summary>
+			/// Enables Spine's Timeline components by downloading the Timeline Package in Unity 2019 and newer
+			/// and setting respective compile definitions once downloaded.
+			/// </summary>
+			internal static void EnableTimelineSupport () {
+				Debug.Log("Downloading Timeline package " + TIMELINE_PACKAGE_NAME + ".");
+				timelineRequest = UnityEditor.PackageManager.Client.Add(TIMELINE_PACKAGE_NAME);
+				// Note: unfortunately there is no callback provided, only polling support.
+				// So polling HandlePendingAsyncTimelineRequest() is necessary.
+
+				EditorApplication.update -= UpdateAsyncTimelineRequest;
+				EditorApplication.update += UpdateAsyncTimelineRequest;
+			}
+
+			public static void UpdateAsyncTimelineRequest () {
+				HandlePendingAsyncTimelineRequest();
+			}
+
+			public static RequestState HandlePendingAsyncTimelineRequest () {
+				if (timelineRequest == null)
+					return RequestState.NoRequestIssued;
+
+				var status = timelineRequest.Status;
+				if (status == UnityEditor.PackageManager.StatusCode.InProgress) {
+					return RequestState.InProgress;
+				}
+				else {
+					EditorApplication.update -= UpdateAsyncTimelineRequest;
+					timelineRequest = null;
+					if (status == UnityEditor.PackageManager.StatusCode.Failure) {
+						Debug.LogError("Download of package " + TIMELINE_PACKAGE_NAME + " failed!");
+						return RequestState.Failure;
+					}
+					else { // status == UnityEditor.PackageManager.StatusCode.Success
+						HandleSuccessfulTimelinePackageDownload();
+						return RequestState.Success;
+					}
+				}
+			}
+
+			internal static void DisableTimelineSupport () {
+				SpineBuildEnvUtility.DisableBuildDefine(SPINE_TIMELINE_PACKAGE_DOWNLOADED_DEFINE);
+				SpineBuildEnvUtility.RemoveDependencyFromAsmdefFile(TIMELINE_ASMDEF_DEPENDENCY_STRING);
+			}
+
+			internal static void HandleSuccessfulTimelinePackageDownload () {
+
+				#if !SPINE_TK2D
+				SpineBuildEnvUtility.EnableSpineAsmdefFiles();
+				#endif
+				SpineBuildEnvUtility.AddDependencyToAsmdefFile(TIMELINE_ASMDEF_DEPENDENCY_STRING);
+				SpineBuildEnvUtility.EnableBuildDefine(SPINE_TIMELINE_PACKAGE_DOWNLOADED_DEFINE);
+
+				ReimportTimelineScripts();
+			}
+
+			internal static void ReimportTimelineScripts () {
+				// Note: unfortunately AssetDatabase::Refresh is not enough and
+				// ImportAsset on a dir does not have the desired effect.
+				List<string> searchStrings = new List<string>();
+				searchStrings.Add("SpineAnimationStateBehaviour t:script");
+				searchStrings.Add("SpineAnimationStateClip t:script");
+				searchStrings.Add("SpineAnimationStateMixerBehaviour t:script");
+				searchStrings.Add("SpineAnimationStateTrack t:script");
+
+				searchStrings.Add("SpineSkeletonFlipBehaviour t:script");
+				searchStrings.Add("SpineSkeletonFlipClip t:script");
+				searchStrings.Add("SpineSkeletonFlipMixerBehaviour t:script");
+				searchStrings.Add("SpineSkeletonFlipTrack t:script");
+
+				searchStrings.Add("SkeletonAnimationPlayableHandle t:script");
+				searchStrings.Add("SpinePlayableHandleBase t:script");
+
+				foreach (string searchString in searchStrings) {
+					string[] guids = AssetDatabase.FindAssets(searchString);
+					foreach (string guid in guids) {
+						string currentPath = AssetDatabase.GUIDToAssetPath(guid);
+						AssetDatabase.ImportAsset(currentPath, ImportAssetOptions.ForceUpdate);
+					}
+				}
+			}
+			#endif
+		}
+	}
+
+	public static class SpineBuildEnvUtility
+	{
+		static bool IsInvalidGroup (BuildTargetGroup group) {
+			int gi = (int)group;
+			return
+				gi == 15 || gi == 16
+				||
+				group == BuildTargetGroup.Unknown;
+		}
+
+		public static bool EnableBuildDefine (string define) {
+
+			bool wasDefineAdded = false;
+			Debug.LogWarning("Please ignore errors \"PlayerSettings Validation: Requested build target group doesn't exist\" below");
+			foreach (BuildTargetGroup group in System.Enum.GetValues(typeof(BuildTargetGroup))) {
+				if (IsInvalidGroup(group))
+					continue;
+
+				string defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(group);
+				if (!defines.Contains(define)) {
+					wasDefineAdded = true;
+					if (defines.EndsWith(";", System.StringComparison.Ordinal))
+						defines += define;
+					else
+						defines += ";" + define;
+
+					PlayerSettings.SetScriptingDefineSymbolsForGroup(group, defines);
+				}
+			}
+			Debug.LogWarning("Please ignore errors \"PlayerSettings Validation: Requested build target group doesn't exist\" above");
+
+			if (wasDefineAdded) {
+				Debug.LogWarning("Setting Scripting Define Symbol " + define);
+			}
+			else {
+				Debug.LogWarning("Already Set Scripting Define Symbol " + define);
+			}
+			return wasDefineAdded;
+		}
+
+		public static bool DisableBuildDefine (string define) {
+
+			bool wasDefineRemoved = false;
+			foreach (BuildTargetGroup group in System.Enum.GetValues(typeof(BuildTargetGroup))) {
+				if (IsInvalidGroup(group))
+					continue;
+
+				string defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(group);
+				if (defines.Contains(define)) {
+					wasDefineRemoved = true;
+					if (defines.Contains(define + ";"))
+						defines = defines.Replace(define + ";", "");
+					else
+						defines = defines.Replace(define, "");
+
+					PlayerSettings.SetScriptingDefineSymbolsForGroup(group, defines);
+				}
+			}
+
+			if (wasDefineRemoved) {
+				Debug.LogWarning("Removing Scripting Define Symbol " + define);
+			}
+			else {
+				Debug.LogWarning("Already Removed Scripting Define Symbol " + define);
+			}
+			return wasDefineRemoved;
+		}
+
+		public static void DisableSpineAsmdefFiles () {
+			SetAsmdefFileActive("spine-unity-editor", false);
+			SetAsmdefFileActive("spine-unity", false);
+		}
+
+		public static void EnableSpineAsmdefFiles () {
+			SetAsmdefFileActive("spine-unity-editor", true);
+			SetAsmdefFileActive("spine-unity", true);
+		}
+
+		public static void AddDependencyToAsmdefFile (string dependencyName) {
+			string asmdefName = "spine-unity";
+			string filePath = FindAsmdefFile(asmdefName);
+			if (string.IsNullOrEmpty(filePath))
+				return;
+
+			if (System.IO.File.Exists(filePath)) {
+				string fileContent = File.ReadAllText(filePath);
+
+				if (!fileContent.Contains("references")) {
+					string nameLine = string.Concat("\"name\": \"", asmdefName, "\"");
+					fileContent = fileContent.Replace(nameLine,
+													nameLine +
+													@",\n""references"": []");
+				}
+
+				if (!fileContent.Contains(dependencyName)) {
+					fileContent = fileContent.Replace(@"""references"": [",
+													@"""references"": [" + dependencyName);
+					File.WriteAllText(filePath, fileContent);
+				}
+			}
+		}
+
+		public static void RemoveDependencyFromAsmdefFile (string dependencyName) {
+			string asmdefName = "spine-unity";
+			string filePath = FindAsmdefFile(asmdefName);
+			if (string.IsNullOrEmpty(filePath))
+				return;
+
+			if (System.IO.File.Exists(filePath)) {
+				string fileContent = File.ReadAllText(filePath);
+				// this simple implementation shall suffice for now.
+				if (fileContent.Contains(dependencyName)) {
+					fileContent = fileContent.Replace(dependencyName, "");
+					File.WriteAllText(filePath, fileContent);
+				}
+			}
+		}
+
+		internal static string FindAsmdefFile (string filename) {
+			string filePath = FindAsmdefFile(filename, isDisabledFile: false);
+			if (string.IsNullOrEmpty(filePath))
+				filePath = FindAsmdefFile(filename, isDisabledFile: true);
+			return filePath;
+		}
+
+		internal static string FindAsmdefFile (string filename, bool isDisabledFile) {
+
+			string typeSearchString = isDisabledFile ? " t:TextAsset" : " t:AssemblyDefinitionAsset";
+			string extension = isDisabledFile ? ".txt" : ".asmdef";
+			string filenameWithExtension = filename + (isDisabledFile ? ".txt" : ".asmdef");
+			string[] guids = AssetDatabase.FindAssets(filename + typeSearchString);
+			foreach (string guid in guids) {
+				string currentPath = AssetDatabase.GUIDToAssetPath(guid);
+				if (!string.IsNullOrEmpty(currentPath)) {
+					if (System.IO.Path.GetFileName(currentPath) == filenameWithExtension)
+						return currentPath;
+				}
+			}
+			return null;
+		}
+
+		internal static void SetAsmdefFileActive (string filename, bool setActive) {
+
+			string typeSearchString = setActive ? " t:TextAsset" : " t:AssemblyDefinitionAsset";
+			string extensionBeforeChange = setActive ? "txt" : "asmdef";
+			string[] guids = AssetDatabase.FindAssets(filename + typeSearchString);
+			foreach (string guid in guids) {
+				string currentPath = AssetDatabase.GUIDToAssetPath(guid);
+				if (!System.IO.Path.HasExtension(extensionBeforeChange)) // asmdef is also found as t:TextAsset, so check
+					continue;
+
+				string targetPath = System.IO.Path.ChangeExtension(currentPath, setActive ? "asmdef" : "txt");
+				if (System.IO.File.Exists(currentPath) && !System.IO.File.Exists(targetPath)) {
+					System.IO.File.Copy(currentPath, targetPath);
+					System.IO.File.Copy(currentPath + ".meta", targetPath + ".meta");
+				}
+				AssetDatabase.DeleteAsset(currentPath);
+			}
+		}
+	}
+}

+ 12 - 0
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/BuildSettings.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 80767dcb23931c645ad70c88670ddeb6
+timeCreated: 1563313275
+licenseType: Free
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 149 - 0
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/DataReloadHandler.cs

@@ -0,0 +1,149 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated May 1, 2019. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2019, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS
+ * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+#pragma warning disable 0219
+
+#define SPINE_SKELETONMECANIM
+
+#if UNITY_2017_2_OR_NEWER
+#define NEWPLAYMODECALLBACKS
+#endif
+
+#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
+#define NEW_PREFAB_SYSTEM
+#endif
+
+#if UNITY_2018 || UNITY_2019 || UNITY_2018_3_OR_NEWER
+#define NEWHIERARCHYWINDOWCALLBACKS
+#endif
+
+#if UNITY_2018_3_OR_NEWER
+#define NEW_PREFERENCES_SETTINGS_PROVIDER
+#endif
+
+#if UNITY_2019_1_OR_NEWER
+#define NEW_TIMELINE_AS_PACKAGE
+#endif
+
+using UnityEngine;
+using UnityEditor;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Linq;
+using System.Reflection;
+using System.Globalization;
+
+namespace Spine.Unity.Editor {
+
+	public partial class SpineEditorUtilities {
+		public static class DataReloadHandler {
+
+			internal static Dictionary<int, string> savedSkeletonDataAssetAtSKeletonGraphicID = new Dictionary<int, string>();
+
+		#if NEWPLAYMODECALLBACKS
+			internal static void OnPlaymodeStateChanged (PlayModeStateChange stateChange) {
+		#else
+			internal static void OnPlaymodeStateChanged () {
+		#endif
+				ReloadAllActiveSkeletonsEditMode();
+			}
+
+			public static void ReloadAllActiveSkeletonsEditMode () {
+
+				if (EditorApplication.isPaused) return;
+				if (EditorApplication.isPlaying) return;
+				if (EditorApplication.isCompiling) return;
+				if (EditorApplication.isPlayingOrWillChangePlaymode) return;
+
+				var skeletonDataAssetsToReload = new HashSet<SkeletonDataAsset>();
+
+				var activeSkeletonRenderers = GameObject.FindObjectsOfType<SkeletonRenderer>();
+				foreach (var sr in activeSkeletonRenderers) {
+					var skeletonDataAsset = sr.skeletonDataAsset;
+					if (skeletonDataAsset != null) skeletonDataAssetsToReload.Add(skeletonDataAsset);
+				}
+
+				// Under some circumstances (e.g. on first import) SkeletonGraphic objects 
+				// have their skeletonGraphic.skeletonDataAsset reference corrupted
+				// by the instance of the ScriptableObject being destroyed but still assigned.
+				// Here we save the skeletonGraphic.skeletonDataAsset asset path in order
+				// to restore it later.
+				var activeSkeletonGraphics = GameObject.FindObjectsOfType<SkeletonGraphic>();
+				foreach (var sg in activeSkeletonGraphics) {
+					var skeletonDataAsset = sg.skeletonDataAsset;
+					if (skeletonDataAsset != null) {
+						var assetPath = AssetDatabase.GetAssetPath(skeletonDataAsset);
+						var sgID = sg.GetInstanceID();
+						savedSkeletonDataAssetAtSKeletonGraphicID[sgID] = assetPath;
+						skeletonDataAssetsToReload.Add(skeletonDataAsset);
+					}
+				}
+
+				foreach (var sda in skeletonDataAssetsToReload) {
+					sda.Clear();
+					sda.GetSkeletonData(true);
+				}
+
+				foreach (var sr in activeSkeletonRenderers) {
+					var meshRenderer = sr.GetComponent<MeshRenderer>();
+					var sharedMaterials = meshRenderer.sharedMaterials;
+					foreach (var m in sharedMaterials) {
+						if (m == null) {
+							sr.Initialize(true);
+							break;
+						}
+					}
+				}
+
+				foreach (var sg in activeSkeletonGraphics) {
+					if (sg.mainTexture == null)
+						sg.Initialize(true);
+				}
+			}
+
+			public static void ReloadSceneSkeletonComponents (SkeletonDataAsset skeletonDataAsset) {
+				if (EditorApplication.isPaused) return;
+				if (EditorApplication.isPlaying) return;
+				if (EditorApplication.isCompiling) return;
+				if (EditorApplication.isPlayingOrWillChangePlaymode) return;
+
+				var activeSkeletonRenderers = GameObject.FindObjectsOfType<SkeletonRenderer>();
+				foreach (var sr in activeSkeletonRenderers) {
+					if (sr.isActiveAndEnabled && sr.skeletonDataAsset == skeletonDataAsset) sr.Initialize(true);
+				}
+
+				var activeSkeletonGraphics = GameObject.FindObjectsOfType<SkeletonGraphic>();
+				foreach (var sg in activeSkeletonGraphics) {
+					if (sg.isActiveAndEnabled && sg.skeletonDataAsset == skeletonDataAsset) sg.Initialize(true);
+				}
+			}
+		}
+	}
+}

+ 12 - 0
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/DataReloadHandler.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: d8a1a76014a68634a8ede50af6db1cae
+timeCreated: 1563310382
+licenseType: Free
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 167 - 0
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/Icons.cs

@@ -0,0 +1,167 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated May 1, 2019. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2019, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS
+ * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+#pragma warning disable 0219
+
+#define SPINE_SKELETONMECANIM
+
+#if UNITY_2017_2_OR_NEWER
+#define NEWPLAYMODECALLBACKS
+#endif
+
+#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
+#define NEW_PREFAB_SYSTEM
+#endif
+
+#if UNITY_2018 || UNITY_2019 || UNITY_2018_3_OR_NEWER
+#define NEWHIERARCHYWINDOWCALLBACKS
+#endif
+
+#if UNITY_2018_3_OR_NEWER
+#define NEW_PREFERENCES_SETTINGS_PROVIDER
+#endif
+
+#if UNITY_2019_1_OR_NEWER
+#define NEW_TIMELINE_AS_PACKAGE
+#endif
+
+using UnityEngine;
+using UnityEditor;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Linq;
+using System.Reflection;
+using System.Globalization;
+
+namespace Spine.Unity.Editor {
+	public partial class SpineEditorUtilities {
+		public static class Icons {
+			public static Texture2D skeleton;
+			public static Texture2D nullBone;
+			public static Texture2D bone;
+			public static Texture2D poseBones;
+			public static Texture2D boneNib;
+			public static Texture2D slot;
+			public static Texture2D slotRoot;
+			public static Texture2D skinPlaceholder;
+			public static Texture2D image;
+			public static Texture2D genericAttachment;
+			public static Texture2D boundingBox;
+			public static Texture2D point;
+			public static Texture2D mesh;
+			public static Texture2D weights;
+			public static Texture2D path;
+			public static Texture2D clipping;
+			public static Texture2D skin;
+			public static Texture2D skinsRoot;
+			public static Texture2D animation;
+			public static Texture2D animationRoot;
+			public static Texture2D spine;
+			public static Texture2D userEvent;
+			public static Texture2D constraintNib;
+			public static Texture2D constraintRoot;
+			public static Texture2D constraintTransform;
+			public static Texture2D constraintPath;
+			public static Texture2D constraintIK;
+			public static Texture2D warning;
+			public static Texture2D skeletonUtility;
+			public static Texture2D hingeChain;
+			public static Texture2D subMeshRenderer;
+			public static Texture2D skeletonDataAssetIcon;
+			public static Texture2D info;
+			public static Texture2D unity;
+
+			static Texture2D LoadIcon (string filename) {
+				return (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/" + filename);
+			}
+
+			public static void Initialize () {
+				skeleton = LoadIcon("icon-skeleton.png");
+				nullBone = LoadIcon("icon-null.png");
+				bone = LoadIcon("icon-bone.png");
+				poseBones = LoadIcon("icon-poseBones.png");
+				boneNib = LoadIcon("icon-boneNib.png");
+				slot = LoadIcon("icon-slot.png");
+				slotRoot = LoadIcon("icon-slotRoot.png");
+				skinPlaceholder = LoadIcon("icon-skinPlaceholder.png");
+
+				genericAttachment = LoadIcon("icon-attachment.png");
+				image = LoadIcon("icon-image.png");
+				boundingBox = LoadIcon("icon-boundingBox.png");
+				point = LoadIcon("icon-point.png");
+				mesh = LoadIcon("icon-mesh.png");
+				weights = LoadIcon("icon-weights.png");
+				path = LoadIcon("icon-path.png");
+				clipping = LoadIcon("icon-clipping.png");
+
+				skin = LoadIcon("icon-skin.png");
+				skinsRoot = LoadIcon("icon-skinsRoot.png");
+				animation = LoadIcon("icon-animation.png");
+				animationRoot = LoadIcon("icon-animationRoot.png");
+				spine = LoadIcon("icon-spine.png");
+				userEvent = LoadIcon("icon-event.png");
+				constraintNib = LoadIcon("icon-constraintNib.png");
+
+				constraintRoot = LoadIcon("icon-constraints.png");
+				constraintTransform = LoadIcon("icon-constraintTransform.png");
+				constraintPath = LoadIcon("icon-constraintPath.png");
+				constraintIK = LoadIcon("icon-constraintIK.png");
+
+				warning = LoadIcon("icon-warning.png");
+				skeletonUtility = LoadIcon("icon-skeletonUtility.png");
+				hingeChain = LoadIcon("icon-hingeChain.png");
+				subMeshRenderer = LoadIcon("icon-subMeshRenderer.png");
+
+				skeletonDataAssetIcon = LoadIcon("SkeletonDataAsset Icon.png");
+
+				info = EditorGUIUtility.FindTexture("console.infoicon.sml");
+				unity = EditorGUIUtility.FindTexture("SceneAsset Icon");
+			}
+
+			public static Texture2D GetAttachmentIcon (Attachment attachment) {
+				// Analysis disable once CanBeReplacedWithTryCastAndCheckForNull
+				if (attachment is RegionAttachment)
+					return Icons.image;
+				else if (attachment is MeshAttachment)
+					return ((MeshAttachment)attachment).IsWeighted() ? Icons.weights : Icons.mesh;
+				else if (attachment is BoundingBoxAttachment)
+					return Icons.boundingBox;
+				else if (attachment is PointAttachment)
+					return Icons.point;
+				else if (attachment is PathAttachment)
+					return Icons.path;
+				else if (attachment is ClippingAttachment)
+					return Icons.clipping;
+				else
+					return Icons.warning;
+			}
+		}
+	}
+}

+ 12 - 0
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/Icons.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 8603301469b2a284db8eaa26dc60ada8
+timeCreated: 1563312057
+licenseType: Free
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 203 - 0
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/Instantiation.cs

@@ -0,0 +1,203 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated May 1, 2019. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2019, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS
+ * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+#pragma warning disable 0219
+
+#define SPINE_SKELETONMECANIM
+
+#if UNITY_2017_2_OR_NEWER
+#define NEWPLAYMODECALLBACKS
+#endif
+
+#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
+#define NEW_PREFAB_SYSTEM
+#endif
+
+#if UNITY_2018 || UNITY_2019 || UNITY_2018_3_OR_NEWER
+#define NEWHIERARCHYWINDOWCALLBACKS
+#endif
+
+#if UNITY_2018_3_OR_NEWER
+#define NEW_PREFERENCES_SETTINGS_PROVIDER
+#endif
+
+#if UNITY_2019_1_OR_NEWER
+#define NEW_TIMELINE_AS_PACKAGE
+#endif
+
+using UnityEngine;
+using UnityEditor;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Linq;
+using System.Reflection;
+using System.Globalization;
+
+namespace Spine.Unity.Editor {
+	using EventType = UnityEngine.EventType;
+
+	public partial class SpineEditorUtilities {
+		public static class DragAndDropInstantiation {
+			public struct SpawnMenuData {
+				public Vector3 spawnPoint;
+				public SkeletonDataAsset skeletonDataAsset;
+				public EditorInstantiation.InstantiateDelegate instantiateDelegate;
+				public bool isUI;
+			}
+
+			public static void SceneViewDragAndDrop (SceneView sceneview) {
+				var current = UnityEngine.Event.current;
+				var references = DragAndDrop.objectReferences;
+				if (current.type == EventType.Layout)
+					return;
+
+				// Allow drag and drop of one SkeletonDataAsset.
+				if (references.Length == 1) {
+					var skeletonDataAsset = references[0] as SkeletonDataAsset;
+					if (skeletonDataAsset != null) {
+						var mousePos = current.mousePosition;
+
+						bool invalidSkeletonData = skeletonDataAsset.GetSkeletonData(true) == null;
+						if (invalidSkeletonData) {
+							DragAndDrop.visualMode = DragAndDropVisualMode.Rejected;
+							Handles.BeginGUI();
+							GUI.Label(new Rect(mousePos + new Vector2(20f, 20f), new Vector2(400f, 40f)), new GUIContent(string.Format("{0} is invalid.\nCannot create new Spine GameObject.", skeletonDataAsset.name), SpineEditorUtilities.Icons.warning));
+							Handles.EndGUI();
+							return;
+						} else {
+							DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
+							Handles.BeginGUI();
+							GUI.Label(new Rect(mousePos + new Vector2(20f, 20f), new Vector2(400f, 20f)), new GUIContent(string.Format("Create Spine GameObject ({0})", skeletonDataAsset.skeletonJSON.name), SpineEditorUtilities.Icons.skeletonDataAssetIcon));
+							Handles.EndGUI();
+
+							if (current.type == EventType.DragPerform) {
+								RectTransform rectTransform = (Selection.activeGameObject == null) ? null : Selection.activeGameObject.GetComponent<RectTransform>();
+								Plane plane = (rectTransform == null) ? new Plane(Vector3.back, Vector3.zero) : new Plane(-rectTransform.forward, rectTransform.position);
+								Vector3 spawnPoint = MousePointToWorldPoint2D(mousePos, sceneview.camera, plane);
+								ShowInstantiateContextMenu(skeletonDataAsset, spawnPoint);
+								DragAndDrop.AcceptDrag();
+								current.Use();
+							}
+						}
+					}
+				}
+			}
+
+			public static void ShowInstantiateContextMenu (SkeletonDataAsset skeletonDataAsset, Vector3 spawnPoint) {
+				var menu = new GenericMenu();
+
+				// SkeletonAnimation
+				menu.AddItem(new GUIContent("SkeletonAnimation"), false, HandleSkeletonComponentDrop, new SpawnMenuData {
+					skeletonDataAsset = skeletonDataAsset,
+					spawnPoint = spawnPoint,
+					instantiateDelegate = (data) => EditorInstantiation.InstantiateSkeletonAnimation(data),
+					isUI = false
+				});
+
+				// SkeletonGraphic
+				var skeletonGraphicInspectorType = System.Type.GetType("Spine.Unity.Editor.SkeletonGraphicInspector");
+				if (skeletonGraphicInspectorType != null) {
+					var graphicInstantiateDelegate = skeletonGraphicInspectorType.GetMethod("SpawnSkeletonGraphicFromDrop", BindingFlags.Static | BindingFlags.Public);
+					if (graphicInstantiateDelegate != null)
+						menu.AddItem(new GUIContent("SkeletonGraphic (UI)"), false, HandleSkeletonComponentDrop, new SpawnMenuData {
+							skeletonDataAsset = skeletonDataAsset,
+							spawnPoint = spawnPoint,
+							instantiateDelegate = System.Delegate.CreateDelegate(typeof(EditorInstantiation.InstantiateDelegate), graphicInstantiateDelegate) as EditorInstantiation.InstantiateDelegate,
+							isUI = true
+						});
+				}
+
+#if SPINE_SKELETONMECANIM
+				menu.AddSeparator("");
+				// SkeletonMecanim
+				menu.AddItem(new GUIContent("SkeletonMecanim"), false, HandleSkeletonComponentDrop, new SpawnMenuData {
+					skeletonDataAsset = skeletonDataAsset,
+					spawnPoint = spawnPoint,
+					instantiateDelegate = (data) => EditorInstantiation.InstantiateSkeletonMecanim(data)
+				});
+#endif
+
+				menu.ShowAsContext();
+			}
+
+			public static void HandleSkeletonComponentDrop (object spawnMenuData) {
+				var data = (SpawnMenuData)spawnMenuData;
+
+				if (data.skeletonDataAsset.GetSkeletonData(true) == null) {
+					EditorUtility.DisplayDialog("Invalid SkeletonDataAsset", "Unable to create Spine GameObject.\n\nPlease check your SkeletonDataAsset.", "Ok");
+					return;
+				}
+
+				bool isUI = data.isUI;
+
+				Component newSkeletonComponent = data.instantiateDelegate.Invoke(data.skeletonDataAsset);
+				GameObject newGameObject = newSkeletonComponent.gameObject;
+				Transform newTransform = newGameObject.transform;
+
+				var activeGameObject = Selection.activeGameObject;
+				if (isUI && activeGameObject != null)
+					newTransform.SetParent(activeGameObject.transform, false);
+
+				newTransform.position = isUI ? data.spawnPoint : RoundVector(data.spawnPoint, 2);
+
+				if (isUI && (activeGameObject == null || activeGameObject.GetComponent<RectTransform>() == null))
+					Debug.Log("Created a UI Skeleton GameObject not under a RectTransform. It may not be visible until you parent it to a canvas.");
+
+				if (!isUI && activeGameObject != null && activeGameObject.transform.localScale != Vector3.one)
+					Debug.Log("New Spine GameObject was parented to a scaled Transform. It may not be the intended size.");
+
+				Selection.activeGameObject = newGameObject;
+				//EditorGUIUtility.PingObject(newGameObject); // Doesn't work when setting activeGameObject.
+				Undo.RegisterCreatedObjectUndo(newGameObject, "Create Spine GameObject");
+			}
+
+			/// <summary>
+			/// Rounds off vector components to a number of decimal digits.
+			/// </summary>
+			public static Vector3 RoundVector (Vector3 vector, int digits) {
+				vector.x = (float)System.Math.Round(vector.x, digits);
+				vector.y = (float)System.Math.Round(vector.y, digits);
+				vector.z = (float)System.Math.Round(vector.z, digits);
+				return vector;
+			}
+
+			/// <summary>
+			/// Converts a mouse point to a world point on a plane.
+			/// </summary>
+			static Vector3 MousePointToWorldPoint2D (Vector2 mousePosition, Camera camera, Plane plane) {
+				var screenPos = new Vector3(mousePosition.x, camera.pixelHeight - mousePosition.y, 0f);
+				var ray = camera.ScreenPointToRay(screenPos);
+				float distance;
+				bool hit = plane.Raycast(ray, out distance);
+				return ray.GetPoint(distance);
+			}
+		}
+	}
+}

+ 12 - 0
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/Instantiation.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 94ecc7f73dfc0484e848a0e6a1a11f2b
+timeCreated: 1563311945
+licenseType: Free
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 313 - 0
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/Preferences.cs

@@ -0,0 +1,313 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated May 1, 2019. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2019, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS
+ * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+#pragma warning disable 0219
+
+#define SPINE_SKELETONMECANIM
+
+#if UNITY_2017_2_OR_NEWER
+#define NEWPLAYMODECALLBACKS
+#endif
+
+#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
+#define NEW_PREFAB_SYSTEM
+#endif
+
+#if UNITY_2018 || UNITY_2019 || UNITY_2018_3_OR_NEWER
+#define NEWHIERARCHYWINDOWCALLBACKS
+#endif
+
+#if UNITY_2018_3_OR_NEWER
+#define NEW_PREFERENCES_SETTINGS_PROVIDER
+#endif
+
+#if UNITY_2019_1_OR_NEWER
+#define NEW_TIMELINE_AS_PACKAGE
+#endif
+
+using UnityEngine;
+using UnityEditor;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Linq;
+using System.Reflection;
+using System.Globalization;
+
+namespace Spine.Unity.Editor {
+	public partial class SpineEditorUtilities {
+
+	#if NEW_PREFERENCES_SETTINGS_PROVIDER
+		static class SpineSettingsProviderRegistration
+		{
+			[SettingsProvider]
+			public static SettingsProvider CreateSpineSettingsProvider()
+			{
+				var provider = new SettingsProvider("Spine", SettingsScope.User)
+				{
+					label = "Spine",
+					guiHandler = (searchContext) =>
+					{
+						var settings = SpinePreferences.GetOrCreateSettings();
+						var serializedSettings = new SerializedObject(settings);
+						SpinePreferences.HandlePreferencesGUI(serializedSettings);
+						if (serializedSettings.ApplyModifiedProperties())
+							OldPreferences.SaveToEditorPrefs(settings);
+					},
+
+					// Populate the search keywords to enable smart search filtering and label highlighting:
+					keywords = new HashSet<string>(new[] { "Spine", "Preferences", "Skeleton", "Default", "Mix", "Duration" })
+				};
+				return provider;
+			}
+		}
+	#else
+		// Preferences entry point
+		[PreferenceItem("Spine")]
+		static void PreferencesGUI () {
+			Preferences.HandlePreferencesGUI();
+		}
+	#endif
+		
+	#if NEW_PREFERENCES_SETTINGS_PROVIDER
+		public static SpinePreferences Preferences {
+			get {
+				return SpinePreferences.GetOrCreateSettings();
+			}
+		}
+	#endif
+
+	#if NEW_PREFERENCES_SETTINGS_PROVIDER
+		public static class OldPreferences {
+	#else
+		public static class Preferences {
+	#endif
+			const string DEFAULT_SCALE_KEY = "SPINE_DEFAULT_SCALE";
+			public static float defaultScale = SpinePreferences.DEFAULT_DEFAULT_SCALE;
+
+			const string DEFAULT_MIX_KEY = "SPINE_DEFAULT_MIX";
+			public static float defaultMix = SpinePreferences.DEFAULT_DEFAULT_MIX;
+
+			const string DEFAULT_SHADER_KEY = "SPINE_DEFAULT_SHADER";
+			public static string defaultShader = SpinePreferences.DEFAULT_DEFAULT_SHADER;
+
+			const string DEFAULT_ZSPACING_KEY = "SPINE_DEFAULT_ZSPACING";
+			public static float defaultZSpacing = SpinePreferences.DEFAULT_DEFAULT_ZSPACING;
+
+			const string DEFAULT_INSTANTIATE_LOOP_KEY = "SPINE_DEFAULT_INSTANTIATE_LOOP";
+			public static bool defaultInstantiateLoop = SpinePreferences.DEFAULT_DEFAULT_INSTANTIATE_LOOP;
+
+			const string SHOW_HIERARCHY_ICONS_KEY = "SPINE_SHOW_HIERARCHY_ICONS";
+			public static bool showHierarchyIcons = SpinePreferences.DEFAULT_SHOW_HIERARCHY_ICONS;
+
+			const string SET_TEXTUREIMPORTER_SETTINGS_KEY = "SPINE_SET_TEXTUREIMPORTER_SETTINGS";
+			public static bool setTextureImporterSettings = SpinePreferences.DEFAULT_SET_TEXTUREIMPORTER_SETTINGS;
+
+			const string ATLASTXT_WARNING_KEY = "SPINE_ATLASTXT_WARNING";
+			public static bool atlasTxtImportWarning = SpinePreferences.DEFAULT_ATLASTXT_WARNING;
+
+			const string TEXTUREIMPORTER_WARNING_KEY = "SPINE_TEXTUREIMPORTER_WARNING";
+			public static bool textureImporterWarning = SpinePreferences.DEFAULT_TEXTUREIMPORTER_WARNING;
+
+			public const float DEFAULT_MIPMAPBIAS = SpinePreferences.DEFAULT_MIPMAPBIAS;
+
+			public const float DEFAULT_SCENE_ICONS_SCALE = SpinePreferences.DEFAULT_SCENE_ICONS_SCALE;
+			public const string SCENE_ICONS_SCALE_KEY = SpinePreferences.SCENE_ICONS_SCALE_KEY;
+
+			const string AUTO_RELOAD_SCENESKELETONS_KEY = "SPINE_AUTO_RELOAD_SCENESKELETONS";
+			public static bool autoReloadSceneSkeletons = SpinePreferences.DEFAULT_AUTO_RELOAD_SCENESKELETONS;
+
+			static bool preferencesLoaded = false;
+
+			public static void Load () {
+				if (preferencesLoaded)
+					return;
+
+				defaultMix = EditorPrefs.GetFloat(DEFAULT_MIX_KEY, SpinePreferences.DEFAULT_DEFAULT_MIX);
+				defaultScale = EditorPrefs.GetFloat(DEFAULT_SCALE_KEY, SpinePreferences.DEFAULT_DEFAULT_SCALE);
+				defaultZSpacing = EditorPrefs.GetFloat(DEFAULT_ZSPACING_KEY, SpinePreferences.DEFAULT_DEFAULT_ZSPACING);
+				defaultShader = EditorPrefs.GetString(DEFAULT_SHADER_KEY, SpinePreferences.DEFAULT_DEFAULT_SHADER);
+				showHierarchyIcons = EditorPrefs.GetBool(SHOW_HIERARCHY_ICONS_KEY, SpinePreferences.DEFAULT_SHOW_HIERARCHY_ICONS);
+				setTextureImporterSettings = EditorPrefs.GetBool(SET_TEXTUREIMPORTER_SETTINGS_KEY, SpinePreferences.DEFAULT_SET_TEXTUREIMPORTER_SETTINGS);
+				autoReloadSceneSkeletons = EditorPrefs.GetBool(AUTO_RELOAD_SCENESKELETONS_KEY, SpinePreferences.DEFAULT_AUTO_RELOAD_SCENESKELETONS);
+				atlasTxtImportWarning = EditorPrefs.GetBool(ATLASTXT_WARNING_KEY, SpinePreferences.DEFAULT_ATLASTXT_WARNING);
+				textureImporterWarning = EditorPrefs.GetBool(TEXTUREIMPORTER_WARNING_KEY, SpinePreferences.DEFAULT_TEXTUREIMPORTER_WARNING);
+
+				SpineHandles.handleScale = EditorPrefs.GetFloat(SCENE_ICONS_SCALE_KEY, DEFAULT_SCENE_ICONS_SCALE);
+				preferencesLoaded = true;
+			}
+
+		#if NEW_PREFERENCES_SETTINGS_PROVIDER
+			public static void CopyOldToNewPreferences(ref SpinePreferences newPreferences) {
+				newPreferences.defaultMix = EditorPrefs.GetFloat(DEFAULT_MIX_KEY, SpinePreferences.DEFAULT_DEFAULT_MIX);
+				newPreferences.defaultScale = EditorPrefs.GetFloat(DEFAULT_SCALE_KEY, SpinePreferences.DEFAULT_DEFAULT_SCALE);
+				newPreferences.defaultZSpacing = EditorPrefs.GetFloat(DEFAULT_ZSPACING_KEY, SpinePreferences.DEFAULT_DEFAULT_ZSPACING);
+				newPreferences.defaultShader = EditorPrefs.GetString(DEFAULT_SHADER_KEY, SpinePreferences.DEFAULT_DEFAULT_SHADER);
+				newPreferences.showHierarchyIcons = EditorPrefs.GetBool(SHOW_HIERARCHY_ICONS_KEY, SpinePreferences.DEFAULT_SHOW_HIERARCHY_ICONS);
+				newPreferences.setTextureImporterSettings = EditorPrefs.GetBool(SET_TEXTUREIMPORTER_SETTINGS_KEY, SpinePreferences.DEFAULT_SET_TEXTUREIMPORTER_SETTINGS);
+				newPreferences.autoReloadSceneSkeletons = EditorPrefs.GetBool(AUTO_RELOAD_SCENESKELETONS_KEY, SpinePreferences.DEFAULT_AUTO_RELOAD_SCENESKELETONS);
+				newPreferences.atlasTxtImportWarning = EditorPrefs.GetBool(ATLASTXT_WARNING_KEY, SpinePreferences.DEFAULT_ATLASTXT_WARNING);
+				newPreferences.textureImporterWarning = EditorPrefs.GetBool(TEXTUREIMPORTER_WARNING_KEY, SpinePreferences.DEFAULT_TEXTUREIMPORTER_WARNING);
+			}
+
+			public static void SaveToEditorPrefs(SpinePreferences preferences) {
+				EditorPrefs.SetFloat(DEFAULT_MIX_KEY, preferences.defaultMix);
+				EditorPrefs.SetFloat(DEFAULT_SCALE_KEY, preferences.defaultScale);
+				EditorPrefs.SetFloat(DEFAULT_ZSPACING_KEY, preferences.defaultZSpacing);
+				EditorPrefs.SetString(DEFAULT_SHADER_KEY, preferences.defaultShader);
+				EditorPrefs.SetBool(SHOW_HIERARCHY_ICONS_KEY, preferences.showHierarchyIcons);
+				EditorPrefs.SetBool(SET_TEXTUREIMPORTER_SETTINGS_KEY, preferences.setTextureImporterSettings);
+				EditorPrefs.SetBool(AUTO_RELOAD_SCENESKELETONS_KEY, preferences.autoReloadSceneSkeletons);
+				EditorPrefs.SetBool(ATLASTXT_WARNING_KEY, preferences.atlasTxtImportWarning);
+				EditorPrefs.SetBool(TEXTUREIMPORTER_WARNING_KEY, preferences.textureImporterWarning);
+			}
+		#endif
+
+		#if !NEW_PREFERENCES_SETTINGS_PROVIDER
+			public static void HandlePreferencesGUI () {
+				if (!preferencesLoaded)
+					Load();
+
+				EditorGUI.BeginChangeCheck();
+				showHierarchyIcons = EditorGUILayout.Toggle(new GUIContent("Show Hierarchy Icons", "Show relevant icons on GameObjects with Spine Components on them. Disable this if you have large, complex scenes."), showHierarchyIcons);
+				if (EditorGUI.EndChangeCheck()) {
+					EditorPrefs.SetBool(SHOW_HIERARCHY_ICONS_KEY, showHierarchyIcons);
+				#if NEWPLAYMODECALLBACKS
+					HierarchyHandler.IconsOnPlaymodeStateChanged(PlayModeStateChange.EnteredEditMode);
+				#else
+					HierarchyHandler.IconsOnPlaymodeStateChanged();
+				#endif
+				}
+
+				BoolPrefsField(ref autoReloadSceneSkeletons, AUTO_RELOAD_SCENESKELETONS_KEY, new GUIContent("Auto-reload scene components", "Reloads Skeleton components in the scene whenever their SkeletonDataAsset is modified. This makes it so changes in the SkeletonDataAsset inspector are immediately reflected. This may be slow when your scenes have large numbers of SkeletonRenderers or SkeletonGraphic."));
+
+				EditorGUILayout.Separator();
+				EditorGUILayout.LabelField("Auto-Import Settings", EditorStyles.boldLabel);
+				{
+					SpineEditorUtilities.FloatPrefsField(ref defaultMix, DEFAULT_MIX_KEY, new GUIContent("Default Mix", "The Default Mix Duration for newly imported SkeletonDataAssets."), min: 0);
+					SpineEditorUtilities.FloatPrefsField(ref defaultScale, DEFAULT_SCALE_KEY, new GUIContent("Default SkeletonData Scale", "The Default skeleton import scale for newly imported SkeletonDataAssets."), min: 0.0000001f);
+
+					EditorGUI.BeginChangeCheck();
+					var shader = (EditorGUILayout.ObjectField("Default Shader", Shader.Find(defaultShader), typeof(Shader), false) as Shader);
+					defaultShader = shader != null ? shader.name : SpinePreferences.DEFAULT_DEFAULT_SHADER;
+					if (EditorGUI.EndChangeCheck())
+						EditorPrefs.SetString(DEFAULT_SHADER_KEY, defaultShader);
+
+					SpineEditorUtilities.BoolPrefsField(ref setTextureImporterSettings, SET_TEXTUREIMPORTER_SETTINGS_KEY, new GUIContent("Apply Atlas Texture Settings", "Apply the recommended settings for Texture Importers."));
+				}
+
+				EditorGUILayout.Space();
+				EditorGUILayout.LabelField("Warnings", EditorStyles.boldLabel);
+				{
+					SpineEditorUtilities.BoolPrefsField(ref atlasTxtImportWarning, ATLASTXT_WARNING_KEY, new GUIContent("Atlas Extension Warning", "Log a warning and recommendation whenever a `.atlas` file is found."));
+					SpineEditorUtilities.BoolPrefsField(ref textureImporterWarning, TEXTUREIMPORTER_WARNING_KEY, new GUIContent("Texture Settings Warning", "Log a warning and recommendation whenever Texture Import Settings are detected that could lead to undesired effects, e.g. white border artifacts."));
+				}
+
+				EditorGUILayout.Space();
+				EditorGUILayout.LabelField("Editor Instantiation", EditorStyles.boldLabel);
+				{
+					EditorGUI.BeginChangeCheck();
+					defaultZSpacing = EditorGUILayout.Slider("Default Slot Z-Spacing", defaultZSpacing, -0.1f, 0f);
+					if (EditorGUI.EndChangeCheck())
+						EditorPrefs.SetFloat(DEFAULT_ZSPACING_KEY, defaultZSpacing);
+
+					SpineEditorUtilities.BoolPrefsField(ref defaultInstantiateLoop, DEFAULT_INSTANTIATE_LOOP_KEY, new GUIContent("Default Loop", "Spawn Spine GameObjects with loop enabled."));
+				}
+
+				EditorGUILayout.Space();
+				EditorGUILayout.LabelField("Handles and Gizmos", EditorStyles.boldLabel);
+				{
+					EditorGUI.BeginChangeCheck();
+					SpineHandles.handleScale = EditorGUILayout.Slider("Editor Bone Scale", SpineHandles.handleScale, 0.01f, 2f);
+					SpineHandles.handleScale = Mathf.Max(0.01f, SpineHandles.handleScale);
+					if (EditorGUI.EndChangeCheck()) {
+						EditorPrefs.SetFloat(SCENE_ICONS_SCALE_KEY, SpineHandles.handleScale);
+						SceneView.RepaintAll();
+					}
+				}
+				
+				#if NEW_TIMELINE_AS_PACKAGE
+				GUILayout.Space(20);
+				EditorGUILayout.LabelField("Timeline Support", EditorStyles.boldLabel);
+				using (new GUILayout.HorizontalScope()) {
+					EditorGUILayout.PrefixLabel("Timeline Package Support");
+
+					var requestState = SpineEditorUtilities.SpinePackageDependencyUtility.HandlePendingAsyncTimelineRequest();
+					using (new EditorGUI.DisabledGroupScope(requestState != SpineEditorUtilities.SpinePackageDependencyUtility.RequestState.NoRequestIssued)) {
+						if (GUILayout.Button("Enable", GUILayout.Width(64)))
+							SpineEditorUtilities.SpinePackageDependencyUtility.EnableTimelineSupport();
+						if (GUILayout.Button("Disable", GUILayout.Width(64)))
+							SpineEditorUtilities.SpinePackageDependencyUtility.DisableTimelineSupport();
+					}
+				}
+				#endif
+
+				GUILayout.Space(20);
+				EditorGUILayout.LabelField("3rd Party Settings", EditorStyles.boldLabel);
+				using (new GUILayout.HorizontalScope()) {
+					EditorGUILayout.PrefixLabel("Define TK2D");
+					if (GUILayout.Button("Enable", GUILayout.Width(64)))
+						SpineTK2DEditorUtility.EnableTK2D();
+					if (GUILayout.Button("Disable", GUILayout.Width(64)))
+						SpineTK2DEditorUtility.DisableTK2D();
+				}
+			}
+		#endif // !NEW_PREFERENCES_SETTINGS_PROVIDER
+		}
+
+		static void BoolPrefsField (ref bool currentValue, string editorPrefsKey, GUIContent label) {
+			EditorGUI.BeginChangeCheck();
+			currentValue = EditorGUILayout.Toggle(label, currentValue);
+			if (EditorGUI.EndChangeCheck())
+				EditorPrefs.SetBool(editorPrefsKey, currentValue);
+		}
+
+		static void FloatPrefsField (ref float currentValue, string editorPrefsKey, GUIContent label, float min = float.NegativeInfinity, float max = float.PositiveInfinity) {
+			EditorGUI.BeginChangeCheck();
+			currentValue = EditorGUILayout.DelayedFloatField(label, currentValue);
+			if (EditorGUI.EndChangeCheck()) {
+				currentValue = Mathf.Clamp(currentValue, min, max);
+				EditorPrefs.SetFloat(editorPrefsKey, currentValue);
+			}
+		}
+
+		public static void FloatPropertyField(SerializedProperty property, GUIContent label, float min = float.NegativeInfinity, float max = float.PositiveInfinity) {
+			EditorGUI.BeginChangeCheck();
+			property.floatValue = EditorGUILayout.DelayedFloatField(label, property.floatValue);
+			if (EditorGUI.EndChangeCheck()) {
+				property.floatValue = Mathf.Clamp(property.floatValue, min, max);
+			}
+		}
+
+		public static void ShaderPropertyField(SerializedProperty property, GUIContent label, string fallbackShaderName) {
+			var shader = (EditorGUILayout.ObjectField(label, Shader.Find(property.stringValue), typeof(Shader), false) as Shader);
+			property.stringValue = shader != null ? shader.name : fallbackShaderName;
+		}
+	}
+}

+ 12 - 0
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/Preferences.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 6d80c945a2e3436459a97b7a9a16ef46
+timeCreated: 1563311495
+licenseType: Free
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

File diff suppressed because it is too large
+ 39 - 1594
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineEditorUtilities.cs


+ 522 - 0
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineHandles.cs

@@ -0,0 +1,522 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated May 1, 2019. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2019, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS
+ * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+#pragma warning disable 0219
+
+#define SPINE_SKELETONMECANIM
+
+#if UNITY_2017_2_OR_NEWER
+#define NEWPLAYMODECALLBACKS
+#endif
+
+#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
+#define NEW_PREFAB_SYSTEM
+#endif
+
+#if UNITY_2018 || UNITY_2019 || UNITY_2018_3_OR_NEWER
+#define NEWHIERARCHYWINDOWCALLBACKS
+#endif
+
+#if UNITY_2018_3_OR_NEWER
+#define NEW_PREFERENCES_SETTINGS_PROVIDER
+#endif
+
+#if UNITY_2019_1_OR_NEWER
+#define NEW_TIMELINE_AS_PACKAGE
+#endif
+
+using UnityEngine;
+using UnityEditor;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Linq;
+using System.Reflection;
+using System.Globalization;
+
+namespace Spine.Unity.Editor {
+	using EventType = UnityEngine.EventType;
+
+	public static class SpineHandles {
+		internal static float handleScale = 1f;
+		public static Color BoneColor { get { return new Color(0.8f, 0.8f, 0.8f, 0.4f); } }
+		public static Color PathColor { get { return new Color(254/255f, 127/255f, 0); } }
+		public static Color TransformContraintColor { get { return new Color(170/255f, 226/255f, 35/255f); } }
+		public static Color IkColor { get { return new Color(228/255f,90/255f,43/255f); } }
+		public static Color PointColor { get { return new Color(1f, 1f, 0f, 1f);  } }
+
+		static Vector3[] _boneMeshVerts = {
+			new Vector3(0, 0, 0),
+			new Vector3(0.1f, 0.1f, 0),
+			new Vector3(1, 0, 0),
+			new Vector3(0.1f, -0.1f, 0)
+		};
+		static Mesh _boneMesh;
+		public static Mesh BoneMesh {
+			get {
+				if (_boneMesh == null) {
+					_boneMesh = new Mesh {
+						vertices = _boneMeshVerts,
+						uv = new Vector2[4],
+						triangles = new [] { 0, 1, 2, 2, 3, 0 }
+					};
+					_boneMesh.RecalculateBounds();
+					_boneMesh.RecalculateNormals();
+				}
+				return _boneMesh;
+			}
+		}
+
+		static Mesh _arrowheadMesh;
+		public static Mesh ArrowheadMesh {
+			get {
+				if (_arrowheadMesh == null) {
+					_arrowheadMesh = new Mesh {
+						vertices = new [] {
+							new Vector3(0, 0),
+							new Vector3(-0.1f, 0.05f),
+							new Vector3(-0.1f, -0.05f)
+						},
+						uv = new Vector2[3],
+						triangles = new [] { 0, 1, 2 }
+					};
+					_arrowheadMesh.RecalculateBounds();
+					_arrowheadMesh.RecalculateNormals();
+				}
+				return _arrowheadMesh;
+			}
+		}
+
+		static Material _boneMaterial;
+		static Material BoneMaterial {
+			get {
+				if (_boneMaterial == null) {
+					_boneMaterial = new Material(Shader.Find("Hidden/Spine/Bones"));
+					_boneMaterial.SetColor("_Color", SpineHandles.BoneColor);
+				}
+
+				return _boneMaterial;
+			}
+		}
+		public static Material GetBoneMaterial () {
+			BoneMaterial.SetColor("_Color", SpineHandles.BoneColor);
+			return BoneMaterial;
+		}
+
+		public static Material GetBoneMaterial (Color color) {
+			BoneMaterial.SetColor("_Color", color);
+			return BoneMaterial;
+		}
+
+		static Material _ikMaterial;
+		public static Material IKMaterial {
+			get {
+				if (_ikMaterial == null) {
+					_ikMaterial = new Material(Shader.Find("Hidden/Spine/Bones"));
+					_ikMaterial.SetColor("_Color", SpineHandles.IkColor);
+				}
+				return _ikMaterial;
+			}
+		}
+
+		static GUIStyle _boneNameStyle;
+		public static GUIStyle BoneNameStyle {
+			get {
+				if (_boneNameStyle == null) {
+					_boneNameStyle = new GUIStyle(EditorStyles.whiteMiniLabel) {
+						alignment = TextAnchor.MiddleCenter,
+						stretchWidth = true,
+						padding = new RectOffset(0, 0, 0, 0),
+						contentOffset = new Vector2(-5f, 0f)
+					};
+				}
+				return _boneNameStyle;
+			}
+		}
+
+		static GUIStyle _pathNameStyle;
+		public static GUIStyle PathNameStyle {
+			get {
+				if (_pathNameStyle == null) {
+					_pathNameStyle = new GUIStyle(SpineHandles.BoneNameStyle);
+					_pathNameStyle.normal.textColor = SpineHandles.PathColor;
+				}
+				return _pathNameStyle;
+			}
+		}
+
+		static GUIStyle _pointNameStyle;
+		public static GUIStyle PointNameStyle {
+			get {
+				if (_pointNameStyle == null) {
+					_pointNameStyle = new GUIStyle(SpineHandles.BoneNameStyle);
+					_pointNameStyle.normal.textColor = SpineHandles.PointColor;
+				}
+				return _pointNameStyle;
+			}
+		}
+
+		public static void DrawBoneNames (Transform transform, Skeleton skeleton, float positionScale = 1f) {
+			GUIStyle style = BoneNameStyle;
+			foreach (Bone b in skeleton.Bones) {
+				if (!b.Active) continue;
+				var pos = new Vector3(b.WorldX * positionScale, b.WorldY * positionScale, 0) + (new Vector3(b.A, b.C) * (b.Data.Length * 0.5f));
+				pos = transform.TransformPoint(pos);
+				Handles.Label(pos, b.Data.Name, style);
+			}
+		}
+
+		public static void DrawBones (Transform transform, Skeleton skeleton, float positionScale = 1f) {
+			float boneScale = 1.8f; // Draw the root bone largest;
+			DrawCrosshairs2D(skeleton.Bones.Items[0].GetWorldPosition(transform), 0.08f, positionScale);
+
+			foreach (Bone b in skeleton.Bones) {
+				if (!b.Active) continue;
+				DrawBone(transform, b, boneScale, positionScale);
+				boneScale = 1f;
+			}
+		}
+
+		static Vector3[] _boneWireBuffer = new Vector3[5];
+		static Vector3[] GetBoneWireBuffer (Matrix4x4 m) {
+			for (int i = 0, n = _boneMeshVerts.Length; i < n; i++)
+				_boneWireBuffer[i] = m.MultiplyPoint(_boneMeshVerts[i]);
+
+			_boneWireBuffer[4] = _boneWireBuffer[0]; // closed polygon.
+			return _boneWireBuffer;
+		}
+		public static void DrawBoneWireframe (Transform transform, Bone b, Color color, float skeletonRenderScale = 1f) {
+			Handles.color = color;
+			var pos = new Vector3(b.WorldX * skeletonRenderScale, b.WorldY * skeletonRenderScale, 0);
+			float length = b.Data.Length;
+
+			if (length > 0) {
+				Quaternion rot = Quaternion.Euler(0, 0, b.WorldRotationX);
+				Vector3 scale = Vector3.one * length * b.WorldScaleX * skeletonRenderScale;
+				const float my = 1.5f;
+				scale.y *= (SpineHandles.handleScale + 1) * 0.5f;
+				scale.y = Mathf.Clamp(scale.x, -my * skeletonRenderScale, my * skeletonRenderScale);
+				Handles.DrawPolyLine(GetBoneWireBuffer(transform.localToWorldMatrix * Matrix4x4.TRS(pos, rot, scale)));
+				var wp = transform.TransformPoint(pos);
+				DrawBoneCircle(wp, color, transform.forward, skeletonRenderScale);
+			} else {
+				var wp = transform.TransformPoint(pos);
+				DrawBoneCircle(wp, color, transform.forward, skeletonRenderScale);
+			}
+		}
+
+		public static void DrawBone (Transform transform, Bone b, float boneScale, float skeletonRenderScale = 1f) {
+			var pos = new Vector3(b.WorldX * skeletonRenderScale, b.WorldY * skeletonRenderScale, 0);
+			float length = b.Data.Length;
+			if (length > 0) {
+				Quaternion rot = Quaternion.Euler(0, 0, b.WorldRotationX);
+				Vector3 scale = Vector3.one * length * b.WorldScaleX * skeletonRenderScale;
+				const float my = 1.5f;
+				scale.y *= (SpineHandles.handleScale + 1f) * 0.5f;
+				scale.y = Mathf.Clamp(scale.x, -my * skeletonRenderScale, my * skeletonRenderScale);
+				SpineHandles.GetBoneMaterial().SetPass(0);
+				Graphics.DrawMeshNow(SpineHandles.BoneMesh, transform.localToWorldMatrix * Matrix4x4.TRS(pos, rot, scale));
+			} else {
+				var wp = transform.TransformPoint(pos);
+				DrawBoneCircle(wp, SpineHandles.BoneColor, transform.forward, boneScale * skeletonRenderScale);
+			}
+		}
+
+		public static void DrawBone (Transform transform, Bone b, float boneScale, Color color, float skeletonRenderScale = 1f) {
+			var pos = new Vector3(b.WorldX * skeletonRenderScale, b.WorldY * skeletonRenderScale, 0);
+			float length = b.Data.Length;
+			if (length > 0) {
+				Quaternion rot = Quaternion.Euler(0, 0, b.WorldRotationX);
+				Vector3 scale = Vector3.one * length * b.WorldScaleX;
+				const float my = 1.5f;
+				scale.y *= (SpineHandles.handleScale + 1f) * 0.5f;
+				scale.y = Mathf.Clamp(scale.x, -my, my);
+				SpineHandles.GetBoneMaterial(color).SetPass(0);
+				Graphics.DrawMeshNow(SpineHandles.BoneMesh, transform.localToWorldMatrix * Matrix4x4.TRS(pos, rot, scale));
+			} else {
+				var wp = transform.TransformPoint(pos);
+				DrawBoneCircle(wp, color, transform.forward, boneScale * skeletonRenderScale);
+			}
+		}
+
+		public static void DrawPaths (Transform transform, Skeleton skeleton) {
+			foreach (Slot s in skeleton.DrawOrder) {
+				var p = s.Attachment as PathAttachment;
+				if (p != null) SpineHandles.DrawPath(s, p, transform, true);
+			}
+		}
+
+		static float[] pathVertexBuffer;
+		public static void DrawPath (Slot s, PathAttachment p, Transform t, bool includeName) {
+			int worldVerticesLength = p.WorldVerticesLength;
+
+			if (pathVertexBuffer == null || pathVertexBuffer.Length < worldVerticesLength)
+				pathVertexBuffer = new float[worldVerticesLength];
+
+			float[] pv = pathVertexBuffer;
+			p.ComputeWorldVertices(s, pv);
+
+			var ocolor = Handles.color;
+			Handles.color = SpineHandles.PathColor;
+
+			Matrix4x4 m = t.localToWorldMatrix;
+			const int step = 6;
+			int n = worldVerticesLength - step;
+			Vector3 p0, p1, p2, p3;
+			for (int i = 2; i < n; i += step) {
+				p0 = m.MultiplyPoint(new Vector3(pv[i], pv[i+1]));
+				p1 = m.MultiplyPoint(new Vector3(pv[i+2], pv[i+3]));
+				p2 = m.MultiplyPoint(new Vector3(pv[i+4], pv[i+5]));
+				p3 = m.MultiplyPoint(new Vector3(pv[i+6], pv[i+7]));
+				DrawCubicBezier(p0, p1, p2, p3);
+			}
+
+			n += step;
+			if (p.Closed) {
+				p0 = m.MultiplyPoint(new Vector3(pv[n - 4], pv[n - 3]));
+				p1 = m.MultiplyPoint(new Vector3(pv[n - 2], pv[n - 1]));
+				p2 = m.MultiplyPoint(new Vector3(pv[0], pv[1]));
+				p3 = m.MultiplyPoint(new Vector3(pv[2], pv[3]));
+				DrawCubicBezier(p0, p1, p2, p3);
+			}
+
+			const float endCapSize = 0.05f;
+			Vector3 firstPoint = m.MultiplyPoint(new Vector3(pv[2], pv[3]));
+			SpineHandles.DrawDot(firstPoint, endCapSize);
+
+			//if (!p.Closed) SpineHandles.DrawDot(m.MultiplyPoint(new Vector3(pv[n - 4], pv[n - 3])), endCapSize);
+			if (includeName) Handles.Label(firstPoint + new Vector3(0,0.1f), p.Name, PathNameStyle);
+
+			Handles.color = ocolor;
+		}
+
+		public static void DrawDot (Vector3 position, float size) {
+			Handles.DotHandleCap(0, position, Quaternion.identity, size * HandleUtility.GetHandleSize(position), EventType.Ignore); //Handles.DotCap(0, position, Quaternion.identity, size * HandleUtility.GetHandleSize(position));
+		}
+
+		public static void DrawBoundingBoxes (Transform transform, Skeleton skeleton) {
+			foreach (var slot in skeleton.Slots) {
+				var bba = slot.Attachment as BoundingBoxAttachment;
+				if (bba != null) SpineHandles.DrawBoundingBox(slot, bba, transform);
+			}
+		}
+
+		public static void DrawBoundingBox (Slot slot, BoundingBoxAttachment box, Transform t) {
+			if (box.Vertices.Length <= 2) return; // Handle cases where user creates a BoundingBoxAttachment but doesn't actually define it.
+
+			var worldVerts = new float[box.WorldVerticesLength];
+			box.ComputeWorldVertices(slot, worldVerts);
+
+			Handles.color = Color.green;
+			Vector3 lastVert = Vector3.zero;
+			Vector3 vert = Vector3.zero;
+			Vector3 firstVert = t.TransformPoint(new Vector3(worldVerts[0], worldVerts[1], 0));
+			for (int i = 0; i < worldVerts.Length; i += 2) {
+				vert.x = worldVerts[i];
+				vert.y = worldVerts[i + 1];
+				vert.z = 0;
+
+				vert = t.TransformPoint(vert);
+
+				if (i > 0)
+					Handles.DrawLine(lastVert, vert);
+
+				lastVert = vert;
+			}
+
+			Handles.DrawLine(lastVert, firstVert);
+		}
+
+		public static void DrawPointAttachment (Bone bone, PointAttachment pointAttachment, Transform skeletonTransform) {
+			if (bone == null) return;
+			if (pointAttachment == null) return;
+
+			Vector2 localPos;
+			pointAttachment.ComputeWorldPosition(bone, out localPos.x, out localPos.y);
+			float localRotation = pointAttachment.ComputeWorldRotation(bone);
+			Matrix4x4 m = Matrix4x4.TRS(localPos, Quaternion.Euler(0, 0, localRotation), Vector3.one) * Matrix4x4.TRS(Vector3.right * 0.25f, Quaternion.identity, Vector3.one);
+
+			DrawBoneCircle(skeletonTransform.TransformPoint(localPos), SpineHandles.PointColor, Vector3.back, 1.3f);
+			DrawArrowhead(skeletonTransform.localToWorldMatrix * m);
+		}
+
+		public static void DrawConstraints (Transform transform, Skeleton skeleton, float skeletonRenderScale = 1f) {
+			Vector3 targetPos;
+			Vector3 pos;
+			bool active;
+			Color handleColor;
+			const float Thickness = 4f;
+			Vector3 normal = transform.forward;
+
+			// Transform Constraints
+			handleColor = SpineHandles.TransformContraintColor;
+			foreach (var tc in skeleton.TransformConstraints) {
+				var targetBone = tc.Target;
+				targetPos = targetBone.GetWorldPosition(transform, skeletonRenderScale);
+
+				if (tc.TranslateMix > 0) {
+					if (tc.TranslateMix != 1f) {
+						Handles.color = handleColor;
+						foreach (var b in tc.Bones) {
+							pos = b.GetWorldPosition(transform, skeletonRenderScale);
+							Handles.DrawDottedLine(targetPos, pos, Thickness);
+						}
+					}
+					SpineHandles.DrawBoneCircle(targetPos, handleColor, normal, 1.3f * skeletonRenderScale);
+					Handles.color = handleColor;
+					SpineHandles.DrawCrosshairs(targetPos, 0.2f, targetBone.A, targetBone.B, targetBone.C, targetBone.D, transform, skeletonRenderScale);
+				}
+			}
+
+			// IK Constraints
+			handleColor = SpineHandles.IkColor;
+			foreach (var ikc in skeleton.IkConstraints) {
+				Bone targetBone = ikc.Target;
+				targetPos = targetBone.GetWorldPosition(transform, skeletonRenderScale);
+				var bones = ikc.Bones;
+				active = ikc.Mix > 0;
+				if (active) {
+					pos = bones.Items[0].GetWorldPosition(transform, skeletonRenderScale);
+					switch (bones.Count) {
+					case 1: {
+							Handles.color = handleColor;
+							Handles.DrawLine(targetPos, pos);
+							SpineHandles.DrawBoneCircle(targetPos, handleColor, normal);
+							var m = bones.Items[0].GetMatrix4x4();
+							m.m03 = targetBone.WorldX * skeletonRenderScale;
+							m.m13 = targetBone.WorldY * skeletonRenderScale;
+							SpineHandles.DrawArrowhead(transform.localToWorldMatrix * m);
+							break;
+						}
+					case 2: {
+							Bone childBone = bones.Items[1];
+							Vector3 child = childBone.GetWorldPosition(transform, skeletonRenderScale);
+							Handles.color = handleColor;
+							Handles.DrawLine(child, pos);
+							Handles.DrawLine(targetPos, child);
+							SpineHandles.DrawBoneCircle(pos, handleColor, normal, 0.5f);
+							SpineHandles.DrawBoneCircle(child, handleColor, normal, 0.5f);
+							SpineHandles.DrawBoneCircle(targetPos, handleColor, normal);
+							var m = childBone.GetMatrix4x4();
+							m.m03 = targetBone.WorldX * skeletonRenderScale;
+							m.m13 = targetBone.WorldY * skeletonRenderScale;
+							SpineHandles.DrawArrowhead(transform.localToWorldMatrix * m);
+							break;
+						}
+					}
+				}
+				//Handles.Label(targetPos, ikc.Data.Name, SpineHandles.BoneNameStyle);
+			}
+
+			// Path Constraints
+			handleColor = SpineHandles.PathColor;
+			foreach (var pc in skeleton.PathConstraints) {
+				active = pc.TranslateMix > 0;
+				if (active)
+					foreach (var b in pc.Bones)
+						SpineHandles.DrawBoneCircle(b.GetWorldPosition(transform, skeletonRenderScale), handleColor, normal, 1f * skeletonRenderScale);
+			}
+		}
+
+		static void DrawCrosshairs2D (Vector3 position, float scale, float skeletonRenderScale = 1f) {
+			scale *= SpineHandles.handleScale * skeletonRenderScale;
+			Handles.DrawLine(position + new Vector3(-scale, 0), position + new Vector3(scale, 0));
+			Handles.DrawLine(position + new Vector3(0, -scale), position + new Vector3(0, scale));
+		}
+
+		static void DrawCrosshairs (Vector3 position, float scale, float a, float b, float c, float d, Transform transform, float skeletonRenderScale = 1f) {
+			scale *= SpineHandles.handleScale * skeletonRenderScale;
+
+			var xOffset = (Vector3)(new Vector2(a, c).normalized * scale);
+			var yOffset = (Vector3)(new Vector2(b, d).normalized * scale);
+			xOffset = transform.TransformDirection(xOffset);
+			yOffset = transform.TransformDirection(yOffset);
+
+			Handles.DrawLine(position + xOffset, position - xOffset);
+			Handles.DrawLine(position + yOffset, position - yOffset);
+		}
+
+		static void DrawArrowhead2D (Vector3 pos, float localRotation, float scale = 1f) {
+			scale *= SpineHandles.handleScale;
+
+			SpineHandles.IKMaterial.SetPass(0);
+			Graphics.DrawMeshNow(SpineHandles.ArrowheadMesh, Matrix4x4.TRS(pos, Quaternion.Euler(0, 0, localRotation), new Vector3(scale, scale, scale)));
+		}
+
+		static void DrawArrowhead (Vector3 pos, Quaternion worldQuaternion) {
+			Graphics.DrawMeshNow(SpineHandles.ArrowheadMesh, pos, worldQuaternion, 0);
+		}
+
+		static void DrawArrowhead (Matrix4x4 m) {
+			float s = SpineHandles.handleScale;
+			m.m00 *= s;
+			m.m01 *= s;
+			m.m02 *= s;
+			m.m10 *= s;
+			m.m11 *= s;
+			m.m12 *= s;
+			m.m20 *= s;
+			m.m21 *= s;
+			m.m22 *= s;
+
+			SpineHandles.IKMaterial.SetPass(0);
+			Graphics.DrawMeshNow(SpineHandles.ArrowheadMesh, m);
+		}
+
+		static void DrawBoneCircle (Vector3 pos, Color outlineColor, Vector3 normal, float scale = 1f) {
+			scale *= SpineHandles.handleScale;
+
+			Color o = Handles.color;
+			Handles.color = outlineColor;
+			float firstScale = 0.08f * scale;
+			Handles.DrawSolidDisc(pos, normal, firstScale);
+			const float Thickness = 0.03f;
+			float secondScale = firstScale - (Thickness * SpineHandles.handleScale * scale);
+
+			if (secondScale > 0f) {
+				Handles.color = new Color(0.3f, 0.3f, 0.3f, 0.5f);
+				Handles.DrawSolidDisc(pos, normal, secondScale);
+			}
+
+			Handles.color = o;
+		}
+
+		internal static void DrawCubicBezier (Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3) {
+			Handles.DrawBezier(p0, p3, p1, p2, Handles.color, Texture2D.whiteTexture, 2f);
+			//			const float dotSize = 0.01f;
+			//			Quaternion q = Quaternion.identity;
+			//			Handles.DotCap(0, p0, q, dotSize);
+			//			Handles.DotCap(0, p1, q, dotSize);
+			//			Handles.DotCap(0, p2, q, dotSize);
+			//			Handles.DotCap(0, p3, q, dotSize);
+			//			Handles.DrawLine(p0, p1);
+			//			Handles.DrawLine(p3, p2);
+		}
+	}
+}

+ 12 - 0
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineHandles.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: f4c97e41d3d90e24bbb40d634e6cc336
+timeCreated: 1563313275
+licenseType: Free
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 7 - 7
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SkeletonBaker.cs

@@ -88,7 +88,7 @@ namespace Spine.Unity.Editor {
 			}
 
 			string dataPath = AssetDatabase.GetAssetPath(skeletonDataAsset);
-			string controllerPath = dataPath.Replace(SpineEditorUtilities.AssetUtility.SkeletonDataSuffix, "_Controller").Replace(".asset", ".controller");
+			string controllerPath = dataPath.Replace(AssetUtility.SkeletonDataSuffix, "_Controller").Replace(".asset", ".controller");
 			UnityEditor.Animations.AnimatorController controller;
 			if (skeletonDataAsset.controller != null) {
 				controller = (UnityEditor.Animations.AnimatorController)skeletonDataAsset.controller;
@@ -295,7 +295,7 @@ namespace Spine.Unity.Editor {
 					}
 				}
 
-				GameObject prefabRoot = SpineEditorUtilities.EditorInstantiation.NewGameObject("root");
+				GameObject prefabRoot = EditorInstantiation.NewGameObject("root");
 
 				Dictionary<string, Transform> slotTable = new Dictionary<string, Transform>();
 				Dictionary<string, Transform> boneTable = new Dictionary<string, Transform>();
@@ -304,7 +304,7 @@ namespace Spine.Unity.Editor {
 				//create bones
 				for (int i = 0; i < skeletonData.Bones.Count; i++) {
 					var boneData = skeletonData.Bones.Items[i];
-					Transform boneTransform = SpineEditorUtilities.EditorInstantiation.NewGameObject(boneData.Name).transform;
+					Transform boneTransform = EditorInstantiation.NewGameObject(boneData.Name).transform;
 					boneTransform.parent = prefabRoot.transform;
 					boneTable.Add(boneTransform.name, boneTransform);
 					boneList.Add(boneTransform);
@@ -335,7 +335,7 @@ namespace Spine.Unity.Editor {
 				//create slots and attachments
 				for (int slotIndex = 0; slotIndex < skeletonData.Slots.Count; slotIndex++) {
 					var slotData = skeletonData.Slots.Items[slotIndex];
-					Transform slotTransform = SpineEditorUtilities.EditorInstantiation.NewGameObject(slotData.Name).transform;
+					Transform slotTransform = EditorInstantiation.NewGameObject(slotData.Name).transform;
 					slotTransform.parent = prefabRoot.transform;
 					slotTable.Add(slotData.Name, slotTransform);
 
@@ -399,7 +399,7 @@ namespace Spine.Unity.Editor {
 						} else
 							continue;
 
-						Transform attachmentTransform = SpineEditorUtilities.EditorInstantiation.NewGameObject(attachmentName).transform;
+						Transform attachmentTransform = EditorInstantiation.NewGameObject(attachmentName).transform;
 
 						attachmentTransform.parent = slotTransform;
 						attachmentTransform.localPosition = offset;
@@ -1425,7 +1425,7 @@ namespace Spine.Unity.Editor {
 			string atlasAssetPath = AssetDatabase.GetAssetPath(atlasAsset);
 			string atlasAssetDirPath = Path.GetDirectoryName(atlasAssetPath);
 			string bakedDirPath = Path.Combine(atlasAssetDirPath, atlasAsset.name);
-			string bakedPrefabPath = Path.Combine(bakedDirPath, SpineEditorUtilities.AssetUtility.GetPathSafeName(region.name) + ".prefab").Replace("\\", "/");
+			string bakedPrefabPath = Path.Combine(bakedDirPath, AssetUtility.GetPathSafeName(region.name) + ".prefab").Replace("\\", "/");
 
 			GameObject prefab = (GameObject)AssetDatabase.LoadAssetAtPath(bakedPrefabPath, typeof(GameObject));
 			GameObject root;
@@ -1436,7 +1436,7 @@ namespace Spine.Unity.Editor {
 				Directory.CreateDirectory(bakedDirPath);
 
 			if (prefab == null) {
-				root = SpineEditorUtilities.EditorInstantiation.NewGameObject("temp", typeof(MeshFilter), typeof(MeshRenderer));
+				root = EditorInstantiation.NewGameObject("temp", typeof(MeshFilter), typeof(MeshRenderer));
 				#if NEW_PREFAB_SYSTEM
 				prefab = PrefabUtility.SaveAsPrefabAsset(root, bakedPrefabPath);
 				#else

Some files were not shown because too many files changed in this diff