Search Unity

  1. We would like to hear your feedback about Unity and our products. Click here for more information.
    Dismiss Notice

Tools API

Discussion in '2019.1 Beta' started by Prodigga, Nov 24, 2018.

  1. Prodigga

    Prodigga

    Joined:
    Apr 13, 2011
    Posts:
    720
    Any more information? Documentation? Feature breakdown? This sounds really cool.
     
    Peter77 likes this.
  2. kaarrrllll

    kaarrrllll

    Unity Technologies

    Joined:
    Aug 24, 2017
    Posts:
    141
    Hey, yes there is API documentation for this coming, but it looks like the online docs haven't caught up to the 2019.1 alpha yet.

    The gist is that you can now write tools that behave like the built-in transform tools, and are accessible from the top toolbar.



    To get you started, here are two examples. One is a CustomEditor tool that applies to any selection with a MeshFilter component, the other is a Global tool that can be used with any selection.

    Code (CSharp):
    1. // CustomEditor tool example.
    2. // Shows a billboard at each vertex position on a selected mesh.
    3.  
    4. using System.Collections.Generic;
    5. using System.Linq;
    6. using UnityEngine;
    7. using UnityEditor;
    8. using UnityEditor.EditorTools;
    9. using UnityEngine.Rendering;
    10.  
    11. // By passing `typeof(MeshFilter)` as the second argument, we register VertexTool as a CustomEditor tool to be presented
    12. // when the current selection contains a MeshFilter component.
    13. [EditorTool("Show Vertices", typeof(MeshFilter))]
    14. class VertexTool : EditorTool
    15. {
    16.     struct TransformAndPositions
    17.     {
    18.         public Transform transform;
    19.         public IList<Vector3> positions;
    20.     }
    21.  
    22.     IEnumerable<TransformAndPositions> m_Vertices;
    23.     GUIContent m_ToolbarIcon;
    24.  
    25.     public override GUIContent toolbarIcon
    26.     {
    27.         get
    28.         {
    29.             if (m_ToolbarIcon == null)
    30.             {
    31.                 // Usually you'll want to use an icon (19x18 px to match Unity's icons)
    32.                 var icon19x18 = AssetDatabase.LoadAssetAtPath<Texture2D>("Assets/Examples/Icons/VertexTool.png");
    33.  
    34.                 if(icon19x18 != null)
    35.                     m_ToolbarIcon = new GUIContent(icon19x18, "Vertex Visualization Tool");
    36.                 else
    37.                     m_ToolbarIcon = new GUIContent("Vertex Tool", "Vertex Visualization Tool");
    38.             }
    39.  
    40.             return m_ToolbarIcon;
    41.         }
    42.     }
    43.  
    44.     // Called when an EditorTool is made the active tool.
    45.     public override void OnActivate()
    46.     {
    47.         Selection.selectionChanged += RebuildVertexPositions;
    48.         RebuildVertexPositions();
    49.     }
    50.  
    51.     // Called when the active tool is changed.
    52.     public override void OnDeactivate()
    53.     {
    54.         Selection.selectionChanged -= RebuildVertexPositions;
    55.     }
    56.  
    57.     void RebuildVertexPositions()
    58.     {
    59.         m_Vertices = targets.Select(x =>
    60.         {
    61.             return new TransformAndPositions()
    62.             {
    63.                 transform = ((MeshFilter)x).transform,
    64.                 positions = ((MeshFilter)x).sharedMesh.vertices
    65.             };
    66.         }).ToArray();
    67.     }
    68.  
    69.     // If you've implemented scene tools before, think of this like the `OnSceneGUI` method. This is where you put the
    70.     // implementation of your tool.
    71.     public override void OnToolGUI(EditorWindow window)
    72.     {
    73.         var evt = Event.current;
    74.  
    75.         var zTest = Handles.zTest;
    76.         Handles.zTest = CompareFunction.LessEqual;
    77.  
    78.         foreach (var entry in m_Vertices)
    79.         {
    80.             var size = HandleUtility.GetHandleSize(entry.transform.position) * .05f;
    81.             DrawHandleCaps(entry.transform.localToWorldMatrix, entry.positions, size);
    82.         }
    83.  
    84.         Handles.zTest = zTest;
    85.     }
    86.  
    87.     static void DrawHandleCaps(Matrix4x4 matrix, IList<Vector3> positions, float size)
    88.     {
    89.         if (Event.current.type != EventType.Repaint)
    90.             return;
    91.  
    92.         Vector3 sideways = (Camera.current == null ? Vector3.right : Camera.current.transform.right) * size;
    93.         Vector3 up = (Camera.current == null ? Vector3.up : Camera.current.transform.up) * size;
    94.         Color col = Handles.color * new Color(1, 1, 1, 0.99f);
    95.  
    96.         // After drawing the first dot cap, the handle material and matrix are set up, so there's no need to keep
    97.         // resetting the state.
    98.         Handles.DotHandleCap(0, matrix.MultiplyPoint(positions[0]), Quaternion.identity,
    99.             HandleUtility.GetHandleSize(matrix.MultiplyPoint(positions[0])) * .05f, EventType.Repaint);
    100.  
    101.         GL.Begin(GL.QUADS);
    102.  
    103.         for (int i = 1, c = positions.Count; i < c; i ++)
    104.         {
    105.             var position = matrix.MultiplyPoint(positions[i]);
    106.  
    107.             GL.Color(col);
    108.             GL.Vertex(position + sideways + up);
    109.             GL.Vertex(position + sideways - up);
    110.             GL.Vertex(position - sideways - up);
    111.             GL.Vertex(position - sideways + up);
    112.         }
    113.  
    114.         GL.End();
    115.     }
    116. }
    117.  
    Code (CSharp):
    1. // Example of a global tool.
    2. // This is a "super" transform handle that shows the position handles for all 6 directions, as well as a rotation
    3. // handle.
    4.  
    5. using UnityEditor;
    6. using UnityEditor.EditorTools;
    7. using UnityEngine;
    8.  
    9. [EditorTool("Super Transform Tool")]
    10. public class SimpleGlobalTool : EditorTool
    11. {
    12.     GUIContent m_ToolbarIcon;
    13.  
    14.     public override GUIContent toolbarIcon
    15.     {
    16.         get
    17.         {
    18.             if (m_ToolbarIcon == null)
    19.                 m_ToolbarIcon = new GUIContent(
    20.                     AssetDatabase.LoadAssetAtPath<Texture2D>("Assets/Examples/Icons/SimpleIcon.png"),
    21.                     "Simple Global Tool");
    22.             return m_ToolbarIcon;
    23.         }
    24.     }
    25.  
    26.     public override void OnToolGUI(EditorWindow window)
    27.     {
    28.         var sceneView = window as SceneView;
    29.  
    30.         if (sceneView == null)
    31.             return;
    32.  
    33.         foreach (var trs in Selection.transforms)
    34.         {
    35.             EditorGUI.BeginChangeCheck();
    36.  
    37.             var rot = trs.rotation;
    38.             var pos = trs.position;
    39.  
    40.             Handles.color = Color.green;
    41.             pos = Handles.Slider(pos, trs.up);
    42.             pos = Handles.Slider(pos, -trs.up);
    43.  
    44.             Handles.color = Color.red;
    45.             pos = Handles.Slider(pos, trs.right);
    46.             pos = Handles.Slider(pos, -trs.right);
    47.  
    48.             Handles.color = Color.blue;
    49.             pos = Handles.Slider(pos, trs.forward);
    50.             pos = Handles.Slider(pos, -trs.forward);
    51.  
    52.             rot = Handles.RotationHandle(rot, pos);
    53.  
    54.             if (EditorGUI.EndChangeCheck())
    55.             {
    56.                 Undo.RecordObject(trs, "Simple Transform Tool");
    57.                 trs.position = pos;
    58.                 trs.rotation = rot;
    59.             }
    60.         }
    61.     }
    62. }
    63.  
     
  3. Prodigga

    Prodigga

    Joined:
    Apr 13, 2011
    Posts:
    720
    Amazing response! Thanks for your time!
     
    kaarrrllll likes this.
  4. kaarrrllll

    kaarrrllll

    Unity Technologies

    Joined:
    Aug 24, 2017
    Posts:
    141
  5. Prodigga

    Prodigga

    Joined:
    Apr 13, 2011
    Posts:
    720
    Sorry to dig this up but would you be able to provide an example that uses a window on the scene GUI too? (IE a mock vertex painting tool with brush settings in a GUI.window). I noticed the tool window and the camera preview windows automatically stack on top of one another, for example. When I was writing my tool, I naturally wanted to have a window for my tool in the scene view, too, but I don't know what the correct way to do it is. I'd want my tool window to fit in this stack of other windows.
     
  6. Prodigga

    Prodigga

    Joined:
    Apr 13, 2011
    Posts:
    720
    It seems the API has changed in b4, and there is no longer an Activate and Deactivate method to override. I am doing a test now to see if OnEnable and OnDisable is a good replacement to those 2 methods, but I thought I'd post here for others and point out that the documentation is now wrong, too. The examples on this page overrides the Activate and Deactivate methods which no longer exist.

    https://docs.unity3d.com/2019.1/Documentation/ScriptReference/EditorTools.EditorToolAttribute.html

    Also, if an example using scene gui windows could be added @kaarrrllll that would be fantastic. (See my previous post) We are flying blind here :)

    Edit:

    No, OnEnable and OnDisable are not good replacements. OnEnable gets called once and thats it. It doesn't tell you if the tool has been activated/deactivated. What is the new recommended way of doing this?

    Edit2:

    For others having the same issue, here is my temporary workaround:

    Code (CSharp):
    1. #region Temporary workaround for Activate and Deactive methods being missing..
    2.    
    3.     private static PaintedGroundTool _instance;
    4.     private static bool _isToolActivated;
    5.     [InitializeOnLoadMethod]
    6.     static void CheckForToolChange()
    7.     {
    8.         EditorTools.activeToolChanged += () =>
    9.         {
    10.             var toolIsActive = EditorTools.activeToolType == typeof(PaintedGroundTool);
    11.             if (toolIsActive && _isToolActivated == false)
    12.             {
    13.                 _isToolActivated = true;
    14.                 _instance.Activate();
    15.             }
    16.             else if(toolIsActive == false &&  _isToolActivated)
    17.             {
    18.                 _isToolActivated = false;
    19.                 _instance.Deactivate();
    20.             }
    21.         };
    22.     }
    23.    
    24.     void OnEnable()
    25.     {
    26.         _instance = this;
    27.     }
    28.  
    29. #endregion
    30.  
     
    Last edited: Feb 21, 2019
  7. kaarrrllll

    kaarrrllll

    Unity Technologies

    Joined:
    Aug 24, 2017
    Posts:
    141
    You pretty much had it, the correct way is to use `activeToolChanging` and `activeToolChanged` to get the state of the current tool.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. using UnityEditor.EditorTools;
    4.  
    5. [EditorTool("Some Tool")]
    6. public class SomeTool : EditorTool
    7. {
    8.     public override GUIContent toolbarIcon { get; }
    9.  
    10.     Rect m_ToolSettingsWindow = new Rect(10f, 30f, 300f, 100f);
    11.     bool m_ResetWindow;
    12.  
    13.     // OnEnable/Disable should be used to load and unload resources
    14.     void OnEnable()
    15.     {
    16.         EditorTools.activeToolChanging += OnActiveToolWillChange;
    17.         EditorTools.activeToolChanged += OnActiveToolDidChange;
    18.     }
    19.  
    20.     void OnDisable()
    21.     {
    22.         EditorTools.activeToolChanging -= OnActiveToolWillChange;
    23.         EditorTools.activeToolChanged -= OnActiveToolDidChange;
    24.     }
    25.  
    26.     void OnActiveToolWillChange()
    27.     {
    28.         if (EditorTools.IsActiveTool(this))
    29.             Debug.Log("De-activating SomeTool");
    30.     }
    31.  
    32.     void OnActiveToolDidChange()
    33.     {
    34.         if (EditorTools.IsActiveTool(this))
    35.         {
    36.             Debug.Log("Activating SomeTool");
    37.             m_ResetWindow = true;
    38.         }
    39.     }
    40.  
    41.     public override void OnToolGUI(EditorWindow window)
    42.     {
    43.         // Reset the position of the settings window to bottom right of the screen
    44.         if (m_ResetWindow)
    45.         {
    46.             m_ResetWindow = false;
    47.             m_ToolSettingsWindow.x = window.position.width - 210f;
    48.             m_ToolSettingsWindow.y = window.position.height - 160f;
    49.             m_ToolSettingsWindow.width = 200f;
    50.             m_ToolSettingsWindow.height = 150f;
    51.         }
    52.  
    53.         // The Context Window used by Camera preview and Tool modes is private API, but you can approximate the effect
    54.         // with a GUI.Window
    55.         m_ToolSettingsWindow = GUI.Window(42, m_ToolSettingsWindow, InSceneWindow, "My Tool Settings");
    56.     }
    57.  
    58.     void InSceneWindow(int windowId)
    59.     {
    60.         GUI.DragWindow(new Rect(0f, 0f, m_ToolSettingsWindow.width, 24f));
    61.  
    62.         GUILayout.Label("Hello from the in-scene window");
    63.     }
    64. }
    65.  
     
    Prodigga likes this.
  8. Mr_Tactic

    Mr_Tactic

    Joined:
    Nov 29, 2013
    Posts:
    9
    Unity 2019.1.1.f1

    EditorTools.activeToolChanging and EditorTools.activeToolChanged are missing

    But this code works for me

    Code (CSharp):
    1. // CustomEditor tool example.
    2. // Shows a billboard at each vertex position on a selected mesh.
    3. using System.Collections.Generic;
    4. using System.Linq;
    5. using UnityEngine;
    6. using UnityEditor;
    7. using UnityEditor.EditorTools;
    8. using UnityEngine.Rendering;
    9. // By passing `typeof(MeshFilter)` as the second argument, we register VertexTool as a CustomEditor tool to be presented
    10. // when the current selection contains a MeshFilter component.
    11. [EditorTool("Show Vertices", typeof(MeshFilter))]
    12. class VertexTool : EditorTool
    13. {
    14.     struct TransformAndPositions
    15.     {
    16.         public Transform transform;
    17.         public IList<Vector3> positions;
    18.     }
    19.     IEnumerable<TransformAndPositions> m_Vertices;
    20.     GUIContent m_ToolbarIcon;
    21.     public override GUIContent toolbarIcon
    22.     {
    23.         get
    24.         {
    25.             if (m_ToolbarIcon == null)
    26.             {
    27.                 // Usually you'll want to use an icon (19x18 px to match Unity's icons)
    28.                 var icon19x18 = AssetDatabase.LoadAssetAtPath<Texture2D>("Assets/Examples/Icons/VertexTool.png");
    29.                 if(icon19x18 != null)
    30.                     m_ToolbarIcon = new GUIContent(icon19x18, "Vertex Visualization Tool");
    31.                 else
    32.                     m_ToolbarIcon = new GUIContent("Vertex Tool", "Vertex Visualization Tool");
    33.             }
    34.             return m_ToolbarIcon;
    35.         }
    36.     }
    37.     // Called when an EditorTool is made the active tool.
    38.     public void OnEnable()
    39.     {
    40.         Selection.selectionChanged += RebuildVertexPositions;
    41.         RebuildVertexPositions();
    42.     }
    43.     // Called when the active tool is changed.
    44.     public void OnDisable()
    45.     {
    46.         Selection.selectionChanged -= RebuildVertexPositions;
    47.     }
    48.     void RebuildVertexPositions()
    49.     {
    50.         m_Vertices = targets.Select(x =>
    51.         {
    52.             return new TransformAndPositions()
    53.             {
    54.                 transform = ((MeshFilter)x).transform,
    55.                 positions = ((MeshFilter)x).sharedMesh.vertices
    56.             };
    57.         }).ToArray();
    58.     }
    59.     // If you've implemented scene tools before, think of this like the `OnSceneGUI` method. This is where you put the
    60.     // implementation of your tool.
    61.     public override void OnToolGUI(EditorWindow window)
    62.     {
    63.         var evt = Event.current;
    64.         var zTest = Handles.zTest;
    65.         Handles.zTest = CompareFunction.LessEqual;
    66.         foreach (var entry in m_Vertices)
    67.         {
    68.             var size = HandleUtility.GetHandleSize(entry.transform.position) * .05f;
    69.             DrawHandleCaps(entry.transform.localToWorldMatrix, entry.positions, size);
    70.         }
    71.         Handles.zTest = zTest;
    72.     }
    73.     static void DrawHandleCaps(Matrix4x4 matrix, IList<Vector3> positions, float size)
    74.     {
    75.         if (Event.current.type != EventType.Repaint)
    76.             return;
    77.         Vector3 sideways = (Camera.current == null ? Vector3.right : Camera.current.transform.right) * size;
    78.         Vector3 up = (Camera.current == null ? Vector3.up : Camera.current.transform.up) * size;
    79.         Color col = Handles.color * new Color(1, 1, 1, 0.99f);
    80.         // After drawing the first dot cap, the handle material and matrix are set up, so there's no need to keep
    81.         // resetting the state.
    82.         Handles.DotHandleCap(0, matrix.MultiplyPoint(positions[0]), Quaternion.identity,
    83.             HandleUtility.GetHandleSize(matrix.MultiplyPoint(positions[0])) * .05f, EventType.Repaint);
    84.         GL.Begin(GL.QUADS);
    85.         for (int i = 1, c = positions.Count; i < c; i ++)
    86.         {
    87.             var position = matrix.MultiplyPoint(positions[i]);
    88.             GL.Color(col);
    89.             GL.Vertex(position + sideways + up);
    90.             GL.Vertex(position + sideways - up);
    91.             GL.Vertex(position - sideways - up);
    92.             GL.Vertex(position - sideways + up);
    93.         }
    94.         GL.End();
    95.     }
    96. }
    97.  
     
  9. Xarbrough

    Xarbrough

    Joined:
    Dec 11, 2014
    Posts:
    379
    Is there a way to add a keyboard shortcut for custom EditorTools to the new 2019 Shortcuts Manager? I assume I can still use the previous approach with a static method and MenuItem with magic string, but since Unity now has proper hotkey handling, I assume we should be able to use for custom tools as well.

    Edit: I simply overlooked the new Shortcut API and thought there would be some way to add shortcuts via the tool itself, but here is the simple working code:

    Code (CSharp):
    1. [Shortcut("My Project/My Tool", KeyCode.Y)]
    2. private static void Shortcut()
    3. {
    4.     EditorTools.SetActiveTool(typeof(MyToolClass));
    5. }
    Pretty nice and easy to use! I like the way we can provide default values, but then everything simple gets drawn in the ShortcutManager UI and users change what they want. Much much better than life before. :)
     
    Last edited: May 7, 2019
    kaarrrllll likes this.