The API

What a plugin's code is given: the project on screen, ways to talk to the user, and the canvas.

In TypeScript it is an api object. In Python it is app, with the same things under Python's own names.

The project

commands: [{
  label: 'Add a dot',
  run(api) {
    const { w, h } = api.size();
    const dot = api.project.add({ kind: 'shape', name: 'Dot', shape: 'Ellipse', w: 48, h: 48, color: api.project.color, radius: 0, stroke: 0 });
    api.project.set(dot.id, { x: Math.round(w / 4), y: Math.round(h / 4) });
    api.say(`${api.project.layers.length} layers`);
  },
}],
@plugin.command('Add a dot')
def add_dot(app):
    w, h = app.size
    dot = app.add(kind='shape', name='Dot', shape='Ellipse', w=48, h=48, color=app.color, radius=0, stroke=0)
    app.set(dot['id'], x=round(w / 4), y=round(h / 4))
    app.say(f'{len(app.layers)} layers')
TypeScriptPythonWhat it is
api.project.docapp.docThe document. See Documents and layers.
api.project.layersapp.layersThe layers, bottom to top.
api.project.selectedapp.selectedThe id of the selected layer.
api.project.entry(id?)app.layer(id=None)A layer by its id (none given: the selected one).
api.project.color, color2app.colorThe colors in hand.
api.project.add(layer)app.add(**values)Adds one layer above the selected one. One step to undo.
api.project.addAll(layers, settings?, group?)app.add_all(layers, group='')Adds several at once: one step to undo.
api.project.set(id, values, undoable?)app.set(id, undo=True, **values)Changes values of a layer.
api.project.mark()app.mark()Call before changing the document yourself: what follows can be undone in one step.
api.size()app.sizeThe picture's size: { w, h } (Python: a pair).
api.addPicture(name, data)app.add_picture(name, data)Adds a picture as a new layer.

Talking to the user

commands: [{
  label: 'Rename the layers…',
  async run(api) {
    const { button, values } = await api.window({
      title: 'Rename the layers',
      items: [
        { kind: 'label', text: 'Every layer takes a name and a number.' },
        { key: 'stem', label: 'Name', usual: 'Layer', text: true },
        { key: 'from', label: 'First number', low: 0, high: 100, step: 1, usual: 1 },
        { kind: 'note', text: 'The background keeps its name.', tone: 'info' },
      ],
      buttons: ['Cancel', 'Rename'],          // the last one is what Enter gives
    });
    if (button !== 'Rename') return;
    const sure = await api.ask('Rename them all?', 'This can be undone.', ['Cancel', 'Rename']);
    if (sure !== 'Rename') return;
    api.project.mark();
    let number = values.from;
    for (const layer of api.project.layers) if (layer.kind) layer.name = `${values.stem} ${number++}`;
    api.notify('Layers renamed', 'success');
  },
}],
from rassam import label, note, slider, text


@plugin.command('Rename the layers…')
async def rename(app):
    answer = await app.window('Rename the layers', [
        label('Every layer takes a name and a number.'),
        text('stem', 'Name', 'Layer'),
        slider('start', 'First number', 0, 100, 1),
        note('The background keeps its name.', 'info'),
    ], buttons=['Cancel', 'Rename'])           # the last one is what Enter gives
    if answer.button != 'Rename':
        return
    if await app.ask('Rename them all?', 'This can be undone.', ['Cancel', 'Rename']) != 'Rename':
        return
    app.mark()
    number = int(answer.start)
    for layer in app.layers:
        if layer.get('kind'):
            app.set(layer['id'], undo=False, name=f'{answer.stem} {number}')
            number += 1
    app.notify('Layers renamed', 'success')
TypeScriptPythonDoes
api.say(text)app.say(text)A line in the status bar.
api.notify(text, tone?, stays?, detail?)app.notify(text, tone, seconds, detail)A notice at the top for a few seconds. tone: info, success, warning or error.
await api.ask(title, text, buttons)await app.ask(title, text, buttons)A question in a small window. Gives the label of the button that answered.
await api.window(spec)await app.window(title, items, buttons)A window of the plugin's own. Gives the button and the fields' values.

A window's items are laid out top to bottom: fields, a label (a line of words), a note (a message box with a tone), a rule (a line across) and a button of its own, whose function may give back new values for the fields or something to say.

The log

The program keeps a log of information, warnings and errors, which View › Logs… lists (a marker in the status bar says when new warnings or errors have come). A plugin writes to it, and its entries carry the plugin's name. Give a second argument for more: a longer text, an error or any value. It shows under the line when the entry is opened.

commands: [{
  label: 'Check the layers',
  run(api) {
    api.log.info(`Checking ${api.project.layers.length} layers`);
    const hidden = api.project.layers.filter((layer) => layer.visible === false);
    if (hidden.length) api.log.warn(`${hidden.length} layers are hidden`, hidden.map((layer) => layer.name ?? layer.id).join('
'));
    try {
      JSON.parse('{ not json');
    } catch (error) {
      api.log.error('The settings could not be read', error);      // an error keeps its trace
    }
  },
}],
@plugin.command('Check the layers')
def check(app):
    app.log(f'Checking {len(app.layers)} layers')
    hidden = [layer for layer in app.layers if layer.get('visible') is False]
    if hidden:
        app.log(f'{len(hidden)} layers are hidden', 'warn', '
'.join(str(layer.get('name', layer['id'])) for layer in hidden))
    try:
        int('not a number')
    except ValueError as error:
        app.log('The settings could not be read', 'error', repr(error))
TypeScriptPythonLogs
api.log.info(text, detail?)app.log(text)Information.
api.log.warn(text, detail?)app.log(text, 'warn', detail)A warning.
api.log.error(text, detail?)app.log(text, 'error', detail)An error.

What goes wrong in a plugin's own code is logged as an error by itself, with its trace, and so is a notice with a warning or error tone. Use the log for what a user may want to look back at; use say and notify for what they should see now.

The canvas

api.view (in Python, app.view) is the canvas: where the picture lies in it, its pixels, and painting on it.

commands: [{
  label: 'Zoom to the true size',
  run(api) {
    const { w, h } = api.size();
    api.view.zoom(1);                          // one screen pixel to each of the picture's
    api.view.center(w / 2, h / 2);
    // Paint on the selected painted layer: one step to undo.
    api.view.paint((pen) => {
      pen.fillStyle = api.project.color;
      pen.fillRect(0, 0, 8, 8);
    });
  },
}],
@plugin.command('Zoom to the true size')
def true_size(app):
    w, h = app.size
    app.view.zoom(1)                           # one screen pixel to each of the picture's
    app.view.center(w / 2, h / 2)

    # Paint on the selected painted layer: one step to undo.
    def corner(picture):
        picture.pen.fillStyle = app.color
        picture.pen.fillRect(0, 0, 8, 8)
    app.view.paint(corner)
CallDoes
state(){ scale, x, y, w, h }: how many screen pixels one picture pixel is, where the picture's top left corner is, and how large the canvas area is.
zoom(scale)Zooms to that scale outright. 1 is the true size.
center(x, y), fit()Brings a point of the picture to the middle; fits the picture in the canvas.
picture(scale?)The whole picture as it stands, as a canvas.
pixels(id?)One layer as it is drawn (none given: the selected one), as a canvas the size of the picture.
paint(run)Repaints the selected painted layer: run(pen) draws on it (in Python it is given a picture, with picture.pen). Gives false when there is no such layer.
stand(id, picture)A canvas stands in for a layer whenever the image is drawn. The layer itself is not changed. Pass null to stop.
redraw()Has the overlays drawn afresh.

Everything else

CallDoes
api.engine()What draws the image, or null. engine().picture_canvas(scale?) gives the picture as a canvas. In Python, app.picture() gives the image as a PNG.
api.prefs(pluginId)A plugin's own settings as they stand. In Python: app.prefs(plugin_id).