/** * Repairs missing pb_Object and pb_Entity references. It is based * on this article by Unity Gems: http://unitygems.com/lateral1/ */ using UnityEngine; using UnityEditor; using System.Collections.Generic; using System.Linq; using ProBuilder2.Common; using ProBuilder2.MeshOperations; namespace ProBuilder2.EditorCommon { /** * Extends MonoBehaviour Inspector, automatically fixing missing script * references (typically caused by ProBuilder upgrade process). */ [CustomEditor(typeof(MonoBehaviour))] public class pb_MissingScriptEditor : Editor { #region Members static bool applyDummyScript = true; ///< If true, any null components that can't be set will have this script applied to their reference, allowing us to later remove them. static float index = 0; ///< general idea of where we are in terms of processing this scene. static float total; ///< general idea of how many missing script references are in this scene. static bool doFix = false; ///< while true, the inspector will attempt to cycle to broken gameobjects until none are found. static List unfixable = new List(); ///< if a non-pb missing reference is encountered, need to let the iterator know not to bother, static MonoScript _mono_pb; ///< MonoScript assets static MonoScript _mono_pe; ///< MonoScript assets static MonoScript _mono_dummy; ///< MonoScript assets /** * Load the pb_Object and pb_Entity classes to MonoScript assets. Saves us from having to fall back on Reflection. */ static void LoadMonoScript() { GameObject go = new GameObject(); pb_Object pb = go.AddComponent(); pb_Entity pe = go.GetComponent(); if(pe == null) pe = go.AddComponent(); pb_DummyScript du = go.AddComponent(); _mono_pb = MonoScript.FromMonoBehaviour( pb ); _mono_pe = MonoScript.FromMonoBehaviour( pe ); _mono_dummy = MonoScript.FromMonoBehaviour( du ); DestroyImmediate(go); } public MonoScript pb_monoscript { get { if(_mono_pb == null) LoadMonoScript(); return _mono_pb; } } public MonoScript pe_monoscript { get { if(_mono_pe == null) LoadMonoScript(); return _mono_pe; } } public MonoScript dummy_monoscript { get { if(_mono_dummy == null) LoadMonoScript(); return _mono_dummy; } } #endregion [MenuItem("Tools/" + pb_Constant.PRODUCT_NAME + "/Repair/Repair Missing Script References")] public static void MenuRepairMissingScriptReferences() { FixAllScriptReferencesInScene(); } static void FixAllScriptReferencesInScene() { EditorApplication.ExecuteMenuItem("Window/Inspector"); Object[] all = Resources.FindObjectsOfTypeAll(typeof(GameObject)).Where(x => ((GameObject)x).GetComponents().Any(n => n == null) ).ToArray(); total = all.Length; unfixable.Clear(); if(total > 1) { Undo.RecordObjects(all, "Fix missing script references"); index = 0; doFix = true; Next(); } else { if( applyDummyScript ) DeleteDummyScripts(); EditorUtility.DisplayDialog("Success", "No missing ProBuilder script references found.", "Okay"); } } /** * Advance to the next gameobject with missing components. If none are found, display dialog and exit. */ static void Next() { bool earlyExit = false; if( EditorUtility.DisplayCancelableProgressBar("Repair ProBuilder Script References", "Fixing " + (int)Mathf.Floor(index+1) + " out of " + total + " objects in scene.", ((float)index/total) ) ) { earlyExit = true; doFix = false; } if(!earlyExit) { // Cycle through FindObjectsOfType on every Next() because using a static list didn't work for some reason. foreach(GameObject go in Resources.FindObjectsOfTypeAll(typeof(GameObject))) { if(go.GetComponents().Any(x => x == null) && !unfixable.Contains(go)) { if( (PrefabUtility.GetPrefabType(go) == PrefabType.PrefabInstance || PrefabUtility.GetPrefabType(go) == PrefabType.Prefab ) ) { GameObject pref = (GameObject)PrefabUtility.GetPrefabParent(go); if(pref && (pref.GetComponent() || pref.GetComponent())) { unfixable.Add(go); continue; } } if(go.hideFlags != HideFlags.None) { unfixable.Add(go); continue; } Selection.activeObject = go; return; } } } pb_Object[] pbs = (pb_Object[])Resources.FindObjectsOfTypeAll(typeof(pb_Object)); for(int i = 0; i < pbs.Length; i++) { EditorUtility.DisplayProgressBar("Checking ProBuilder Meshes", "Refresh " + (i+1) + " out of " + total + " objects in scene.", ((float)i/pbs.Length) ); try { pbs[i].ToMesh(); pbs[i].Refresh(); pbs[i].Optimize(); } catch (System.Exception e) { Debug.LogWarning("Failed reconstituting " + pbs[i].name + ". Proceeding with upgrade anyways. Usually this means a prefab is already fixed, and just needs to be instantiated to take effect.\n" + e.ToString()); } } EditorUtility.ClearProgressBar(); if( applyDummyScript ) DeleteDummyScripts(); EditorUtility.DisplayDialog("Success", "Successfully repaired " + total + " ProBuilder objects.", "Okay"); if(!pb_EditorSceneUtility.SaveCurrentSceneIfUserWantsTo()) Debug.LogWarning("Repaired script references will be lost on exit if this scene is not saved!"); doFix = false; skipEvent = true; } /** * SerializedProperty names found in pb_Entity. */ List PB_OBJECT_SCRIPT_PROPERTIES = new List() { "_sharedIndices", "_vertices", "_uv", "_sharedIndicesUV", "_quads" }; /** * SerializedProperty names found in pb_Object. */ List PB_ENTITY_SCRIPT_PROPERTIES = new List() { "pb", "userSetDimensions", "_entityType", "forceConvex" }; // Prevents ArgumentException after displaying 'Done' dialog. For some reason the Event loop skips layout phase after DisplayDialog. private static bool skipEvent = false; public override void OnInspectorGUI() { if(skipEvent && Event.current.type == EventType.Repaint) { skipEvent = false; return; } SerializedProperty scriptProperty = this.serializedObject.FindProperty("m_Script"); if(scriptProperty == null || scriptProperty.objectReferenceValue != null) { if(doFix) { if(Event.current.type == EventType.Repaint) { Next(); } } else { base.OnInspectorGUI(); } return; } int pbObjectMatches = 0, pbEntityMatches = 0; // Shows a detailed tree view of all the properties in this serializedobject. // GUILayout.Label( SerializedObjectToString(this.serializedObject) ); SerializedProperty iterator = this.serializedObject.GetIterator(); iterator.Next(true); while( iterator.Next(true) ) { if( PB_OBJECT_SCRIPT_PROPERTIES.Contains(iterator.name) ) pbObjectMatches++; if( PB_ENTITY_SCRIPT_PROPERTIES.Contains(iterator.name) ) pbEntityMatches++; } // If we can fix it, show the help box, otherwise just default inspector it up. if(pbObjectMatches >= 3 || pbEntityMatches >= 3) { EditorGUILayout.HelpBox("Missing Script Reference\n\nProBuilder can automatically fix this missing reference. To fix all references in the scene, click \"Fix All in Scene\". To fix just this one, click \"Reconnect\".", MessageType.Warning); } else { if(doFix) { if( applyDummyScript ) { index += .5f; scriptProperty.objectReferenceValue = dummy_monoscript; scriptProperty.serializedObject.ApplyModifiedProperties(); scriptProperty = this.serializedObject.FindProperty("m_Script"); scriptProperty.serializedObject.Update(); } else { unfixable.Add( ((Component)target).gameObject ); } Next(); GUIUtility.ExitGUI(); return; } else { base.OnInspectorGUI(); } return; } GUI.backgroundColor = Color.green; if(!doFix) { if(GUILayout.Button("Fix All in Scene")) { FixAllScriptReferencesInScene(); return; } } GUI.backgroundColor = Color.cyan; if((doFix && Event.current.type == EventType.Repaint) || GUILayout.Button("Reconnect")) { if(pbObjectMatches >= 3) // only increment for pb_Object otherwise the progress bar will fill 2x faster than it should { index++; } else { // Make sure that pb_Object is fixed first if we're automatically cycling objects. if(doFix && ((Component)target).gameObject.GetComponent() == null) return; } if(!doFix) { Undo.RegisterCompleteObjectUndo(target, "Fix missing reference."); } // Debug.Log("Fix: " + (pbObjectMatches > 2 ? "pb_Object" : "pb_Entity") + " " + ((Component)target).gameObject.name); scriptProperty.objectReferenceValue = pbObjectMatches >= 3 ? pb_monoscript : pe_monoscript; scriptProperty.serializedObject.ApplyModifiedProperties(); scriptProperty = this.serializedObject.FindProperty("m_Script"); scriptProperty.serializedObject.Update(); if(doFix) Next(); GUIUtility.ExitGUI(); } GUI.backgroundColor = Color.white; } /** * Scan the scene for gameObjects referencing `pb_DummyScript` and delete them. */ static void DeleteDummyScripts() { pb_DummyScript[] dummies = (pb_DummyScript[])Resources.FindObjectsOfTypeAll(typeof(pb_DummyScript)); dummies = dummies.Where(x => x.hideFlags == HideFlags.None).ToArray(); if(dummies.Length > 0) { int ret = EditorUtility.DisplayDialogComplex("Found Unrepairable Objects", "Repair script found " + dummies.Length + " missing components that could not be repaired. Would you like to delete those components now, or attempt to rebuild (ProBuilderize) them?", "Delete", "Cancel", "ProBuilderize"); switch(ret) { case 1: // cancel {} break; default: { // Delete and ProBuilderize if(ret == 2) { // Only interested in objects that have 2 null components (pb_Object and pb_Entity) Object[] broken = (Object[])Resources.FindObjectsOfTypeAll(typeof(GameObject)) .Where(x => !x.Equals(null) && x is GameObject && ((GameObject)x).GetComponents().Length == 2 && ((GameObject)x).GetComponent() != null && ((GameObject)x).GetComponent() != null && ((GameObject)x).GetComponent().sharedMesh != null ).ToArray(); broken = broken.Distinct().ToArray(); ProBuilder2.Actions.ProBuilderize.DoProBuilderize( System.Array.ConvertAll(broken, x => (GameObject) x) .Select(x => x.GetComponent()), pb_MeshImporter.Settings.Default); } // Always delete components Undo.RecordObjects(dummies.Select(x=>x.gameObject).ToArray(), "Delete Broken Scripts"); for(int i = 0; i < dummies.Length; i++) GameObject.DestroyImmediate( dummies[i] ); } break; } } } /** * Returns a formatted string with all properties in serialized object. */ static string SerializedObjectToString(SerializedObject serializedObject) { System.Text.StringBuilder sb = new System.Text.StringBuilder(); if(serializedObject == null) { sb.Append("NULL"); return sb.ToString(); } SerializedProperty iterator = serializedObject.GetIterator(); iterator.Next(true); while( iterator.Next(true) ) { string tabs = ""; for(int i = 0; i < iterator.depth; i++) tabs += "\t"; sb.AppendLine(tabs + iterator.name + (iterator.propertyType == SerializedPropertyType.ObjectReference && iterator.type.Contains("Component") && iterator.objectReferenceValue == null ? " -> NULL" : "") ); tabs += " - "; sb.AppendLine(tabs + "Type: (" + iterator.type + " / " + iterator.propertyType + " / " + " / " + iterator.name + ")"); sb.AppendLine(tabs + iterator.propertyPath); sb.AppendLine(tabs + "Value: " + SerializedPropertyValue(iterator)); } return sb.ToString(); } /** * Return a string from the value of a SerializedProperty. */ static string SerializedPropertyValue(SerializedProperty sp) { switch(sp.propertyType) { case SerializedPropertyType.Integer: return sp.intValue.ToString(); case SerializedPropertyType.Boolean: return sp.boolValue.ToString(); case SerializedPropertyType.Float: return sp.floatValue.ToString(); case SerializedPropertyType.String: return sp.stringValue.ToString(); case SerializedPropertyType.Color: return sp.colorValue.ToString(); case SerializedPropertyType.ObjectReference: return (sp.objectReferenceValue == null ? "null" : sp.objectReferenceValue.name); case SerializedPropertyType.LayerMask: return sp.intValue.ToString(); case SerializedPropertyType.Enum: return sp.enumValueIndex.ToString(); case SerializedPropertyType.Vector2: return sp.vector2Value.ToString(); case SerializedPropertyType.Vector3: return sp.vector3Value.ToString(); // Not public api as of 4.3? // case SerializedPropertyType.Vector4: // return sp.vector4Value.ToString(); case SerializedPropertyType.Rect: return sp.rectValue.ToString(); case SerializedPropertyType.ArraySize: return sp.intValue.ToString(); case SerializedPropertyType.Character: return "Character"; case SerializedPropertyType.AnimationCurve: return sp.animationCurveValue.ToString(); case SerializedPropertyType.Bounds: return sp.boundsValue.ToString(); case SerializedPropertyType.Gradient: return "Gradient"; default: return "Unknown type"; } } } }