Skip to content

Paths & Parametric Positioning

This is PyFreeform's "killer feature" — position any entity at any point along any path using the along / t system.

The Core Idea

Every Line, Curve, and Ellipse is a Pathable — it has a point_at(t) method where t goes from 0.0 (start) to 1.0 (end). You can place entities along these paths:

line = cell.add_diagonal()
cell.add_dot(along=line, t=cell.brightness)  # (1)!
  1. The dot's position slides along the diagonal based on brightness.

Dots along diagonals

Each dot slides along its cell's diagonal — bright areas push dots toward the top-right.

Along Curves

Curves make the positioning non-linear and organic:

curve = cell.add_curve(start="bottom_left", end="top_right", curvature=0.6, ...)
for t_val in [0.25, 0.5, 0.75]:
    cell.add_dot(along=curve, t=t_val, radius=0.10, color=colors.primary)

Dots along curves

Three dots per cell positioned along curves — the Bezier shape creates smooth distribution.

Along Ellipses

Ellipses are closed paths — t=0 is the rightmost point, going counterclockwise:

t value Position
0.0 Right
0.25 Top
0.5 Left
0.75 Bottom
ellipse = cell.add_ellipse(at="center", rx=0.4, ry=0.25, fill="none", stroke=colors.line)
cell.add_dot(along=ellipse, t=ny, radius=0.10, color=colors.accent)

Dots orbiting ellipses

Each dot orbits its cell's ellipse at a position driven by the row.

Understanding t Values

On a single path, t selects the position:

t values visualized

Five dots at t=0.00, 0.25, 0.50, 0.75, and 1.00 along a curve.

Built-in Path Shapes

PyFreeform includes four ready-to-use path shapes, accessible as Path.Wave, Path.Spiral, Path.Lissajous, and Path.Zigzag:

from pyfreeform import Path

wave = Path.Wave(start=(cx - 10, cy), end=(cx + 10, cy), amplitude=8, frequency=3)
cell.add_path(wave, segments=32, width=1.5, color=colors.primary)

All four work with add_path(), along=/t= positioning, and as connection shapes. See the API Reference for full parameter details.

Filled Closed Paths

Use closed=True and fill= to create filled shapes from any path. Layering semi-transparent fills produces striking geometric art:

liss = Path.Lissajous(center=(200, 200), a=3, b=2, size=150)
path = Path(liss, closed=True, fill="#4a90d9", color="#6ab0ff",
            width=1.2, fill_opacity=0.25, stroke_opacity=0.7, segments=128)
scene.place(path)

Filled Lissajous curves

Three Lissajous curves with different frequency ratios, layered with semi-transparent fills.

Custom Pathables

You can also create your own — any object with point_at(t) -> Coord works as a path:

from pyfreeform import Coord

class MyPath:
    def point_at(self, t):
        x = t * 100
        y = 50 + 20 * math.sin(t * math.pi * 4)
        return Coord(x, y)

Custom wave paths

Wave paths with increasing frequency across the grid.

Sub-Paths and Arcs

Use start_t and end_t to render only a portion of a path:

cell.add_path(
    ellipse,
    start_t=0.1,      # Start at 10% around
    end_t=0.6,         # End at 60% around
    segments=24,
    width=2,
    color=colors.primary,
)

Arcs from ellipses

Partial arcs of ellipses — the start and length vary by position.

Text Along Paths

Pass a path to add_text(along=) without t to warp text along the full path:

curve = cell.add_curve(start=(0.05, 0.7), end=(0.95, 0.3), curvature=0.5, ...)
cell.add_text("Text flows along any path", along=curve, font_size=0.05, color=colors.accent)

Text along curve

Text automatically warped along a Bezier curve using SVG <textPath>.

Text along ellipse


Alignment

Set align=True to rotate entities to follow the path's tangent direction:

cell.add_polygon(
    Polygon.triangle(size=0.06),
    along=curve, t=0.5, align=True,  # (1)!
    fill=colors.primary,
)
  1. The triangle points in the direction the curve is heading at t=0.5.

Aligned triangles along curve

Triangles aligned to the curve's tangent — they point the way the path flows.

What's Next?

Explore the shape system and learn to compose reusable groups:

Shapes & Polygons →