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 a bunch of packages from different places. These places are:

  • The packages folder in the user's scripts folder. This folder is located in the user's preference folder.
  • The system's packages folder.
  • The system's beta-packages folder. (if the preference TB_SCRIPT_USE_BETA_PACKAGES is set to true)
  • A user specified list of folders. (if the preference TB_EXTERNAL_SCRIPT_PACKAGES_FOLDER is set)

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 beta-packages is located in the installation folder under Plugins/ScriptingInterfaces/resources/beta-packages. The TB_EXTERNAL_SCRIPT_PACKAGES_FOLDER preference is a list of semicolon (;) separated folders on windows and colon (:) separated folder on MacOS and Linux.

The beta-packages folder contains examples of tools, toolbars and windows. It can be enabled by running the following script:


   preferences.setBool("TB_SCRIPT_USE_BETA_PACKAGES", true);
   
   

And restarting Harmony. After restart, the Toolbars menu will contain a "Custom Tools" entry.

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 package folder, create a folder named after you 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 this package folder, you can create a folder named icons. This folder will be added automatically to the icon search path of harmony.

Here is a script to define a preference in your home folder to the HarmonyScriptPackages folder. Please refer to the Harmony scripting documentation on how to run this script.

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 the previous steps, you can restart Harmony. A message should appear in the Message Log view telling you that your package was called.

Inside a package, the configure() function is the entry point where you can install new menu items, toolbars and define new tools.

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