Tutorial/Scripting

From Knotter
Jump to: navigation, search

Knotter has a script engine that can be used to automate tasks and provide new tools. This tutorial shows how to execute simple scripts and to create a plugin.

Introduction

To execute simple code snippets you can use the script console, which can be toggled via View → Dialogs → Script Console. From there, you can type your line at the bottom and hit enter to execute it.

The scripting language used by Knotter is ECMAScript. This section gives a minimal introduction to the language, if you are already familiar with the language you can skip it.

Hello World

Let's start with a classical example, the Hello World program:

Example:
print("Hello World")

Output:

Hello World

Here print is one of the built-in functions; Template:"Hello World" is a string literal, passed as an argument to that function.

The print function writes its arguments to the script console. If multiple arguments are passed, each one is written, separated by a space character.

Example:
print("Hello", 'World')

Output:

Hello World

Variables and values

Expressions can yield a value, for example 5+3 results in 8.

It's often useful to assign a name to a value to be able to retrieve it quickly later on. To do this we can use variables. A value can be copied to a variable using an assignment expression:

Example:
a = 2 + 3

Output:

5

Here the expression 2+3 is evaluated and the result (5) is copied to the variable a. Any previous value of Template:A is discarded. It is a good practice to declare variables with var before using them.

Example:
var a = 2+3
var b = 7
var c = a+b
print("a + b =", c)
b = 3
a = b
print ("a =",a)
print ("b =",b)
print ("c =",c)

Output:

a + b = 12 
3
3
a = 3 
b = 3 
c = 12

Objects

Objects are values that can contain variables and functions.

Example:
knotter.version

Output:

0.9.3

In the above example knotter is one of the built-in global objects. version is one of its properties.

Interactions with Knotter

Interacting with the user

The object window.dialog can be used to show dialogs and get input from the user.

Example:
var number = window.dialog.get_number("Select a number")
if ( number > 4 ) window.dialog.information("That's a large number")

The first line will show a dialog asking for a number.

Tutorial Script Number Dialog.png

If the user has chosen a value greater than four, an information dialog is displayed.

Tutorial Script Info Dialog.png

Interacting with the knot

The object document represent the current document, document.graph contains the nodes and edges contained by the graph and can modify it.

Example:
for ( var i = 0; i < 4; i++ ) document.graph.add_node(i*16,0);

The script above will add a line of four nodes at a distance of 16 pixels from one another.

Writing a plugin

The list of directories in which you can place a plugin can be found Help → About → Plugins. You can create subdirectories to keep all the code for your plugin separated from the others.

Metadata

A plugin is defined by a file called plugin_name.json, name there is not important and can be anything. This file uses the JSON format to describe the properties of the plugin.

{
    "name"        : "Plugin Tutorial",
    "description" : "This is the plugin created following the tutorial",
    "script"      : "line.js"
}

Script

Each plugin must have a script file, with the same name as defined in the JSON file. As of version 0.9.3 you have to restart Knotter when you want that changes to the plugin code get applied.

For our plugin we want to ask the user to select the number of nodes and then create a line with that many nodes.

// Ask the user for a number. Default value is 4 and minimum is 1
var length = window.dialog.get_integer("Number of segments","Segmented line", 4, 1 );

for ( var i = 0; i < length; i++ )
{
    // Create a new node
    document.graph.add_node(i*document.grid.size,0);
}

This will add the nodes as requested, using the grid size to space them.

This is fine but we also want to connect them.

// Ask the user for a number. Default value is 4 and minimum is 1
var length = window.dialog.get_integer("Number of segments","Segmented line", 4, 1 );

for ( var i = 0; i < length; i++ )
{
    // Create a new node
    var current_node  = document.graph.add_node(i*document.grid.size,0);
        
    // From the second iteration connect to the previous node
    if ( i > 0 )
    {
        var previous_node = document.graph.node_at((i-1)*document.grid.size,0);
        document.graph.connect(current_node,previous_node);
    }
}

Now there is a problem: the plugin makes several changes to the document and the user will have to undo each step. This can be solved by wrapping get code in a macro.

All commands executed inside a macro can then be undone/redone in a single step.

// Ask the user for a number. Default value is 4 and minimum is 1
var length = window.dialog.get_integer("Number of segments","Segmented line", 4, 1 );


// Place all the edits to the document in a macro called "Segmented line"
document.begin_macro("Segmented line");
   
for ( var i = 0; i < length; i++ )
{
    // Create a new node
    var current_node  = document.graph.add_node(i*document.grid.size,0);
        
    // From the second iteration connect to the previous node
    if ( i > 0 )
    {
        var previous_node = document.graph.node_at((i-1)*document.grid.size,0);
        document.graph.connect(current_node,previous_node);
    }
}
    
// End the macro
document.end_macro();

Now the plugin will create a macro even if the user clicked "Cancel" on the dialog.

To fix this we add a condition to execute the code only if the user really wants to add the line

// Ask the user for a number. Default value is 4 and minimum is 1
var length = window.dialog.get_integer("Number of segments","Segmented line", 4, 1 );

// If the user canceled the dialog  length is set to NaN
if ( !isNaN(length) )
{
    // Place all the edits to the document in a macro called "Segmented line"
    document.begin_macro("Segmented line");
    
    for ( var i = 0; i < length; i++ )
    {
        // Create a new node
        var current_node  = document.graph.add_node(i*document.grid.size,0);
        
        // From the second iteration connect to the previous node
        if ( i > 0 )
        {
            var previous_node = document.graph.node_at((i-1)*document.grid.size,0);
            document.graph.connect(current_node,previous_node);
        }
    }
    
    // End the macro
    document.end_macro();
}