Extended Scripting API

This is the documentation related to the Script packaging and the creation and manipulation of Toon Boom Animation's vector drawings using the scripting interface.
This page contains the following sections:

Package Loading

At startup time, the JavaScript package manager loads packages from different the different supported locations. Those locations are:

  • The "packages" folder from the preferences. The "packages" folder is located in the scripts folder. Ex: C:\Users\\AppData\Roaming\Toon Boom Animation\Toon Boom Harmony Premium\2000-scripts\packages
  • The "packages" folder from the Harmony installation folder. Ex: C:\Program Files (x86)\Toon Boom Animation\Toon Boom Harmony 20 Premium\Plugins\ScriptingInterfaces\resources\packages
  • A location defined by the TB_EXTERNAL_SCRIPT_PACKAGES_FOLDER preference.

The users script package folder is located in the user's preferences and is different on every platform. On macOS, it will be located in the user's home in the folder: "Library/Preferences/Toon\ Boom\ Animation/Toon\ Boom\ Harmony\ Premium/2000-scripts/packages". As one can see, it also depends on the version of the software. The system packages folder is located in the installation folder under Plugins/ScriptingInterfaces/resources/packages. It is always scanned for packages and its packages are loaded. The TB_EXTERNAL_SCRIPT_PACKAGES_FOLDER preference is a list of semicolon (;) separated folders on Windows and colon (:) separated folder on macOS and Linux.

Examples of tools, toolbars and views can be found in "Plugins/ScriptingInterfaces/resources/beta-packages". To use the examples, copy their folder in the script user preference folder or in at the located defined by TB_EXTERNAL_SCRIPT_PACKAGES_FOLDER.

The loaded script toolbar packages will be found in the Toolbars menu under the "Custom Tools" entry. The loaded script drawing tools will be found under Drawing -> Drawing Tools -> "name-of-the-tool"

Creating a Package

To create a package, you must:

  • Create a folder named packages in your script preference folder or create an empty folder somewhere on your hard drive and define the preference: TB_EXTERNAL_SCRIPT_PACKAGES_FOLDER to point to that folder.
  • Inside the created packages folder, create a folder named after your package. e.g. MyPackage
  • In this new folder, create a file named configure.js that defines and export a function named configure.

Here is a minimal configure.js file:

function configure(packageFolder, packageName)
{
  MessageLog.trace("Package " + packageName + " configure was called in folder: " + packageFolder);
}
exports.configure = configure;

Inside the package folder, it is possible to create a folder named icons. This folder will be added automatically to the icon search path of Harmony.

If you select to specify the script packages folder using the preference, here is an example of how to do so using the Script Editor. This example demonstrate how to add the HarmonyScriptPackages folder located inside the home directory to the list of scanned locations. You can refer to the Harmony scripting documentation for more information about the preferences API.

function defineScriptPackageFolder()
{
  preferences.setString("TB_EXTERNAL_SCRIPT_PACKAGES_FOLDER", System.getenv("HOME") + "/HarmonyScriptPackages");
}

Now, you could create the following folder hierarchy in your home folder. You can fill the configure.js file with the example above.

HarmonyScriptPackages/
   MyPackage/
     icons/
     configure.js

After setting up the TB_EXTERNAL_SCRIPT_PACKAGES_FOLDER preference or modifying the configuration file (configure.js), restart Harmony. A message should appear in the Message Log view telling you that your package was loaded.

Inside a package, the configure() function is the entry point where the tools, toolbars and menu items are installed.

Here is a small example of a real life example of a configure function.

function configure(packageFolder, packageName) {
  // Filter out modes we do not want to add the package in
  if (about.isPaintMode())
    return;

  // Define script actions with scripted validators
  // This action toggles a preference value
  var toggleCoordinatesAction = {
    id: "com.toonboom.toggleMouseCoordinateDisplay",
    text: "Toggle Mouse Coordinates Display Settings",
    icon: "earth.png",
    checkable: true,
    isEnabled: true,
    isChecked: preferences.getBool("DRAWING_VIEW_SHOW_RAW_MOUSE_COORDINATES", false),
    onPreferenceChanged: function () {
      this.isChecked = preferences.getBool("DRAWING_VIEW_SHOW_RAW_MOUSE_COORDINATES", false);
    },
    onTrigger: function () {
      this.isChecked = !this.isChecked;
      preferences.setBool("DRAWING_VIEW_SHOW_RAW_MOUSE_COORDINATES", this.isChecked);
    }
  };
  ScriptManager.addAction(toggleCoordinatesAction);

  // Add the action in a menu item
  // To know the targetMeniId, you must
  // open the menus.xml file located in the
  // Harmony resources folder. The menu id is hierarchical.
  ScriptManager.addMenuItem({
    targetMenuId: "View",
    id: toggleCoordinatesAction.id,
    text: toggleCoordinatesAction.text,
    action: toggleCoordinatesAction.id
  });

  // Another useful preference toggler
  var toggleShowParticlesAsDotAction = {
    id: "com.toonboom.ParticleShowParticlesAsDotsInOpenGL",
    text: "Toggle Show Particles as Dots",
    icon: "dots.png",
    checkable: true,
    isEnabled: true,
    isChecked: preferences.getBool("ParticleShowParticlesAsDotsInOpenGL", false),
    onPreferenceChanged: function () {
      this.isChecked = preferences.getBool("ParticleShowParticlesAsDotsInOpenGL", false);
    },
    onTrigger: function () {
      this.isChecked = !this.isChecked;
      preferences.setBool("ParticleShowParticlesAsDotsInOpenGL", this.isChecked);
      view.refreshViews();
    }
  };
  ScriptManager.addAction(toggleShowParticlesAsDotAction);

  // An action that reacts on selection change to
  // update its isEnabled state.
  var toggleCacheAction = {
    id: "com.toonboom.toggleCacheAction",
    text: "Toggle the Cached flag of the selected nodes",
    icon: "cache.png",
    checkable: false,
    isEnabled: false,
    onSelectionChanged: function () {
      this.isEnabled = selection.numberOfNodesSelected();
    },
    onTrigger: function () {
      scene.beginUndoRedoAccum("TOGGLE CACHED OF SELECTION");
      var msg = ""
      var statusDefined = false;
      var status = true;
      var sel = selection.selectedNodes();
      sel.forEach(function (n) {
        var v = node.getCached(n);
        if (statusDefined == false) {
          statusDefined = true;
          status = !v;
        }
        v = status;
        node.setCached(n, v);
        msg += "Node " + n + " toggle to " + v + "\n";
      });
      if (status)
        selection.clearSelection();
      scene.endUndoRedoAccum();
      MessageLog.trace(msg);
    }
  };
  ScriptManager.addAction(toggleCacheAction);

  //---------------------------
  //Create Toolbar
  //---------------------------

  var customToolToolbar = new ScriptToolbarDef({
    id: "com.toonboom.MyDenoToolbar",
    text: "Custom Toolbar",
    customizable: true
  });

  // Add buttons in the toolbar using the
  // actions defined earlier
  
  customToolToolbar.addButton({
    text: toggleCoordinatesAction.text,
    icon: toggleCoordinatesAction.icon,
    checkable: toggleCoordinatesAction.checkable,
    action: toggleCoordinatesAction.id
  });

  customToolToolbar.addButton({
    text: toggleShowParticlesAsDotAction.text,
    icon: toggleShowParticlesAsDotAction.icon,
    checkable: toggleShowParticlesAsDotAction.checkable,
    action: toggleShowParticlesAsDotAction.id
  });

  customToolToolbar.addButton({
    text: toggleCacheAction.text,
    icon: toggleCacheAction.icon,
    checkable: toggleCacheAction.checkable,
    action: toggleCacheAction.id
  });
 
  // This final step is crucial, it will add the
  // toolbar to the Harmony toolbar system.
  ScriptManager.addToolbar(customToolToolbar);
}

// Export the configure function to the external
exports.configure = configure;

Manipulating the vector drawings

There are many types of operations that can be performed on vector drawings using the script API. These operations can take user inputs or determine their inputs automatically. For example, a user can make a script to create a circle of a certain radius in the center of the drawing. This type of manipulation only takes the radius as an argument. So, to specify it, there might be a modeless dialog with a slider to control the size of the circle and a button that the user would press to create the circle. Another option would be to have a modal dialog with an input field and Apply/Cancel buttons. The developer could also want that the user clicks on the drawing to specify the center of the circle and drag to adjust the size of the circle and release the mouse to create the cirle. All these ways are valid and feasible using the script API. But the first thing to know before being able to manipulate a drawing, is how to specify the drawing.

Drawing Descriptor

A Drawing Descriptor is a JavaScript object that is used to find the drawing in the scene. There are many ways to create one. The simplest way is probably to use the element node name and the frame.

// Let's suppose that the selection contains an Element module.
var drawingDescriptor = {
  node : selection.selectedNode(0),
  frame : frame.current();
}
// If there is an exposure at the current frame for the selected node and the selected node is
// an Element module, the drawingDescriptor will be valid and could be used to query or modify it's vector drawing.

There is another easy way of getting a valid drawing descriptor which is the current edited drawing. The current edited drawing can be retrieved from the Tools.getToolSettings() function.

var settings = Tools.getToolSettings();
if (settings.currentDrawing) 
{
  // settings.currentDrawing is a valid descriptor
}

Drawing Modification

Now that we can specify valid drawing descriptors, we can use the DrawingTools methods to modify any vector drawing via scripting. Let's come back at the example from the introduction of this section. We will write a small function that creates a circle in the the current drawing. Please note that the vector coordinates for a 12-field drawing in the drawing view range from [-2500, 2500] in X and [-1875, 1875] in Y. Now, let's write the function.

  function createBigCircle()
  {
    var settings = Tools.getToolSettings();
    if (settings.currentDrawing) 
    {
      var cid = PaletteManager.getCurrentColorId();
  
      // settings.currentDrawing is a valid descriptor
      DrawingTools.createLayers( {
          label    : "Create Big Circle", 
          drawing  : settings.currentDrawing,
          art      : settings.activeArt,
          layers   : [
                 {
                    contours : [ 
                    {
                     stroke : true,
                     colorId : cid,
                     polygon: false,
                     path: Drawing.geometry.createCircle({ x: 0, y: 0, radius : 1875.0 })
                    }
                 ] }
              ]
        });
    }
  }

To try this example, go in the script editor view and paste the code in a new script file. You can bind the script to a button in the script toolbar or call the function from the script view. The function will create a circle in the current drawing if there is one in the drawing view. Remember: if the current frame does not have a valid drawing, nothing will happen. You can use the "Create Empty Drawing" from the timeline contextual menu to create a valid drawing in the selected exposure.

To get a better understanding of the vector model of Harmony, you should read the Vector Model Description that will explain how the vector drawings are built.

Running the examples

If you want to try out the examples contained in the documentation, most of them can be run if you install this package ScriptAPIDemos.zip in your script packages folder. Once you have decompressed this package in the folder and restarted Harmony, there will be a new window named "Script API Demos" available in the Windows menu of Harmony. You can also browse this demo package here: Browse ScriptAPIDemos