Python in Grapple.

Grapple comes with its own Python, so there's nothing to install. You can put a line of Python on a property, write an effect that runs every frame, run a script that builds part of a project, or read the scripts Ape writes. This guide shows where each one lives and what they share.

Where Python runs

Python shows up in six places. They all use the same grapple package, imported as gp, and the same rules for units: pixels from the top left of the composition with y pointing down, degrees, percent and seconds.

The places Python runs in Grapple
WhereWhat it doesGuide
An expressionSets one property at every frame, on top of its own keys.Property expressions
A program or operatorRuns every frame as an effect: filters pictures, generates shapes, drives several properties at once.Python programs and effects
Your scriptRuns once when you press Run in Code, and changes the project as one step in History.Your scripts in Code
Ape's scriptApe does its project work as Python against gp. A whole script is one History row you can undo.Scripts with gp
A pluginA tool you pick up on the Viewer and the Timeline, with its actions written in Python.Plugins
A model providerConnects Grapple to a model service it doesn't include.Model providers in Python

Shaders are the GPU's side of the same idea: a GLSL function that returns a colour for every point of the frame. See GLSL shaders.

The Code panel

Open Code from the Window menu. It docks beside the Timeline as a tab, so you can switch between your keys and your code. Everything Python in the project is listed on its left, in four groups: Modules, Expressions, Operators and Scripts. Search filters them.

New code
The add button at the top of Code. It makes a Module, a Script, or an Operator: a Shape generator, an Image filter or a Transform controller.
The primary button
Says what it will do for the open document. Save for a module (Ctrl+S), Run and Stop for a script (Ctrl+Enter runs, Esc stops), and Apply for an expression or an operator (Ctrl+Enter).
Output, Problems and Changes
Under the editor. Output shows what a script prints. Problems lists errors with their line, and the editor marks the line itself. Changes lists what a script changed in the project, each one a link to the object.
Code actions
The More menu. For a script or module: Open script…, Export script…, Save, Rename…, Revert and Delete. For an expression: Enabled, Convert to function, Revert and Remove expression.

An expression shows an Inputs section above the editor, and an operator shows a Contract section with its inputs, outputs and parameters. Both are covered in their own guides.

If someone else changes a module while you're editing it, such as Ape saving a new version, Code says "This module changed while you were editing it." and offers Keep mine, Save mine as… and Load the project's version.

Share code with modules

A module is a library of your own functions, saved in the project. Expressions, programs, operators and scripts can all import it, so a rule you like lives in one place.

  1. In Code, choose New code, then Module, and give it a name. A name is words joined by dots, like motion or layouts.grid.
  2. Write your functions and press Save.
  3. Import it anywhere in the project as grapple_project. followed by its name.

Save this as a module named motion:

import math


def pulse(t, rate=1.0):
    """A soft pulse between 0.7 and 0.9, rate times a second."""
    return 0.8 + 0.1 * math.sin(math.tau * rate * t)

Then any function in the project can import it. This is an expression on a title's Opacity, in its function form:

from grapple_project.motion import pulse


def evaluate(ctx):
    return ctx.base_value * pulse(ctx.time)

A one-line expression can't hold an import, so turn it into a function first with Convert to function.

The grapple package

import grapple as gp gives you Grapple's values and helpers. Expressions and programs get gp and Python's math without importing them. Values crossing into Grapple are checked: a wrong type is refused with a sentence such as "Expected number; received gp.Vec2."

The package has more when Ape runs a script: handles to objects, keys, rendering and measuring. That part is in Scripts with gp and the gp reference.

Number helpers

These take and return plain numbers, so they work the same in an expression, a program and a script.

gp.remap(v, in0, in1, out0, out1)
Maps v from one range to another in a straight line. It doesn't stop at the ends: a value past in1 goes past out1.
gp.ease(v, in0, in1, out0, out1)
The same mapping with a smooth start and finish, held at out0 before in0 and at out1 after in1. gp.ease(time, 1, 2, 0, 100) rises from 0 to 100 between one and two seconds.
gp.loop(v, period)
Wraps v into 0 up to period, so gp.loop(time, 2) counts from 0 to 2 seconds and starts again.
gp.wiggle(t, seed, frequency, amplitude)
Smooth random movement between minus and plus amplitude, changing frequency times a second. The same seed gives the same wiggle every time. The seed is a whole number.
gp.random(seed, key)
A number from 0 up to 1 that depends only on the seed and the key, a word or a number. Use the layer's index or name as the key for a different value per copy that never changes between renders.
gp.noise(seed, key, t)
Smooth noise between −1 and 1 along t, for the same seed and key.
gp.memo(key, factory)
A cache you control, inside a program or expression. The first call runs factory() and keeps the result under key; later calls return it. Use it for work that doesn't change between frames, such as loading a table.

Paths have three helpers, which take a list of gp.Path values and return a new list: gp.simplify(paths, tolerance), gp.resample(paths, spacing) and gp.round_corners(paths, radius). They run in programs and in Ape's scripts.

Values

Grapple's values are small, fixed objects. Change one by making a new one.

gp.Vec2(x, y), gp.Vec3(x, y, z)
Points, sizes, scales and colours. They add and subtract, multiply and divide by a number, unpack as x, y = v, and have length().
gp.Rect(x, y, width, height)
A rectangle, such as a crop.
gp.Path
A shape's outline: gp.Path(start, segments, closed=True) for one contour, where each segment is gp.Line(end) or gp.Cubic(control1, control2, end), or gp.Path(contours=[...], fill_rule="evenodd") for several.
gp.Shape, gp.Shapes
The output of a shape generator: one shape's geometry, and a list of copies with position, scale, rotation, opacity and colour.
gp.Table
Typed columns of equal length, which a program can hand to a shader or another program.
gp.TextStyle, gp.TextStyleRun
A text style, and a style over part of the text. Sizes are in pixels.
gp.AssetRef(id)
A reference to an imported asset, for a program that swaps pictures.
import grapple as gp

# A triangle 200 pixels wide, as one closed contour.
triangle = gp.Path(gp.Vec2(0, -100),
                   [gp.Line(gp.Vec2(100, 80)), gp.Line(gp.Vec2(-100, 80))],
                   closed=True)

Libraries that come with Grapple

Grapple's Python is Python 3.12 with NumPy, SciPy and Pillow, plus the standard library. Frames reach Python as NumPy arrays, so a filter can be a few lines of array maths. A model provider can pin other packages in its own manifest.

Trust

Python in a project runs only on a computer that trusts that project. A project with no Python in it is trusted, and Python you or Ape add while you work keeps running. When you open a project whose Python you haven't trusted, such as one someone sent you, Code says "Python in this project runs only after you trust it." with a Trust project button. Until you press it, you can read and edit the code, and none of it runs.