Vector geometry — splines, shapes, and path operations. Most outputs here are `spline` values that downstream nodes can resample, stroke, or sample along.
Generators
Accretive Growth
accretive-growthpolymorphic
Grow a branching structure that accumulates over time — elements attach to what's already there and never move again, so Progress just reveals the growth (scrub it either way, it's free). Space Colonization scatters attractor points and lets branches race toward them, consuming each as it's reached (influence radius = how far a branch can 'see', kill distance = how close it must get) — trees, veins, river deltas, coral. DLA fires random walkers at the cluster and sticks them where they touch: frost and lichen, with Stickiness below 1 packing it denser and Drift blowing it sideways; seed from the Region edge for frost creeping off a shape. Percolation eats across a grid — Invasion follows the wired Resistance image, burrowing through dark areas and stalling at light ones, while Eden spreads uniformly into compact rough blobs. Laplacian solves the electric field around the growing shape and grows where it's strongest, so one slider (Eta) morphs the whole family: 0 is a compact blob, 1 is DLA-like coral, and high values leave only the most exposed tips growing — lightning. Wire an Attractor mask to steer the discharge toward a target. It is the expensive mode; Grid and Max elements drive its cost. Crack and Hyphal grow networks rather than trees — tips propagate, branch, and stop dead when they touch existing growth, recording the join: Crack nucleates at scattered origins that each spread both ways and T into each other (mud cracks, craquelure, shattered glass), while Hyphal radiates from a single seed and fuses where strands meet (mycelium, neurites). Space Colonization's Venation can also be set to closed, so converging branches fuse into the loops real leaf veins have instead of staying a strict tree. Region (mask or spline) confines any of them: scatter into a text silhouette and growth fills the letters. Branch thickness follows Da Vinci's rule and rides the anchor width channel; age rides the subpath driver channel, so Stroke's driver ramps colour by how old each branch is. Emit as Limbs (one continuous path per branch — the cheap default), Branches (overlapping root-to-tip paths for Trim Path; costly on big trees), Segments (one subpath per edge) or Boundary (the silhouette of the grown mass, for filling).
margin scales off min(width,height); every other radius (influence_radius, kill_distance, step_length, fuse_radius, contact_radius, hit_radius, boundary_radius) scales off width only.
gotcha
The whole growth run simulates once into a trace cached in node state; progress just slices it — only a structural param or wired-input identity change forces a retrace.
gotcha
A wired seeds or attractors input that resolves empty means grow nothing, not fall back to the internal default seed or scatter; only an entirely unwired socket falls back.
gotcha
emit=branches silently falls back to segments whenever non-tree fusion edges exist (venation=closed, or any crack/hyphal contact), since a root-to-leaf walk can't express a graph join.
gotcha
Branch thickness (Da Vinci's rule, root=1 down to tip_width at the tips) rides each anchor's width multiplier; age (birth iteration normalized) rides each subpath's driver.
gotcha
The aux points output mirrors that thickness onto attr:scale (x=y) and adds attr:rotation = heading of arrival in radians, which the spline output doesn't carry.
gotcha
id_mode picks what attr:group encodes: branch (dense per-limb id, default), depth (hops from seed), root (seed descended from), or birth (growth iteration bucketed into id_groups).
gotcha
mode=crack only honors tip_count for its own internal scatter; wired seeds each get exactly 2 opposite-heading tips regardless of tip_count. mode=hyphal always honors it.
Generate a circular arc / pie wedge as a spline — set radius, start/end angle, and whether it's an open arc, a pie, or a chord. Wire Center / Start / End to drive those anchors from a vec2 (unwired sockets keep the polar params). Authored in [0,1]² Y-down; the rasterizer scales y about 0.5 by W/H so radii stay width-relative.
When both start and end are wired, the circle is refitted so both positions are exact; centerX/Y/radius are ignored and the angle params only pick the major or minor sweep.
gotcha
Wiring only start or only end keeps the center fixed and recomputes radius and that endpoint's angle from it; the other angle param still applies.
gotcha
Angles are degrees, 0° at +X and positive clockwise on screen (Y is down); a sweep past 360° (e.g. endAngle=630) is honored, not wrapped.
gotcha
stroke_thickness is pixels by default; switching stroke_units to % makes it a fraction of canvas width instead.
gotcha
trim_offset is unbounded mod 1, so keyframing it past ±1 keeps orbiting the arc rather than clamping.
Parameters
Name
Type
Default
Center X
centerX
scalar
0 … 1
0.5
Center Y
centerY
scalar
0 … 1
0.5
Radius
radius
scalar
0 … 1
0.3
Start angle (°)
startAngle
scalar
-360 … 360
0
End angle (°)
endAngle
scalar
-360 … 360
270
Mode
mode
enum
"open"
open · pie · chord
Trim start
trim_start
scalar
0 … 1
0
Trim end
trim_end
scalar
0 … 1
1
Trim offset
trim_offset
scalar
-1 … 1
0
Stroke
stroke_enabled
boolean
true
Thickness
stroke_thickness
scalar
0 … 200
4
Units
stroke_units
enum
"px"
px · %
Stroke color
stroke_color
color
"#ffffff"
Fill
fill_enabled
boolean
false
Fill color
fill_color
color
"#ffffff"
Fill fit
fill_fit
enum
"window"
window · contain · cover
Opacity
opacity
scalar
0 … 1
1
Sockets
Direction
Name
Type
input
fill
fill
image
input
Transform
transform
transform
input
Center
center
vec2
input
Start
start
vec2
input
End
end
vec2
outputprimary
out
spline
outputaux
image
image
outputaux
element
element
Arrow
arrowpolymorphic
Generate an arrow outline as a closed spline — aim it tail→tip and set the shaft thickness and head size.
tailX/Y, tipX/Y, shaftThickness, headLength and headWidth are all canvas01: width-relative fractions, not absolute pixels.
gotcha
shaftThickness and headWidth are full widths; the outline offsets each side of the centerline by half that amount.
gotcha
headLength is clamped to the tail-to-tip distance, so the head can't outgrow the shaft on a short arrow.
gotcha
If tail and tip coincide, the arrow degenerates to a single open anchor (a dot) instead of a closed 7-point outline.
gotcha
stroke_thickness is pixels by default; switching stroke_units to % makes it a fraction of canvas width instead.
Parameters
Name
Type
Default
Tail X
tailX
scalar
0 … 1
0.25
Tail Y
tailY
scalar
0 … 1
0.5
Tip X
tipX
scalar
0 … 1
0.75
Tip Y
tipY
scalar
0 … 1
0.5
Shaft thickness
shaftThickness
scalar
0 … 0.5
0.04
Head length
headLength
scalar
0 … 1
0.12
Head width
headWidth
scalar
0 … 1
0.12
Trim start
trim_start
scalar
0 … 1
0
Trim end
trim_end
scalar
0 … 1
1
Trim offset
trim_offset
scalar
-1 … 1
0
Stroke
stroke_enabled
boolean
true
Thickness
stroke_thickness
scalar
0 … 200
4
Units
stroke_units
enum
"px"
px · %
Stroke color
stroke_color
color
"#ffffff"
Fill
fill_enabled
boolean
false
Fill color
fill_color
color
"#ffffff"
Fill fit
fill_fit
enum
"window"
window · contain · cover
Opacity
opacity
scalar
0 … 1
1
Sockets
Direction
Name
Type
input
fill
fill
image
input
Transform
transform
transform
outputprimary
out
spline
outputaux
image
image
outputaux
element
element
Circle
circlepolymorphic
Generate a circle (or ellipse, via non-uniform radii) as a closed spline. Authored in [0,1]² Y-down; the rasterizer scales y about 0.5 by W/H so radii stay width-relative.
radiusX and radiusY are linked by default (dragging one moves the other in lockstep); unlink them in the param panel to author an independent ellipse.
gotcha
Equal radiusX/radiusY only renders as a true circle on a square canvas, since y is scaled by W/H at raster time like every canvas01 distance.
gotcha
stroke_thickness is pixels by default; switching stroke_units to % makes it a fraction of canvas width instead.
gotcha
trim_offset is unbounded mod 1, so keyframing it past ±1 keeps orbiting the outline rather than clamping.
Parameters
Name
Type
Default
Center X
centerX
scalar
0 … 1
0.5
Center Y
centerY
scalar
0 … 1
0.5
Radius X
radiusX
scalar
0 … 1
0.25
Radius Y
radiusY
scalar
0 … 1
0.25
Trim start
trim_start
scalar
0 … 1
0
Trim end
trim_end
scalar
0 … 1
1
Trim offset
trim_offset
scalar
-1 … 1
0
Stroke
stroke_enabled
boolean
true
Thickness
stroke_thickness
scalar
0 … 200
4
Units
stroke_units
enum
"px"
px · %
Stroke color
stroke_color
color
"#ffffff"
Fill
fill_enabled
boolean
false
Fill color
fill_color
color
"#ffffff"
Fill fit
fill_fit
enum
"window"
window · contain · cover
Opacity
opacity
scalar
0 … 1
1
Sockets
Direction
Name
Type
input
fill
fill
image
input
Transform
transform
transform
outputprimary
out
spline
outputaux
image
image
outputaux
element
element
Connect Points
connect-points
Connect pairs of input points whose separation sits between min-distance and max-distance. Path modes shape each connection: straight chords, circular arcs / S-curves (curved), hanging-wire droop (sag), noise-field tangents (flow), smooth curves flowing through shared points (network), parallel connections merging into trunks (bundle), or bowing toward/away from a center (attract). `Min connections` prunes the sparse fringe: an edge survives only when BOTH its endpoints found at least that many neighbours inside the distance band, so stringy one-off segments drop out and the dense core stays. Primary output is the segments as a spline; the passthrough `points` aux keeps the original points available on the same wire for further downstream use.
min_connections is a degree filter, not a floor — it prunes existing edges in one pass after the min/max distance band; it never adds edges and doesn't cascade.
gotcha
max_distance and min_distance are compared in raw UV, not aspect-corrected, so the same threshold reaches farther in one axis than the other on a non-square canvas.
gotcha
An edge inherits groupIndex only when both endpoints share one; a cross-group edge is left untagged rather than assigned to either side.
gotcha
path shape modes (curved/sag/flow/network/bundle/attract) compute handles in an internal aspect-scaled space so arcs stay round, unlike the distance params' raw-UV threshold.
gotcha
min_distance ≥ max_distance yields no edges; min_connections then counts degree on the remaining band, not the full max_distance disk.
startOffset is the central gap; 0 makes the four arms meet at the center (a plus), and rotation=45 turns that into an X.
gotcha
Emits four open 2-anchor subpaths (one per arm), meant to be stroked; fill_enabled draws nothing since each arm has zero area.
gotcha
stroke_thickness is px by default; stroke_units=% resolves it against canvas width so the stroke keeps its look at any resolution.
gotcha
The image aux only appears when stroke_enabled or fill_enabled is on; the element aux is always present for Auto Layout.
Parameters
Name
Type
Default
Center X
centerX
scalar
0 … 1
0.5
Center Y
centerY
scalar
0 … 1
0.5
Arm length
length
scalar
0 … 1
0.15
Start offset (gap)
startOffset
scalar
0 … 1
0
Rotation (°)
rotation
scalar
-180 … 180
0
Trim start
trim_start
scalar
0 … 1
0
Trim end
trim_end
scalar
0 … 1
1
Trim offset
trim_offset
scalar
-1 … 1
0
Stroke
stroke_enabled
boolean
true
Thickness
stroke_thickness
scalar
0 … 200
4
Units
stroke_units
enum
"px"
px · %
Stroke color
stroke_color
color
"#ffffff"
Fill
fill_enabled
boolean
false
Fill color
fill_color
color
"#ffffff"
Fill fit
fill_fit
enum
"window"
window · contain · cover
Opacity
opacity
scalar
0 … 1
1
Sockets
Direction
Name
Type
input
fill
fill
image
input
Transform
transform
transform
outputprimary
out
spline
outputaux
image
image
outputaux
element
element
L-System
l-system
Grow botanical and fractal structures from a rewriting grammar — a short rule set expanded a few times and drawn by a turtle. Pick a preset (fern, bush, plant, tree, Koch, Hilbert, dragon, Sierpinski) and shape it with Angle, Length and Length decay, or switch to Custom and write your own productions: one per line as `F=F[+F]F[-F]F`, with `F`/`G` drawing, `f`/`g` moving without drawing, `+`/`-` turning, and `[`/`]` saving and restoring the turtle. Rules can be stochastic — `A=0.7:F[+A] | 0.3:F[-A]` — which, with Angle jitter and Tropism (a global bend, for gravity or light), is what stops L-system plants looking like clip art. Two separate animation inputs: Progress reveals the finished structure outward level by level, while Iterations is fractional and morphs between structural levels, extending the newest tips out of their parents. Branch thickness follows Da Vinci's rule on the anchor width channel and age rides the subpath driver channel, so Stroke ramps can colour by either.
progress reveals the fixed expanded structure outward as a cheap slice; iterations is fractional and re-expands the grammar itself, growing the newest tips out of their parents.
gotcha
The turtle grows in isotropic pixel space then normalizes as x/W, y/H per axis with no aspect correction, so shapes are undistorted only on square canvases.
gotcha
Anchor width follows Da Vinci's rule (root=1, tips=tip_width) and each subpath's driver carries birth iteration 0..1 for Stroke/Rasterize age ramps; neither is a renamable attribute.
gotcha
id_mode picks what the aux points' group index encodes: branch id, hop depth, seed root index, or a birth-time bucket (id_groups sets bucket count, only for id_mode=birth).
gotcha
Expansion is capped at 2,000,000 symbols and 12 levels; hitting the symbol cap truncates that expansion level rather than erroring.
gotcha
use_preset_shape locks angle/start_angle to the chosen preset's own values; turn it off (or pick preset=custom) to reveal the manual angle/start_angle rows.
Parameters
Name
Type
Default
Preset
preset
enum
"fern"
fern · bush · plant · tree · koch · hilbert · dragon · sierpinski · custom
Axiom
axiom
string
"X"
Rules
rules
string
"X=F+[[X]-X]-F[-FX]+X\nF=FF"
Iterations
iterations
scalar
0 … 12
5
Progress
progress
scalar
0 … 1
1
Preset angle
use_preset_shape
boolean
true
Angle
angle
scalar
0 … 180
25
Angle jitter
angle_jitter
scalar
0 … 1
0
Length
length
scalar
0.0005 … 0.2
0.04
Length decay
length_decay
scalar
0.5 … 1.2
0.9
Tropism angle
tropism_angle
scalar
-180 … 180
90
Tropism
tropism_strength
scalar
0 … 1
0
Origin X
origin_x
scalar
-0.5 … 1.5
0.5
Origin Y
origin_y
scalar
-0.5 … 1.5
0.92
Start angle
start_angle
scalar
-180 … 180
-90
Seed
seed
scalar
0 … 10000
0
Max elements
max_elements
scalar
100 … 200000
20000
Emit
emit
enum
"limbs"
limbs · branches · segments
Tip width
tip_width
scalar
0 … 1
0.1
ID mode
id_mode
enum
"branch"
branch · depth · root · birth
ID groups
id_groups
scalar
2 … 32
6
Sockets
Direction
Name
Type
outputprimary
out
spline
outputaux
points
points
Line
linepolymorphic
Generate a straight line as an open spline — set the two endpoints. Authored in [0,1]² Y-down; the rasterizer scales y about 0.5 by W/H so lengths stay width-relative.
stroke_thickness is absolute pixels by default; set stroke_units=% to make it a percentage of canvas width instead, so it scales with output size.
gotcha
Fill is degenerate on this open 2-anchor path (no enclosed area), so fill_enabled/fill_color have no visible effect; the line only reads as its stroke.
gotcha
The image aux exists only while stroke_enabled or fill_enabled is on; with both off there is nothing to rasterize and only the spline output carries data.
gotcha
Wire a transform input (a Gizmo, say) to move/rotate/scale the whole line without touching startX/startY/endX/endY.
Parameters
Name
Type
Default
Start X
startX
scalar
0 … 1
0.25
Start Y
startY
scalar
0 … 1
0.5
End X
endX
scalar
0 … 1
0.75
End Y
endY
scalar
0 … 1
0.5
Trim start
trim_start
scalar
0 … 1
0
Trim end
trim_end
scalar
0 … 1
1
Trim offset
trim_offset
scalar
-1 … 1
0
Stroke
stroke_enabled
boolean
true
Thickness
stroke_thickness
scalar
0 … 200
4
Units
stroke_units
enum
"px"
px · %
Stroke color
stroke_color
color
"#ffffff"
Fill
fill_enabled
boolean
false
Fill color
fill_color
color
"#ffffff"
Fill fit
fill_fit
enum
"window"
window · contain · cover
Opacity
opacity
scalar
0 … 1
1
Sockets
Direction
Name
Type
input
fill
fill
image
input
Transform
transform
transform
outputprimary
out
spline
outputaux
image
image
outputaux
element
element
Lissajous 2D
lissajous-2d
Generate a 2D Lissajous curve as a spline. x(t) = Ax·sin(fx·t + φx), y(t) = Ay·sin(fy·t + φy) over t ∈ [0, 2π]. Integer frequency ratios produce closed curves. Aux `points` output carries the same samples as a points value.
phase_x/phase_y are in units of pi (0.5 = pi/2, 1 = pi); rotation is plain radians, not the pi-scaled convention.
gotcha
Integer fx:fy frequency ratios trace a closed curve; non-integer ratios wander ergodically and never close.
gotcha
samples clamps internally to 2..8192, wider than the panel's 16..4096 slider range, so a driven value can exceed the slider bounds.
Parameters
Name
Type
Default
Samples
samples
scalar
16 … 4096
256
Amplitude X
ax
scalar
0 … 1
0.4
Amplitude Y
ay
scalar
0 … 1
0.4
Frequency X
fx
scalar
0 … 64
3
Frequency Y
fy
scalar
0 … 64
2
Phase X (× π)
phase_x
scalar
-2 … 2
0.5
Phase Y (× π)
phase_y
scalar
-2 … 2
0
Rotation (rad)
rotation
scalar
-3.141592653589793 … 3.141592653589793
0
Center X
center_x
scalar
0 … 1
0.5
Center Y
center_y
scalar
0 … 1
0.5
Sockets
Direction
Name
Type
outputprimary
out
spline
outputaux
points
points
Lissajous 3D
lissajous-3d
Generate a 3D Lissajous curve, rotate it (extrinsic XYZ), and orthographically project to UV space. Three frequencies, three phases (in turns — each whole 0→1 span is one full cycle, so a keyframed phase loops seamlessly), three rotations (rad), and per-axis amplitudes give classic 3D Lissajous knots and harmonograph-style figures. Aux `points` output carries the projected samples as a points value.
phase_x/y/z are in turns (1 = one full 2pi cycle), unlike the 2D node's units-of-pi convention; rx/ry/rz stay in plain radians.
gotcha
Rotation order is extrinsic X then Y then Z; Z is then dropped (orthographic projection) and the result translated to center_x/y.
gotcha
samples clamps internally to 2..8192, wider than the panel's 16..4096 slider range, so a driven value can exceed the slider bounds.
gotcha
az still shapes the projected 2D curve via rotation before Z is dropped; it is not simply discarded even though the output is 2D.
Parameters
Name
Type
Default
Samples
samples
scalar
16 … 4096
512
Amplitude X
ax
scalar
0 … 1
0.3
Amplitude Y
ay
scalar
0 … 1
0.3
Amplitude Z
az
scalar
0 … 1
0.3
Frequency X
fx
scalar
0 … 64
1
Frequency Y
fy
scalar
0 … 64
3
Frequency Z
fz
scalar
0 … 64
2
Phase X (turns)
phase_x
scalar
-2 … 2
0.25
Phase Y (turns)
phase_y
scalar
-2 … 2
0
Phase Z (turns)
phase_z
scalar
-2 … 2
0
Rotation X (rad)
rx
scalar
-3.141592653589793 … 3.141592653589793
0
Rotation Y (rad)
ry
scalar
-3.141592653589793 … 3.141592653589793
0
Rotation Z (rad)
rz
scalar
-3.141592653589793 … 3.141592653589793
0
Center X
center_x
scalar
0 … 1
0.5
Center Y
center_y
scalar
0 … 1
0.5
Sockets
Direction
Name
Type
outputprimary
out
spline
outputaux
points
points
Loop Weave
loop-weave
Draw one continuous spline through a point set, loop-de-looping around each point in an elliptical orbit. Alternate-handed orbits weave (connectors cross between points); same-handed reads as cursive loops. Spiral, lobes and wobble reshape the loops; tension, swing and sag reshape the runs between them. Aux outputs carry the orbit ellipses and untraveled dashed-guide arcs. Progress reveals the path point by point; auto reveal draws each arriving point's loop with the timeline.
space
param:radius=canvas01
gotcha
radius is a canvas-width fraction (canvas01); internally floored to at least 0.5px so tiny radii do not collapse to zero.
gotcha
spiral maps as a per-turn radius factor of 2^-spiral: +1 halves the radius each winding, -1 doubles it, not a linear scale.
gotcha
orient_angle only applies when orient=fixed; other orient modes (travel/random/point) ignore it.
gotcha
progress and reveal_mode=auto combine via min per orbit, so whichever is more restrictive caps how much is drawn.
gotcha
reveal_mode=auto integrates scene time: frozen while the timeline is paused, and resets to redraw from the start on any backward jump (loop wrap, scrub back).
gotcha
orbits and skipped aux outputs are always built, even unwired, since this node caches (stable) and consumption-gating would risk serving a stale empty spline.
gotcha
orbits and skipped subpaths are groupIndex-tagged with the source point's index, letting per-group styling target one orbit.
Parameters
Name
Type
Default
Order
order
enum
"index"
index · nearest
Radius
radius
scalar
0.005 … 1
0.06
Radius jitter
radius_jitter
scalar
0 … 1
0.35
Adaptive radius
adaptive_radius
scalar
0 … 1
0
Use point scale
use_point_scale
boolean
true
Squash
squash
scalar
0 … 0.9
0.25
Orient
orient
enum
"travel"
travel · fixed · random · point
Angle (°)
orient_angle
scalar
0 … 360
0
Spiral
spiral
scalar
-1 … 1
0
Lobes
lobes
scalar
0 … 8
0
Lobe depth
lobe_depth
scalar
0 … 1
0.25
Wobble
wobble
scalar
0 … 1
0
Loops
loops
scalar
0 … 8
1
Loops jitter
loops_jitter
scalar
0 … 1
0
Direction
direction
enum
"alternate"
alternate · cw · ccw · random
Tension
tension
scalar
0.2 … 3
1
Swing
swing
scalar
-1 … 1
0
Sag
sag
scalar
-1 … 1
0
Seed
seed
scalar
0 … 9999
0
Ends
ends
enum
"center"
center · orbit
Smoothness
smoothness
scalar
0 … 1
0.5
Progress
progress
scalar
0 … 1
1
Reveal
reveal_mode
enum
"off"
off · auto
Reveal time
reveal_time
scalar
0.05 … 5
0.6
Sockets
Direction
Name
Type
input*
points
points
points
outputprimary
out
spline
outputaux
orbits
spline
outputaux
skipped
spline
Points to Spline
points-to-spline
Turn points into a spline. Chain walks index order (each groupIndex is one subpath). Grid walks rows, columns, or both (default both: rows then columns) — use after a Grid (or set Columns to match count X) so a warp keeps the lattice instead of a zigzag. Stride emits one subpath per k consecutive points (k=2 is pairs). Zip pairs corresponding points across groupIndex groups (Collect two curves, then zip — connect a_i to b_i). Curve = linear polyline or smooth catmull-rom; Close wraps each chain/stride/zip subpath. Named point channels land on the matching anchors so Spline to Points, Rope, and Points on Path can round-trip them.
reads
attr:group, attr:ix, attr:iy
gotcha
layout=grid reads a group's own attr:ix/attr:iy (e.g. from Grid) for lattice coordinates when both are present, else treats indices as row-major with columns width.
gotcha
layout=zip's subpath count is the shortest group's point count; extra points in longer groups are dropped.
gotcha
layout=stride drops a trailing run shorter than stride; every layout also drops groups with fewer than 2 points.
gotcha
Each output subpath gets a fresh groupIndex per layout (chain reuses the input's group key; grid/stride number sequentially; zip uses the pair index), not the input's original tags.
gotcha
point attributes carry onto the matching spline anchors (so Spline to Points, Rope, and Points on Path can round-trip named channels).
Parameters
Name
Type
Default
Layout
layout
enum
"chain"
chain · grid · stride · zip
Columns
columns
scalar
1 … 64
5
Walk
gridWalk
enum
"both"
rows · columns · both
Stride
stride
scalar
2 … 1024
2
Curve
curve
enum
"linear"
linear · smooth
Close
closed
boolean
false
Sockets
Direction
Name
Type
input*
points
points
points
outputprimary
out
spline
Polygon
polygonpolymorphic
Generate a regular polygon as a closed spline — set the number of sides, radius, and rotation. Authored in [0,1]² Y-down; the rasterizer scales y about 0.5 by W/H so radii stay width-relative.
sides is floored and clamped to a minimum of 3, so fractional or sub-3 values just draw a triangle.
gotcha
The first vertex sits at 12 o'clock (-90°) plus rotation; rotation is entered in degrees and converted to radians internally.
gotcha
stroke_thickness is absolute pixels by default; set stroke_units=% to make it a percentage of canvas width instead.
gotcha
The image aux exists only while stroke_enabled or fill_enabled is on.
Parameters
Name
Type
Default
Center X
centerX
scalar
0 … 1
0.5
Center Y
centerY
scalar
0 … 1
0.5
Radius
radius
scalar
0 … 1
0.3
Sides
sides
scalar
3 … 64
5
Rotation (°)
rotation
scalar
-180 … 180
0
Trim start
trim_start
scalar
0 … 1
0
Trim end
trim_end
scalar
0 … 1
1
Trim offset
trim_offset
scalar
-1 … 1
0
Stroke
stroke_enabled
boolean
true
Thickness
stroke_thickness
scalar
0 … 200
4
Units
stroke_units
enum
"px"
px · %
Stroke color
stroke_color
color
"#ffffff"
Fill
fill_enabled
boolean
false
Fill color
fill_color
color
"#ffffff"
Fill fit
fill_fit
enum
"window"
window · contain · cover
Opacity
opacity
scalar
0 … 1
1
Sockets
Direction
Name
Type
input
fill
fill
image
input
Transform
transform
transform
outputprimary
out
spline
outputaux
image
image
outputaux
element
element
Rectangle
rectanglepolymorphic
Generate a rectangle as a closed spline, optionally with round or squircle corners. Authored in [0,1]² Y-down; the rasterizer scales y about 0.5 by W/H so sizes stay width-relative.
originX/originY is the rectangle's CENTER, not its top-left corner, despite the param name kept for save-load back-compat; pre-conversion projects can look shifted on load.
gotcha
corner_radius is clamped to half the shorter side (SVG's rule); corner_style (round/squircle) only shows and matters once corner_radius > 0.
gotcha
rotate spins about the rectangle's own center (originX/originY) in degrees, +CW, independent of any wired transform input's own rotation.
gotcha
Anchor count varies with shape: 4 sharp corners at radius 0, 8 for round corners, 12 (or 16 with a residual circular arc) for squircle corners.
gotcha
stroke_thickness is absolute pixels by default; set stroke_units=% to make it a percentage of canvas width instead.
Parameters
Name
Type
Default
X
originX
scalar
0 … 1
0.5
Y
originY
scalar
0 … 1
0.5
Width
width
scalar
0 … 1
0.5
Height
height
scalar
0 … 1
0.5
Corner radius
corner_radius
scalar
0 … 0.5
0
Corner style
corner_style
enum
"round"
Round · Squircle
Rotate (°)
rotate
scalar
-360 … 360
0
Trim start
trim_start
scalar
0 … 1
0
Trim end
trim_end
scalar
0 … 1
1
Trim offset
trim_offset
scalar
-1 … 1
0
Stroke
stroke_enabled
boolean
true
Thickness
stroke_thickness
scalar
0 … 200
4
Units
stroke_units
enum
"px"
px · %
Stroke color
stroke_color
color
"#ffffff"
Fill
fill_enabled
boolean
false
Fill color
fill_color
color
"#ffffff"
Fill fit
fill_fit
enum
"window"
window · contain · cover
Opacity
opacity
scalar
0 … 1
1
Sockets
Direction
Name
Type
input
fill
fill
image
input
Transform
transform
transform
outputprimary
out
spline
outputaux
image
image
outputaux
element
element
Shortest Path
shortest-pathpolymorphic
Route through a network and output the path as a spline. Points mode: hops between points within the max distance, from a start position (snapped to the nearest point) to an end position or group — visited points ride the aux output. Spline mode: walks a wired network's own curved segments between two groups, or emits N seeded random routes. Tree mode: one root point (by index) fans out to every reachable point as a branching tree that never re-crosses — wander morphs direct↔minimal branching, and the update mode locks the topology at frame 0 (edges stretch as points move) or rebuilds live with an optional glide. Points and tree modes share Connect Points' path shaping (arcs / S-curves, sag, flow, network smoothing, bundling, attract). Animate the draw-on with Trim Path.
mode=points/tree treat any two points within max_distance as a hop (Connect Points graph); mode=spline instead walks a spline's own segments, welded within weld_distance.
gotcha
points mode: end_mode=group routes to the nearest member of a groupIndex bucket; end_mode=position uses the end vec2/x,y — both resolve to the nearest existing point.
gotcha
tree mode: wander=0 grows a direct shortest-path tree to root_index (radial); wander=1 a minimum-spanning tree (least wire); values between blend via one Prim/Dijkstra pass.
gotcha
tree mode jitter hashes on the unordered point-index pair, not edge order, so the wobble stays stable when a live rebuild reorders or drops edges.
gotcha
tree update=locked freezes topology at tick 0 (or a topology/count change), so edges just stretch as points move; update=live rebuilds every frame instead.
gotcha
tree update=live's `smooth` eases a reconnected branch's far end to its new parent — the node's only per-frame stateful behavior.
gotcha
Path shaping (arcs, sag, flow, network, bundle, attract) applies only to points and tree modes; spline mode always re-emits the network's own authored bezier geometry.
gotcha
The `points` aux (visited points, original attributes intact) exists only for points and tree modes; spline mode has no points aux.
Generate a sine wave as an open spline — set amplitude, cycles, phase, and the horizontal span. Phase is in ×π units (0.5 = π/2, 1 = π; range −2..2), not cycles or radians. Authored in [0,1]² Y-down; the rasterizer scales y about 0.5 by W/H so amplitude stays width-relative.
stroke_thickness is absolute pixels when stroke_units=px (default); stroke_units=% resolves it as a fraction of canvas width instead, so it scales with output size.
gotcha
resolution sets sample points per cycle threaded through a Catmull-Rom spline; too low visibly facets the curve at high cycles or amplitude.
gotcha
The fill aux only samples a wired `fill` image when fill_enabled is on and something is connected; otherwise it flat-fills with fill_color.
Parameters
Name
Type
Default
Center X
centerX
scalar
0 … 1
0.5
Center Y
centerY
scalar
0 … 1
0.5
Width
width
scalar
0 … 1
0.8
Amplitude
amplitude
scalar
0 … 1
0.15
Cycles
cycles
scalar
0.25 … 20
2
Phase (×π)
phase
scalar
-2 … 2
0
Points / cycle
resolution
scalar
4 … 64
16
Trim start
trim_start
scalar
0 … 1
0
Trim end
trim_end
scalar
0 … 1
1
Trim offset
trim_offset
scalar
-1 … 1
0
Stroke
stroke_enabled
boolean
true
Thickness
stroke_thickness
scalar
0 … 200
4
Units
stroke_units
enum
"px"
px · %
Stroke color
stroke_color
color
"#ffffff"
Fill
fill_enabled
boolean
false
Fill color
fill_color
color
"#ffffff"
Fill fit
fill_fit
enum
"window"
window · contain · cover
Opacity
opacity
scalar
0 … 1
1
Sockets
Direction
Name
Type
input
fill
fill
image
input
Transform
transform
transform
outputprimary
out
spline
outputaux
image
image
outputaux
element
element
Space Fill
space-fill
Fill space with packed self-avoiding walk lines — a walker steps across a pixel grid always heading as straight as it can through free cells, blocking a fat neighborhood as it goes, so lines pack tightly against each other into dense maze-like structure with long parallel runs and 45°/90° kinks. Each line is one open subpath tagged with a group id (ID Mode: per-line, seeded random, spatial cluster, draw order, or length rank) — style downstream with Rasterize Spline / Stroke ramps set to 'group', and map per-line weight with Stroke's thickness 'driver' mode. Progress reveals the drawing over time (keyframe it to watch it draw); Region and Obstacles masks confine where the walk may go. Aux `tips` emits one point per line at its current drawing tip.
Primary and tips positions are raw pixel fractions (x/width, y/height) — uv01, not aspect-corrected canvas01 — so shapes skew on non-square canvases.
gotcha
Region: mask value < 0.5 is blocked (white is walkable). Obstacles: value >= 0.5 is blocked (white is blocked) — opposite polarity from Region.
gotcha
The full trace is built once per (seed, region/obstacles content, resolution, walk params) and cached in ctx.state; progress only slices it, never retraces.
gotcha
step_min/step_max/spacing are literal pixel-grid steps on ctx.width×ctx.height; margin (0..0.25) is a fraction of min(width,height) converted to pixels.
gotcha
tips aux sets scale.x and scale.y both to the per-line weight (not a size) and rotation to the heading of the line's last drawn segment.
gotcha
Trace building stops early if max_lines/coverage aren't reached within 500,000 total steps or after 8 consecutive lines fail to find a start.
Parameters
Name
Type
Default
Progress
progress
scalar
0 … 1
1
Coverage
coverage
scalar
0.05 … 1
0.85
Max lines
max_lines
scalar
1 … 2000
250
Seed
seed
scalar
0 … 10000
0
Margin
margin
scalar
0 … 0.25
0.02
Step min
step_min
scalar
1 … 8
1
Step max
step_max
scalar
1 … 8
4
Metric
metric
enum
"straight"
straight · farthest · manhattan · weighted
Wobble
wobble
scalar
0 … 1
1
Wander
wander
scalar
0 … 1
0.1
Auto spacing
spacing_auto
boolean
true
Spacing (px)
spacing
scalar
0 … 8
2
Max line steps
line_steps
scalar
16 … 20000
3000
Starts
start_mode
enum
"near-previous"
near-previous · anywhere
Start area
start_area
scalar
0.05 … 1
1
Weight min
weight_min
scalar
0 … 1
0.6
Weight max
weight_max
scalar
0 … 1
0.9
ID mode
id_mode
enum
"cluster"
cluster · random · order · size · line
ID groups
id_groups
scalar
2 … 32
6
Cluster grid
cluster_grid
scalar
2 … 16
4
ID seed
id_seed
scalar
0 … 10000
0
Sockets
Direction
Name
Type
input
Region
region
mask
input
Obstacles
obstacles
mask
outputprimary
out
spline
outputaux
tips
points
Spiral
spiralpolymorphic
Generate an Archimedean spiral as an open spline — set turns, inner/outer radius, start angle (degrees, 0 = +X), direction, and reverse (walk outer→inner without swapping radii). Authored in [0,1]² Y-down; the rasterizer scales y about 0.5 by W/H so radii stay width-relative.
stroke_thickness is pixels by default; stroke_units=% resolves it as a percent of canvas width instead, so it holds its look across resolutions.
gotcha
innerRadius/outerRadius are each clamped to ≥0 independently with no ordering clamp — inner > outer just makes the spiral wind inward instead of out.
gotcha
The fill input image is only sampled when fill_enabled is on; fill_fit (window/contain/cover) likewise does nothing while fill is off.
gotcha
The image aux only exists when stroke_enabled or fill_enabled is on; the element aux is always emitted regardless.
Parameters
Name
Type
Default
Center X
centerX
scalar
0 … 1
0.5
Center Y
centerY
scalar
0 … 1
0.5
Turns
turns
scalar
0.25 … 20
3
Inner radius
innerRadius
scalar
0 … 1
0
Outer radius
outerRadius
scalar
0 … 1
0.35
Points / turn
pointsPerTurn
scalar
4 … 64
16
Start angle (°)
startAngle
scalar
-360 … 360
0
Direction
direction
enum
"clockwise"
clockwise · counterclockwise
Reverse
reverse
boolean
false
Trim start
trim_start
scalar
0 … 1
0
Trim end
trim_end
scalar
0 … 1
1
Trim offset
trim_offset
scalar
-1 … 1
0
Stroke
stroke_enabled
boolean
true
Thickness
stroke_thickness
scalar
0 … 200
4
Units
stroke_units
enum
"px"
px · %
Stroke color
stroke_color
color
"#ffffff"
Fill
fill_enabled
boolean
false
Fill color
fill_color
color
"#ffffff"
Fill fit
fill_fit
enum
"window"
window · contain · cover
Opacity
opacity
scalar
0 … 1
1
Sockets
Direction
Name
Type
input
fill
fill
image
input
Transform
transform
transform
outputprimary
out
spline
outputaux
image
image
outputaux
element
element
Spline Draw
spline-drawpolymorphic
Author a bezier path with the pen tool over the preview canvas. Outputs the spline as data and, when stroke or fill is enabled, as a rasterized image.
space
param:stroke_thickness=pixels
gotcha
fill_enabled closes only the raster fill via a bezier curve from the last anchor's handles to the first; the spline output's closed flag stays as authored.
gotcha
Live Corners (per-anchor cornerRadius) are resolved into fillets before trim_start/trim_end, so trim measures arc length after filleting.
gotcha
spline (the anchor data) is hidden from the params panel — authored via the on-canvas pen tool — but is a normal, keyframable param under 'Path Animation'.
gotcha
stroke_thickness is pixels by default; stroke_units=% resolves it as a percent of canvas width instead.
gotcha
The image aux only exists when stroke_enabled or fill_enabled is on; unlike other spline primitives, this node has no element aux for Auto Layout.
Parameters
Name
Type
Default
Opacity
opacity
scalar
0 … 1
1
Trim start
trim_start
scalar
0 … 1
0
Trim end
trim_end
scalar
0 … 1
1
Trim offset
trim_offset
scalar
-1 … 1
0
Stroke
stroke_enabled
boolean
true
Thickness
stroke_thickness
scalar
0 … 200
4
Units
stroke_units
enum
"px"
px · %
Stroke color
stroke_color
color
"#ffffff"
Fill (closes path)
fill_enabled
boolean
false
Fill color
fill_color
color
"#ffffff"
Fill fit
fill_fit
enum
"window"
window · contain · cover
Sockets
Direction
Name
Type
input
fill
fill
image
outputprimary
out
spline
outputaux
image
image
Spline Trails
spline-trails
Trace each input point's motion over time as its own spline — the classic motion trail. Feed it anything animated (Points on Path with Animate, a particle sim's points, Modulate Points wiggle) and wire the output into Stroke or Rasterize Spline. Length sets how many seconds of history each trail keeps; Tail width tapers the stroke toward the oldest end (0 = fades to nothing, 1 = uniform). Trails follow point identity through membership changes, so a vanished point's trail fades out naturally. Scene-time driven: paused edits reposition trail heads without growing them, and exports reproduce the preview.
flags
simulation, unstable
gotcha
length is seconds of scene time (ctx.time), min 0.05s; not wall-clock, so retiming/export pre-roll reproduces the preview exactly.
gotcha
Identity is groupIndex+ordinal when the input is tagged, else array index; a vanished point's trail keeps aging out instead of vanishing.
gotcha
clear_on_loop wipes every trail when scene time jumps backward (timeline loop/rewind); off, old trails just keep aging out normally.
gotcha
Paused or same-tick re-evals move the newest sample to the point's current position without growing the trail's history.
gotcha
tail_width < 1 stamps anchor width tapering from tail_width at the oldest sample to 1 at the head; at 1 no width is written.
gotcha
Each trail is emitted oldest-to-newest, so the path's end anchor is the current point position (matters for Trim/draw-on reveal).
gotcha
A stationary point (moved under ~1e-6 units) refreshes its head sample's timestamp instead of pushing a near-duplicate.
gotcha
curve=smooth fits a Catmull-Rom spline through samples; linear emits a straight polyline through the raw sample points.
Parameters
Name
Type
Default
Length (s)
length
scalar
0.05 … 30
0.75
Curve
curve
enum
"smooth"
linear · smooth
Tail width
tail_width
scalar
0 … 1
0
Clear on loop
clear_on_loop
boolean
true
Sockets
Direction
Name
Type
input*
points
points
points
outputprimary
out
spline
Star
starpolymorphic
Generate an N-point star as a closed spline — set the point count, outer/inner radius, and rotation. Authored in [0,1]² Y-down; the rasterizer scales y about 0.5 by W/H so radii stay width-relative.
stroke_thickness is pixels by default; stroke_units=% resolves it as a percent of canvas width instead.
gotcha
outerRadius/innerRadius are each clamped to ≥0 independently with no ordering clamp — innerRadius > outerRadius makes the valleys poke out past the points.
gotcha
The fill input image is only sampled when fill_enabled is on; fill_fit (window/contain/cover) likewise does nothing while fill is off.
gotcha
The image aux only exists when stroke_enabled or fill_enabled is on; the element aux is always emitted regardless.
Parameters
Name
Type
Default
Center X
centerX
scalar
0 … 1
0.5
Center Y
centerY
scalar
0 … 1
0.5
Points
points
scalar
3 … 32
5
Outer radius
outerRadius
scalar
0 … 1
0.3
Inner radius
innerRadius
scalar
0 … 1
0.13
Rotation (°)
rotation
scalar
-180 … 180
0
Trim start
trim_start
scalar
0 … 1
0
Trim end
trim_end
scalar
0 … 1
1
Trim offset
trim_offset
scalar
-1 … 1
0
Stroke
stroke_enabled
boolean
true
Thickness
stroke_thickness
scalar
0 … 200
4
Units
stroke_units
enum
"px"
px · %
Stroke color
stroke_color
color
"#ffffff"
Fill
fill_enabled
boolean
false
Fill color
fill_color
color
"#ffffff"
Fill fit
fill_fit
enum
"window"
window · contain · cover
Opacity
opacity
scalar
0 … 1
1
Sockets
Direction
Name
Type
input
fill
fill
image
input
Transform
transform
transform
outputprimary
out
spline
outputaux
image
image
outputaux
element
element
String Art
string-art
Times-table string art: connect ordered point i to point (i·k + offset) mod N with a straight chord. Connecting i → 2i around a circle draws a cardioid, 3i a nephroid, etc. — the line envelopes are the curve. Feed evenly-spaced points (e.g. Points on Path around a circle or rectangle). Optional second layer with its own multiplier. Pairs with Spline Intersections to mark the chord crossings.
writes
attr:group
gotcha
Connects ordered point i to point (i*k+offset) mod N; k, offset, and k2 are floored to integers and k clamps up to 1 even below the param's UI min of 2.
gotcha
A chord whose target equals its source index (i === j) is skipped, so the input point order/spacing decides which indices land on themselves.
gotcha
layer2 adds a second chord set stamped groupIndex 1 (attr:group) alongside the first layer's 0, so a later node can separate the two layers.
gotcha
The points aux output passes the input points through unchanged, letting a downstream node consume both the chords and the source ring.
Parameters
Name
Type
Default
Multiplier k
k
scalar
2 … 200
2
Offset
offset
scalar
0 … 200
0
Second layer
layer2
boolean
false
Multiplier k₂
k2
scalar
2 … 200
5
Sockets
Direction
Name
Type
input*
points
points
points
outputprimary
out
spline
outputaux
points
points
SVG Source
svg-sourcepolymorphic
Load an SVG file and emit it as spline data. Built-in translate/scale/rotate operate on the result; stroke and fill rasterize to an image.
rotate/scale apply directly in raw [0,1]² space before the y-aspect correction at raster time, so shapes skew rather than rotate/scale rigidly on non-square canvases.
gotcha
stroke_thickness is always literal pixels here — unlike Spiral/Star/Spline Draw, there is no stroke_units toggle for percent-of-width.
gotcha
The fill input image is only sampled when fill_enabled is on (defaults true here, unlike sibling primitives); fill_fit does nothing while fill is off.
gotcha
The image aux only exists when stroke_enabled or fill_enabled is on; this node has no element aux for Auto Layout, unlike Spiral/Star.
Parameters
Name
Type
Default
Opacity
opacity
scalar
0 … 1
1
SVG
file
svg_file
—
Stroke
stroke_enabled
boolean
false
Thickness (px)
stroke_thickness
scalar
0 … 200
2
Stroke color
stroke_color
color
"#ffffff"
Fill
fill_enabled
boolean
true
Fill color
fill_color
color
"#ffffff"
Fill fit
fill_fit
enum
"window"
window · contain · cover
Translate X
translateX
scalar
-2 … 2
0
Translate Y
translateY
scalar
-2 … 2
0
Scale X
scaleX
scalar
-5 … 5
1
Scale Y
scaleY
scalar
-5 … 5
1
Rotate (deg)
rotate
scalar
-360 … 360
0
Pivot X
pivotX
scalar
0 … 1
0.5
Pivot Y
pivotY
scalar
0 … 1
0.5
Sockets
Direction
Name
Type
input
fill
fill
image
outputprimary
out
spline
outputaux
image
image
Modifiers
Blend Intersections
blend-intersections
Fuse a network of splines into one closed outline shape: thin stroke bodies that swell into webbed ink-pools wherever strokes cross or come within the Blend radius — including a stroke crossing itself. Width sets the stroke body thickness, Blend the webbing size (0 = plain union of the stroked bodies). Outputs a spline — wire into Rasterize Spline to fill it, or stroke its outline. Combine multiple splines upstream with Collect.
space
param:width,param:blend=pixels
gotcha
width/blend are absolute pixels by default (do not scale with output size); switching units to "%" resolves them against canvas width instead.
gotcha
blend=0 reduces the effect to a plain union of the stroked bodies, with no webbing between crossings.
gotcha
The SDF field samples only the network's bounding box at `resolution` taps per side, not the whole canvas.
gotcha
Geometry only — nothing rasterizes until the output spline reaches Rasterize Spline or a stroke node.
Parameters
Name
Type
Default
Width
width
scalar
0.5 … 200
6
Blend
blend
scalar
0 … 400
24
Units
units
enum
"px"
px · %
Resolution
resolution
scalar
64 … 768
288
Smoothing
smoothing
scalar
0 … 1
0.5
Sockets
Direction
Name
Type
input*
path
path
spline
outputprimary
out
spline
Differential Growth
differential-growth
Grow a curve faster than the space around it can hold, so it has no choice but to buckle — brain coral, kelp, ruffled leaf margins, intestines. Every node pushes its neighbours apart while the chain pulls itself together, and any edge stretched past Split length inserts a new node, so the curve keeps lengthening in place. Closed input loops become inflating blobs (raise Pressure), open ones become tendrils. The mode sets WHERE new length appears: Uniform ruffles evenly, Curvature grows convex regions faster so bulges sharpen and sub-divide into cauliflower lobes, Field takes a wired mask so you can paint exactly where it crinkles, and Noise breaks it up patchily. Region confines the curve and Obstacles carve holes it must ruffle around. This is a live simulation, not a cached one — it evolves while the timeline plays and resets when the scene loops, so there is no Progress slider. Bend stiffness trades a smooth ruffle for a jagged one; Collapse length keeps the anchor count from running away.
split_length, collapse_length, and repulsion_radius are canvas-width-relative fractions (×W to pixels), like spline positions, so repulsion reads as a circle on any aspect ratio.
gotcha
Insertion is probabilistic below split_length (growth_rate×0.04×edge weight per frame), forced above it; repulsion_radius clamps to 0.35–0.9×split_length, collapse_length to ≤0.25×repulsion_radius.
gotcha
mode=field samples growth_field at each edge midpoint (defaults to 1, i.e. uniform, if unwired); mode=noise hashes the quantized edge midpoint instead of reading any input.
gotcha
No progress control: a per-frame sim that only advances when ctx.time changes, and reseeds from the input spline when its topology signature changes or time wraps back near 0 (scene loop).
gotcha
Each output subpath's driver field is 1 − 1/max(1, currentLength/seedLength), asymptotic toward 1 as the curve outgrows its seed; read via Rasterize/Stroke's driver ramp, not an attrs map entry.
gotcha
aux:points stamps rotation from local tangent angle and scale.x/scale.y from half the local edge length (a growth-crowding proxy), not from any styling param.
Parameters
Name
Type
Default
Mode
mode
enum
"uniform"
uniform · curvature · field · noise
Split length
split_length
scalar
0.002 … 0.1
0.012
Collapse length
collapse_length
scalar
0 … 0.05
0.003
Growth rate
growth_rate
scalar
0 … 1
1
Repulsion radius
repulsion_radius
scalar
0.002 … 0.2
0.009
Repulsion
repulsion_strength
scalar
0 … 2
1
Attraction
attraction_strength
scalar
0 … 2
0.6
Bend stiffness
bend_stiffness
scalar
0 … 1
0.3
Pressure
pressure
scalar
-1 … 1
0
Damping
damping
scalar
0.01 … 1
0.5
Iterations
iterations
scalar
1 … 20
3
Curves collide
inter_curve
boolean
true
Pin ends
pin_ends
boolean
false
Containment
containment
scalar
0 … 1
1
Max anchors
max_anchors
scalar
32 … 100000
8000
Seed
seed
scalar
0 … 10000
0
Sockets
Direction
Name
Type
input*
Spline
spline
spline
input
Region
region
mask
input
Obstacles
obstacles
mask
input
Growth field
growth_field
mask
outputprimary
out
spline
outputaux
points
points
Diffusion Curves
diffusion-curves
Paint with gradients by drawing their boundaries (Orzan et al. 2008). Every subpath of the wired spline becomes a diffusion curve: the Left/Right color ramps run along the curve's length and diffuse outward from its two sides, filling the whole canvas with smooth shading while staying sharp across the curve itself. Per-stop ramp alpha diffuses too, so a drawing can fade to transparent for Merge compositing. Wire an image into `Trace` (color source: image) to sample its colors along your strokes instead — a few curves over a photo reconstruct a painterly version, live when the trace input animates. Blur softens the transition across the curve, shaped along its length by the blur curve. Crossing curves compete and blend — split curves or add stops to control junctions. Quality raises solver iterations; Resolution trades edge sharpness for speed on animated curves.
space
param:blur_max,param:source_distance=pixels
gotcha
blur_max is canvas pixels; the blur pass runs at canvas resolution after upsampling so radii stay exact regardless of the resolution param.
gotcha
source_distance is canvas pixels, rescaled internally to the solve grid by resolution before use as the color-source/gradient band offset.
gotcha
ramp_span=whole spline positions ramp stops by t along the entire spline; per curve (default) positions them by t within each subpath.
gotcha
color_source=image samples the wired trace image directly for both source color and the cross-curve jump, ignoring left_colors/right_colors.
gotcha
view=sources/blur map return diagnostic buffers (the color-source texture, the normalized blur map) instead of the diffused result.
gotcha
Blur is skipped entirely when blur_max is 0 or every sampled blur-curve sigma rounds to ~0, even if blur_max itself is nonzero.
Fill the interior of a spline. Open subpaths close implicitly for rendering. Choose evenodd (SVG default, punches holes in nested subpaths) or nonzero winding rule.
gotcha
stack_subpaths=true (default) fills each subpath independently and stacks overlaps as opaque; turn it off to union all subpaths into one path so rule can punch holes.
gotcha
rule (evenodd/nonzero) is only used, and only shown, when stack_subpaths is off.
gotcha
Open subpaths are always closed implicitly for the fill, regardless of the spline's own closed flag.
Parameters
Name
Type
Default
Color
color
color
"#ffffff"
Stack subpaths
stack_subpaths
boolean
true
Fill rule
rule
enum
"evenodd"
evenodd · nonzero
Sockets
Direction
Name
Type
input*
path
path
spline
outputprimary
out
image
Filter Splines
filter-splines
Keep or discard whole subpaths of a spline by predicate — the spline sibling of Filter Points. Bbox tests each subpath's bounds against an XY window (by center, fully inside, or overlapping); Mask keeps subpaths where the wired image's luminance is ≥ threshold, sampled at the center or averaged along the path; Size keeps subpaths whose length or area falls in a range (raise Min to cull speck fragments after a boolean or trace); Index keeps 1 of every N subpaths; Random keeps a stable random subset (hashed on index, so shapes don't flicker in/out — raising Amount reveals more of the same subpaths, Seed re-rolls the selection). Invert flips which side is kept. Anchors are never touched, so surviving subpaths keep their exact geometry, group tags, and drivers.
bbox test picks what "inside" means: center (bbox center in window, like Filter Points), inside (whole bbox inside), or overlap; an empty subpath always fails the test.
gotcha
size_metric=length measures the flattened polyline's arc length (~1 spans canvas width); area is the |shoelace| of that polyline, implicitly closed even for open subpaths.
gotcha
mask_sample=average samples every flattened polyline vertex (up to 16 per curve) and averages luminance; center samples only the bbox center, which can miss long open strokes.
gotcha
index/random key off subpath order in the input array, using the same triple32 hash as Filter Points and Point Expression's rand() (seed 0 == hash01(index)), so a cull agrees across nodes.
gotcha
Anchors, closed, groupIndex, and driver are never rewritten — surviving subpaths pass through by reference, and group tags are not renumbered (identity, like Filter Points).
gotcha
An unwired mask input passes every subpath through unchanged instead of dropping them.
Parameters
Name
Type
Default
Mode
mode
enum
"bbox"
bbox · mask · size · index · random
X min
x_min
scalar
-1 … 2
0
X max
x_max
scalar
-1 … 2
1
Y min
y_min
scalar
-1 … 2
0
Y max
y_max
scalar
-1 … 2
1
Test
test
enum
"center"
center · inside · overlap
Threshold
threshold
scalar
0 … 1
0.5
Sample
mask_sample
enum
"center"
center · average
Metric
size_metric
enum
"length"
length · area
Min
size_min
scalar
0 … 4
0
Max
size_max
scalar
0 … 4
4
Keep 1 of every
every
scalar
1 … 32
2
Offset
offset
scalar
0 … 32
0
Amount
amount
scalar
0 … 1
0.5
Seed
seed
scalar
0 … 999
0
Invert
invert
boolean
false
Sockets
Direction
Name
Type
input*
path
path
spline
input
Mask
mask
image
outputprimary
out
spline
Modulate Splines
modulate-splinespolymorphic
Per-subpath scale and rotation around each subpath's centroid. Uniform inputs apply to every subpath; image fields are sampled at the centroid UV. Drop after Copy to Points (spline mode) for per-instance Transform — each copy is its own subpath. Stack to layer modulations.
gotcha
Transform is destructive: anchor positions and handles are actually scaled/rotated around each subpath's own centroid, unlike Modulate Points which only tags metadata.
gotcha
Field images are read back synchronously at full resolution every eval (ctx.readImagePixels), with no downsampling or async caching like Modulate Points uses.
gotcha
Centroid UV sampling has no aspect correction, so on non-square canvases the sample point can drift from the subpath's visual location.
gotcha
Empty subpaths sample the field at a hardcoded (0.5, 0.5) fallback centroid since there are no anchors to average.
Parameters
Name
Type
Default
Scale × default
scale_mul_default
scalar
0 … 4
1
Rotate + default (rad)
rotate_add_default
scalar
-3.141592653589793 … 3.141592653589793
0
Scale field — black
scale_field_lo
scalar
0 … 4
0.5
Scale field — white
scale_field_hi
scalar
0 … 4
1.5
Rotate field amount (rad)
rotate_field_amount
scalar
0 … 3.141592653589793
3.141592653589793
Sockets
Direction
Name
Type
input*
splines
splines
spline
input
Scale × (uniform)
scale_mul
scalar
input
Rotate + (uniform)
rotate_add
scalar
input
Scale field
scale_field
image
input
Rotate field
rotate_field
image
outputprimary
out
spline
Offset Path
spline-offset
Offset each subpath perpendicular to its tangent. Useful for variable-width strokes and outline variants without rasterizing. When sharp corners make the offset overlap itself, the Overlap mode cuts the crossing loop — Sharp resolves it to a single point, Smooth rounds the cut.
space
param:distance=canvas01
gotcha
distance=0 short-circuits and returns the input spline unchanged, skipping offsetSubpath entirely.
gotcha
overlap=keep leaves self-intersections from sharp corners in place; sharp/smooth only cut/fillet the crossing loop when set to one of those modes.
gotcha
The fillet radius for overlap=smooth is smoothing x |distance| x canvas width, computed in pixel space even though distance itself is canvas01.
gotcha
Each subpath is offset independently, so compound paths (e.g. letter holes) keep separate offset loops rather than merging.
Parameters
Name
Type
Default
Distance
distance
scalar
-0.5 … 0.5
0.02
Overlap
overlap
enum
"keep"
keep · sharp · smooth
Smoothing
smoothing
scalar
0 … 1
0.5
Sockets
Direction
Name
Type
input*
path
path
spline
outputprimary
out
spline
Optimize Path
optimize-pathpolymorphic
Refit messy spline data — traces, scribbles, simulation output — to the fewest clean Bézier segments within a pixel tolerance. Corners sharper than the corner angle are preserved exactly; smooth spans are refit with far fewer anchors. Smoothing denoises jittery input before fitting (handle-less anchors only — authored geometry never moves); Cull specks drops subpaths shorter than the given length, the dust tracing leaves behind. Optimal mode searches for the true minimum segment count (slower — use for finals, not per-frame animation). Debug skeleton overlays the source path with the optimized result's anchors and handles (select the node to see it in the viewport) and mints it as an image output.
space
param:tolerance,param:cull_length=pixels
gotcha
tolerance and cull_length are pixels at render resolution and do not scale with output size, so changing canvas resolution changes how aggressively paths simplify.
gotcha
corner_angle is in degrees, not radians, unlike most angle params elsewhere in the engine.
gotcha
Until the WASM vector kernel finishes loading (async ~77KB fetch), the node passes the input spline through unchanged, then re-evaluates via a pipeline-bump event once ready.
Parameters
Name
Type
Default
Tolerance (px)
tolerance
scalar
0.01 … 50
0.5
Mode
mode
enum
"adaptive"
adaptive · optimal
Corner angle (°)
corner_angle
scalar
1 … 89
30
Smoothing
smoothing
scalar
0 … 1
0
Cull specks (px)
cull_length
scalar
0 … 1000
0
Debug skeleton
show_debug
boolean
false
Sockets
Direction
Name
Type
input*
path
path
spline
outputprimary
out
spline
Rasterize Spline
rasterize-spline
Rasterize a spline as fill, stroke, or both in a single pass. Fill draws underneath the stroke. Toggle each independently. Fill and stroke colors can each be a flat color or a ramp — fill keys per subpath (index, random, group, position, driver) or lays the ramp out as a gradient inside each subpath's own frame (linear / radial / conic; the shape frame rotates with each Copy to Points copy); stroke can do the same, or ramp by progress along each path's length (color A at the start, color B at the end, independent of canvas direction). Ramp offset slides the stroke gradient and wraps past 1 so it loops. Driver attribute (visible when Ramp by is Driver) reads a named subpath channel — Set Named Attribute authors it, Copy to Points gathers point attrs onto copies. Thickness source can vary per subpath the same way Stroke does (index / random / group / position / driver × lo→hi), so a graph can stay at one raster node. A wired fill image can drive the fill, the stroke, or both via the Image → fill / Image → stroke toggles. Per-anchor width (Spline Draw's Width tool) rides the spline: Thickness is the base, each anchor's width is a multiplier on it.
thickness/dash_length/dash_gap/dot_spacing/arrow_length default to raw pixels; units=% resolves them as a percent of canvas width instead.
gotcha
overlap=flatten draws all fills then all strokes on top; overlap=layered fills+strokes each subpath in order so later opaque fills occlude earlier strokes.
gotcha
holes groups subpaths into containment islands and fills each as one even-odd path so nested contours punch instead of filling solid; fill only, capped at 2048 subpaths.
gotcha
A per-anchor width profile (Spline Draw's Width tool) fills a variable-width envelope multiplying Thickness by each anchor's width, and ignores dash/dot style.
gotcha
stroke_ramp_by=progress or attribute ramps color along each subpath's own arc length (start→end) instead of one color per subpath; attribute reads stroke_driver_attr.
gotcha
ramp_by/stroke_ramp_by/thickness_by=group key off each subpath's stamped groupIndex (attr:group), e.g. from Copy to Points or String Art layers.
gotcha
fill_source=gradient lays fill_ramp out inside each subpath's own frame (gradient_kind linear/radial/conic), spanning its outline; gradient_offset/gradient_vary shift the phase and wrap.
gotcha
gradient_frame=shape takes up as center→first anchor (handedness from winding) so the gradient rotates/mirrors with each copy, angle 0 = left→right on an unrotated Circle; canvas fixes the angle.
Index · Random · Group · Position · Driver · Progress · Attribute
Seed
stroke_ramp_seed
scalar
0 … 9999
0
Gradient angle
stroke_ramp_angle
scalar
-180 … 180
0
Driver attribute
stroke_driver_attr
string
""
Ramp interpolation
stroke_ramp_interp
enum
"linear"
linear · ease · constant
Image → stroke
image_stroke
boolean
false
Thickness
thickness
scalar
0 … 200
4
Units
units
enum
"px"
px · %
Thickness source
thickness_source
enum
"uniform"
uniform · vary
Vary by
thickness_by
enum
"random"
index · random · group · position · driver
Seed
thickness_seed
scalar
0 … 9999
0
Gradient angle
thickness_angle
scalar
-180 … 180
0
Driver attribute
thickness_driver_attr
string
""
Thickness — low ×
thickness_lo
scalar
0 … 4
0.5
Thickness — high ×
thickness_hi
scalar
0 … 4
1.5
Style
style
enum
"solid"
solid · dashed · dotted
Dash length
dash_length
scalar
0.5 … 200
10
Dash gap
dash_gap
scalar
0.5 … 200
8
Dot spacing
dot_spacing
scalar
1 … 200
12
Cap
cap
enum
"round"
round · butt · square · arrowhead
Arrow length
arrow_length
scalar
0 … 200
16
Arrow angle
arrow_angle
scalar
5 … 80
25
Arrow style
arrow_style
enum
"filled"
filled · stroke
Arrow at start
arrow_start
boolean
false
Arrow at end
arrow_end
boolean
true
Join
join
enum
"round"
round · miter · bevel
Miter limit
miter_limit
scalar
1 … 20
10
Close open paths
close_open_paths
boolean
false
Sockets
Direction
Name
Type
input*
path
path
spline
input
fill
fill
image
outputprimary
out
image
Repeat Path
spline-repeat
Repeat a path as parallel-offset copies — count, inner/outer/both direction, band width, and a spacing curve that places each copy within the band. Closed shapes are winding-normalized (outer always expands); for open paths inner/outer mean left/right of the travel direction. Each copy is tagged with its own group index. When sharp corners make an offset copy overlap itself, the Overlap mode cuts the crossing loop — Sharp resolves it to a single point, Smooth rounds the cut.
space
param:width=canvas01
writes
attr:group
gotcha
width is a canvas-width-relative band fraction (SDF's aspect convention); count strokes pack inside that band, not stack past it — more count doesn't grow the outer radius.
gotcha
direction=both mirrors every nonzero offset to both sides; count is then per side, and a zero offset still emits once, not doubled.
gotcha
Closed subpaths are winding-normalized so outer always expands and inner always contracts; open subpaths have no interior, so inner/outer become left/right of travel.
gotcha
overlap=sharp/smooth resolves self-crossings per ring in pixel space; smoothing is a 0..1 fraction of that ring's own offset distance used as the fillet radius.
gotcha
Each ring is stamped with its own groupIndex (0..count-1), so group-aware downstream nodes (e.g. Stroke's ramp/vary by=group) can address rings individually.
Redistribute anchors evenly along the arc length of the spline. Preserves subpath count and closed/open state.
gotcha
Handle length is local: 1/3 of the chord to each side's neighbour, not mean spacing, so tight bends don't overshoot into a loop or kink.
gotcha
Per-anchor width profile (e.g. from Taper Spline) and any named anchor attrs are resampled by interpolating along arc length, not dropped.
gotcha
count is anchors per subpath — every subpath in a multi-subpath spline is resampled independently to the same count.
gotcha
Closed subpaths distribute count samples around the full loop without a duplicate seam anchor; open subpaths always keep both endpoints.
Parameters
Name
Type
Default
Anchors per subpath
count
scalar
2 … 512
16
Sockets
Direction
Name
Type
input*
path
path
spline
outputprimary
out
spline
Rigid Body Simulator
rigid-body-simulatorpolymorphic
2D rigid body dynamics: each input subpath becomes a rigid body that falls, tumbles, and stacks — colliding with collider nodes, the canvas bounds, and other bodies (edge-accurate contacts). Glue makes touching bodies bond and snap apart under stress. Rigidity below 1 turns bodies to jelly. At full rigidity the output is your original curves under each body's rotation + translation. Resets when scene time returns to 0.
segment_px, pin_radius, thickness, and glue_break are literal pixels at render resolution, not canvas01 fractions, so re-rendering at a different output size changes the sim.
gotcha
At rigidity=1 the output is the exact original beziers under each body's fitted rotation+translation; output_mode (smooth/polyline) only exists and matters below rigidity=1.
gotcha
glue>0 mints particle-particle bonds at body contacts (rest = contact distance); a bond overstretched past glue_break x its strength snaps, emitting a point on snaps that frame.
gotcha
Reseeds (loses velocity/bond state) when subpath/anchor topology or seed params change, or when scene time wraps back near 0; shape-only animation of the same topology does not reseed.
gotcha
The bodies aux point per body stamps attr:rotation from the fitted angle and attr:group from the subpath's array index, not any groupIndex tag carried on the input spline.
gotcha
follow_input re-samples the live input spline at each free particle's recorded arc-length fraction every eval, layering secondary motion on top of keyframed spline animation.
gotcha
pins captures the nearest unclaimed particle within pin_radius per input point at reseed, then snaps it to that point's live position every eval (puppet dragging).
Parameters
Name
Type
Default
Segment length (px)
segment_px
scalar
2 … 64
8
Max points
max_points
scalar
16 … 32768
2000
Pin ends
pin_mode
enum
"none"
none · start · end · both
Pin capture radius (px)
pin_radius
scalar
2 … 64
12
Follow input
follow_input
boolean
false
Gravity X
gravity_x
scalar
-2 … 2
0
Gravity Y
gravity_y
scalar
-2 … 2
0.35
Rigidity
rigidity
scalar
0.05 … 1
1
Damping
damping
scalar
0 … 1
0.02
Friction
friction
scalar
0 … 1
0.2
Bounciness
bounciness
scalar
0 … 1
0.2
Glue
glue
scalar
0 … 1
0
Glue break (px)
glue_break
scalar
0.5 … 64
6
Body collisions
body_collide
boolean
true
Self-collision
self_collide
boolean
false
Thickness (px)
thickness
scalar
1 … 32
4
Friction map: live
friction_map_live
boolean
false
Bounce map: live
bounce_map_live
boolean
false
Glue map: live
glue_map_live
boolean
false
Substeps
substeps
scalar
1 … 16
6
Iterations
iterations
scalar
1 … 16
6
Frame dt
fixedDt
scalar
0.001 … 0.1
0.016666666666666666
Force slots
forceCount
scalar
0 … 6
1
Collider slots
colliderCount
scalar
0 … 6
1
Canvas bounds
boundsMode
enum
"off"
off · bounce · clamp
Bounds bounce
boundsRestitution
scalar
0 … 1
0.5
Output
output_mode
enum
"smooth"
smooth · polyline
Sockets
Direction
Name
Type
input*
splines
splines
spline
input
force1
force1
force
input
collider1
collider1
collider
input
pins
pins
points
input
friction_map
friction_map
mask
input
bounce_map
bounce_map
mask
input
glue_map
glue_map
mask
input
pin_map
pin_map
mask
outputprimary
out
spline
outputaux
bodies
points
outputaux
points
points
outputaux
snaps
points
Rope Simulator
rope-simulatorpolymorphic
2D rope/string dynamics: each input subpath becomes a chain of particles with distance and bending constraints, pulled by gravity and force nodes, colliding with collider nodes and the canvas bounds. Pin the ends and the rope hangs and swings; output is a live spline. Named attributes on the input spline (per-anchor and per-subpath) are sampled at rest and ride the particles, so an image-driven channel stays glued as the rope moves. Resets when scene time returns to 0.
segment_px, pin_radius, and thickness are literal pixels at render resolution, not canvas01 fractions, so re-rendering at a different output size changes the sim.
gotcha
stretchiness sets XPBD compliance (alpha proportional to stretchiness^2 x stretch_map), so 0 is rigid PBD and the constraint stays substep/iteration independent.
gotcha
tearing breaks an edge when strain exceeds tear_threshold x (1 - tear_map weakness); tear_map is a weakness field, so white tears at a touch and unwired/black keeps the base threshold.
gotcha
stickiness welds a touching particle to its collider (offset anchor for circle/line, world point for mask); drift past stickiness x stick_strength releases it, giving stick-slip sliding.
gotcha
Named per-anchor/per-subpath attrs on the input spline are sampled once at each particle's rest arc-length position and ride with it, not re-sampled live as the rope drapes.
gotcha
Reseeds (losing velocity/weld/tear state) on topology change, seed-param change, canvas resize, or scene-time wrap; shape-only animation of the same topology does not reseed.
gotcha
pins captures the nearest unclaimed particle within pin_radius per input point at reseed, then snaps it to that point's live position every eval (puppet dragging).
Parameters
Name
Type
Default
Segment length (px)
segment_px
scalar
2 … 64
8
Max points
max_points
scalar
16 … 32768
2000
Pin ends
pin_mode
enum
"both"
none · start · end · both
Pin capture radius (px)
pin_radius
scalar
2 … 64
12
Follow input
follow_input
boolean
false
Gravity X
gravity_x
scalar
-2 … 2
0
Gravity Y
gravity_y
scalar
-2 … 2
0.35
Stiffness
stiffness
scalar
0 … 1
0.1
Damping
damping
scalar
0 … 1
0.02
Friction
friction
scalar
0 … 1
0.2
Bounciness
bounciness
scalar
0 … 1
0.3
Stretchiness
stretchiness
scalar
0 … 1
0
Stickiness
stickiness
scalar
0 … 1
0
Stick strength
stick_strength
scalar
0 … 5
1
Self-collision
self_collide
boolean
true
Thickness (px)
thickness
scalar
1 … 32
4
Tearing
tearing
boolean
false
Tear threshold
tear_threshold
scalar
0.01 … 4
0.5
Friction map: live
friction_map_live
boolean
false
Bounce map: live
bounce_map_live
boolean
false
Stretch map: live
stretch_map_live
boolean
false
Tear map: live
tear_map_live
boolean
false
Stick map: live
stick_map_live
boolean
false
Substeps
substeps
scalar
1 … 16
4
Iterations
iterations
scalar
1 … 16
4
Frame dt
fixedDt
scalar
0.001 … 0.1
0.016666666666666666
Force slots
forceCount
scalar
0 … 6
1
Collider slots
colliderCount
scalar
0 … 6
1
Canvas bounds
boundsMode
enum
"off"
off · bounce · clamp
Bounds bounce
boundsRestitution
scalar
0 … 1
0.5
Output
output_mode
enum
"smooth"
smooth · polyline
Sockets
Direction
Name
Type
input*
splines
splines
spline
input
force1
force1
force
input
collider1
collider1
collider
input
pins
pins
points
input
friction_map
friction_map
mask
input
bounce_map
bounce_map
mask
input
stretch_map
stretch_map
mask
input
tear_map
tear_map
mask
input
stick_map
stick_map
mask
input
pin_map
pin_map
mask
outputprimary
out
spline
outputaux
points
points
outputaux
tears
points
Round Corners
round-corners
Round the sharp corners of a spline with circular fillets. Radius is in normalized space (0.5 = half the canvas), capped per corner at half each edge. Curved anchors and open-path endpoints are left as-is. Outputs a spline — view it with Stroke or Rasterize Spline.
space
param:radius=canvas01
gotcha
radius is a canvas01 distance like anchor positions, capped per corner at half the length of each adjacent edge, so tight polygons round less than the slider value.
gotcha
Only handle-less (straight) interior corners round; anchors that already carry a handle and the endpoints of an open subpath pass through untouched.
gotcha
A per-anchor cornerStyle (chamfer/scoop) set upstream via Spline Draw's live corner tool is still honored even though this node exposes only one uniform radius.
gotcha
radius<=0 returns the input spline unchanged (same object reference), skipping the fillet pass entirely.
Parameters
Name
Type
Default
Radius
radius
scalar
0 … 0.5
0.04
Sockets
Direction
Name
Type
input*
path
path
spline
outputprimary
out
spline
Sample Along Path
sample-along-path
Output the position, tangent, and orientation angle at a given arc-length t ∈ [0,1] along a spline. Expose `t` as a socket to animate along the path; set Past ends to loop/ping-pong so `t` past 1 repeats. Wire `angle` (radians, tangent direction) or `normalAngle` (perpendicular) into a rotation input to orient an object along the path.
space
out=canvas01
gotcha
wrap folds t outside [0,1]: clamp (default) holds the nearest endpoint, loop is fract() so t=1 snaps to 0, ping-pong bounces 0-1-0; both handle negative t too.
gotcha
Arc length is measured across ALL subpaths concatenated in order, so a multi-subpath spline animates evenly by total painted distance, not per-subpath.
gotcha
angle/normalAngle are atan2 of the unit tangent in canvas01's Y-down frame, in radians; normalAngle = angle + 90 degrees. Transform's rotate param is degrees, not radians.
gotcha
position and both angle outputs come from the same arc-length sample, so they stay in sync as t animates.
Parameters
Name
Type
Default
t
t
scalar
0 … 2
0
Past ends
wrap
enum
"clamp"
clamp · loop · ping-pong
Sockets
Direction
Name
Type
input*
path
path
spline
outputprimary
out
vec2
outputaux
tangent
vec2
outputaux
angle
scalar
outputaux
normalAngle
scalar
Set Spline Type
set-spline-type
Convert a spline's anchors between linear (handles stripped — a polyline) and smooth (catmull-rom auto handles through every anchor, scaled by tension). Positions, closed state, groups, and named attributes are unchanged.
gotcha
spline_type=smooth is destructive to hand-authored handles — it replaces every anchor's in/out handles with catmull-rom auto handles regardless of what was there.
gotcha
tension scales the fitted handle length: 0 collapses close to linear, 1 is the true catmull-rom fit, values above 1 overshoot past the anchors.
gotcha
spline_type=linear strips handles entirely, producing a straight polyline through the existing anchor positions.
gotcha
Positions, closed flags, groupIndex, and named attributes are copied through unchanged in both modes.
Parameters
Name
Type
Default
Type
spline_type
enum
"smooth"
linear · smooth
Tension
tension
scalar
0 … 2
1
Sockets
Direction
Name
Type
input*
path
path
spline
outputprimary
out
spline
Spline Boolean
spline-booleanpolymorphic
Boolean of two splines. Subtract (A − B) cuts B out of A's filled region; also union, intersect, and exclude (XOR). For subtract/intersect, 'Treat A as line' cuts A's curves instead — gaps where B covers them (subtract) or only the covered arcs (intersect), keeping true bezier geometry. Outputs a spline, plus an image when stroke or fill is on.
space
param:stroke_thickness=pixels
gotcha
treat_a=line (subtract/intersect only) cuts A's curves instead of its fill: subtract leaves gaps where B covers A, intersect keeps only the covered arcs, as true bezier geometry.
gotcha
The default shape-mode boolean polygonalizes both curves first; `resolution` sets line segments per curve for that flattening (higher = smoother, heavier).
gotcha
With B unwired, A passes through completely unchanged (treated as an empty region/line).
gotcha
stroke_thickness is always literal pixels at render width — unlike the spline-primitive raster nodes, this node has no stroke_units %-toggle.
gotcha
The image aux only appears when stroke or fill is enabled, and shares the spline primitives' rasterizer, including even-odd fill so boolean holes render correctly.
Parameters
Name
Type
Default
Opacity
opacity
scalar
0 … 1
1
Operation
operation
enum
"subtract"
subtract · union · intersect · exclude
Treat A as
treat_a
enum
"shape"
shape · line
Curve resolution
resolution
scalar
3 … 96
24
Stroke
stroke_enabled
boolean
false
Thickness (px)
stroke_thickness
scalar
0 … 200
4
Stroke color
stroke_color
color
"#ffffff"
Fill
fill_enabled
boolean
true
Fill color
fill_color
color
"#ffffff"
Fill fit
fill_fit
enum
"window"
window · contain · cover
Sockets
Direction
Name
Type
input*
A (base)
a
spline
input
B (cut)
b
spline
input
fill
fill
image
outputprimary
out
spline
outputaux
image
image
Spline Flow Field
spline-flow-field
Turn a drawn spline into a divergence-free velocity field (encoded as a signed-RG image, midlevel 0.5): content wired through Advect Image / Advect Points (field mode `vector`) / Displace streams along the curve. `along` mode flows down the path's direction inside a ribbon of `width`; `orbit` circulates around the whole stroke like a vortex filament. Negative strength reverses the flow. Chain fields through the optional `field` input (e.g. Perlin Noise curl for ambient turbulence + this for direction) — contributions sum into one flow. The field has no sources or sinks by construction, so advected content swirls instead of piling up.
space
param:width=canvas01
gotcha
Output is a velocity field encoded as a signed-RG image (midlevel 0.5); wire it into Advect Image / Advect Points (field mode vector) / Displace, not viewed directly.
gotcha
width is the dipole half-separation in canvas-width units either side of the curve normal; softness scales the regularization epsilon (eps = max(0.001, softness*width)), not the width itself.
gotcha
strength is normalized so a long straight run advects at ≈ strength regardless of sample count or mode (gain = strength/2π for along, strength/π for orbit).
gotcha
mode=orbit drops the dipole pairing for single same-sign vortices, circulating around the whole stroke instead of flowing along it.
gotcha
field input is decoded, summed with this curve's contribution, then re-encoded, so chaining fields composes additively rather than overwriting.
gotcha
samples (arc-length-uniform along the concatenated subpaths) is clamped to 8..96; with no spline and no field input the output is cleared to the neutral (zero-velocity) encoding.
Parameters
Name
Type
Default
Mode
mode
enum
"along"
along · orbit
Strength
strength
scalar
-2 … 2
0.5
Width
width
scalar
0.005 … 0.5
0.06
Softness
softness
scalar
0.05 … 2
0.5
Samples
samples
scalar
8 … 96
48
Sockets
Direction
Name
Type
input*
spline
spline
spline
input
Field (add)
field
image
outputprimary
out
image
Spline Interpolate
spline-interpolatepolymorphic
Interpolate between 2 or more splines. Feed shapes into the auto-growing sockets (there's always one spare) and get a single spline containing the inputs plus a set number of interpolated in-betweens, spread evenly across the whole sequence. Shapes are auto-aligned by orientation and start vertex; each output subpath is groupIndex-tagged in order for Select by Index / Copy-to-Points. Wire a spline into the Spine socket to distribute the whole family along it (the Illustrator Blend-along-a-path move), optionally rotated to the spine's tangent.
writes
attr:group
flags
no-mask
gotcha
count is the total number of in-betweens spread across the whole chain (not per gap), placed strictly between knots so none coincides with an input shape.
gotcha
Per-pair morph correspondence (resample to resolution + orientation/start-vertex alignment) is cached by input identity and resolution; sweeping count alone is cheap.
gotcha
attr:group tags every output subpath in chain order (input, its in-betweens, next input, ...) for Select by Index / Count Indices / Copy-to-Points.
gotcha
Wiring Spine moves the whole family to even arc-length stations along it and, if spine_align is on, rotates each member to the spine's tangent there.
gotcha
With exactly one input shape it passes through untouched as group 0 and count has no effect.
gotcha
spine is a fixed socket excluded from the auto-growing numbered slots, unlike the spline inputs which always keep one trailing empty spare.
Parameters
Name
Type
Default
In-betweens
count
scalar
0 … 200
5
Resolution
resolution
scalar
3 … 256
64
Align to spine
spine_align
boolean
true
Sockets
Direction
Name
Type
input
Spline
in
spline
input
Spine
spine
spline
outputprimary
out
spline
Spline Merge
spline-merge
Merge all subpaths of one spline into combined region(s). Union collapses overlapping shapes into a single silhouette (one clean outer stroke when rasterized); also intersect and exclude (even-odd XOR). Flow turns the merge into a liquid simulation — the silhouette flows toward the new shape, bridging and snapping as parts meet and stretching before it pinches off as they part. Outputs a spline — wire into Rasterize Spline to draw. Note: union treats each subpath as solid, so holes inside a single shape fill in.
space
param:blend=canvas01
gotcha
resolution (curve flattening steps) only applies in exact mode (flow off); Flow rasterizes true beziers and ignores it.
gotcha
flow_speed is a 0..1 dial mapped onto a much smaller actual relaxation rate (dial 1.0 → 0.001), so the useful slow range spans the whole slider.
gotcha
blend is the goo/bridge radius as a canvas-width-relative distance, converted internally to field texels via detail's grid scale.
gotcha
detail sets the flow field's grid resolution (texels across the canvas's longer side, 128..768); higher is sharper but heavier per tick.
gotcha
Exact mode caches its result and only recomputes when geometry, operation, or resolution change; Flow instead advances every tick via fingerprintExtras.
Parameters
Name
Type
Default
Operation
operation
enum
"union"
union · intersect · exclude
Curve resolution
resolution
scalar
3 … 96
24
Flow
flow
boolean
true
Flow Speed
flow_speed
scalar
0 … 1
0.3
Surface Tension
tension
scalar
0 … 1
0.35
Adhesion
adhesion
scalar
0 … 1
0.5
Blend
blend
scalar
0 … 0.25
0.06
Detail
detail
scalar
128 … 768
320
Sockets
Direction
Name
Type
input*
path
path
spline
outputprimary
out
spline
Spline Morph
spline-morphpolymorphic
Morph (tween) between two or more splines by Amount (0 = first, 1 = last). Extra spline sockets auto-grow; Amount is split evenly across the chain (3 shapes → 0 / 0.5 / 1). Shapes are auto-aligned by orientation and start vertex; surplus subpaths grow/shrink from a point. Outputs a spline, plus an image when stroke or fill is on.
space
param:stroke_thickness=pixels
gotcha
Amount is split evenly across the whole wired chain (3 shapes → 0/0.5/1, not 0..1 per adjacent pair); a wired amount scalar input overrides the Amount param.
gotcha
Correspondence (resample to resolution + orientation/start-vertex alignment) rebuilds only when the shape set or resolution changes, not when Amount sweeps.
gotcha
The image aux output only exists when stroke_enabled or fill_enabled is on; with both off only the spline output is produced.
gotcha
stroke_thickness is pixels at render resolution, unlike the canvas01 spline geometry it strokes.
gotcha
With zero or one wired shape the result passes through unchanged (or is empty) and Amount has no effect.
Parameters
Name
Type
Default
Opacity
opacity
scalar
0 … 1
1
Amount
amount
scalar
0 … 1
0.5
Resolution
resolution
scalar
3 … 256
64
Stroke
stroke_enabled
boolean
false
Thickness (px)
stroke_thickness
scalar
0 … 200
4
Stroke color
stroke_color
color
"#ffffff"
Fill
fill_enabled
boolean
true
Fill color
fill_color
color
"#ffffff"
Sockets
Direction
Name
Type
input
A
a
spline
input
B
b
spline
input
Amount
amount
scalar
outputprimary
out
spline
outputaux
image
image
Spline Pack
spline-pack
Greedy collision-packing of spline fragments so no two overlap. The staggered brickwork, mixed widths, and gaps all emerge from collision-cutting — no dashes, no offset curves. Outputs a spline — wire into Stroke / Rasterize Spline to draw. Stroke recipe: thickness = Max Width (same units), cap: butt, thickness_source: vary, thickness_by: driver, thickness_lo: 0, thickness_hi: 1. Rendered width then equals each piece's packed width. Count reveals pieces in pack order (−1 = all).
min_width/max_width/gap/min_length/spacing default to raw pixels; units=% resolves them against canvas width instead (same convention as Stroke).
gotcha
Each piece stamps attr:group = source subpath index, attr:driver = width/max_width in 0..1 (for Stroke's thickness_by=driver), and attr:width = its own packed pixel width.
gotcha
randomize_width off targets every piece at max_width before collision-shrinking, so seed only has an effect when it's on.
gotcha
A piece shrunk below min_width by collisions, or shorter than min_length after cutting, is discarded rather than kept undersized.
gotcha
count only reslices the already-packed order (−1 = all); it never triggers a repack, so scrubbing it is instant.
Parameters
Name
Type
Default
Min Width
min_width
scalar
0.5 … 500
4
Max Width
max_width
scalar
0.5 … 500
24
Randomize Width
randomize_width
boolean
true
Gap
gap
scalar
0 … 100
0
Min Length
min_length
scalar
0 … 2000
20
Sample spacing
spacing
scalar
0.5 … 20
2
Units
units
enum
"px"
px · %
Seed
seed
scalar
0 … 9999
0
Count
count
scalar
-1 … 2000
-1
Sockets
Direction
Name
Type
input*
path
path
spline
outputprimary
out
spline
Stroke
spline-stroke
Render a spline as a stroked outline. Three styles: solid (continuous), dashed (alternating dashes and gaps), dotted (round dots at a fixed spacing). Color source can be a flat color or a ramp keyed by index, seeded random, group, centroid position, a per-subpath driver, along-path progress, or an interpolated named anchor channel — the same sourcing Rasterize Spline's stroke has. Thickness source varies each subpath's width the same way (a lo→hi multiplier on the base thickness) — with Copy to Points' 'Tag copies: copy index', every copy gets its own stroke weight. Repeats draws N parallel-offset rings — inner/outer/both, a band width, and a spacing curve placing each ring within the band (closed shapes always expand outward regardless of draw direction; for open paths inner/outer mean left/right of travel). Per-ring styling: thickness and opacity falloff curves, plus an optional color ramp across the rings (which takes precedence over the per-subpath color source).
thickness/dash_length/dash_gap/dot_spacing default to raw pixels; units=% resolves them as a percent of canvas width instead, so the look holds across resolutions.
gotcha
repeat_width is a canvas-width-relative band fraction regardless of the units toggle; repeat_spacing places each ring within that band (0 = on the source path).
gotcha
With repeats>1, repeat_color_mode=ramp overrides the per-subpath color_source ramp; ramp_by (index/random/group/position/driver/progress/attribute) drives both.
gotcha
A per-anchor width profile (e.g. from Taper Spline) renders as a filled variable-width envelope instead of a stroked line, but only when repeats=1; repeats>1 strokes it at uniform ring width.
gotcha
thickness_source=vary forces per-subpath stroking even with a flat color, since Canvas lineWidth is one value per stroke() call.
gotcha
ramp_by=group or thickness_by=group reads each subpath's stamped groupIndex (attr:group), e.g. from String Art's layers or Copy to Points' copy tagging.
Scale each anchor's offset from a pivot by a curve over path progress — taper, flare, or envelope a path without an expression. Pivot is the subpath centroid (default), the first/last anchor, or a custom canvas point. Axis scales x, y, or both. The curve is sampled at arc-length t ∈ [0,1] along each subpath, then mapped through Out Lo → Out Hi (defaults 1 → 0: full size at the start, a point at the end). Handles scale with the same factor. Per-subpath, unlike Modulate Splines (one scale per whole subpath) or Trim Path (arc-length window).
space
param:pivotX,param:pivotY=canvas01
gotcha
Curve t is arc-length progress per subpath (measureSubpath cumulative distance), not anchor index, so unevenly-spaced anchors still taper smoothly.
gotcha
The curve output is remapped through out_lo (t=0) to out_hi (t=1) as a scale multiplier on each anchor's offset from the pivot; defaults 1 → 0 shrink the path to a point at its end.
gotcha
axis=x or axis=y locks the other axis's scale to 1 instead of skipping it, so an anchor can still move if its handle is asymmetric.
gotcha
Handles are scaled by the same factor as their anchor, so a heavily tapered cubic can invert if out_lo/out_hi go negative-adjacent (values beyond the 0..4 range aren't clamped in compute).
Reveal only a portion of a path by arc length. Combined mode measures start/end across all subpaths (draw-on of a whole compound path); Per subpath trims each path in its own 0–1 domain. In Per subpath, start / end / offset can each come from the slider, a named subpath attribute, or index / random / group / position / driver. Offset slides the window along the path, wrapping past the ends (unbounded — keyframe it to orbit a loop). Outputs a spline — view it with Stroke or Rasterize Spline.
gotcha
mode=combined (default) treats all subpaths as one concatenated length domain; mode=per subpath gives each subpath its own 0-1 domain instead.
gotcha
In per subpath mode, start/end/offset each independently pull from the slider (value), a named subpath attribute, or the shared driver keys index/random/group/position/driver.
gotcha
trim_start/trim_end/trim_offset are arc-length fractions (0-1) of the measured path, not canvas or pixel units.
gotcha
offset is unbounded and taken mod 1, sliding the window cyclically; a window that straddles the wrap seam on a closed subpath is stitched into one continuous piece.
Parameters
Name
Type
Default
Mode
mode
enum
"combined"
combined · per subpath
Trim start
trim_start
scalar
0 … 1
0
Trim end
trim_end
scalar
0 … 1
1
Trim offset
trim_offset
scalar
-1 … 1
0
Start source
start_source
enum
"value"
value · attribute · index · random · group · position · driver
Start attribute
start_attr
string
""
End source
end_source
enum
"value"
value · attribute · index · random · group · position · driver
End attribute
end_attr
string
""
Offset source
offset_source
enum
"value"
value · attribute · index · random · group · position · driver
Offset attribute
offset_attr
string
""
Seed
source_seed
scalar
0 … 9999
0
Gradient angle
source_angle
scalar
-180 … 180
0
Sockets
Direction
Name
Type
input*
path
path
spline
outputprimary
out
spline
Utilities
Bezier Handles
bezier-handles
Visualize a spline's bezier control structure like a pen-tool editor view: the path (optional), the tangent handle lines, anchor dots, and handle-end dots — each independently styled (color/thickness/dashed·dotted for lines; radius/fill/stroke for dots). Outputs both an image (primary) and a spline (aux: the handle lines + dots as vectors, group-tagged 0=lines, 1=anchors, 2=handles).
Every px-labeled param (widths, radii, dash/gap lengths) is a raster pixel at render resolution and does not scale with output size.
gotcha
A handle offset counts as present only when nonzero; zero-length handles (corner points, straight segments) draw no line and no handle dot.
gotcha
The aux spline's dots use radius = px_radius / W, so a downstream rasterizer's aspect correction reproduces the same round pixel radius drawn here.
gotcha
Dashed style is [dash, gap] with round caps; dotted is [0, dot_gap] with round caps so the zero-length dash renders as a dot, same as Rasterize Spline.
Parameters
Name
Type
Default
Show path
show_path
boolean
true
Path color
path_color
color
"#8a8a8a"
Path width (px)
path_width
scalar
0 … 50
1.5
Show handle lines
show_handles
boolean
true
Handle color
handle_color
color
"#4a90ff"
Handle width (px)
handle_width
scalar
0 … 50
1
Handle style
handle_style
enum
"solid"
solid · dashed · dotted
Dash length (px)
handle_dash
scalar
0.5 … 200
6
Dash gap (px)
handle_gap
scalar
0.5 … 200
4
Dot spacing (px)
handle_dot_gap
scalar
1 … 200
6
Show anchor dots
show_anchor_dots
boolean
true
Anchor radius (px)
anchor_radius
scalar
0 … 50
4
Anchor fill
anchor_fill
boolean
true
Anchor fill color
anchor_fill_color
color
"#ffffff"
Anchor stroke
anchor_stroke
boolean
true
Anchor stroke color
anchor_stroke_color
color
"#4a90ff"
Anchor stroke (px)
anchor_stroke_width
scalar
0 … 50
1.5
Anchor stroke style
anchor_stroke_style
enum
"solid"
solid · dashed · dotted
Dash length (px)
anchor_stroke_dash
scalar
0.5 … 200
6
Dash gap (px)
anchor_stroke_gap
scalar
0.5 … 200
4
Dot spacing (px)
anchor_stroke_dot_gap
scalar
1 … 200
6
Show handle dots
show_handle_dots
boolean
true
Handle dot radius (px)
cp_radius
scalar
0 … 50
3
Handle dot fill
cp_fill
boolean
true
Handle dot fill color
cp_fill_color
color
"#4a90ff"
Handle dot stroke
cp_stroke
boolean
false
Handle dot stroke color
cp_stroke_color
color
"#ffffff"
Handle dot stroke (px)
cp_stroke_width
scalar
0 … 50
1
Handle dot stroke style
cp_stroke_style
enum
"solid"
solid · dashed · dotted
Dash length (px)
cp_stroke_dash
scalar
0.5 … 200
6
Dash gap (px)
cp_stroke_gap
scalar
0.5 … 200
4
Dot spacing (px)
cp_stroke_dot_gap
scalar
1 … 200
6
Opacity
opacity
scalar
0 … 1
1
Sockets
Direction
Name
Type
input*
path
path
spline
outputprimary
out
image
outputaux
spline
spline
SVG Export
svg-export
Save the wired spline as a standalone .svg file, snapshotted at the current playhead. Styling (stroke width/color, fill color and rule) is set here — the exported path carries it. The spline passes through unchanged, so the node can sit inline in a chain.
space
param:stroke_width=pixels
flags
terminal
gotcha
Passthrough: outputs the wired spline unchanged, so the node can sit inline in a chain while capturing an export snapshot as a side effect.
gotcha
The snapshot is taken at the current playhead and at ctx.width/height, so scrubbing or resizing the canvas before hitting Export SVG changes what gets saved.
gotcha
stroke_width is in pixels at the export's raster resolution (SVG viewBox 0 0 W H), not canvas01 or UV fractions.
gotcha
All subpaths are joined into one <path> element so fill_rule=evenodd holes work across subpaths, matching the app's own fill rasterization.